name: Deploy on: push: branches: [main] tags: ["v*"] # Required repository secrets: # 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 stack directory, e.g. /srv/librenotes # HEALTH_URL public URL to verify post-deploy, e.g. # https://ln.cloud.librete.ch/healthz # # 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 }} steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: Log in to registry uses: docker/login-action@v3 with: registry: ${{ secrets.REGISTRY }} username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_PASS }} - name: Compute tags id: tags run: | 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" else SHA7="${GITHUB_SHA::7}" echo "tags=${BASE}:main,${BASE}:${SHA7}" >> "$GITHUB_OUTPUT" echo "version=${SHA7}" >> "$GITHUB_OUTPUT" echo "image_ref=${BASE}:main" >> "$GITHUB_OUTPUT" fi - uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ steps.tags.outputs.tags }} build-args: | VERSION=${{ steps.tags.outputs.version }} BUILDTIME=${{ github.event.head_commit.timestamp }} 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 printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_deploy chmod 600 ~/.ssh/id_deploy 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 }} run: | REMOTE_CMD='cd "$DEPLOY_PATH" || exit 1 git pull --ff-only # Always pin LIBRENOTES_IMAGE so first deploy works without manual # .env priming and so rollback only ever needs an .env edit. perl -i -pe "s|^LIBRENOTES_IMAGE=.*|LIBRENOTES_IMAGE=$IMAGE_REF|" .env grep -q "^LIBRENOTES_IMAGE=" .env || echo "LIBRENOTES_IMAGE=$IMAGE_REF" >> .env 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' bash -s" <<< "$REMOTE_CMD" - name: Verify health env: HEALTH_URL: ${{ secrets.HEALTH_URL }} run: | # 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" exit 0 fi sleep 5 done echo "deploy verification failed" exit 1