diff --git a/.gitea/workflows/build-backend.yml b/.gitea/workflows/build-backend.yml new file mode 100644 index 0000000..7851896 --- /dev/null +++ b/.gitea/workflows/build-backend.yml @@ -0,0 +1,105 @@ +name: build-backend + +# Builds + pushes the backend image to justin/provenance-backend's package area +# on Gitea on every merge to main. Servers pull from git.jpaul.io. +# +# Push goes to the LAN registry endpoint 192.168.0.2:1234 (plain HTTP) to bypass +# Cloudflare's request-body limit; pulls use the public git.jpaul.io FQDN. Same +# Gitea registry either way. Mirrors the drawbar setup. +# +# Tag scheme: test-main | test-sha- | | latest (v* tags) + +on: + workflow_dispatch: + push: + branches: [main] + tags: ['v*'] + paths: + - 'backend/**' + - '.gitea/workflows/build-backend.yml' + +concurrency: + group: build-backend-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from pyproject.toml + id: ver + run: | + v=$(grep -oP '^version = "\K[^"]+' backend/pyproject.toml | head -1) + if [ -z "$v" ]; then echo "could not parse version from backend/pyproject.toml"; exit 1; fi + echo "semver=$v" >> "$GITHUB_OUTPUT" + echo "backend semver: $v" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + # LAN registry serves plain HTTP on :1234 (git.jpaul.io is the only TLS + # endpoint, via Cloudflare). Treat the LAN endpoint as insecure so + # buildkit doesn't try to upgrade the push to HTTPS. + config-inline: | + [registry."192.168.0.2:1234"] + http = true + insecure = true + + - name: Configure registry credentials for buildx + env: + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + REGISTRY_USER: ${{ github.actor }} + run: | + mkdir -p ~/.docker + AUTH=$(printf '%s:%s' "$REGISTRY_USER" "$REGISTRY_TOKEN" | base64 -w0) + cat > ~/.docker/config.json < provenance: HTTP $code" + case "$code" in + 201) echo "OK — newly linked" ;; + 400|409) echo "OK — already linked" ;; + *) cat /tmp/link.out; exit 1 ;; + esac diff --git a/.gitea/workflows/build-frontend.yml b/.gitea/workflows/build-frontend.yml new file mode 100644 index 0000000..99289d8 --- /dev/null +++ b/.gitea/workflows/build-frontend.yml @@ -0,0 +1,102 @@ +name: build-frontend + +# Builds + pushes the Next.js image to justin/provenance-frontend's package area +# on Gitea on every merge to main. Servers pull from git.jpaul.io. +# +# Push -> LAN registry 192.168.0.2:1234 (plain HTTP); pull -> git.jpaul.io. +# Mirrors the drawbar setup; see build-backend.yml for the rationale. +# +# Tag scheme: test-main | test-sha- | | latest (v* tags) + +on: + workflow_dispatch: + push: + branches: [main] + tags: ['v*'] + paths: + - 'frontend/**' + - '.gitea/workflows/build-frontend.yml' + +concurrency: + group: build-frontend-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from package.json + id: ver + run: | + v=$(grep -oP '"version"\s*:\s*"\K[^"]+' frontend/package.json | head -1) + if [ -z "$v" ]; then echo "could not parse version from frontend/package.json"; exit 1; fi + echo "semver=$v" >> "$GITHUB_OUTPUT" + echo "frontend version: $v" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + # See build-backend.yml for why these flags are needed. + config-inline: | + [registry."192.168.0.2:1234"] + http = true + insecure = true + + - name: Configure registry credentials for buildx + env: + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + REGISTRY_USER: ${{ github.actor }} + run: | + mkdir -p ~/.docker + AUTH=$(printf '%s:%s' "$REGISTRY_USER" "$REGISTRY_TOKEN" | base64 -w0) + cat > ~/.docker/config.json < provenance: HTTP $code" + case "$code" in + 201) echo "OK — newly linked" ;; + 400|409) echo "OK — already linked" ;; + *) cat /tmp/link.out; exit 1 ;; + esac diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml deleted file mode 100644 index b582c49..0000000 --- a/.gitea/workflows/build.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: build-images - -# Gitea Actions build container images and push to the Gitea registry on -# git.jpaul.io. Servers pull to deploy — no build on the host. -# -# Requires repo/org secrets: REGISTRY_USERNAME, REGISTRY_PASSWORD (a token with -# package:write). Adjust the runner label to one your Gitea runner advertises. - -on: - push: - branches: [main] - tags: ["v*"] - -env: - REGISTRY: git.jpaul.io - IMAGE_BASE: git.jpaul.io/${{ github.repository }} - -jobs: - backend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Gitea registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: Build and push backend image - uses: docker/build-push-action@v6 - with: - context: ./backend - push: true - tags: | - ${{ env.IMAGE_BASE }}/backend:latest - ${{ env.IMAGE_BASE }}/backend:${{ github.sha }} - - frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Gitea registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: Build and push frontend image - uses: docker/build-push-action@v6 - with: - context: ./frontend - push: true - tags: | - ${{ env.IMAGE_BASE }}/frontend:latest - ${{ env.IMAGE_BASE }}/frontend:${{ github.sha }} diff --git a/CLAUDE.md b/CLAUDE.md index fc865c1..56858d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,7 @@ These are product invariants, not preferences. Do not violate them, and flag any - **Object storage:** S3-compatible (MinIO for self-host). - **Edge:** Caddy reverse proxy; optional Cloudflare Tunnel (preferred ingress, never required). - **Email:** operator-configured SMTP. -- **CI/CD:** Gitea Actions on `git.jpaul.io` build container images to the Gitea registry; servers pull to deploy. +- **CI/CD:** Gitea Actions build per-component images. **Push** to the LAN registry `192.168.0.2:1234` (plain HTTP, bypasses Cloudflare's body limit); **pull** via the public `git.jpaul.io` FQDN. Servers pull to deploy — no host build. Mirrors the drawbar setup; see [[gitea-lan-push-fqdn-pull]]. Pick libraries consistent with this stack. If you introduce a significant dependency or a new service, note it in ARCHITECTURE.md in the same change. diff --git a/deploy/.env.example b/deploy/.env.example index 9e59dac..321e1db 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -4,6 +4,10 @@ # --- Core --- APP_ENV=development +# --- Images (pulled from git.jpaul.io; CI pushes to the LAN registry) --- +# test-main = current main build; or pin a semver / test-sha- for rollback. +IMAGE_TAG=test-main + # --- Database (Postgres) --- POSTGRES_USER=provenance POSTGRES_PASSWORD=change-me diff --git a/deploy/docker-compose.dev.yml b/deploy/docker-compose.dev.yml new file mode 100644 index 0000000..c459e4e --- /dev/null +++ b/deploy/docker-compose.dev.yml @@ -0,0 +1,18 @@ +name: provenance + +# Dev override: build the backend/frontend images locally instead of pulling +# them from the registry. Layer it on top of the base compose: +# docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build +# +# Use this before CI has published images, or to test local changes. + +services: + backend: + build: + context: ../backend + dockerfile: Dockerfile + + frontend: + build: + context: ../frontend + dockerfile: Dockerfile diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index b484163..7022340 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -2,6 +2,11 @@ name: provenance # One stack stands up the whole system. Configuration is entirely env-driven # (see .env.example). Run from this directory: `docker compose up -d`. +# +# backend/frontend are PULLED from the public registry (git.jpaul.io); CI pushes +# them to the LAN endpoint (192.168.0.2:1234). For local building instead of +# pulling, layer the dev override: +# docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build services: postgres: @@ -36,9 +41,7 @@ services: restart: unless-stopped backend: - build: - context: ../backend - dockerfile: Dockerfile + image: git.jpaul.io/justin/provenance-backend:${IMAGE_TAG:-test-main} environment: APP_ENV: ${APP_ENV:-development} DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://provenance:provenance@postgres:5432/provenance} @@ -58,9 +61,7 @@ services: restart: unless-stopped frontend: - build: - context: ../frontend - dockerfile: Dockerfile + image: git.jpaul.io/justin/provenance-frontend:${IMAGE_TAG:-test-main} environment: NODE_ENV: production depends_on: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 56d6f6b..81ee560 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -170,8 +170,10 @@ Jobs are idempotent and retryable; an external failure degrades gracefully rathe ## 12. Deployment & CI/CD -- **Images** are built by **Gitea Actions** on `git.jpaul.io` and pushed to the **Gitea container registry**. -- Servers **pull** new images to deploy — no build on the host. +- **Images** are built by **Gitea Actions** (`runs-on: docker`) and pushed to the **Gitea container registry**, one package per component (`provenance-backend`, `provenance-frontend`) linked to the repo. +- **Split push/pull endpoints** (mirrors the drawbar setup): CI **pushes** to the LAN registry endpoint `192.168.0.2:1234` over plain HTTP (buildx configured `insecure`/`http`) to bypass the Cloudflare request-body limit; servers **pull** from the public `git.jpaul.io` FQDN (TLS via Cloudflare). Same Gitea registry, two front doors. Auth uses the `REGISTRY_TOKEN` Actions secret. +- Tag scheme: `test-main` (current main), `test-sha-` (rollback pins), the component version, and `latest` on `v*` tags. +- Servers **pull** new images to deploy — no build on the host. The deploy compose references `git.jpaul.io/justin/provenance-{backend,frontend}:${IMAGE_TAG:-test-main}`; `docker-compose.dev.yml` is a local-build override. - **Caddy** terminates TLS and reverse-proxies frontend + backend. **Cloudflare Tunnel** is the preferred ingress (no open inbound ports) but is never required; a plain Caddy-on-a-public-host deployment is equally supported. - **Configuration** is entirely environment-driven (twelve-factor). One `.env` plus the compose file is enough to stand up a deployment. - **Migrations** run on backend start (or via an explicit job) so an image pull + restart is a complete upgrade.