From 51fb2fe636f5147c1521819e3a86dad73fad7b6e Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Wed, 29 Apr 2026 02:00:23 +0200 Subject: [PATCH] ci(deploy): fix registry path, compose refs, Gitea Actions compat - Image base is now ${REGISTRY}/public/librenotes (matches Gitea owner/repo). - Remote step writes LIBRENOTES_IMAGE on tag pushes via perl, then pulls and restarts using the new compose.yaml + compose.netcup.yaml stack files. - Both jobs run inside catthehacker/ubuntu:runner-latest; the default node:20-bookworm runner image lacks make + docker. The build job bind-mounts /var/run/docker.sock for build-push-action; the runner config must whitelist that path under valid_volumes. --- .gitea/workflows/ci.yml | 4 ++ .gitea/workflows/deploy.yml | 76 +++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index a85a9bc..a3f0894 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -9,6 +9,10 @@ on: jobs: ci: runs-on: ubuntu-latest + # Pin image: needs make (not in node:20-bookworm). runner-latest + # bundles make, git, curl + node for setup-go. + container: + image: catthehacker/ubuntu:runner-latest timeout-minutes: 5 steps: - name: Checkout diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index c2655de..a5c7793 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -6,24 +6,39 @@ on: tags: ["v*"] # Required repository secrets: -# REGISTRY registry hostname, e.g. registry.librete.ch -# REGISTRY_USER robot account -# REGISTRY_PASS robot token -# DEPLOY_HOST deployment SSH target, e.g. root@librenot.es +# REGISTRY registry hostname, e.g. git.librete.ch +# REGISTRY_USER robot account or PAT username +# REGISTRY_PASS robot/PAT token with package:write +# DEPLOY_HOST deployment SSH target, e.g. root@netcup # DEPLOY_KEY private SSH key (PEM, no passphrase) -# DEPLOY_PATH remote directory containing the compose stack +# DEPLOY_PATH remote stack directory, e.g. /srv/librenotes # HEALTH_URL public URL to verify post-deploy, e.g. -# https://librenot.es/healthz +# https://ln.cloud.librete.ch/healthz # -# Tag pushes deploy the tag (vX.Y.Z); main-branch pushes deploy -# the rolling :main image. Set image to immutable tag so rollback -# is just `docker compose -f ... up -d` with the previous tag. +# Required repository variable: +# DEPLOY_ENABLED set to "true" to enable the workflow +# +# Image path: ${REGISTRY}/public/librenotes (matches Gitea owner/repo). +# Main pushes publish :main and :. Tag pushes publish : and +# :latest, then pin LIBRENOTES_IMAGE on the host to the immutable tag +# so rollback is just `perl -i -pe 's|^LIBRENOTES_IMAGE=.*|...=...:vX.Y.Z|' .env` +# followed by `docker compose ... up -d`. jobs: build: runs-on: ubuntu-latest + # Gitea Actions: pin image so docker CLI is present and mount the + # host docker socket so build-push-action can push to the registry. + # The runner declares /var/run/docker.sock in valid_volumes. + container: + image: catthehacker/ubuntu:runner-latest + volumes: + - /var/run/docker.sock:/var/run/docker.sock timeout-minutes: 15 if: ${{ vars.DEPLOY_ENABLED == 'true' }} + outputs: + image_ref: ${{ steps.tags.outputs.image_ref }} + is_tag: ${{ steps.tags.outputs.is_tag }} steps: - uses: actions/checkout@v4 @@ -39,14 +54,19 @@ jobs: - name: Compute tags id: tags run: | - BASE="${{ secrets.REGISTRY }}/librenotes" + BASE="${{ secrets.REGISTRY }}/public/librenotes" if [[ "${GITHUB_REF}" == refs/tags/* ]]; then TAG="${GITHUB_REF##refs/tags/}" echo "tags=${BASE}:${TAG},${BASE}:latest" >> "$GITHUB_OUTPUT" echo "version=${TAG}" >> "$GITHUB_OUTPUT" + echo "image_ref=${BASE}:${TAG}" >> "$GITHUB_OUTPUT" + echo "is_tag=true" >> "$GITHUB_OUTPUT" else - echo "tags=${BASE}:main,${BASE}:${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" - echo "version=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + SHA7="${GITHUB_SHA::7}" + echo "tags=${BASE}:main,${BASE}:${SHA7}" >> "$GITHUB_OUTPUT" + echo "version=${SHA7}" >> "$GITHUB_OUTPUT" + echo "image_ref=${BASE}:main" >> "$GITHUB_OUTPUT" + echo "is_tag=false" >> "$GITHUB_OUTPUT" fi - uses: docker/build-push-action@v6 @@ -60,34 +80,48 @@ jobs: deploy: runs-on: ubuntu-latest + # Same image as build — bundles ssh, perl, curl. No docker needed. + container: + image: catthehacker/ubuntu:runner-latest needs: build timeout-minutes: 10 if: ${{ vars.DEPLOY_ENABLED == 'true' }} steps: - name: Configure SSH + env: + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} run: | mkdir -p ~/.ssh - echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_deploy + printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_deploy chmod 600 ~/.ssh/id_deploy - ssh-keyscan -H "${{ secrets.DEPLOY_HOST#*@ }}" >> ~/.ssh/known_hosts || true + ssh-keyscan -H "${DEPLOY_HOST#*@}" >> ~/.ssh/known_hosts 2>/dev/null || true - name: Pull and restart on deploy host env: DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} + IMAGE_REF: ${{ needs.build.outputs.image_ref }} + IS_TAG: ${{ needs.build.outputs.is_tag }} run: | - ssh -i ~/.ssh/id_deploy "$DEPLOY_HOST" \ - "cd $DEPLOY_PATH && \ - docker compose -f docker-compose.yml -f docker-compose.prod.yml pull && \ - docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --remove-orphans" + REMOTE_CMD='cd "$DEPLOY_PATH" || exit 1 + if [ "$IS_TAG" = "true" ]; then + perl -i -pe "s|^LIBRENOTES_IMAGE=.*|LIBRENOTES_IMAGE=$IMAGE_REF|" .env + grep -q "^LIBRENOTES_IMAGE=" .env || echo "LIBRENOTES_IMAGE=$IMAGE_REF" >> .env + fi + docker compose -f compose.yaml -f compose.netcup.yaml pull + docker compose -f compose.yaml -f compose.netcup.yaml up -d --remove-orphans' + ssh -i ~/.ssh/id_deploy \ + -o StrictHostKeyChecking=accept-new \ + "$DEPLOY_HOST" \ + "DEPLOY_PATH='$DEPLOY_PATH' IMAGE_REF='$IMAGE_REF' IS_TAG='$IS_TAG' bash -s" <<< "$REMOTE_CMD" - name: Verify health env: HEALTH_URL: ${{ secrets.HEALTH_URL }} run: | - # Give the new container ~30s to come up, then poll for - # a 200 from /healthz. Failure aborts the workflow which - # is the alert. + # Give the new container ~60s to come up, then poll for + # 200 from /healthz. Failure aborts the workflow. for i in $(seq 1 12); do if curl -fsS "$HEALTH_URL" >/dev/null; then echo "deploy verified"