From 7c1a88febbce787ba2269bd0dc0ee6c511131718 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Fri, 7 Aug 2026 13:36:51 -0500 Subject: [PATCH] docs(installer): correct the Windows service decision, and the 1053 advice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PLAN.md §8 recorded that `sc create` against the plain console sidecar worked and needed no change to `link`. The first Windows install disproved it: 1053, "a timeout was reached (30000 milliseconds) while waiting for the service to connect", with SERVICE_EXIT_CODE 0. The premise was a false symmetry with systemd. systemd supervises any foreground process; the Windows SCM supervises only one that calls StartServiceCtrlDispatcher within ~30 seconds. Record the reversal and what it costs: link gains a Windows service entry point, kept at the edges so the whole sidecar stays shared and Cargo builds neither Windows crate for Linux. INSTALL.md: - Troubleshooting gains a 1053 row naming the real cause (a sidecar older than v1.2.0) and the two tell-tales that distinguish it from a crash: exit code 0, and a foreground run of the same binary working fine. - The existing "stops immediately" row said the same wrong thing; it now covers the genuine-crash case only, and points at the log file and journalctl. - §3 and Appendix A4 document the service log, and A4 states the version floor. - Fixes a literal 0x08 byte in the backups path row, which rendered as `%ProgramData%\RunicGatewayackups\` — the backslash had been eaten. Co-Authored-By: Claude --- installer/INSTALL.md | 18 ++++++++++++++---- installer/PLAN.md | 45 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/installer/INSTALL.md b/installer/INSTALL.md index 65a198c..c8fc730 100644 --- a/installer/INSTALL.md +++ b/installer/INSTALL.md @@ -258,9 +258,10 @@ else still was). | `%ProgramData%\RunicGateway\install.json` | As above | | `%ProgramData%\RunicGateway\patches\` | As above | | `%ProgramData%\RunicGateway\patches\originals\` | As above | -| `%ProgramData%\RunicGatewayackups\\` | As above | +| `%ProgramData%\RunicGateway\backups\\` | As above | | `%ProgramData%\RunicGateway\uo-link.db` | The sidecar's SQLite store | -| Service `RunicGatewayLink` | Automatic start, restart on failure, running as `NT SERVICE\RunicGatewayLink` | +| `%ProgramData%\RunicGateway\uo-link-sidecar..log` | The service's log. A Windows service has no console to write to, so it logs here instead; rolled daily, seven kept. A foreground run still logs to stdout as usual | +| Service `RunicGatewayLink` | Automatic start, restart on failure, running as `NT SERVICE\RunicGatewayLink`. Needs a sidecar **v1.2.0 or newer** — see [Troubleshooting](#troubleshooting) on error 1053 | **Inside your ServUO tree** (added by the overlay sync — 24 files): @@ -630,7 +631,8 @@ release tarball is gone, and the report tells you to diff against them. | 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 `; 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/`. | +| **Windows: `sc start` fails with 1053, "the service did not respond in a timely fashion"** | Almost always a **sidecar older than v1.2.0**, which cannot start as a service no matter how correct its config. 1053 is a handshake failure, not a crash: Windows waited 30 seconds for the process to identify itself to the service control manager, and a sidecar built before service support was added never does. Check with `"C:\Program Files\RunicGateway\uo-link-sidecar.exe" --version`. Tell-tale signs: `sc query` shows `SERVICE_EXIT_CODE : 0` (nothing crashed), and running the same binary in the foreground with the same `--config` works perfectly. | +| Service registered but stops immediately | Distinct from 1053 above — here the process really did exit. On Windows read `%ProgramData%\RunicGateway\uo-link-sidecar..log`, which is where a service logs since it has no stdout, and 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/`, and read `journalctl -u runicgateway-link`. | | 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. | @@ -800,8 +802,13 @@ icacls "$env:ProgramData\RunicGateway" /grant 'NT SERVICE\RunicGate sc.exe start RunicGatewayLink ``` -Three things there are easy to get wrong: +Four things there are easy to get wrong: +- **The sidecar must be v1.2.0 or newer.** Earlier builds are plain console programs, and the + Windows service control manager cannot supervise one: it waits 30 seconds for the process to + identify itself, then fails the start with **1053** even though the process is running and healthy. + From v1.2.0 the same binary does both — started by the SCM it runs as a service, started from a + shell it runs in the foreground, with no flag to choose between them. - **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 @@ -813,6 +820,9 @@ Three things there are easy to get wrong: this service. Omit `obj=` and you get the most privileged local identity there is, for a process listening on two TCP ports. +Once it is running, `%ProgramData%\RunicGateway\uo-link-sidecar..log` is where it logs — a +service has no console to write to. Seven days are kept. + ### A5. Connect the website, start the shard, verify Exactly as in [§5](#5-connect-the-website) and [§6](#6-start-servuo-and-verify): paste the four diff --git a/installer/PLAN.md b/installer/PLAN.md index 2b068c0..c931e14 100644 --- a/installer/PLAN.md +++ b/installer/PLAN.md @@ -1284,15 +1284,42 @@ mismatched pair from being published as a bundle — which is the mechanism that different host, or hard-assume co-location? Resolved and moved into §1 / §2.2 / §5: uninstall scope, and minimum ServUO version. -**Resolved — Windows service mechanism** (was question 1). `sc create` against the plain console -binary, as recommended: it works on a stock host, ships nothing extra, and needs no change to -`link`. A WinSW/NSSM shim would be a third binary to keep current, and a native `--service` mode -using the `windows-service` crate would put Windows service plumbing inside a component whose whole -job is being platform-agnostic. Restart semantics turned out to be adequate — -`sc failure … actions= restart/5000` is the direct counterpart of systemd's `Restart=on-failure` / -`RestartSec=5`. What the recommendation did *not* anticipate is the service identity: plain -`sc create` runs as `LocalSystem`, so Phase 2 registers with `obj= "NT SERVICE\RunicGatewayLink"` -instead (see [Phase 2 as built](#phase-2--uo-link-install-and-service)). +**Resolved — Windows service mechanism** (was question 1). Registration is `sc create` with +`obj= "NT SERVICE\RunicGatewayLink"` (plain `sc create` would run as `LocalSystem`, which the Linux +half pointedly does not do), `sc failure … actions= restart/5000` as the counterpart of systemd's +`Restart=on-failure` / `RestartSec=5`, and the config pinned in `binPath` rather than in a +machine-wide environment variable. No WinSW/NSSM shim: that would be a third binary to keep current. + +**Corrected 2026-08-07 — the sidecar needs its own service mode after all.** This section previously +recorded that `sc create` against the *plain console binary* worked and needed no change to `link`. +It does not, and the first Windows run proved it: `sc start` failed with **1053** and the event log +read *"a timeout was reached (30000 milliseconds) while waiting for the … service to connect"*, +with `SERVICE_EXIT_CODE : 0` — the process had started fine and simply never spoke to the SCM. + +The premise was a false symmetry with systemd. systemd supervises *any* foreground process; the +Windows SCM supervises only a process that calls `StartServiceCtrlDispatcher` within ~30 seconds and +then reports its own state transitions. There is no third option where `sc.exe` adopts an arbitrary +console executable — it is a service-aware binary or a shim, and the shim was already rejected. + +So `link` gains a Windows service entry point (the `windows-service` crate, behind +`[target.'cfg(windows)'.dependencies]`). The objection that this puts Windows plumbing inside a +platform-agnostic component is answered by keeping it *only* at the edges: `app::run` is the whole +sidecar and is shared, while `windows.rs` and `unix.rs` do nothing but start it and tell it when to +stop. Nothing platform-specific reaches the shared path, and Cargo neither resolves nor builds the +Windows crates for Linux. + +Consequences worth knowing: + +- **One binary, no `--service` flag.** The dispatcher is tried first; failing with + `ERROR_FAILED_SERVICE_CONTROLLER_CONNECT` (1063) means "not started by the SCM" and falls through + to a normal foreground run. `cargo run` and a hand-run diagnostic are unchanged. +- **A service has no stdout**, so in service mode the sidecar logs to a daily-rolled file beside its + config instead of into the void. +- **`Running` is reported only once the shard port is bound and the store is open**, so a bad config + fails the *start* rather than flapping Running → Stopped, and a failed run leaves a nonzero + `SERVICE_EXIT_CODE` behind rather than the misleading `0` above. +- **A sidecar older than v1.2.0 can never start as a service on Windows**, however good its config. + The installer says so by name when it sees 1053. **Resolved — branch targeting for the new repo** (was question 4). The v3 cutover landed: `servuo-plugins#6` merged, so that repo's `main` and `edge` agree at protocol 3. The release