ci(release): publish the overlay as a release tarball with a manifest #7

Merged
whitlocktech merged 1 commits from ci/overlay-release-workflow into main 2026-08-04 14:26:48 +00:00
Member

What & why

Phase 0 item 1 of the installer plan (docs/installer/PLAN.md §5).

The installer deploys the plugin from a release tarball rather than from git, because the shard host gets neither git nor Gitea credentials. But this repo published no releases at all — link was the only repo with a release workflow — so there was nothing for the installer to fetch. Nothing else in the installer plan can proceed until this exists.

Reuses link/.gitea/workflows/release.yml's conventional-commit engine, exactly as that file's own header anticipated: the plan and release steps consume only {version, changelog, artifacts} and know nothing about what's inside the artifacts.

1. No build. The plugin ships as C# source and ServUO compiles it at boot; it needs ServUO reference assemblies, so nothing here can be compiled in CI. The build gates are replaced by structural ones that assert what can honestly be asserted without a ServUO tree:

  • overlay/Config/Bridge.cfg and overlay/Scripts/Custom/Bridge/*.cs are present — without them the deploy silently no-ops.
  • overlay/Scripts/Scripts.csproj is present — that's the plugin's own Phase 0, the fix for ServUO's silent script-build bug. An overlay shipped without it installs code that never compiles, and ServUO reports success anyway.
  • Every .patch parses as a unified diff (git apply --stat, which needs the diff but not the target tree). A malformed patch is otherwise invisible until an operator runs the patch tier on their live shard.
  • Each patch's companion .cs exists, since it references symbols the patch introduces.

2. No bump commit, so no push to main. link writes the version into Cargo.toml because the binary embeds it; a tarball embeds nothing but the manifest CI generates, so the git tag is the version. This workflow therefore never needs main to accept a direct push — no branch-protection exception required for it.

3. A manifest, which matters more than it looks.

{
  "component": "servuo-plugins-overlay",
  "version": "0.1.0",
  "commit": "968b526…",
  "protocol": 3,
  "servuo": { "min_version": "57.4", "patches_verified_against": "57.4" },
  "files": { "overlay/Config/Bridge.cfg": "32718424…",  }
}

The plugin announces no version on the wire and none is queryable before ServUO boots (PLAN.md §2.6), so its declared protocol version is the only thing that lets the installer's bundle CI verify sidecar/overlay agreement before an operator installs the pair (PLAN.md §7.1, gate 1). That declaration lives in the new overlay.toml alongside the ServUO compatibility values — one commented line to maintain, rather than a literal buried in a workflow. Currently protocol = 3 per docs/link/v3.md.

The files map gives each shipped file a SHA256, so a deployment can later tell "an operator edited this" from "the overlay moved on".

Packaging details

The tarball uses a fixed top-level directory (runicgateway-overlay/), not a versioned one, so the installer can find overlay/, patches/ and manifest.json at known paths instead of parsing the version it's trying to read. tar's member order, mtime and ownership are pinned, so a given tree produces a byte-identical tarball and its checksum changes only when the contents do.

How it was tested

Ran locally against the real tree before pushing:

  • YAML parses; all six run: blocks pass bash -n.
  • Plan step against actual historyrelease=true version=0.1.0 bump=minor last_tag=<none>, changelog rendered.
  • Gates → pass: 22 bridge scripts found, all three patches parse (git apply --stat reports the expected hunks against Logging.cs, EventSink.cs, PlayerVendorGumps.cs).
  • Manifestprotocol=3, min_servuo=57.4, 30 file hashes, read out of overlay.toml rather than hardcoded.
  • Tarball → 31 files under runicgateway-overlay/, correct layout; two consecutive builds produce the identical SHA256 (ca69d03a…), confirming reproducibility.

One real bug this caught — and it only showed up by running it, not reading it: sha256sum marks binary mode by prefixing the path with * (<hash> *path) instead of the two-space text-mode separator. The original capture would have put a leading * on every key in the manifest, silently producing a manifest whose paths match nothing on disk. Coreutils on Linux defaults to text mode so CI would likely have been fine, but that's luck, not design. The regex now tolerates both, with a comment saying why.

Also updated

README.md — the "no separate build artifact and no CI build" line was accurate about the build but is now wrong about artifacts. Corrected, plus a new Releases section documenting the tarball layout, the manifest, the protocol-bump duty, and the fact that deploy.ps1 remains the developer-facing tool while operators get the installer. .gitignore gains dist/.

Follow-ups (deliberately not here)

  • The workflow-dispatch call into the installer's bundle CI (PLAN.md §7.2) is Phase 0 item 3; there's nothing to dispatch yet, and a step that 404s on every release is worse than no step. The insertion point is marked in the workflow header.
  • REGISTRY_USER / REGISTRY_TOKEN (write:repository) need to exist on this repo before the first merge, or the tag push and release creation will fail.
  • Related open question PLAN.md §8.4 (branch targeting for a mid-cutover repo) is now moot: servuo-plugins#6 merged, main == edge, and this targets main.

Checklist

  • I have read CONTRIBUTING.md.
  • The change builds and existing tests/checks pass locally.
  • I have added or updated tests/docs where it makes sense.
  • My commits are reasonably scoped with clear messages.

AI-assisted contributions (required)

  • AI tools were used. Tool(s): Claude Code (Claude Opus 5). Every change has been reviewed and is understood. AI-authored commits are marked with a Co-Authored-By trailer.

License

  • I agree that my contribution is licensed under this project's license (GNU GPL v3.0 or later), and I have the right to contribute it.
## What & why **Phase 0 item 1** of the installer plan ([`docs/installer/PLAN.md` §5](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/installer/PLAN.md)). The installer deploys the plugin from a **release tarball** rather than from git, because the shard host gets neither git nor Gitea credentials. But this repo published no releases at all — `link` was the only repo with a release workflow — so there was nothing for the installer to fetch. Nothing else in the installer plan can proceed until this exists. Reuses `link/.gitea/workflows/release.yml`'s conventional-commit engine, exactly as that file's own header anticipated: the plan and release steps consume only `{version, changelog, artifacts}` and know nothing about what's inside the artifacts. ### Three changes from link's copy, each forced by this repo **1. No build.** The plugin ships as C# source and ServUO compiles it at boot; it needs ServUO reference assemblies, so nothing here can be compiled in CI. The build gates are replaced by structural ones that assert what *can* honestly be asserted without a ServUO tree: - `overlay/Config/Bridge.cfg` and `overlay/Scripts/Custom/Bridge/*.cs` are present — without them the deploy silently no-ops. - `overlay/Scripts/Scripts.csproj` is present — that's the plugin's own Phase 0, the fix for ServUO's silent script-build bug. An overlay shipped without it installs code that never compiles, and ServUO reports success anyway. - Every `.patch` parses as a unified diff (`git apply --stat`, which needs the diff but not the target tree). A malformed patch is otherwise invisible until an operator runs the patch tier on their live shard. - Each patch's companion `.cs` exists, since it references symbols the patch introduces. **2. No bump commit, so no push to `main`.** `link` writes the version into `Cargo.toml` because the binary embeds it; a tarball embeds nothing but the manifest CI generates, so the git tag *is* the version. This workflow therefore never needs `main` to accept a direct push — no branch-protection exception required for it. **3. A manifest**, which matters more than it looks. ```json { "component": "servuo-plugins-overlay", "version": "0.1.0", "commit": "968b526…", "protocol": 3, "servuo": { "min_version": "57.4", "patches_verified_against": "57.4" }, "files": { "overlay/Config/Bridge.cfg": "32718424…", … } } ``` The plugin announces no version on the wire and none is queryable before ServUO boots (PLAN.md §2.6), so its **declared protocol version is the only thing** that lets the installer's bundle CI verify sidecar/overlay agreement *before* an operator installs the pair (PLAN.md §7.1, gate 1). That declaration lives in the new `overlay.toml` alongside the ServUO compatibility values — one commented line to maintain, rather than a literal buried in a workflow. Currently `protocol = 3` per `docs/link/v3.md`. The `files` map gives each shipped file a SHA256, so a deployment can later tell "an operator edited this" from "the overlay moved on". ### Packaging details The tarball uses a **fixed** top-level directory (`runicgateway-overlay/`), not a versioned one, so the installer can find `overlay/`, `patches/` and `manifest.json` at known paths instead of parsing the version it's trying to read. `tar`'s member order, mtime and ownership are pinned, so a given tree produces a byte-identical tarball and its checksum changes only when the contents do. ## How it was tested Ran locally against the real tree before pushing: - YAML parses; all six `run:` blocks pass `bash -n`. - **Plan step against actual history** → `release=true version=0.1.0 bump=minor last_tag=<none>`, changelog rendered. - **Gates** → pass: 22 bridge scripts found, all three patches parse (`git apply --stat` reports the expected hunks against `Logging.cs`, `EventSink.cs`, `PlayerVendorGumps.cs`). - **Manifest** → `protocol=3`, `min_servuo=57.4`, 30 file hashes, read out of `overlay.toml` rather than hardcoded. - **Tarball** → 31 files under `runicgateway-overlay/`, correct layout; two consecutive builds produce the identical SHA256 (`ca69d03a…`), confirming reproducibility. **One real bug this caught** — and it only showed up by running it, not reading it: `sha256sum` marks binary mode by prefixing the path with `*` (`<hash> *path`) instead of the two-space text-mode separator. The original capture would have put a leading `*` on **every key in the manifest**, silently producing a manifest whose paths match nothing on disk. Coreutils on Linux defaults to text mode so CI would likely have been fine, but that's luck, not design. The regex now tolerates both, with a comment saying why. ## Also updated `README.md` — the "no separate build artifact and no CI build" line was accurate about the *build* but is now wrong about *artifacts*. Corrected, plus a new **Releases** section documenting the tarball layout, the manifest, the protocol-bump duty, and the fact that `deploy.ps1` remains the developer-facing tool while operators get the installer. `.gitignore` gains `dist/`. ## Follow-ups (deliberately not here) - The workflow-dispatch call into the installer's bundle CI (PLAN.md §7.2) is **Phase 0 item 3**; there's nothing to dispatch yet, and a step that 404s on every release is worse than no step. The insertion point is marked in the workflow header. - `REGISTRY_USER` / `REGISTRY_TOKEN` (`write:repository`) need to exist on this repo before the first merge, or the tag push and release creation will fail. - Related open question PLAN.md §8.4 (branch targeting for a mid-cutover repo) is now moot: servuo-plugins#6 merged, `main` == `edge`, and this targets `main`. ## Checklist - [x] I have read [CONTRIBUTING.md](CONTRIBUTING.md). - [x] The change builds and existing tests/checks pass locally. - [x] I have added or updated tests/docs where it makes sense. - [x] My commits are reasonably scoped with clear messages. ## AI-assisted contributions (required) - [x] AI tools were used. Tool(s): `Claude Code (Claude Opus 5)`. Every change has been reviewed and is understood. AI-authored commits are marked with a `Co-Authored-By` trailer. ## License - [x] I agree that my contribution is licensed under this project's license (**GNU GPL v3.0 or later**), and I have the right to contribute it.
wtclaude added 1 commit 2026-08-04 14:24:35 +00:00
Phase 0 item 1 of the installer plan (docs/installer/PLAN.md §5). The
installer deploys the plugin from a release tarball rather than from git,
because the shard host gets neither git nor Gitea credentials — but this
repo published no releases at all, so there was nothing for it to fetch.
`link` was the only repo with a release workflow.

Reuses link/.gitea/workflows/release.yml's conventional-commit engine, as
that file's own header anticipated: the plan and release steps consume only
{version, changelog, artifacts}. Three things had to change, each forced by
this repo rather than chosen:

  No build. The plugin ships as C# source and ServUO compiles it at boot;
  it needs ServUO reference assemblies, so nothing here can be compiled in
  CI. The build gates are replaced by structural ones that assert what can
  honestly be asserted without a ServUO tree: Bridge.cfg and the Bridge
  scripts are present, Scripts.csproj (the silent-build-bug fix) is present,
  every .patch parses as a unified diff via `git apply --stat`, and each
  patch's companion .cs exists. Each of those has a way of shipping broken
  and only failing on an operator's live shard.

  No bump commit, so no push to main. link writes the version into
  Cargo.toml because the binary embeds it; a tarball embeds nothing but the
  manifest CI generates, so the git tag is the version. This workflow
  therefore never needs main to accept a direct push — no branch-protection
  exception for it.

  A manifest. The tarball carries manifest.json: version, commit, declared
  protocol version, ServUO compatibility, and a SHA256 per shipped file.

The manifest matters more than it looks. The plugin announces no version on
the wire and none is queryable before ServUO boots (PLAN.md §2.6), so its
declared protocol version is the ONLY thing that lets the installer's bundle
CI verify sidecar/overlay agreement before an operator installs the pair
(PLAN.md §7.1 gate 1). That declaration lives in the new overlay.toml
alongside the ServUO compatibility values, so it is one commented line to
maintain rather than a literal buried in a workflow — currently protocol 3,
per docs/link/v3.md.

Tarball layout uses a FIXED top-level directory (runicgateway-overlay/)
rather than a versioned one, so the installer can find overlay/, patches/
and manifest.json at known paths instead of parsing the version it is trying
to read. tar's member order, mtime and ownership are pinned, so a given tree
produces a byte-identical tarball and its checksum changes only when the
contents do.

Verified locally against the real tree before pushing: YAML parses, all six
run blocks pass bash -n, the plan step produces v0.1.0 from actual history,
the gates pass (22 bridge scripts, all three patches parse), the manifest
renders with protocol=3 and 30 file hashes, and two consecutive builds of
the tarball produce the same SHA256.

One real bug caught by running it rather than reading it: sha256sum marks
binary mode by prefixing the path with `*` instead of the two-space
text-mode separator, which would have put a leading `*` on every key in the
manifest. The capture now tolerates both.

Not included: the workflow-dispatch call into the installer's bundle CI
(PLAN.md §7.2). That is Phase 0 item 3 and there is nothing to dispatch yet;
the insertion point is marked in the header. A step that 404s on every
release is worse than no step.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech approved these changes 2026-08-04 14:26:42 +00:00
whitlocktech merged commit 724262548b into main 2026-08-04 14:26:48 +00:00
whitlocktech deleted branch ci/overlay-release-workflow 2026-08-04 14:26:50 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/servuo-plugins#7
No description provided.