feat: add forgejo deploy script, fix nginx envsubst variable leak

- services/forgejo/deploy.sh: deploys Forgejo via Docker to /opt/frp,
  sets up nginx vhost, optionally installs GitHub mirror sync cron
- services/forgejo/{.env.example,docker-compose.yml,nginx.conf.example}:
  bundled templates following find_template pattern (INFRA_DIR override)
- services/nginx/deploy.sh: fix bare envsubst clobbering nginx $vars
  (e.g. $host, $uri) by scoping substitution to known domain vars only
This commit is contained in:
m1ngsama 2026-02-28 13:22:41 +08:00
parent 23b8d1abd8
commit 9d8a08900d
5 changed files with 127 additions and 1 deletions

View file

@ -0,0 +1,10 @@
# role: vps
# description: Forgejo self-hosted git service with optional GitHub mirror sync
GIT_DOMAIN=git.your-domain.com
# Optional: GitHub → Forgejo mirror sync (leave blank to skip cron setup)
GITHUB_USER=
GITHUB_TOKEN=
FORGEJO_URL=https://git.your-domain.com
FORGEJO_TOKEN=

67
services/forgejo/deploy.sh Executable file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Deploys Forgejo (self-hosted git) via Docker on a VPS.
# Usage: INFRA_DIR=/path/to/infra/services/forgejo ./deploy.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../../bin/lib/common.sh"
ENV_FILE="${INFRA_DIR:-.}/.env"
[ -f "$ENV_FILE" ] || { log_error "No .env found at $ENV_FILE"; exit 1; }
set -a; source "$ENV_FILE"; set +a
require_env GIT_DOMAIN
require_command docker "https://docs.docker.com/engine/install/"
find_template() {
local f="$1"
if [[ -n "${INFRA_DIR:-}" && -f "${INFRA_DIR}/$f" ]]; then
echo "${INFRA_DIR}/$f"
elif [[ -f "$SCRIPT_DIR/$f" ]]; then
echo "$SCRIPT_DIR/$f"
else
log_error "Template not found: $f"
return 1
fi
}
DEPLOY_DIR="/opt/forgejo"
log_info "Creating deploy directory $DEPLOY_DIR..."
mkdir -p "$DEPLOY_DIR/data"
log_info "Deploying docker-compose.yml..."
cp "$(find_template docker-compose.yml)" "$DEPLOY_DIR/docker-compose.yml"
log_info "Starting Forgejo container..."
docker compose -f "$DEPLOY_DIR/docker-compose.yml" up -d
log_info "Deploying nginx vhost for ${GIT_DOMAIN}..."
envsubst '${GIT_DOMAIN}' < "$(find_template nginx.conf.example)" > "/etc/nginx/sites-available/forgejo"
ln -sf /etc/nginx/sites-available/forgejo /etc/nginx/sites-enabled/forgejo
nginx -t
systemctl reload nginx
# Optional: set up GitHub mirror sync cron
if [[ -n "${GITHUB_USER:-}" && -n "${GITHUB_TOKEN:-}" && -n "${FORGEJO_URL:-}" && -n "${FORGEJO_TOKEN:-}" ]]; then
SYNC_SCRIPT="$(find_template migrate_github_to_forgejo.py 2>/dev/null || true)"
if [[ -n "$SYNC_SCRIPT" ]]; then
log_info "Installing GitHub mirror sync script..."
cp "$SYNC_SCRIPT" "$DEPLOY_DIR/migrate_github_to_forgejo.py"
mkdir -p "$DEPLOY_DIR/logs"
CRON_LINE="0 3 * * * cd $DEPLOY_DIR && GITHUB_USER=${GITHUB_USER} GITHUB_TOKEN=${GITHUB_TOKEN} FORGEJO_URL=${FORGEJO_URL} FORGEJO_TOKEN=${FORGEJO_TOKEN} python3 migrate_github_to_forgejo.py >> $DEPLOY_DIR/logs/mirror-sync.log 2>&1"
(crontab -l 2>/dev/null | grep -v "migrate_github_to_forgejo"; echo "$CRON_LINE") | crontab -
log_info "Cron sync installed (daily 03:00)"
else
log_warn "migrate_github_to_forgejo.py not found in INFRA_DIR — skipping cron setup"
fi
else
log_info "GitHub sync vars not set — skipping cron setup"
fi
log_info "Forgejo deployed at http://localhost:3000"
echo ""
echo "Remaining manual steps:"
echo " 1. Get TLS cert: certbot --nginx -d ${GIT_DOMAIN}"
echo " 2. Complete Forgejo initial setup at https://${GIT_DOMAIN}"
echo " 3. Generate Forgejo API token: https://${GIT_DOMAIN}/user/settings/applications"

View file

@ -0,0 +1,23 @@
version: '3'
networks:
forgejo:
external: false
services:
server:
image: codeberg.org/forgejo/forgejo:9
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
networks:
- forgejo
volumes:
- /opt/forgejo/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2223:22"

View file

@ -0,0 +1,26 @@
server {
listen 80;
server_name ${GIT_DOMAIN};
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ${GIT_DOMAIN};
ssl_certificate /etc/letsencrypt/live/${GIT_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${GIT_DOMAIN}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
client_max_body_size 512M;
}

View file

@ -23,7 +23,7 @@ mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
for conf in "${INFRA_DIR}/sites/"*.conf; do
name="$(basename "$conf" .conf)"
envsubst < "$conf" > "/etc/nginx/sites-available/${name}"
envsubst '${DOMAIN}${BLOG_DOMAIN}${CHAN_DOMAIN}${MAIL_DOMAIN}${GIT_DOMAIN}' < "$conf" > "/etc/nginx/sites-available/${name}"
ln -sf "/etc/nginx/sites-available/${name}" "/etc/nginx/sites-enabled/${name}"
log_info " Deployed ${name}"
done