name: Podman Rootless Demo on: [push, pull_request] jobs: test-backend: runs-on: [ci] if: false # Point all steps at the host's rootless Podman socket env: # Point the client at the mounted socket CONTAINER_HOST: unix:///run/user/1001/podman/podman.sock # Make sure podman looks in the correct runtime dir hierarchy XDG_RUNTIME_DIR: /tmp RUN_ID: ${{ github.run_id }} POSTGRES_IMG_DIGEST: ${{ secrets.POSTGRES_IMG_DIGEST }} RUST_IMG_DIGEST: ${{ secrets.RUST_IMG_DIGEST }} PREBUILT_BACKEND_TEST_IMAGE: ${{ secrets.REGISTRY_HOST }}/${{ github.repository }}/sharenet-test-rust CACHE_REPO: ${{ secrets.REGISTRY_HOST }}/${{ github.repository }}/sharenet-test-rust-cache steps: - name: Checkout code uses: actions/checkout@v4 - name: Verify socket visibility run: | set -euo pipefail id -u; id -g ls -ld /run/user/1001/podman ls -l /run/user/1001/podman/podman.sock test -S /run/user/1001/podman/podman.sock - name: Use host rootless Podman run: | set -euo pipefail podman --remote info --format '{{.Host.RemoteSocket.Path}} (remote={{.Host.RemoteSocket.Exists}})' podman --remote version podman --remote run --rm alpine:3.20 echo "Hello from host rootless Podman!" - name: Login to container registry with PAT run: | echo "${{ secrets.REGISTRY_TOKEN }}" | podman --remote login \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin \ "${{ secrets.REGISTRY_HOST }}" - name: Create network run: podman --remote network create integ-${{ env.RUN_ID }} - name: Generate cache key from lockfile id: cache-key run: | cd "$GITHUB_WORKSPACE/backend" LOCK_HASH=$(sha256sum Cargo.lock | cut -d' ' -f1) SHORT_HASH=$(echo "$LOCK_HASH" | cut -c1-12) echo "cache_key=$SHORT_HASH" >> $GITHUB_OUTPUT echo "Using cache key: $SHORT_HASH" - name: Build dependencies cache image run: | # Build deps stage with layer caching podman --remote build \ --layers \ --cache-to "$CACHE_REPO" \ --cache-from "$CACHE_REPO" \ --target deps \ -f Dockerfile.test-rust \ -t "$PREBUILT_BACKEND_TEST_IMAGE:deps-${{ steps.cache-key.outputs.cache_key }}" \ backend podman --remote push \ "$PREBUILT_BACKEND_TEST_IMAGE:deps-${{ steps.cache-key.outputs.cache_key }}" - name: Build full test image with cached dependencies run: | # Build final image using cached deps podman --remote build \ --layers \ --cache-from "$CACHE_REPO" \ --target runner \ -f Dockerfile.test-rust \ -t "$PREBUILT_BACKEND_TEST_IMAGE:test-${{ github.sha }}" \ backend - name: Start PostgreSQL run: | podman --remote run -d \ --name test-postgres-${{ env.RUN_ID }} \ --network integ-${{ env.RUN_ID }} \ -e POSTGRES_PASSWORD=password \ -e POSTGRES_USER=postgres \ -e POSTGRES_DB=sharenet_test \ "$POSTGRES_IMG_DIGEST" - name: Wait for PostgreSQL run: | timeout 60 bash -euc ' until podman --remote exec test-postgres-${{ env.RUN_ID }} \ pg_isready -h 127.0.0.1 -p 5432 -U postgres; do sleep 1 done ' - name: Ensure host Cargo cache directory exists run: | podman --remote run --rm \ -v /home/ci-service/.cache:/c \ alpine:3.20 sh -lc 'mkdir -p /c/cargo' - name: Run backend tests run: | # Run tests in the pre-built test image podman --remote run --rm \ --network integ-${{ env.RUN_ID }} \ -e DATABASE_URL=postgres://postgres:password@test-postgres-${{ env.RUN_ID }}:5432/sharenet_test \ "$PREBUILT_BACKEND_TEST_IMAGE:test-${{ github.sha }}" - name: Cleanup if: always() run: | podman --remote rm -f test-postgres-${{ env.RUN_ID }} 2>/dev/null || true podman --remote network rm integ-${{ env.RUN_ID }} 2>/dev/null || true - name: Debug DB (on failure) if: failure() run: podman --remote logs --tail=200 test-postgres-${{ env.RUN_ID }} || true test-frontend: runs-on: [ci] if: false needs: test-backend steps: - name: Checkout code uses: actions/checkout@v4 - name: Pass-through (no frontend tests yet) run: echo "Frontend tests placeholder - no tests implemented yet" build-backend: runs-on: [ci] # if: false # needs: [test-backend, test-frontend] # needs: [test-frontend] env: CONTAINER_HOST: unix:///run/user/1001/podman/podman.sock XDG_RUNTIME_DIR: /tmp RUN_ID: ${{ github.run_id }} BACKEND_IMAGE: ${{ secrets.REGISTRY_HOST }}/${{ github.repository }}/sharenet-backend-api-postgres steps: - name: Checkout code uses: actions/checkout@v4 - name: Login to container registry with PAT run: | echo "${{ secrets.REGISTRY_TOKEN }}" | podman --remote login \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin \ "${{ secrets.REGISTRY_HOST }}" - name: Build backend container image run: | podman --remote build \ -f backend/Dockerfile \ -t "$BACKEND_IMAGE:${{ github.sha }}" \ -t "$BACKEND_IMAGE:latest" \ backend - name: Push backend container image run: | podman --remote push "$BACKEND_IMAGE:${{ github.sha }}" podman --remote push "$BACKEND_IMAGE:latest" build-frontend: runs-on: [ci] # if: false # needs: [test-backend, test-frontend] # needs: [test-frontend] env: CONTAINER_HOST: unix:///run/user/1001/podman/podman.sock XDG_RUNTIME_DIR: /tmp RUN_ID: ${{ github.run_id }} FRONTEND_IMAGE: ${{ secrets.REGISTRY_HOST }}/${{ github.repository }}/sharenet-frontend steps: - name: Checkout code uses: actions/checkout@v4 - name: Login to container registry with PAT run: | echo "${{ secrets.REGISTRY_TOKEN }}" | podman --remote login \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin \ "${{ secrets.REGISTRY_HOST }}" - name: Disk preflight diagnostic run: | echo "=== Podman storage info ===" podman --remote system df echo "=== Podman graph root ===" podman --remote info --format '{{.Store.GraphRoot}}' echo "=== Disk space info ===" df -h /home/ci-service /tmp /var/tmp 2>/dev/null || df -h /tmp /var/tmp echo "=== Inode info ===" df -i /home/ci-service /tmp /var/tmp 2>/dev/null || df -i /tmp /var/tmp - name: Install Rust toolchain and wasm-pack run: | # Install Rust using rustup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source $HOME/.cargo/env # Install wasm32 target rustup target add wasm32-unknown-unknown # Configure cargo registry for sharenet-sh-forgejo mkdir -p $HOME/.cargo echo '[registries.sharenet-sh-forgejo]' > $HOME/.cargo/config.toml echo 'index = "sparse+https://${{ secrets.REGISTRY_HOST }}/api/packages/${{ github.repository }}/cargo/index/"' >> $HOME/.cargo/config.toml echo '' >> $HOME/.cargo/config.toml echo '[net]' >> $HOME/.cargo/config.toml echo 'git-fetch-with-cli = true' >> $HOME/.cargo/config.toml # Install wasm-pack cargo install wasm-pack - name: Build WASM module run: | source $HOME/.cargo/env cd frontend/wasm wasm-pack build --target web - name: Build frontend container image run: | # Create temp directory on larger filesystem TMP_DIR="/home/ci-service/tmp" if [ ! -d "$TMP_DIR" ]; then TMP_DIR="$(mktemp -d)" fi export TMPDIR="$TMP_DIR" podman --remote build \ --build-arg NEXT_PUBLIC_API_HOST=${{ secrets.PROD_BACKEND_HOST }} \ --build-arg NEXT_PUBLIC_API_PORT=${{ secrets.PROD_BACKEND_PORT }} \ -f frontend/Dockerfile \ -t "$FRONTEND_IMAGE:${{ github.sha }}" \ -t "$FRONTEND_IMAGE:latest" \ frontend - name: Cleanup storage if: always() run: podman --remote system prune -f - name: Push frontend container image run: | podman --remote push "$FRONTEND_IMAGE:${{ github.sha }}" podman --remote push "$FRONTEND_IMAGE:latest" deploy-prod: runs-on: [prod] # needs: [build-backend] # needs: [build-frontend] needs: [build-backend, build-frontend] env: CONTAINER_HOST: unix:///run/user/1001/podman/podman.sock XDG_RUNTIME_DIR: /tmp RUN_ID: ${{ github.run_id }} APP_NAME: ${{ github.repository }} REGISTRY_HOST: ${{ secrets.REGISTRY_HOST }} IMAGE_TAG: latest POSTGRES_DATABASE_NAME: ${{ secrets.PROD_DB_DATABASE_NAME }} POSTGRES_USERNAME: ${{ secrets.PROD_DB_USERNAME }} POSTGRES_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }} POSTGRES_PORT: ${{ secrets.PROD_DB_PORT }} PROD_BACKEND_PORT: ${{ secrets.PROD_BACKEND_PORT }} PROD_FRONTEND_PORT: ${{ secrets.PROD_FRONTEND_PORT }} PROD_BACKEND_HOST: ${{ secrets.PROD_BACKEND_HOST }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Login to container registry with PAT run: | echo "${{ secrets.REGISTRY_TOKEN }}" | podman --remote login \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin \ "${{ secrets.REGISTRY_HOST }}" - name: (Optional) Pre-pull images to speed up play run: | podman --remote pull "$REGISTRY_HOST/$APP_NAME/sharenet-backend-api-postgres:$IMAGE_TAG" podman --remote pull "$REGISTRY_HOST/$APP_NAME/sharenet-frontend:$IMAGE_TAG" # RENDER & COPY NGINX CONF (unchanged except for whitelisted envsubst) - name: Render nginx.conf and write to host run: | set -euo pipefail apk add --no-cache gettext >/dev/null envsubst '${PROD_FRONTEND_PORT} ${PROD_BACKEND_PORT}' < nginx/nginx.conf > /tmp/nginx.conf podman --remote run --rm -i \ --userns=keep-id \ -v /opt/sharenet/nginx:/host-nginx:rw \ alpine:3.20 sh -c 'install -D -m 0644 /dev/stdin /host-nginx/nginx.conf' \ < /tmp/nginx.conf # LINT NGINX CONF BEFORE APPLYING - name: Validate nginx.conf with throwaway container run: | set -euo pipefail podman --remote run --rm \ -v /opt/sharenet/nginx:/etc/nginx:ro \ docker.io/nginx:alpine \ sh -lc 'nginx -t -c /etc/nginx/nginx.conf' # APPLY/RE-APPLY THE POD (no explicit "down"; use --replace) - name: Apply pod (kube play --replace) run: | set -euo pipefail # If your Podman supports --replace, this is the cleanest: envsubst < deploy/prod-pod.yml | podman --remote kube play --replace - # If --replace is NOT supported in your Podman, use this fallback instead: # - name: Recreate pod (fallback) # run: | # set -euo pipefail # podman --remote pod rm -f sharenet-production-pod 2>/dev/null || true # envsubst < deploy/prod-pod.yml | podman --remote kube play - # VERIFY (install curl first) - name: Verify in-pod Nginx run: | set -euo pipefail apk add --no-cache curl >/dev/null curl -sS -D- http://127.0.0.1:8080/healthz curl -sS -I http://127.0.0.1:8080/