7122bc69119a6e98b87c2bc7865bfd75cd4658a0
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| dff4ad41c9 |
feat(installer): implement Phase 1 — the installer core
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m31s
Adds the Rust crate at the repo root and implements `install` end to end for the overlay half of a deployment: resolve the published bundle, find and validate the ServUO root, refuse to deploy under a running shard, sync the plugin overlay, and record what was deployed in install.json. `doctor`, `update` and `uninstall` parse and answer with the phase they arrive in rather than "unrecognized command", and the run states plainly that the uo-link sidecar (Phase 2) and the patch tier (Phase 3) were not installed — `--patches` in particular reports REQUESTED BUT NOT APPLIED, since a quiet completion would be read as a patched shard. Landing on `edge` rather than `main`: release.yml publishes a binary on every push to main, and an installer that deploys the overlay but cannot install the sidecar is not something to hand an operator. pr-checks.yml now gates PRs into edge on the same rules, so the branch the work happens on is not the ungated one. Notable decisions, all documented in docs/installer/PLAN.md §5 Phase 1: - The code lives in a library called `rgdeploy` with a thin binary that keeps the published name. Windows' UAC installer detection refuses to launch an unsigned executable whose file name contains "install" (os error 740), and Cargo names test harnesses after their target — so a target under that name makes `cargo test` unrunnable on Windows. - The running-shard check matches processes by path, not by process name: on Linux a live shard is `mono`/`dotnet` with ServUO.exe as an argument, and a name match would report "not running" for a shard that is running. - install.json records a state (`deployed` / `kept-operator-modified`), not the run's verb, so an unchanged re-run produces an identical record and writes nothing. - The Bridge.cfg keep rule compares against the hash the installer last deployed, not the last hash it saw — otherwise a kept file is overwritten on the very next run. - Downloads are verified against the bundle's SHA256 while being written, then every extracted file is re-hashed against the release's own manifest.json, whose protocol and version are cross-checked against the bundle. Verified against a real ServUO 57.4 tree and end to end into a scratch tree: 24 files deployed, an unchanged re-run that writes nothing, an edited Bridge.cfg kept across repeated runs while code files are overwritten, bundle pinning, and a refusal with a shard running out of the tree. Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| c100d4a2d7 |
ci(bundle): compose and publish the bundle manifest
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 5s
Phase 0 item 3 of docs/installer/PLAN.md (§7.1-§7.3). The installer resolves what to install *from* the bundle, so this has to exist before Phase 1 code is useful. Both components it composes now have releases, which is what unblocked it. Adds .gitea/workflows/bundle.yml — resolve both components' latest releases, run the two compose-time gates, and publish bundles/current.json — plus the first real bundle (2026.08.04: link v1.1.0 + overlay v0.1.1, protocol 3). Bundles are COMMITTED under bundles/, not published as releases. This repo's own releases are the installer binaries, and /releases/latest returns whichever release is newest regardless of kind, so interleaving bundle releases would make "latest" intermittently resolve to a release carrying no installer binary. The push to main needs no new branch-protection exception: release.yml's version-bump commit already requires it. Gate 1 (protocol agreement) reads the sidecar's PROTOCOL_VERSION from sidecar/src/main.rs at the release tag, not from the binary. --print-config would answer, but only for releases from v1.1.0 on, and --bundle <tag> has to be able to recompose an older bundle. It also avoids executing a downloaded artifact and provisioning a throwaway config whose auth token would land in a CI log. The overlay half comes from manifest.json inside the tarball, which is the only statement of that version that exists. Gate 2 (assets) downloads every asset and verifies it against the SHA256SUMS its publishing repo shipped, then records the hash it computed itself. These artifacts are deliberately unsigned, so a hash copied from a file nobody checked would make the whole chain decorative. An asset with no SHA256SUMS entry is caught separately, since `sha256sum -c` passes right over it. Release reads are ANONYMOUS on purpose: they are exactly the requests the shipped installer makes on a host with no Gitea credentials, so a repo flipped to private fails here rather than on an operator's machine. Stale components (§7.3) are dispatched, never awaited — Gitea's dispatch endpoint returns no run handle. "Ahead of its release" counts only releasable commits and excludes merge commits, whose subject quotes the feat/fix title and would otherwise re-dispatch a workflow that correctly declines to run, every night. A run that finds nothing changed writes nothing, comparing everything except `bundle` and `generated` — that is what makes the nightly cron free rather than a dated duplicate every morning. Verified by running the workflow's exact compose steps in a Linux container against the live releases: both gates pass, the composed bundle is the file committed here, a re-run reports changed=false, and the stale-detection filter scores 1 releasable commit for link v1.0.0..main (excluding the merge that quotes it) and 0 for a docs-only range. Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| 60ae6f1f75 |
fix(ci): preflight release credentials and recover from an orphan tag
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 5s
servuo-plugins hit both of these on its first real release run; this repo runs the same engine, so it has the same two defects latent. REGISTRY_USER / REGISTRY_TOKEN were empty there, yet the tag push SUCCEEDED: actions/checkout leaves an `http.<host>.extraheader` credential in the local git config, so `git remote set-url` to a URL with empty credentials still authenticated through that leftover header. The release API call had no such fallback and returned 401. Net result was the worst available outcome — the repo tagged, no release, and a failed job. Two fixes: A credential preflight, before anything is built or pushed, gated on the run actually intending to publish so a docs:/chore:-only merge (or this repo's pre-crate no-op) still passes on a repo with no secrets. It names the missing secrets and the scope they need instead of failing wherever they happen to be used first. Orphan-tag recovery. A tag with no release behind it means an earlier run died after tagging, and the old code treated any existing tag as "nothing to release" — so that state could never clear itself: every later run would see the tag and stand down, forever. The plan step now asks the API whether a release exists for the tag, and on 404 reuses the tag and publishes the release it is missing. This deliberately overrides the RELEASE=false the bump logic just decided, which is the whole point — with the tag in place there are no releasable commits after it. Anything other than 200/404 (network failure, bad token) is refused rather than guessed, since assuming "no release" would republish over a good one. The tag step now reuses an existing tag instead of failing on `git tag`, and the changelog for a recovery run summarizes what the tag contains (previous-tag..this-tag) rather than the empty range after it. sync-project-tree gets the same preflight: its first run on main failed with an opaque `git clone` error against `https://:@host/...` that said nothing about a missing secret. Verified by extracting every run block and exercising the paths: empty secrets fail the preflight with a legible message and populated ones pass; the no-Cargo.toml guard still short-circuits to release=false; a crate with no tag still takes the seed path; and against real repo state, a tag with a release stands down while an orphan tag recovers. Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| ea9aad6e9b |
ci(installer): add pr-checks, release, and project-tree sync workflows
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 11s
Bring this repo's CI up to parity with the other Runic Gateway repos. All three are retargeted from RunicGateway/link, which is the closest analog (same Rust toolchain, same release engine, same runner). pr-checks.yml Gates PRs into main on cargo fmt --check, clippy -D warnings, and cargo test --locked, in that order, one job — mirroring release.yml's gates so a green PR implies a green release. release.yml The conventional-commit release engine from link/, with the Rust adapter retargeted: crate at the repo root, binary runicgateway-installer, cross-compiled for x86_64 Linux and Windows. Artifact names follow PLAN.md §3. The generated changelog now carries the checksum-verification block, because releases are deliberately unsigned and SHA256SUMS is the trust anchor (PLAN.md §3) — that makes the verify instructions part of the release, not a doc someone has to find. sync-project-tree.yml (+ .gitea/scripts/gen_tree.py) Regenerates docs/installer/PROJECT_TREE.md on every push to main and opens or force-updates a PR against the docs repo. Verbatim from link/ apart from the repo/path/label env block. Crate guard This repo has no Cargo project yet — Phase 1 creates it. Landing the workflows unguarded would red-X every governance and docs PR until then, and holding them back leaves the repo ungated exactly while its conventions are being set. So both Rust workflows check for a root Cargo.toml first: pr-checks skips its gates with a notice, and release.yml's plan step sets RELEASE=false and exits. Both arm themselves the moment Cargo.toml lands, with no edit here. Verified before pushing: all three files parse as YAML, every run block passes bash -n, and the release plan step was simulated against a throwaway git repo both without a crate (release=false, exit 0) and with one (first-release path -> v0.1.0 with the changelog rendered). Not included: the bundle-manifest workflow (PLAN.md §7) and the release-dispatch hook, which are Phase 0 item 3 and depend on servuo-plugins having a release workflow first. Note for setup: release.yml and sync-project-tree.yml need REGISTRY_USER and REGISTRY_TOKEN (write:repository, plus read/write on RunicGateway/docs) configured for this repo. Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| d2b311197b |
chore(installer): bootstrap repo with governance docs and templates
The installer repo was created empty. Seed it with the same governance set
every other Runic Gateway repo carries, so it starts on the same footing
before any Rust code lands (see docs/installer/PLAN.md for the design of
record — this repo is still in the planning phase).
Copied verbatim, byte-identical to the other repos:
LICENSE.md (GPL-3.0-or-later), CODE_OF_CONDUCT.md, CONTRIBUTORS.md,
.gitea/PULL_REQUEST_TEMPLATE.md, .gitea/ISSUE_TEMPLATE/{bug_report,
feature_request}.md
Repo-specific:
README.md what the installer is, what it deliberately is not
(no curl|bash, never writes a ServUO launcher), the
planned commands, and the constraints a reader needs
up front: unsigned releases, bundle-manifest
composition, opt-in patch tier, and the fact that a
successful copy is not a working bridge.
CONTRIBUTING.md adapted from link/ (same Rust toolchain and checks),
plus a planning-status note pointing changes of scope
at the plan in docs/, and the two shard-testing traps.
SECURITY.md adds the installer to the component scope table and a
short subsection on its distinct trust model: unsigned
releases anchored on SHA256SUMS, mandatory
verification of downloaded artifacts, and the
never-contacts-the-website token handoff. This is the
only file that now differs from the other repos' copies.
.gitignore Rust build output plus local deployment state
(install.json, sidecar.toml, *.db) that must never be
committed from a test run.
.gitea/ISSUE_TEMPLATE/config.yaml same as elsewhere, repo-local URL.
No CI workflows yet — there is no crate for pr-checks to build, and the
release/bundle workflows are Phase 0 work that depends on servuo-plugins
gaining a release workflow first.
Co-Authored-By: Claude <noreply@anthropic.com>
|