mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
docs(install): describe installer v2 and the Tailscale naming options
README, the Installation / Remote-Access / Running-As-A-Service wiki pages, docs/security-architecture.md and CLAUDE.md describe the three-question flow, the flags, the subcommands, the sub-path answer for an occupied :443 and why the rename is opt-in. docs/installer-v2-plan.md is the design and the verification record (what was measured, what still needs a fresh machine); docs/tailscale-installer-plan.md points at it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -405,7 +405,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L
|
||||
|
||||
## State Files
|
||||
|
||||
All in `~/.codeman/`: `state.json` (sessions, settings, respawn, orchestrator, cron jobs/runs, owner tab layouts), `mux-sessions.json` (tmux recovery), `settings.json` (user prefs), `push-keys.json` + `push-subscriptions.json`, `session-lifecycle.jsonl` (audit log), `update-status.json` (self-updater progress, polled across the service restart), `docker-env-applied.json` (Compose deployment only: sha256 of the Dockerfile + compose file the running container was built from, written by `Start-Codeman.sh`, read by the self-updater's environment gate), `docker-build-source.json` (Compose deployment only: the checkout's HEAD commit and `package-lock.json` hash the `codeman-node-modules`/`codeman-dist` volumes currently reflect, written by both `Start-Codeman.sh` and a successful in-place self-update, compared to detect and refresh a volume left stale by an externally-triggered rebuild), `linked-cases.json`, `webviews.json` (saved web-tab dashboard URLs), `remote-hosts.json` + `remote-cases.json`, `docker-hosts.json` + `docker-cases.json` + `docker-exports/`, `subagent-window-states.json` + `subagent-parents.json` (subagent window layout, GET/PUT `/api/subagent-window-states`/`-parents`), `hook-secret` (per-instance), `users.json` (multi-user, mode 0600) + `admin-audit.jsonl`, `intents.json` (Read My Mind intent profiles, mode 0600), `certs/` (self-signed TLS for `--https`), `.env` (CODEMAN_USERNAME/PASSWORD fallback for the `codeman attach` CLI). Transient: `self-update-runner.sh`. Multi-user case spaces live OUTSIDE the data dir at `~/codeman-users/<username>/cases` (shared across instances like `~/codeman-cases`, override `CODEMAN_USER_SPACES_DIR`).
|
||||
All in `~/.codeman/`: `state.json` (sessions, settings, respawn, orchestrator, cron jobs/runs, owner tab layouts), `mux-sessions.json` (tmux recovery), `settings.json` (user prefs), `push-keys.json` + `push-subscriptions.json`, `session-lifecycle.jsonl` (audit log), `update-status.json` (self-updater progress, polled across the service restart), `docker-env-applied.json` (Compose deployment only: sha256 of the Dockerfile + compose file the running container was built from, written by `Start-Codeman.sh`, read by the self-updater's environment gate), `docker-build-source.json` (Compose deployment only: the checkout's HEAD commit and `package-lock.json` hash the `codeman-node-modules`/`codeman-dist` volumes currently reflect, written by both `Start-Codeman.sh` and a successful in-place self-update, compared to detect and refresh a volume left stale by an externally-triggered rebuild), `linked-cases.json`, `webviews.json` (saved web-tab dashboard URLs), `remote-hosts.json` + `remote-cases.json`, `docker-hosts.json` + `docker-cases.json` + `docker-exports/`, `subagent-window-states.json` + `subagent-parents.json` (subagent window layout, GET/PUT `/api/subagent-window-states`/`-parents`), `hook-secret` (per-instance), `users.json` (multi-user, mode 0600) + `admin-audit.jsonl`, `intents.json` (Read My Mind intent profiles, mode 0600), `certs/` (self-signed TLS for `--https`), `.env` (CODEMAN_USERNAME/PASSWORD fallback for the `codeman attach` CLI), `install.log` (installer step output, written by `install.sh`'s `run_step`) and `tailscale-rename` (the node name before `install.sh` renamed it, so uninstall can offer it back; both installer-route only). Transient: `self-update-runner.sh`. Multi-user case spaces live OUTSIDE the data dir at `~/codeman-users/<username>/cases` (shared across instances like `~/codeman-cases`, override `CODEMAN_USER_SPACES_DIR`).
|
||||
|
||||
**Generated top-level dirs** (all gitignored — don't edit or commit): `dist/` (esbuild output), `out/`, `coverage/`, `test-results/`, `tmp/`, `screenshots-echo-diag/`. The committed gesture bundle (`src/web/public/gesture/gesture-codeman.js`) IS tracked, but its runtime wasm/model assets (`src/web/public/gesture/wasm/`, `*.task`) are fetched and gitignored.
|
||||
|
||||
@@ -468,6 +468,6 @@ Two constraints worth knowing before you touch them: the env-derived PTY buffer
|
||||
|
||||
## Scripts & Tunnel
|
||||
|
||||
**`install.sh`** (repo root, ~112KB) is the public entry point: `curl -fsSL <raw url> | bash` installs Node/tmux if missing, clones to `~/.codeman/app`, builds, and offers a systemd/launchd service. The network-access prompt is 3-way: **Tailscale** (loopback bind + guided `tailscale serve --bg <port>` HTTPS setup: install/login/operator/tailnet-HTTPS-toggle, then curl-verified end-to-end), **LAN** (0.0.0.0 + password prompt), or **local-only**; it preserves the existing binding on re-runs via `read_existing_binding()`. Tailscale state is detected dynamically from `tailscale serve status --json` (no marker files); the installer must NEVER `tailscale serve reset` or touch serve mappings other than 443→Codeman's port (users have unrelated serve config). `install.sh update`, `install.sh uninstall`, and `install.sh tailscale` (retrofit Tailscale access onto an existing install) also exist; `CODEMAN_NONINTERACTIVE=1` approves system changes for automation, `CODEMAN_TAILSCALE=1` presets the Tailscale choice (never installs Tailscale non-interactively). Its CLI knowledge is a GENERATED block (`npm run generate:cli-catalog`, markers in the file), not a hand-written list: detection, the install menu and the closing reminder all read it, which is what stops the class of bug upstream `b6d0f1fa` fixed by hand (a user with only omp installed being told no AI CLI was found). ⚠️ It must stay **bash 3.2** clean — macOS ships it and the documented install is `curl | bash` under `set -euo pipefail`, so `declare -A`, `mapfile`, namerefs, `${x,,}` and here-strings are all fatal there; CI runs `bash -n` plus a real `bash:3.2` container, since expanding an EMPTY array under `set -u` is a runtime abort `bash -n` cannot see. ⚠️ It executes ONLY commands from the embedded block (`CLI_INSTALL_CMD_TRUSTED`) — there is no network fetch of the catalogue at install time to worry about at all.
|
||||
**`install.sh`** (repo root, ~140KB) is the public entry point: `curl -fsSL <raw url> | bash` installs Node/tmux/git/build tools if missing, clones to `~/.codeman/app`, builds, and offers a systemd/launchd service. Since installer v2 (2026-09-20) it is **look, ask, work, done**: `preflight_detect` prints what is on the machine, every human step runs BEFORE the build (one consent for all missing packages, ONE sudo prompt kept warm by `sudo_session_start`, the AI CLI menu, the Tailscale login/operator/HTTPS-toggle preflight), then the clone/`npm install`/build/service/serve run unattended behind `run_step` spinners (output in `~/.codeman/install.log`, tail shown on failure), and `print_done_screen` ends on the URL with a terminal QR code (the `qrcode` package Codeman already ships). The network-access question is 3-way: **Tailscale** (loopback bind + `tailscale serve`, curl-verified end-to-end), **LAN** (0.0.0.0 + password prompt), or **local-only**; it preserves the existing binding on re-runs via `read_existing_binding()`, which also reads back `CODEMAN_BASE_URL`/`CODEMAN_PORT` from the unit. ⚠️ **Tailscale is two halves on purpose**: `tailscale_prepare` (question phase: preflight, the opt-in rename, the serve SHAPE) and `tailscale_apply` (after the build: the one serve command). The shape is decided up front because it can change the service unit: when `:443` root already belongs to another app, the default is a **sub-path** (`tailscale serve --bg --set-path /codeman <port>` + `CODEMAN_BASE_URL=/codeman` in the unit; measured 2026-09-20: serve STRIPS the mount prefix before proxying, Codeman's `stripBasePath` tolerates unprefixed requests, and `--base-url` is what makes the emitted URLs carry it, verified live through the maintainer's tailnet incl. hashed assets and SSE), else a second port (8443+), replace, or skip. `detect_tailscale_serve_url` recognises all three shapes (`ts_serve_find_port_mapping`). ⚠️ **Rename is opt-in and defaults to NO everywhere** (owner decision 2026-09-20: the tailnet name is the machine's SSH identity); `--name`/`install.sh name` do it, `--yes` and non-interactive never do. Serve config is keyed by the DNS name it was written under, so `tailscale_rename_node` takes OUR mapping down first (`tailscale_remove_our_mapping`, `TS_MAPPING_REMOVED_BY_RENAME`) and it is re-added under the new name; the previous name is recorded in `~/.codeman/tailscale-rename` so uninstall can offer it back. Tailscale state is detected dynamically from `tailscale serve status --json` (no marker files); the installer must NEVER `tailscale serve reset`, touch a mapping it did not create, run `tailscale funnel` or advertise a Tailscale Service (a Service needs a TAGGED node + admin approval, so it is a docs hint only); `test/install-sh-invariants.test.ts` pins all of those plus the rename-before-shape order and the flag/header parity. A foreign `/Library/LaunchDaemons/com.codeman.web.plist` (the Mac mini's headless setup) is now LEFT ALONE rather than replaced by a LaunchAgent. Subcommands: `update`, `uninstall`, `tailscale` (retrofit), `name [<n>]`, `status` (the done screen again), `cloudflared` (the tunnel client, no longer a question in the main flow); flags `--tailscale|--lan|--local`, `--name|--no-rename`, `--service|--run|--no-start`, `--yes`, `--password`, `--port` pipe through `bash -s --` and set the same variables as their env twins (`CODEMAN_NONINTERACTIVE=1` approves system changes for automation and never installs Tailscale, renames or starts a service; `CODEMAN_TAILSCALE=1` presets the Tailscale choice). Design + verification record: `docs/installer-v2-plan.md`. Its CLI knowledge is a GENERATED block (`npm run generate:cli-catalog`, markers in the file), not a hand-written list: detection, the install menu and the closing reminder all read it, which is what stops the class of bug upstream `b6d0f1fa` fixed by hand (a user with only omp installed being told no AI CLI was found). ⚠️ It must stay **bash 3.2** clean — macOS ships it and the documented install is `curl | bash` under `set -euo pipefail`, so `declare -A`, `mapfile`, namerefs, `${x,,}` and here-strings are all fatal there; CI runs `bash -n` plus a real `bash:3.2` container, since expanding an EMPTY array under `set -u` is a runtime abort `bash -n` cannot see. ⚠️ It executes ONLY commands from the embedded block (`CLI_INSTALL_CMD_TRUSTED`) — there is no network fetch of the catalogue at install time to worry about at all.
|
||||
|
||||
Other key scripts: `scripts/tmux-manager.sh` (safe tmux mgmt), `scripts/tunnel.sh [quick|named] start|stop|status|url` (quick = random trycloudflare URL, default; `named setup|enable` = fixed-hostname tunnel via `scripts/codeman-tunnel-named.service`; bare `start|stop|url` still means quick), `scripts/run-beta.sh` (isolated beta instance), `scripts/build-agent-image.mjs` (docker base image), `scripts/self-update.sh` (detached updater). Production services: `scripts/codeman-web.service`, `scripts/codeman-tunnel.service`. **Always set `CODEMAN_PASSWORD`** before exposing via tunnel.
|
||||
|
||||
@@ -61,12 +61,13 @@ The installer asks before every system change, and re-running the same line upda
|
||||
curl -fsSL https://getcodeman.com/install | bash
|
||||
```
|
||||
|
||||
This installs Node.js, tmux and a build toolchain if missing (node-pty ships no Linux prebuilds, so it compiles from source), clones Codeman to `~/.codeman/app`, and builds it. A few things worth knowing:
|
||||
This installs Node.js, tmux and a build toolchain if missing (node-pty ships no Linux prebuilds, so it compiles from source), clones Codeman to `~/.codeman/app`, and builds it. It looks at what is already on the machine, asks at most three questions, then does all the work unattended and ends on the URL with a QR code for your phone. A few things worth knowing:
|
||||
|
||||
- **It asks first.** Every system change (package installs, AI CLI download) is prompted, and a menu at the end lets you choose: run Codeman in this terminal, install it as a background service (systemd/launchd, auto-start on boot), or don't start yet. Nothing runs in the background unless you pick it.
|
||||
- **How it's reachable, your choice.** The installer offers three ways to reach the dashboard: **Tailscale** (loopback bind fronted by `tailscale serve`, so you get `https://<machine>.<tailnet>.ts.net` with a real certificate and your tailnet as the login, no password needed), **any device on your network** (`0.0.0.0`, with a strongly recommended password prompt), or **this machine only** (`127.0.0.1`, safest). Skipping the password on a network bind requires an explicit confirmation and ends with a loud warning. The highlighted default reflects what is already on the machine (Tailscale when it is already in use, your existing binding on a re-run), and a bare Enter never pulls in new software. A bare `codeman web` started by hand still defaults to loopback.
|
||||
- **Re-run to update.** The same one-liner updates a finished install in place: local changes in `~/.codeman/app` are stashed (never discarded), and a running service is restarted and verified. If a first install was interrupted, re-running resumes the full setup instead. `install.sh update` and `install.sh uninstall` also exist.
|
||||
- **CI / headless:** without a terminal attached, steps that would change your system abort with instructions instead of running silently. Set `CODEMAN_NONINTERACTIVE=1` to approve them for automation.
|
||||
- **Three questions, all up front.** How the dashboard is reached, optionally what to call this machine on your tailnet, and whether to run Codeman as a background service (systemd/launchd, auto-start on boot; Enter says yes). Everything that needs you, including one consent for all missing packages, one sudo password, and the Tailscale login, happens before the build, so you can walk away while it compiles.
|
||||
- **How it's reachable, your choice.** **Tailscale** (loopback bind fronted by `tailscale serve`, so you get `https://<machine>.<tailnet>.ts.net` with a real certificate and your tailnet as the login, no password needed), **any device on your network** (`0.0.0.0`, with a strongly recommended password prompt), or **this machine only** (`127.0.0.1`, safest). Skipping the password on a network bind requires an explicit confirmation and ends with a loud warning. The highlighted default reflects what is already on the machine (Tailscale when it is already connected, your existing binding on a re-run), and a bare Enter never pulls in new software. If another app already owns `:443` on your node, Codeman goes under `https://<machine>.<tailnet>.ts.net/codeman` or on a second port instead of replacing it. A bare `codeman web` started by hand still defaults to loopback.
|
||||
- **The name is yours to choose.** By default the URL uses the machine's existing tailnet name. Answering yes to the second question renames the machine to `codeman-<hostname>` (which also renames it for SSH, so the default is no); `install.sh name` does it later.
|
||||
- **Re-run to update.** The same one-liner updates a finished install in place: local changes in `~/.codeman/app` are stashed (never discarded), and a running service is restarted and verified. If a first install was interrupted, re-running resumes the full setup instead. `install.sh status` prints the URLs and the QR code again; `install.sh update`, `install.sh tailscale` and `install.sh uninstall` also exist.
|
||||
- **Flags for the impatient.** `curl -fsSL https://getcodeman.com/install | bash -s -- --tailscale --service` answers the questions from the command line (`--lan`, `--local`, `--run`, `--no-start`, `--name <n>`, `--port <n>`, `--yes` too). **CI / headless:** without a terminal attached, steps that would change your system abort with instructions instead of running silently; set `CODEMAN_NONINTERACTIVE=1` to approve them for automation.
|
||||
|
||||
You'll need at least one AI coding CLI installed — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [OpenCode](https://opencode.ai), [Codex](https://developers.openai.com/codex/cli), [Antigravity](https://antigravity.google), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Pi](https://pi.dev), [Grok Build](https://github.com/xai-org/grok-build), [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), or [OMP](https://github.com/can1357/oh-my-pi) (any combination works; Gemini CLI is enterprise-only since Google's consumer cutover, and Antigravity is its successor). The installer detects whichever of the nine is present; if none is found, it offers to install any of them from a menu (DeepSeek excepted, since its npm package installs only a launcher with no runnable profile), or you can skip and install one yourself later. After install:
|
||||
|
||||
@@ -219,7 +220,7 @@ codeman web --https
|
||||
# Open on your phone: https://<your-ip>:3000
|
||||
```
|
||||
|
||||
> `localhost` works over plain HTTP. Use `--https` when accessing from another device, or use [Tailscale](https://tailscale.com/) (recommended): the installer can set it up for you (choose **Tailscale** at the network-access prompt, or run `bash ~/.codeman/app/install.sh tailscale` on an existing install). That gives you `https://<your-machine>.<tailnet>.ts.net` with a real certificate: private to your tailnet, no password required, and PWA install + push notifications work on your phone.
|
||||
> `localhost` works over plain HTTP. Use `--https` when accessing from another device, or use [Tailscale](https://tailscale.com/) (recommended): the installer can set it up for you (choose **Tailscale** at the network-access prompt, or run `bash ~/.codeman/app/install.sh tailscale` on an existing install). That gives you `https://<your-machine>.<tailnet>.ts.net` with a real certificate: private to your tailnet, no password required, and PWA install + push notifications work on your phone. The installer ends on that URL with a QR code to scan, and `bash ~/.codeman/app/install.sh status` prints it again any time.
|
||||
|
||||
### Secure QR Code Authentication
|
||||
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
# 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.
|
||||
|
||||
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 YES only when the installer itself just joined this machine to the tailnet (nobody depends on the old name yet), default NO on a pre-existing node. |
|
||||
| **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 Y when the installer just joined the tailnet)
|
||||
|
||||
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. Record
|
||||
`TS_JOINED_HERE=1` when this run performed the login: it drives the rename default.
|
||||
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
|
||||
default YES, 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.** Proposed: default YES only when this run joined the tailnet,
|
||||
default NO otherwise, never on re-runs. The alternative is always NO with `--name` as
|
||||
the only way in.
|
||||
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.
|
||||
@@ -270,9 +270,15 @@ tailscale serve --bg 3000 # HTTPS at https://<node>.<tailnet>.ts.net
|
||||
Only devices on your tailnet can reach it; Tailscale handles identity and
|
||||
terminates TLS with a real Let's Encrypt certificate (so PWA install and web
|
||||
push work). No app password and no `0.0.0.0` bind required. (This is the
|
||||
maintainer's production setup.) `CODEMAN_TAILSCALE=1` presets the choice for
|
||||
automation; the installer never runs `tailscale serve reset` and never touches
|
||||
serve mappings other than `443 -> Codeman's port`.
|
||||
maintainer's production setup.) `CODEMAN_TAILSCALE=1` or `--tailscale` presets
|
||||
the choice for automation. When `:443` on the node already belongs to another
|
||||
app, the installer mounts Codeman under `/codeman` (`tailscale serve --set-path`
|
||||
plus `--base-url`, which keeps the loopback bind and the same host guard) or on a
|
||||
second port rather than replacing it. The installer never runs `tailscale serve
|
||||
reset`, never touches serve mappings other than the one it created, never opens a
|
||||
`tailscale funnel` (public internet, a different risk class) and never advertises
|
||||
a Tailscale Service. Renaming the node (`--name`, `install.sh name`) is opt-in
|
||||
and defaults to no, because the tailnet name is also the machine's SSH identity.
|
||||
|
||||
### B. Authenticated cloudflared tunnel + password
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Tailscale Setup in the Installer (Plan)
|
||||
|
||||
> Superseded in part by [`installer-v2-plan.md`](installer-v2-plan.md) (2026-09-20), which
|
||||
> moved every human step before the build, added the sub-path / second-port answer for an
|
||||
> occupied `:443`, the opt-in rename, flags, and the done screen with a QR code. The
|
||||
> state machine and safety rules below still hold.
|
||||
|
||||
Goal: make "Codeman over Tailscale, with real HTTPS" a first-class, guided path in
|
||||
`install.sh`, instead of a one-line hint pointing at the docs. Today the safest
|
||||
recommended deployment (loopback bind + `tailscale serve`) is exactly what the
|
||||
|
||||
+45
-13
@@ -24,16 +24,22 @@ This installs Node.js, tmux and a build toolchain if they are missing (node-pty
|
||||
Linux prebuild, so it compiles from source), clones Codeman into `~/.codeman/app`, and
|
||||
builds it.
|
||||
|
||||
What it asks you:
|
||||
It starts by printing what it found (git, Node, tmux, build tools, agent CLIs, Tailscale,
|
||||
an existing install), then asks everything it needs up front, then does the work
|
||||
unattended. You can leave while it builds. What it asks you:
|
||||
|
||||
1. **Permission for every system change.** Package installs and agent CLI downloads are
|
||||
prompted individually. Nothing is installed silently. If no agent CLI is found, a menu
|
||||
offers to install any of them (DeepSeek excepted: its npm package installs only a
|
||||
launcher with no runnable profile), or you skip and install one yourself later.
|
||||
1. **One consent for the missing packages.** Git, Node.js, tmux and (on Linux) the build
|
||||
toolchain are installed after a single yes, and sudo asks for your password once for
|
||||
the whole run. Nothing is installed silently. If no agent CLI is found, a menu offers
|
||||
to install any of them (DeepSeek excepted: its npm package installs only a launcher
|
||||
with no runnable profile), or you skip and install one yourself later.
|
||||
2. **How the dashboard should be reachable.** Three choices:
|
||||
- **Tailscale** (recommended for phone access): keeps the loopback bind and walks you
|
||||
through `tailscale serve`, including the tailnet HTTPS toggle, then verifies the result
|
||||
end to end.
|
||||
- **Tailscale** (recommended for phone access): keeps the loopback bind, installs
|
||||
Tailscale if needed, logs in, enables the tailnet HTTPS toggle (it opens the admin
|
||||
page for you and waits), then configures `tailscale serve` after the build and
|
||||
verifies the result end to end. If another app already owns `:443` on your node,
|
||||
you choose between a sub-path (`https://<machine>.<tailnet>.ts.net/codeman`, the
|
||||
default), a second port, replacing the other mapping, or skipping.
|
||||
- **Your local network** (`0.0.0.0`): prompts for a password. Skipping the password takes
|
||||
an explicit confirmation and ends on a loud warning.
|
||||
- **This machine only** (`127.0.0.1`): the safest option, and the default for a bare
|
||||
@@ -44,26 +50,52 @@ What it asks you:
|
||||
Tailscale. An existing loopback install defaults to keeping loopback, or to Tailscale when
|
||||
a serve mapping for Codeman is already there. A bare Enter never pulls in new software,
|
||||
and a non-interactive run always keeps the safe loopback default.
|
||||
3. **What to do when it finishes.** Run in this terminal, install as a background service
|
||||
that starts on boot, or do nothing yet.
|
||||
3. **What to call this machine on your tailnet** (Tailscale route only). By default the URL
|
||||
uses the machine's existing name. Answer yes to rename it `codeman-<hostname>`; the
|
||||
default is no, because the tailnet name is also what SSH and everything else on that
|
||||
machine are reached by.
|
||||
4. **Whether to run Codeman in the background.** Enter installs a systemd user service or a
|
||||
macOS LaunchAgent that starts on boot; answering no offers to start it in this terminal
|
||||
instead, or not at all.
|
||||
|
||||
It ends on a screen with the URL (your tailnet, your network, or this machine), a QR code to
|
||||
scan with your phone, and the two commands you need to manage the service.
|
||||
|
||||
Re-running the same one-liner **updates an existing install in place**. Local changes in
|
||||
`~/.codeman/app` are stashed rather than discarded, a running service is restarted and
|
||||
verified, and your existing network binding is preserved. An interrupted first install
|
||||
resumes instead of restarting.
|
||||
|
||||
Two other entry points exist:
|
||||
Other entry points:
|
||||
|
||||
```bash
|
||||
install.sh status # print the URLs, the QR code and the manage commands again
|
||||
install.sh update # update only
|
||||
install.sh uninstall # remove
|
||||
install.sh uninstall # remove (offers to undo a rename it performed)
|
||||
install.sh tailscale # retrofit Tailscale access onto an existing install
|
||||
install.sh name [<n>] # rename this machine on your tailnet (default codeman-<hostname>)
|
||||
install.sh cloudflared # install cloudflared for the in-app Cloudflare tunnel
|
||||
```
|
||||
|
||||
**Flags** answer the questions from the command line 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
|
||||
```
|
||||
|
||||
`--tailscale` / `--lan` / `--local` answer the access question, `--name <n>` / `--no-rename`
|
||||
the name, `--service` / `--run` / `--no-start` the last one. `--yes` takes every default
|
||||
(it still waits on a Tailscale login URL, and a network bind still asks for a password).
|
||||
`--port <n>` moves Codeman off 3000; the service file and the serve mapping follow it.
|
||||
|
||||
**Automation and CI**: with no terminal attached, any step that would change the system
|
||||
aborts with instructions instead of running silently. Set `CODEMAN_NONINTERACTIVE=1` to
|
||||
approve those steps. `CODEMAN_TAILSCALE=1` preselects the Tailscale answer, and never
|
||||
installs Tailscale itself non-interactively.
|
||||
installs Tailscale itself non-interactively; a non-interactive run never renames the
|
||||
machine and never starts a service. Everything the unattended steps print goes to
|
||||
`~/.codeman/install.log`, and the last lines of it are shown when a step fails.
|
||||
|
||||
## Route B: npm
|
||||
|
||||
|
||||
@@ -33,11 +33,12 @@ Your devices join a private network, and Codeman stays bound to loopback. Nothin
|
||||
published to the internet, and you get real HTTPS with a real certificate.
|
||||
|
||||
The installer sets this up for you, including installing Tailscale, logging in, enabling
|
||||
tailnet HTTPS, and verifying the result end to end. To retrofit it onto an existing
|
||||
install:
|
||||
tailnet HTTPS, and verifying the result end to end. It ends on the URL with a QR code to
|
||||
scan. To retrofit it onto an existing install, or to see the URL and QR code again:
|
||||
|
||||
```bash
|
||||
install.sh tailscale
|
||||
install.sh status
|
||||
```
|
||||
|
||||
By hand:
|
||||
@@ -49,6 +50,22 @@ tailscale serve status
|
||||
|
||||
Then open `https://<machine>.<tailnet>.ts.net` from any device on your tailnet.
|
||||
|
||||
### The name in the URL
|
||||
|
||||
The URL is the machine's MagicDNS name, so on a machine called `tnode` it is
|
||||
`https://tnode.<tailnet>.ts.net`. Three ways to influence that, from least to most work:
|
||||
|
||||
| You want | How |
|
||||
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| The machine's existing name (default) | Nothing. This is what the installer does unless you say otherwise. |
|
||||
| `https://codeman-<hostname>.<tailnet>.ts.net` | Answer yes to the installer's name question, pass `--name codeman-<hostname>`, or run `install.sh name`. This renames the machine tailnet-wide (SSH included), which is why the installer defaults to no. `install.sh uninstall` offers to rename it back. |
|
||||
| `https://codeman.<tailnet>.ts.net` | A [Tailscale Service](https://tailscale.com/docs/features/tailscale-services). Only a **tagged** node can host one (a device signed in with a user account cannot), the service is defined and approved in the admin console, and the feature is in beta. The installer does not set this up; it is a `tailscale serve --service=svc:codeman --https=443 127.0.0.1:3000` on a tagged host once the service exists. |
|
||||
|
||||
If `:443` on your node already belongs to another app, the installer offers Codeman under
|
||||
`https://<machine>.<tailnet>.ts.net/codeman` (the default, via `tailscale serve --set-path`
|
||||
plus Codeman's `--base-url`), on a second port (`https://<machine>.<tailnet>.ts.net:8443`),
|
||||
or replacing the other mapping. It never replaces anything without asking.
|
||||
|
||||
Notes:
|
||||
|
||||
- Keep the loopback bind. `tailscale serve` connects to `127.0.0.1:3000` locally, so
|
||||
@@ -58,7 +75,11 @@ Notes:
|
||||
- Codeman's Host-header allowlist already accepts `.ts.net`, so no extra configuration is
|
||||
needed.
|
||||
- The installer never resets or rewrites `serve` mappings other than the one pointing at
|
||||
Codeman's port, so unrelated serve configuration is left alone.
|
||||
Codeman's port, so unrelated serve configuration is left alone. It also never opens a
|
||||
`tailscale funnel` (that is the public internet) and never advertises a Tailscale Service.
|
||||
- On macOS, the App Store and standalone Tailscale apps only run once someone is logged in,
|
||||
so a headless Mac needs the open-source `tailscaled` for the URL to come back after a
|
||||
reboot on its own.
|
||||
|
||||
## Cloudflare tunnel
|
||||
|
||||
|
||||
@@ -67,6 +67,14 @@ On Linux, if you want the service running while you are not logged in:
|
||||
loginctl enable-linger $USER
|
||||
```
|
||||
|
||||
On macOS, a LaunchAgent starts when you log in, not at boot. A headless Mac (no GUI login)
|
||||
needs a system LaunchDaemon instead, written by hand as root. The installer recognises an
|
||||
existing `/Library/LaunchDaemons/com.codeman.web.plist` and leaves it alone rather than
|
||||
installing a LaunchAgent next to it, since the two would fight over the port; remove the
|
||||
daemon first if you want to switch. The same login caveat applies to the App Store and
|
||||
standalone Tailscale apps, so on a headless Mac the Tailscale URL only comes back after a
|
||||
reboot if the open-source `tailscaled` is used.
|
||||
|
||||
### Writing the unit by hand
|
||||
|
||||
**Linux (systemd user unit):**
|
||||
|
||||
Reference in New Issue
Block a user