name: CI on: push: branches: - main - proof workflow_dispatch: env: REGISTRY: git.lukas-harat.com IMAGE_NAME: ${{ github.repository }} permissions: contents: read packages: write jobs: test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci - name: Typecheck run: npx tsc --noEmit build: needs: test runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.USERNAME }} password: ${{ secrets.TOKEN }} - name: Build and push image uses: docker/build-push-action@v6 with: context: . push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max deploy: needs: build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Deploy to TrueNAS env: DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }} DEPLOY_USER: ${{ secrets.DEPLOY_USER }} DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }} BETTER_AUTH_URL: ${{ secrets.BETTER_AUTH_URL }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} STAFF_SEED_EMAIL: ${{ secrets.STAFF_SEED_EMAIL }} STAFF_SEED_PASSWORD: ${{ secrets.STAFF_SEED_PASSWORD }} RESERVATIONS_FROM_EMAIL: ${{ secrets.RESERVATIONS_FROM_EMAIL }} IMAGE_TAG: ${{ github.sha }} run: | set -Eeuo pipefail trap 'echo "ERROR: command failed at line $LINENO: $BASH_COMMAND" >&2' ERR echo "==> Preparing SSH" echo "DEPLOY_HOST=${DEPLOY_HOST:-}" echo "DEPLOY_PORT=${DEPLOY_PORT:-22}" echo "DEPLOY_USER=${DEPLOY_USER:-}" echo "DEPLOY_PATH=${DEPLOY_PATH:-}" echo "IMAGE_TAG=${IMAGE_TAG:-}" echo "==> Checking required variables" required_vars=( DEPLOY_HOST DEPLOY_USER DEPLOY_PATH DEPLOY_SSH_KEY POSTGRES_PASSWORD BETTER_AUTH_SECRET BETTER_AUTH_URL RESEND_API_KEY RESERVATIONS_FROM_EMAIL STAFF_SEED_EMAIL STAFF_SEED_PASSWORD IMAGE_TAG ) for var in "${required_vars[@]}"; do if [[ -z "${!var:-}" ]]; then echo "ERROR: $var is missing or empty" >&2 exit 1 fi echo " OK: $var is set" done echo "==> Creating ~/.ssh" mkdir -p ~/.ssh echo "==> Writing deployment SSH key" printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key echo "==> Scanning SSH host key" ssh-keyscan \ -p "${DEPLOY_PORT:-22}" \ -H "$DEPLOY_HOST" \ >> ~/.ssh/known_hosts echo "==> SSH host key added" echo "==> Validating SSH private key" ssh-keygen -y -f ~/.ssh/deploy_key >/dev/null \ && echo "SSH private key is valid" \ || { echo "ERROR: SSH private key cannot be parsed" exit 1 } echo "==> Testing SSH connection" ssh \ -i ~/.ssh/deploy_key \ -p "${DEPLOY_PORT:-22}" \ "$DEPLOY_USER@$DEPLOY_HOST" \ "echo 'SSH connection successful'; echo \"Remote host: \$(hostname)\"; echo \"Remote user: \$(whoami)\"" echo "==> Creating deployment directory if needed..." ssh \ -i ~/.ssh/deploy_key \ -p "${DEPLOY_PORT:-22}" \ "$DEPLOY_USER@$DEPLOY_HOST" \ "mkdir -pv '$DEPLOY_PATH'" echo "==> Verifying deployment directory..." ssh \ -i ~/.ssh/deploy_key \ -p "${DEPLOY_PORT:-22}" \ "$DEPLOY_USER@$DEPLOY_HOST" \ "ls -ld '$DEPLOY_PATH'" echo "==> Uploading compose file..." echo " Local file: docker-compose.prod.yml" echo " Remote file: $DEPLOY_PATH/docker-compose.yml" scp \ -v \ -i ~/.ssh/deploy_key \ -P "${DEPLOY_PORT:-22}" \ docker-compose.prod.yml \ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/docker-compose.yml" echo "==> Verifying uploaded compose file..." ssh \ -i ~/.ssh/deploy_key \ -p "${DEPLOY_PORT:-22}" \ "$DEPLOY_USER@$DEPLOY_HOST" \ "ls -lh '$DEPLOY_PATH/docker-compose.yml'" echo "==> Starting deployment..." ssh \ -v \ -i ~/.ssh/deploy_key \ -p "${DEPLOY_PORT:-22}" \ "$DEPLOY_USER@$DEPLOY_HOST" \ "POSTGRES_PASSWORD=$(printf '%q' "$POSTGRES_PASSWORD") \ BETTER_AUTH_SECRET=$(printf '%q' "$BETTER_AUTH_SECRET") \ BETTER_AUTH_URL=$(printf '%q' "$BETTER_AUTH_URL") \ RESEND_API_KEY=$(printf '%q' "$RESEND_API_KEY") \ RESERVATIONS_FROM_EMAIL=$(printf '%q' "$RESERVATIONS_FROM_EMAIL") \ STAFF_SEED_EMAIL=$(printf '%q' "$STAFF_SEED_EMAIL") \ STAFF_SEED_PASSWORD=$(printf '%q' "$STAFF_SEED_PASSWORD") \ IMAGE_TAG=$(printf '%q' "$IMAGE_TAG") \ DEPLOY_PATH=$(printf '%q' "$DEPLOY_PATH") \ bash -c ' set -Eeuo pipefail trap '\''echo "ERROR: remote command failed at line $LINENO: $BASH_COMMAND" >&2'\'' echo "========================================" echo "REMOTE DEPLOYMENT" echo "========================================" echo "==> Environment" echo " DEPLOY_PATH=\$DEPLOY_PATH" echo " IMAGE_TAG=\$IMAGE_TAG" echo " DEPLOY_USER=\$(whoami)" echo " HOST=\$(hostname)" echo " PWD=\$(pwd)" echo "==> Checking required commands" command -v docker echo " Docker: \$(docker --version)" docker compose version echo " Docker Compose: OK" echo "==> Changing directory" cd \"\$DEPLOY_PATH\" echo " Current directory: \$(pwd)" echo "==> Listing deployment directory" ls -lah echo "==> Validating Docker Compose configuration" docker compose config echo "==> Docker Compose configuration is valid" echo "==> Pulling images..." docker compose pull echo "==> Image pull completed" echo "==> Current containers before deployment" docker compose ps echo "==> Starting PostgreSQL..." docker compose up -d postgres echo "==> PostgreSQL start command completed" echo "==> PostgreSQL status" docker compose ps postgres echo "==> PostgreSQL logs (last 50 lines)" docker compose logs --tail=50 postgres || true echo "==> Running database migrations..." docker compose run --rm app npx drizzle-kit migrate echo "==> Database migrations completed" echo "==> Seeding database..." docker compose run --rm app npm run db:seed echo "==> Database seeding completed" echo "==> Starting application..." docker compose up -d echo "==> Application startup completed" echo "==> Current container status" docker compose ps echo "==> Recent application logs" docker compose logs --tail=100 app || true echo "==> Cleaning unused images..." docker image prune -f echo "========================================" echo "DEPLOYMENT COMPLETE" echo "========================================" '" echo "==> Deployment finished successfully"