Skip to content

feat(onboard): add configurable port overrides via environment variables#621

Closed
jnun wants to merge 15 commits intoNVIDIA:mainfrom
jnun:feat/configurable-ports
Closed

feat(onboard): add configurable port overrides via environment variables#621
jnun wants to merge 15 commits intoNVIDIA:mainfrom
jnun:feat/configurable-ports

Conversation

@jnun
Copy link
Copy Markdown
Contributor

@jnun jnun commented Mar 22, 2026

Summary

Add environment-variable-based port configuration so users on shared machines or non-standard environments can override the default gateway, NIM, and service ports without editing source files. This also fixes several onboarding bugs uncovered during implementation: stale .env loading on fresh installs, spurious port-conflict warnings, command injection in CLI entry points, and missing env bootstrap in shell scripts.

Related Issue

Supersedes #598 (closed).

Changes

  • bin/lib/ports.js (new) — single source of truth for all port constants, loaded from env vars with validated defaults.
  • bin/lib/env.js (new) — .env / .env.local loader that searches both git root and CWD, with fallback behavior for fresh installs.
  • bin/lib/onboard.js — port-conflict detection now validates against configured ports; shows override guidance when a conflict is found; env loaded at startup.
  • bin/lib/local-inference.js, bin/lib/nim.js, bin/lib/preflight.js, bin/nemoclaw.js — consume ports from ports.js instead of hardcoded values.
  • nemoclaw-blueprint/blueprint.yaml + orchestrator/runner.py — propagate port env vars into the blueprint/sandbox.
  • Shell scripts (scripts/brev-setup.sh, check-ports.sh (new), debug.sh, nemoclaw-start.sh, setup.sh, start-services.sh, lib/runtime.sh, uninstall.sh) — source .env before using ports; new check-ports.sh helper.
  • .env.example — documents all overridable port variables.
  • docs/reference/port-configuration.md (new) — user-facing doc for port overrides.
  • docs/reference/troubleshooting.md — updated with port-conflict resolution steps.
  • scripts/telegram-bridge.js — removed unused crypto import.
  • bin/lib/onboard.js + CLI entry points — fixed command-injection vulnerability (fix(security): command injection across all CLI entry points #584).
  • Tests — new test/env.test.js; updated onboard-readiness, onboard-selection, local-inference, preflight, runtime-shell, and e2e tests to use configurable ports.

Type of Change

  • Code change with doc updates.

Testing

  • make check passes.
  • npm test passes.

Checklist

General

Code Changes

  • make format applied (TypeScript and Python).
  • Tests added or updated for new or changed behavior.
  • No secrets, API keys, or credentials committed.
  • Doc pages updated for any user-facing behavior changes (new commands, changed defaults, new features, bug fixes that contradict existing docs).

Doc Changes

  • Follows the style guide.
  • New pages include SPDX license header and frontmatter, if creating a new page.
  • Cross-references and links verified.

Summary by CodeRabbit

Release Notes

  • New Features

    • Configurable network ports for Dashboard, Gateway, vLLM/NIM, and Ollama services via .env files or environment variables
    • Port conflict detection script to validate configurations before onboarding
  • Documentation

    • Added comprehensive port configuration guide with setup instructions and updated troubleshooting
  • Tests

    • Expanded test coverage for environment and port configuration handling

jnun and others added 15 commits March 21, 2026 14:10
Replace hardcoded ports (8000, 8080, 11434, 18789) with env-var-driven
configuration via NEMOCLAW_*_PORT variables and .env file support.

- Add central port config module (ports.js), .env loader (env.js),
  and port conflict diagnostics (check-ports.sh)
- Update all Node.js modules, shell scripts, blueprint runner, and
  tests to use configurable ports instead of hardcoded values
- Add port configuration reference docs and README section
Align port-configuration.md H1 with title.page frontmatter value.
Apply ruff auto-format to runner.py.
Remove empty line for stylecheck.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
)

* security: fix command injection across all CLI entry points

Comprehensive fix for shell injection vulnerabilities where user input
(instance names, sandbox names, model names, API keys) was interpolated
unsanitized into shell commands via run()/runInteractive()/execSync().

Changes:
- Add shellQuote() and validateName() to runner.js as shared utilities
- Replace all execSync() with execFileSync() in deploy (no shell)
- Apply shellQuote() to every user-controlled variable in shell commands
  across nemoclaw.js, onboard.js, nim.js, policies.js
- Add RFC 1123 name validation at CLI dispatch for sandbox/instance names
- Fix path traversal in policies.js loadPreset()
- Replace predictable temp file with mkdtempSync()
- Remove duplicate shellQuote() definitions (now single source in runner.js)
- 9 new test cases for shellQuote, validateName, and path traversal

Supersedes NVIDIA#55, NVIDIA#110, NVIDIA#475, NVIDIA#540, NVIDIA#48, NVIDIA#171.

* fix: deduplicate shellQuote in local-inference.js

Import shellQuote from runner.js instead of defining a local copy.
Single source of truth for shell quoting across the codebase.

* security: fix telegram bridge injection + add regression guards

Telegram bridge:
- Replace execSync with execFileSync for ssh-config retrieval
- shellQuote message, API key, and session ID in remote command
- Validate SANDBOX_NAME at startup
- Use mkdtempSync for temp SSH config (not predictable path)

Regression tests:
- nemoclaw.js must not use execSync
- Single shellQuote definition in bin/
- CLI rejects malicious sandbox names (e2e, no mocking)
- telegram-bridge.js validates SANDBOX_NAME and avoids execSync

* security: address CodeRabbit review findings on shell injection PR

- Shell-quote secret values written to .env before remote source
- Wrap scp upload in try/finally to guarantee temp secret cleanup
- Shell-quote CHAT_UI_URL and NVIDIA_API_KEY env args in onboard
- Replace predictable Date.now() temp path with mkdtempSync in policies
- Strengthen e2e test with canary file to prove payload never executes
- Fix merge-introduced test expectations for shellQuote single-quote format
* docs: add community feedback invitation for policy presets

* docs: link baseline policy reference to the YAML file on GitHub

* docs: apply suggestion from @naderkhalil

---------

Co-authored-by: Carlos Villela <cvillela@nvidia.com>
* docs: rename strict policy tier to default

* docs: remove remaining "strict" language from docs and comments
Add missing docstrings to runner.py (log, progress, emit_run_id,
load_blueprint, main) to meet the 80% coverage threshold. Normalize
README blockquote headings to match existing emoji + bold style.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oad env in uninstall

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The env loader only searched relative to __dirname, which resolves to
the sandbox source directory where gitignored .env.local is absent.
Now also checks the git repo root and CWD so user overrides are found
without manually copying .env.local into .nemoclaw/source/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On a fresh clone neither .env nor .env.local exist (both gitignored),
so ports.js falls back to hardcoded defaults. If the default port is
occupied the preflight error gave no hint about .env.local overrides.

- Copy .env.example → .env during preflight when .env is missing
- Add .env.local override instructions to the port conflict message

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- scripts/nemoclaw-start.sh, scripts/debug.sh: add .env/.env.local
  loading before port vars are used
- scripts/brev-setup.sh: add missing .env.local loading
- bin/lib/env.js: export parseEnvFile and findGitRoot for testing
- README.md: document .env.local as the override mechanism
- test/env.test.js: add tests for env parser, findGitRoot, and
  first-write-wins semantics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded 8080 with the GATEWAY_PORT constant and load
env/ports modules so stale-gateway tests respect port overrides.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

The changes introduce configurable network port support by adding an environment variable loader, centralizing port constants, and replacing hardcoded port values across Node.js modules, shell scripts, and Python orchestration code with environment-driven configuration.

Changes

Cohort / File(s) Summary
Environment Configuration
.env.example, .gitignore
Added environment variable template with default port values and updated gitignore to exclude .env files and local artifacts.
Core Environment & Port Modules
bin/lib/env.js, bin/lib/ports.js
Introduced .env file parser that reads from multiple locations and centralizes port constant exports with validation and fallback chains.
CLI Entrypoints
bin/nemoclaw.js, bin/lib/onboard.js, bin/lib/preflight.js
Updated CLI to load env files early, parameterized port handling in onboarding preflight (including .env bootstrapping), sandbox creation, and gateway startup.
Service & Inference Modules
bin/lib/nim.js, bin/lib/local-inference.js
Replaced hardcoded NIM and Ollama/vLLM ports with configurable constants via imported port module.
Python Orchestration
nemoclaw-blueprint/orchestrator/runner.py, nemoclaw-blueprint/blueprint.yaml
Added port override logic to parse environment variables and mutate blueprint endpoints at load time; updated comments documenting runtime overrides.
Shell Scripts (Startup & Service Management)
scripts/brev-setup.sh, scripts/setup.sh, scripts/start-services.sh, scripts/nemoclaw-start.sh
Updated service startup scripts to load .env files and use environment-driven port variables instead of hardcoded values.
Shell Scripts (Diagnostics & Cleanup)
scripts/debug.sh, scripts/check-ports.sh, uninstall.sh, scripts/lib/runtime.sh
Updated diagnostic and cleanup scripts to load env configuration and use configurable ports; added new port-conflict detection script with validation and liveness checks.
Documentation
README.md, docs/reference/port-configuration.md, docs/reference/troubleshooting.md
Added comprehensive port configuration documentation, usage instructions (including .env workflow), and updated troubleshooting guide with multi-port conflict resolution.
Test Suite
test/env.test.js, test/local-inference.test.js, test/onboard-readiness.test.js, test/onboard-selection.test.js, test/preflight.test.js, test/runtime-shell.test.js
Added test coverage for env parsing and port resolution; updated existing tests to use configurable port constants instead of hardcoded values.
Minor Cleanup
scripts/telegram-bridge.js, test/e2e/test-double-onboard.sh
Removed unused import and parameterized e2e test port assertions using environment variables.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as nemoclaw CLI
    participant EnvLoader as env.js
    participant PortModule as ports.js
    participant Preflight as onboard.js<br/>(preflight)
    participant Gateway as Gateway Service
    participant Dashboard as Dashboard<br/>(Port: DASHBOARD_PORT)
    participant NIM as NIM/vLLM<br/>(Port: VLLM_PORT)
    participant Ollama as Ollama<br/>(Port: OLLAMA_PORT)

    User->>CLI: nemoclaw onboard
    CLI->>EnvLoader: require('./lib/env')
    EnvLoader->>EnvLoader: scan .git root,<br/>load .env & .env.local
    EnvLoader->>EnvLoader: populate process.env<br/>(non-overwrite)
    
    CLI->>PortModule: import DASHBOARD_PORT,<br/>GATEWAY_PORT, VLLM_PORT,<br/>OLLAMA_PORT
    PortModule->>PortModule: parse env vars<br/>with fallbacks
    PortModule-->>CLI: return port constants

    CLI->>Preflight: preflight() check
    Preflight->>Preflight: validate all 4 ports<br/>available via lsof
    Preflight->>Preflight: bootstrap .env.example<br/>→ .env on first run
    alt Port conflict
        Preflight-->>User: error + env var guidance
    else Ports OK
        Preflight-->>CLI: pass
    end

    CLI->>Gateway: startGateway(GATEWAY_PORT)
    Gateway-->>Gateway: openshell gateway start<br/>--port GATEWAY_PORT

    CLI->>Dashboard: sandbox forwarding<br/>to DASHBOARD_PORT
    Dashboard-->>Dashboard: listen on<br/>DASHBOARD_PORT

    CLI->>NIM: local inference setup<br/>(VLLM_PORT)
    NIM-->>NIM: health check<br/>localhost:VLLM_PORT

    CLI->>Ollama: Ollama service<br/>(OLLAMA_PORT)
    Ollama-->>Ollama: bind 0.0.0.0:OLLAMA_PORT
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Port configurations bloom with grace,
Environment variables find their place,
No more hardcoding—freedom's song,
Services dance where they belong.
Flexibility hops through every line,
The system now runs by design!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding configurable port overrides via environment variables, which is the primary feature across this comprehensive PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch feat/configurable-ports
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
uninstall.sh (1)

30-49: Loading order differs from other scripts but achieves the same result.

This custom _load_env function loads .env.local before .env but uses first-wins semantics (line 43: only set if not already defined). Other scripts in this PR use the opposite file order with last-wins semantics (set -a && . .env && . .env.local).

Both approaches give .env.local precedence, but the inconsistent patterns could confuse future maintainers. Consider adding a comment explaining the first-wins behavior:

📝 Suggested comment
+# Load .env.local first; first-wins semantics means it takes precedence over .env
 _load_env "$SCRIPT_DIR/.env.local"
 _load_env "$SCRIPT_DIR/.env"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@uninstall.sh` around lines 30 - 49, The _load_env function loads
$SCRIPT_DIR/.env.local before $SCRIPT_DIR/.env and uses first-wins semantics (it
only exports a variable if it's not already set), which differs from the other
scripts that source .env then .env.local with last-wins behavior; add a brief
inline comment above the _load_env definition explaining the load order and that
first-wins is intentional (i.e., .env.local takes precedence here) and note the
difference vs the other scripts so future maintainers understand the behavior
and why it differs.
docs/reference/troubleshooting.md (1)

93-115: Minor style suggestions.

  1. Lines 96, 103: Colons are used before code blocks. The style guide states colons should only introduce lists. Consider rephrasing to avoid colons before code examples:

    • Line 96: "To resolve, either stop the conflicting process." (remove colon)
    • Line 103: "Or set the corresponding environment variable before onboarding." (remove colon)
  2. Line 103: Missing period at the end of the sentence before the code block.

📝 Suggested changes
-To resolve, either stop the conflicting process:
+To resolve, either stop the conflicting process.
-Or use a different port by setting the corresponding environment variable before onboarding:
+Or set the corresponding environment variable before onboarding.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/troubleshooting.md` around lines 93 - 115, Update the two
sentences that introduce code blocks so they follow the style guide: change "To
resolve, either stop the conflicting process:" to "To resolve, either stop the
conflicting process." and change "Or use a different port by setting the
corresponding environment variable before onboarding:" to "Or use a different
port by setting the corresponding environment variable before onboarding." also
ensure the second sentence ends with a period; these lines are the ones
immediately before the lsof/kill example and before the NEMOCLAW_GATEWAY_PORT
export + nemoclaw onboard example (referenced ENV var NEMOCLAW_GATEWAY_PORT and
the nemoclaw onboard command) so you can locate and edit them accordingly.
scripts/setup.sh (1)

35-37: Consider adding shellcheck directives for consistency.

Other scripts in this PR (e.g., brev-setup.sh, nemoclaw-start.sh) include # shellcheck source=/dev/null before sourcing .env files. Adding the same here would maintain consistency and suppress potential shellcheck warnings.

♻️ Suggested change
 # Load port overrides if present (.env.local takes precedence)
+# shellcheck source=/dev/null
 [ -f "${REPO_DIR}/.env" ] && set -a && . "${REPO_DIR}/.env" && set +a
+# shellcheck source=/dev/null
 [ -f "${REPO_DIR}/.env.local" ] && set -a && . "${REPO_DIR}/.env.local" && set +a
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/setup.sh` around lines 35 - 37, Add the shellcheck source suppression
comment before the lines that source environment files: insert "# shellcheck
source=/dev/null" immediately above the two lines that source ".env" and
".env.local" (the lines starting with "[ -f \"${REPO_DIR}/.env\" ] && set -a &&
. \"${REPO_DIR}/.env\" && set +a" and the similar ".env.local" line) so
shellcheck warnings are suppressed and the script matches the other scripts'
pattern.
bin/lib/ports.js (1)

19-24: Consider clarifying error messages for fallback chain failures.

If an intermediate fallback variable (e.g., DASHBOARD_PORT or PUBLIC_PORT) contains an invalid value, the error will reference that variable name, which might confuse users who only set NEMOCLAW_DASHBOARD_PORT. This is a minor edge case since users typically only set one of these variables.

The implementation is otherwise correct and the fallback chain matches the documented behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/ports.js` around lines 19 - 24, The parsePort fallback chain may
throw errors that only name the intermediate env var (e.g., DASHBOARD_PORT or
PUBLIC_PORT), which can confuse users who set NEMOCLAW_DASHBOARD_PORT; fix by
surfacing the full fallback context when an error occurs: update the parsePort
function to accept/propagate an optional context or chain label (or catch and
rethrow errors) so errors include the original requested variable and the
fallback chain (e.g., "NEMOCLAW_DASHBOARD_PORT (fallbacks: DASHBOARD_PORT →
PUBLIC_PORT → 18789)"). Adjust the call that assigns DASHBOARD_PORT (the
parsePort invocation that uses "NEMOCLAW_DASHBOARD_PORT" and nested parsePort
calls) so it passes/propagates the chain information into parsePort so thrown
errors reference the full chain instead of only the intermediate variable name.
scripts/check-ports.sh (1)

105-111: Consider validating custom port arguments.

Custom ports passed via CLI arguments are not validated for integer format or port range before being passed to lsof. While lsof will handle invalid input gracefully, adding validation would provide clearer error messages.

♻️ Optional: Validate custom ports
 if [[ $# -gt 0 ]]; then
   echo ""
   echo "Custom ports:"
   for p in "$@"; do
+    validate_port "custom" "$p"
     check_port "$p" || true
   done
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/check-ports.sh` around lines 105 - 111, Validate each custom port
argument before calling check_port: in the for loop that iterates "$@", ensure
the value matches a numeric regex (e.g. only digits) and that it falls within
the valid port range 1–65535; if a value fails, print a clear error like
"Invalid port: <value> (must be integer 1-65535)" and skip or exit with non-zero
as appropriate, then only call check_port "<port>" for validated ports (this
prevents passing malformed values to lsof and gives clearer messages).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/env.js`:
- Around line 57-70: The inline-comment logic currently removes " #..." before
stripping quotes, which truncates quoted values like KEY="value # inside";
change the order and condition so comments are stripped only when the raw value
is not quoted: first detect if value starts and ends with matching quotes
(value.startsWith('"') && value.endsWith('"') or same for single quotes) and
when it is quoted, skip the hash-trimming and only remove surrounding quotes;
when it is not quoted, perform the existing hashIndex check (value.indexOf("
#")) and trim the comment, then trim whitespace. Locate the code handling the
variable named value in bin/lib/env.js to implement this conditional flow.

In `@README.md`:
- Around line 188-192: Add a single blank line after the blockquote that starts
with "**⚠️ Network exposure**" and before the horizontal rule ("---") to satisfy
the markdown linter (MD028); edit the README block containing that blockquote
header and ensure there is an empty line between the end of the quoted paragraph
and the "---" separator.

In `@scripts/debug.sh`:
- Line 294: The lsof diagnostic call only uses NEMOCLAW_DASHBOARD_PORT (or
18789) and misses other env fallbacks; update the collect invocation that runs
lsof -i to use the same fallback chain as elsewhere (check DASHBOARD_PORT, then
PUBLIC_PORT, then NEMOCLAW_DASHBOARD_PORT, then default 18789) so it inspects
the effective dashboard port; modify the variable expansion in the collect
"lsof-dashboard" lsof -i :"${...}" invocation accordingly (refer to the collect
call and the environment variable names DASHBOARD_PORT, PUBLIC_PORT,
NEMOCLAW_DASHBOARD_PORT).

In `@scripts/start-services.sh`:
- Line 24: The DASHBOARD_PORT assignment currently falls back only to
DASHBOARD_PORT and 18789, causing mismatch with bin/lib/ports.js which also
considers PUBLIC_PORT; update the fallback chain for the DASHBOARD_PORT variable
assignment in start-services.sh so it first checks NEMOCLAW_DASHBOARD_PORT, then
PUBLIC_PORT, then DASHBOARD_PORT, then the default 18789 (i.e., include
PUBLIC_PORT in the parameter expansion order) so the shell script and
bin/lib/ports.js resolve the same port.

---

Nitpick comments:
In `@bin/lib/ports.js`:
- Around line 19-24: The parsePort fallback chain may throw errors that only
name the intermediate env var (e.g., DASHBOARD_PORT or PUBLIC_PORT), which can
confuse users who set NEMOCLAW_DASHBOARD_PORT; fix by surfacing the full
fallback context when an error occurs: update the parsePort function to
accept/propagate an optional context or chain label (or catch and rethrow
errors) so errors include the original requested variable and the fallback chain
(e.g., "NEMOCLAW_DASHBOARD_PORT (fallbacks: DASHBOARD_PORT → PUBLIC_PORT →
18789)"). Adjust the call that assigns DASHBOARD_PORT (the parsePort invocation
that uses "NEMOCLAW_DASHBOARD_PORT" and nested parsePort calls) so it
passes/propagates the chain information into parsePort so thrown errors
reference the full chain instead of only the intermediate variable name.

In `@docs/reference/troubleshooting.md`:
- Around line 93-115: Update the two sentences that introduce code blocks so
they follow the style guide: change "To resolve, either stop the conflicting
process:" to "To resolve, either stop the conflicting process." and change "Or
use a different port by setting the corresponding environment variable before
onboarding:" to "Or use a different port by setting the corresponding
environment variable before onboarding." also ensure the second sentence ends
with a period; these lines are the ones immediately before the lsof/kill example
and before the NEMOCLAW_GATEWAY_PORT export + nemoclaw onboard example
(referenced ENV var NEMOCLAW_GATEWAY_PORT and the nemoclaw onboard command) so
you can locate and edit them accordingly.

In `@scripts/check-ports.sh`:
- Around line 105-111: Validate each custom port argument before calling
check_port: in the for loop that iterates "$@", ensure the value matches a
numeric regex (e.g. only digits) and that it falls within the valid port range
1–65535; if a value fails, print a clear error like "Invalid port: <value> (must
be integer 1-65535)" and skip or exit with non-zero as appropriate, then only
call check_port "<port>" for validated ports (this prevents passing malformed
values to lsof and gives clearer messages).

In `@scripts/setup.sh`:
- Around line 35-37: Add the shellcheck source suppression comment before the
lines that source environment files: insert "# shellcheck source=/dev/null"
immediately above the two lines that source ".env" and ".env.local" (the lines
starting with "[ -f \"${REPO_DIR}/.env\" ] && set -a && . \"${REPO_DIR}/.env\"
&& set +a" and the similar ".env.local" line) so shellcheck warnings are
suppressed and the script matches the other scripts' pattern.

In `@uninstall.sh`:
- Around line 30-49: The _load_env function loads $SCRIPT_DIR/.env.local before
$SCRIPT_DIR/.env and uses first-wins semantics (it only exports a variable if
it's not already set), which differs from the other scripts that source .env
then .env.local with last-wins behavior; add a brief inline comment above the
_load_env definition explaining the load order and that first-wins is
intentional (i.e., .env.local takes precedence here) and note the difference vs
the other scripts so future maintainers understand the behavior and why it
differs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 22784033-f298-444d-b594-0be22c80c1f5

📥 Commits

Reviewing files that changed from the base of the PR and between b1fb013 and bc0a5d0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (30)
  • .env.example
  • .gitignore
  • README.md
  • bin/lib/env.js
  • bin/lib/local-inference.js
  • bin/lib/nim.js
  • bin/lib/onboard.js
  • bin/lib/ports.js
  • bin/lib/preflight.js
  • bin/nemoclaw.js
  • docs/reference/port-configuration.md
  • docs/reference/troubleshooting.md
  • nemoclaw-blueprint/blueprint.yaml
  • nemoclaw-blueprint/orchestrator/runner.py
  • scripts/brev-setup.sh
  • scripts/check-ports.sh
  • scripts/debug.sh
  • scripts/lib/runtime.sh
  • scripts/nemoclaw-start.sh
  • scripts/setup.sh
  • scripts/start-services.sh
  • scripts/telegram-bridge.js
  • test/e2e/test-double-onboard.sh
  • test/env.test.js
  • test/local-inference.test.js
  • test/onboard-readiness.test.js
  • test/onboard-selection.test.js
  • test/preflight.test.js
  • test/runtime-shell.test.js
  • uninstall.sh
💤 Files with no reviewable changes (1)
  • scripts/telegram-bridge.js

Comment on lines +57 to +70
// Remove inline comments for unquoted values first, then strip quotes.
// This handles cases like KEY='value' # comment correctly.
const hashIndex = value.indexOf(" #");
if (hashIndex !== -1) {
value = value.slice(0, hashIndex).trim();
}

// Strip surrounding quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inline comment stripping may truncate quoted values containing #.

The current logic strips inline comments before stripping quotes. For KEY="value # inside", this results in "value (malformed). Most .env parsers preserve content within quotes.

This edge case may be acceptable if your .env files never contain # within quoted values, but it's worth documenting or handling.

🔧 Proposed fix: Strip comments only for unquoted values
     let value = line.slice(eqIndex + 1).trim();
 
-    // Remove inline comments for unquoted values first, then strip quotes.
-    // This handles cases like KEY='value' # comment correctly.
-    const hashIndex = value.indexOf(" #");
-    if (hashIndex !== -1) {
-      value = value.slice(0, hashIndex).trim();
-    }
-
     // Strip surrounding quotes
     if (
       (value.startsWith('"') && value.endsWith('"')) ||
       (value.startsWith("'") && value.endsWith("'"))
     ) {
       value = value.slice(1, -1);
+    } else {
+      // Remove inline comments only for unquoted values
+      const hashIndex = value.indexOf(" #");
+      if (hashIndex !== -1) {
+        value = value.slice(0, hashIndex).trim();
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Remove inline comments for unquoted values first, then strip quotes.
// This handles cases like KEY='value' # comment correctly.
const hashIndex = value.indexOf(" #");
if (hashIndex !== -1) {
value = value.slice(0, hashIndex).trim();
}
// Strip surrounding quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
// Strip surrounding quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
} else {
// Remove inline comments only for unquoted values
const hashIndex = value.indexOf(" #");
if (hashIndex !== -1) {
value = value.slice(0, hashIndex).trim();
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/env.js` around lines 57 - 70, The inline-comment logic currently
removes " #..." before stripping quotes, which truncates quoted values like
KEY="value # inside"; change the order and condition so comments are stripped
only when the raw value is not quoted: first detect if value starts and ends
with matching quotes (value.startsWith('"') && value.endsWith('"') or same for
single quotes) and when it is quoted, skip the hash-trimming and only remove
surrounding quotes; when it is not quoted, perform the existing hashIndex check
(value.indexOf(" #")) and trim the comment, then trim whitespace. Locate the
code handling the variable named value in bin/lib/env.js to implement this
conditional flow.

Comment on lines +188 to +192
> **⚠️ Network exposure**
>
> When using local inference (Ollama or vLLM), the inference service binds to `0.0.0.0` so that containers can reach it via `host.openshell.internal`. This means the service is reachable from your local network, not just localhost. This is required for the sandbox architecture but should be considered in shared or untrusted network environments.
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdown lint warning: missing blank line after blockquote.

The static analysis tool flagged a blank line issue inside/after the blockquote. Add a blank line between the warning block (line 190) and the horizontal rule (line 191) to fix the MD028 violation.

📝 Proposed fix
 > When using local inference (Ollama or vLLM), the inference service binds to `0.0.0.0` so that containers can reach it via `host.openshell.internal`. This means the service is reachable from your local network, not just localhost. This is required for the sandbox architecture but should be considered in shared or untrusted network environments.
+
 ---
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> **⚠️ Network exposure**
>
> When using local inference (Ollama or vLLM), the inference service binds to `0.0.0.0` so that containers can reach it via `host.openshell.internal`. This means the service is reachable from your local network, not just localhost. This is required for the sandbox architecture but should be considered in shared or untrusted network environments.
---
> **⚠️ Network exposure**
>
> When using local inference (Ollama or vLLM), the inference service binds to `0.0.0.0` so that containers can reach it via `host.openshell.internal`. This means the service is reachable from your local network, not just localhost. This is required for the sandbox architecture but should be considered in shared or untrusted network environments.
---
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 188 - 192, Add a single blank line after the
blockquote that starts with "**⚠️ Network exposure**" and before the horizontal
rule ("---") to satisfy the markdown linter (MD028); edit the README block
containing that blockquote header and ensure there is an empty line between the
end of the quoted paragraph and the "---" separator.

collect "curl-models" sh -c 'code=$(curl -s -o /dev/null -w "%{http_code}" https://integrate.api.nvidia.com/v1/models); echo "HTTP $code"; if [ "$code" -ge 200 ] && [ "$code" -lt 500 ]; then echo "NIM API reachable"; else echo "NIM API unreachable"; exit 1; fi'
collect "lsof-net" sh -c 'lsof -i -P -n 2>/dev/null | head -50'
collect "lsof-18789" lsof -i :18789
collect "lsof-dashboard" lsof -i :"${NEMOCLAW_DASHBOARD_PORT:-18789}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the same dashboard-port fallback chain for diagnostics.

Line 294 only checks NEMOCLAW_DASHBOARD_PORT (or 18789). If DASHBOARD_PORT/PUBLIC_PORT is used, the debug report can inspect the wrong port.

Proposed fix
-  collect "lsof-dashboard" lsof -i :"${NEMOCLAW_DASHBOARD_PORT:-18789}"
+  dashboard_port="${NEMOCLAW_DASHBOARD_PORT:-${DASHBOARD_PORT:-${PUBLIC_PORT:-18789}}}"
+  collect "lsof-dashboard" lsof -i :"${dashboard_port}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
collect "lsof-dashboard" lsof -i :"${NEMOCLAW_DASHBOARD_PORT:-18789}"
dashboard_port="${NEMOCLAW_DASHBOARD_PORT:-${DASHBOARD_PORT:-${PUBLIC_PORT:-18789}}}"
collect "lsof-dashboard" lsof -i :"${dashboard_port}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/debug.sh` at line 294, The lsof diagnostic call only uses
NEMOCLAW_DASHBOARD_PORT (or 18789) and misses other env fallbacks; update the
collect invocation that runs lsof -i to use the same fallback chain as elsewhere
(check DASHBOARD_PORT, then PUBLIC_PORT, then NEMOCLAW_DASHBOARD_PORT, then
default 18789) so it inspects the effective dashboard port; modify the variable
expansion in the collect "lsof-dashboard" lsof -i :"${...}" invocation
accordingly (refer to the collect call and the environment variable names
DASHBOARD_PORT, PUBLIC_PORT, NEMOCLAW_DASHBOARD_PORT).

[ -f "${REPO_DIR}/.env" ] && set -a && . "${REPO_DIR}/.env" && set +a
[ -f "${REPO_DIR}/.env.local" ] && set -a && . "${REPO_DIR}/.env.local" && set +a

DASHBOARD_PORT="${NEMOCLAW_DASHBOARD_PORT:-${DASHBOARD_PORT:-18789}}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align Line 24 fallback chain with bin/lib/ports.js to avoid port drift.

Line 24 currently ignores PUBLIC_PORT, but bin/lib/ports.js includes it. If only PUBLIC_PORT is configured, cloudflared may tunnel to the wrong local port.

Proposed fix
-DASHBOARD_PORT="${NEMOCLAW_DASHBOARD_PORT:-${DASHBOARD_PORT:-18789}}"
+DASHBOARD_PORT="${NEMOCLAW_DASHBOARD_PORT:-${DASHBOARD_PORT:-${PUBLIC_PORT:-18789}}}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DASHBOARD_PORT="${NEMOCLAW_DASHBOARD_PORT:-${DASHBOARD_PORT:-18789}}"
DASHBOARD_PORT="${NEMOCLAW_DASHBOARD_PORT:-${DASHBOARD_PORT:-${PUBLIC_PORT:-18789}}}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/start-services.sh` at line 24, The DASHBOARD_PORT assignment
currently falls back only to DASHBOARD_PORT and 18789, causing mismatch with
bin/lib/ports.js which also considers PUBLIC_PORT; update the fallback chain for
the DASHBOARD_PORT variable assignment in start-services.sh so it first checks
NEMOCLAW_DASHBOARD_PORT, then PUBLIC_PORT, then DASHBOARD_PORT, then the default
18789 (i.e., include PUBLIC_PORT in the parameter expansion order) so the shell
script and bin/lib/ports.js resolve the same port.

@jnun
Copy link
Copy Markdown
Contributor Author

jnun commented Mar 22, 2026

Closing to improve alignment with the latest main branch.

@jnun jnun closed this Mar 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants