* feat(cli): add --foreground flag for systemd and process manager deployments
Adds --foreground / --no-daemon to `openchamber serve` which runs the
server inline in the CLI process instead of spawning a detached daemon child.
Required for systemd Type=simple (and other process managers) that track the
direct child — the always-daemon behavior introduced in #640 broke this use case.
Also documents OPENCHAMBER_HOST (bind address) in --help, which was
implemented but never exposed to users.
* docs: add systemd service guide for VPN/LAN deployments
Documents how to run OpenCode and OpenChamber as separate systemd
user services for persistent access over Tailscale or LAN, using
the new --foreground flag and OPENCODE_HOST to wire them together.
* fix(cli): address foreground mode parity issues from PR review
- Fix Ctrl+C handling: CLI SIGINT handler now defers to server in
foreground mode; dedicated signal handlers perform graceful shutdown
and clean exit
- Restore lifecycle parity: foreground instances write PID/instance
files so status, stop, and restart can discover them
- Add deterministic --foreground --json output: emits stable startup
JSON with port, pid, url, and foreground flag before blocking
* fix(cli): tighten inline foreground behavior for restart UX and JSON-only output
* fix(cli): pass --host to foreground server, reject --json, add --quiet output
- Pass options.host through to startWebUiServer() in foreground mode so
the bind address is respected (fixes localhost-only regression from #750)
- Reject --foreground --json with a clear usage error; --json is only
supported in background (daemon) mode
- Emit resolved port on stdout in --quiet foreground mode, matching
daemon parity
- Update systemd docs to include --host 0.0.0.0 for LAN/VPN access
now that the default bind is 127.0.0.1
* fix(cli): remove duplicate OPENCHAMBER_HOST entry from help text
* fix(cli): emit restart summary before foreground serve() blocks
restart --json (and --quiet / human) with a foreground instance would
hang forever without output because serve() blocks and the post-loop
summary was unreachable. Emit the final output after stop succeeds
but before the blocking serve call — foreground is always sorted last
so all daemon results are already collected.
* fix(cli): restart stops foreground instances without re-attaching
Foreground instances are managed by a process manager (systemd, Docker,
etc.) that will restart them automatically. The restart command now
just stops the foreground instance, records the result, and exits —
no serve() call, no blocking. This makes restart --json and all
other output modes work correctly for foreground instances.
Run OpenChamber and OpenCode as separate persistent services — useful when you want to access your
dev machine over a VPN (e.g. Tailscale) or LAN without a Cloudflare tunnel.
How it works:
OpenCode runs as its own service, binding only to localhost.
OpenChamber connects to it via OPENCODE_HOST and --host 0.0.0.0 makes it reachable on your VPN IP.
--foreground keeps the CLI process alive so systemd can track and restart it.
Why set PATH and SSH_AUTH_SOCK?
systemd user services start with a minimal environment — no shell profile is sourced.
Without an explicit PATH, OpenCode won't find tools installed via Homebrew, npm, or ~/.local/bin.
Without SSH_AUTH_SOCK, git operations over SSH (push, pull, clone) will fail because the agent socket isn't inherited.
Adjust the PATH to match your own tool installation paths.
%t expands to $XDG_RUNTIME_DIR (e.g. /run/user/1000), where most SSH agents write their socket.
OpenChamber will be reachable at http://<your-vpn-hostname>:3000 from any device on your VPN.
Note:--host 0.0.0.0 is required to listen on all interfaces. The default
bind address is 127.0.0.1 (localhost only). Use --host <ip> or
OPENCHAMBER_HOST=<ip> to bind to a specific interface instead.
Managed-local path note: OPENCHAMBER_TUNNEL_CONFIG must point to a path inside the container user home (/home/openchamber/...). If your Cloudflare config references a credentials JSON file, that file path must also be accessible inside the container (mount with volumes).
Tunnel behavior notes
OpenChamber supports one active tunnel per running instance (port).
Starting a tunnel with a different mode/provider on the same instance replaces the current tunnel.
Replacing or stopping a tunnel revokes existing connect links and invalidates remote tunnel sessions for that instance.
Connect links are one-time tokens; generating a new link revokes the previous unused link.
Data Directory Permission Note: The data/ directory is mounted into the container for persistent storage (config, sessions, SSH keys, workspaces). Before running, ensure the directory exists and has proper permissions:
My wife, who - with zero AI background - sat down with the app for the first time and built the firework celebration that plays on every successful push.
Every contributor who shaped this project with their PRs, ideas, and attention to detail.