Skip to content

Deployment (Universal) #242

Deployment (Universal)

Deployment (Universal) #242

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@v4
- 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.yml -f docker-compose.deploy.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.yml -f docker-compose.deploy.yml -f docker-compose.deploy.ssl.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.yml -f docker-compose.deploy.yml stop backend
docker compose -f docker-compose.yml -f docker-compose.deploy.yml up -d mysql --wait
docker compose -f docker-compose.yml -f docker-compose.deploy.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.yml -f docker-compose.deploy.yml stop backend
docker compose -f docker-compose.yml -f docker-compose.deploy.yml up -d mysql --wait
docker compose -f docker-compose.yml -f docker-compose.deploy.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.yml -f docker-compose.deploy.yml up -d mysql --wait
echo "update orbitar_db.content_source set indexed = 0;" | \
docker compose -f docker-compose.yml -f docker-compose.deploy.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.yml -f docker-compose.deploy.yml stop backend
docker compose -f docker-compose.yml -f docker-compose.deploy.yml up -d redis --wait
docker compose -f docker-compose.yml -f docker-compose.deploy.yml exec -e REDISCLI_AUTH=$REDIS_PASSWORD redis redis-cli FLUSHALL
- name: Deploy
working-directory: /orbitar
run: docker compose -f docker-compose.yml -f docker-compose.deploy.yml -f docker-compose.deploy.ssl.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
}'