INBUXA Admin had CI and nothing after it: twelve tags inherited from upstream's numbering, no GitHub releases at all, and no image. Deploying it meant building the tree yourself. This adds the three workflows ihasmail already runs -- weekly release, publish, prune -- and the Dockerfile they need. Monday 09:37 UTC, and nothing on a quiet week. Staggered twenty minutes behind ihasmail-inbuxa's and twenty ahead of the server's, so three releases do not compete for runners and a bad Monday names one repository rather than three. The version is the difference from ihasmail. ihasmail derives its version from the commit it builds, so its release only reads. INBUXA Admin keeps its version in inbuxa-version.json, so the release writes it: the bump is committed to main and the tag names that commit. The tree a tag points at therefore reports the version the tag claims, which a tag placed beside an unbumped file cannot promise. The bump is written with a JSON parser rather than sed, because a version substituted into JSON as a string is one stray quote from a file nothing can read. The image is nginx serving the built files and nothing else -- the interface talks to the mail server from the browser, never from the container. It is built from source in the image rather than copied from dist/, so an image always matches the commit it claims. One image serves any installation: API_BASE_URL writes the `<meta name="api-base-url">` tag that README already documents as the deploy-time way to point the interface at its server. Set nothing and the container still starts, for a build that was given VITE_API_BASE_URL instead. Two things the smoke test found rather than review. Unprivileged nginx runs as uid 101, so the copied files are chowned to it or the tag can never be written. And the directory stays root's, so the entrypoint writes back through the existing file instead of `sed -i`, which replaces the file and needs to create a temp file in the directory. Verified by running it: the tag lands, a deep route falls back to index.html, hashed assets come back immutable while index.html is no-cache, it runs as uid 101, and it starts with no API_BASE_URL set.
70 lines
3.1 KiB
YAML
70 lines
3.1 KiB
YAML
# Prune old image versions from GHCR.
|
|
#
|
|
# Releases are kept forever -- they carry no assets and their generated notes
|
|
# are this project's only changelog, so deleting one destroys history that
|
|
# cannot be reconstructed for nothing saved. Images are the opposite: a
|
|
# multi-arch build a week, and the by-digest push in publish.yml leaves two
|
|
# untagged per-architecture manifests behind each time on top of the tagged
|
|
# index. Those accumulate and nobody wants fifty of them.
|
|
#
|
|
# THE FOOTGUN: the obvious tool for this -- delete-package-versions with
|
|
# `delete-only-untagged-versions` -- will happily delete the per-architecture
|
|
# manifests that a multi-arch tag points *at*, because they are untagged by
|
|
# design. Nothing appears to break: the tag still exists, and pulls simply
|
|
# start failing for one architecture. This action understands manifest lists
|
|
# and will not orphan a retained index, and `validate` re-checks every
|
|
# multi-arch manifest against the registry afterwards.
|
|
#
|
|
# Separate from publish.yml, and dispatchable on its own, so `dry_run` can show
|
|
# exactly what would be deleted without rebuilding and re-pushing an image to
|
|
# find out.
|
|
name: Prune images
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
dry_run:
|
|
type: boolean
|
|
default: false
|
|
workflow_dispatch:
|
|
inputs:
|
|
dry_run:
|
|
description: "List what would be deleted, delete nothing"
|
|
type: boolean
|
|
default: true
|
|
|
|
jobs:
|
|
prune:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
packages: write
|
|
steps:
|
|
# The only third-party action here that is not published by GitHub or
|
|
# Docker, and the one with the most to lose: it is handed
|
|
# `packages: write` and its whole job is deletion, so a ref repointed at
|
|
# something else -- by a compromise or a mistake upstream -- is a bad
|
|
# day. It was pinned to a commit long before the rest of them were.
|
|
- uses: dataaxiom/ghcr-cleanup-action@d52806a0dc70b430571a37da1fde39733ffd640f # v1.2.2
|
|
with:
|
|
owner: inbuxa
|
|
package: inbuxa-admin
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
# Ten weekly releases is roughly a quarter of history, which is more
|
|
# than enough to roll back to and far less than the year's worth that
|
|
# would otherwise pile up. Older *releases* stay either way; this
|
|
# only removes the images.
|
|
keep-n-tagged: 10
|
|
# Belt and braces on top of the action's own manifest awareness:
|
|
# `latest` is never a candidate for deletion under any counting.
|
|
exclude-tags: latest
|
|
delete-untagged: true
|
|
# Sweeps the wreckage of a half-failed run: an index whose platform
|
|
# images did not all land, and referrers whose parent is gone.
|
|
delete-partial-images: true
|
|
delete-orphaned-images: true
|
|
# Checks every remaining multi-architecture manifest still resolves
|
|
# in the registry. This is the step that would catch the footgun
|
|
# above rather than leaving a reader to discover it on `docker pull`.
|
|
validate: true
|
|
dry-run: ${{ inputs.dry_run }}
|