fix(browser): remove --no-sandbox and persist profile across restarts - #97
Conversation
- Drop --no-sandbox flag; kernel.unprivileged_userns_clone=1 on Jetson means the user-namespace sandbox works without it, and Chromium was showing a persistent "unsupported command-line flag" warning banner - Stop wiping the profile dir on every launch so cookies/logins/session state survive service restarts (UI already advertised this as a "persistent profile") Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a Remote Control settings panel (beta-gated behind the feature flag
`ff_remote_control_enabled`) that starts a Cloudflare Quick Tunnel
pointing at the local ClawBox UI, captures the *.trycloudflare.com URL
for copy/paste into the portal's "Add Device" dialog, and stops it on
demand.
- scripts/setup-tunnel.sh: cloudflared installer (arm64/amd64/arm)
- scripts/run-tunnel.sh: wraps cloudflared, persists URL to
data/cloudflared/tunnel.url
- config/clawbox-tunnel.service: on-demand systemd unit
- install.sh: new step_cloudflared_install + unit registration
- sudoers: allow clawbox user to start/stop/restart the tunnel
- setup-api/portal/{start,stop,status}: thin API around the service
- RemoteControlPanel: off → starting → online → failed states with
URL field, Copy button, "Add to portal" link, Stop control
- next.config.ts: drop X-Frame-Options, relax CSP frame-ancestors
to allow openclawhardware.dev (+*.openclawhardware.dev) iframes
so the portal can embed the device UI
Bonus fixes discovered while validating the tunnel:
- TerminalApp + ws-config now build same-origin WS URLs; the
production server's upgrade proxy routes /terminal-ws to
127.0.0.1:3006 and everything else to the gateway port. Fixes
terminal + mascot chat under HTTPS and via the tunnel.
- AIModelsStep: restore the "Subscription" auth option for Anthropic
Claude so users can connect a Pro/Max subscription via OAuth
(backend already supported it).
- i18n: add settings.notConnected (previously referenced but
undefined) and a full remoteControl.* block across all 10 locales;
RemoteControlPanel uses t() throughout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- translations: the known-namespace guard was failing because the new `remoteControl.*` keys didn't appear in the allowlist. Add it. - ws-config route test: the endpoint now returns a same-origin URL without the gateway port (the production server's upgrade proxy forwards to 18789 for us), and upgrades the scheme to wss when the client reached us over HTTPS. Update the existing assertion and add two new ones covering X-Forwarded-Proto and Cloudflare cf-visitor. Full suite: 1075/1075 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…igin
Three related regressions surfaced after wiring the Cloudflare Quick
Tunnel; all shared the assumption that the browser could reach
individual upstream ports (18789, 6080) directly. Through the tunnel
only port 80 is exposed, so clients have to go through the production
server's upgrade proxy.
- Chat ("origin not allowed"): the WebSocket upgrade proxy rewrote
Origin to `http://127.0.0.1:18789`, but the gateway's controlUi
allowedOrigins list is port-less (`http://127.0.0.1`). Strip the
port from Origin in both the HTTP and HTTPS upgrade paths. Host
keeps the port since the gateway still needs it for Host routing.
- OpenClaw Dashboard form pre-filling wss://<host>:18789: the SPA
injection in gateway-proxy.ts hardcoded GATEWAY_PORT into the
gatewayUrl. Switch to `location.host` so the client uses same-
origin and the port-80 upgrade proxy forwards to 18789. Legacy
"bun can't do WS upgrades" comment removed — we run under node.
- VNC Remote Desktop connecting to :6080: VNCApp built
`ws://<host>:6080` directly. Change to
`${wss|ws}://${location.host}/novnc-ws` and register the path in
production-server's UPGRADE_ROUTES → 127.0.0.1:6080.
End-to-end through a live tunnel:
chat → gateway's connect.challenge arrives
VNC → websockify returns "RFB 003.008" banner
SPA → built JS shows `wsUrl=...+location.host` (no port)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces end-to-end remote control for ClawBox devices via Cloudflare Tunnel. It adds tunnel installation/management scripts, systemd service configuration, portal API endpoints for start/stop/status operations, a UI component for control, updated WebSocket routing, feature flag support, and installer integration. Changes
Sequence DiagramsequenceDiagram
actor User
participant RC as RemoteControlPanel
participant API as Portal API
participant LIB as cloudflared.ts
participant SVC as systemd Tunnel<br/>Service
User->>RC: Click "Start Tunnel"
RC->>API: POST /setup-api/portal/start
API->>LIB: startTunnelService()
LIB->>SVC: sudo systemctl restart clawbox-tunnel.service
SVC-->>LIB: Service started
LIB-->>API: Promise resolves
API-->>RC: { success: true }
RC->>API: Poll /setup-api/portal/status
API->>LIB: getTunnelServiceState()
LIB->>SVC: systemctl is-active
SVC-->>LIB: "active"
API->>LIB: readTunnelUrl()
LIB-->>API: "https://xxx.trycloudflare.com"
API-->>RC: { tunnel: { state: "active", url: "..." } }
RC-->>User: Display tunnel URL + copy button
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
CI Summary✅ Tests
⏳ E2E
|
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@config/clawbox-tunnel.service`:
- Around line 1-38: The systemctl enable loop in install.sh currently skips only
templates and clawbox-browser.service, so clawbox-tunnel.service will be enabled
at boot; modify the enable loop in install.sh (the systemctl enable loop around
the referenced lines) to explicitly skip the tunnel service by adding a
condition that checks if the current svc equals "clawbox-tunnel.service" and
continue if so, ensuring the tunnel remains on-demand only.
In `@install.sh`:
- Line 814: The enable-loop currently iterates ALL_SERVICES and will auto-enable
clawbox-tunnel.service; modify the loop that handles systemctl enable (iterating
ALL_SERVICES) to skip the tunnel by adding a condition that continues when svc
equals "clawbox-tunnel.service" (e.g., [[ "$svc" == "clawbox-tunnel.service" ]]
&& continue) alongside the existing checks for template services and
clawbox-browser.service so the tunnel is not auto-enabled at boot.
In `@production-server.js`:
- Around line 29-39: In resolveUpgradeTarget, when r.stripPrefix is true the
rewritten URL uses reqUrl.slice(r.prefix.length) which drops the leading slash
for cases like "/terminal-ws?token=..."; update the rewrite logic to ensure the
result always has a leading "/" when the sliced string is empty or begins with
"?" (so rewrite to "/" + sliced when needed) and keep existing behavior
otherwise; reference UPGRADE_ROUTES, the stripPrefix flag, r.prefix, and
resolveUpgradeTarget to locate and change the rewrite branch accordingly.
In `@remote_control.md`:
- Line 31: The documentation path for setup-tunnel.sh is inconsistent with the
implemented PROJECT_DIR; update remote_control.md so references to
/opt/clawbox/scripts/setup-tunnel.sh are changed to the actual deployment path
/home/clawbox/clawbox/scripts/setup-tunnel.sh (or add a clear note that this doc
is conceptual and may differ from install.sh's PROJECT_DIR), and ensure any
other mentions of setup-tunnel.sh or PROJECT_DIR in the document match the value
used in install.sh.
- Around line 244-262: Update the documentation example for
clawbox-tunnel.service to match the real unit file: change the User from root to
the non-root user "clawbox" and replace the ExecStart command shown
(/usr/local/bin/cloudflared ... --config /etc/cloudflared/config.yml run) with
the actual ExecStart used in the repo
(/home/clawbox/clawbox/scripts/run-tunnel.sh) and note that it uses the Quick
Tunnel invocation (no config file) so readers see the precise service name
"clawbox-tunnel.service", the correct User "clawbox", and the actual ExecStart
script path.
- Line 11: The fenced code block containing the architecture diagram in
remote_control.md currently lacks a language specifier; update the opening fence
from ``` to include a language (e.g., ```text or ```ascii) so the block is
rendered consistently—locate the "architecture diagram" fenced block and change
its opening backticks to include the chosen language specifier.
In `@scripts/setup-tunnel.sh`:
- Around line 28-39: The script downloads a mutable release (URL using
releases/latest) and installs it as root without verification; change it to pin
a specific release (do not use releases/latest), fetch the corresponding
published SHA256 or signature for that release, verify the downloaded artifact
(TMP) against the checksum or GPG signature before running chmod and mv, and
fail early on mismatch; use the existing URL/CF_ARCH to build both the artifact
and checksum/signature URLs, verify TMP (e.g., via sha256sum --check or gpg
--verify) and only proceed to chmod 0755 "$TMP" and mv "$TMP" "$INSTALL_PATH"
when verification succeeds.
In `@src/app/setup-api/portal/stop/route.ts`:
- Around line 6-15: The route currently returns success after calling
stopTunnelService() even though that helper swallows all errors; update the
behavior so a successful HTTP 200 is only returned when the service is actually
stopped: either (A) change stopTunnelService() to only suppress the specific
“service not running” error and rethrow any other failures, or (B) keep
stopTunnelService() as-is but, inside the POST handler, after await
stopTunnelService() call a service-state checker (e.g., a helper like
getTunnelServiceState/getCloudflaredServiceStatus or an equivalent that returns
active/inactive) and if the unit is still active return a 500 with an
explanatory error; reference the POST route and stopTunnelService() to locate
where to implement the change.
In `@src/components/AIModelsStep.tsx`:
- Around line 440-445: The component's provider-selection effect can leave
authMode stale (e.g., authMode remains "local" while selectedProvider is set to
Anthropic which has "subscription" first), causing activeAuth to fall back
incorrectly; in the AIModelsStep component update the prop-sync effect that sets
selectedProvider to also set authMode (and activeAuth if used) to the provider's
intended default option (match the provider's options array, e.g., set authMode
to "subscription" for Anthropic) or compute the active option from the
provider's options instead of relying on existing authMode; ensure you adjust
the same logic paths that selectProvider() would run so state stays consistent
when selectedProvider changes externally.
In `@src/components/RemoteControlPanel.tsx`:
- Around line 62-75: handleStart (and similarly handleStop) currently calls
res.json() before checking res.ok which will throw on non-JSON responses; change
the flow to first check res.ok and only attempt res.json() when ok, otherwise
read fallback text (e.g., await res.text()) or try/catch JSON parsing to extract
an error message, then use that message in the thrown Error/ setError so you
don't crash on HTML/non-JSON error pages; update the logic inside handleStart
and handleStop to branch on res.ok (or safely parse JSON in a try/catch) and set
a meaningful error message when the response isn't JSON.
In `@src/components/SettingsApp.tsx`:
- Around line 233-236: The feature flag state currently initializes
FEATURE_FLAG_KEYS.remoteControl to false causing the UI to treat "remote" as
disabled before preferences load; change the featureFlags handling so the
remoteControl flag can be "unknown" until preferences fetch completes (e.g.,
initialize as Record<string, boolean | undefined> or add a separate
featureFlagsLoaded boolean), update the useState declaration that creates
featureFlags and the code that sets them (setFeatureFlags) to preserve an
undefined/unknown value, and modify the nav-filter/redirect logic (the effect
that currently hides "remote" and redirects to "appearance") to only filter or
force-navigation after the flags have been loaded (featureFlagsLoaded true or
remoteControl !== undefined); apply the same pattern to the other affected block
referenced (lines ~1109-1119) so deep links are not blocked during initial load.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9391fb2c-fd5f-4b60-a38d-26507102d1ab
📒 Files selected for processing (24)
config/clawbox-sudoersconfig/clawbox-tunnel.serviceinstall.shnext.config.tsproduction-server.jsremote_control.mdscripts/run-tunnel.shscripts/setup-tunnel.shsrc/app/setup-api/gateway/ws-config/route.tssrc/app/setup-api/portal/start/route.tssrc/app/setup-api/portal/status/route.tssrc/app/setup-api/portal/stop/route.tssrc/components/AIModelsStep.tsxsrc/components/RemoteControlPanel.tsxsrc/components/SettingsApp.tsxsrc/components/TerminalApp.tsxsrc/components/VNCApp.tsxsrc/lib/cloudflared.tssrc/lib/feature-flags.tssrc/lib/gateway-proxy.tssrc/lib/translations.tssrc/tests/components/settings-app.test.tsxsrc/tests/routes/gateway/ws-config.test.tssrc/tests/unit/translations.test.ts
| [Unit] | ||
| Description=ClawBox Cloudflare Quick Tunnel | ||
| After=network-online.target clawbox-setup.service | ||
| Wants=network-online.target | ||
|
|
||
| [Service] | ||
| Type=simple | ||
| User=clawbox | ||
| WorkingDirectory=/home/clawbox/clawbox | ||
| ExecStart=/home/clawbox/clawbox/scripts/run-tunnel.sh | ||
| Restart=on-failure | ||
| RestartSec=5 | ||
| TimeoutStartSec=30 | ||
| TimeoutStopSec=15 | ||
| StandardOutput=journal | ||
| StandardError=journal | ||
| Environment=CLAWBOX_ROOT=/home/clawbox/clawbox | ||
| Environment=LOCAL_SERVICE_URL=http://localhost:80 | ||
|
|
||
| # ── Sandboxing ───────────────────────────────────────────────────────────── | ||
| # Light-touch hardening that works with the bash-wrapped cloudflared process. | ||
| # Heavier restrictions (ProtectSystem=strict, namespacing) conflict with the | ||
| # shell pipeline setup on this Jetson kernel. | ||
| NoNewPrivileges=yes | ||
| PrivateTmp=yes | ||
| ProtectKernelTunables=yes | ||
| ProtectKernelModules=yes | ||
| ProtectControlGroups=yes | ||
| RestrictSUIDSGID=yes | ||
|
|
||
| # ── Resource caps ────────────────────────────────────────────────────────── | ||
| CPUAccounting=yes | ||
| MemoryAccounting=yes | ||
| MemoryMax=256M | ||
| CPUQuota=50% | ||
|
|
||
| [Install] | ||
| WantedBy=multi-user.target |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the tunnel service won't be auto-enabled at boot
# The install.sh loop should skip clawbox-tunnel.service
grep -n "clawbox-tunnel" install.sh | head -20
# Check if there's explicit logic to skip enabling tunnel service
rg -n -B2 -A2 'clawbox-tunnel|tunnel.service' install.shRepository: ID-Robots/clawbox
Length of output: 840
🏁 Script executed:
# Show the complete loop structure in install.sh around lines 814-913
sed -n '813,914p' install.shRepository: ID-Robots/clawbox
Length of output: 4325
The tunnel service will be incorrectly enabled at boot, contradicting the on-demand requirement.
The service unit has good security properties (sandboxing, resource limits, unprivileged user, proper dependencies), but there is a critical gap in the install logic:
The systemctl enable loop in install.sh (lines 825–829) skips only template services (@*) and clawbox-browser.service. Since clawbox-tunnel.service matches neither condition, it will be enabled at boot despite the stated goal of on-demand-only operation. The comment at line 911–912 reflects the intended behavior, but the enable logic doesn't enforce it.
Add clawbox-tunnel.service to the skip conditions:
[[ "$svc" == "clawbox-tunnel.service" ]] && continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@config/clawbox-tunnel.service` around lines 1 - 38, The systemctl enable loop
in install.sh currently skips only templates and clawbox-browser.service, so
clawbox-tunnel.service will be enabled at boot; modify the enable loop in
install.sh (the systemctl enable loop around the referenced lines) to explicitly
skip the tunnel service by adding a condition that checks if the current svc
equals "clawbox-tunnel.service" and continue if so, ensuring the tunnel remains
on-demand only.
means the user-namespace sandbox works without it, and Chromium was
showing a persistent "unsupported command-line flag" warning banner
state survive service restarts (UI already advertised this as a
"persistent profile")
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com## Summary
Type of change
How was this tested?
bun run lintpassesbun run testpassesbun run buildsucceedsChecklist
mainScreenshots / logs (if UI or runtime change)
Summary by CodeRabbit
New Features
Documentation
Chores