name: Build and Deploy Spoon on: push: branches: [main] paths: - 'apps/**' - 'packages/**' - 'tools/**' - 'scripts/**' - 'docker/**' - '.gitea/workflows/build-next.yml' - '.infisical.json' - 'package.json' - 'bun.lock' - 'turbo.json' # One production deploy at a time; a newer push waits rather than racing. concurrency: group: build-spoon-production cancel-in-progress: false # Production env comes from the Infisical `prod` environment. Each job logs in # as the read-only `spoon-ci` machine identity and writes the env to a temp # file (scripts/ci-export-env.mjs), so no prod values live in Gitea. jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.10 - run: ./scripts/ci-install - name: Lint, typecheck, and test env: INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_CLIENT_ID }} INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} run: | env_file="$(mktemp)" trap 'rm -f "$env_file"' EXIT node scripts/ci-export-env.mjs prod > "$env_file" bunx dotenv -e "$env_file" -- env NODE_ENV=test SKIP_E2E=1 bun run ci:check build-images: needs: [quality] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Log in to container registry run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.gbrown.org -u "${{ secrets.REGISTRY_USER }}" --password-stdin - uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.10 - run: ./scripts/ci-install - name: Build Next image env: INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_CLIENT_ID }} INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} run: | env_file="$(mktemp)" trap 'rm -f "$env_file"' EXIT node scripts/ci-export-env.mjs prod > "$env_file" CI_ENV_FILE="$env_file" ./scripts/build-next-app production - name: Build agent images env: SPOON_BUILD_SHA: ${{ gitea.sha }} run: SPOON_AGENT_CONTAINER_RUNTIME=docker ./scripts/build-agent-images - name: Tag and push images run: | docker tag spoon-next:latest git.gbrown.org/gib/spoon-next:${{ gitea.sha }} docker tag spoon-next:latest git.gbrown.org/gib/spoon-next:latest docker push git.gbrown.org/gib/spoon-next:${{ gitea.sha }} docker push git.gbrown.org/gib/spoon-next:latest docker tag spoon-agent-worker:latest git.gbrown.org/gib/spoon-agent-worker:${{ gitea.sha }} docker tag spoon-agent-worker:latest git.gbrown.org/gib/spoon-agent-worker:latest docker push git.gbrown.org/gib/spoon-agent-worker:${{ gitea.sha }} docker push git.gbrown.org/gib/spoon-agent-worker:latest docker tag spoon-agent-job:latest git.gbrown.org/gib/spoon-agent-job:${{ gitea.sha }} docker tag spoon-agent-job:latest git.gbrown.org/gib/spoon-agent-job:latest docker push git.gbrown.org/gib/spoon-agent-job:${{ gitea.sha }} docker push git.gbrown.org/gib/spoon-agent-job:latest # Asks the VPS to pull the new images and restart podman-spoon.service # through the forced-command deploy key (~/Server/AGENTS.md ยง15). A # failure only warns: the images are pushed and the midnight # podman-update.timer deploys them anyway. The key comes in through env, # not ${{ secrets.* }} in the script: the runner prints the script, and # Gitea does not mask a multi-line secret split across script lines. - name: Trigger immediate deploy env: DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} run: | install -m600 /dev/null "$HOME/.deploy_key" printf '%s\n' "$DEPLOY_SSH_KEY" > "$HOME/.deploy_key" ssh -i "$HOME/.deploy_key" \ -o BatchMode=yes \ -o StrictHostKeyChecking=accept-new \ -o ConnectTimeout=15 \ gib@192.168.2.2 spoon \ || echo "::warning::deploy trigger failed; nightly sweep will deploy instead" rm -f "$HOME/.deploy_key"