mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
Four merges to develop this morning left five superseded runs building dead
commits while the branch head waited. Applied one policy, per class:
- build.yml: cancel-in-progress was `github.event_name == 'pull_request'`, so
branch pushes never superseded each other — every merge started its own 3-h
compile. Now true for all events; it writes nothing outside the run.
- release-{demo,stage,prod}: made the release ATOMIC so it becomes safe to
supersede. `github-tag-action` now runs with dry_run (calculate only) and
`ncipollo/release-action` creates the tag AND the release in one call with
`commit: github.sha`. Previously the tag was pushed by an earlier step, so a
cancelled run stranded a tag with no release forever (the next run computes the
NEXT version and never backfills). With that window gone: cancel-in-progress true.
- 6 non-prod image builds: now supersede within their own workflow+ref, matching
what the prod image builds already did. Group stays per-workflow — the shared
`gauzy-docker-build` group is what starved them before. Trade-off documented in
the file: an intermediate version tag may end up without an image, which is inert
because demo/stage deploy from :latest.
- 26 deploy workflows had NO concurrency key at all, so every merge queued its own
deploy. They now share a group per workflow+ref with cancel-in-progress FALSE:
superseded PENDING runs collapse to the newest, but a rollout already applying is
never interrupted.
Left alone deliberately: cache-janitor and external-uptime-monitor (cron probes,
must not cancel each other) and harvest-secrets-to-openbao (manual only).
Every file parses as YAML; the classification came from reading each workflow's
steps for external side effects, not from its name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
212 lines
11 KiB
YAML
212 lines
11 KiB
YAML
name: Deploy to DigitalOcean Droplet Demo
|
|
|
|
on:
|
|
workflow_run:
|
|
workflows: ['Deploy to DigitalOcean Droplet Demo Pre']
|
|
branches:
|
|
- droplets
|
|
types:
|
|
- completed
|
|
|
|
# Least-privilege scope for the automatic GITHUB_TOKEN.
|
|
# This workflow only builds/tests/deploys from a checkout — read access is sufficient.
|
|
concurrency:
|
|
# Collapse superseded runs WITHOUT interrupting one that is already applying: GitHub
|
|
# keeps a single pending run per group, so a newer commit replaces the one waiting
|
|
# while the in-flight deploy finishes. `cancel-in-progress: true` is deliberately NOT
|
|
# used here — killing a rollout mid-apply can leave the target half-updated.
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
deploy:
|
|
if: ${{ vars.DO_ENABLED == 'true' }}
|
|
runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }}
|
|
timeout-minutes: 300
|
|
|
|
environment: demo
|
|
|
|
steps:
|
|
- name: checkout out code
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Modify API_BASE_URL
|
|
run: |
|
|
echo "INGRESS_CERT_TYPE=${{ env.INGRESS_CERT_TYPE }}" >> $GITHUB_ENV
|
|
|
|
if [ "${{ env.INGRESS_CERT_TYPE }}" = "cloudflare" ]; then
|
|
echo "API_BASE_URL=https://apidemodt.gauzy.co" >> $GITHUB_ENV
|
|
elif [ "${{ env.INGRESS_CERT_TYPE }}" = "letsencrypt" ]; then
|
|
echo "API_BASE_URL=https://apidemodts.gauzy.co" >> $GITHUB_ENV
|
|
else
|
|
echo "UNKNOWN INGRESS_CERT_TYPE"
|
|
fi
|
|
env:
|
|
INGRESS_CERT_TYPE: 'letsencrypt'
|
|
|
|
- name: Inject secrets into .env-template.compose
|
|
run: |
|
|
if [ "${{ env.INGRESS_CERT_TYPE }}" = "cloudflare" ]; then
|
|
envsubst < $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.template.yml > temp.yaml && mv temp.yaml $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml
|
|
touch $GITHUB_WORKSPACE/.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml
|
|
elif [ "${{ env.INGRESS_CERT_TYPE }}" = "letsencrypt" ]; then
|
|
envsubst < $GITHUB_WORKSPACE/.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.template.yml > temp.yaml && mv temp.yaml $GITHUB_WORKSPACE/.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml
|
|
touch $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml
|
|
else
|
|
echo "UNKNOWN INGRESS_CERT_TYPE"
|
|
fi
|
|
env:
|
|
ENV_NAME: 'demo'
|
|
DEMO: 'true'
|
|
NODE_ENV: 'development'
|
|
ADMIN_PASSWORD_RESET: 'true'
|
|
API_HOST: $API_HOST
|
|
API_BASE_URL: '${{ env.API_BASE_URL }}'
|
|
CLIENT_BASE_URL: 'https://demo.gauzy.co'
|
|
DB_TYPE: '${{ secrets.DB_TYPE }}'
|
|
DB_URI: '${{ secrets.DB_URI }}'
|
|
DB_HOST: '${{ secrets.DB_HOST }}'
|
|
DB_USER: '${{ secrets.DB_USER }}'
|
|
DB_PASS: '${{ secrets.DB_PASS }}'
|
|
DB_NAME: '${{ secrets.DB_NAME }}'
|
|
DB_PORT: '${{ secrets.DB_PORT }}'
|
|
DB_CA_CERT: '${{ secrets.DB_CA_CERT }}'
|
|
DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}'
|
|
DB_POOL_SIZE: '${{ secrets.DB_POOL_SIZE }}'
|
|
DB_POOL_SIZE_KNEX: '${{ secrets.DB_POOL_SIZE_KNEX }}'
|
|
REDIS_ENABLED: '${{ secrets.REDIS_ENABLED }}'
|
|
REDIS_URL: '${{ secrets.REDIS_URL }}'
|
|
REDIS_HOST: '${{ secrets.REDIS_HOST }}'
|
|
REDIS_PASSWORD: '${{ secrets.REDIS_PASSWORD }}'
|
|
REDIS_PORT: '${{ secrets.REDIS_PORT }}'
|
|
REDIS_USER: '${{ secrets.REDIS_USER }}'
|
|
REDIS_TLS: '${{ secrets.REDIS_TLS }}'
|
|
CLOUD_PROVIDER: 'DO'
|
|
SENTRY_DSN: '${{ secrets.SENTRY_DSN }}'
|
|
SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}'
|
|
SENTRY_PROFILE_SAMPLE_RATE: '${{ secrets.SENTRY_PROFILE_SAMPLE_RATE }}'
|
|
SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}'
|
|
SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}'
|
|
SENTRY_PROFILING_ENABLED: '${{ secrets.SENTRY_PROFILING_ENABLED }}'
|
|
AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'
|
|
AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}'
|
|
AWS_REGION: '${{ secrets.AWS_REGION }}'
|
|
AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}'
|
|
WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}'
|
|
WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}'
|
|
WASABI_REGION: '${{ secrets.WASABI_REGION }}'
|
|
WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}'
|
|
WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}'
|
|
WASABI_S3_FORCE_PATH_STYLE: '${{ secrets.WASABI_S3_FORCE_PATH_STYLE }}'
|
|
DIGITALOCEAN_ACCESS_KEY_ID: '${{ secrets.DIGITALOCEAN_ACCESS_KEY_ID }}'
|
|
DIGITALOCEAN_SECRET_ACCESS_KEY: '${{ secrets.DIGITALOCEAN_SECRET_ACCESS_KEY }}'
|
|
DIGITALOCEAN_REGION: '${{ secrets.DIGITALOCEAN_REGION }}'
|
|
DIGITALOCEAN_SERVICE_URL: '${{ secrets.DIGITALOCEAN_SERVICE_URL }}'
|
|
DIGITALOCEAN_CDN_URL: '${{ secrets.DIGITALOCEAN_CDN_URL }}'
|
|
DIGITALOCEAN_S3_BUCKET: '${{ secrets.DIGITALOCEAN_S3_BUCKET }}'
|
|
DIGITALOCEAN_S3_FORCE_PATH_STYLE: '${{ secrets.DIGITALOCEAN_S3_FORCE_PATH_STYLE }}'
|
|
EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}'
|
|
JWT_SECRET: '${{ secrets.JWT_SECRET }}'
|
|
JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}'
|
|
JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}'
|
|
ALLOWED_ORIGINS: '${{ secrets.ALLOWED_ORIGINS }}'
|
|
CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}'
|
|
CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}'
|
|
CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}'
|
|
CHATWOOT_SDK_TOKEN: '${{ secrets.CHATWOOT_SDK_TOKEN }}'
|
|
GOOGLE_MAPS_API_KEY: '${{ secrets.GOOGLE_MAPS_API_KEY }}'
|
|
MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}'
|
|
MAIL_HOST: '${{ secrets.MAIL_HOST }}'
|
|
MAIL_PORT: '${{ secrets.MAIL_PORT }}'
|
|
MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}'
|
|
MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}'
|
|
ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}'
|
|
GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}'
|
|
GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}'
|
|
GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}'
|
|
FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}'
|
|
FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}'
|
|
FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}'
|
|
FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}'
|
|
INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}'
|
|
UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}'
|
|
FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}'
|
|
GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}'
|
|
GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}'
|
|
UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}'
|
|
UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}'
|
|
UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}'
|
|
UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}'
|
|
UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}'
|
|
UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}'
|
|
JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}'
|
|
JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}'
|
|
OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}'
|
|
OTEL_PROVIDER: '${{ secrets.OTEL_PROVIDER }}'
|
|
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}'
|
|
OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}'
|
|
GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}'
|
|
GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}'
|
|
GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}'
|
|
GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}'
|
|
GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}'
|
|
GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}'
|
|
GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}'
|
|
GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}'
|
|
GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}'
|
|
GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}'
|
|
GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}'
|
|
GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}'
|
|
JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}'
|
|
JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}'
|
|
MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}'
|
|
APP_NAME: '${{ secrets.APP_NAME }}'
|
|
APP_LOGO: '${{ secrets.APP_LOGO }}'
|
|
APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}'
|
|
APP_LINK: '${{ secrets.APP_LINK }}'
|
|
APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}'
|
|
APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}'
|
|
COMPANY_LINK: '${{ secrets.COMPANY_LINK }}'
|
|
COMPANY_NAME: '${{ secrets.COMPANY_NAME }}'
|
|
FEATURE_OPEN_STATS: '${{ vars.FEATURE_OPEN_STATS }}'
|
|
|
|
- name: Copy file via scp - with-cloudflare
|
|
if: ${{ env.INGRESS_CERT_TYPE == 'cloudflare' }}
|
|
uses: appleboy/scp-action@master
|
|
with:
|
|
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
|
|
username: ${{secrets.DO_DROPLET_USERNAME}}
|
|
key: ${{secrets.DO_DROPLET_KEY}}
|
|
source: '.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml'
|
|
target: '.'
|
|
- name: Copy file via scp - with-letsencrypt
|
|
if: ${{ env.INGRESS_CERT_TYPE == 'letsencrypt' }}
|
|
uses: appleboy/scp-action@master
|
|
with:
|
|
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
|
|
username: ${{secrets.DO_DROPLET_USERNAME}}
|
|
key: ${{secrets.DO_DROPLET_KEY}}
|
|
source: '.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml'
|
|
target: '.'
|
|
- name: Deploy to DigitalOcean Droplet
|
|
uses: appleboy/ssh-action@master
|
|
with:
|
|
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
|
|
username: ${{secrets.DO_DROPLET_USERNAME}}
|
|
key: ${{secrets.DO_DROPLET_KEY}}
|
|
envs: INGRESS_CERT_TYPE
|
|
script: |
|
|
if [ "${{ env.INGRESS_CERT_TYPE }}" = "cloudflare" ]; then
|
|
docker-compose -f .deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml down
|
|
docker-compose -f .deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml up -d
|
|
elif [ "${{ env.INGRESS_CERT_TYPE }}" = "letsencrypt" ]; then
|
|
docker-compose -f .deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml down
|
|
docker-compose -f .deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml up -d
|
|
else
|
|
echo "Unknown INGRESS_CERT_TYPE: $INGRESS_CERT_TYPE"
|
|
exit 1
|
|
fi
|