Host ksen@34.42.88.157 (Ubuntu 24.04, GCP) · changes to sorcar-cloud + new sorcar-connect · commit 1c27aa44 · 4 August 2026
The remote editor used to be reachable only from the laptop that deployed it, and only during that
deploy. It is now a permanently-running service that any machine with SSH access can discover and open with a
single 300-line file — sorcar-connect.
sorcar-cloud pushes your working directory to a Linux host, installs KISS Sorcar there and starts
code-server — VS Code running as a web app. The editor binds
127.0.0.1 only (it runs with --auth none, so it must never face the internet), and an SSH
tunnel is the way in. That part was fine. Two things were not.
It was launched like this, from inside the deploy's SSH session:
setsid bash -c "... exec code-server --bind-addr 127.0.0.1:$PORT --auth none ... '$WORKSPACE'" \
> "$LOG_DIR/code-server.log" 2>&1 < /dev/null &
A detached process has no supervisor: if it crashed or was OOM-killed it stayed dead, and after a reboot the
machine came back with no editor at all. The only way to get it back was another full redeploy
(10–20 minutes, because it re-copies the tree and re-runs install.sh).
The IDE port, the webapp port and the workspace path existed only as shell variables in the deploying laptop's script run. A second machine — your desktop, a borrowed laptop, a phone with a shell — had no way to ask the host "which port is the editor on, and what folder is it serving?", and no supported command to open it. The tunnel logic lived in the middle of a 1000-line deploy script, so it could not be run on its own.
Step 7 of the deploy now writes a systemd user unit (no sudo anywhere) and enables
lingering for the remote user:
# ~/.config/systemd/user/kiss-code-server.service
[Unit]
Description=KISS Sorcar IDE (code-server) on 127.0.0.1:8080
After=network-online.target
[Service]
Type=exec
EnvironmentFile=-%h/.kiss/api_keys.systemd.env
Environment=PATH=%h/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment="KISS_PROJECT_PATH=/home/ksen/kiss"
WorkingDirectory=/home/ksen/kiss
ExecStart=/home/ksen/.local/bin/code-server --bind-addr 127.0.0.1:8080 --auth none \
--disable-workspace-trust --enable-proposed-api ksenxx.kiss-sorcar "/home/ksen/kiss"
Restart=always
RestartSec=3
StandardOutput=append:%h/.kiss/code-server.log
StandardError=append:%h/.kiss/code-server.log
[Install]
WantedBy=default.target
Why each line is there:
Restart=always — a crashed or OOM-killed editor comes back in 3 seconds.WantedBy=default.target + loginctl enable-linger — the pair is what makes the
editor start at boot. Lingering, in systemd's own words, means a user manager is spawned for the user at boot and kept around after logouts; without it the whole user manager (and
kiss-web
with it) is killed the moment the deploying SSH session closes.EnvironmentFile=-…api_keys.systemd.env — the API keys, from the very file the
kiss-web unit already uses. A systemd service inherits nothing from a login shell, so this (and
the explicit PATH) replaces the environment the old setsid launch inherited by
accident. The leading - keeps a missing file non-fatal.append: — output goes to ~/.kiss/code-server.log, the file every error
message already points at. The deploy truncates it first so it cannot grow forever across restarts.kiss-code-server, not code-server, so it can never collide with a
unit a distro package owns.Two ordering details matter. Lingering is enabled before systemctl --user enable, because
without a lingering user manager "enabled at boot" is an empty promise. And the deploy's wipe step now stops the
unit before its pkill: with Restart=always, killing the process would just make
systemd start a new editor on the directory about to be deleted.
The deploy writes the facts a client needs into the remote home directory:
# ~/.kiss/sorcar-cloud.env (chmod 600, no secrets)
SORCAR_IDE_PORT=8080
SORCAR_WEB_PORT=8787
SORCAR_WORKSPACE=/home/ksen/kiss
SORCAR_IDE_SERVICE=kiss-code-server
It is excluded from the ~/.kiss mirror (which runs with --delete), so a later deploy
from a laptop that has no such file cannot wipe the host's record of itself.
The new sorcar-connect is the client. It is deliberately one self-contained file that needs
nothing but bash and ssh:
scp user@host:kiss/sorcar-connect . # once, on the new machine
./sorcar-connect user@host # every time after that
What it does, in order:
sorcar-cloud.env facts./healthz route — which proves the editor
is serving, not merely that something holds the port — and systemctl --user starts the
unit if it is silent. So even a host where someone stopped the service is repaired by connecting to it.kiss-web webapp with
ssh -N -n -o ExitOnForwardFailure=yes -o ServerAliveInterval=30.--print skips all of that and just prints the two commands the host supports, for a machine where you
would rather not run a script at all:
$ ./sorcar-connect --print ksen@34.42.88.157
[STEP] Asking ksen@34.42.88.157 about its KISS Sorcar deployment ...
[ksen-vm-32] IDE is answering on 127.0.0.1:8080 (workspace: /home/ksen/kiss).
Forward the ports yourself, then open http://localhost:8080 :
ssh -N -n -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \
-L 8080:127.0.0.1:8080 -L 18787:127.0.0.1:8787 ksen@34.42.88.157
Or, with desktop VS Code + the Remote-SSH extension, skip the tunnel:
code --remote ssh-remote+ksen@34.42.88.157 /home/ksen/kiss
The deploy itself now ends with exec ./sorcar-connect … — about 110 lines of tunnel, browser and
hold logic were deleted from sorcar-cloud rather than duplicated. That is not just tidiness: the path
your other machines take is therefore exercised on every single deploy, so it cannot quietly rot.
./sorcar-cloud --connect user@host is a shortcut to the same script for people who only remember one
command.
| Option | What it needs on the client | Verdict |
|---|---|---|
| SSH port forward (chosen) | An SSH client and your key. Nothing else. | Coder's own documentation recommends it: If you have an SSH server on your remote machine, this approach doesn't require any additional setup at all.The editor stays on loopback, and browsers treat localhost as a secure context, so web views work. |
| Expose the port publicly | Nothing | Rejected. Never expose code-server directly to the internet without some form of authentication and encryption, otherwise someone can take over your machine via the terminal. |
| Caddy/NGINX + Let's Encrypt | Nothing (a real URL) | Needs a domain, an open port and a certificate — real infrastructure for a personal dev VM. Kept in mind for tablets, which have no SSH client. |
VS Code Remote Tunnels (code tunnel) | Desktop VS Code + a GitHub/Microsoft account | Rejected: it authenticates with an account, not your SSH keys, and relays traffic through a third-party service. |
| VS Code Remote-SSH | Desktop VS Code + the Remote-SSH extension | Genuinely nice when you have it — so --print hands you the exact
code --remote ssh-remote+user@host /path command. Not universal, hence not the default. |
One more research finding that made this safe: a single code-server instance is happy with several concurrent
browsers and tunnels (unlike Microsoft's VS Code Server, which documents an instance of the server is designed to
be accessed by one user or client at a time
). Your desktop and your laptop can be connected at once.
| # | Test | Observed | |
|---|---|---|---|
| 1 | Full deploy installs the service | Installing the kiss-code-server user service (127.0.0.1:8080, workspace: /home/ksen/kiss) →
code-server is up on 127.0.0.1:8080 | pass |
| 2 | Unit and state are as designed | is-active=active, is-enabled=enabled, Linger=yes,
/healthz answers, and ss -tlnp shows only
127.0.0.1:8080 — nothing public | pass |
| 3 | Self-healing: kill -9 the editor |
MainPID 181482 killed → 8 s later active, MainPID 181698,
NRestarts=1, /healthz OK | pass |
| 4 | Foreign machine: a directory holding only sorcar-connect, with the remote
service deliberately stopped |
IDE not answering on 127.0.0.1:8080 — starting kiss-code-server … →
IDE is answering … → tunnel up → curl http://localhost:8080/ returns
302 → ./?folder=/home/ksen/kiss with the workbench HTML | pass |
| 5 | The IDE really loads in a browser through the tunnel | Workbench renders the deployed tree; the KISS SORCAR panel activates; status bar shows
Forwarded Ports: 8787, … and 0 problems | pass |
| 6 | The extension still starts the webapp under systemd | With kiss-web stopped and its fingerprint deleted beforehand, both came back:
ActiveEnterTimestamp Tue 2026-08-04 01:14:12 UTC, fingerprint recreated | pass |
| 7 | A real agent task runs on the VM | Newest task_history row on the VM: result
hostname → ksen-vm-32.c.r2eg-441800.internal, python3 -c "print(6*7)" → 42,
model=claude-opus-5, work_dir=/home/ksen/kiss | pass |
| 8 | Reboot the VM | Editor active at 01:20:33 UTC; my first successful SSH login was at 01:20:46 — it
started before anyone logged in. ./sorcar-connect --print afterwards needed no repair:
IDE is answering … | pass |
Test 7 is the one that closes the loop on the riskiest part of the change. Moving off the
setsid launch meant the editor no longer inherits a login shell's environment, so the agent could have
lost its API keys. A successful claude-opus-5 call — recorded in the VM's own database, reporting the
VM's own GCP-internal hostname — proves the EnvironmentFile path works. Test 3 also confirmed the
tunnel hygiene from the earlier review still holds: SIGTERM to the client prints
Closing the SSH tunnels…, frees the local port and leaves the remote editor untouched.
My first test deploy wrote its log to ./tmp/deploy.log — inside the very tree being copied. Twenty
minutes later the script was gone, the log ended mid-sentence and nothing was printed. The remote had the
complete tree; the deploy had simply died.
Cause: tar exits 1 for warnings, and the classic warning is "file changed as we read
it" — which any live checkout produces (a log being written, an editor autosaving). Under
set -e with pipefail that warning aborted the script, and set -e exits
without a message. So a normal, harmless condition destroyed a finished transfer and explained nothing.
The copy now classifies the two halves of the pipe instead of trusting a single status:
COPYFILE_DISABLE=1 tar --no-xattrs --exclude='.venv' --exclude='.kiss-worktrees' \
-C "$src" -czpf - . | ssh "$TARGET" "mkdir -p -- '$dst' && tar -C '$dst' -xzpf -" || true
local tar_rc="${PIPESTATUS[0]}" ssh_rc="${PIPESTATUS[1]}"
((ssh_rc == 0)) || die "The remote refused the copy (ssh/tar exit $ssh_rc)."
((tar_rc <= 1)) || die "Local tar failed with exit $tar_rc — the copy is incomplete."
((tar_rc == 0)) || warn "tar reported warnings (exit 1) — usually a file that changed while being read."
The receiving ssh is the half that can lose data, so any failure there is still fatal; a real
tar error (≥2) is fatal; a warning is a warning.
| File | Change |
|---|---|
sorcar-connect new |
~310 lines. The whole client: discovery, service repair, local-port probing, the two forwards, browser,
webapp wait, hold-and-teardown. Flags --print and --no-browser. Depends only on
bash and ssh. |
sorcar-cloud |
189 insertions, 210 deletions — it got shorter while gaining features. Step 7h installs the
systemd unit and enables lingering; new step 7i writes ~/.kiss/sorcar-cloud.env; the wipe step
stops the unit before pkill; the ~/.kiss mirror excludes the new state file;
copy_tree() classifies tar/ssh exits; steps 8–11 are replaced by
exec ./sorcar-connect; --connect added; the duplicated local-port probes were removed
(they live in the client now). |
Both files pass bash -n and shellcheck (only the pre-existing intentional informational
notes remain: client-side expansion in ssh "VAR='$X' bash -s", and tildes inside message strings).
# From the machine with the checkout — deploy, then it opens the IDE for you:
./sorcar-cloud ksen@34.42.88.157
# From that same machine later, without redeploying:
./sorcar-cloud --connect ksen@34.42.88.157
# From ANY machine that can ssh into the host:
scp ksen@34.42.88.157:kiss/sorcar-connect .
./sorcar-connect ksen@34.42.88.157
# Or with no script at all:
./sorcar-connect --print ksen@34.42.88.157 # copy the ssh -L line it prints
If the host is ever truly stuck, the two commands that tell you everything are
systemctl --user status kiss-code-server and tail ~/.kiss/code-server.log.