Publish the native release as a container image and document deployment
Checks / web (push) Failing after 19s
Private ROM checks / private-rom (push) Failing after 25m51s

A static nginx image (unprivileged, read-only) of the production build,
published to Gitea's registry for amd64 and arm64: version tags go to
prod, manual dev-<commit> builds to dev, each smoke-tested first and
optionally announced to Dockhand. The README describes running it with
Docker Compose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-18 00:27:23 +02:00
1 parent d3e3faff72
commit b33725adfa
9 files changed
+584

No files matched your search

+115
View File
@@ -0,0 +1,115 @@
import { describe, expect, it, vi } from "vitest";
import { createHmac } from "node:crypto";
import { imagePublication } from "../scripts/image-publication.mjs";
import { notifyDockhand } from "../scripts/notify-dockhand.mjs";
const revision = "abcdef01".repeat(5);
const publish = (event: string, ref: string, inputTag = "") =>
imagePublication({ event, ref, inputTag, revision });
describe("image publication channels", () => {
it.each(["v1.2.3", "1.2.3", "v0.0.0", "v10.20.300"])(
"publishes stable version %s to prod",
(tag) =>
expect(publish("push", `refs/tags/${tag}`)).toEqual({
tag,
channel: "prod",
revision,
}),
);
it.each(["abcdef0", revision])(
"publishes a matching manual commit %s only to dev",
(commit) => {
expect(
publish("workflow_dispatch", "refs/heads/main", `dev-${commit}`),
).toEqual({ tag: `dev-${commit}`, channel: "dev", revision });
expect(
publish("workflow_dispatch", "refs/tags/v1.2.3", `dev-${commit}`)
.channel,
).toBe("dev");
},
);
it.each([
"v1.2.3-rc.1",
"v1.2.3+build",
"v01.2.3",
"v1.2",
"prod",
"latest",
"dev-abcdef0",
"release/v1.2.3",
"v1.2.3\nchannel=prod",
"v1.2.3\n",
])("rejects non-release Git tag %s before publication", (tag) =>
expect(() => publish("push", `refs/tags/${tag}`)).toThrow(),
);
it.each([
"v1.2.3",
"prod",
"latest",
"dev",
"",
"dev-1234567",
"dev-abcdef",
"dev-ABCDEF0",
"dev-abcdef0\nchannel=prod",
"dev-abcdef0\n",
"dev-$(exit 1)",
])("rejects manual tag %s instead of promoting it", (tag) =>
expect(() =>
publish("workflow_dispatch", "refs/heads/main", tag),
).toThrow(),
);
it("does not publish branches or pull requests", () => {
expect(() => publish("push", "refs/heads/main")).toThrow();
expect(() => publish("pull_request", "refs/tags/v1.2.3")).toThrow();
});
});
describe("optional Dockhand deployment", () => {
const url = "https://dockhand.example.com/api/git/stacks/42/webhook";
const secret = "test-webhook-secret";
it("does not contact Dockhand when this channel has no configuration", async () => {
const send = vi.fn();
await expect(notifyDockhand({}, send)).resolves.toBe(false);
expect(send).not.toHaveBeenCalled();
});
it.each([{ url }, { secret }])(
"rejects incomplete configuration",
async (config) => {
const send = vi.fn();
await expect(notifyDockhand(config, send)).rejects.toThrow();
expect(send).not.toHaveBeenCalled();
},
);
it("signs exactly the posted payload and refuses redirects", async () => {
const send = vi.fn().mockResolvedValue(new Response("{}"));
await expect(notifyDockhand({ url, secret }, send)).resolves.toBe(true);
const [target, request] = send.mock.calls[0];
expect(target.href).toBe(url);
expect(request.method).toBe("POST");
expect(request.body).toBe("{}");
expect(request.redirect).toBe("error");
expect(request.headers["X-Hub-Signature-256"]).toBe(
`sha256=${createHmac("sha256", secret).update(request.body).digest("hex")}`,
);
});
it("reports failed deployment requests", async () => {
const send = vi
.fn()
.mockResolvedValue(
new Response("private server details", { status: 403 }),
);
await expect(notifyDockhand({ url, secret }, send)).rejects.toThrow(
"HTTP 403",
);
});
});