ci: remove container publication workflows
This commit is contained in:
1 parent
cfb4c36569
commit
fff284aaa0
4 files changed
+2
-195
No files matched your search
@@ -150,26 +150,3 @@ jobs:
|
|||||||
path: build-reports/
|
path: build-reports/
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
retention-days: 14
|
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
|
|
||||||
@@ -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
|
|
||||||
@@ -250,9 +250,7 @@ bash cms update
|
|||||||
|
|
||||||
The updater pulls the configured Git upstream, loads `.docker-install`, uses the
|
The updater pulls the configured Git upstream, loads `.docker-install`, uses the
|
||||||
matching application/migration images and verifies the running release locally
|
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,
|
and at the saved public URL. Existing application rollback remains available on a failed cutover;
|
||||||
it stops before replacing the current container; run the same command after CI
|
|
||||||
succeeds. Existing application rollback remains available on a failed cutover;
|
|
||||||
database migrations are not reversed.
|
database migrations are not reversed.
|
||||||
|
|
||||||
`bash cms install --configure-only` saves configuration without preparing runtime
|
`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
|
`bash scripts/docker-update.sh` retain the previous behavior when no wizard
|
||||||
profile exists; explicit `CMS_IMAGE_REPOSITORY`/`CMS_PUBLIC_URL` overrides still work.
|
profile exists; explicit `CMS_IMAGE_REPOSITORY`/`CMS_PUBLIC_URL` overrides still work.
|
||||||
|
|
||||||
To publish from Gitea:
|
Container publication is disabled. CI builds, checks and deploys the CMS, but it does not log in to a registry or upload container images.
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
### Diagnose an update that is not visible
|
### Diagnose an update that is not visible
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
Reference in new issue
Block a user