docs(installer): record Phase 2 as built — sidecar install and service
Phases 1 and 2 now live on the installer repo's `edge` branch, so PLAN.md's status, the config-path section, and the operator guide all move with them. PLAN.md - Status: phases 1 and 2 built. The `edge -> main` cutover now follows Phase 3 rather than Phase 2, because INSTALL.md §4 describes the patch tier as part of the run and a release that answers "not implemented" to all of it is the same half-capable binary that kept Phase 1 off `main`. - §2.3: the service definition always pins the config path, but only Linux pins the database. On Windows config and data share a directory, so the sidecar's own anchoring rule already lands it correctly — and `sc.exe` offers no per-service environment, only a machine-wide one that every process inherits and that outlives an uninstall. - New "Phase 2 as built" section: the virtual service account, the config lockdown and why its two halves straddle registration, `--verify` running no part of the sidecar half, the protocol check against the installed binary, `RUNICGATEWAY_STATE_DIR` relocating the binary and suppressing service registration, degrading to a printed recipe with no root/LocalSystem fallback, and the token never entering install.json. - §8 question 1 (Windows service mechanism) resolved: `sc create`, as recommended — plus the service identity the recommendation did not anticipate. INSTALL.md - Status banner: what is built, and that the patch tier is the remaining gap. - §2: the illustrated run matches the sidecar block the binary actually prints. - §3: a table of how each platform pins config and database, the dedicated service account on both, and the fact that sidecar.toml's permissions are restricted because it holds the auth token. - Appendix A4: the Windows recipe now matches what the installer does — `--config` in binPath (single-quoted so PowerShell keeps the inner quotes), `obj=` for the virtual account, the icacls lockdown before and grants after, and no machine-wide environment variables. - Troubleshooting: a row for a run that could not register a service, and one for a service that starts and immediately stops. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,10 +5,12 @@ installation and connects it to a Runic Gateway website.
|
||||
|
||||
> **Status: the installer binary is not released yet.**
|
||||
>
|
||||
> Phase 1 — the installer core: bundle resolution, ServUO detection, the overlay sync and
|
||||
> `install.json` — is built and lives on the installer repo's `edge` branch. It is deliberately not
|
||||
> released: a binary that deploys the overlay but cannot yet install the sidecar would not do what
|
||||
> this guide describes. The first release follows Phase 2.
|
||||
> Phases 1 and 2 are built and live on the installer repo's `edge` branch: the installer core
|
||||
> (bundle resolution, ServUO detection, the overlay sync, `install.json`) and the sidecar half (the
|
||||
> binary, its config, its service, and the token handoff). What is still missing is the **patch
|
||||
> tier** ([§4](#4-the-patch-tier-optional)), which `install` reports as not applied rather than
|
||||
> silently skipping. The first release follows Phase 3, so that a released binary answers every
|
||||
> question this guide says it answers.
|
||||
>
|
||||
> Everything it installs *is* released and published — the sidecar, the plugin overlay, and the
|
||||
> [bundle manifest](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/main/bundles/current.json)
|
||||
@@ -176,11 +178,14 @@ Overlay sync
|
||||
Patch tier skipped (not selected)
|
||||
Without it: no vendor.sale events, no in-game moderation audit forwarding.
|
||||
|
||||
uo-link
|
||||
binary /usr/bin/runicgateway-link
|
||||
config /etc/runicgateway/sidecar.toml (created)
|
||||
database /var/lib/runicgateway/uo-link.db
|
||||
service runicgateway-link.service enabled, running
|
||||
uo-link sidecar
|
||||
binary /usr/bin/runicgateway-link install
|
||||
✓ sidecar binary verified sha256 27d491ef…
|
||||
config /etc/runicgateway/sidecar.toml created
|
||||
database /var/lib/runicgateway/uo-link.db
|
||||
listening on shard 127.0.0.1:7788 website 127.0.0.1:8080
|
||||
service runicgateway-link.service active, enabled
|
||||
running as runicgateway
|
||||
|
||||
Recorded /etc/runicgateway/install.json
|
||||
|
||||
@@ -238,7 +243,7 @@ reports "unchanged" and writes nothing.
|
||||
| `%ProgramData%\RunicGateway\install.json` | As above |
|
||||
| `%ProgramData%\RunicGateway\patches\` | As above |
|
||||
| `%ProgramData%\RunicGateway\uo-link.db` | The sidecar's SQLite store |
|
||||
| Service `RunicGatewayLink` | Automatic start, restart on failure |
|
||||
| Service `RunicGatewayLink` | Automatic start, restart on failure, running as `NT SERVICE\RunicGatewayLink` |
|
||||
|
||||
**Inside your ServUO tree** (added by the overlay sync — 24 files):
|
||||
|
||||
@@ -248,11 +253,30 @@ Scripts/Custom/Bridge/*.cs 22 files: the plugin itself
|
||||
Scripts/Scripts.csproj OVERWRITES a stock file (see below)
|
||||
```
|
||||
|
||||
Both service definitions pin `UOLINK_CONFIG` and `UOLINK_DB_PATH` explicitly. The sidecar's own
|
||||
defaults are relative to its working directory, and a service manager's working directory is not
|
||||
somewhere you want a database — on Windows it can be `%SystemRoot%\System32` or, under
|
||||
**Both service definitions pin the config path**, because the sidecar's own default is relative to
|
||||
its working directory — and a service manager's working directory is not somewhere you want a
|
||||
database or a config file. On Windows it can be `%SystemRoot%\System32` or, under
|
||||
`C:\Program Files\`, a silently redirected VirtualStore copy.
|
||||
|
||||
How the *database* path is pinned differs by platform, and that is deliberate:
|
||||
|
||||
| | Config | Database |
|
||||
|---|---|---|
|
||||
| **Linux** | `Environment=UOLINK_CONFIG=` in the unit | `Environment=UOLINK_DB_PATH=` in the unit — `/etc` and `/var/lib` are different directories, so both need naming |
|
||||
| **Windows** | `--config` inside the service's own `binPath` | nothing to set: a relative `[store] path` resolves against the config's directory, which *is* `%ProgramData%\RunicGateway` |
|
||||
|
||||
The Windows service would otherwise need a **machine-wide** environment variable — `sc.exe` has no
|
||||
per-service one — which every process on the host inherits and which outlives an uninstall.
|
||||
|
||||
**Both run as a dedicated, unprivileged account.** Linux gets a `runicgateway` system user; Windows
|
||||
gets a virtual service account, `NT SERVICE\RunicGatewayLink`, which Windows creates as part of
|
||||
registering the service and which has no password. Neither runs as root or `LocalSystem`.
|
||||
|
||||
**`sidecar.toml` is locked down, because it holds your auth token.** Neither default location
|
||||
protects it on its own — `/etc` is world-readable, and `%ProgramData%` grants `Users` read access by
|
||||
inheritance — so the installer sets the permissions itself: `chmod 600` plus `chown` to the service
|
||||
user on Linux, and an explicit ACL of SYSTEM, Administrators and the service account on Windows.
|
||||
|
||||
> **`Scripts.csproj` is overwritten deliberately.** The stock file omits `Scripts/Custom/`, so the
|
||||
> plugin would sit in the tree and never compile — and ServUO would not tell you, because it
|
||||
> ignores the script build's exit code and silently reloads the previous `Scripts.dll`. That
|
||||
@@ -509,6 +533,8 @@ The report is also written to a file, so it survives the scrollback.
|
||||
| Website says the shard is offline; `/health` is fine locally | The website cannot reach port 8080 — bind address or firewall. See [§5](#if-your-website-is-on-a-different-machine). Note that the site is *designed* to render normally with the shard offline, so this fails quietly by design. |
|
||||
| Website logs `409` from the sidecar | Protocol mismatch: the number in Admin → Shard does not match the sidecar's. The sidecar rejects rather than mis-parsing. Set the field to what `/health` reports (`protocol`). If the *sidecar* and *overlay* disagree, you have a hand-assembled pair — reinstall from a bundle. |
|
||||
| `401` from the sidecar | Wrong or missing auth token. Read the live one back with `uo-link-sidecar --print-config --config <path>`; do not retype it from a screenshot. |
|
||||
| **"service NOT REGISTERED" at the end of an otherwise successful run** | The host has no service manager the installer can drive — most often no systemd (a container, or a distro that never had it), or the `runicgateway` user could not be created. The binary and config *are* installed; the run prints the exact unit and commands to finish by hand. It never falls back to running the service as root or `LocalSystem`. |
|
||||
| Service registered but stops immediately | It cannot read its config. On Windows check that `sc qc RunicGatewayLink` shows `--config` in `BINARY_PATH_NAME` and that `NT SERVICE\RunicGatewayLink` has read access to `sidecar.toml`; on Linux check the `runicgateway` user can read `/etc/runicgateway/sidecar.toml` and write `/var/lib/runicgateway/`. |
|
||||
| A patch will not apply | Expected on a hand-modified shard. The base install is unaffected; you lose only the two features in [§4](#4-the-patch-tier-optional). Apply the hunks by hand if you want them. |
|
||||
| `vendor.sale` events never arrive despite patching | The `EventSink.cs` patch is a **core** change. A shard restart is not enough — rebuild the solution (`dotnet build ServUO.sln`). |
|
||||
| Sidecar writes its database somewhere unexpected | A relative `[store] path` resolves against the directory holding `sidecar.toml` — not the working directory. Run `--print-config` to see the absolute path it will actually use. |
|
||||
@@ -651,15 +677,35 @@ Copy-Item .\uo-link-sidecar-windows-x86_64.exe "$env:ProgramFiles\RunicGateway\u
|
||||
|
||||
& "$env:ProgramFiles\RunicGateway\uo-link-sidecar.exe" --print-config --config "$env:ProgramData\RunicGateway\sidecar.toml"
|
||||
|
||||
sc.exe create RunicGatewayLink binPath= "\"$env:ProgramFiles\RunicGateway\uo-link-sidecar.exe\"" start= auto
|
||||
# The config file now holds your auth token. Lock it down before anything else can read it:
|
||||
icacls "$env:ProgramData\RunicGateway\sidecar.toml" /inheritance:r /grant:r '*S-1-5-18:(F)' /grant:r '*S-1-5-32-544:(F)'
|
||||
|
||||
# binPath carries the config path. The single quotes matter: the value itself contains the double
|
||||
# quotes the service manager needs around a path with spaces in it.
|
||||
sc.exe create RunicGatewayLink `
|
||||
binPath= '"C:\Program Files\RunicGateway\uo-link-sidecar.exe" --config "C:\ProgramData\RunicGateway\sidecar.toml"' `
|
||||
obj= 'NT SERVICE\RunicGatewayLink' start= auto
|
||||
sc.exe failure RunicGatewayLink reset= 86400 actions= restart/5000
|
||||
[Environment]::SetEnvironmentVariable('UOLINK_CONFIG', "$env:ProgramData\RunicGateway\sidecar.toml", 'Machine')
|
||||
[Environment]::SetEnvironmentVariable('UOLINK_DB_PATH', "$env:ProgramData\RunicGateway\uo-link.db", 'Machine')
|
||||
|
||||
# The service account exists only once sc create has created it, so its grants come after:
|
||||
icacls "$env:ProgramData\RunicGateway\sidecar.toml" /grant 'NT SERVICE\RunicGatewayLink:(R)'
|
||||
icacls "$env:ProgramData\RunicGateway" /grant 'NT SERVICE\RunicGatewayLink:(OI)(CI)M'
|
||||
|
||||
sc.exe start RunicGatewayLink
|
||||
```
|
||||
|
||||
Machine environment variables are read at service start, so set them before starting — and never
|
||||
leave the config path to the default, which is relative to the service's working directory.
|
||||
Three things there are easy to get wrong:
|
||||
|
||||
- **The config path goes in `binPath`, not in a machine environment variable.** `sc.exe` has no
|
||||
per-service environment, and a machine-wide `UOLINK_CONFIG` would be inherited by every process on
|
||||
the host and survive an uninstall. Never leave the config path to the default — it is relative to
|
||||
the service's working directory, which for a service is `%SystemRoot%\System32`.
|
||||
- **The database needs no pinning here.** A relative `[store] path` resolves against the directory
|
||||
holding `sidecar.toml`, which is already `%ProgramData%\RunicGateway`.
|
||||
- **`obj=` is what keeps this off `LocalSystem`.** `NT SERVICE\RunicGatewayLink` is a virtual
|
||||
service account: Windows creates it with the service, it has no password, and it exists only for
|
||||
this service. Omit `obj=` and you get the most privileged local identity there is, for a process
|
||||
listening on two TCP ports.
|
||||
|
||||
### A5. Connect the website, start the shard, verify
|
||||
|
||||
|
||||
Reference in New Issue
Block a user