Some checks failed
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
207 lines
9.0 KiB
YAML
207 lines
9.0 KiB
YAML
# .gitea/workflows/promote.yml
|
||
# Manual promotion workflow to copy staged artefacts to production
|
||
|
||
name: Promote Feedser (Manual)
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
include_docs:
|
||
description: 'Also promote the generated documentation bundle'
|
||
required: false
|
||
default: 'true'
|
||
type: boolean
|
||
tag:
|
||
description: 'Optional build identifier to record in the summary'
|
||
required: false
|
||
default: 'latest'
|
||
type: string
|
||
|
||
jobs:
|
||
promote:
|
||
runs-on: ubuntu-22.04
|
||
environment: production
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Resolve staging credentials
|
||
id: staging
|
||
run: |
|
||
missing=()
|
||
|
||
host="${{ secrets.STAGING_DEPLOYMENT_HOST }}"
|
||
if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi
|
||
if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
|
||
if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
|
||
if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi
|
||
|
||
user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}"
|
||
if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi
|
||
if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
|
||
if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
|
||
if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi
|
||
|
||
path="${{ secrets.STAGING_DEPLOYMENT_PATH }}"
|
||
if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi
|
||
if [ -z "$path" ]; then missing+=("STAGING_DEPLOYMENT_PATH")
|
||
fi
|
||
|
||
docs_path="${{ secrets.STAGING_DOCS_PATH }}"
|
||
if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi
|
||
|
||
key="${{ secrets.STAGING_DEPLOYMENT_KEY }}"
|
||
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
|
||
if [ -z "$key" ]; then key="${{ vars.STAGING_DEPLOYMENT_KEY }}"; fi
|
||
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
|
||
if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi
|
||
|
||
if [ ${#missing[@]} -gt 0 ]; then
|
||
echo "❌ Missing staging configuration: ${missing[*]}"
|
||
exit 1
|
||
fi
|
||
|
||
key_file="$RUNNER_TEMP/staging_key"
|
||
printf '%s\n' "$key" > "$key_file"
|
||
chmod 600 "$key_file"
|
||
|
||
echo "host=$host" >> $GITHUB_OUTPUT
|
||
echo "user=$user" >> $GITHUB_OUTPUT
|
||
echo "path=$path" >> $GITHUB_OUTPUT
|
||
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
|
||
echo "key-file=$key_file" >> $GITHUB_OUTPUT
|
||
|
||
- name: Resolve production credentials
|
||
id: production
|
||
run: |
|
||
missing=()
|
||
|
||
host="${{ secrets.PRODUCTION_DEPLOYMENT_HOST }}"
|
||
if [ -z "$host" ]; then host="${{ vars.PRODUCTION_DEPLOYMENT_HOST }}"; fi
|
||
if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
|
||
if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
|
||
if [ -z "$host" ]; then missing+=("PRODUCTION_DEPLOYMENT_HOST"); fi
|
||
|
||
user="${{ secrets.PRODUCTION_DEPLOYMENT_USERNAME }}"
|
||
if [ -z "$user" ]; then user="${{ vars.PRODUCTION_DEPLOYMENT_USERNAME }}"; fi
|
||
if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
|
||
if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
|
||
if [ -z "$user" ]; then missing+=("PRODUCTION_DEPLOYMENT_USERNAME"); fi
|
||
|
||
path="${{ secrets.PRODUCTION_DEPLOYMENT_PATH }}"
|
||
if [ -z "$path" ]; then path="${{ vars.PRODUCTION_DEPLOYMENT_PATH }}"; fi
|
||
if [ -z "$path" ]; then missing+=("PRODUCTION_DEPLOYMENT_PATH")
|
||
fi
|
||
|
||
docs_path="${{ secrets.PRODUCTION_DOCS_PATH }}"
|
||
if [ -z "$docs_path" ]; then docs_path="${{ vars.PRODUCTION_DOCS_PATH }}"; fi
|
||
|
||
key="${{ secrets.PRODUCTION_DEPLOYMENT_KEY }}"
|
||
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
|
||
if [ -z "$key" ]; then key="${{ vars.PRODUCTION_DEPLOYMENT_KEY }}"; fi
|
||
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
|
||
if [ -z "$key" ]; then missing+=("PRODUCTION_DEPLOYMENT_KEY"); fi
|
||
|
||
if [ ${#missing[@]} -gt 0 ]; then
|
||
echo "❌ Missing production configuration: ${missing[*]}"
|
||
exit 1
|
||
fi
|
||
|
||
key_file="$RUNNER_TEMP/production_key"
|
||
printf '%s\n' "$key" > "$key_file"
|
||
chmod 600 "$key_file"
|
||
|
||
echo "host=$host" >> $GITHUB_OUTPUT
|
||
echo "user=$user" >> $GITHUB_OUTPUT
|
||
echo "path=$path" >> $GITHUB_OUTPUT
|
||
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
|
||
echo "key-file=$key_file" >> $GITHUB_OUTPUT
|
||
|
||
- name: Install rsync
|
||
run: |
|
||
if command -v rsync >/dev/null 2>&1; then
|
||
exit 0
|
||
fi
|
||
CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt"
|
||
mkdir -p "$CACHE_DIR"
|
||
KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)"
|
||
DEB_DIR="$CACHE_DIR/$KEY"
|
||
mkdir -p "$DEB_DIR"
|
||
if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then
|
||
apt-get update
|
||
apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb
|
||
else
|
||
apt-get update
|
||
apt-get download rsync libpopt0
|
||
mv rsync*.deb libpopt0*.deb "$DEB_DIR"/
|
||
dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y
|
||
fi
|
||
|
||
- name: Fetch staging artefacts
|
||
id: fetch
|
||
run: |
|
||
staging_root="${{ runner.temp }}/staging"
|
||
mkdir -p "$staging_root/service" "$staging_root/docs"
|
||
|
||
echo "📥 Copying service bundle from staging"
|
||
rsync -az --delete \
|
||
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs.path }}/" \
|
||
"$staging_root/service/"
|
||
|
||
if [ "${{ github.event.inputs.include_docs }}" = "true" ] && [ -n "${{ steps.staging.outputs['docs-path'] }}" ]; then
|
||
echo "📥 Copying documentation bundle from staging"
|
||
rsync -az --delete \
|
||
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs['docs-path'] }}/" \
|
||
"$staging_root/docs/"
|
||
else
|
||
echo "ℹ️ Documentation promotion skipped"
|
||
fi
|
||
|
||
echo "service-dir=$staging_root/service" >> $GITHUB_OUTPUT
|
||
echo "docs-dir=$staging_root/docs" >> $GITHUB_OUTPUT
|
||
|
||
- name: Backup production service content
|
||
run: |
|
||
ssh -o StrictHostKeyChecking=no -i "${{ steps.production.outputs['key-file'] }}" \
|
||
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}" \
|
||
"set -e; TARGET='${{ steps.production.outputs.path }}'; \
|
||
if [ -d \"$TARGET\" ]; then \
|
||
parent=\$(dirname \"$TARGET\"); \
|
||
base=\$(basename \"$TARGET\"); \
|
||
backup=\"\$parent/\${base}.backup.\$(date +%Y%m%d_%H%M%S)\"; \
|
||
mkdir -p \"\$backup\"; \
|
||
rsync -a --delete \"$TARGET/\" \"\$backup/\"; \
|
||
ls -dt \"\$parent/\${base}.backup.*\" 2>/dev/null | tail -n +6 | xargs rm -rf || true; \
|
||
echo 'Backup created at ' \"\$backup\"; \
|
||
else \
|
||
echo 'Production service path missing; skipping backup'; \
|
||
fi"
|
||
|
||
- name: Publish service to production
|
||
run: |
|
||
rsync -az --delete \
|
||
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||
"${{ steps.fetch.outputs['service-dir'] }}/" \
|
||
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs.path }}/"
|
||
|
||
- name: Promote documentation bundle
|
||
if: github.event.inputs.include_docs == 'true' && steps.production.outputs['docs-path'] != ''
|
||
run: |
|
||
rsync -az --delete \
|
||
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||
"${{ steps.fetch.outputs['docs-dir'] }}/" \
|
||
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs['docs-path'] }}/"
|
||
|
||
- name: Promotion summary
|
||
run: |
|
||
echo "✅ Promotion completed"
|
||
echo " Tag: ${{ github.event.inputs.tag }}"
|
||
echo " Service: ${{ steps.staging.outputs.host }} → ${{ steps.production.outputs.host }}"
|
||
if [ "${{ github.event.inputs.include_docs }}" = "true" ]; then
|
||
echo " Docs: included"
|
||
else
|
||
echo " Docs: skipped"
|
||
fi
|