Deployment (Universal) #243
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Deployment (Universal) | |
on: | |
workflow_dispatch: | |
inputs: | |
environment: | |
description: 'Environment to deploy to' | |
required: true | |
default: 'stage' | |
type: choice | |
options: ['stage', 'prod'] | |
delete-volumes: | |
description: 'Delete volumes?' | |
type: boolean | |
default: false | |
restore-db: | |
description: 'Restore DB?' | |
type: boolean | |
default: false | |
manual-sql: | |
description: 'SQL intervention file path' | |
type: string | |
default: '' | |
reindex-elastic: | |
description: 'Reindex elasticsearch?' | |
type: boolean | |
default: false | |
flush-redis: | |
description: 'Clear redis?' | |
type: boolean | |
default: false | |
build-flag: | |
type: choice | |
description: docker compose additional build flag | |
default: "None" | |
options: | |
- "None" | |
- "--no-cache" | |
jobs: | |
info: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Info | |
run: | | |
if [ "${{ github.event.inputs.environment }}" = "prod" ]; then | |
echo "::warning::Deploying to PRODUCTION!" | |
elif [ "${{ github.event.inputs.environment }}" = "stage" ]; then | |
echo "::notice::Deploying to STAGE" | |
fi | |
# warn if flags are set | |
if [ "${{ github.event.inputs.delete-volumes }}" = "true" ]; then | |
echo "::warning::Deleting volumes!" | |
fi | |
if [ "${{ github.event.inputs.restore-db }}" = "true" ]; then | |
echo "::warning::Restoring DB!" | |
fi | |
if [ "${{ github.event.inputs.manual-sql }}" != "" ]; then | |
echo "::warning::SQL to be run: ${{ github.event.inputs.manual-sql }}" | |
fi | |
if [ "${{ github.event.inputs.flush-redis }}" = "true" ]; then | |
echo "::warning::Flushing redis!" | |
fi | |
if [ "${{ github.event.inputs.reindex-elastic }}" = "true" ]; then | |
echo "::warning::Reindexing elasticsearch!" | |
fi | |
if [ "${{ github.event.inputs.build-flag }}" != "None" ]; then | |
echo "::notice::Build flag: ${{ github.event.inputs.build-flag }}" | |
fi | |
deployment: | |
needs: info | |
concurrency: ${{ github.event.inputs.environment }} | |
runs-on: [self-hosted, "${{ github.event.inputs.environment }}"] | |
environment: ${{ github.event.inputs.environment == 'stage' && 'Staging' || 'Production' }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v3 | |
- name: Verify environment | |
run: | | |
(diff -wB <(grep -vE '^\s*#' /opt/deployment-specific-files/.env | cut -d '=' -f 1 | sort) \ | |
<(grep -vE '^\s*#' .env.sample | cut -d '=' -f 1 | sort)) ||\ | |
(echo "Environment variables mismatch" &&\ | |
echo "Update /opt/deployment-specific-files/.env on the host or .env.sample in your branch" &&\ | |
exit 1) | |
- name: Verify manual interventions | |
if: github.event.inputs.manual-sql != '' | |
run: | | |
if [ ! -r "${{ github.event.inputs.manual-sql }}" ]; then | |
echo "File ${{ github.event.inputs.manual-sql }} does not exist or is not readable" | |
exit 1 | |
fi | |
- name: Copy configuration | |
run: | | |
cp -f /opt/deployment-specific-files/chain.pem ./caddy/certs | |
cp -f /opt/deployment-specific-files/priv.pem ./caddy/certs | |
cp -f /opt/deployment-specific-files/.env ./ | |
# remove any FINGERPRINT= lines from .env | |
sed -i '/^FINGERPRINT=/d' .env | |
# add FINGERPRINT to .env | |
echo "FINGERPRINT=$(date +%s%N)$RANDOM" >> .env | |
- name: Build | |
env: | |
# replace None with an empty string | |
BUILD_FLAG: ${{ github.event.inputs.build-flag != 'None' && github.event.inputs.build-flag || '' }} | |
run: docker compose -f docker-compose.ssl.local.yml build $BUILD_FLAG | |
- name: Download DB dump | |
id: get-db | |
if: github.event.inputs.restore-db == 'true' | |
uses: ./.github/actions/get-db | |
with: | |
# taking the dump file from different workflows: full dump for prod and sanitized for stage | |
workflow: ${{ github.event.inputs.environment == 'stage' && 'restore-sanitized-db.yml' || 'backup-db.yml' }} | |
zip-password: ${{ github.event.inputs.environment == 'stage' && secrets.STAGING_BACKUP_PASSWORD || secrets.BACKUP_PASSWORD }} | |
- name: Copy everything to /orbitar | |
run: | | |
mkdir -p /orbitar | |
# Include dot files in * | |
shopt -s dotglob | |
rm -rf /orbitar/* | |
cp -r ./* /orbitar | |
shopt -u dotglob | |
- name: Delete volumes | |
if: github.event.inputs.delete-volumes == 'true' | |
working-directory: /orbitar | |
run: docker compose -f docker-compose.ssl.local.yml down -v | |
- name: Restore DB | |
if: github.event.inputs.restore-db == 'true' | |
working-directory: /orbitar | |
env: | |
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }} | |
run: | | |
docker compose -f docker-compose.ssl.local.yml stop backend | |
docker compose -f docker-compose.ssl.local.yml up -d mysql --wait | |
docker compose -f docker-compose.ssl.local.yml exec -T mysql /usr/bin/mysql \ | |
-u root --password=$MYSQL_ROOT_PASSWORD --default-character-set=utf8mb4 \ | |
< ${{ steps.get-db.outputs.db-dump-file }} | |
- name: Run manual SQL interventions | |
working-directory: /orbitar | |
if: github.event.inputs.manual-sql != '' | |
env: | |
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }} | |
run: | | |
set -ex | |
docker compose -f docker-compose.ssl.local.yml stop backend | |
docker compose -f docker-compose.ssl.local.yml up -d mysql --wait | |
docker compose -f docker-compose.ssl.local.yml exec -T mysql /usr/bin/mysql \ | |
-u root --password=$MYSQL_ROOT_PASSWORD --default-character-set=utf8mb4 orbitar_db \ | |
< ${{ github.event.inputs.manual-sql }} | |
- name: Reindex ElasticSearch | |
if: github.event.inputs.reindex-elastic == 'true' | |
working-directory: /orbitar | |
env: | |
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }} | |
run: | | |
docker compose -f docker-compose.ssl.local.yml up -d mysql --wait | |
echo "update orbitar_db.content_source set indexed = 0;" | \ | |
docker compose -f docker-compose.ssl.local.yml exec -T mysql /usr/bin/mysql -u root --password=$MYSQL_ROOT_PASSWORD | |
- name: Flush redis | |
if: github.event.inputs.flush-redis == 'true' | |
working-directory: /orbitar | |
env: | |
REDIS_PASSWORD: orbitar | |
run: | | |
docker compose -f docker-compose.ssl.local.yml stop backend | |
docker compose -f docker-compose.ssl.local.yml up -d redis --wait | |
docker compose -f docker-compose.ssl.local.yml exec -e REDISCLI_AUTH=$REDIS_PASSWORD redis redis-cli FLUSHALL | |
- name: Deploy | |
working-directory: /orbitar | |
run: docker compose -f docker-compose.ssl.local.yml up -d | |
- name: Purge Cloudflare Cache | |
env: | |
CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }} | |
CLOUDFLARE_BEARER_TOKEN: ${{ secrets.CLOUDFLARE_BEARER_TOKEN }} | |
if: env.CLOUDFLARE_ZONE != '' && env.CLOUDFLARE_BEARER_TOKEN != '' | |
run: | | |
curl --request POST \ | |
--url https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE }}/purge_cache \ | |
--header 'Content-Type: application/json' \ | |
--header 'Authorization: Bearer ${{ secrets.CLOUDFLARE_BEARER_TOKEN }}' \ | |
--data '{ | |
"purge_everything": true | |
}' |