chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
name: Container Images
|
||||
|
||||
# Builds the multi-arch (linux/amd64 + linux/arm64) gateway image and
|
||||
# publishes it to GHCR so home-server/NAS users can `docker pull` a release
|
||||
# instead of building from a source checkout. See docs/docker.md.
|
||||
#
|
||||
# Publishing model:
|
||||
# * Pushing a release tag (v0.5.0rc3, v0.6.0, ...) first publishes the
|
||||
# immutable ghcr.io/<owner>/<repo>:<tag> image. The workflow verifies its
|
||||
# amd64/arm64 manifest and container HEALTHCHECK before moving :latest.
|
||||
# :latest tracks the most recently pushed release tag — including
|
||||
# prereleases and backports; if a backport repoints it, re-run this
|
||||
# workflow from the newest tag.
|
||||
# * workflow_dispatch validates the full multi-arch build without
|
||||
# publishing; set publish=true to push the result as :edge.
|
||||
#
|
||||
# The GITHUB_TOKEN with job-level `packages: write` is the only credential.
|
||||
# First-time note for maintainers: a new GHCR package is created private —
|
||||
# flip ghcr.io/<owner>/<repo> to public in the package settings once.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: Push the built image to GHCR as the edge tag (leave off to only validate the build)
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: container-images-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
name: Build multi-arch gateway image
|
||||
runs-on: ubuntu-latest
|
||||
# The arm64 leg runs under QEMU emulation. All compiled dependencies
|
||||
# install from prebuilt aarch64 wheels, so this is emulated pip time,
|
||||
# not source builds — slow but bounded.
|
||||
timeout-minutes: 120
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Validate release tag
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
if [[ ! "${GITHUB_REF_NAME}" =~ ^v[0-9]+[.][0-9]+[.][0-9]+.*$ ]]; then
|
||||
echo "Release tag must look like v0.2.0rc1 or v0.2.0: ${GITHUB_REF_NAME}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check tag version
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
version="$(python3 - <<'PY'
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
|
||||
print(project["project"]["version"])
|
||||
PY
|
||||
)"
|
||||
tag_version="${GITHUB_REF_NAME#v}"
|
||||
if [[ "${tag_version}" != "${version}" ]]; then
|
||||
echo "Tag ${GITHUB_REF_NAME} does not match project version ${version}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Hydrate router assets
|
||||
run: git lfs pull --include="src/opensquilla/squilla_router/models/**"
|
||||
|
||||
# The Dockerfile hard-fails on LFS pointer files; failing here first
|
||||
# gives a clearer error than a mid-build SystemExit.
|
||||
- name: Verify router assets are hydrated
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
root = Path("src/opensquilla/squilla_router/models/v4.2_phase3_inference")
|
||||
required = [
|
||||
root / "PROVENANCE.md",
|
||||
root / "artifact_manifest.json",
|
||||
root / "bge_onnx" / "model.onnx",
|
||||
root / "features" / "tfidf.pkl",
|
||||
root / "lgbm_main.bin",
|
||||
root / "mlp" / "model.onnx",
|
||||
root / "router.runtime.yaml",
|
||||
]
|
||||
for path in required:
|
||||
assert path.is_file(), f"missing router asset: {path}"
|
||||
prefix = path.read_bytes()[:80]
|
||||
assert not prefix.startswith(b"version https://git-lfs.github.com/spec/v1"), (
|
||||
f"Git LFS pointer file is not hydrated: {path}"
|
||||
)
|
||||
PY
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: ${{ github.event_name == 'push' || inputs.publish == true }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Release tags are PEP 440 (v0.5.0rc3), not strict semver, so the git
|
||||
# tag is mapped through as-is instead of via type=semver.
|
||||
- name: Compute image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=ref,event=tag,enable=${{ github.event_name == 'push' }}
|
||||
type=raw,value=edge,enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Build multi-arch image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name == 'push' || inputs.publish == true }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
# Keep the pushed artifact a plain two-entry manifest list; the
|
||||
# default provenance attestation adds an unknown/unknown entry that
|
||||
# confuses registry UIs and `docker pull` inspection.
|
||||
provenance: false
|
||||
|
||||
- name: Select pushed image
|
||||
if: ${{ github.event_name == 'push' || inputs.publish == true }}
|
||||
id: pushed_image
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
|
||||
image_tag="${GITHUB_REF_NAME}"
|
||||
else
|
||||
image_tag="edge"
|
||||
fi
|
||||
echo "ref=ghcr.io/${GITHUB_REPOSITORY}:${image_tag}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Verify pushed manifest platforms
|
||||
if: ${{ github.event_name == 'push' || inputs.publish == true }}
|
||||
env:
|
||||
IMAGE_REF: ${{ steps.pushed_image.outputs.ref }}
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
image_ref = os.environ["IMAGE_REF"]
|
||||
manifest = json.loads(
|
||||
subprocess.check_output(
|
||||
["docker", "buildx", "imagetools", "inspect", image_ref, "--raw"],
|
||||
text=True,
|
||||
)
|
||||
)
|
||||
platforms = {
|
||||
f'{item["platform"]["os"]}/{item["platform"]["architecture"]}'
|
||||
for item in manifest.get("manifests", [])
|
||||
}
|
||||
expected = {"linux/amd64", "linux/arm64"}
|
||||
if platforms != expected:
|
||||
raise SystemExit(
|
||||
f"{image_ref} has platforms {sorted(platforms)}; "
|
||||
f"expected {sorted(expected)}"
|
||||
)
|
||||
print(f"Verified {image_ref}: {sorted(platforms)}")
|
||||
PY
|
||||
|
||||
- name: Smoke pushed image HEALTHCHECK
|
||||
if: ${{ github.event_name == 'push' || inputs.publish == true }}
|
||||
env:
|
||||
IMAGE_REF: ${{ steps.pushed_image.outputs.ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
container_id="$(docker run --detach --pull=always "${IMAGE_REF}")"
|
||||
trap 'docker rm --force "${container_id}" >/dev/null 2>&1 || true' EXIT
|
||||
|
||||
deadline=$((SECONDS + 180))
|
||||
while (( SECONDS < deadline )); do
|
||||
running="$(docker inspect --format '{{.State.Running}}' "${container_id}")"
|
||||
health="$(docker inspect \
|
||||
--format '{{if .State.Health}}{{.State.Health.Status}}{{else}}missing{{end}}' \
|
||||
"${container_id}")"
|
||||
echo "Container health: ${health}"
|
||||
if [[ "${health}" == "healthy" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${running}" != "true" || "${health}" == "unhealthy" || "${health}" == "missing" ]]; then
|
||||
docker logs "${container_id}"
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "Container did not become healthy within 180 seconds" >&2
|
||||
docker logs "${container_id}"
|
||||
exit 1
|
||||
|
||||
# Promotion happens only after the immutable release image passes both
|
||||
# remote-manifest verification and its image-defined HEALTHCHECK.
|
||||
- name: Promote verified release image to latest
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
IMAGE_REF: ${{ steps.pushed_image.outputs.ref }}
|
||||
LATEST_REF: ghcr.io/${{ github.repository }}:latest
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag "${LATEST_REF}" \
|
||||
"${IMAGE_REF}"
|
||||
Reference in New Issue
Block a user