diff --git a/.forgejo/actions/build-push-image/action.yaml b/.forgejo/actions/build-push-image/action.yaml index 1d5676b..b278e02 100644 --- a/.forgejo/actions/build-push-image/action.yaml +++ b/.forgejo/actions/build-push-image/action.yaml @@ -1,5 +1,8 @@ name: 'Build and Push Image' -description: 'Build a container image with Buildah and push to registry' +description: 'Build a container image with Buildah and push to zot registry' + +# TODO: Investigate zot tag immutability to prevent overwriting released versions +# See: https://zotregistry.dev/v2.1.1/articles/immutable-tags/ inputs: context: @@ -12,10 +15,9 @@ inputs: image_name: description: 'Image name (without registry, e.g. blumeops/devpi)' required: true - tag: - description: 'Image tag' - required: false - default: 'latest' + version: + description: 'Version tag (e.g. v1.0.0)' + required: true registry: description: 'Registry URL' required: false @@ -27,8 +29,9 @@ runs: - name: Build image with Buildah shell: bash run: | + echo "Building ${{ inputs.image_name }}:${{ inputs.version }}" buildah bud \ - --tag ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.tag }} \ + --tag ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.version }} \ --tag ${{ inputs.registry }}/${{ inputs.image_name }}:${{ github.sha }} \ --file ${{ inputs.context }}/${{ inputs.dockerfile }} \ ${{ inputs.context }} @@ -36,12 +39,16 @@ runs: - name: Push to registry shell: bash run: | - buildah push ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.tag }} + echo "Pushing ${{ inputs.image_name }}:${{ inputs.version }}" + buildah push ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.version }} buildah push ${{ inputs.registry }}/${{ inputs.image_name }}:${{ github.sha }} - - name: Verify push + - name: Summary shell: bash run: | - echo "✅ Pushed: ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.tag }}" - echo "✅ Pushed: ${{ inputs.registry }}/${{ inputs.image_name }}:${{ github.sha }}" - curl -sf "https://${{ inputs.registry }}/v2/${{ inputs.image_name }}/tags/list" | jq . + echo "✅ Built and pushed:" + echo " ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.version }}" + echo " ${{ inputs.registry }}/${{ inputs.image_name }}:${{ github.sha }}" + echo "" + echo "Registry tags:" + curl -sf "https://${{ inputs.registry }}/v2/${{ inputs.image_name }}/tags/list" | jq -r '.tags[]' | sort -V | tail -10 diff --git a/.forgejo/workflows/build-devpi.yaml b/.forgejo/workflows/build-devpi.yaml index 9937bc9..89318b6 100644 --- a/.forgejo/workflows/build-devpi.yaml +++ b/.forgejo/workflows/build-devpi.yaml @@ -1,13 +1,14 @@ -name: Build devpi Image +name: Build devpi on: push: - paths: - - 'argocd/manifests/devpi/Dockerfile' - - 'argocd/manifests/devpi/start.sh' - - '.forgejo/workflows/build-devpi.yaml' - branches: [main] + tags: + - 'devpi-v*' workflow_dispatch: + inputs: + version: + description: 'Version (e.g. v1.0.0)' + required: true jobs: build: @@ -16,8 +17,21 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Extract version from tag + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + # Extract version from tag: devpi-v1.0.0 -> v1.0.0 + VERSION="${GITHUB_REF_NAME#devpi-}" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Building version: $VERSION" + - name: Build and push uses: ./.forgejo/actions/build-push-image with: context: argocd/manifests/devpi image_name: blumeops/devpi + version: ${{ steps.version.outputs.version }} diff --git a/.forgejo/workflows/build-runner.yaml b/.forgejo/workflows/build-runner.yaml index 110f43d..54162b6 100644 --- a/.forgejo/workflows/build-runner.yaml +++ b/.forgejo/workflows/build-runner.yaml @@ -1,13 +1,14 @@ -name: Build Runner Image +name: Build forgejo-runner on: push: - paths: - - 'argocd/manifests/forgejo-runner/Dockerfile' - - '.forgejo/actions/build-push-image/**' - - '.forgejo/workflows/build-runner.yaml' - branches: [main] + tags: + - 'runner-v*' workflow_dispatch: + inputs: + version: + description: 'Version (e.g. v1.0.0)' + required: true jobs: build: @@ -16,8 +17,21 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Extract version from tag + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + # Extract version from tag: runner-v1.0.0 -> v1.0.0 + VERSION="${GITHUB_REF_NAME#runner-}" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Building version: $VERSION" + - name: Build and push uses: ./.forgejo/actions/build-push-image with: context: argocd/manifests/forgejo-runner image_name: blumeops/forgejo-runner + version: ${{ steps.version.outputs.version }} diff --git a/CLAUDE.md b/CLAUDE.md index 82ed044..1399f9a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,6 +134,13 @@ When migrating a service from indri to k8s, the Tailscale hostname must be freed Use `ssh indri 'tailscale serve status --json'` to check current serve entries (the non-JSON output may be empty even when entries exist). +## Container Image Releases + +```fish +mise run container-list # Show containers and recent tags +mise run container-release runner v1.0.0 # Tag and trigger build workflow +``` + ## Third-Party Projects When a task requires cloning or using a third-party git repository (e.g., for building from source), **ask the user to mirror it on forge first**, then clone from the mirror: diff --git a/mise-tasks/container-list b/mise-tasks/container-list new file mode 100755 index 0000000..21a2ad9 --- /dev/null +++ b/mise-tasks/container-list @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +#MISE description="List available containers and their recent tags" + +set -euo pipefail + +REGISTRY="registry.tail8d86e.ts.net" +WORKFLOW_DIR=".forgejo/workflows" + +echo "Container Images" +echo "================" +echo "" + +# Find all build-*.yaml workflows +for workflow in "$WORKFLOW_DIR"/build-*.yaml; do + [[ -f "$workflow" ]] || continue + + # Extract container name from filename: build-runner.yaml -> runner + filename=$(basename "$workflow") + container="${filename#build-}" + container="${container%.yaml}" + + # Skip if not a container build workflow (check for image_name) + if ! grep -q "image_name:" "$workflow" 2>/dev/null; then + continue + fi + + # Extract image name from workflow + image=$(grep -E "^\s+image_name:" "$workflow" | head -1 | awk '{print $2}') + + echo "📦 $container" + echo " Image: $REGISTRY/$image" + echo " Workflow: $workflow" + + # Query zot for recent tags + tags=$(curl -sf "https://$REGISTRY/v2/$image/tags/list" 2>/dev/null | jq -r '.tags // [] | .[]' | grep -E '^v[0-9]' | sort -V | tail -4 || true) + + if [[ -n "$tags" ]]; then + echo " Recent tags:" + echo "$tags" | while read -r tag; do + echo " - $tag" + done + else + echo " Recent tags: (none)" + fi + echo "" +done + +echo "---" +echo "To release a new version:" +echo " mise run container-release " +echo "" +echo "Example:" +echo " mise run container-release runner v1.0.0" diff --git a/mise-tasks/container-release b/mise-tasks/container-release new file mode 100755 index 0000000..9e8802b --- /dev/null +++ b/mise-tasks/container-release @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +#MISE description="Release a container image by creating a git tag" + +set -euo pipefail + +CONTAINER="${1:-}" +VERSION="${2:-}" + +if [[ -z "$CONTAINER" || -z "$VERSION" ]]; then + echo "Usage: mise run container-release " + echo "" + echo "Run 'mise run container-list' to see available containers and recent tags." + exit 1 +fi + +# Validate version format +if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Version must be in format vX.Y.Z (e.g. v1.0.0)" + exit 1 +fi + +TAG="${CONTAINER}-${VERSION}" + +echo "Creating release tag: $TAG" +echo "" + +# Check if tag already exists +if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Error: Tag '$TAG' already exists" + echo "Existing tags for $CONTAINER:" + git tag -l "${CONTAINER}-v*" | sort -V | tail -5 + exit 1 +fi + +# Find the workflow file to determine image name +WORKFLOW_FILE=".forgejo/workflows/build-${CONTAINER}.yaml" +if [[ ! -f "$WORKFLOW_FILE" ]]; then + echo "Error: No workflow found for container '$CONTAINER'" + echo "" + echo "Run 'mise run container-list' to see available containers." + exit 1 +fi + +# Extract image name from workflow +IMAGE=$(grep -E "^\s+image_name:" "$WORKFLOW_FILE" | head -1 | awk '{print $2}') +if [[ -z "$IMAGE" ]]; then + echo "Error: Could not determine image name from $WORKFLOW_FILE" + exit 1 +fi + +echo "Container: $CONTAINER" +echo "Workflow: $WORKFLOW_FILE" +echo "Image: registry.tail8d86e.ts.net/$IMAGE:$VERSION" +echo "" + +# Confirm +read -p "Create tag and push? [y/N] " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 0 +fi + +# Create and push tag +git tag "$TAG" +git push origin "$TAG" + +echo "" +echo "✅ Tag '$TAG' created and pushed" +echo "" +echo "The workflow will now build and push:" +echo " registry.tail8d86e.ts.net/$IMAGE:$VERSION" +echo "" +echo "Monitor the build at:" +echo " https://forge.tail8d86e.ts.net/eblume/blumeops/actions"