Add Gitea Actions workflows, CI config, and docs
This commit is contained in:
451
.gitea/workflows/deploy.yml
Normal file
451
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,451 @@
|
||||
# =============================================================================
|
||||
# Deploy Workflow — Automated Deployment to VPS
|
||||
# =============================================================================
|
||||
#
|
||||
# PURPOSE:
|
||||
# Deploy your application to a VPS after a successful push to the default
|
||||
# branch (main). Supports two deployment modes:
|
||||
#
|
||||
# (A) local-runner — The deploy job runs directly on a self-hosted act_runner
|
||||
# installed ON the VPS. No SSH needed. The runner is selected by a label
|
||||
# (DEPLOY_RUNNER_LABEL). This is the recommended mode.
|
||||
#
|
||||
# (B) ssh — The deploy job runs on any runner and SSHs into the VPS to
|
||||
# execute commands remotely. Requires SSH secrets. Use as fallback when
|
||||
# you can't install a runner on the target VPS.
|
||||
#
|
||||
# SAFE BY DEFAULT:
|
||||
# ENABLE_DEPLOY=false in .ci/config.env. Deploy never runs unless you
|
||||
# explicitly enable it. It also never runs on pull_request events.
|
||||
#
|
||||
# DEPLOY STRATEGIES:
|
||||
# compose — docker compose pull && docker compose up -d
|
||||
# systemd — systemctl restart <service>
|
||||
# script — run a custom deploy script
|
||||
#
|
||||
# TRIGGERS:
|
||||
# - push to DEFAULT_BRANCH (main) → deploy if enabled
|
||||
# - tag v* (only if DEPLOY_ON_TAG=true) → deploy if enabled
|
||||
# - pull_request → NEVER (not in trigger list)
|
||||
#
|
||||
# REQUIRED SECRETS (ssh mode only):
|
||||
# DEPLOY_SSH_KEY — private SSH key (ed25519 or RSA)
|
||||
# DEPLOY_HOST — VPS hostname or IP
|
||||
# DEPLOY_USER — SSH username on VPS
|
||||
# DEPLOY_KNOWN_HOSTS — (optional) known_hosts entry for the VPS
|
||||
#
|
||||
# For local-runner mode: NO secrets needed. The runner already has local
|
||||
# access. Just ensure the runner is registered with the correct label.
|
||||
#
|
||||
# See docs/DEPLOY.md for full setup instructions.
|
||||
# =============================================================================
|
||||
|
||||
name: Deploy
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TRIGGERS
|
||||
# ---------------------------------------------------------------------------
|
||||
# Only push events — never pull_request.
|
||||
# Branch filter is further enforced in the "branch guard" step below,
|
||||
# because config.env may specify a different DEFAULT_BRANCH.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
# =============================================================================
|
||||
# JOB: deploy-local-runner
|
||||
# =============================================================================
|
||||
# Runs directly on the VPS via a labeled self-hosted act_runner.
|
||||
# This job is skipped if DEPLOY_MODE != local-runner.
|
||||
# ---------------------------------------------------------------------------
|
||||
jobs:
|
||||
deploy-local-runner:
|
||||
# -------------------------------------------------------------------------
|
||||
# Runner selection:
|
||||
# The 'runs-on' value is read from config at workflow parse time, but
|
||||
# Gitea Actions does not support dynamic runs-on from env vars.
|
||||
# We use the default label here; if you changed DEPLOY_RUNNER_LABEL in
|
||||
# config.env, you MUST also update this runs-on value to match.
|
||||
#
|
||||
# HOW TO CHANGE: replace 'deploy-ovh' below with your label.
|
||||
# -------------------------------------------------------------------------
|
||||
runs-on: deploy-ovh
|
||||
steps:
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 1: Load configuration
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Checkout (for config only)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Load config
|
||||
id: config
|
||||
run: |
|
||||
# Source config.env
|
||||
if [ -f .ci/config.env ]; then
|
||||
set -a
|
||||
source .ci/config.env
|
||||
set +a
|
||||
echo "Config loaded."
|
||||
else
|
||||
echo "WARNING: .ci/config.env not found, using defaults."
|
||||
fi
|
||||
|
||||
# Export all deploy-related vars with safe defaults
|
||||
echo "ENABLE_DEPLOY=${ENABLE_DEPLOY:-false}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_MODE=${DEPLOY_MODE:-local-runner}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_WORKDIR=${DEPLOY_WORKDIR:-/opt/app}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_STRATEGY=${DEPLOY_STRATEGY:-compose}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_COMPOSE_FILE=${DEPLOY_COMPOSE_FILE:-docker-compose.yml}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_SYSTEMD_SERVICE=${DEPLOY_SYSTEMD_SERVICE:-}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_SCRIPT=${DEPLOY_SCRIPT:-scripts/deploy.sh}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_ON_TAG=${DEPLOY_ON_TAG:-false}" >> "$GITHUB_ENV"
|
||||
echo "DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}" >> "$GITHUB_ENV"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 2: Gate checks — abort early if deploy should not run
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Check if deploy is enabled
|
||||
run: |
|
||||
if [ "$ENABLE_DEPLOY" != "true" ]; then
|
||||
echo "========================================="
|
||||
echo " Deploy is DISABLED (ENABLE_DEPLOY=$ENABLE_DEPLOY)"
|
||||
echo " To enable: set ENABLE_DEPLOY=true in .ci/config.env"
|
||||
echo "========================================="
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ensure this job is for the correct mode
|
||||
if [ "$DEPLOY_MODE" != "local-runner" ]; then
|
||||
echo "DEPLOY_MODE=$DEPLOY_MODE (not local-runner). This job is a no-op."
|
||||
echo "The ssh job will handle deployment instead."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "DEPLOY_ACTIVE=true" >> "$GITHUB_ENV"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 3: Branch guard
|
||||
# Only deploy from DEFAULT_BRANCH. For tags, check DEPLOY_ON_TAG.
|
||||
# This is a SAFETY net — even though 'on.push.branches' is set above,
|
||||
# DEFAULT_BRANCH might differ from 'main'.
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Branch guard
|
||||
if: env.DEPLOY_ACTIVE == 'true'
|
||||
run: |
|
||||
REF="${GITHUB_REF:-}"
|
||||
|
||||
# Tag push?
|
||||
if echo "$REF" | grep -q '^refs/tags/v'; then
|
||||
if [ "$DEPLOY_ON_TAG" != "true" ]; then
|
||||
echo "Tag push detected but DEPLOY_ON_TAG=$DEPLOY_ON_TAG. Skipping."
|
||||
echo "DEPLOY_ACTIVE=false" >> "$GITHUB_ENV"
|
||||
exit 0
|
||||
fi
|
||||
echo "Deploying on tag: $REF"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Branch push — verify it's DEFAULT_BRANCH
|
||||
BRANCH="$(echo "$REF" | sed 's|refs/heads/||')"
|
||||
if [ "$BRANCH" != "$DEFAULT_BRANCH" ]; then
|
||||
echo "Branch '$BRANCH' is not DEFAULT_BRANCH '$DEFAULT_BRANCH'. Skipping."
|
||||
echo "DEPLOY_ACTIVE=false" >> "$GITHUB_ENV"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Deploying on branch: $BRANCH"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 4: Execute deploy strategy (LOCAL — runs on the VPS itself)
|
||||
# -----------------------------------------------------------------------
|
||||
- name: "Deploy: compose"
|
||||
if: env.DEPLOY_ACTIVE == 'true' && env.DEPLOY_STRATEGY == 'compose'
|
||||
run: |
|
||||
echo ">>> Deploy strategy: compose"
|
||||
echo ">>> Working directory: $DEPLOY_WORKDIR"
|
||||
echo ">>> Compose file: $DEPLOY_COMPOSE_FILE"
|
||||
|
||||
cd "$DEPLOY_WORKDIR" || { echo "ERROR: Cannot cd to $DEPLOY_WORKDIR"; exit 1; }
|
||||
|
||||
echo ">>> docker compose -f $DEPLOY_COMPOSE_FILE pull"
|
||||
docker compose -f "$DEPLOY_COMPOSE_FILE" pull
|
||||
|
||||
echo ">>> docker compose -f $DEPLOY_COMPOSE_FILE up -d"
|
||||
docker compose -f "$DEPLOY_COMPOSE_FILE" up -d
|
||||
|
||||
echo "Deploy (compose) complete."
|
||||
|
||||
- name: "Deploy: systemd"
|
||||
if: env.DEPLOY_ACTIVE == 'true' && env.DEPLOY_STRATEGY == 'systemd'
|
||||
run: |
|
||||
echo ">>> Deploy strategy: systemd"
|
||||
|
||||
if [ -z "$DEPLOY_SYSTEMD_SERVICE" ]; then
|
||||
echo "ERROR: DEPLOY_SYSTEMD_SERVICE is not set."
|
||||
echo "Set it in .ci/config.env for strategy=systemd."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">>> systemctl restart $DEPLOY_SYSTEMD_SERVICE"
|
||||
sudo systemctl restart "$DEPLOY_SYSTEMD_SERVICE"
|
||||
|
||||
echo ">>> systemctl status $DEPLOY_SYSTEMD_SERVICE"
|
||||
sudo systemctl status "$DEPLOY_SYSTEMD_SERVICE" --no-pager
|
||||
|
||||
echo "Deploy (systemd) complete."
|
||||
|
||||
- name: "Deploy: script"
|
||||
if: env.DEPLOY_ACTIVE == 'true' && env.DEPLOY_STRATEGY == 'script'
|
||||
run: |
|
||||
echo ">>> Deploy strategy: script"
|
||||
echo ">>> Script: $DEPLOY_SCRIPT"
|
||||
echo ">>> Workdir arg: $DEPLOY_WORKDIR"
|
||||
|
||||
if [ ! -f "$DEPLOY_SCRIPT" ]; then
|
||||
echo "ERROR: Deploy script not found: $DEPLOY_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x "$DEPLOY_SCRIPT"
|
||||
"./$DEPLOY_SCRIPT" "$DEPLOY_WORKDIR"
|
||||
|
||||
echo "Deploy (script) complete."
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 5: Summary
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Deploy summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "=============================="
|
||||
echo " Deploy (local-runner)"
|
||||
echo " Enabled: ${ENABLE_DEPLOY:-false}"
|
||||
echo " Mode: ${DEPLOY_MODE:-local-runner}"
|
||||
echo " Strategy: ${DEPLOY_STRATEGY:-compose}"
|
||||
echo " Workdir: ${DEPLOY_WORKDIR:-/opt/app}"
|
||||
echo " Active: ${DEPLOY_ACTIVE:-false}"
|
||||
echo "=============================="
|
||||
|
||||
# ===========================================================================
|
||||
# JOB: deploy-ssh
|
||||
# ===========================================================================
|
||||
# Runs on a normal runner and SSHs into the VPS to deploy.
|
||||
# This job is skipped if DEPLOY_MODE != ssh.
|
||||
# ---------------------------------------------------------------------------
|
||||
deploy-ssh:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 1: Load configuration
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Checkout (for config + scripts)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Load config
|
||||
run: |
|
||||
if [ -f .ci/config.env ]; then
|
||||
set -a
|
||||
source .ci/config.env
|
||||
set +a
|
||||
echo "Config loaded."
|
||||
else
|
||||
echo "WARNING: .ci/config.env not found, using defaults."
|
||||
fi
|
||||
|
||||
echo "ENABLE_DEPLOY=${ENABLE_DEPLOY:-false}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_MODE=${DEPLOY_MODE:-local-runner}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_WORKDIR=${DEPLOY_WORKDIR:-/opt/app}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_STRATEGY=${DEPLOY_STRATEGY:-compose}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_COMPOSE_FILE=${DEPLOY_COMPOSE_FILE:-docker-compose.yml}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_SYSTEMD_SERVICE=${DEPLOY_SYSTEMD_SERVICE:-}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_SCRIPT=${DEPLOY_SCRIPT:-scripts/deploy.sh}" >> "$GITHUB_ENV"
|
||||
echo "DEPLOY_ON_TAG=${DEPLOY_ON_TAG:-false}" >> "$GITHUB_ENV"
|
||||
echo "DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}" >> "$GITHUB_ENV"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 2: Gate checks
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Check if deploy is enabled
|
||||
run: |
|
||||
if [ "$ENABLE_DEPLOY" != "true" ]; then
|
||||
echo "========================================="
|
||||
echo " Deploy is DISABLED (ENABLE_DEPLOY=$ENABLE_DEPLOY)"
|
||||
echo " To enable: set ENABLE_DEPLOY=true in .ci/config.env"
|
||||
echo "========================================="
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_MODE" != "ssh" ]; then
|
||||
echo "DEPLOY_MODE=$DEPLOY_MODE (not ssh). This job is a no-op."
|
||||
echo "The local-runner job will handle deployment instead."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "DEPLOY_ACTIVE=true" >> "$GITHUB_ENV"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 3: Branch guard (same logic as local-runner)
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Branch guard
|
||||
if: env.DEPLOY_ACTIVE == 'true'
|
||||
run: |
|
||||
REF="${GITHUB_REF:-}"
|
||||
|
||||
if echo "$REF" | grep -q '^refs/tags/v'; then
|
||||
if [ "$DEPLOY_ON_TAG" != "true" ]; then
|
||||
echo "Tag push detected but DEPLOY_ON_TAG=$DEPLOY_ON_TAG. Skipping."
|
||||
echo "DEPLOY_ACTIVE=false" >> "$GITHUB_ENV"
|
||||
exit 0
|
||||
fi
|
||||
echo "Deploying on tag: $REF"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BRANCH="$(echo "$REF" | sed 's|refs/heads/||')"
|
||||
if [ "$BRANCH" != "$DEFAULT_BRANCH" ]; then
|
||||
echo "Branch '$BRANCH' is not DEFAULT_BRANCH '$DEFAULT_BRANCH'. Skipping."
|
||||
echo "DEPLOY_ACTIVE=false" >> "$GITHUB_ENV"
|
||||
exit 0
|
||||
fi
|
||||
echo "Deploying on branch: $BRANCH"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 4: Set up SSH
|
||||
#
|
||||
# Secrets required:
|
||||
# DEPLOY_SSH_KEY — private key (ed25519 recommended)
|
||||
# DEPLOY_HOST — VPS IP or hostname
|
||||
# DEPLOY_USER — SSH username
|
||||
# DEPLOY_KNOWN_HOSTS — (optional) output of ssh-keyscan for the host
|
||||
#
|
||||
# If DEPLOY_KNOWN_HOSTS is not set, StrictHostKeyChecking is disabled.
|
||||
# This is less secure but avoids first-connect failures in CI.
|
||||
# For production, always set DEPLOY_KNOWN_HOSTS.
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Set up SSH
|
||||
if: env.DEPLOY_ACTIVE == 'true'
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
# Write private key (never echo it)
|
||||
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
|
||||
# Known hosts — if provided, use it; otherwise disable strict checking
|
||||
KNOWN_HOSTS="${{ secrets.DEPLOY_KNOWN_HOSTS }}"
|
||||
if [ -n "$KNOWN_HOSTS" ]; then
|
||||
echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts
|
||||
chmod 644 ~/.ssh/known_hosts
|
||||
echo "known_hosts configured from secret."
|
||||
else
|
||||
echo "WARNING: DEPLOY_KNOWN_HOSTS not set. Disabling StrictHostKeyChecking."
|
||||
echo "For production, set DEPLOY_KNOWN_HOSTS (run: ssh-keyscan your-host)"
|
||||
{
|
||||
echo "Host *"
|
||||
echo " StrictHostKeyChecking no"
|
||||
echo " UserKnownHostsFile /dev/null"
|
||||
} > ~/.ssh/config
|
||||
chmod 600 ~/.ssh/config
|
||||
fi
|
||||
|
||||
# Build SSH command for reuse
|
||||
SSH_CMD="ssh -i ~/.ssh/deploy_key ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}"
|
||||
echo "SSH_CMD=${SSH_CMD}" >> "$GITHUB_ENV"
|
||||
|
||||
# Verify connectivity
|
||||
echo "Testing SSH connection..."
|
||||
$SSH_CMD "echo 'SSH connection successful.'" || {
|
||||
echo "ERROR: SSH connection failed."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 5: Execute deploy strategy (REMOTE — via SSH)
|
||||
# -----------------------------------------------------------------------
|
||||
- name: "Deploy via SSH: compose"
|
||||
if: env.DEPLOY_ACTIVE == 'true' && env.DEPLOY_STRATEGY == 'compose'
|
||||
run: |
|
||||
echo ">>> Deploy strategy: compose (via SSH)"
|
||||
$SSH_CMD << DEPLOY_EOF
|
||||
set -e
|
||||
echo ">>> cd $DEPLOY_WORKDIR"
|
||||
cd "$DEPLOY_WORKDIR" || { echo "ERROR: Cannot cd to $DEPLOY_WORKDIR"; exit 1; }
|
||||
|
||||
echo ">>> docker compose -f $DEPLOY_COMPOSE_FILE pull"
|
||||
docker compose -f "$DEPLOY_COMPOSE_FILE" pull
|
||||
|
||||
echo ">>> docker compose -f $DEPLOY_COMPOSE_FILE up -d"
|
||||
docker compose -f "$DEPLOY_COMPOSE_FILE" up -d
|
||||
|
||||
echo "Deploy (compose) complete."
|
||||
DEPLOY_EOF
|
||||
|
||||
- name: "Deploy via SSH: systemd"
|
||||
if: env.DEPLOY_ACTIVE == 'true' && env.DEPLOY_STRATEGY == 'systemd'
|
||||
run: |
|
||||
echo ">>> Deploy strategy: systemd (via SSH)"
|
||||
|
||||
if [ -z "$DEPLOY_SYSTEMD_SERVICE" ]; then
|
||||
echo "ERROR: DEPLOY_SYSTEMD_SERVICE is not set."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$SSH_CMD << DEPLOY_EOF
|
||||
set -e
|
||||
echo ">>> sudo systemctl restart $DEPLOY_SYSTEMD_SERVICE"
|
||||
sudo systemctl restart "$DEPLOY_SYSTEMD_SERVICE"
|
||||
|
||||
echo ">>> systemctl status $DEPLOY_SYSTEMD_SERVICE"
|
||||
sudo systemctl status "$DEPLOY_SYSTEMD_SERVICE" --no-pager
|
||||
|
||||
echo "Deploy (systemd) complete."
|
||||
DEPLOY_EOF
|
||||
|
||||
- name: "Deploy via SSH: script"
|
||||
if: env.DEPLOY_ACTIVE == 'true' && env.DEPLOY_STRATEGY == 'script'
|
||||
run: |
|
||||
echo ">>> Deploy strategy: script (via SSH)"
|
||||
|
||||
# Copy the deploy script to the VPS
|
||||
scp -i ~/.ssh/deploy_key \
|
||||
"$DEPLOY_SCRIPT" \
|
||||
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/tmp/_deploy_script.sh"
|
||||
|
||||
$SSH_CMD << DEPLOY_EOF
|
||||
set -e
|
||||
chmod +x /tmp/_deploy_script.sh
|
||||
/tmp/_deploy_script.sh "$DEPLOY_WORKDIR"
|
||||
rm -f /tmp/_deploy_script.sh
|
||||
echo "Deploy (script) complete."
|
||||
DEPLOY_EOF
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 6: Clean up SSH key (always runs)
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Clean up SSH
|
||||
if: always()
|
||||
run: |
|
||||
rm -rf ~/.ssh/deploy_key ~/.ssh/config 2>/dev/null || true
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 7: Summary
|
||||
# -----------------------------------------------------------------------
|
||||
- name: Deploy summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "=============================="
|
||||
echo " Deploy (ssh)"
|
||||
echo " Enabled: ${ENABLE_DEPLOY:-false}"
|
||||
echo " Mode: ${DEPLOY_MODE:-ssh}"
|
||||
echo " Strategy: ${DEPLOY_STRATEGY:-compose}"
|
||||
echo " Workdir: ${DEPLOY_WORKDIR:-/opt/app}"
|
||||
echo " Active: ${DEPLOY_ACTIVE:-false}"
|
||||
echo "=============================="
|
||||
Reference in New Issue
Block a user