ci: remove container publication workflows
CI / check (push) Failing after 1m26s
CI / preflight (push) Skipped
CI / deploy (push) Skipped

This commit is contained in:
Simo committed 2026-09-14 21:28:21 +02:00
1 parent cfb4c36569
commit fff284aaa0
4 files changed
+2 -195

No files matched your search

-23
View File
@@ -150,26 +150,3 @@ jobs:
path: build-reports/
if-no-files-found: warn
retention-days: 14
# Publish only after checks and the production deployment have succeeded.
# Serial execution also avoids two builds competing on the self-hosted runner.
publish-container:
needs: deploy
if: gitea.event_name == 'push' && (gitea.ref_name == 'main' || gitea.ref_name == 'master')
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: ${{ gitea.repository }}
token: ${{ gitea.token }}
- name: Build, verify portability and publish
shell: bash
env:
REGISTRY_SERVER: ${{ gitea.server_url }}
REGISTRY_REPOSITORY: ${{ gitea.repository }}
REGISTRY_NAMESPACE: ${{ vars.CONTAINER_REGISTRY_NAMESPACE }}
REGISTRY_USER: ${{ secrets.CONTAINER_REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
run: bash scripts/publish-container.sh
-35
View File
@@ -1,35 +0,0 @@
name: Publish portable container
on:
workflow_dispatch:
jobs:
publish:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: ${{ gitea.repository }}
token: ${{ gitea.token }}
- name: Install and verify
run: |
node scripts/check-node-toolchain.mjs
pnpm install --frozen-lockfile
pnpm typecheck
pnpm biome:lint
pnpm test:coverage
env:
SKIP_ENV_VALIDATION: 1
NODE_ENV: test
DATABASE_URL: mysql://test:[email protected]:9/test
AUTH_SECRET: container-test-secret-at-least-32-characters
- name: Build, verify portability and publish
shell: bash
env:
REGISTRY_SERVER: ${{ gitea.server_url }}
REGISTRY_REPOSITORY: ${{ gitea.repository }}
REGISTRY_NAMESPACE: ${{ vars.CONTAINER_REGISTRY_NAMESPACE }}
REGISTRY_USER: ${{ secrets.CONTAINER_REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
run: bash scripts/publish-container.sh
+2 -64
View File
@@ -250,9 +250,7 @@ bash cms update
The updater pulls the configured Git upstream, loads `.docker-install`, uses the
matching application/migration images and verifies the running release locally
and at the saved public URL. If publication for the new commit is still running,
it stops before replacing the current container; run the same command after CI
succeeds. Existing application rollback remains available on a failed cutover;
and at the saved public URL. Existing application rollback remains available on a failed cutover;
database migrations are not reversed.
`bash cms install --configure-only` saves configuration without preparing runtime
@@ -261,67 +259,7 @@ committed or sent in Docker build contexts. Existing users of
`bash scripts/docker-update.sh` retain the previous behavior when no wizard
profile exists; explicit `CMS_IMAGE_REPOSITORY`/`CMS_PUBLIC_URL` overrides still work.
To publish from Gitea:
Gitea packages belong to an account or organization, independently of repository
permissions. Publication defaults to the lowercase `CONTAINER_REGISTRY_USER`
namespace, so a Simo token publishes `simo/epicnext-cms` even though the Git
repository belongs to remco. Set the Actions variable
`CONTAINER_REGISTRY_NAMESPACE` only to override this (for example, an organization
where the token account has package write access). Keep the login username and
token from the same account. Changing namespace also changes the image URL used
by installations; existing remco image tags are not moved automatically.
1. In the repository's Actions secrets, configure `CONTAINER_REGISTRY_USER` and
`CONTAINER_REGISTRY_TOKEN`. Use a Gitea access token with package read/write
permission belonging to the login account. For the Simo token, set
`CONTAINER_REGISTRY_USER=Simo`; the default package namespace will be `simo`.
2. Every push to `main` or `master` automatically builds and publishes the images
after the CI checks and production deployment succeed. Pull requests do not
publish images. The publication job builds from committed source only and checks
the same application image with two runtime configurations before pushing.
Missing registry secrets fail the publication job explicitly; they do not undo
an already successful production deployment. No `latest` tag is moved.
**Publish portable container** remains available for manual retries on the
commit/branch to distribute, without redeploying production.
3. The images are `<gitea-host>/<owner>/<repository-lowercase>:<full-commit>` and
`:<full-commit>-migrations`. Only the application image runs the website; the
migrations image is used temporarily for the matching database migrations.
Publication uses checksum-pinned regctl v0.11.6 with 8 MiB blob requests to
avoid monolithic layer uploads exceeding reverse-proxy limits. Both images are
exported and uploaded sequentially; temporary archives and credentials are removed
on exit. The runner needs curl, sha256sum and temporary disk space for one Docker
image archive plus its extracted OCI layout. The remote image config digest is checked against the locally normalized archive
after each upload. A proxy must still allow the OCI registry PATCH/PUT endpoints.
For this repository the image base is
`gitlab.epicnabbo.nl/simo/epicnext-cms`. Package access is controlled by Gitea.
For private packages, run `docker login gitlab.epicnabbo.nl` on the installation
with a token that can read packages. Then update with:
```bash
CMS_IMAGE_REPOSITORY=gitlab.epicnabbo.nl/simo/epicnext-cms \
CMS_PUBLIC_URL=https://your-hotel.example \
bash scripts/docker-update.sh
```
The updater pulls the configured Git upstream and requires both images for that
exact commit. A missing image or failed login stops before replacing the running
CMS. Local builds remain the default when `CMS_IMAGE_REPOSITORY` is unset. Both
paths retain the existing image/HTTP checks and automatic application rollback.
Migration secrets are mounted read-only for the temporary migration container;
they are never copied into its image. Registry images currently target the Linux
architecture of the self-hosted build runner; this is not a multi-architecture release.
The portability gate checks release identity, runtime avatar/badge routing and
absence of installation environment files in the application image. It uses an
unreachable fixture database and does not replace a full live database/site smoke
test. See `scripts/verify-portable-image.mjs`. Production deployment remains verified
separately by the existing CI workflow.
References: [Gitea container registry](https://docs.gitea.com/usage/packages/container/)
and [Next.js runtime environment variables](https://nextjs.org/docs/app/guides/self-hosting).
Container publication is disabled. CI builds, checks and deploys the CMS, but it does not log in to a registry or upload container images.
### Diagnose an update that is not visible
-73
View File
@@ -1,73 +0,0 @@
#!/usr/bin/env bash
set -Eeuo pipefail
: "${REGISTRY_SERVER:?Missing Gitea server URL}"
: "${REGISTRY_REPOSITORY:?Missing owner/repository}"
: "${REGISTRY_USER:?Configure CONTAINER_REGISTRY_USER}"
: "${REGISTRY_TOKEN:?Configure CONTAINER_REGISTRY_TOKEN with package write access}"
sha="$(git rev-parse HEAD)"
[[ "$sha" =~ ^[0-9a-f]{40}$ ]] || exit 1
registry="${REGISTRY_SERVER#https://}"
registry="${registry%/}"
[[ "$REGISTRY_SERVER" = https://* && "$registry" != */* ]] || { echo "Registry must use HTTPS at the Gitea server root" >&2; exit 1; }
repository="${REGISTRY_REPOSITORY,,}"
[[ "$repository" =~ ^[a-z0-9._-]+/[a-z0-9._-]+$ ]] || exit 1
# Gitea packages belong to a user/organization, independently of repository ACLs.
# A collaborator token cannot publish to another user's personal namespace.
namespace="${REGISTRY_NAMESPACE:-$REGISTRY_USER}"
namespace="${namespace,,}"
[[ "$namespace" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || { echo "Invalid registry namespace; use a Gitea username or organization" >&2; exit 1; }
repository="$namespace/${repository#*/}"
image="$registry/$repository:$sha"
# Isolate credentials from the self-hosted runner's normal Docker configuration.
export DOCKER_CONFIG
DOCKER_CONFIG="$(mktemp -d)"
context="$(mktemp -d)"
trap 'rm -rf -- "$DOCKER_CONFIG" "$context"' EXIT
# Build only the committed source, never untracked files from a shared runner.
git archive HEAD | tar -x -C "$context"
printf '%s' "$REGISTRY_TOKEN" | docker login "$registry" --username "$REGISTRY_USER" --password-stdin
unset REGISTRY_TOKEN
# On the shared runner, publish the exact image already verified by deployment.
local_image="epicnext-cms:$sha"
local_revision="$(docker image inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' "$local_image" 2>/dev/null || true)"
local_id="$(docker image inspect --format '{{.Id}}' "$local_image" 2>/dev/null || true)"
verified_id="$(docker image inspect --format '{{.Id}}' "epicnext-cms:verified-$sha" 2>/dev/null || true)"
if [[ "$local_revision" = "$sha" && -n "$local_id" && "$local_id" = "$verified_id" ]]; then
docker tag "$verified_id" "$image"
echo "Reusing verified release image $local_image"
else
docker build --network=host --build-arg NEXT_DEPLOYMENT_ID="$sha" -t "$image" "$context"
fi
docker build --network=host --target migrations -t "$image-migrations" "$context"
node scripts/verify-portable-image.mjs "$image" "$sha"
# Publish only after the same application image passed both runtime configurations.
# Bound each blob request below reverse-proxy upload limits. Pin the uploader
# and verify its checksum before giving it access to the temporary Docker login.
case "$(uname -m)" in
x86_64) arch=amd64; checksum=8e0e62a497fcdb8048d18aa927a139613176ba0531f412bc541044e28f9856bd ;;
aarch64|arm64) arch=arm64; checksum=a9b71a3ee79b2d1dbbd7d51fd5e8fa214722c192864235d3d8764463c751a1ff ;;
*) echo "Unsupported registry uploader architecture" >&2; exit 1 ;;
esac
curl --fail --silent --show-error --location --retry 3 --connect-timeout 15 --max-time 120 \
"https://github.com/regclient/regclient/releases/download/v0.11.6/regctl-linux-$arch" -o "$context/regctl"
printf '%s %s\n' "$checksum" "$context/regctl" | sha256sum --check --status
chmod 700 "$context/regctl"
export REGCTL_CONFIG="$DOCKER_CONFIG/regctl.json"
regctl() { "$context/regctl" "$@"; }
regctl registry set "$registry" --blob-chunk 8388608 --blob-max 8388608
for target in "$image-migrations" "$image"; do
echo "Publishing $target with blob requests up to 8 MiB"
docker image save --output "$context/image.tar" "$target"
# Normalize the saved archive locally before copying it unchanged to Gitea.
# Docker engine IDs and OCI index IDs are not interchangeable with config IDs.
local_ref="ocidir://$context/oci:verified"
regctl image import "$local_ref" "$context/image.tar"
expected_config="$(regctl manifest get "$local_ref" --platform "linux/$arch" --format '{{.GetConfig.Digest}}')"
[[ "$expected_config" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "Invalid local image config digest" >&2; exit 1; }
regctl image copy "$local_ref" "$target"
remote_config="$(regctl manifest get "$target" --platform "linux/$arch" --format '{{.GetConfig.Digest}}')"
[[ "$remote_config" = "$expected_config" ]] || { echo "Published image config does not match verified local image" >&2; exit 1; }
rm -f -- "$context/image.tar"
rm -rf -- "$context/oci"
done
echo "Published application and migrations: $image"