Files
klz-cables.com/.gitea/workflows/deploy.yml

243 lines
11 KiB
YAML

name: Build & Deploy
on:
push:
branches:
- '**'
tags:
- 'v*'
workflow_dispatch:
inputs:
skip_checks:
description: 'Skip tests? (true/false)'
required: false
default: 'false'
concurrency:
group: ${{ github.workflow }}-${{ (github.ref_type == 'tag' && !contains(github.ref_name, '-')) && 'prod' || (github.ref_name == 'main' && 'testing' || github.ref_name) }}
cancel-in-progress: true
jobs:
# ──────────────────────────────────────────────────────────────────────────────
# JOB 1: Prepare Environment
# ──────────────────────────────────────────────────────────────────────────────
prepare:
name: 🔍 Prepare
runs-on: docker
outputs:
target: ${{ steps.determine.outputs.target }}
image_tag: ${{ steps.determine.outputs.image_tag }}
env_file: ${{ steps.determine.outputs.env_file }}
traefik_host: ${{ steps.determine.outputs.traefik_host }}
traefik_rule: ${{ steps.determine.outputs.traefik_rule }}
next_public_url: ${{ steps.determine.outputs.next_public_url }}
directus_url: ${{ steps.determine.outputs.directus_url }}
project_name: ${{ steps.determine.outputs.project_name }}
short_sha: ${{ steps.determine.outputs.short_sha }}
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: 🔍 Environment ermitteln
id: determine
shell: bash
run: |
REF="${{ github.ref_name }}"
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
DOMAIN="klz-cables.com"
PRJ="klz"
if [[ "${{ github.ref_type }}" == "branch" && "$REF" == "main" ]]; then
TARGET="testing"
IMAGE_TAG="main-${SHORT_SHA}"
ENV_FILE=".env.testing"
TRAEFIK_HOST="testing.${DOMAIN}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
if [[ "$REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TARGET="production"
IMAGE_TAG="$REF"
ENV_FILE=".env.prod"
TRAEFIK_HOST="${DOMAIN}, www.${DOMAIN}"
else
TARGET="staging"
IMAGE_TAG="$REF"
ENV_FILE=".env.staging"
TRAEFIK_HOST="staging.${DOMAIN}"
fi
else
TARGET="branch"
SLUG=$(echo "$REF" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
IMAGE_TAG="branch-${SLUG}-${SHORT_SHA}"
ENV_FILE=".env.branch-${SLUG}"
TRAEFIK_HOST="${SLUG}.branch.mintel.me"
fi
# Standardize Traefik Rule
if [[ "$TRAEFIK_HOST" == *","* ]]; then
TRAEFIK_RULE=$(echo "$TRAEFIK_HOST" | sed 's/,/ /g' | awk '{for(i=1;i<=NF;i++) printf "Host(`%s`)%s", $i, (i==NF?"":" || ")}')
PRIMARY_HOST=$(echo "$TRAEFIK_HOST" | cut -d',' -f1 | sed 's/ //g')
else
TRAEFIK_RULE="Host(\`$TRAEFIK_HOST\`)"
PRIMARY_HOST="$TRAEFIK_HOST"
fi
{
echo "target=$TARGET"
echo "image_tag=$IMAGE_TAG"
echo "env_file=$ENV_FILE"
echo "traefik_host=$PRIMARY_HOST"
echo "traefik_rule=$TRAEFIK_RULE"
echo "next_public_url=https://$PRIMARY_HOST"
echo "directus_url=https://cms.$PRIMARY_HOST"
echo "project_name=$PRJ-$TARGET"
echo "short_sha=$SHORT_SHA"
} >> "$GITHUB_OUTPUT"
# ──────────────────────────────────────────────────────────────────────────────
# JOB 2: QA (Lint, Typecheck, Test)
# ──────────────────────────────────────────────────────────────────────────────
qa:
name: 🧪 QA
needs: prepare
if: needs.prepare.outputs.target != 'skip'
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
- name: 🔐 Registry Auth
run: |
echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
echo "//${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}/:_authToken=${{ secrets.REGISTRY_PASS }}" >> .npmrc
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: 🧪 Parallel Checks
if: github.event.inputs.skip_checks != 'true'
run: |
pnpm lint &
pnpm typecheck &
pnpm test &
wait
# ──────────────────────────────────────────────────────────────────────────────
# JOB 3: Build & Push
# ──────────────────────────────────────────────────────────────────────────────
build:
name: 🏗️ Build
needs: prepare
if: needs.prepare.outputs.target != 'skip'
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: 🐳 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🔐 Registry Login
run: echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: 🏗️ Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/arm64
build-args: |
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
DIRECTUS_URL=${{ needs.prepare.outputs.directus_url }}
NPM_TOKEN=${{ secrets.REGISTRY_PASS }}
tags: registry.infra.mintel.me/mintel/klz-cables.com:${{ needs.prepare.outputs.image_tag }}
cache-from: type=registry,ref=registry.infra.mintel.me/mintel/klz-cables.com:buildcache
cache-to: type=registry,ref=registry.infra.mintel.me/mintel/klz-cables.com:buildcache,mode=max
secrets: |
"NPM_TOKEN=${{ secrets.REGISTRY_PASS }}"
# ──────────────────────────────────────────────────────────────────────────────
# JOB 4: Deploy
# ──────────────────────────────────────────────────────────────────────────────
deploy:
name: 🚀 Deploy
needs: [prepare, build, qa]
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: 🚀 SSH Deploy
shell: bash
env:
TARGET: ${{ needs.prepare.outputs.target }}
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
PROJECT_NAME: ${{ needs.prepare.outputs.project_name }}
ENV_FILE: ${{ needs.prepare.outputs.env_file }}
TRAEFIK_RULE: ${{ needs.prepare.outputs.traefik_rule }}
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
# Generate Environment File
cat > .env.deploy << EOF
IMAGE_TAG=$IMAGE_TAG
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
DIRECTUS_URL=${{ needs.prepare.outputs.directus_url }}
DIRECTUS_HOST=cms.${{ needs.prepare.outputs.traefik_host }}
INTERNAL_DIRECTUS_URL=http://directus:8055
TRAEFIK_HOST_RULE='$TRAEFIK_RULE'
PROJECT_NAME=$PROJECT_NAME
TARGET=$TARGET
SENTRY_ENVIRONMENT=$TARGET
SENTRY_DSN=${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }}
GATEKEEPER_PASSWORD=${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
AUTH_MIDDLEWARE=$( [[ "$TARGET" == "production" ]] && echo "${PROJECT_NAME}-compress" || echo "${PROJECT_NAME}-auth,${PROJECT_NAME}-compress" )
EOF
# Transfer and Restart
SITE_DIR="/home/deploy/sites/klz-cables.com"
ssh root@alpha.mintel.me "mkdir -p $SITE_DIR"
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
scp docker-compose.yml root@alpha.mintel.me:$SITE_DIR/docker-compose.yml
ssh root@alpha.mintel.me "cd $SITE_DIR && \
echo '${{ secrets.REGISTRY_PASS }}' | docker login registry.infra.mintel.me -u '${{ secrets.REGISTRY_USER }}' --password-stdin && \
docker compose -p '$PROJECT_NAME' --env-file '$ENV_FILE' pull && \
docker compose -p '$PROJECT_NAME' --env-file '$ENV_FILE' up -d --wait --remove-orphans"
# ──────────────────────────────────────────────────────────────────────────────
# JOB 5: Notifications
# ──────────────────────────────────────────────────────────────────────────────
notifications:
name: 🔔 Notify
needs: [prepare, deploy]
if: always()
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: 🔔 Gotify
run: |
STATUS="${{ needs.deploy.result }}"
TITLE="klz-cables.com: $STATUS"
[[ "$STATUS" == "success" ]] && PRIORITY=5 || PRIORITY=8
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
-F "title=$TITLE" \
-F "message=Deploy to ${{ needs.prepare.outputs.target }} finished with status $STATUS.\nVersion: ${{ needs.prepare.outputs.image_tag }}" \
-F "priority=$PRIORITY" || true