e8f3e954e7
Archetype: oci-image (buildx -> in-cluster remote buildkit), the HARD case. DESIGN/PARTIAL, not a finished migration: - ci/MIGRATION.md: concrete plan to escape buildkit via nix2container/skopeo +regctl. The app is pure-stdlib Python, so both arches are buildable on emmett (amd64-native + Nix-cross-from-amd64 python3 closure) with no buildkit/qemu/docker -> no foreign-arch leg needed; Dockerfile retired on cutover. Covers per-arch build, entrypoints, .woodpecker.yaml target, escape hatch (unused here), risks, remaining work. - flake.nix: scaffolds the natively-buildable amd64 leg only (stage-amd64, publish-amd64), dry-run by default (PUBLISH=1 to push), $REGISTRY_TOKEN -> pass fallback, registry-down/empty-token blockers. Mirrors reference impl claude-plugin-registry@9850745. arm64 leg, publish-index/publish, and YAML cutover are designed but NOT wired. Verified: nix eval .#apps.x86_64-linux (-> stage-amd64, publish-amd64); no image build run (downloads closure).
205 lines
8.2 KiB
Nix
205 lines
8.2 KiB
Nix
{
|
|
description = "alertmanager-gotify-bridge — pure-stdlib Python forwarder, containerized with Nix (nix2container). DESIGN/PARTIAL: amd64 leg only — see ci/MIGRATION.md.";
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
# SCAFFOLD / PARTIAL MIGRATION (emmett#44, archetype oci-image buildx→nix2container).
|
|
# This flake intentionally implements ONLY the natively-buildable amd64 leg
|
|
# (`nix run .#publish-amd64`, dry-run by default). The arm64 cross leg, the
|
|
# multi-arch `publish-index`/`publish` apps, and the `.woodpecker.yaml` cutover
|
|
# are DESIGNED in ci/MIGRATION.md but NOT yet wired. Do not treat this as the
|
|
# finished pipeline. Pattern mirrors the reference impl
|
|
# (oleks/claude-plugin-registry @ 9850745).
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
|
|
inputs = {
|
|
fleet.url = "git+https://git.oleks.space/oleks/fleet-pins";
|
|
nixpkgs.follows = "fleet/nixpkgs-projects";
|
|
|
|
nix2container.url = "github:nlewo/nix2container";
|
|
nix2container.inputs.nixpkgs.follows = "nixpkgs";
|
|
|
|
flake-utils.url = "github:numtide/flake-utils";
|
|
};
|
|
|
|
outputs =
|
|
{
|
|
nixpkgs,
|
|
nix2container,
|
|
flake-utils,
|
|
...
|
|
}:
|
|
flake-utils.lib.eachDefaultSystem (
|
|
system:
|
|
let
|
|
pkgs = import nixpkgs { inherit system; };
|
|
inherit (pkgs) lib;
|
|
n2c = nix2container.packages.${system}.nix2container;
|
|
|
|
registry = "git.oleks.space/oleks/alertmanager-gotify-bridge";
|
|
|
|
# TAG is derived identically for CI + local in shared code: CI exports
|
|
# VERSION (= strip-v $CI_COMMIT_TAG); local dev may override $VERSION.
|
|
# No version.nix side-channel — the app is a static asset, the flake has
|
|
# no version-baked source build, so the tag lives purely in $VERSION.
|
|
version = "0.0.0-dev";
|
|
|
|
# The whole payload: bridge.py + a python3 interpreter symlink under /app.
|
|
# The symlink keeps the (arch-correct) python3 Nix closure tracked while
|
|
# contributing no extra files. Parameterised over a pkg set so the SAME
|
|
# expression builds natively (amd64) and cross (arm64, future leg).
|
|
appRoot =
|
|
targetPkgs: arch:
|
|
pkgs.runCommand "app-root-${arch}" { } ''
|
|
mkdir -p $out/app
|
|
cp ${./bridge.py} $out/app/bridge.py
|
|
ln -s ${targetPkgs.python3}/bin/python3 $out/app/python3
|
|
'';
|
|
|
|
mkImage =
|
|
targetPkgs: arch:
|
|
n2c.buildImage {
|
|
name = registry;
|
|
tag = "${version}-${arch}";
|
|
inherit arch;
|
|
# reproducible = false materializes the layer tar so the image streams
|
|
# verbatim from any host (remote-builder + binary-cache safe). For a
|
|
# pure-stdlib interpreter closure the determinism risk is low, but the
|
|
# standard's caveat still applies: emmett+CI must resolve the identical
|
|
# python3 store path from the shared cache (fleet pin + flake.lock).
|
|
layers = [
|
|
(n2c.buildLayer {
|
|
copyToRoot = [
|
|
(appRoot targetPkgs arch)
|
|
pkgs.cacert
|
|
];
|
|
maxLayers = 25;
|
|
reproducible = false;
|
|
})
|
|
];
|
|
config = {
|
|
Cmd = [
|
|
"/app/python3"
|
|
"/app/bridge.py"
|
|
];
|
|
WorkingDir = "/app";
|
|
ExposedPorts = {
|
|
"8080/tcp" = { };
|
|
};
|
|
Env = [
|
|
"PORT=8080"
|
|
# urllib → Gotify over HTTPS needs an explicit CA bundle (the old
|
|
# Alpine base provided one via the distro; the Nix image ships it).
|
|
"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
|
|
];
|
|
};
|
|
};
|
|
|
|
imageAmd64 = mkImage pkgs "amd64";
|
|
|
|
# ── publish/stage apps (shared by CI + local; cannot drift). ──────────
|
|
# SAFETY: dry-run by default. Set PUBLISH=1 / --publish to actually push.
|
|
passEntry = "infra/gitea/personal_access_token_packages_rw";
|
|
|
|
publishGate = ''
|
|
PUBLISH="''${PUBLISH:-0}"
|
|
for a in "$@"; do
|
|
case "$a" in
|
|
--publish) PUBLISH=1 ;;
|
|
--dry-run) PUBLISH=0 ;;
|
|
--help|-h)
|
|
echo "usage: [VERSION=x.y.z] [PUBLISH=1] $(basename "$0") [--publish|--dry-run|--help]" >&2
|
|
echo " dry-run by default: builds the amd64 image and prints what would be pushed." >&2
|
|
echo " --publish / PUBLISH=1 actually mutates the registry." >&2
|
|
echo " NOTE: amd64 leg ONLY (scaffold) — see ci/MIGRATION.md." >&2
|
|
exit 0 ;;
|
|
*) echo "error: unknown argument '$a' (try --help)" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
TAG="''${VERSION:-${version}}"
|
|
'';
|
|
|
|
# Token resolved lazily (only when actually publishing); never echoed,
|
|
# never under set -x (writeShellApplication uses -euo pipefail only).
|
|
resolveToken = ''
|
|
TOKEN="''${REGISTRY_TOKEN:-}"
|
|
if [ -z "$TOKEN" ] && command -v pass >/dev/null 2>&1; then
|
|
TOKEN="$(pass show ${passEntry} 2>/dev/null || true)"
|
|
fi
|
|
if [ -z "$TOKEN" ]; then
|
|
echo "BLOCKER(empty-token): set REGISTRY_TOKEN env (CI from_secret) or have 'pass ${passEntry}' available; refusing to publish without credentials." >&2
|
|
exit 1
|
|
fi
|
|
'';
|
|
|
|
registryPreflight = ''
|
|
if ! curl -fsS -o /dev/null --max-time 10 "https://git.oleks.space/v2/" 2>/dev/null; then
|
|
echo "BLOCKER(registry-down): https://git.oleks.space/v2/ is unreachable — CI shares fate with the cluster (Zot/buildkit on armer/k3s). Re-run when the registry is back; the staged image in the Nix store is unchanged." >&2
|
|
exit 1
|
|
fi
|
|
'';
|
|
|
|
stageAmd64 = ''
|
|
echo "→ staging ${registry}:$TAG-amd64 (local build, no registry contact)"
|
|
OUT="$(nix build --no-link --print-out-paths "$FLAKE#image-amd64")"
|
|
echo " staged image derivation: $OUT"
|
|
'';
|
|
|
|
mkStageAmd64 = pkgs.writeShellApplication {
|
|
name = "stage-amd64";
|
|
runtimeInputs = [ pkgs.nix ];
|
|
text = ''
|
|
FLAKE="''${FLAKE:-.}"
|
|
''
|
|
+ publishGate
|
|
+ stageAmd64;
|
|
};
|
|
|
|
mkPublishAmd64 = pkgs.writeShellApplication {
|
|
name = "publish-amd64";
|
|
runtimeInputs = [
|
|
pkgs.regctl
|
|
pkgs.curl
|
|
pkgs.nix
|
|
];
|
|
text = ''
|
|
FLAKE="''${FLAKE:-.}"
|
|
''
|
|
+ publishGate
|
|
+ stageAmd64
|
|
+ ''
|
|
echo "→ ${registry}:$TAG-amd64"
|
|
if [ "$PUBLISH" != "1" ]; then
|
|
echo " [dry-run] would push ${registry}:$TAG-amd64 (set PUBLISH=1 / --publish to push)"
|
|
echo " [dry-run] NOTE: amd64 leg only — arm64 + index not yet implemented (ci/MIGRATION.md)"
|
|
exit 0
|
|
fi
|
|
''
|
|
+ resolveToken
|
|
+ registryPreflight
|
|
+ ''
|
|
${lib.getExe imageAmd64.copyTo} --dest-creds "oleks:$TOKEN" "docker://${registry}:$TAG-amd64"
|
|
'';
|
|
};
|
|
in
|
|
{
|
|
packages = {
|
|
image-amd64 = imageAmd64;
|
|
default = imageAmd64;
|
|
};
|
|
|
|
apps = {
|
|
stage-amd64 = {
|
|
type = "app";
|
|
program = lib.getExe mkStageAmd64;
|
|
meta.description = "Build the amd64 nix2container image into the local store (no registry contact). Scaffold: amd64 leg only.";
|
|
};
|
|
publish-amd64 = {
|
|
type = "app";
|
|
program = lib.getExe mkPublishAmd64;
|
|
meta.description = "Stage + push the amd64 image (dry-run by default; PUBLISH=1 to push). Scaffold: amd64 leg only — see ci/MIGRATION.md.";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
}
|