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>
26 KiB
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 -npasses.- Every new decision function driven with stubbed tailscale state under bash 5.2 and
bash 3.2 (the
bash:3.2container 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;--yesand non-interactive never rename;codeman-*nodes are skipped;--nameis sanitized),run_stepsuccess/failure/stdin, the unit round-trip ofCODEMAN_BASE_URL/CODEMAN_PORT/an escaped password, and the done screen. - A full non-interactive install into a sandboxed
HOMEwithCODEMAN_TAILSCALE=1: preflight summary, kept the existing prod mapping (no serve mutation), clone 2 s,npm install18 s, build 23 s, symlink, done screen;install.sh statuson 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) behindtailscale serve --https=8445 --set-path /codeman 3999answered/codeman/api/status,/codeman/(with<base href="/codeman/">and__CODEMAN_BASE__="/codeman"), the hashed CSS/JS,/codemanwithout 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/statuson the server is 404 while the same path through serve is 200). That is fine because Codeman's ingress tolerates unprefixed requests;--base-urlis 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 asexport_bind_env), so "do not start" under a sub-path or a custom port no longer prints a barecodeman web; the--lan/--tailscale/env preset paths keep an existing password instead of rewriting the unit open;--password/--portflipRECONFIGUREso they reach the unit;install.sh namere-syncs the unit's base URL after a rename; the sudo keepalive is ended before theexecinto the foreground server; Ctrl+C in the HTTPS-toggle poll skips Tailscale instead of killing the run;uninstallasks 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 theinstall.sh tailscalerecovery;TS_JOINED_HEREis 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 uplogin URL, the operator grant, and the tailnet HTTPS toggle all move into the question phase. The build, the service,tailscale serveand 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, runssudo -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
cloudflaredis 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 pollstailscale status --jsonevery 5 s for up to 5 minutes. Ctrl+C or the timeout falls back exactly as today. - Progress, not silence.
npm installandnpm run buildrun behind one line each with elapsed time; their output goes to~/.codeman/install.logand 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 --:
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.
-
Preflight, before the build (
tailscale_preflight): installed? -> install (Linux: official script; macOS: brew cask, else download link and wait). Logged in? ->tailscale upwith 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. -
Name (
tailscale_choose_name, question 2/3): shown only on the Tailscale route. Defaultcodeman-<oshostname>sanitized to[a-z0-9-], max 63. Applied withts_cmd_serve set --hostname, then poll.Self.DNSNameuntil 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 namedcodeman-*, the question is skipped. -
Serve, after the service is up (
setup_tailscale_serve): unchanged idempotent "kept as-is" path first. When:443root 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 nowOption 1 writes
--base-url /codemaninto the service unit (it is aWebLaunchOptionsfield already, andbuildWebArgscarries it) and runstailscale serve --bg --set-path /codeman <port>. Option 2 runs--https=8443.detect_tailscale_serve_urllearns to recognize all three shapes (root, path, port) so uninstall, the security notice and the re-run default keep working. -
Warm the certificate. Right after serve is configured, fire one background
curl -sk https://<url>/api/statusso Let's Encrypt issuance overlaps the rest of the install instead of adding 30 s to the verify step. -
Verify as today (200 or 401 on
/api/status), with the path-aware URL. -
Services hint (option C): when
.Self.Tagsis non-empty andserve --helplists--service, the done screen adds one line: "This is a tagged node, so it can also hosthttps://codeman.<tailnet>.ts.netas a Tailscale Service: see Remote Access in the wiki." No flow, no prompt. -
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, andinstall.shmust 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: detectUserNamein the daemon plist and leave it alone with a message). -
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). -
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-accessreturns{ tailscale: { installed, connected, dnsName, url, mode: 'root'|'path'|'port'|null } }by runningtailscale status --jsonandtailscale serve status --jsonthroughexecFilewith the existing exec timeout, cached 30 s, resolved through the sameget_tailscale_pathsearch as the installer (PATH, then the macOS app bundle), and a no-op underVITESTlike 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 withbash ~/.codeman/app/install.sh tailscale. The welcome screen's "open on your phone" affordance shows the same QR. codeman doctorgrows atailscaleentry underotherinconfig/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:
- Tailscale absent, declined -> local-only, done screen shows the retrofit command.
- 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.
- Tailscale present and logged in on a pre-existing node -> rename default NO, URL is the
node name,
serve statusgains exactly one entry. :443root occupied -> path option ->https://<node>/codemananswers, hooks still fire (raw port),install.sh statusprints the path URL.- Rename on a node that already has our serve mapping (
install.sh name) -> off, rename, re-add,serve statushas no stale key. - Re-run the one-liner -> quiet update, binding and name preserved, no prompts.
--yesend to end;CODEMAN_NONINTERACTIVE=1end to end (no software installed).- Uninstall -> mapping removed, other mappings intact, rename reversal offered.
- 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:
- Rename default. Decided 2026-09-20: always NO; the yes answer,
--nameandinstall.sh nameare 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.) - Name pattern.
codeman-<hostname>(proposed; unique per machine, and two Codemans on one tailnet stay distinguishable) versus plaincodeman(nicer once, collides on the second install, Tailscale silently appends-1). - Path versus port as the default answer for an occupied 443. Proposed: path, because
the URL has no port and
--base-urlalready exists for exactly this proxy shape. - Whether Phase 2 ships in the same release. It is the part that helps people who installed months ago.