Three faults in code that only compiles under cfg(unix), none of which the
Windows build could see:
- `run(...).map(...) == Ok(true)` compared two `Result<_, anyhow::Error>`
values, and anyhow::Error is not PartialEq. Replaced with `is_ok_and`.
- `command_line` is used only by the Windows registration path, so importing it
unconditionally is an unused-import error under `-D warnings`. Qualified at
its call site instead.
- A cfg(not(windows)) assertion block had ended up in the wrong test, leaving it
referencing a binding from its original one.
Caught by running the same gates the CI runner does inside a rust:1-slim
container against this working tree — fmt, clippy --all-targets -D warnings, and
cargo test --locked all pass there now, as they do on Windows.
Co-Authored-By: Claude <noreply@anthropic.com>
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>