name: Release on: push: tags: - "v*" # e.g. v1.0.0, v1.2.3 concurrency: group: ${{ gitea.workflow }}-${{ gitea.ref }} cancel-in-progress: true jobs: # ── backend: vet & test ─────────────────────────────────────────────────────── test-backend: name: Test backend runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: backend/go.mod cache-dependency-path: backend/go.sum - name: go vet working-directory: backend run: go vet ./... - name: Run tests working-directory: backend run: go test -short -race -count=1 -timeout=60s ./... # ── ui: type-check & build ──────────────────────────────────────────────────── check-ui: name: Check ui runs-on: ubuntu-latest defaults: run: working-directory: ui steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "22" cache: npm cache-dependency-path: ui/package-lock.json - name: Install dependencies run: npm ci - name: Type check run: npm run check - name: Build run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: ui-build path: ui/build retention-days: 1 # ── docker: build + push all images via docker bake ────────────────────────── docker: name: Docker runs-on: ubuntu-latest needs: [test-backend, check-ui] steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_TOKEN }} - name: Compute version tags id: ver run: | V="${{ gitea.ref_name }}" VER="${V#v}" echo "version=$VER" >> "$GITHUB_OUTPUT" echo "major_minor=$(echo "$VER" | cut -d. -f1-2)" >> "$GITHUB_OUTPUT" - name: Download ui build artifacts uses: actions/download-artifact@v3 with: name: ui-build path: ui/build - name: Allow build/ into Docker context (override .dockerignore) run: | grep -v '^build$' ui/.dockerignore > ui/.dockerignore.tmp mv ui/.dockerignore.tmp ui/.dockerignore - name: Build and push all images uses: docker/bake-action@v6 with: files: docker-bake.hcl set: | *.output=type=image,push=true env: VERSION: ${{ steps.ver.outputs.version }} MAJOR_MINOR: ${{ steps.ver.outputs.major_minor }} COMMIT: ${{ gitea.sha }} BUILD_TIME: ${{ gitea.event.head_commit.timestamp }} # ── deploy: sync docker-compose.yml + restart prod ─────────────────────────── # Runs after all images are pushed to Docker Hub. # Copies the compose file from the tagged commit to the server, pulls the new # images, and restarts only the services whose image or config changed. # --remove-orphans cleans up containers no longer defined in the compose file # (e.g. the now-removed pb-init container). # # Required Gitea secrets: # PROD_HOST — prod server IP or hostname # PROD_USER — SSH login user (typically root) # PROD_SSH_KEY — private key whose public half is in authorized_keys # PROD_SSH_KNOWN_HOSTS — output of: ssh-keyscan -H deploy: name: Deploy to prod runs-on: ubuntu-latest needs: [docker] steps: - uses: actions/checkout@v4 - name: Install SSH key run: | mkdir -p ~/.ssh printf '%s\n' "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key printf '%s\n' "${{ secrets.PROD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts - name: Copy docker-compose.yml to prod run: | scp -i ~/.ssh/deploy_key \ docker-compose.yml \ "${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}:/opt/libnovel/docker-compose.yml" - name: Pull new images and restart changed services run: | ssh -i ~/.ssh/deploy_key \ "${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}" \ 'set -euo pipefail cd /opt/libnovel doppler run -- docker compose pull backend runner ui caddy pocketbase doppler run -- docker compose up -d --remove-orphans' # ── Gitea release ───────────────────────────────────────────────────────────── release: name: Gitea Release runs-on: ubuntu-latest needs: [docker] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Extract release notes from tag commit id: notes run: | set -euo pipefail # Subject line (first line of commit message) → release title SUBJECT=$(git log -1 --format="%s" "${{ gitea.sha }}") # Body (everything after the blank line) → release body BODY=$(git log -1 --format="%b" "${{ gitea.sha }}" | sed '/^Co-Authored-By:/d' | sed '/^[[:space:]]*$/{ N; /^\n$/d }' | sed 's/^[[:space:]]*$//' | awk 'NF || !p; {p = !NF}') echo "title=${SUBJECT}" >> "$GITHUB_OUTPUT" # Use a heredoc delimiter to safely handle multi-line body { echo "body<> "$GITHUB_OUTPUT" - name: Create release uses: https://gitea.com/actions/gitea-release-action@v1 with: token: ${{ secrets.GITEA_TOKEN }} title: ${{ steps.notes.outputs.title }} body: ${{ steps.notes.outputs.body }}