#!/usr/bin/env bash # ════════════════════════════════════════════════════════════════ # 国内域名机 · Forgejo 二进制装机脚本 # Sovereign: TCS-0002∞ · ICE-GL∞ · 国作登字-2026-A-00037559 # 守护: 铸渊 · ICE-GL-ZY001 # # 用途: # 在广州 2C2G 域名机 (ZY-SVR-CN01) 上装 Forgejo (二进制方式, 不用 docker — # 2C2G 内存紧, docker layers 还要再吃 200MB+). # # 被 .github/workflows/migrate-to-cn-restore.yml 远端调用. 不通过 bootstrap.sh, # 因为只有真正搬家时才需要 forgejo. # # 用法: # sudo bash setup-forgejo.sh \ # --data-root /data/guanghulab \ # --version 7.0.10 \ # [--port 3001] \ # [--ssh-port 2222] # # 关键设计: # - cc-001 涌现洁净: 国内 forgejo 起一段独立历史, 不 mirror GitHub # - cc-003 动态适配: tune tier=tiny 强制以下让步: # max-conn=20 / 关 LFS / 关内置 actions runner / SQLite 而非 PostgreSQL # - cc-004 中文一次性: 首启 user/pass 落 _logs/forgejo-credentials-FIRST-BOOT.txt 抄完删 # - 监听 127.0.0.1:3001 (永远不公网), nginx 反代到 /git/ # - SSH-git 默认关掉 (tiny 档省内存, 真要 ssh git 升档再开) # ════════════════════════════════════════════════════════════════ set -euo pipefail DATA_ROOT="/data/guanghulab" DEPLOY_ROOT="/opt/guanghulab" FORGEJO_VERSION="7.0.10" # LTS 系列, 2C2G 跑得动. 如要升级先在霜砚环境验证. FORGEJO_HTTP_PORT="3001" # 监听端口 (loopback) FORGEJO_SSH_ENABLED="false" # tiny 档默认关 FORGEJO_MAX_CONN="20" # tiny 档严限 FORGEJO_LFS_ENABLED="false" DOMAIN="guanghulab.com" while [ $# -gt 0 ]; do case "$1" in --data-root) DATA_ROOT="$2"; shift 2 ;; --deploy-root) DEPLOY_ROOT="$2"; shift 2 ;; --version) FORGEJO_VERSION="$2"; shift 2 ;; --port) FORGEJO_HTTP_PORT="$2"; shift 2 ;; --enable-ssh) FORGEJO_SSH_ENABLED="true"; shift ;; --max-conn) FORGEJO_MAX_CONN="$2"; shift 2 ;; --enable-lfs) FORGEJO_LFS_ENABLED="true"; shift ;; --domain) DOMAIN="$2"; shift 2 ;; -h|--help) head -n 30 "$0"; exit 0 ;; *) echo "❌ 未知参数: $1" >&2; exit 1 ;; esac done if [ "$EUID" -ne 0 ]; then echo "❌ 需要 root (sudo bash setup-forgejo.sh ...)" >&2; exit 2 fi # 读 .env.tune 让档位决策接管 (tune 已经在 bootstrap 时跑过) if [ -f "$DATA_ROOT/.env.tune" ]; then # shellcheck disable=SC1091 . "$DATA_ROOT/.env.tune" case "${SIZE_TIER:-tiny}" in tiny) FORGEJO_MAX_CONN="20" FORGEJO_LFS_ENABLED="false" FORGEJO_SSH_ENABLED="false" ;; small) FORGEJO_MAX_CONN="50" FORGEJO_LFS_ENABLED="false" FORGEJO_SSH_ENABLED="false" ;; medium|large|xlarge) FORGEJO_MAX_CONN="100" FORGEJO_LFS_ENABLED="true" FORGEJO_SSH_ENABLED="true" ;; esac fi FORGEJO_HOME="$DATA_ROOT/forgejo" FORGEJO_BIN="/usr/local/bin/forgejo" FORGEJO_USER="forgejo" FORGEJO_GROUP="forgejo" LOG_DIR="$DATA_ROOT/logs/forgejo" CRED_FILE="$DEPLOY_ROOT/_logs/forgejo-credentials-FIRST-BOOT.txt" RECEIPT_TS="$(date +%Y%m%d-%H%M%S)" SETUP_RECEIPT="$DEPLOY_ROOT/_logs/forgejo-setup-$RECEIPT_TS.json" mkdir -p "$DEPLOY_ROOT/_logs" "$LOG_DIR" echo "═══════════════════════════════════════════════════════════" echo " Forgejo 装机 · ZY-SVR-CN01" echo " 版本: $FORGEJO_VERSION" echo " 端口: 127.0.0.1:$FORGEJO_HTTP_PORT (loopback only)" echo " 数据根: $FORGEJO_HOME" echo " 日志根: $LOG_DIR" echo " 域名: $DOMAIN (走 nginx /git/ 反代)" echo " max-conn: $FORGEJO_MAX_CONN" echo " LFS: $FORGEJO_LFS_ENABLED" echo " SSH-git: $FORGEJO_SSH_ENABLED" echo " 档位: ${SIZE_TIER:-tiny}" echo "═══════════════════════════════════════════════════════════" # ─── 1. 建用户 ──────────────────────────────────────────────── if ! id -u "$FORGEJO_USER" >/dev/null 2>&1; then groupadd --system "$FORGEJO_GROUP" useradd --system --gid "$FORGEJO_GROUP" \ --home-dir "$FORGEJO_HOME" \ --shell /usr/sbin/nologin \ --comment "Forgejo self-hosted git for guanghulab.com" \ "$FORGEJO_USER" echo "✅ 创建用户 $FORGEJO_USER" else echo "ℹ️ 用户 $FORGEJO_USER 已存在" fi # ─── 2. 建目录 ──────────────────────────────────────────────── mkdir -p "$FORGEJO_HOME"/{custom/conf,data,repos,lfs,tmp/uploads} mkdir -p "$LOG_DIR" chown -R "$FORGEJO_USER:$FORGEJO_GROUP" "$FORGEJO_HOME" "$LOG_DIR" chmod 750 "$FORGEJO_HOME" # ─── 3. 下载 Forgejo 二进制 ─────────────────────────────────── ARCH="$(uname -m)" case "$ARCH" in x86_64) FORGEJO_ARCH="amd64" ;; aarch64) FORGEJO_ARCH="arm64" ;; *) echo "❌ 不支持架构: $ARCH" >&2; exit 1 ;; esac NEED_DOWNLOAD="true" if [ -x "$FORGEJO_BIN" ]; then CUR_VER="$($FORGEJO_BIN --version 2>/dev/null | head -1 | awk '{print $3}' || true)" if [ "$CUR_VER" = "$FORGEJO_VERSION" ]; then echo "ℹ️ Forgejo $FORGEJO_VERSION 已装 (跳过下载)" NEED_DOWNLOAD="false" fi fi if [ "$NEED_DOWNLOAD" = "true" ]; then echo "[1/6] 下载 Forgejo $FORGEJO_VERSION ($FORGEJO_ARCH) ..." TMP_BIN="/tmp/forgejo-$FORGEJO_VERSION-$FORGEJO_ARCH" # codeberg.org 国内可达 (Cloudflare); 不行降级 GH proxy 镜像 URL_PRIMARY="https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${FORGEJO_ARCH}" URL_FALLBACK="https://gh-proxy.com/https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${FORGEJO_ARCH}" if ! curl -fsSL --max-time 120 -o "$TMP_BIN" "$URL_PRIMARY"; then echo " 主源失败, 改用代理镜像..." curl -fsSL --max-time 120 -o "$TMP_BIN" "$URL_FALLBACK" || { echo "❌ Forgejo 下载失败 (主源 + 镜像都不通)"; exit 1 } fi chmod +x "$TMP_BIN" mv "$TMP_BIN" "$FORGEJO_BIN" echo " ✅ 安装到 $FORGEJO_BIN" fi # ─── 4. 生成 app.ini ────────────────────────────────────────── APP_INI="$FORGEJO_HOME/custom/conf/app.ini" # 生成随机 SECRET_KEY / INTERNAL_TOKEN / JWT_SECRET / LFS_JWT_SECRET # (用 -hex: 直接 64 个十六进制字符 = 256 bits 熵 · review 建议: 比 base64+filter 更干净) gen_secret() { openssl rand -hex 32; } if [ ! -f "$APP_INI" ]; then echo "[2/6] 生成 app.ini ..." SECRET_KEY="$(gen_secret)" INTERNAL_TOKEN="$(gen_secret)" JWT_SECRET="REDACTED_PASSWORD" LFS_JWT_SECRET="REDACTED_PASSWORD" cat > "$APP_INI" < "$UNIT" </dev/null 2>&1 || true systemctl restart forgejo.service # 等待 forgejo 监听 :3001 HEALTH_OK="false" for i in $(seq 1 30); do if curl -fsS -m 3 "http://127.0.0.1:$FORGEJO_HTTP_PORT/api/v1/version" >/dev/null 2>&1; then HEALTH_OK="true" break fi sleep 2 done if [ "$HEALTH_OK" != "true" ]; then echo "❌ Forgejo 60 秒内没起来" systemctl status forgejo.service --no-pager | tail -30 exit 1 fi echo " ✅ Forgejo 已起 (127.0.0.1:$FORGEJO_HTTP_PORT)" # ─── 7. 创建首个管理员 + 仓库 ──────────────────────────────── ADMIN_USER="bingshuo" ADMIN_EMAIL="bingshuo@guanghulab.com" # 首启凭据文件 (chmod 600, 抄完删) if [ ! -f "$CRED_FILE" ]; then echo "[5/6] 创建管理员 $ADMIN_USER ..." # 24 位 alphanumeric ≈ 142 bits 熵, 远超现代标准. 不引入特殊字符是为了避免: # 1. shell 转义 (这个密码会被嵌入 nginx/git URL 里短暂使用, 特殊字符会让 URL 编码出错) # 2. 让冰朔抄起来麻烦 (特殊字符在 SSH 终端里看不清) # 抄完即删 → 长期密码由冰朔在 forgejo Web UI 自行改成更复杂的就行. ADMIN_PASS="$(LC_ALL=C tr -dc 'A-Za-z0-9' &1 | tail -5 || { # 已存在则跳过 echo " (用户可能已存在, 继续)" } # 落首启凭据文件 umask 077 cat > "$CRED_FILE" < "$SETUP_RECEIPT" </dev/null || echo unknown)" } EOF echo "═══════════════════════════════════════════════════════════" echo " ✅ Forgejo 装机完成" echo "═══════════════════════════════════════════════════════════" echo " 端点: http://127.0.0.1:$FORGEJO_HTTP_PORT" echo " 对外 (nginx): https://$DOMAIN/git/" echo " 管理员凭据: $CRED_FILE (chmod 600 抄完删)" echo " systemd: forgejo.service ($(systemctl is-active forgejo.service 2>/dev/null))" echo " 决策回执: $SETUP_RECEIPT" echo "═══════════════════════════════════════════════════════════"