name: Build and Push on: push: branches: [main] # Don't rebuild on doc-only or CI-config-only changes paths-ignore: - 'README.md' - '.gitea/**' - 'deploy/**' workflow_dispatch: env: REGISTRY: registry.c5ai.ch IMAGE: pieced/pieced-portal jobs: build: # 'self-hosted' matches the label our act_runner registers with. # 'ubuntu-latest' would work too because we configure both labels in the # runner config, but self-hosted makes intent explicit. runs-on: ubuntu-latest env: DOCKER_HOST: tcp://172.17.0.1:2375 outputs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v4 - name: Determine next patch version id: version # Reads tags from the registry's OCI Distribution v2 API, filters to # strict semver (skips 'latest', 'dev', '-dirty', etc.), picks the # highest with version-sort, and bumps the patch component. If nothing # numeric exists yet (fresh registry), starts at 0.1.0. env: REG_USER: ${{ secrets.REGISTRY_USERNAME }} REG_PASS: ${{ secrets.REGISTRY_PASSWORD }} run: | set -euo pipefail tags_json=$(curl -sf -u "$REG_USER:$REG_PASS" \ "https://${REGISTRY}/v2/${IMAGE}/tags/list") highest=$(echo "$tags_json" \ | jq -r '.tags // [] | .[]' \ | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \ | sort -V \ | tail -n1 || true) if [ -z "$highest" ]; then next="0.1.0" echo "No semver tags found — starting at $next" else major=$(echo "$highest" | cut -d. -f1) minor=$(echo "$highest" | cut -d. -f2) patch=$(echo "$highest" | cut -d. -f3) next="${major}.${minor}.$((patch + 1))" echo "Highest existing: $highest → next: $next" fi echo "version=${next}" >> "$GITHUB_OUTPUT" - name: Debug DNS run: | getent hosts registry.c5ai.ch curl -v https://registry.c5ai.ch/v2/ 2>&1 | grep -E "Connected to|HTTP/|server" - name: Build and push image # Combine login + build + push in a single run block. act_runner can # use ephemeral per-step containers in some configurations, in which # case `docker login` from one step doesn't leave its cached # ~/.docker/config.json visible to the next step. Doing everything # in one shell session sidesteps that entirely. env: REG_USER: ${{ secrets.REGISTRY_USERNAME }} REG_PASS: ${{ secrets.REGISTRY_PASSWORD }} VERSION: ${{ steps.version.outputs.version }} run: | set -euo pipefail # Write docker auth config directly. This guarantees the Authorization # header is sent on every request — including PATCH during blob # upload — without depending on a credential store or `docker login` # state. Resolves a known issue where docker-in-docker drops auth # mid-push. mkdir -p /tmp/docker-config AUTH=$(printf '%s:%s' "$REG_USER" "$REG_PASS" | base64 -w 0) cat > /tmp/docker-config/config.json <> "$GITHUB_STEP_SUMMARY"