From 3045ac0726feb8f50a4376fa3e2302a5fe0d24ae Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Wed, 29 Apr 2026 17:28:18 +0200 Subject: [PATCH] ci(deploy): Gitea-driven build/push/deploy for netcup (#14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the GitHub Pages workflow with a Gitea Actions pipeline that publishes the cc image to `git.librete.ch/libretech/code-crispies` and ssh-deploys it to `/srv/cc` on netcup. ## Changes - `.gitea/workflows/ci.yml` — npm test + sanity build (with placeholder VITE_*) on every push / PR. - `.gitea/workflows/deploy.yml` — single-job build → push → ssh-deploy → /healthz check, gated on `vars.DEPLOY_ENABLED`. Tag push → `:vX.Y.Z` + `:latest`; main push → `:main` + `:sha-<7>`. - `compose.yaml` — adds `image: ${CC_IMAGE:-cc:local}` so production pulls the published tag while dev still builds locally. - Both workflows pin `git.librete.ch/libretech/runner-image:v1` (no third-party Docker Hub images, no `--user root`). ## Operator follow-up (before merging into hot deploy) - Set repo secrets at `https://git.librete.ch/libretech/code-crispies/settings/actions/secrets`: - `REGISTRY=git.librete.ch` - `REGISTRY_USER=libretech` (user-namespace packages — bot can't push) - `REGISTRY_PASS=` (same PAT used for `libretech/runner-image`) - `DEPLOY_HOST=root@cloud.librete.ch` - `DEPLOY_KEY=` (same key as librenotes deploy) - `DEPLOY_PATH=/srv/cc` - `HEALTH_URL=https://cc.cloud.librete.ch/` - `VITE_SUPABASE_URL=https://yretixuyfuiresnrjkbs.supabase.co` - `VITE_SUPABASE_ANON_KEY=` (public-by-design supabase key) - Set repo variable `DEPLOY_ENABLED=true` once the secrets are in. - Add `CC_IMAGE=git.librete.ch/libretech/code-crispies:main` to `/srv/cc/.env` on netcup (no rebuild on host). ## Verification - `yq -e .` parses both workflow YAMLs. - `docker compose config` resolves cleanly in both build mode (no `CC_IMAGE`) and image-pull mode (`CC_IMAGE=test:1`). - `npm test` is the same script the previous github-pages workflow ran. Reviewed-on: https://git.librete.ch/libretech/code-crispies/pulls/14 Co-authored-by: Michael Czechowski Co-committed-by: Michael Czechowski --- .gitea/workflows/ci.yml | 37 +++++++++++++++ .gitea/workflows/deploy.yml | 89 +++++++++++++++++++++++++++++++++++++ compose.yaml | 10 ++++- 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/deploy.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..2023243 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + ci: + runs-on: ubuntu-latest + container: + image: git.librete.ch/libretech/runner-image:v1 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - name: Cache npm + uses: actions/cache@v4 + with: + path: ~/.npm + key: npm-${{ hashFiles('package-lock.json') }} + restore-keys: npm- + + - name: Install + run: npm ci --no-audit --no-fund + + - name: Test + run: npm test + + - name: Build (sanity) + env: + # Build needs VITE_* injected at compile time. Use placeholders + # for CI sanity build — real values are passed at deploy time. + VITE_SUPABASE_URL: https://example.invalid + VITE_SUPABASE_ANON_KEY: ci-placeholder + run: npm run build diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..fa56dea --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,89 @@ +name: Deploy + +on: + push: + branches: [main] + tags: ["v*"] + +# Required repository secrets: +# REGISTRY git.librete.ch +# REGISTRY_USER libretech (user-namespace packages — bot can't push) +# REGISTRY_PASS libretech-user PAT scoped write:package +# DEPLOY_HOST user@your-deploy-host +# DEPLOY_KEY passphrase-less private key on netcup root authorized_keys +# DEPLOY_PATH /srv/cc +# HEALTH_URL https://cc.cloud.librete.ch/ +# VITE_SUPABASE_URL public; baked at build time +# VITE_SUPABASE_ANON_KEY public-by-design supabase anon key; baked at build time +# +# Required repository variable: +# DEPLOY_ENABLED "true" to enable +# +# Image: git.librete.ch/public/code-crispies +# main pushes → :main + :sha- +# tag pushes → : + :latest + +jobs: + deploy: + runs-on: ubuntu-latest + container: + image: git.librete.ch/libretech/runner-image:v1 + timeout-minutes: 20 + if: ${{ vars.DEPLOY_ENABLED == 'true' }} + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASS }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.REGISTRY }}/public/code-crispies + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,format=short + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }} + VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }} + + - name: Deploy to host + env: + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} + DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} + HEALTH_URL: ${{ secrets.HEALTH_URL }} + run: | + mkdir -p ~/.ssh && chmod 700 ~/.ssh + printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_deploy + chmod 600 ~/.ssh/id_deploy + + ssh -i ~/.ssh/id_deploy \ + -o StrictHostKeyChecking=accept-new \ + "$DEPLOY_HOST" \ + "set -e + cd '$DEPLOY_PATH' + git pull --ff-only + docker compose pull + docker compose up -d --remove-orphans" + + # Wait up to 60s for the cc vhost to return a 200. + for i in $(seq 1 12); do + curl -fsS "$HEALTH_URL" >/dev/null && exit 0 + sleep 5 + done + echo "deploy health check failed"; exit 1 diff --git a/compose.yaml b/compose.yaml index 1301491..4c429fc 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,5 +1,12 @@ name: cc +# Default behaviour: +# - On dev (no CC_IMAGE in env): build from local context. +# - On netcup (CC_IMAGE pinned in /srv/cc/.env to the published Gitea +# package): pull the image and skip the build context entirely. +# The Gitea deploy workflow pushes git.librete.ch/public/code-crispies:main +# on every main push. + services: cc: build: @@ -7,7 +14,8 @@ services: args: VITE_SUPABASE_URL: ${VITE_SUPABASE_URL} VITE_SUPABASE_ANON_KEY: ${VITE_SUPABASE_ANON_KEY} - image: cc:local + image: ${CC_IMAGE:-cc:local} + pull_policy: ${CC_PULL_POLICY:-missing} restart: always networks: - edge