mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
The two reviews on the PR (DeepSeek Harness, then Claude) found one class of
bug twice and a list of smaller ones; all of them land here, each pinned in
test/install-sh-invariants.test.ts and, where it is bash logic, driven in the
bash:3.2 CI step as well.
The Start line the done screen prints is now composed in one place
(start_command_hint) from every non-default value, the same five the exec
branch exports through export_bind_env, so "do not start" under a sub-path or
a custom port no longer prints a bare `codeman web`. The --lan / --tailscale /
env preset paths read ${CODEMAN_PASSWORD:-$EXISTING_PASSWORD}: a flag re-run on
a unit that carried a password used to rewrite it without the password and
with the unauthenticated ack. --password and --port flip RECONFIGURE so they
reach the unit instead of taking the quiet update path, and `install.sh name`
re-syncs the unit's base URL after the mapping is re-added.
Also: the sudo keepalive is ended before the exec into the foreground server
(exec skips the EXIT trap, and the loop keys on $$); Ctrl+C in the HTTPS-toggle
poll is trapped for the poll only and skips Tailscale for the run instead of
killing the installer; uninstall asks before removing a LaunchDaemon this
installer never wrote; a foreign daemon gets a launchctl kickstart hint and the
done screen stops claiming the new build is running; the preflight summary
reads the Tailscale state with a line grep when node is not installed yet; the
LAN security notice uses the configured port; a bare re-run ends on the done
screen; a build failure after a rename names the install.sh tailscale
recovery; TS_JOINED_HERE (written, never read) is gone; the plan doc and
architecture-invariants say what the code does. A minor changeset is included.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
381 lines
26 KiB
Markdown
381 lines
26 KiB
Markdown
# Installer v2: three questions, then a URL you can open on your phone (Plan)
|
|
|
|
Status: **Phase 1 IMPLEMENTED (2026-09-20)**, phases 2 and 3 open. It builds on
|
|
`docs/tailscale-installer-plan.md` (implemented 2026-08-04), which made Tailscale a
|
|
guided option; this round makes it the thing the install ENDS on, and makes the whole
|
|
installer shorter to sit through. Owner decisions taken before implementation: rename
|
|
is opt-in and **defaults to no everywhere** (the machine name is used for other things);
|
|
the URL keeps the node name unless asked; `codeman-<hostname>` is the suggested name;
|
|
sub-path is the default for an occupied `:443`.
|
|
|
|
Verification record for phase 1 (all on the maintainer's box, 2026-09-20):
|
|
|
|
- `test/install-sh-invariants.test.ts` (28 tests, incl. the new Tailscale safety pins)
|
|
and the detection-parity test pass; `bash -n` passes.
|
|
- Every new decision function driven with stubbed tailscale state under **bash 5.2 and
|
|
bash 3.2** (the `bash:3.2` container CI uses): flags, the launch default, the serve
|
|
shape for free / ours / occupied `:443` (all four answers plus the non-interactive
|
|
default), the three serve commands, the rename question (Enter keeps the name; `--yes`
|
|
and non-interactive never rename; `codeman-*` nodes are skipped; `--name` is
|
|
sanitized), `run_step` success/failure/stdin, the unit round-trip of
|
|
`CODEMAN_BASE_URL`/`CODEMAN_PORT`/an escaped password, and the done screen.
|
|
- A full non-interactive install into a sandboxed `HOME` with `CODEMAN_TAILSCALE=1`:
|
|
preflight summary, kept the existing prod mapping (no serve mutation), clone 2 s,
|
|
`npm install` 18 s, build 23 s, symlink, done screen; `install.sh status` on a pty
|
|
renders the QR code. Nothing on the real system changed.
|
|
- **Sub-path mode end to end over the real tailnet**: an isolated Codeman
|
|
(`CODEMAN_INSTANCE`, port 3999, `--base-url /codeman`) behind
|
|
`tailscale serve --https=8445 --set-path /codeman 3999` answered `/codeman/api/status`,
|
|
`/codeman/` (with `<base href="/codeman/">` and `__CODEMAN_BASE__="/codeman"`), the
|
|
hashed CSS/JS, `/codeman` without a slash, and the SSE stream; mapping and server
|
|
removed afterwards. **Correction to section 2**: serve STRIPS the mount prefix
|
|
before proxying (a direct `/codeman/api/status` on the server is 404 while the same
|
|
path through serve is 200). That is fine because Codeman's ingress tolerates
|
|
unprefixed requests; `--base-url` is needed for the URLs Codeman EMITS, not for
|
|
what it receives.
|
|
- Not yet exercised on a fresh machine (unchanged from the previous plan): Tailscale
|
|
absent / logged out / HTTPS toggle off, the rename against a real node (the
|
|
off-rename-re-add order is implemented but only unit-driven), macOS, uninstall. The
|
|
Mac mini and a throwaway VM are the venues; see section 8.
|
|
- **Review fixes (2026-09-21)**, from the two reviews on PR #460 (DeepSeek Harness, then
|
|
Claude): the done screen's Start line is composed from every non-default value
|
|
(`start_command_hint`, shared with the exec branch as `export_bind_env`), so "do not
|
|
start" under a sub-path or a custom port no longer prints a bare `codeman web`; the
|
|
`--lan`/`--tailscale`/env preset paths keep an existing password instead of rewriting
|
|
the unit open; `--password`/`--port` flip `RECONFIGURE` so they reach the unit;
|
|
`install.sh name` re-syncs the unit's base URL after a rename; the sudo keepalive is
|
|
ended before the `exec` into the foreground server; Ctrl+C in the HTTPS-toggle poll
|
|
skips Tailscale instead of killing the run; `uninstall` asks before removing a
|
|
LaunchDaemon it never wrote; a foreign LaunchDaemon gets a restart hint and the done
|
|
screen stops claiming the new build is running; the preflight summary reads the
|
|
Tailscale state without node; the LAN security notice uses the configured port; a
|
|
bare re-run ends on the done screen; a build failure after a rename names the
|
|
`install.sh tailscale` recovery; `TS_JOINED_HERE` is gone.
|
|
|
|
Goal, in one sentence: a user runs the one-liner, answers at most three questions, walks
|
|
away during the build, and comes back to `https://<name>.<tailnet>.ts.net` printed with a
|
|
QR code, already answering, on every device in their tailnet. That is exactly the
|
|
maintainer's own production setup (`tnode.tailf80371.ts.net` fronting `127.0.0.1:3000`),
|
|
and the installer should produce it without the user knowing what `tailscale serve` is.
|
|
|
|
## 1. Where the installer is today
|
|
|
|
Facts from reading `install.sh` (2886 lines, 19 `prompt_yes_no` sites) and the live
|
|
Tailscale state on the maintainer's box (tailscale 1.102.2, user-owned node, MagicDNS +
|
|
HTTPS certs on, serve mapping `443 -> https+insecure://localhost:3000`).
|
|
|
|
**The order is backwards for a human.** The flow is: detect -> ask about git -> ask about
|
|
node -> ask about tmux -> ask about build tools -> AI CLI menu -> ask about cloudflared ->
|
|
clone -> `npm install` -> build (minutes) -> **then** the network-access question -> the
|
|
Tailscale sub-steps (install? login URL, sudo for operator, admin-console toggle loop) ->
|
|
the launch menu (no default; a bare Enter re-prompts) -> tunnel-service question. A fresh
|
|
Ubuntu server taking the Tailscale route answers roughly ten prompts plus two to four sudo
|
|
password prompts, split around a multi-minute build. The user cannot walk away at any
|
|
point, and the question that matters most (how do I reach it) comes last.
|
|
|
|
**The Tailscale flow works but was never exercised on a fresh machine.** The previous
|
|
plan's manual matrix still lists items 1-4, 7 and 10-12 (Tailscale absent, logged out,
|
|
HTTPS toggle off, port 443 occupied, macOS, uninstall, phone PWA) as untested. The
|
|
maintainer's own verification was the idempotent "kept as-is" path.
|
|
|
|
**The URL is the machine's name, full stop.** `setup_tailscale_serve` derives it from
|
|
`.Self.DNSName`, and nothing lets the user influence it. A second Codeman on the same
|
|
tailnet is `macminis-mac-mini.tailf80371.ts.net`, which tells you nothing about Codeman.
|
|
|
|
**Port 443 taken means give up or clobber.** If another app already owns the root of
|
|
`:443`, the only offer is "replace it?" (default no), and declining falls back to
|
|
local-only. Codeman already supports running under a sub-path (`--base-url`), and
|
|
Tailscale serve supports mounting a path (`--set-path`), so there is a third answer nobody
|
|
is offered.
|
|
|
|
**The result is invisible afterwards.** Once the terminal scrolls away, nothing in the app
|
|
or the CLI tells the user their Tailscale URL again. `codeman doctor` does not probe
|
|
Tailscale; App Settings -> Remote access shows only the Cloudflare tunnel.
|
|
|
|
**Two service writers exist.** `install.sh` carries its own plist/unit generator (~180
|
|
lines) next to `codeman service install` (`src/service-installer.ts`). They agree on the
|
|
job name by design, but the bash copy is the one that writes `CODEMAN_PASSWORD` into the
|
|
unit, so they cannot simply be merged. Left as-is in this plan (see section 9).
|
|
|
|
## 2. What Tailscale makes possible for the name (researched 2026-09-20)
|
|
|
|
| Option | Resulting URL | What it needs | Side effects | Verdict |
|
|
| ------ | ------------- | ------------- | ------------ | ------- |
|
|
| **A. Node name** (today) | `https://tnode.tailf80371.ts.net` | `tailscale serve --bg 3000` | none | **Default.** Zero admin-console work, matches the maintainer's prod. |
|
|
| **B. Rename the node** | `https://codeman-tnode.tailf80371.ts.net` | `tailscale set --hostname codeman-<host>` (operator or root) | Renames the machine tailnet-wide: ssh targets, other serve URLs, the admin console entry. Tailscale de-dups a clash as `-1`. The cert follows the new name. | **Opt-in, default NO everywhere** (owner decision 2026-09-20: the machine is used for other things, so a bare Enter never renames it). The proposal was YES when the installer itself had just joined the tailnet; rejected. |
|
|
| **C. Tailscale Service** | `https://codeman.tailf80371.ts.net` | tailscale >= 1.86 on the host; the host must have a **tag-based identity** ("You cannot use a device authenticated with a user account as a Service host"); the service is defined in the admin console first; the host is then approved there (or via `autoApprovers.services`). Public beta since 2025-10-28, all plans. | Re-authenticating a personal machine as a tagged node changes its identity (SSH ACLs, user attribution). Known daemon quirk: approval is not picked up until `serve clear` + re-advertise (tailscale/tailscale#18821). | **Detect and hint only** in this round. The maintainer's own node has `Self.Tags: null`, so it could not host one without re-tagging. Worth a real flow once someone with a tagged fleet asks. |
|
|
| **D. Sub-path** | `https://tnode.tailf80371.ts.net/codeman` | `tailscale serve --bg --set-path /codeman 3000` plus `--base-url /codeman` on the server | Codeman runs under a prefix. Hooks are unaffected (they hit the raw port with no prefix, which `rewriteUrl` already tolerates). Serve forwards the prefix unchanged, which is exactly the shape `--base-url` was built for. | **The answer when `:443` root is already taken.** Replaces today's replace-or-nothing prompt. |
|
|
| **E. Second port** | `https://tnode.tailf80371.ts.net:8443` | `tailscale serve --bg --https=8443 3000` | Port in the URL; the beta-preview recipe already uses this. | Fallback when the user rejects D. |
|
|
| Funnel (public internet) | `https://tnode.tailf80371.ts.net` from anywhere | `tailscale funnel` | Public exposure; different risk class. | **Out of scope**, as before. Docs only, with the password warning. |
|
|
|
|
Sources: Tailscale Services docs (`tailscale.com/docs/features/tailscale-services`), the
|
|
Services beta announcement (`tailscale.com/blog/services-beta`), machine names
|
|
(`tailscale.com/kb/1098/machine-names`), the serve CLI reference
|
|
(`tailscale.com/docs/reference/tailscale-cli/serve`), the macOS variants page
|
|
(`tailscale.com/docs/concepts/macos-variants`), and `tailscale serve --help` on 1.102.2
|
|
(which lists `--service`, `--set-path`, `--yes`, `advertise`, `get-config`/`set-config`).
|
|
|
|
**Trap for option B (verify on the Mac mini before shipping):** the serve config is keyed
|
|
by `host:port` using the DNS name at configuration time (`"Web": {"tnode.tailf80371.ts.net:443": ...}`
|
|
in `serve status --json`). Renaming a node after serve is configured most likely orphans that
|
|
entry: the handler lookup uses the current name and never matches the old key, and the only
|
|
tool that removes a stale key is `serve reset`, which this installer must never run. So the
|
|
order is **rename first, then configure serve** on a fresh install, and on a retrofit
|
|
(`install.sh name`) **turn our mapping off, rename, wait for `.Self.DNSName` to change,
|
|
re-add**.
|
|
|
|
## 3. Target UX
|
|
|
|
### 3.1 Three questions, then walk away
|
|
|
|
```
|
|
Codeman installer
|
|
|
|
Found: git, Node 22.14, tmux 3.4, build tools Missing: nothing
|
|
AI CLIs: Claude Code (~/.local/bin/claude)
|
|
Tailscale: connected as tnode (tailf80371.ts.net)
|
|
Existing: none
|
|
|
|
1/3 How should the dashboard be reachable?
|
|
1) Tailscale https://tnode.tailf80371.ts.net (recommended, already connected)
|
|
2) Any device on your network (0.0.0.0, password required)
|
|
3) This machine only (127.0.0.1)
|
|
Choose [1/2/3] (default 1):
|
|
|
|
2/3 Name this machine "codeman-tnode" on your tailnet? [y/N]
|
|
(only shown for option 1; default no, always)
|
|
|
|
3/3 Run Codeman as a background service that starts on boot? [Y/n]
|
|
|
|
Installing… this takes a few minutes. You can leave this running.
|
|
✓ dependencies ✓ clone ✓ build (2m 41s) ✓ service ✓ tailscale serve
|
|
```
|
|
|
|
Rules that make this work:
|
|
|
|
- **Every step that needs a human runs BEFORE the build.** The dependency consent, the
|
|
AI CLI menu, the Tailscale install consent, the `tailscale up` login URL, the operator
|
|
grant, and the tailnet HTTPS toggle all move into the question phase. The build, the
|
|
service, `tailscale serve` and the verification are unattended.
|
|
- **One consent for all missing system packages.** "Install git, Node 22 and build tools
|
|
now? [Y/n]" replaces four separate prompts. Each package still runs its own
|
|
distro-specific installer.
|
|
- **One sudo prompt.** When anything needs root (packages, the Tailscale installer,
|
|
`tailscale up`, the operator grant), the installer says so once, runs `sudo -v`, and keeps
|
|
the timestamp alive in a background loop until it exits. macOS needs no sudo for the
|
|
Tailscale GUI-app CLI and the pattern still holds for Homebrew packages.
|
|
- **Service is the default.** Enter on the last question installs the service; "run in
|
|
this terminal" and "don't start" stay reachable by answering, and by flag.
|
|
- **The cloudflared question is gone from the main flow.** It is optional, defaults to
|
|
no, and has an in-app toggle (App Settings -> Remote access). The done screen mentions it
|
|
only when `cloudflared` is already installed. The Linux tunnel-service prompt goes with it.
|
|
- **The HTTPS-certificates toggle no longer asks "re-check now?"** The installer prints the
|
|
admin URL, opens it in a browser when one is available (`xdg-open` / `open`, never on a
|
|
headless box), and polls `tailscale status --json` every 5 s for up to 5 minutes. Ctrl+C or
|
|
the timeout falls back exactly as today.
|
|
- **Progress, not silence.** `npm install` and `npm run build` run behind one line each
|
|
with elapsed time; their output goes to `~/.codeman/install.log` and is printed only on
|
|
failure, with the exact retry command.
|
|
|
|
### 3.2 The done screen
|
|
|
|
One block, the URL first, a QR code the phone can scan, and nothing the user does not need
|
|
right now.
|
|
|
|
```
|
|
✓ Codeman 1.31.0 is running
|
|
|
|
Your tailnet: https://codeman-tnode.tailf80371.ts.net (HTTPS, any of your devices)
|
|
This machine: http://localhost:3000
|
|
|
|
▄▄▄▄▄▄▄ ▄ ▄▄ ▄▄▄▄▄▄▄
|
|
█ ▄▄▄ █ ▄▄▀ ▄ █ ▄▄▄ █ scan with your phone
|
|
█ ███ █ ███▀▀ █ ███ █
|
|
█▄▄▄▄▄█ █ ▄ █ █▄▄▄▄▄█
|
|
|
|
Manage systemctl --user restart codeman-web · journalctl --user -u codeman-web -f
|
|
Update re-run the install line, or App Settings → System → Updates
|
|
Docs https://github.com/Ark0N/Codeman/wiki
|
|
|
|
Security: Codeman binds 127.0.0.1. Tailscale authenticates every device before a
|
|
packet reaches it. Details: docs/security-architecture.md
|
|
```
|
|
|
|
The QR comes from the `qrcode` package Codeman already depends on
|
|
(`node -e "require('qrcode').toString(url, {type:'terminal', small:true}, …)"` from
|
|
`$INSTALL_DIR`, verified locally: 17 rows by 45 columns). Skipped when the terminal has no
|
|
color support or fewer than 50 columns. The QR encodes the plain URL, not an auth token:
|
|
the tailnet is the login.
|
|
|
|
### 3.3 Express mode and flags
|
|
|
|
Env vars stay (`CODEMAN_TAILSCALE=1`, `CODEMAN_HOST`, `CODEMAN_PASSWORD`,
|
|
`CODEMAN_NONINTERACTIVE=1`, `CODEMAN_PORT`). Flags are added because they are
|
|
discoverable from the one-liner and pipe through `bash -s --`:
|
|
|
|
```bash
|
|
curl -fsSL https://getcodeman.com/install | bash -s -- --tailscale --service
|
|
curl -fsSL https://getcodeman.com/install | bash -s -- --lan --password 'x' --service
|
|
curl -fsSL https://getcodeman.com/install | bash -s -- --local --run
|
|
curl -fsSL https://getcodeman.com/install | bash -s -- --tailscale --name codeman-build --yes
|
|
```
|
|
|
|
| Flag | Meaning |
|
|
| ---- | ------- |
|
|
| `--tailscale` / `--lan` / `--local` | Answer 1/3 (same semantics as `CODEMAN_TAILSCALE=1`, `CODEMAN_HOST=0.0.0.0`, `CODEMAN_HOST=127.0.0.1`) |
|
|
| `--name <n>` / `--no-rename` | Answer 2/3: rename the node to `<n>`, or never ask |
|
|
| `--service` / `--run` / `--no-start` | Answer 3/3 |
|
|
| `--yes` | Accept every default, still prompt for a login URL (a human must open it) |
|
|
| `--password <p>` | Same as `CODEMAN_PASSWORD` |
|
|
| `--port <n>` | Same as `CODEMAN_PORT`; the serve target follows it |
|
|
|
|
`--yes` differs from `CODEMAN_NONINTERACTIVE=1`: it is the interactive user saying "I trust
|
|
the defaults", so it may install software and may wait on a login URL. Non-interactive stays
|
|
the CI contract and never installs Tailscale.
|
|
|
|
## 4. The Tailscale flow, v2
|
|
|
|
The state machine from the previous plan stays; these are the changes.
|
|
|
|
1. **Preflight, before the build** (`tailscale_preflight`): installed? -> install
|
|
(Linux: official script; macOS: brew cask, else download link and wait). Logged in? ->
|
|
`tailscale up` with the URL printed prominently and a 5-minute poll. Operator (Linux):
|
|
grant once under the single sudo session. HTTPS certs: poll instead of ask (Ctrl+C
|
|
during the poll skips Tailscale for this run rather than ending the installer). The
|
|
rename default does not depend on whether this run performed the login (decided NO
|
|
everywhere), so nothing records it.
|
|
2. **Name** (`tailscale_choose_name`, question 2/3): shown only on the Tailscale route.
|
|
Default `codeman-<oshostname>` sanitized to `[a-z0-9-]`, max 63. Applied with
|
|
`ts_cmd_serve set --hostname`, then poll `.Self.DNSName` until it carries the new name
|
|
(up to 60 s). Order matters: this runs before any serve mutation (section 2 trap).
|
|
Declining keeps the node name. On a re-run against a node already named `codeman-*`,
|
|
the question is skipped.
|
|
3. **Serve, after the service is up** (`setup_tailscale_serve`): unchanged idempotent
|
|
"kept as-is" path first. When `:443` root belongs to another target, the new prompt is:
|
|
|
|
```
|
|
tailscale serve already sends https://tnode.tailf80371.ts.net to port 8080.
|
|
1) Add Codeman under a path: https://tnode.tailf80371.ts.net/codeman (default)
|
|
2) Use another port: https://tnode.tailf80371.ts.net:8443
|
|
3) Replace the existing mapping with Codeman
|
|
4) Skip Tailscale for now
|
|
```
|
|
|
|
Option 1 writes `--base-url /codeman` into the service unit (it is a `WebLaunchOptions`
|
|
field already, and `buildWebArgs` carries it) and runs
|
|
`tailscale serve --bg --set-path /codeman <port>`. Option 2 runs `--https=8443`.
|
|
`detect_tailscale_serve_url` learns to recognize all three shapes (root, path, port) so
|
|
uninstall, the security notice and the re-run default keep working.
|
|
4. **Warm the certificate.** Right after serve is configured, fire one background
|
|
`curl -sk https://<url>/api/status` so Let's Encrypt issuance overlaps the rest of the
|
|
install instead of adding 30 s to the verify step.
|
|
5. **Verify** as today (200 or 401 on `/api/status`), with the path-aware URL.
|
|
6. **Services hint** (option C): when `.Self.Tags` is non-empty and `serve --help`
|
|
lists `--service`, the done screen adds one line: "This is a tagged node, so it can also
|
|
host `https://codeman.<tailnet>.ts.net` as a Tailscale Service: see Remote Access in the
|
|
wiki." No flow, no prompt.
|
|
7. **macOS**: the App Store and Standalone variants cannot run before login, so a
|
|
LaunchAgent plus serve only comes back after someone logs in. The done screen says so on
|
|
macOS. The Mac mini (`arbbot`, headless, system LaunchDaemon) is the reference for the
|
|
"headless Mac" caveat, and `install.sh` must keep refusing to replace a LaunchDaemon it
|
|
did not write (today it removes one; that is a bug for the Mac mini and is fixed here:
|
|
detect `UserName` in the daemon plist and leave it alone with a message).
|
|
8. **Uninstall** additionally offers to restore the original node name when this installer
|
|
renamed it (the original is recorded in `~/.codeman/install.json`, the one marker file
|
|
this feature adds, because tailscaled does not remember previous names).
|
|
9. **Subcommands**: `install.sh tailscale` (unchanged purpose, now runs the v2 flow),
|
|
`install.sh name [<n>]` (rename with the off/rename/re-add dance), `install.sh status`
|
|
(prints the done screen again, URL and QR included, for the "what was my URL" moment).
|
|
|
|
## 5. In-app: the URL stays discoverable
|
|
|
|
Small, read-only, and the first server-side code this feature has ever needed.
|
|
|
|
- **`GET /api/system/remote-access`** returns
|
|
`{ tailscale: { installed, connected, dnsName, url, mode: 'root'|'path'|'port'|null } }`
|
|
by running `tailscale status --json` and `tailscale serve status --json` through
|
|
`execFile` with the existing exec timeout, cached 30 s, resolved through the same
|
|
`get_tailscale_path` search as the installer (PATH, then the macOS app bundle), and a
|
|
no-op under `VITEST` like every other IO probe. Never mutates serve config.
|
|
- **App Settings -> Remote access** gains a **Tailscale** row above the Cloudflare toggle:
|
|
the URL as a copy chip, a QR button reusing `showTunnelQR`'s modal, and when nothing is
|
|
configured a one-line hint with `bash ~/.codeman/app/install.sh tailscale`. The welcome
|
|
screen's "open on your phone" affordance shows the same QR.
|
|
- **`codeman doctor`** grows a `tailscale` entry under `other` in
|
|
`config/dependency-registry.ts`: installed, connected, serving Codeman (URL). Pure
|
|
engine, injectable probe host, like the existing rows.
|
|
- No new SSE event, no settings key, no state.json change.
|
|
|
|
## 6. Security posture
|
|
|
|
Nothing widens. The bind stays loopback; the tailnet is the authentication boundary;
|
|
`.ts.net` is already in `DEFAULT_TRUSTED_HOST_SUFFIXES`. New surfaces are read-only
|
|
probes. `install.sh` still never runs `tailscale serve reset`, still touches only the
|
|
mapping it created, and gains one more never: it never advertises a Tailscale Service or
|
|
runs `tailscale funnel`. The sudo keep-alive loop is killed by the existing `cleanup` trap.
|
|
The rename records the previous name locally and offers the reversal at uninstall.
|
|
|
|
## 7. Implementation inventory
|
|
|
|
| File | Change |
|
|
| ---- | ------ |
|
|
| `install.sh` | New `parse_flags`, `preflight_summary`, `ask_everything` (the three questions), `sudo_session`, `run_step` (spinner + log), `tailscale_preflight`, `tailscale_choose_name`, `tailscale_rename_node`, `print_done_screen`, `print_qr`, `status` subcommand, `name` subcommand. Modified: `main` (reordered into ask -> work -> done), `choose_network_binding` (question 1/3, same defaults), `setup_tailscale_serve` (path/port options), `detect_tailscale_serve_url` (three shapes), `setup_systemd_service`/`setup_launchd_service` (`--base-url`, LaunchDaemon guard), `uninstall` (rename reversal), header docs (flags). Removed from the main flow: the cloudflared prompt, the tunnel-service prompt. bash 3.2 rules unchanged. |
|
|
| `src/web/routes/system-routes.ts` | `GET /api/system/remote-access` |
|
|
| `src/tailscale-status.ts` (new) | Pure parser for the two JSON shapes + the IO wrapper; unit-tested against captured `serve status --json` fixtures (root, path, port, foreign target, none) |
|
|
| `src/config/dependency-registry.ts`, `src/utils/dependency-checker.ts` | `tailscale` doctor row |
|
|
| `src/web/public/index.html`, `settings-ui.js`, `panels-ui.js` | Tailscale row + QR, welcome-screen QR |
|
|
| `test/install-sh-invariants.test.ts` | Extend: flags documented in the header, no `serve reset`, no `funnel`, no `--service` advertise, every serve mutation goes through `ts_cmd_serve`, rename happens before serve in `main` (static order check) |
|
|
| `.github/workflows/ci.yml` | The bash 3.2 step additionally sources the script with stubbed `ts_cmd`/`ts_cmd_serve`/`read_reply` and drives `ask_everything` through all three answers and the 443-occupied menu |
|
|
| `test/tailscale-status.test.ts`, `test/routes/system-routes-remote-access.test.ts` | Parser + route |
|
|
| Docs | README install + remote-access sections, `docs/wiki/Installation.md`, `Remote-Access.md` (naming options table, Services caveat, path/port variants), `Mobile-Guide.md`, `Running-As-A-Service.md` (macOS login caveat), `FAQ.md`, `docs/security-architecture.md` §A, CLAUDE.md Scripts & Tunnel paragraph, `docs/tailscale-installer-plan.md` gets a pointer here. getcodeman.com copy lives outside the repo (maintainer handbook). |
|
|
|
|
Changeset: `minor` (new flags, new subcommands, new API route).
|
|
|
|
## 8. Test plan
|
|
|
|
Automated (the gate): the static invariants above, the bash 3.2 container drive of the
|
|
question phase, the JSON parser fixtures, the route test.
|
|
|
|
Manual matrix, on a fresh Ubuntu 24 VM and on the Mac mini, since the previous plan's
|
|
items never ran on a fresh machine:
|
|
|
|
1. Tailscale absent, declined -> local-only, done screen shows the retrofit command.
|
|
2. Tailscale absent, accepted -> install, login URL, operator, certs toggle polled, rename
|
|
question shown (default no), service, serve, URL verified, QR scans on a phone, PWA installs.
|
|
3. Tailscale present and logged in on a pre-existing node -> rename default NO, URL is the
|
|
node name, `serve status` gains exactly one entry.
|
|
4. `:443` root occupied -> path option -> `https://<node>/codeman` answers, hooks still
|
|
fire (raw port), `install.sh status` prints the path URL.
|
|
5. Rename on a node that already has our serve mapping (`install.sh name`) -> off, rename,
|
|
re-add, `serve status` has no stale key.
|
|
6. Re-run the one-liner -> quiet update, binding and name preserved, no prompts.
|
|
7. `--yes` end to end; `CODEMAN_NONINTERACTIVE=1` end to end (no software installed).
|
|
8. Uninstall -> mapping removed, other mappings intact, rename reversal offered.
|
|
9. Mac mini: LaunchDaemon left alone with the message; done screen carries the login caveat.
|
|
|
|
## 9. Phasing and open decisions
|
|
|
|
**Phase 1 (this round):** the reorder, the three questions, one consent + one sudo, flags,
|
|
the done screen with QR, Tailscale preflight-before-build, the path/port answer for an
|
|
occupied 443, the rename step, `status` and `name` subcommands, docs.
|
|
|
|
**Phase 2:** the in-app Tailscale row + QR, `codeman doctor` row, the `remote-access`
|
|
route. Independent of phase 1 and useful on its own for existing installs.
|
|
|
|
**Phase 3 (optional):** replace the bash service writers with `codeman service install`
|
|
once that command can carry `CODEMAN_PASSWORD` behind an explicit flag; and a Tailscale
|
|
Services flow if a tagged-fleet user asks for `codeman.<tailnet>.ts.net`.
|
|
|
|
Decisions for the maintainer:
|
|
|
|
1. **Rename default.** Decided 2026-09-20: always NO; the yes answer, `--name` and
|
|
`install.sh name` are the ways in. (The proposal was YES only when this run had joined
|
|
the tailnet, NO otherwise; rejected because the host is used for other things.)
|
|
2. **Name pattern.** `codeman-<hostname>` (proposed; unique per machine, and two Codemans
|
|
on one tailnet stay distinguishable) versus plain `codeman` (nicer once, collides on the
|
|
second install, Tailscale silently appends `-1`).
|
|
3. **Path versus port** as the default answer for an occupied 443. Proposed: path, because
|
|
the URL has no port and `--base-url` already exists for exactly this proxy shape.
|
|
4. **Whether Phase 2 ships in the same release.** It is the part that helps people who
|
|
installed months ago.
|