mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
Move the SaaS dev deployment out to its own branch
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
# SaaS dev deployment secrets
|
||||
|
||||
`Saas-Dev-Deploy.yml` deploys a SaaS-mode server on every push to `main`
|
||||
(frontend `901`, backend `902`, AI engine internal-only). Every SaaS credential
|
||||
comes from the **`saas-dev` GitHub Environment**, so the secret names match the
|
||||
container env vars exactly - there is no `SAAS_DEV_` prefix to keep in sync.
|
||||
|
||||
Repo settings -> Environments -> `saas-dev` -> Environment secrets.
|
||||
|
||||
If any required secret is missing the job logs a warning and skips: a half
|
||||
configured SaaS backend boot-loops on its datasource rather than failing
|
||||
usefully, so no deploy is better than a broken one.
|
||||
|
||||
## Required
|
||||
|
||||
| Secret | What it is | Where to get it |
|
||||
| --- | --- | --- |
|
||||
| `SAAS_DB_PROJECT_REF` | Dev Supabase project ref (the `<ref>` in `<ref>.supabase.co`) | Supabase -> Project Settings -> General |
|
||||
| `SAAS_DB_URL` | JDBC URL for that project's Postgres, e.g. `jdbc:postgresql://db.<ref>.supabase.co:5432/postgres` | built from the ref |
|
||||
| `SAAS_DB_PASSWORD` | Postgres password | Supabase -> Project Settings -> Database |
|
||||
| `SAAS_SUPABASE_PUBLISHABLE_KEY` | `sb_publishable_...`, baked into the browser bundle at build time. Frontend-only - the backend never reads it under the `saas` profile - but the frontend is deployed, so it is required | Supabase -> Project Settings -> API |
|
||||
|
||||
Not secrets, so not stored: the Supabase URL and the PAYG meter endpoint are
|
||||
derived from `SAAS_DB_PROJECT_REF` the same way the app derives them.
|
||||
|
||||
## Optional
|
||||
|
||||
| Secret | Effect when set | Effect when absent |
|
||||
| --- | --- | --- |
|
||||
| `SUPABASE_EDGE_FUNCTION_SECRET` | Backend -> edge function calls work (team invites, PAYG meter) | app boots fine; invites error, metering no-ops with a WARN |
|
||||
| `ANTHROPIC_API_KEY` | AI engine is built, deployed, and wired to the backend | deploy skips the engine; AI features are off |
|
||||
| `VOYAGE_API_KEY` | RAG embeddings work (`voyageai:voyage-4`) | engine runs, RAG search fails |
|
||||
| `SAAS_DB_USERNAME` | overrides the DB user | defaults to `postgres` |
|
||||
| `KEYGEN_ACCOUNT_ID` + `KEYGEN_API_TOKEN` + `KEYGEN_POLICY_ID` | real enterprise licences via Keygen (all three needed) | mock licence service |
|
||||
|
||||
## Already at repo level
|
||||
|
||||
`NEW_VPS_SSH_KEY`, `NEW_VPS_USERNAME`, `NEW_VPS_HOST` - shared with the PR
|
||||
previews and the main demo. Environment secrets override repo secrets of the
|
||||
same name, so adding a `saas-dev` copy is how you point this deploy at a
|
||||
different host without touching the others.
|
||||
|
||||
No registry credential is needed: images go to
|
||||
`ghcr.io/<owner>/stirling-pdf-test` authenticated with the job's `GITHUB_TOKEN`,
|
||||
per #7435.
|
||||
|
||||
## Pointing at a different project
|
||||
|
||||
Everything project-specific is in the environment, so a second target (staging,
|
||||
a throwaway Supabase branch) is a second environment plus a one-line change to
|
||||
`environment:` in the workflow.
|
||||
|
||||
## Schema
|
||||
|
||||
The workflow deploys against an existing Supabase project, it does not create
|
||||
one. Schema comes from the Supabase migrations in the SaaS repo; the Java side
|
||||
only reconciles its own entity tables (`ddl-auto=update`) inside the
|
||||
`stirling_pdf` schema.
|
||||
@@ -1,371 +0,0 @@
|
||||
name: Auto SaaS Dev Deployment
|
||||
|
||||
# Dev SaaS server, built from this branch and deployed beside the PR previews
|
||||
# and the main demo.
|
||||
#
|
||||
# Unlike those two, SaaS cannot be seeded from a committed H2 file: the saas
|
||||
# profile overrides the H2 datasource with Postgres and fails fast when the
|
||||
# credentials are missing, and identities live in Supabase Auth rather than the
|
||||
# local user table. A dev Supabase project therefore has to exist first - this
|
||||
# workflow deploys against it, it does not create it.
|
||||
#
|
||||
# Every SaaS-specific credential comes from the `saas-dev` GitHub Environment,
|
||||
# so the names here are the app's own env vars rather than SAAS_DEV_-prefixed
|
||||
# copies, and pointing this at another project means swapping one environment.
|
||||
# See ".github/workflows/README-saas-dev.md" for the full secret list.
|
||||
#
|
||||
# Ports: frontend 901, backend 902. Both sit below every PR number (previews
|
||||
# publish on the bare PR number) and beside the main demo on 900. The AI engine
|
||||
# is reachable only over the compose network.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# Temporary, for shaking this out before it lands. Remove before merge -
|
||||
# it deploys to the same host, dir and ports as the main trigger, so
|
||||
# whichever pushes last wins.
|
||||
- seeded-enterprise-deployments
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FRONTEND_PORT: "901"
|
||||
BACKEND_PORT: "902"
|
||||
DEPLOY_DIR: /stirling/SAAS-DEV
|
||||
|
||||
jobs:
|
||||
deploy-saas-dev:
|
||||
runs-on: ubuntu-latest
|
||||
# Branch policy is enforced by GitHub before the job starts, so editing this
|
||||
# file cannot reach the SaaS credentials from a fork or a side branch.
|
||||
environment: saas-dev
|
||||
concurrency:
|
||||
group: saas-dev-deploy
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
# The dev Supabase project is the one thing this workflow cannot stand up
|
||||
# for itself. Without it the backend would boot-loop on a missing
|
||||
# datasource, so skip the whole deploy rather than leave a broken server.
|
||||
# Everything derivable from the project ref is derived, not stored.
|
||||
- name: Check SaaS configuration
|
||||
id: config
|
||||
env:
|
||||
PROJECT_REF: ${{ secrets.SAAS_DB_PROJECT_REF }}
|
||||
DB_URL: ${{ secrets.SAAS_DB_URL }}
|
||||
DB_PASSWORD: ${{ secrets.SAAS_DB_PASSWORD }}
|
||||
PUBLISHABLE_KEY: ${{ secrets.SAAS_SUPABASE_PUBLISHABLE_KEY }}
|
||||
EDGE_FUNCTION_SECRET: ${{ secrets.SUPABASE_EDGE_FUNCTION_SECRET }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
run: |
|
||||
missing=""
|
||||
[ -z "$PROJECT_REF" ] && missing="$missing SAAS_DB_PROJECT_REF"
|
||||
[ -z "$DB_URL" ] && missing="$missing SAAS_DB_URL"
|
||||
[ -z "$DB_PASSWORD" ] && missing="$missing SAAS_DB_PASSWORD"
|
||||
[ -z "$PUBLISHABLE_KEY" ] && missing="$missing SAAS_SUPABASE_PUBLISHABLE_KEY"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "::warning::Skipping SaaS dev deploy - missing secrets in the saas-dev environment:$missing"
|
||||
echo "ready=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
# Both the Java side and the browser build derive these from the ref,
|
||||
# so storing them separately would only add a way to disagree.
|
||||
echo "supabase_url=https://${PROJECT_REF}.supabase.co" >> "$GITHUB_OUTPUT"
|
||||
echo "meter_endpoint=https://${PROJECT_REF}.supabase.co/functions/v1/meter-payg-units" >> "$GITHUB_OUTPUT"
|
||||
# Backend-only, and it degrades rather than failing: blank means team
|
||||
# invites error out and the PAYG meter no-ops with a WARN. Not worth
|
||||
# blocking a whole deploy over.
|
||||
[ -z "$EDGE_FUNCTION_SECRET" ] \
|
||||
&& echo "::warning::SUPABASE_EDGE_FUNCTION_SECRET not set - team invites and PAYG metering will not work"
|
||||
# AI is optional: without a model key the engine boots but every
|
||||
# request 500s, so deploy the rest and leave the engine out.
|
||||
if [ -n "$ANTHROPIC_API_KEY" ]; then
|
||||
echo "ai=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::ANTHROPIC_API_KEY not set - deploying without the AI engine"
|
||||
echo "ai=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "ready=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
# GHCR with the job token, per #7435: no registry credential is needed and
|
||||
# the Docker Hub token that publishes the release images stays out of here.
|
||||
- name: Login to GitHub Container Registry
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Convert repository owner to lowercase
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
id: repoowner
|
||||
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get commit hash
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
id: commit-hash
|
||||
run: echo "app_short=$(git rev-parse --short=8 HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
# SaaS ships as two images: a backend-only JAR and a separately-built UI.
|
||||
# Each image also gets a moving -latest tag: the per-commit one is the
|
||||
# record of what is deployed, the moving one is what you pull to test
|
||||
# without first looking up a sha.
|
||||
- name: Build and push backend image
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/backend/Dockerfile
|
||||
push: true
|
||||
cache-from: type=gha,scope=stirling-saas-backend
|
||||
cache-to: type=gha,mode=max,scope=stirling-saas-backend
|
||||
tags: |
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-backend-${{ steps.commit-hash.outputs.app_short }}
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-backend-latest
|
||||
build-args: |
|
||||
VERSION_TAG=v2-alpha
|
||||
STIRLING_FLAVOR=saas
|
||||
platforms: linux/amd64
|
||||
|
||||
# The Supabase URL and publishable key are baked in at build time by vite,
|
||||
# so a dev server needs its own frontend image - a prod-built one would
|
||||
# point the browser at the wrong Supabase. Both values are client-safe.
|
||||
- name: Build and push frontend image
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/frontend/Dockerfile
|
||||
push: true
|
||||
cache-from: type=gha,scope=stirling-saas-frontend
|
||||
cache-to: type=gha,mode=max,scope=stirling-saas-frontend
|
||||
tags: |
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-frontend-${{ steps.commit-hash.outputs.app_short }}
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-frontend-latest
|
||||
build-args: |
|
||||
VERSION_TAG=v2-alpha
|
||||
STIRLING_FLAVOR=saas
|
||||
VITE_BUILD_MODE=development
|
||||
VITE_SUPABASE_URL=${{ steps.config.outputs.supabase_url }}
|
||||
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=${{ secrets.SAAS_SUPABASE_PUBLISHABLE_KEY }}
|
||||
platforms: linux/amd64
|
||||
|
||||
# The engine carries no model credentials in its image - they are supplied
|
||||
# per-deployment, so the same image serves dev and prod.
|
||||
- name: Build and push AI engine image
|
||||
if: steps.config.outputs.ready == 'true' && steps.config.outputs.ai == 'true'
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./engine/Dockerfile
|
||||
push: true
|
||||
cache-from: type=gha,scope=stirling-saas-engine
|
||||
cache-to: type=gha,mode=max,scope=stirling-saas-engine
|
||||
tags: |
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-engine-${{ steps.commit-hash.outputs.app_short }}
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-engine-latest
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up SSH
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "$SSH_KEY" > ../private.key
|
||||
sudo chmod 600 ../private.key
|
||||
|
||||
- name: Deploy to VPS
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
env:
|
||||
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
|
||||
IMAGE_TAG: ${{ steps.commit-hash.outputs.app_short }}
|
||||
AI_ENABLED: ${{ steps.config.outputs.ai }}
|
||||
# The VPS pulls from GHCR as this workflow's SSH user, which has no
|
||||
# stored registry credentials of its own. Use the job token rather
|
||||
# than parking a long-lived PAT on the box - it dies with the job.
|
||||
GHCR_USER: ${{ github.actor }}
|
||||
GHCR_TOKEN: ${{ github.token }}
|
||||
VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
|
||||
VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
|
||||
# Read as shell vars rather than interpolated into the script body, so
|
||||
# no secret ever reaches a rendered command line (#7435).
|
||||
SAAS_DB_URL: ${{ secrets.SAAS_DB_URL }}
|
||||
# Fall back explicitly: the property default (${SAAS_DB_USERNAME:postgres})
|
||||
# only applies when the var is unset, and compose would set it empty.
|
||||
SAAS_DB_USERNAME: ${{ secrets.SAAS_DB_USERNAME || 'postgres' }}
|
||||
SAAS_DB_PASSWORD: ${{ secrets.SAAS_DB_PASSWORD }}
|
||||
SAAS_DB_PROJECT_REF: ${{ secrets.SAAS_DB_PROJECT_REF }}
|
||||
SUPABASE_EDGE_FUNCTION_SECRET: ${{ secrets.SUPABASE_EDGE_FUNCTION_SECRET }}
|
||||
PAYG_METER_ENDPOINT: ${{ steps.config.outputs.meter_endpoint }}
|
||||
# Off unless all three are present, in which case the mock licence
|
||||
# service is replaced by real Keygen calls.
|
||||
STIRLING_KEYGEN_ENABLED: ${{ secrets.KEYGEN_ACCOUNT_ID != '' && secrets.KEYGEN_API_TOKEN != '' && secrets.KEYGEN_POLICY_ID != '' }}
|
||||
KEYGEN_ACCOUNT_ID: ${{ secrets.KEYGEN_ACCOUNT_ID }}
|
||||
KEYGEN_API_TOKEN: ${{ secrets.KEYGEN_API_TOKEN }}
|
||||
KEYGEN_POLICY_ID: ${{ secrets.KEYGEN_POLICY_ID }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
# Only needed once RAG is used; the engine boots without it.
|
||||
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Absolute URLs the backend hands out (swagger server, generated links).
|
||||
# <port>.ssl.stirlingpdf.cloud resolves to a separate proxy host that
|
||||
# does not currently reach this box, so point at the instance directly
|
||||
# and switch back once that proxy is repaired.
|
||||
BASE_URL="http://${VPS_HOST}:${FRONTEND_PORT}"
|
||||
|
||||
# Renders a value as a single-quoted YAML scalar that compose will not
|
||||
# touch: '' is the YAML escape for a quote, $$ is compose's escape for
|
||||
# a literal $ (in both v1 and v2). Without this a Supabase password
|
||||
# containing " breaks the YAML and one containing $ is silently
|
||||
# rewritten to a different password - env_file is no safer, compose
|
||||
# interpolates that too.
|
||||
yaml() {
|
||||
printf "'%s'" "$(printf '%s' "$1" | sed -e "s/'/''/g" -e 's/\$/$$/g')"
|
||||
}
|
||||
|
||||
if [ "$AI_ENABLED" = "true" ]; then
|
||||
# The engine image defaults STIRLING_ENGINE_REQUIRE_AUTH=true, and with no
|
||||
# secret its middleware 503s every non-public route while /health keeps
|
||||
# answering - a deploy that looks healthy and fails every AI call. Both
|
||||
# halves read the same env var, and compose rewrites both together, so a
|
||||
# fresh per-deploy value is enough; Java reads it via System.getenv.
|
||||
ENGINE_SECRET="$(openssl rand -hex 32)"
|
||||
AI_BACKEND_VARS="
|
||||
SYSTEM_AIENGINE_ENABLED: \"true\"
|
||||
SYSTEM_AIENGINE_URL: \"http://saas-engine:5001\"
|
||||
# The SaaS AI proxy has its own property; both point at the
|
||||
# same container but neither falls back to the other.
|
||||
APP_AI_SERVICEBASEURL: \"http://saas-engine:5001\"
|
||||
STIRLING_ENGINE_SHARED_SECRET: $(yaml "$ENGINE_SECRET")"
|
||||
AI_SERVICE="
|
||||
|
||||
saas-engine:
|
||||
container_name: stirling-saas-dev-engine
|
||||
image: ${IMAGE_BASE}:saas-engine-${IMAGE_TAG}
|
||||
environment:
|
||||
ANTHROPIC_API_KEY: $(yaml "$ANTHROPIC_API_KEY")
|
||||
VOYAGE_API_KEY: $(yaml "$VOYAGE_API_KEY")
|
||||
STIRLING_ENGINE_SHARED_SECRET: $(yaml "$ENGINE_SECRET")
|
||||
restart: on-failure:5"
|
||||
else
|
||||
AI_BACKEND_VARS=""
|
||||
AI_SERVICE=""
|
||||
fi
|
||||
|
||||
cat > docker-compose.yml << EOF
|
||||
version: '3.3'
|
||||
services:
|
||||
saas-backend:
|
||||
container_name: stirling-saas-dev-backend
|
||||
image: ${IMAGE_BASE}:saas-backend-${IMAGE_TAG}
|
||||
ports:
|
||||
- "${BACKEND_PORT}:8080"
|
||||
volumes:
|
||||
- ${DEPLOY_DIR}/config:/configs:rw
|
||||
- ${DEPLOY_DIR}/logs:/logs:rw
|
||||
- ${DEPLOY_DIR}/storage:/storage:rw
|
||||
environment:
|
||||
# Nothing activates the saas profile implicitly - every SaaS
|
||||
# bean is @Profile("saas"), so this has to be explicit.
|
||||
SPRING_PROFILES_ACTIVE: "saas"
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SAAS_DB_URL: $(yaml "$SAAS_DB_URL")
|
||||
SAAS_DB_USERNAME: $(yaml "$SAAS_DB_USERNAME")
|
||||
SAAS_DB_PASSWORD: $(yaml "$SAAS_DB_PASSWORD")
|
||||
SAAS_DB_PROJECT_REF: $(yaml "$SAAS_DB_PROJECT_REF")
|
||||
SUPABASE_EDGE_FUNCTION_SECRET: $(yaml "$SUPABASE_EDGE_FUNCTION_SECRET")
|
||||
PAYG_METER_ENDPOINT: $(yaml "$PAYG_METER_ENDPOINT")
|
||||
STIRLING_KEYGEN_ENABLED: $(yaml "$STIRLING_KEYGEN_ENABLED")
|
||||
KEYGEN_ACCOUNT_ID: $(yaml "$KEYGEN_ACCOUNT_ID")
|
||||
KEYGEN_API_TOKEN: $(yaml "$KEYGEN_API_TOKEN")
|
||||
KEYGEN_POLICY_ID: $(yaml "$KEYGEN_POLICY_ID")
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||
SWAGGER_SERVER_URL: "${BASE_URL}"
|
||||
baseUrl: "${BASE_URL}"${AI_BACKEND_VARS}
|
||||
restart: on-failure:5
|
||||
|
||||
saas-frontend:
|
||||
container_name: stirling-saas-dev-frontend
|
||||
image: ${IMAGE_BASE}:saas-frontend-${IMAGE_TAG}
|
||||
ports:
|
||||
- "${FRONTEND_PORT}:80"
|
||||
environment:
|
||||
# Not browser-facing: the frontend image's entrypoint substitutes
|
||||
# this into its own nginx as the /api proxy_pass upstream, so the
|
||||
# browser only ever talks to this container. Keep it on the
|
||||
# compose network - routing it through a public hostname would
|
||||
# send every API call out to the internet and back, and tie the
|
||||
# deploy to a proxy and a public IP it does not need.
|
||||
VITE_API_BASE_URL: "http://saas-backend:8080"
|
||||
depends_on:
|
||||
- saas-backend
|
||||
restart: on-failure:5${AI_SERVICE}
|
||||
EOF
|
||||
|
||||
SSH_OPTS=(-i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
|
||||
|
||||
scp "${SSH_OPTS[@]}" docker-compose.yml "${VPS_USERNAME}@${VPS_HOST}:/tmp/saas-dev-docker-compose.yml"
|
||||
|
||||
ssh "${SSH_OPTS[@]}" -T "${VPS_USERNAME}@${VPS_HOST}" << ENDSSH
|
||||
set -e
|
||||
mkdir -p ${DEPLOY_DIR}/{config,logs,storage}
|
||||
mv /tmp/saas-dev-docker-compose.yml ${DEPLOY_DIR}/docker-compose.yml
|
||||
# The compose file carries the credentials, so it is not world-readable.
|
||||
chmod 600 ${DEPLOY_DIR}/docker-compose.yml
|
||||
cd ${DEPLOY_DIR}
|
||||
printf '%s' "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USER}" --password-stdin
|
||||
docker-compose down --remove-orphans 2>/dev/null || true
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
# The token expires with the job; do not leave it on disk.
|
||||
docker logout ghcr.io >/dev/null 2>&1 || true
|
||||
# Images only - --volumes would take the other deployments' data.
|
||||
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
|
||||
ENDSSH
|
||||
|
||||
- name: Wait for the backend to answer
|
||||
if: steps.config.outputs.ready == 'true'
|
||||
env:
|
||||
VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
|
||||
run: |
|
||||
URL="http://${VPS_HOST}:${BACKEND_PORT}/api/v1/info/status"
|
||||
for i in $(seq 1 60); do
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$URL" || true)
|
||||
if [ "$code" = "200" ]; then echo "Healthy after $((i * 10))s"; exit 0; fi
|
||||
sleep 10
|
||||
done
|
||||
echo "::error::SaaS dev backend did not become healthy within 10 minutes"
|
||||
exit 1
|
||||
|
||||
- name: Cleanup temporary files
|
||||
if: always()
|
||||
run: rm -f ../private.key docker-compose.yml
|
||||
continue-on-error: true
|
||||
Reference in New Issue
Block a user