70 lines
4.1 KiB
Bash
70 lines
4.1 KiB
Bash
#!/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"
|
|
regctl image import "$target" "$context/image.tar"
|
|
# Import may change compression/manifest representation, but the immutable
|
|
# image config digest must still match the exact local image we verified.
|
|
expected_config="$(docker image inspect --format '{{.Id}}' "$target")"
|
|
remote_config="$(regctl manifest get "$target" --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"
|
|
done
|
|
echo "Published application and migrations: $image"
|