mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
ci(release): publish OCI container images
Motivation: Release images should be built, checked, published, and consumed through the same Nostr-native OCI path operators will use, without relying on an unreviewed local release procedure. Approach: Add a checked-in container manifest, a Docker-to-OCI layout helper, automatic tag publication and pull verification, a safe exact-tag backfill workflow, and deployment CI that imports and runs the exact generated layout. Correctness: Release tags come only from reviewed OCI index annotations; ordinary publication preserves prior tags; historical backfills cannot move latest or prerelease channels; generated images and temporary resources use bounded, validated names and cleanup. Excluded scope: This change does not alter ngit-grasp runtime behavior, change package versions, create v3.0.2, publish a container, move a release tag, or run the heavyweight container build in the coding VM. Validation: git diff --check; shellcheck on all container scripts; actionlint on all affected workflows; ngit parsing of .ngit/containers.yaml; canonical source and v3.0.1 tag resolution. The PR pipeline performs the full OCI build, import, and deployment test. Assisted-by: Codex (GPT-5)
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
.direnv
|
||||
.env
|
||||
.relay-owner.nsec
|
||||
artifacts
|
||||
data
|
||||
result
|
||||
result-*
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
# Rust build artifacts (single workspace target at repo root)
|
||||
target/
|
||||
|
||||
# Generated OCI release layouts
|
||||
/artifacts/
|
||||
|
||||
# Working directory (session-specific temporary files)
|
||||
work/*
|
||||
!work/README.md
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: container release backfill
|
||||
|
||||
jobs:
|
||||
linux-x86_64:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: danconwaydev/setup-ngit@v3
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- name: Require the disposable guest container daemon
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ ! -S /var/run/docker.sock ]]; then
|
||||
echo "container daemon socket is not available" >&2
|
||||
echo "this workflow requires an ngit-ci operator opt-in" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Build exact release OCI layout
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) ;;
|
||||
*)
|
||||
echo "manual container publication requires a refs/tags/v* context" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
version="${GITHUB_REF#refs/tags/v}"
|
||||
if [[ -z "$version" || "$version" == *[!A-Za-z0-9._-]* ]]; then
|
||||
echo "unsupported release version from tag: $GITHUB_REF" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch --depth=1 --no-tags \
|
||||
https://ngit.dev/ngit-grasp.git "$GITHUB_REF"
|
||||
revision="$(git rev-parse 'FETCH_HEAD^{commit}')"
|
||||
source_directory="$RUNNER_TEMP/ngit-grasp-${revision}"
|
||||
mkdir "$source_directory"
|
||||
git archive FETCH_HEAD | tar -xf - -C "$source_directory"
|
||||
|
||||
source_version="$(nix eval --raw "path:${source_directory}#static.version")"
|
||||
if [[ "$version" != "$source_version" ]]; then
|
||||
echo "tag version $version does not match package version $source_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
layout="$PWD/artifacts/ngit-grasp"
|
||||
CONTAINER_CONTEXT="$source_directory" \
|
||||
CONTAINER_EXACT_ONLY=true \
|
||||
nix shell nixpkgs#docker-client nixpkgs#jq nixpkgs#skopeo \
|
||||
--command scripts/build-container-layout.sh \
|
||||
"$version" "$revision" "$layout"
|
||||
- name: Publish exact container tag
|
||||
shell: bash
|
||||
env:
|
||||
NGIT_PUBLISHER_NBUNKSEC: ${{ secrets.NGIT_PUBLISHER_NBUNKSEC }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
: "${NGIT_PUBLISHER_NBUNKSEC:?Set the NGIT_PUBLISHER_NBUNKSEC repository secret}"
|
||||
|
||||
umask 077
|
||||
signer_file="$RUNNER_TEMP/ngit-container-nbunksec"
|
||||
trap 'rm -f "$signer_file"' EXIT
|
||||
printf '%s\n' "$NGIT_PUBLISHER_NBUNKSEC" > "$signer_file"
|
||||
|
||||
ngit container publish ngit-grasp \
|
||||
--repo nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/relay.ngit.dev/ngit-grasp \
|
||||
--manifest .ngit/containers.yaml \
|
||||
--nbunksec-file "$signer_file" \
|
||||
--defaults \
|
||||
--repo-relay-only \
|
||||
--json
|
||||
- name: Pull and verify the published image
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
version="${GITHUB_REF#refs/tags/v}"
|
||||
nix shell nixpkgs#docker-client \
|
||||
--command scripts/verify-published-container.sh "$version"
|
||||
@@ -10,7 +10,9 @@ on:
|
||||
- "build.rs"
|
||||
- "compose*.yaml"
|
||||
- "deploy/**"
|
||||
- "scripts/build-container-layout.sh"
|
||||
- "scripts/test-container-deployment.sh"
|
||||
- "scripts/test-container-release.sh"
|
||||
- "src/**"
|
||||
pull_request:
|
||||
|
||||
@@ -32,7 +34,12 @@ jobs:
|
||||
echo "this workflow requires an ngit-ci operator opt-in" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Build, replace, and verify the container
|
||||
- name: Build, import, replace, and verify the OCI release layout
|
||||
shell: bash
|
||||
run: |
|
||||
nix shell nixpkgs#docker-client nixpkgs#curl nixpkgs#jq \
|
||||
--command scripts/test-container-deployment.sh
|
||||
set -euo pipefail
|
||||
|
||||
version="$(nix eval --raw .#static.version)"
|
||||
revision="$(git rev-parse 'HEAD^{commit}')"
|
||||
nix shell nixpkgs#docker-client nixpkgs#curl nixpkgs#jq nixpkgs#skopeo \
|
||||
--command scripts/test-container-release.sh "$version" "$revision"
|
||||
|
||||
@@ -14,6 +14,14 @@ jobs:
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- name: Require the disposable guest container daemon
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ ! -S /var/run/docker.sock ]]; then
|
||||
echo "container daemon socket is not available" >&2
|
||||
echo "this workflow requires an ngit-ci operator opt-in" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Restore Nix store cache
|
||||
continue-on-error: true
|
||||
uses: nix-community/cache-nix-action@v7
|
||||
@@ -53,12 +61,50 @@ jobs:
|
||||
tar --sort=name --mtime="@${source_date_epoch}" \
|
||||
--owner=0 --group=0 --numeric-owner -C release-stage \
|
||||
-czf "dist/${archive}.tar.gz" "$archive"
|
||||
- name: Build OCI image layout
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
revision="$(git rev-parse "${GITHUB_REF_NAME}^{commit}")"
|
||||
nix shell nixpkgs#docker-client nixpkgs#jq nixpkgs#skopeo \
|
||||
--command scripts/build-container-layout.sh "$version" "$revision"
|
||||
- name: Upload release assets
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ngit-grasp-release-assets
|
||||
path: dist/*.tar.gz
|
||||
if-no-files-found: error
|
||||
- name: Publish OCI container
|
||||
shell: bash
|
||||
env:
|
||||
NGIT_PUBLISHER_NBUNKSEC: ${{ secrets.NGIT_PUBLISHER_NBUNKSEC }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
: "${NGIT_PUBLISHER_NBUNKSEC:?Set the NGIT_PUBLISHER_NBUNKSEC repository secret}"
|
||||
|
||||
umask 077
|
||||
signer_file="$RUNNER_TEMP/ngit-container-nbunksec"
|
||||
trap 'rm -f "$signer_file"' EXIT
|
||||
printf '%s\n' "$NGIT_PUBLISHER_NBUNKSEC" > "$signer_file"
|
||||
|
||||
ngit container publish ngit-grasp \
|
||||
--repo nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/relay.ngit.dev/ngit-grasp \
|
||||
--manifest .ngit/containers.yaml \
|
||||
--nbunksec-file "$signer_file" \
|
||||
--defaults \
|
||||
--repo-relay-only \
|
||||
--json
|
||||
- name: Pull and verify the published image
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
nix shell nixpkgs#docker-client \
|
||||
--command scripts/verify-published-container.sh "$version"
|
||||
- name: Publish NIP-82 release
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
schema: 1
|
||||
publication:
|
||||
blossom_servers:
|
||||
- https://blossom.primal.net
|
||||
- https://blossom.ditto.pub
|
||||
- https://haven.danconwaydev.com
|
||||
relays:
|
||||
- wss://relay.zapstore.dev
|
||||
- wss://relay.ditto.pub
|
||||
- wss://relay.dreamith.to
|
||||
- wss://relay.primal.net
|
||||
containers:
|
||||
ngit-grasp:
|
||||
layout: artifacts/ngit-grasp
|
||||
title: ngit-grasp
|
||||
description: A self-hostable Nostr relay and Git server for decentralized repositories.
|
||||
source: https://ngit.dev/ngit-grasp.git
|
||||
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Publish release container images as Nostr kind-30624 repositories backed by
|
||||
redundant Blossom storage. Stable images receive version and `latest` tags;
|
||||
prereleases receive version and channel tags. Maintainers can backfill an
|
||||
exact prior-release tag through CI without moving a stable channel backwards.
|
||||
|
||||
## [3.0.1] - 2026-08-29
|
||||
|
||||
### Fixed
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ ARG NGIT_IMAGE_REVISION=unknown
|
||||
LABEL org.opencontainers.image.title="ngit-grasp" \
|
||||
org.opencontainers.image.description="GRASP relay and Git Smart HTTP server" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.source="https://gitnostr.com/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git" \
|
||||
org.opencontainers.image.source="https://ngit.dev/ngit-grasp.git" \
|
||||
org.opencontainers.image.version="${NGIT_IMAGE_VERSION}" \
|
||||
org.opencontainers.image.revision="${NGIT_IMAGE_REVISION}"
|
||||
|
||||
|
||||
@@ -417,10 +417,15 @@ The shortest fresh-VPS path uses Docker Compose and Caddy:
|
||||
```bash
|
||||
cp deploy.env.example .env
|
||||
# Set NGIT_DOMAIN in .env and point DNS at this server.
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml up --build -d
|
||||
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest"
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml pull ngit-grasp
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml up --no-build -d
|
||||
scripts/verify-deployment.sh https://ngit.example.com
|
||||
```
|
||||
|
||||
Pin an explicit release version instead of `latest` for reproducible
|
||||
production deployments. The Docker guide also covers source builds.
|
||||
|
||||
For development from source:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -7,6 +7,17 @@ Caddy overlay for automatic HTTPS.
|
||||
The image contains ngit-grasp, Git, CA certificates, and a small init process.
|
||||
It prepares `/data` and then runs ngit-grasp as UID/GID `10001`.
|
||||
|
||||
Stable release images are published through Nostr, with their OCI blobs stored
|
||||
on Blossom. Docker and Podman can pull them through the ncontainer gateway:
|
||||
|
||||
```bash
|
||||
docker pull ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest
|
||||
```
|
||||
|
||||
`latest` tracks the newest stable release. Replace it with an explicit release
|
||||
version for a reproducible deployment. Release candidates are also published
|
||||
under their prerelease channel, such as `rc`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Engine with Compose v2, or a compatible Podman Compose setup
|
||||
@@ -28,7 +39,9 @@ cp deploy.env.example .env
|
||||
Set `NGIT_DOMAIN` in `.env`, then start the relay and Caddy:
|
||||
|
||||
```bash
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml up --build -d
|
||||
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest"
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml pull ngit-grasp
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml up --no-build -d
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml ps
|
||||
scripts/verify-deployment.sh https://ngit.example.com
|
||||
```
|
||||
@@ -46,10 +59,15 @@ its path routing explicitly.
|
||||
Start only the relay service:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest"
|
||||
docker compose pull ngit-grasp
|
||||
docker compose up --no-build -d
|
||||
curl -H 'Accept: application/nostr+json' http://127.0.0.1:7334
|
||||
```
|
||||
|
||||
To build a checked-out source revision instead, leave `NGIT_IMAGE` unset and
|
||||
use `docker compose up --build -d` with the same Compose file selection.
|
||||
|
||||
Proxy the public HTTPS hostname to `http://127.0.0.1:7334`. Preserve WebSocket
|
||||
upgrades, request methods, bodies, and query strings. Forwarded client IP
|
||||
headers are ignored by default; configure `NGIT_TRUSTED_PROXY_CIDRS` only after
|
||||
@@ -109,16 +127,21 @@ non-public deployment before relying on it.
|
||||
|
||||
## Upgrade
|
||||
|
||||
Pin or check out the intended tag, read `CHANGELOG.md`, take a backup, and
|
||||
rebuild without overlapping the old and new writers:
|
||||
Read `CHANGELOG.md`, take a backup, select an explicit release tag, and replace
|
||||
the container without overlapping the old and new writers:
|
||||
|
||||
```bash
|
||||
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:3.0.2"
|
||||
docker compose pull ngit-grasp
|
||||
docker compose stop ngit-grasp
|
||||
docker compose build --pull ngit-grasp
|
||||
docker compose up -d ngit-grasp
|
||||
docker compose up --no-build -d ngit-grasp
|
||||
scripts/verify-deployment.sh https://ngit.example.com
|
||||
```
|
||||
|
||||
Replace `3.0.2` with the intended release. For a source-built deployment,
|
||||
check out that tag and run `docker compose build --pull ngit-grasp` before
|
||||
starting the service instead.
|
||||
|
||||
For storage-changing releases, follow the linked migration guide and restore
|
||||
the pre-upgrade volume snapshot before attempting a binary rollback.
|
||||
|
||||
|
||||
@@ -76,9 +76,18 @@ created. The fields follow Render's
|
||||
[persistent disk guide](https://render.com/docs/disks) describes the storage
|
||||
and scaling constraints.
|
||||
|
||||
Render Blueprints require a repository connected through one of Render's
|
||||
supported Git providers. Until ngit-grasp has an authorized mirror or published
|
||||
OCI image, use Render's **Public Git Repository** flow with the canonical URL:
|
||||
The published image is available to provider workflows that accept a public
|
||||
Docker-compatible registry reference:
|
||||
|
||||
```text
|
||||
ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest
|
||||
```
|
||||
|
||||
Pin an explicit release version instead of `latest` for repeatable production
|
||||
deployments. The supplied `render.yaml` remains source-backed because Render
|
||||
Blueprints require a repository connected through one of Render's supported
|
||||
Git providers. Use Render's **Public Git Repository** flow with the canonical
|
||||
URL:
|
||||
|
||||
```text
|
||||
https://gitnostr.com/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
|
||||
|
||||
@@ -6,7 +6,7 @@ guide matching the host you already operate; every guide implements the same
|
||||
|
||||
| Environment | Start here | Supplied artifact |
|
||||
| --- | --- | --- |
|
||||
| Docker or Podman host | [Docker and Compose](deploy-docker.md) | `Dockerfile`, `compose.yaml`, optional Caddy overlay |
|
||||
| Docker or Podman host | [Docker and Compose](deploy-docker.md) | Published OCI image, `compose.yaml`, optional Caddy overlay |
|
||||
| NixOS | [NixOS module](deploy-nixos.md) | `nixosModules.default` |
|
||||
| Debian, Ubuntu, or another systemd Linux | [Static binary and systemd](deploy-linux.md) | Static flake package and service unit |
|
||||
| Proxmox LXC or VM | [Proxmox](deploy-proxmox-lxc.md) | Direct systemd or Compose path |
|
||||
@@ -18,7 +18,9 @@ with the Caddy overlay:
|
||||
```bash
|
||||
cp deploy.env.example .env
|
||||
# Set NGIT_DOMAIN in .env and point its DNS records at this server.
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml up --build -d
|
||||
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest"
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml pull ngit-grasp
|
||||
docker compose -f compose.yaml -f compose.caddy.yaml up --no-build -d
|
||||
scripts/verify-deployment.sh https://ngit.example.com
|
||||
```
|
||||
|
||||
|
||||
Executable
+174
@@ -0,0 +1,174 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
|
||||
echo "usage: $0 <version> <40-character-git-revision> [layout]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
version=$1
|
||||
revision=$2
|
||||
script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
repository_root=$(CDPATH='' cd -- "${script_dir}/.." && pwd)
|
||||
container_context=${CONTAINER_CONTEXT:-${repository_root}}
|
||||
container_context=$(CDPATH='' cd -- "${container_context}" && pwd)
|
||||
exact_only=${CONTAINER_EXACT_ONLY:-false}
|
||||
|
||||
case "${exact_only}" in
|
||||
true | false) ;;
|
||||
*)
|
||||
echo "CONTAINER_EXACT_ONLY must be true or false" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${version}" in
|
||||
[A-Za-z0-9_]*) ;;
|
||||
*)
|
||||
echo "version is not a valid OCI tag: ${version}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
case "${version}" in
|
||||
*[!A-Za-z0-9._-]*)
|
||||
echo "version is not a valid OCI tag: ${version}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
if [ "${#version}" -gt 128 ]; then
|
||||
echo "version is longer than the OCI tag limit: ${version}" >&2
|
||||
exit 2
|
||||
fi
|
||||
case "${revision}" in
|
||||
*[!0-9a-f]*)
|
||||
echo "revision is not a lowercase hexadecimal Git object ID: ${revision}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
if [ "${#revision}" -ne 40 ]; then
|
||||
echo "revision is not a 40-character Git object ID: ${revision}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
layout=${3:-artifacts/ngit-grasp}
|
||||
case "${layout}" in
|
||||
"")
|
||||
echo "layout path must not be empty" >&2
|
||||
exit 2
|
||||
;;
|
||||
/*) ;;
|
||||
*) layout=${container_context}/${layout} ;;
|
||||
esac
|
||||
if [ -e "${layout}" ]; then
|
||||
echo "refusing to replace existing OCI layout: ${layout}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for required_command in docker du jq skopeo; do
|
||||
if ! command -v "${required_command}" >/dev/null 2>&1; then
|
||||
echo "missing required command: ${required_command}" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
image_name=ngit-grasp-release-layout:${revision}-$$
|
||||
case "${image_name}" in
|
||||
ngit-grasp-release-layout:[0-9a-f]*-[0-9]*) ;;
|
||||
*)
|
||||
echo "refusing to use unexpected temporary image name" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
remove_image=false
|
||||
cleanup() {
|
||||
if [ "${remove_image}" = true ]; then
|
||||
docker image rm --force "${image_name}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
docker build \
|
||||
--pull \
|
||||
--platform linux/amd64 \
|
||||
--build-arg "NGIT_BUILD_REVISION=${revision}" \
|
||||
--build-arg "NGIT_IMAGE_REVISION=${revision}" \
|
||||
--build-arg "NGIT_IMAGE_VERSION=${version}" \
|
||||
--tag "${image_name}" \
|
||||
"${container_context}"
|
||||
remove_image=true
|
||||
|
||||
mkdir -p "$(dirname -- "${layout}")"
|
||||
skopeo --insecure-policy copy --format oci \
|
||||
"docker-daemon:${image_name}" \
|
||||
"oci:${layout}:${version}"
|
||||
|
||||
if [ "${exact_only}" = true ]; then
|
||||
alias_tag=${version}
|
||||
else
|
||||
case "${version}" in
|
||||
*-*)
|
||||
prerelease=${version#*-}
|
||||
alias_tag=${prerelease%%[.+]*}
|
||||
;;
|
||||
*) alias_tag=latest ;;
|
||||
esac
|
||||
fi
|
||||
case "${alias_tag}" in
|
||||
[A-Za-z0-9_]*) ;;
|
||||
*)
|
||||
echo "derived channel is not a valid OCI tag: ${alias_tag}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
case "${alias_tag}" in
|
||||
*[!A-Za-z0-9_.-]*)
|
||||
echo "derived channel is not a valid OCI tag: ${alias_tag}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
index=${layout}/index.json
|
||||
temporary_index=${index}.tmp.$$
|
||||
jq --arg version "${version}" --arg alias "${alias_tag}" '
|
||||
if .schemaVersion != 2 then
|
||||
error("OCI layout index schemaVersion must be 2")
|
||||
elif (.manifests | length) != 1 then
|
||||
error("OCI exporter must produce exactly one root manifest")
|
||||
else
|
||||
.manifests[0] as $image
|
||||
| .manifests = (
|
||||
[($image | .annotations = ((.annotations // {}) + {
|
||||
"org.opencontainers.image.ref.name": $version
|
||||
}))]
|
||||
+ if $alias == $version then [] else [
|
||||
($image | .annotations = ((.annotations // {}) + {
|
||||
"org.opencontainers.image.ref.name": $alias
|
||||
}))
|
||||
] end
|
||||
)
|
||||
end
|
||||
' "${index}" > "${temporary_index}"
|
||||
mv "${temporary_index}" "${index}"
|
||||
|
||||
jq -e --arg version "${version}" --arg alias "${alias_tag}" '
|
||||
[.manifests[].annotations["org.opencontainers.image.ref.name"]]
|
||||
== (if $version == $alias then [$version] else [$version, $alias] end)
|
||||
' "${index}" >/dev/null
|
||||
|
||||
layout_measure=$(du --apparent-size --block-size=1 --summarize -- "${layout}")
|
||||
layout_bytes=${layout_measure%%[!0-9]*}
|
||||
case "${layout_bytes}" in
|
||||
'' | *[!0-9]*)
|
||||
echo "failed to measure OCI layout: ${layout_measure}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${version}" = "${alias_tag}" ]; then
|
||||
printf 'OCI layout ready at %s with tag %s (%s apparent bytes)\n' \
|
||||
"${layout}" "${version}" "${layout_bytes}"
|
||||
else
|
||||
printf 'OCI layout ready at %s with tags %s and %s (%s apparent bytes)\n' \
|
||||
"${layout}" "${version}" "${alias_tag}" "${layout_bytes}"
|
||||
fi
|
||||
@@ -7,9 +7,11 @@ container_name=ngit-grasp-deployment-test-${test_suffix}
|
||||
volume_name=ngit-grasp-deployment-test-${test_suffix}
|
||||
if [ -n "${NGIT_TEST_IMAGE:-}" ]; then
|
||||
image_name=${NGIT_TEST_IMAGE}
|
||||
build_test_image=false
|
||||
remove_test_image=false
|
||||
else
|
||||
image_name=ngit-grasp:deployment-test-${test_suffix}
|
||||
build_test_image=true
|
||||
remove_test_image=true
|
||||
fi
|
||||
|
||||
@@ -71,7 +73,9 @@ relay_pubkey() {
|
||||
"$1" | jq -er '.pubkey'
|
||||
}
|
||||
|
||||
"${container_engine}" build --tag "${image_name}" .
|
||||
if [ "${build_test_image}" = true ]; then
|
||||
"${container_engine}" build --tag "${image_name}" .
|
||||
fi
|
||||
"${container_engine}" volume create "${volume_name}" >/dev/null
|
||||
|
||||
start_container
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "usage: $0 <version> <40-character-git-revision>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
version=$1
|
||||
revision=$2
|
||||
script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
||||
|
||||
case "${revision}" in
|
||||
*[!0-9a-f]*)
|
||||
echo "revision is not a lowercase hexadecimal Git object ID: ${revision}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
if [ "${#revision}" -ne 40 ]; then
|
||||
echo "revision is not a 40-character Git object ID: ${revision}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for required_command in docker jq skopeo; do
|
||||
if ! command -v "${required_command}" >/dev/null 2>&1; then
|
||||
echo "missing required command: ${required_command}" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
temporary_parent=${RUNNER_TEMP:-${TMPDIR:-/tmp}}
|
||||
temporary_parent=${temporary_parent%/}
|
||||
case "${temporary_parent}" in
|
||||
/*) ;;
|
||||
*)
|
||||
echo "temporary directory parent must be absolute: ${temporary_parent}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
if [ ! -d "${temporary_parent}" ]; then
|
||||
echo "temporary directory parent does not exist: ${temporary_parent}" >&2
|
||||
exit 2
|
||||
fi
|
||||
test_directory=$(mktemp -d "${temporary_parent}/ngit-grasp-container-release-test.XXXXXX")
|
||||
case "${test_directory}" in
|
||||
"${temporary_parent}"/ngit-grasp-container-release-test.??????) ;;
|
||||
*)
|
||||
echo "refusing to use unexpected test directory: ${test_directory}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
layout=${test_directory}/layout
|
||||
image_name=ngit-grasp:release-test-${revision}-$$
|
||||
case "${image_name}" in
|
||||
ngit-grasp:release-test-[0-9a-f]*-[0-9]*) ;;
|
||||
*)
|
||||
echo "refusing to use unexpected test image name" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
cleanup() {
|
||||
docker image rm --force "${image_name}" >/dev/null 2>&1 || true
|
||||
rm -rf -- "${test_directory}"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
"${script_dir}/build-container-layout.sh" \
|
||||
"${version}" \
|
||||
"${revision}" \
|
||||
"${layout}"
|
||||
|
||||
skopeo --insecure-policy copy --format v2s2 \
|
||||
"oci:${layout}:${version}" \
|
||||
"docker-daemon:${image_name}"
|
||||
|
||||
NGIT_TEST_IMAGE=${image_name} \
|
||||
"${script_dir}/test-container-deployment.sh"
|
||||
|
||||
printf 'OCI release layout and imported image verified for %s\n' "${version}"
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: $0 <version>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
version=$1
|
||||
case "${version}" in
|
||||
[A-Za-z0-9_]*) ;;
|
||||
*)
|
||||
echo "version is not a valid OCI tag: ${version}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
case "${version}" in
|
||||
*[!A-Za-z0-9._-]*)
|
||||
echo "version is not a valid OCI tag: ${version}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "missing required command: docker" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
image="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:${version}"
|
||||
cleanup() {
|
||||
docker image rm --force "${image}" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
deadline=$(($(date +%s) + 300))
|
||||
until docker pull "${image}"; do
|
||||
if [ "$(date +%s)" -ge "${deadline}" ]; then
|
||||
echo "published image did not become pullable within five minutes" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
actual=$(docker run --rm --entrypoint /usr/local/bin/ngit-grasp \
|
||||
"${image}" --version)
|
||||
expected="ngit-grasp ${version}"
|
||||
if [ "${actual}" != "${expected}" ]; then
|
||||
echo "expected '${expected}' from the published image, got '${actual}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'published image verified: %s\n' "${image}"
|
||||
Reference in New Issue
Block a user