Files
redefined-designs/.gitea/workflows/spike-registry.yml
T
bermudalamb 30859703d2
Linting / lint (pull_request) Successful in 2m35s
SonarQube Analysis / sonarqube (pull_request) Successful in 19m38s
spike(ci): authenticate with a package-scoped PAT instead of the Actions token (#237)
Iteration 2 got as far as the registry and was refused with a 401, not a certificate error, which established that everything except the credential already worked: the docker CLI installs, the daemon is reachable through the mounted socket, the container registry answers on /v2/, and TLS to the Gitea host is trusted from the runner.

The token Actions injects automatically is scoped for the repository API and is not accepted by the package registry. This switches to REGISTRY_TOKEN, a personal access token carrying write:package, which is the one thing the workflow could not arrange for itself.

Ref #237
2026-08-30 16:32:19 -05:00

155 lines
7.3 KiB
YAML

name: SPIKE — registry build and push
# THROWAWAY. Delete this file when #237 is answered, whichever way it goes.
#
# It exists to answer three questions that cannot be answered by reading
# anything in this repository, before a design is built on assumptions about
# them. #233 was designed on an assumption about the build context and broke
# every Portainer deploy; this is the correction to that habit.
#
# 1. Can a job on this runner run `docker build` at all? The other workflows
# use `services:`, which proves the runner can *start* containers, not
# that a job can build images. That needs a Docker socket or buildx.
# 2. Can it push to this Gitea's container registry? The packages API answers
# and lists nothing, so the registry has never been used here and
# "enabled" is unconfirmed.
# 3. How long does the real image take to build here? Not a nice-to-know:
# backend-integration.yml is manual because a job once held this runner
# for 3h12m, and any design that builds on every merge to main lands on
# the same runner.
#
# workflow_dispatch only. It cannot fire on push or pull request, so merging it
# changes nothing until someone presses the button.
on:
workflow_dispatch:
env:
REGISTRY: gitea.bermudalamb.synology.me
# Gitea's container registry namespaces by owner: <host>/<owner>/<image>.
IMAGE: gitea.bermudalamb.synology.me/bermudalamb/redefined-designs
jobs:
spike:
runs-on: ubuntu-latest
# The same reasoning as backend-integration.yml: not a performance budget,
# a stop so that a hang costs minutes rather than hours of a runner that
# everything else queues behind.
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
# First, and never failing, so that a failure in any later step is still
# informative rather than just "it did not work".
- name: Question 1 — what can this runner do?
continue-on-error: true
run: |
echo "--- docker client/daemon ---"
docker version || echo "NO DOCKER CLI OR DAEMON"
echo "--- buildx ---"
docker buildx version || echo "NO BUILDX"
echo "--- socket ---"
ls -l /var/run/docker.sock || echo "NO DOCKER SOCKET"
echo "--- disk ---"
df -h / | tail -1
# Iteration 2. The first run answered Question 1 with "no": the job
# container has no `docker` binary and login died with exit 127. What it
# also showed is that /var/run/docker.sock IS mounted, so the daemon is
# reachable and only the client is missing — which is a gap this step can
# close without changing how the runner itself is configured.
#
# The static binary rather than apt: it is one download with no package
# index to refresh and no repository to add, and only the CLI is wanted.
# The daemon already exists on the other side of that socket.
#
# Not continue-on-error. If this fails there is nothing left to measure,
# and a clear failure here is more useful than three skipped steps.
- name: Install the docker CLI, since the runner has none
run: |
DOCKER_VERSION=27.3.1
# Resolved rather than assumed. The NAS's architecture has never been
# confirmed anywhere in this repository, and hardcoding x86_64 would
# waste a whole run on a machine that everything else queues behind.
case "$(uname -m)" in
x86_64) DOCKER_ARCH=x86_64 ;;
aarch64|arm64) DOCKER_ARCH=aarch64 ;;
*) echo "unsupported architecture $(uname -m) — no static docker build for it"; exit 1 ;;
esac
echo "runner architecture: $(uname -m) -> downloading $DOCKER_ARCH"
curl -fsSL "https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz" -o /tmp/docker.tgz
tar -xzf /tmp/docker.tgz -C /tmp
install -m 0755 /tmp/docker/docker /usr/local/bin/docker
docker version
echo "--- can it reach the daemon through the socket? ---"
docker info --format 'server {{.ServerVersion}}, {{.Driver}}, {{.Architecture}}'
# REGISTRY_TOKEN, not GITEA_TOKEN. Iteration 2 established that the token
# Actions injects automatically is scoped for the repository API and is
# refused by the package registry — the login failed with a 401, not a
# certificate error, so everything except the credential was already
# working. This is a personal access token carrying write:package.
#
# Reported as a boolean and never printed. If this says EMPTY the fix is
# the secret, not the workflow.
- name: Is a token available?
continue-on-error: true
run: |
if [ -n "${{ secrets.REGISTRY_TOKEN }}" ]; then
echo "REGISTRY_TOKEN is present"
else
echo "REGISTRY_TOKEN is EMPTY — add a repo secret holding a PAT with write:package"
fi
- name: Question 2a — log in to the registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" \
| docker login "$REGISTRY" -u "${{ github.actor }}" --password-stdin
# A trivial image first, deliberately. It separates "can this runner build
# and push anything" from "does our real Dockerfile work here" — if the
# real build fails later, this having passed says the plumbing is fine and
# the problem is ours.
- name: Question 2b — build and push a trivial image
run: |
mkdir -p /tmp/spike
printf 'FROM alpine:3.20\nRUN echo spike > /spike.txt\n' > /tmp/spike/Dockerfile
docker build -t "$IMAGE:spike-trivial" /tmp/spike
docker push "$IMAGE:spike-trivial"
echo "pushed $IMAGE:spike-trivial"
# The real thing, timed. The tag records the commit so that the pulled
# image can be checked against what was built — which is the whole point
# of the design this spike is testing.
- name: Question 3 — build the real image and time it
run: |
SHORT_SHA="$(git rev-parse --short HEAD)"
echo "building $IMAGE:spike-$SHORT_SHA"
START=$(date +%s)
docker build -t "$IMAGE:spike-$SHORT_SHA" .
END=$(date +%s)
echo "REAL BUILD TOOK $((END - START)) SECONDS"
docker push "$IMAGE:spike-$SHORT_SHA"
echo "pushed $IMAGE:spike-$SHORT_SHA"
# Proves the commit can actually be baked in, which is the reason for the
# whole exercise. In a Gitea Actions checkout .git is present, unlike in
# Portainer's build context — that difference is exactly what #235 was.
- name: Does the built image know its own commit?
continue-on-error: true
run: |
SHORT_SHA="$(git rev-parse --short HEAD)"
echo "git says: $SHORT_SHA"
echo "image says:"
docker run --rm "$IMAGE:spike-$SHORT_SHA" cat dist/buildInfo.json || echo "no stamp"
- name: What to do next
if: always()
run: |
echo "If the pushes succeeded, try pulling from Portainer:"
echo " 1. Portainer > Registries > Add registry > Custom"
echo " URL: $REGISTRY (username + a token with package read)"
echo " 2. docker pull $IMAGE:spike-trivial"
echo "Then delete this workflow and the spike-* packages. See #237."