Forgejo reserves names starting with FORGEJO_, and since runner v13 secrets.FORGEJO_TOKEN, _USER and _REGISTRY no longer reach the job as the values set on the repository: Kaniko logged in with the wrong credentials and every image push failed with UNAUTHORIZED. The registry host and user are not secret (they are in every image name), so write them in the workflow; the token comes from FJ_TOKEN, as in datacenterbingo. Assisted-by: Claude:opus-5-5
202 lines
7.2 KiB
YAML
202 lines
7.2 KiB
YAML
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@v4
|
|
|
|
- name: Cache gems
|
|
uses: actions/cache@v4
|
|
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@v4
|
|
|
|
- name: Deploy docker-compose.yml
|
|
uses: easingthemes/ssh-deploy@v5.1.0
|
|
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.3
|
|
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 <<EOF
|
|
RACK_ENV=production
|
|
SESSION_SECRET=${SESSION_SECRET}
|
|
SMTP_ADDR=smtp.fastmail.com
|
|
SMTP_PORT=587
|
|
SMTP_USER=me@asxp.io
|
|
SMTP_PASSWORD=${SMTP_PASSWORD}
|
|
MAIL_FROM=IE Sergei Poljanski Contact Form <me@asxp.io>
|
|
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.3
|
|
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
|