# Build all OSINT images, publish to the Forgejo container registry, then # redeploy on the Pi runner (docker.sock mounted). # # Public pull host: forgejo.siriusdevops.com (NOT ghcr.io) # CI push host: 127.0.0.1:3000 — Cloudflare 413s layers ≳100MB on the public # hostname, even from the Pi (hairpins out through the tunnel). # Images (public names): # forgejo.siriusdevops.com/sirius/osint-dashboard # forgejo.siriusdevops.com/sirius/osint-dashboard-pg # forgejo.siriusdevops.com/sirius/osint-news-scraper # forgejo.siriusdevops.com/sirius/osint-news-summarizer # # Optional repo variable FORGEJO_REGISTRY overrides the *public* pull host. # Deploy still uses the local docker socket on the runner host (rpi). name: build-and-deploy on: push: branches: [main, master] workflow_dispatch: env: PUBLIC_REGISTRY: ${{ vars.FORGEJO_REGISTRY || 'forgejo.siriusdevops.com' }} PUSH_REGISTRY: 127.0.0.1:3000 OWNER: sirius # Keep compose project/volumes stable on the Pi COMPOSE_PROJECT_NAME: osint-dashboard jobs: build-push-deploy: runs-on: docker permissions: contents: read packages: write steps: - name: Checkout uses: https://code.forgejo.org/actions/checkout@v4 - name: Image refs id: img run: | set -euo pipefail PUSH="${PUSH_REGISTRY}" PUB="${PUBLIC_REGISTRY}" OWN="${OWNER}" SHA="${GITHUB_SHA::12}" { echo "reg=$PUSH" echo "pub=$PUB" echo "sha=$SHA" echo "app=$PUSH/$OWN/osint-dashboard" echo "pg=$PUSH/$OWN/osint-dashboard-pg" echo "scraper=$PUSH/$OWN/osint-news-scraper" echo "summarizer=$PUSH/$OWN/osint-news-summarizer" } >> "$GITHUB_OUTPUT" echo "Push registry: $PUSH" echo "Public pull: $PUB" echo "SHA tag: $SHA" - name: Login to Forgejo registry run: | set -euo pipefail # GITHUB_TOKEN login "succeeds" but blob uploads 401 (Forgejo packages # reject the Actions token). Use a user PAT with write:package. if [ -z "${{ secrets.FORGEJO_TOKEN }}" ]; then echo "Missing repo secret FORGEJO_TOKEN (user PAT, not GITHUB_TOKEN)" exit 1 fi echo "${{ secrets.FORGEJO_TOKEN }}" | docker login "${{ steps.img.outputs.reg }}" \ -u sirius --password-stdin - name: Build application image (api / ingester / cameras) run: | set -ex APP="${{ steps.img.outputs.app }}" SHA="${{ steps.img.outputs.sha }}" docker build -f Dockerfile -t "${APP}:latest" -t "${APP}:${SHA}" \ -t "localhost/osint-dashboard:latest" . docker push "${APP}:latest" docker push "${APP}:${SHA}" - name: Build news-scraper image run: | set -ex IMG="${{ steps.img.outputs.scraper }}" SHA="${{ steps.img.outputs.sha }}" docker build -f news/scraper/Dockerfile -t "${IMG}:latest" -t "${IMG}:${SHA}" \ -t "localhost/osint-news-scraper:latest" news/scraper docker push "${IMG}:latest" docker push "${IMG}:${SHA}" - name: Build news-summarizer image run: | set -ex IMG="${{ steps.img.outputs.summarizer }}" SHA="${{ steps.img.outputs.sha }}" docker build -f news/summerizer/Dockerfile -t "${IMG}:latest" -t "${IMG}:${SHA}" \ -t "localhost/osint-news-summarizer:latest" news/summerizer docker push "${IMG}:latest" docker push "${IMG}:${SHA}" - name: Build / refresh Timescale+PostGIS image run: | set -ex PG="${{ steps.img.outputs.pg }}" SHA="${{ steps.img.outputs.sha }}" # Prefer rebuild so registry always has a current pg image. If packagecloud # is unreachable, fall back to whatever local image already exists. if docker build -f Dockerfile.pg -t "${PG}:latest" -t "${PG}:${SHA}" \ -t "localhost/osint-dashboard-pg:latest" .; then docker push "${PG}:latest" docker push "${PG}:${SHA}" elif docker image inspect "localhost/osint-dashboard-pg:latest" >/dev/null 2>&1; then echo "WARN: Dockerfile.pg build failed; retagging existing local pg image into registry" docker tag "localhost/osint-dashboard-pg:latest" "${PG}:latest" docker tag "localhost/osint-dashboard-pg:latest" "${PG}:${SHA}" docker push "${PG}:latest" docker push "${PG}:${SHA}" else echo "ERROR: cannot build or find osint-dashboard-pg image" exit 1 fi - name: Deploy on runner host (compose) run: | set -ex cd "${GITHUB_WORKSPACE}" # Pull from Forgejo registry into local tags compose expects, then up. # Compose file still uses localhost/* for stable local names; we mirror # registry tags so a cold host can recover via docker pull. REG="${{ steps.img.outputs.reg }}" PUB="${{ steps.img.outputs.pub }}" OWN="${{ env.OWNER }}" for name in osint-dashboard osint-dashboard-pg osint-news-scraper osint-news-summarizer; do docker pull "${REG}/${OWN}/${name}:latest" || true docker tag "${REG}/${OWN}/${name}:latest" "localhost/${name}:latest" || true docker tag "${REG}/${OWN}/${name}:latest" "${PUB}/${OWN}/${name}:latest" || true done # Do NOT set COMPOSE_PROJECT_NAME differently — volumes must stay # osint-dashboard_osint-pgdata (pinned by `name:` in compose). docker compose build --no-cache app ingester camera-service news-scraper news-summarizer || \ docker compose build app ingester camera-service news-scraper news-summarizer docker compose up -d --force-recreate docker image prune -f echo "osint-dashboard deployed; images also on ${PUB}/${OWN}/" - name: Summary if: always() run: | { echo "## Forgejo registry images" echo "Pushed via ${{ steps.img.outputs.reg }} (loopback). Pull publicly:" echo "- \`${{ steps.img.outputs.pub }}/sirius/osint-dashboard:latest\`" echo "- \`${{ steps.img.outputs.pub }}/sirius/osint-dashboard-pg:latest\`" echo "- \`${{ steps.img.outputs.pub }}/sirius/osint-news-scraper:latest\`" echo "- \`${{ steps.img.outputs.pub }}/sirius/osint-news-summarizer:latest\`" } >> "$GITHUB_STEP_SUMMARY"