@vibelord/lanes 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/bin/lanes-install.mjs +158 -0
- package/lib/plan.mjs +65 -0
- package/package.json +37 -0
- package/payload/lanes/README.md +152 -0
- package/payload/lanes/VERSION +1 -0
- package/payload/lanes/bin/lanes +1035 -0
- package/payload/lanes/client/ghostty/common.conf +22 -0
- package/payload/lanes/client/ghostty/macos.conf +6 -0
- package/payload/lanes/client/ghostty/omarchy.conf +19 -0
- package/payload/lanes/client/install.sh +16 -0
- package/payload/lanes/client/lanes-launch +315 -0
- package/payload/lanes/client/lanes-upload +147 -0
- package/payload/lanes/client/lanes-window +36 -0
- package/payload/lanes/client/macos-helper.applescript +14 -0
- package/payload/lanes/client/omarchy/discover.sh +56 -0
- package/payload/lanes/client/omarchy/lanes.conf +5 -0
- package/payload/lanes/client/omarchy/lanes.desktop +9 -0
- package/payload/lanes/client/omarchy/lanes.lua +14 -0
- package/payload/lanes/client/ssh.stanza.in +54 -0
- package/payload/lanes/container/env.sh +16 -0
- package/payload/lanes/container/lane +268 -0
- package/payload/lanes/container/lane-run +34 -0
- package/payload/lanes/docs/ARCHITECTURE.md +93 -0
- package/payload/lanes/docs/HOST-GUARD.md +108 -0
- package/payload/lanes/docs/INSTALL.md +192 -0
- package/payload/lanes/docs/RUNBOOK.md +385 -0
- package/payload/lanes/docs/SHARING.md +124 -0
- package/payload/lanes/docs/ZMX-RING0.md +51 -0
- package/payload/lanes/docs/images/desktop.png +0 -0
- package/payload/lanes/docs/images/iphone.png +0 -0
- package/payload/lanes/holders/exec.sh +21 -0
- package/payload/lanes/holders/tmux.sh +111 -0
- package/payload/lanes/holders/zmx.sh +22 -0
- package/payload/lanes/holders.lock +5 -0
- package/payload/lanes/install/enable-phone.sh +43 -0
- package/payload/lanes/install/enable-web.sh +16 -0
- package/payload/lanes/install/host-install.sh +72 -0
- package/payload/lanes/install/services.py +61 -0
- package/payload/lanes/install.sh +603 -0
- package/payload/lanes/libexec/cursor_commit.py +159 -0
- package/payload/lanes/libexec/cursor_sync.py +166 -0
- package/payload/lanes/libexec/guard.py +231 -0
- package/payload/lanes/libexec/lanes_lib.py +610 -0
- package/payload/lanes/libexec/legacy_sessions.py +130 -0
- package/payload/lanes/libexec/session_actions.py +276 -0
- package/payload/lanes/libexec/sidebar +8 -0
- package/payload/lanes/libexec/sidebar.py +872 -0
- package/payload/lanes/libexec/statusd.py +764 -0
- package/payload/lanes/libexec/tmpscan.py +921 -0
- package/payload/lanes/libexec/view.sh +179 -0
- package/payload/lanes/scripts/adopt-sharing-candidate.py +109 -0
- package/payload/lanes/site/README.md +28 -0
- package/payload/lanes/site/beyondwork/known_hosts +1 -0
- package/payload/lanes/site/beyondwork/site.conf +11 -0
- package/payload/lanes/site/site.example.conf +10 -0
- package/payload/lanes/sudo/host-guard.sh +356 -0
- package/payload/lanes/sudo/one-time-root.sh +32 -0
- package/payload/lanes/systemd/lanes-ensure.service +7 -0
- package/payload/lanes/systemd/lanes-ensure.timer +10 -0
- package/payload/lanes/systemd/lanes-gc.service +7 -0
- package/payload/lanes/systemd/lanes-gc.timer +9 -0
- package/payload/lanes/systemd/lanes-guard.service +8 -0
- package/payload/lanes/systemd/lanes-guard.timer +10 -0
- package/payload/lanes/systemd/lanes-mobile.service +23 -0
- package/payload/lanes/systemd/lanes-statusd.service +15 -0
- package/payload/lanes/systemd/lanes-sync.service +10 -0
- package/payload/lanes/systemd/lanes-sync.timer +10 -0
- package/payload/lanes/systemd/lanes-tmpreap.service +11 -0
- package/payload/lanes/systemd/lanes-tmpreap.timer +13 -0
- package/payload/lanes/systemd/lanes-web.service +13 -0
- package/payload/lanes/systemd/lanes.slice +11 -0
- package/payload/lanes/web/Caddyfile +37 -0
- package/payload/lanes/web/README.md +35 -0
- package/payload/lanes/web/icon-192.png +0 -0
- package/payload/lanes/web/icon.svg +4 -0
- package/payload/lanes/web/index.html +30 -0
- package/payload/lanes/web/manifest.webmanifest +15 -0
- package/payload/lanes/web/mobile.css +301 -0
- package/payload/lanes/web/mobile.js +367 -0
- package/payload/lanes/workspaces.example/README.md +27 -0
- package/payload/lanes/workspaces.example/catalog.json +7 -0
- package/payload/lanes/workspaces.example/commit-targets.json +8 -0
- package/payload/lanes/workspaces.example/terminals.example.json +19 -0
- package/payload/lanes/workspaces.example/workspaces.product.json +15 -0
- package/payload/lanes/workspaces.example/workspaces.research.json +16 -0
- package/payload/lanes/zellij/config.kdl +360 -0
- package/payload/manifest.json +31 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# lanes — host guard (`sudo/host-guard.sh`)
|
|
2
|
+
|
|
3
|
+
One small root script, run by hand. It exists because of 2026-09-19: ceobox thrashed (swap full, PSI memory ≈ 68 %),
|
|
4
|
+
the ~1 GiB lanes harness and the ssh login path were swapped out with everything else, and the only thing standing
|
|
5
|
+
between a container at its memory limit and a dead lane was the OOM killer's default choice.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
sudo sh ./sudo/host-guard.sh # read-only status + the plan of what --apply would do
|
|
9
|
+
sudo sh ./sudo/host-guard.sh --apply
|
|
10
|
+
sudo sh ./sudo/host-guard.sh --undo
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
It refuses to run when it is not root, when cgroup v2 is not mounted, or when `user-<uid>.slice` of the calling user
|
|
14
|
+
(`SUDO_USER`, or explicit `--user ACCOUNT`) does not exist. It never restarts, stops or signals anything, never calls docker, and
|
|
15
|
+
never runs or opens anything inside a container. Status: **not yet run on ceobox** — tested only against a fake
|
|
16
|
+
`/proc` + cgroup tree on a Mac (see the end).
|
|
17
|
+
|
|
18
|
+
The no-argument status shows: `MemoryLow` of the three units (systemd's view and the cgroup file), whether the cgroup2
|
|
19
|
+
mount has `memory_recursiveprot`, PSI, per container `memory.current` / `max` / `high` / `swap.max` / `oom.group` /
|
|
20
|
+
anon / shmem / `oom_kill` and the size and use of its `/tmp` (one `statfs` through `/proc/<init-pid>/root`, no
|
|
21
|
+
nsenter), `oom_score_adj` per protected class, systemd-oomd's state, whether the guard timer is installed — and then
|
|
22
|
+
the plan of what `--apply` would do.
|
|
23
|
+
|
|
24
|
+
## What `--apply` does, and why each piece needs root
|
|
25
|
+
|
|
26
|
+
| Piece | What | Why root |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Memory envelope | `systemctl set-property` `user.slice MemoryLow=4G`, `user-<uid>.slice MemoryLow=4G`, `user@<uid>.service MemoryLow=3G`. Live and persistent (drop-ins under `/etc/systemd/system.control/`), no daemon-reload, nothing restarts. 4G covers the harness + the ssh sessions (`session-N.scope` sits beside `user@`, not under it); 3G of it is for the user manager, where statusd, the zellij servers and the viewers live. | These are system units. A user manager cannot protect itself: `memory.low` is capped by every ancestor, so the value has to be set from `user.slice` down, which only PID 1 does for root. |
|
|
29
|
+
| `lanes-oom-guard` | `/usr/local/sbin/lanes-oom-guard` + `lanes-oom-guard.service/.timer`, every 60 s. Lowers `oom_score_adj` of tmux servers and container inits (−901), agents and zellij (−501). | *Lowering* an `oom_score_adj` needs `CAP_SYS_RESOURCE`; raising one does not (that is why Step 0.4 of the plan, +800 on builds, needs no root). |
|
|
30
|
+
| Record | Previous `MemoryLow` values under `/var/lib/lanes-host-guard/prev/`, written once — a second `--apply` keeps the first record. | root-owned state for `--undo`. |
|
|
31
|
+
|
|
32
|
+
`MemoryLow` protects from *future* reclaim; it does not swap anything back in. It only reaches the leaf cgroups when
|
|
33
|
+
the cgroup2 mount has `memory_recursiveprot` (systemd's default; the status says yes/no and warns on no). And the
|
|
34
|
+
envelope belongs to everything in the user's slice, not to lanes: protection is shared out by usage, so a large
|
|
35
|
+
host-side process of the same user (the restic run) takes its share of it. It is a floor for the login path, not a
|
|
36
|
+
reservation for the harness.
|
|
37
|
+
|
|
38
|
+
### The OOM guard
|
|
39
|
+
|
|
40
|
+
- **Where it looks:** `cgroup.procs` of every docker container cgroup (`system.slice/docker-*.scope`, or `docker/*` with
|
|
41
|
+
the cgroupfs driver; sub-cgroups included) and of the whole `user-<uid>.slice` (host zellij servers, any host tmux).
|
|
42
|
+
- **What it matches:** the exact `comm` — `tmux: server` (tmux names itself so; clients are `tmux: client`), `zellij`,
|
|
43
|
+
and the agent list in `/etc/lanes-host-guard.conf` (`agents='claude codex'`; exact `/proc/<pid>/comm` strings as
|
|
44
|
+
verified in Step 0 — edit it if an agent runs as `node` or a version-named binary, but then every `node` is an
|
|
45
|
+
agent). Container inits come from `~/.local/state/lanes/containers/*.pid`, believed only when the pid is pid 1 of
|
|
46
|
+
its own pid namespace **and** in a docker cgroup (the files are written by the unprivileged server and can be stale).
|
|
47
|
+
Nothing else is ever given a protective value.
|
|
48
|
+
- **Why −901 / −501 and not the plan's −900 / −500:** `oom_score_adj` is inherited over fork. Every pane shell a
|
|
49
|
+
marked tmux server starts, and every build a marked agent starts, is born with the parent's value; left alone,
|
|
50
|
+
within days everything descended from a lane is as protected as the lane, and the killer takes the Coder agent or
|
|
51
|
+
sshd instead. So each pass also sets an *inherited* mark back to 0 — and the odd values are how the guard knows a
|
|
52
|
+
mark is its own (docker's own value is −500; nobody uses −501). A foreign value is never raised (a tmux server at
|
|
53
|
+
−1000 stays there) and never reset. A fresh child carries the mark for at most 60 s.
|
|
54
|
+
- **Never −1000:** a cgroup in which nothing can be killed turns an OOM into a hang.
|
|
55
|
+
- Cost: `sh`, one `find`+`cat` per cgroup tree, then builtin reads of two tiny `/proc` files per pid. No fork per pid.
|
|
56
|
+
It logs one line to the journal only when it changed something.
|
|
57
|
+
|
|
58
|
+
## Verify
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
sudo sh ./sudo/host-guard.sh # memory.low 4294967296 / 4294967296 / 3221225472; marks by class
|
|
62
|
+
systemctl list-timers lanes-oom-guard.timer # next run within 60 s
|
|
63
|
+
journalctl -u lanes-oom-guard -n 5 # "N protected, M inherited marks cleared"
|
|
64
|
+
lanes invariant check # as the workspace user: no lane changed
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Undo
|
|
68
|
+
|
|
69
|
+
`sudo sh ./sudo/host-guard.sh --undo` sets the recorded `MemoryLow` values back (`set-property`, so a drop-in
|
|
70
|
+
saying `MemoryLow=0` stays behind — it is inert), sets every −901 / −501 mark back to 0, disables and removes the
|
|
71
|
+
timer, service and script, and reloads systemd. `/etc/lanes-host-guard.conf` is kept (your agent list).
|
|
72
|
+
"Back to 0" is exact inside a container (docker's default); on the host, a process the user manager had started at
|
|
73
|
+
100 / 200 ends at 0 — marginally *more* protected than before, and only until it restarts.
|
|
74
|
+
|
|
75
|
+
## Deferred — by hand, and not code
|
|
76
|
+
|
|
77
|
+
The plan defers two tighter limits until pressure recurs *after* `/tmp` has been emptied (Steps 1–2). They are
|
|
78
|
+
deliberately **not** sub-commands of the root script: neither is needed now, and the first cannot be done from outside
|
|
79
|
+
without running a container's binary as host root. What would otherwise be forgotten is here instead. Both need the
|
|
80
|
+
status output first (it prints every number below, the init pid and the `docker-<id>.scope` name).
|
|
81
|
+
|
|
82
|
+
| Measure | Only when | By hand | Back |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| Smaller tmpfs `/tmp` (96 → 64 → 32 G, one notch at a time) | `/tmp` is its own tmpfs and holds ≤ 60 % of the **new** size. The kernel itself refuses a size below current use (`EINVAL`), so a wrong number fails instead of destroying — but a cap close to use is `ENOSPC` for every writer in the container minutes later. It frees nothing; it only stops growth. | `nsenter -t <init-pid> -m mount -o remount,<the mount's current flags>,size=64G /tmp` — flags (`nosuid,nodev,noexec…`) from field 6 of the `/tmp` line in `/proc/<init-pid>/mountinfo`: not every `mount` keeps them on a remount. **Hazard:** `nsenter -m` also moves the root directory, so the `mount` that runs is the *container's* file, as host root and outside the container's confinement. Only for a container whose contents you trust as root. | the same command with the old size |
|
|
85
|
+
| Container `memory.high` (e.g. 185 G under a 208 G `memory.max`) | `memory.current` ≤ 90 % **and** anon + shmem ≤ 80 % of the value. Above `memory.high` every allocation in the container is throttled into reclaim; with swap full that is not a kill but a freeze of all lanes. | `systemctl set-property --runtime docker-<id>.scope MemoryHigh=185G` — through systemd, which rewrites `memory.*` of its scopes from its own view at every daemon-reload (a raw `echo` into `memory.high` can silently disappear). | `MemoryHigh=infinity` |
|
|
86
|
+
|
|
87
|
+
## NEVER
|
|
88
|
+
|
|
89
|
+
- systemd-oomd on docker scopes or `system.slice` (it kills whole cgroups: every lane of a container at once).
|
|
90
|
+
- `memory.oom.group=1` on a container (same effect, from the kernel).
|
|
91
|
+
- `memory.max` below current usage — that is a live OOM sweep. `memory.high` only, and only through the check above.
|
|
92
|
+
- `swapoff` under pressure.
|
|
93
|
+
- Touching `/tmp/tmux-1000` (the lanes' sockets).
|
|
94
|
+
- Restarting a container, docker, or `user@<uid>.service`.
|
|
95
|
+
- `oom_score_adj` −1000 for everything.
|
|
96
|
+
- Tightening anything before usage has dropped.
|
|
97
|
+
|
|
98
|
+
## How it was tested
|
|
99
|
+
|
|
100
|
+
Against a synthetic `/proc` + cgroup tree with `id` / `systemctl` / `stat` / `df` shims on `PATH`, under both `sh` and
|
|
101
|
+
`dash` on macOS: refusals (not root, no cgroup v2, no user slice, unknown or surplus arguments); status writes nothing
|
|
102
|
+
and calls nothing but `systemctl show` / `is-active`; `/tmp` via GNU `stat -f` and the `df` fallback; `--apply` order
|
|
103
|
+
(parent first), record-once, atomic install, conf kept on re-apply; the guard's classification incl. inherited marks,
|
|
104
|
+
foreign values (−1000, docker's −500, a build at +800), sub-cgroups, vanished pids and a stale pid file; `--undo`
|
|
105
|
+
incl. clearing the marks, and that it is idempotent. The overrides that make this possible (`LANES_PROC_ROOT`,
|
|
106
|
+
`LANES_CGROUP_ROOT`, `LANES_HG_DESTDIR`) do not survive sudo's `env_reset`.
|
|
107
|
+
Not covered by any of that: real systemd, a real kernel, a real container — the first real run is the no-argument
|
|
108
|
+
status, which changes nothing.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Install the complete Lanes setup
|
|
2
|
+
|
|
3
|
+
The same `lanes/` directory installs either a desktop client or the workspace server. A deployment needs
|
|
4
|
+
one server and one client installation per desktop. Safari uses the optional web services on the server.
|
|
5
|
+
Configuration identifies your server and workspaces; credentials and generated runtime state stay outside Git.
|
|
6
|
+
|
|
7
|
+
## 1. Prepare a workspace host
|
|
8
|
+
|
|
9
|
+
Supported server: Linux x86_64, Python 3.9+, systemd user services, OpenSSH server, Docker, tmux, curl, tar,
|
|
10
|
+
`sha256sum`, `infocmp` and `tic`. The pinned binaries in `holders.lock` are Linux x86_64 builds.
|
|
11
|
+
Install missing packages using your distribution's package manager. The installer checks them before changing files.
|
|
12
|
+
|
|
13
|
+
Run the server as an unprivileged workspace account with Docker access. Container homes must be mounted at
|
|
14
|
+
`/home/coder` with UID 1000; the host account must be able to read the mounted files and reach those users'
|
|
15
|
+
Unix sockets through `/proc`. This is the current container contract, including on a host provisioned without Coder.
|
|
16
|
+
Containers need Bash, Python 3 and tmux. Use compatible tmux versions on the host and containers (tested with
|
|
17
|
+
tmux 3.4): the host client attaches directly to the container server. Zmx is installed as an optional holder,
|
|
18
|
+
not a migration of existing lanes.
|
|
19
|
+
|
|
20
|
+
For services to stay available after logout, an administrator enables the account's user manager:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
sudo loginctl enable-linger workspace # replace workspace with the actual account
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
As that account, clone the repository and install:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
git clone https://github.com/bwllaming/bw-tools.git
|
|
30
|
+
cd bw-tools/lanes
|
|
31
|
+
./install.sh --server --check
|
|
32
|
+
./install.sh --server
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`--server --no-start` installs files and units without contacting Docker or starting services. The normal install
|
|
36
|
+
verifies archive checksums, installs the pinned binaries, registers this checkout's absolute paths, and enables
|
|
37
|
+
`lanes-statusd`, `lanes-sync.timer`, `lanes-ensure.timer` and `lanes-gc.timer`. It creates an empty workspace catalog
|
|
38
|
+
only if no configuration exists. Host protection, temporary-file deletion and web access remain opt-in.
|
|
39
|
+
|
|
40
|
+
## 2. Configure workspaces and editor sync
|
|
41
|
+
|
|
42
|
+
Start from the included configuration, then edit container names and repository paths:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
mkdir -p ~/.config/lanes/workspaces
|
|
46
|
+
cp -R workspaces.example/. ~/.config/lanes/workspaces/
|
|
47
|
+
$EDITOR ~/.config/lanes/workspaces/catalog.json
|
|
48
|
+
$EDITOR ~/.config/lanes/workspaces/workspaces.product.json
|
|
49
|
+
$EDITOR ~/.config/lanes/workspaces/workspaces.research.json
|
|
50
|
+
lanes install-container my-workspace-container
|
|
51
|
+
lanes sync
|
|
52
|
+
lanes doctor
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Remove unused example boards from the catalog. A complete terminal registry is provided in
|
|
56
|
+
[`workspaces.example/terminals.example.json`](../workspaces.example/terminals.example.json): adapt it to the
|
|
57
|
+
repository's `.vscode/terminals.json` inside the container. Every terminal needs an explicit tmux socket/session
|
|
58
|
+
binding; there is no guessed command parsing. See [the configuration reference](../workspaces.example/README.md).
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
lanes cursor-sync --diff # preview editor commands and retained create recipes
|
|
62
|
+
lanes cursor-sync --write # apply with backups, after reviewing the preview
|
|
63
|
+
lanes sync
|
|
64
|
+
lanes attach laptop
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A lane that has not started is shown as absent. Open its terminal in Cursor / VS Code, or run
|
|
68
|
+
`/home/coder/.lz/bin/lane open product--shell` inside its container to create it from its recorded recipe.
|
|
69
|
+
The viewer itself only attaches. `lanes cursor-commit --push` is a separate, explicit publishing action;
|
|
70
|
+
it is not run by installation or automatic sync.
|
|
71
|
+
|
|
72
|
+
## 3. Give clients a site profile
|
|
73
|
+
|
|
74
|
+
Copy [`site/site.example.conf`](../site/site.example.conf) and fill in the server's reachable hostname,
|
|
75
|
+
SSH account, and host-key alias. Put the server's public Ed25519 **host** key in a file named `known_hosts`
|
|
76
|
+
next to that profile. Obtain it through a trusted administrator/session:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
workspace-server ssh-ed25519 <base64 public key from /etc/ssh/ssh_host_ed25519_key.pub>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The actual known_hosts line has three fields: alias, `ssh-ed25519`, and the base64 key. Never distribute
|
|
83
|
+
`ssh_host_ed25519_key` (the private key). [Site instructions](../site/README.md) cover profiles and updates.
|
|
84
|
+
The supplied `site/beyondwork/` profile is an explicit choice for the existing team server, not a default.
|
|
85
|
+
|
|
86
|
+
On each Mac or Linux desktop, install from npm (members of the `@vibelord` npm organization; Node.js 18+):
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
npm login # once per machine, or a read-only token in ~/.npmrc
|
|
90
|
+
npx @vibelord/lanes --site beyondwork # bundled team profile; or --site /path/to/site.conf
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The package runs this same installer with the same options and exit codes; see
|
|
94
|
+
[`npm/lanes/README.md`](../../npm/lanes/README.md). From a checkout instead:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
git clone https://github.com/bwllaming/bw-tools.git
|
|
98
|
+
cd bw-tools/lanes
|
|
99
|
+
./install.sh --site /path/to/site.conf
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
OpenSSH is required. Install Ghostty for a dedicated window, or run `lanes-launch` in your existing terminal.
|
|
103
|
+
Linux also supports Foot, plus optional Omarchy/Hyprland shortcuts. Tailscale is required when your profile uses
|
|
104
|
+
a tailnet address. The installer installs its own configuration; your normal terminal remains usable.
|
|
105
|
+
|
|
106
|
+
Authorize the printed public key in the server account's `~/.ssh/authorized_keys`. The client never edits that
|
|
107
|
+
file remotely. Then `./install.sh --check` and `lanes-window`. Useful options:
|
|
108
|
+
|
|
109
|
+
- `--device NAME`: independent view name; a stable per-machine name is the default.
|
|
110
|
+
- `--key PATH`: choose an existing SSH private key; the key stays on the client.
|
|
111
|
+
- `--no-desktop` / `--no-uploads`: omit those integrations.
|
|
112
|
+
- `--link`: develop against the checkout instead of installing a client copy.
|
|
113
|
+
- `--yes`: noninteractive installation; a newly created dedicated key has no passphrase.
|
|
114
|
+
|
|
115
|
+
The site selection is saved in `~/.config/lanes/client-site.conf`. Subsequent installs and checks can omit `--site`.
|
|
116
|
+
On macOS, the signed Lanes Helper app requests access to protected folders. Allow Downloads/Desktop/Documents
|
|
117
|
+
as needed. The allowlist is separate from macOS permission; inspect it with `lanes-upload --allowed`.
|
|
118
|
+
|
|
119
|
+
## 4. Optional Safari / Home Screen access
|
|
120
|
+
|
|
121
|
+
Install Caddy and Tailscale on the server and enable Tailscale HTTPS for its tailnet. The web services use
|
|
122
|
+
loopback ports **8082** (native Zellij) and **8083** (Lanes frontend); private HTTPS uses **8443**.
|
|
123
|
+
Reserve those ports for Lanes on this host. Caddy runs as a separate user service with its admin API disabled.
|
|
124
|
+
|
|
125
|
+
Once, a server administrator allows the workspace account to manage Tailscale Serve:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
sudo sh sudo/one-time-root.sh --user workspace
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This does not change SSH daemon settings unless `--ssh-keepalive` is explicitly supplied. Then, in your own
|
|
132
|
+
interactive terminal, either run on the server:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
sh install/enable-phone.sh --local
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
or run from an installed Mac/Linux client:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
sh install/enable-phone.sh --host lanes
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The remote path uses the installed checkout shortcut and the SSH key selected by the client installer;
|
|
145
|
+
there is no password fallback or agent forwarding. It enables the services, prepares a separate `phone` view,
|
|
146
|
+
starts private Tailscale Serve and prints a fresh token. Existing tokens remain valid.
|
|
147
|
+
|
|
148
|
+
Connect Tailscale on the phone, open the printed URL in Safari, paste only the token value, then
|
|
149
|
+
**Share → Add to Home Screen**. A token grants terminal access as the server account. Revoke a named token with:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
~/.local/share/lanes/bin/zellij-0.45.1 web --revoke-token NAME
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`sh install/enable-web.sh` starts only the loopback services and prints no token. The frontend and services
|
|
156
|
+
run on the workspace host; a Mac does not need to stay awake for the phone to work.
|
|
157
|
+
|
|
158
|
+
## 5. Updates, optional operations and removal
|
|
159
|
+
|
|
160
|
+
A server installed with `npx @vibelord/lanes --server` runs from the staged tree `~/.local/share/lanes/pkg/<version>/`; updating is `npx @vibelord/lanes@latest --server`, and the previous versions a service may still run from are kept.
|
|
161
|
+
|
|
162
|
+
Keep the server checkout at a stable location. To move it, run `./install.sh --server` from the new location:
|
|
163
|
+
service definitions and `~/.local/share/lanes/current` are regenerated. Existing workspace configuration is preserved.
|
|
164
|
+
Services get the explicit `LANES_SHARE` and `LANES_STATE` paths supplied at installation, or the defaults below;
|
|
165
|
+
use the same overrides when reinstalling. `LANES_WORKSPACES` is also carried into the generated user services
|
|
166
|
+
when supplied; the standard configuration directory is the simplest default.
|
|
167
|
+
|
|
168
|
+
After updating server code, restart the status observer with `systemctl --user restart lanes-statusd.service`.
|
|
169
|
+
If web service definitions or the pinned Zellij binary changed, restart `lanes-web` and `lanes-mobile` too;
|
|
170
|
+
browser clients reconnect. Existing Zellij view sessions use their original layout until explicitly rebuilt.
|
|
171
|
+
`lanes rebuild DEVICE` replaces only that device's view, never the underlying agent sessions.
|
|
172
|
+
|
|
173
|
+
Normal paths:
|
|
174
|
+
|
|
175
|
+
| Path | Contents |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `~/.config/lanes/` | Site profile, client settings and server workspace configuration |
|
|
178
|
+
| `~/.local/share/lanes/` | Client copy, pinned binaries, download cache, installed checkout link and npm-staged trees (`pkg/<version>/`) |
|
|
179
|
+
| `~/.local/state/lanes/` | Host registry, device selections, status, upload handoff and housekeeping projections |
|
|
180
|
+
| `~/.local/state/lanes-client/` | Client helper heartbeat and logs |
|
|
181
|
+
| `/home/coder/.lz/` in each container | Lane helper, bindings and optional zmx metadata |
|
|
182
|
+
|
|
183
|
+
`lanes pressure`, `lanes sockets check` and `lanes tmp plan` are inspection tools. Review
|
|
184
|
+
[the runbook](RUNBOOK.md) before applying cleanup/repair; read [host protection](HOST-GUARD.md) before enabling
|
|
185
|
+
its root guard. These operations are included in the package but are not an install-time requirement.
|
|
186
|
+
|
|
187
|
+
Clients uninstall with `./install.sh --uninstall`; SSH keys, `~/Lanes Uploads`, and any server workspace
|
|
188
|
+
configuration and state on the same account are kept.
|
|
189
|
+
To stop the host services, disable `lanes-statusd.service`, `lanes-sync.timer`, `lanes-ensure.timer`,
|
|
190
|
+
`lanes-gc.timer`, and any optional units you enabled. For the web endpoint, also run
|
|
191
|
+
`tailscale serve --https=8443 off`. Keep the state and container lane data until you have decided which
|
|
192
|
+
sessions to retain; no broad deletion command is part of uninstall.
|