name: Build and Deploy to Production on: push: branches: - main jobs: # Same job as in test.yaml (which covers non-main branches); duplicated here # so a red suite blocks the build+deploy — Forgejo has no cross-workflow needs. test: runs-on: nix-latest # Settings via env, not /etc/nix/nix.conf: in the image that is a # symlink into /nix/store, which the runner's nix-store volume # shadows, so it dangles once the image moves on. env: NIX_CONFIG: | experimental-features = nix-command flakes build-users-group = sandbox = false services: postgres: image: postgres:17-alpine env: POSTGRES_USER: postgres POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_DB: asxpio_test steps: - name: Prepare container for actions run: | # node for JS actions (checkout); git-minimal is already in the image nix-env -iA nixpkgs.nodejs_22 - name: Checkout uses: actions/checkout@v7 - name: Cache gems uses: actions/cache@v6 with: path: .gems key: gems-${{ hashFiles('Gemfile.lock') }} - name: Wait for Postgres run: | for i in $(seq 1 30); do nix develop -c pg_isready -h postgres -U postgres && exit 0 sleep 2 done echo "Postgres service never became ready" >&2 exit 1 - name: Run test suite env: TEST_DATABASE_URL: postgres://postgres@postgres:5432/asxpio_test run: nix develop -c bundle exec rake test build: runs-on: arch-latest needs: test container: image: gcr.io/kaniko-project/executor:debug outputs: image_tag: ${{ steps.build_image.outputs.image_tag }} steps: - name: Build and push Docker Image using Kaniko id: build_image run: | # Tags SHORT_SHA=${GITHUB_SHA::8} IMAGE_BASE=git.czsk.it/asxpi/asxpio IMAGE_TAG=${IMAGE_BASE}:${SHORT_SHA} IMAGE_LATEST=${IMAGE_BASE}:latest # Auth Conf mkdir -p /kaniko/.docker echo "{\"auths\":{\"git.czsk.it\":{\"auth\":\"$(echo -n asxpi:${{ secrets.FJ_TOKEN }} | base64)\"}}}" > /kaniko/.docker/config.json # Build & push /kaniko/executor \ --context=git://git.czsk.it/${{ github.repository }}.git \ --git=branch=${{ github.ref_name }} \ --destination=$IMAGE_TAG \ --destination=$IMAGE_LATEST # Output the specific tag for deployment echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT deploy: runs-on: arch-latest needs: build steps: - name: Install dependencies run: | pacman -Syu --noconfirm nodejs openssh rsync - name: Checkout code uses: actions/checkout@v7 - name: Deploy docker-compose.yml uses: easingthemes/ssh-deploy@v6.0.3 with: SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }} REMOTE_HOST: ${{ secrets.DEPLOY_IP }} REMOTE_USER: ${{ secrets.DEPLOY_USER }} SOURCE: "docker-compose.yml" TARGET: "/opt/asxpio/" - name: Deploy .env from secrets uses: appleboy/ssh-action@v1.2.5 env: SESSION_SECRET: ${{ secrets.SESSION_SECRET }} SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} ADMIN_USER: ${{ secrets.ADMIN_USER }} ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }} ASXPIO_DB_PASSWORD: ${{ secrets.ASXPIO_DB_PASSWORD }} ASXPIO_S3_ACCESS_KEY: ${{ secrets.ASXPIO_S3_ACCESS_KEY }} ASXPIO_S3_SECRET_KEY: ${{ secrets.ASXPIO_S3_SECRET_KEY }} LTC_ADDRESS: ${{ secrets.LTC_ADDRESS }} CRYPTO_ADDRESSES: ${{ secrets.CRYPTO_ADDRESSES }} INDEXNOW: ${{ secrets.INDEXNOW }} with: host: ${{ secrets.DEPLOY_IP }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} envs: SESSION_SECRET,SMTP_PASSWORD,ADMIN_USER,ADMIN_PASSWORD,ASXPIO_DB_PASSWORD,ASXPIO_S3_ACCESS_KEY,ASXPIO_S3_SECRET_KEY,LTC_ADDRESS,CRYPTO_ADDRESSES,INDEXNOW script_stop: true script: | umask 077 mkdir -p /opt/asxpio # Garage only takes its own key format (id: GK + 24 hex digits, # secret: 64 hex digits). Hash the MinIO-era secrets into it, as # storage's init/garage-bootstrap.sh does; a pair already in # Garage's format is used as it is. garage_key_id() { if printf '%s' "$1" | grep -Eq '^GK[0-9a-f]{24}$'; then printf '%s' "$1" else printf 'GK%s' "$(printf '%s' "$1" | sha256sum | cut -c1-24)"; fi } garage_secret() { if printf '%s' "$1" | grep -Eq '^[0-9a-f]{64}$'; then printf '%s' "$1" else printf '%s' "$1" | sha256sum | cut -c1-64; fi } S3_ACCESS_KEY="$(garage_key_id "${ASXPIO_S3_ACCESS_KEY}")" S3_SECRET_KEY="$(garage_secret "${ASXPIO_S3_SECRET_KEY}")" cat > /opt/asxpio/.env < MAIL_TO=ie@asxp.io ADMIN_USER=${ADMIN_USER} ADMIN_PASSWORD=${ADMIN_PASSWORD} DATABASE_URL=postgres://asxpio:${ASXPIO_DB_PASSWORD}@postgres:5432/asxpio S3_ENDPOINT=http://garage:3900 S3_PUBLIC_ENDPOINT=https://s3.asxp.io S3_REGION=us-east-1 S3_BUCKET=asxpio-invoices S3_ACCESS_KEY=${S3_ACCESS_KEY} S3_SECRET_KEY=${S3_SECRET_KEY} LTC_ADDRESS=${LTC_ADDRESS} CRYPTO_ADDRESSES=${CRYPTO_ADDRESSES} BINGO_HOST=datacenterbingo.xyz BINGO_S3_BUCKET=datacenterbingo-photos PUBLIC_S3_BUCKET=public PUBLIC_BASE_URL=https://s3.asxp.io/public INDEXNOW_KEY=${INDEXNOW} EOF chmod 600 /opt/asxpio/.env - name: Deploy and update container uses: appleboy/ssh-action@v1.2.5 env: NEW_IMAGE: ${{ needs.build.outputs.image_tag }} with: host: ${{ secrets.DEPLOY_IP }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} envs: NEW_IMAGE script: | cd /opt/asxpio sed -i "s|image:.*|image: $NEW_IMAGE|" docker-compose.yml docker pull $NEW_IMAGE docker compose up -d # Wait for the image HEALTHCHECK (GET /healthz) to report healthy; # `docker ps | grep` passed even while the app crash-looped. for i in $(seq 1 18); do status=$(docker inspect --format '{{.State.Health.Status}}' asxpio 2>/dev/null || echo missing) if [ "$status" = "healthy" ]; then echo "Deployment successful!" exit 0 fi sleep 5 done echo "asxpio never became healthy (last status: $status)" >&2 docker logs --tail 50 asxpio exit 1