Commit Graph

2 Commits

Author SHA1 Message Date
6da385425e fix(handoff): print the shard screen's real path
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m39s
The end-of-run block told operators to paste the four values at
`<site>/admin/shard`. That page moved when the shard screens became part of the
`uo` module: a module owns one path segment wherever it appears (website
`MODULE_SYSTEM.md` §2.8), so it is `/admin/uo/link`, labelled "Shard (uo-link)".

The old path is worse than a 404. The SPA has no route for it, so it sends the
operator to the dashboard — the link looks like it worked, and the values they
were told to paste have nowhere to go.

- The path is now a named constant, `ADMIN_SHARD_PATH`, carrying why it is not
  the obvious string and the fact that API routes are NOT affected by the module
  namespacing rule (they keep `/api/v1/admin/shard/*`).
- Both handoff tests assert the new path, so this cannot regress quietly.
- The two user-facing labels that name the screen — the `--site-url` help text
  and `update`'s protocol-change instruction — say "Admin → Shard (uo-link)",
  matching what the sidebar actually reads.

Found while writing the runicgateway.com installation journey, by pasting the
printed link into a real deployment and landing on the dashboard.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 11:22:45 -05:00
2228e0848b feat(installer): implement Phase 2 — uo-link install and service
Some checks failed
PR Checks / rust-gates (pull_request) Failing after 1m15s
Adds the sidecar half of a deployment to the same `install` run: download and
verify the bundle's binary, provision its config, register and start a service,
and print the token handoff PLAN.md §6 specifies. `src/sidecar.rs` owns the
binary and the config document; `src/service.rs` owns systemd and the Windows
SCM.

The order is fixed by PLAN.md §5 and matters: stop anything running the old
binary, replace it, then `--print-config` (which writes the config the service
will be pointed at), then register. Registering first points a service at a file
that does not exist yet.

Decisions worth a reviewer's attention:

- Both platforms run the sidecar as a dedicated unprivileged identity. Linux gets
  the `runicgateway` system user the plan already specified; Windows gets a
  virtual service account, `sc create ... obj= "NT SERVICE\RunicGatewayLink"`,
  which the SCM creates itself and which has no password. Plain `sc create` runs
  as LocalSystem — the most privileged local identity there is, for a process
  listening on two TCP ports while its Linux twin deliberately does not run as
  root.
- `sidecar.toml` holds the auth token and neither default location protects it:
  /etc is world-readable and %ProgramData% grants Users read by inheritance, so a
  stock install would leave the shard's token readable by any local account. The
  lockdown straddles registration because it has to — on Windows the service
  account does not exist until `sc create` creates it, so the file is first cut
  down to SYSTEM + Administrators, and the account's read grant comes after.
- Only Linux pins UOLINK_DB_PATH. On Windows config and data share a directory
  and the sidecar anchors a relative [store] path to its config's directory, so
  the pin is redundant — and `sc.exe` has no per-service environment, only a
  machine-wide one that every process inherits and that outlives an uninstall.
  The config path rides in the service's own binPath instead.
- `--verify` runs no part of the sidecar half. `--print-config` provisions: it
  writes the config and mints a token, so a dry run that called it would create
  the state it claims not to. It also carries an existing `link` section of
  install.json through untouched, so a dry run cannot make a service disappear
  from the record.
- The installed binary's protocol version is checked against the bundle before
  the service is registered. Gate 1 read that number from source at the release
  tag; this is the same check applied to the binary that will actually answer the
  website.
- RUNICGATEWAY_STATE_DIR now relocates the sidecar binary as well, and suppresses
  service registration and the file-permission hardening. There is no such thing
  as a relocated systemd unit, and hardening a scratch config against the only
  account that will ever read it just breaks the next test run.
- A host with no systemd, or where the service user cannot be created, still gets
  a working binary and config plus the exact unit and commands. There is no
  fallback to User=root or LocalSystem: a service quietly running with more
  privilege than its documentation promises is worse than one that was not
  registered.
- install.json never records the token. The `link` section carries versions, the
  binary's hash, the config and database paths, and the service's name, unit path
  and account.

Docs half: docs#91.

Tested: cargo fmt --check, clippy --all-targets -D warnings, 72 tests. End to end
on Windows against a relocated layout — bundle sidecar downloaded and verified,
config provisioned, handoff printed with URLs composed from the host rather than
the bind address, second run reporting unchanged with install.json byte-identical,
--verify over an installed host writing nothing and preserving the link section,
and a tampered binary detected by hash and replaced with no staging file left.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 15:40:56 -05:00