name: CD on: push: branches: [main] tags: ["v*"] pull_request: branches: [main] env: IMAGE: __PROJECT_NAME__ jobs: guard: name: Detect unsubstituted template runs-on: self-hosted outputs: is_template: ${{ steps.detect.outputs.is_template }} steps: - uses: actions/checkout@v4 - id: detect run: | if grep -q '__MODULE_PATH__' go.mod; then echo "is_template=true" >> "$GITHUB_OUTPUT" else echo "is_template=false" >> "$GITHUB_OUTPUT" fi check: name: Lint / Test / Vet needs: guard if: needs.guard.outputs.is_template != 'true' runs-on: self-hosted steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod cache: false - name: Install toolchain run: | go version go install github.com/a-h/templ/cmd/templ@latest curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \ | sh -s -- -b "$(go env GOPATH)/bin" v2.11.4 - name: Run checks run: task check build: name: Build & Import needs: [guard, check] if: needs.guard.outputs.is_template != 'true' && github.event_name != 'pull_request' runs-on: self-hosted outputs: image-tag: ${{ steps.meta.outputs.sha-tag }} steps: - uses: actions/checkout@v4 - name: Derive image tags id: meta run: | SHA=$(git rev-parse --short HEAD) echo "sha-tag=${SHA}" >> "$GITHUB_OUTPUT" - name: Build and push to local registry run: | set -e # infra#241: buildctl, NOT buildah. Rootless buildah in gitea-runner's # hardened securityContext cannot remap the UID/GID range # newuidmap/newgidmap need on a fresh base-image pull, and the pod has # no OCI runtime at all. buildctl talks to the host's buildkitd over a # socket, so the privileged extraction happens on the host. Same # pattern as mathias/infra's segment-embedder.yml. # # Highest-leverage place to fix it: every repo generated from this # template inherits this CI, so a buildah-based template manufactures # new instances of the bug. Warm layers mask it, so a generated repo # builds fine until its first genuinely cold base-image pull. BUILDKIT_HOST="unix:///run/buildkit/buildkitd.sock" REGISTRY="localhost:5000" REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}" TAR_FILE="$(mktemp)" trap 'rm -f "$TAR_FILE"' EXIT buildctl --addr "$BUILDKIT_HOST" build \ --frontend dockerfile.v0 \ --local context=. \ --local dockerfile=. \ --opt label:org.opencontainers.image.revision="${{ github.sha }}" \ --output type=oci,dest="$TAR_FILE" # buildctl has no registry-push equivalent to `buildah push`; skopeo # copies the OCI archive up. Twice, to keep the :sha and :latest tags. skopeo copy --dest-tls-verify=false \ oci-archive:"$TAR_FILE" docker://${REF} skopeo copy --dest-tls-verify=false \ oci-archive:"$TAR_FILE" docker://${REGISTRY}/${{ env.IMAGE }}:latest echo "✓ Image pushed to ${REF} and :latest" deploy: name: Deploy via GitOps needs: [guard, build] if: needs.guard.outputs.is_template != 'true' && github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: self-hosted steps: - name: Update image tag in infra repo env: IMAGE_TAG: ${{ needs.build.outputs.image-tag }} DEPLOY_KEY: ${{ secrets.INFRA_DEPLOY_KEY }} run: | set -euo pipefail mkdir -p ~/.ssh echo "$DEPLOY_KEY" > ~/.ssh/id_infra chmod 600 ~/.ssh/id_infra ssh-keyscan -p 30022 10.0.1.20 >> ~/.ssh/known_hosts 2>/dev/null export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_infra -o IdentitiesOnly=yes" rm -rf /tmp/infra git clone -b main ssh://git@10.0.1.20:30022/mathias/infra.git /tmp/infra cd /tmp/infra DEPLOYMENT="k3s/apps/__PROJECT_NAME__/deployment.yaml" sed -i "s|image: localhost:5000/__PROJECT_NAME__:.*|image: localhost:5000/__PROJECT_NAME__:${IMAGE_TAG}|" "$DEPLOYMENT" grep -q "localhost:5000/__PROJECT_NAME__:${IMAGE_TAG}" "$DEPLOYMENT" \ || { echo "✗ image tag patch failed"; exit 1; } if git diff --quiet "$DEPLOYMENT"; then echo "ℹ image tag unchanged — skipping push" else git -c user.name="__PROJECT_NAME__ CI" \ -c user.email="ci@__PROJECT_NAME__.local" \ commit -m "chore(deploy): __PROJECT_NAME__ → ${IMAGE_TAG}" "$DEPLOYMENT" git push origin main echo "✓ pushed to infra repo" fi shred -u ~/.ssh/id_infra - name: Trigger Flux reconcile run: | kubectl -n flux-system annotate gitrepository flux-system \ reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite kubectl -n flux-system annotate kustomization apps \ reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite - name: Verify rollout run: | kubectl rollout status deployment/__PROJECT_NAME__ \ --namespace __PROJECT_NAME__ \ --timeout=120s \ || { kubectl get pods -n __PROJECT_NAME__ -o wide kubectl get events -n __PROJECT_NAME__ --sort-by='.lastTimestamp' | tail -20 exit 1 }