From f28cd5220a4dd42e02873762c8c896e938c93733 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Wed, 29 Apr 2026 17:26:05 +0200 Subject: [PATCH] ci(deploy): build + push image on Gitea, ssh-deploy to netcup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds .gitea/workflows/{ci,deploy}.yml. Both jobs run inside the custom git.librete.ch/libretech/runner-image:v1 image. CI on every push runs npm test + a sanity build with placeholder VITE_*. The deploy workflow (gated on vars.DEPLOY_ENABLED) builds the multi-stage Vite + nginx Dockerfile, pushes to git.librete.ch/libretech/code-crispies (main → :main + :sha-; tag → : + :latest), and ssh-deploys the netcup stack with 'docker compose pull && up -d'. compose.yaml gains an opt-in image-pull mode: CC_IMAGE pins the published tag in production (set in /srv/cc/.env), while the dev shell falls through to a local build when CC_IMAGE is unset. Replaces the legacy github-pages workflow at .github/workflows/main.yml which targeted GitHub Pages, not the netcup deployment. --- .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..9fc2436 --- /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 root@cloud.librete.ch +# 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/libretech/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 }}/libretech/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..1266d07 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/libretech/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 -- 2.36.6