|
|
@@ -13,6 +13,11 @@ jobs:
|
|
|
runs-on: [self-hosted, Linux, X64, hetzner]
|
|
|
|
|
|
steps:
|
|
|
+ # Only for .github/scripts; the app itself arrives as an image.
|
|
|
+ - uses: actions/checkout@v4
|
|
|
+ with:
|
|
|
+ sparse-checkout: .github/scripts
|
|
|
+
|
|
|
- name: Resolve version tag
|
|
|
id: tag
|
|
|
run: |
|
|
|
@@ -78,11 +83,11 @@ jobs:
|
|
|
# Optional, a repository variable rather than a secret: the largest backup data
|
|
|
# file a restore reads into memory, in MiB (128 unless set, see .env.example).
|
|
|
BACKUP_MAX_DATA_FILE_MB: ${{ vars.CLOUD_BACKUP_MAX_DATA_FILE_MB }}
|
|
|
- # Optional. Next encrypts what a server action closes over with a key
|
|
|
- # made fresh by every build, so a tab left open across a deploy can
|
|
|
- # no longer be read by the new container. A fixed key keeps those tabs
|
|
|
- # working. 32 random bytes, base64: `openssl rand -base64 32`. Left
|
|
|
- # unset, the build's own key is used, as before.
|
|
|
+ # Optional. Next encrypts what a server action closes over with this
|
|
|
+ # key; left unset, the key the image was built with is used. It does
|
|
|
+ # not keep open tabs working across a deploy: action IDs are fixed
|
|
|
+ # when the image is built, from the build's key (see the Dockerfile).
|
|
|
+ # 32 random bytes, base64: `openssl rand -base64 32`.
|
|
|
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.CLOUD_NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
|
|
|
GOOGLE_INTEGRATION_CLIENT_ID: ${{ secrets.CLOUD_GOOGLE_INTEGRATION_CLIENT_ID }}
|
|
|
GOOGLE_INTEGRATION_CLIENT_SECRET: ${{ secrets.CLOUD_GOOGLE_INTEGRATION_CLIENT_SECRET }}
|
|
|
@@ -160,6 +165,17 @@ jobs:
|
|
|
QUICKBOOKS_INTEGRATION_CLIENT_SECRET: ${QUICKBOOKS_INTEGRATION_CLIENT_SECRET}
|
|
|
FIKEN_INTEGRATION_CLIENT_ID: ${FIKEN_INTEGRATION_CLIENT_ID}
|
|
|
FIKEN_INTEGRATION_CLIENT_SECRET: ${FIKEN_INTEGRATION_CLIENT_SECRET}
|
|
|
+ # Healthy once the server answers and reaches the database. The
|
|
|
+ # deploy waits for it and puts the previous version back when it
|
|
|
+ # never comes. Failures in start_period do not count, so a long
|
|
|
+ # migration at start is not taken for a crash. nginx-proxy routes
|
|
|
+ # by container, not by health, so this changes no traffic.
|
|
|
+ healthcheck:
|
|
|
+ test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/api/v1/health"]
|
|
|
+ interval: 10s
|
|
|
+ timeout: 10s
|
|
|
+ retries: 3
|
|
|
+ start_period: 30m
|
|
|
networks:
|
|
|
- proxy
|
|
|
COMPOSE
|
|
|
@@ -242,26 +258,33 @@ jobs:
|
|
|
# folder belong to the backup cron and age out there.
|
|
|
ls -t "$BACKUP_DIR"/*-pre-*.dump 2>/dev/null | tail -n +11 | xargs -r rm --
|
|
|
|
|
|
+ # What to put back if the new version never gets healthy. By image ID,
|
|
|
+ # since a tag can have moved by the time it is needed.
|
|
|
+ - name: Note the running version
|
|
|
+ id: previous
|
|
|
+ run: |
|
|
|
+ if docker inspect torqvoice-app > /dev/null 2>&1; then
|
|
|
+ echo "image_id=$(docker inspect --format '{{.Image}}' torqvoice-app)" >> "$GITHUB_OUTPUT"
|
|
|
+ echo "image=$(docker inspect --format '{{.Config.Image}}' torqvoice-app)" >> "$GITHUB_OUTPUT"
|
|
|
+ echo "Running now: $(docker inspect --format '{{.Config.Image}}' torqvoice-app)"
|
|
|
+ else
|
|
|
+ echo "Nothing running yet, so there is nothing to roll back to."
|
|
|
+ fi
|
|
|
+
|
|
|
- name: Deploy
|
|
|
run: |
|
|
|
cd $HOME/torqvoice-deploy/prod
|
|
|
APP_TAG=${{ steps.tag.outputs.value }} docker compose up -d --pull always
|
|
|
|
|
|
- - name: Health check
|
|
|
+ - name: Health check, rolling back if it fails
|
|
|
run: |
|
|
|
- echo "Waiting for prod container to be ready..."
|
|
|
- for i in $(seq 1 12); do
|
|
|
- STATUS=$(docker inspect --format='{{.State.Status}}' torqvoice-app 2>/dev/null || echo "not found")
|
|
|
- if [ "$STATUS" = "running" ]; then
|
|
|
- echo "Deployed ${{ steps.tag.outputs.value }}."
|
|
|
- exit 0
|
|
|
- fi
|
|
|
- echo "Status: $STATUS, retrying in 5s..."
|
|
|
- sleep 5
|
|
|
- done
|
|
|
- echo "::error::Container failed to start"
|
|
|
- docker logs torqvoice-app --tail 50
|
|
|
- exit 1
|
|
|
+ cd "$HOME/torqvoice-deploy/prod"
|
|
|
+ bash "$GITHUB_WORKSPACE/.github/scripts/roll-back-if-unhealthy.sh" \
|
|
|
+ torqvoice-app \
|
|
|
+ "${{ steps.tag.outputs.value }}" \
|
|
|
+ "${{ steps.previous.outputs.image_id }}" \
|
|
|
+ "${{ steps.previous.outputs.image }}" \
|
|
|
+ prod
|
|
|
|
|
|
- name: Prune old images and build cache
|
|
|
run: docker image prune -f && docker builder prune -f
|