Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

441 lines
15 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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" <<EOF
; Forgejo · 国内自托管 git · ZY-SVR-CN01
; Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559
; 守护: 铸渊 · ICE-GL-ZY001
; 生成时间: $(date -u +%Y-%m-%dT%H:%M:%SZ)
; 档位: ${SIZE_TIER:-tiny}
;
; ⚠️ 此文件含 SECRET_KEY/INTERNAL_TOKEN/JWT_SECRET, chmod 640 不上 git 不上 COS
APP_NAME = 光湖 · 国内自托管 git
RUN_USER = $FORGEJO_USER
RUN_MODE = prod
WORK_PATH = $FORGEJO_HOME
[server]
PROTOCOL = http
DOMAIN = $DOMAIN
ROOT_URL = https://$DOMAIN/git/
HTTP_ADDR = 127.0.0.1
HTTP_PORT = $FORGEJO_HTTP_PORT
DISABLE_SSH = $([ "$FORGEJO_SSH_ENABLED" = "true" ] && echo "false" || echo "true")
SSH_DOMAIN = $DOMAIN
LFS_START_SERVER = $FORGEJO_LFS_ENABLED
LFS_JWT_SECRET = $LFS_JWT_SECRET
OFFLINE_MODE = true
LANDING_PAGE = login
APP_DATA_PATH = $FORGEJO_HOME/data
ENABLE_GZIP = false
[database]
DB_TYPE = sqlite3
PATH = $FORGEJO_HOME/data/forgejo.db
LOG_SQL = false
[repository]
ROOT = $FORGEJO_HOME/repos
DEFAULT_BRANCH = main
DEFAULT_PRIVATE = private
ENABLE_PUSH_CREATE_USER = false
ENABLE_PUSH_CREATE_ORG = false
[security]
INSTALL_LOCK = true
SECRET_KEY = $SECRET_KEY
INTERNAL_TOKEN = $INTERNAL_TOKEN
PASSWORD_HASH_ALGO = pbkdf2_hi
DISABLE_GIT_HOOKS = true
IMPORT_LOCAL_PATHS = false
[oauth2]
JWT_SECRET = $JWT_SECRET
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
ENABLE_CAPTCHA = false
DEFAULT_KEEP_EMAIL_PRIVATE = true
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
[session]
PROVIDER = file
PROVIDER_CONFIG = $FORGEJO_HOME/data/sessions
[picture]
AVATAR_UPLOAD_PATH = $FORGEJO_HOME/data/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = $FORGEJO_HOME/data/repo-avatars
DISABLE_GRAVATAR = true
[attachment]
PATH = $FORGEJO_HOME/data/attachments
[lfs]
PATH = $FORGEJO_HOME/lfs
[log]
ROOT_PATH = $LOG_DIR
MODE = file
LEVEL = warn
ROUTER = none
[ui]
DEFAULT_THEME = forgejo-auto
SHOW_USER_EMAIL = false
[other]
SHOW_FOOTER_VERSION = false
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false
[mailer]
ENABLED = false
[indexer]
REPO_INDEXER_ENABLED = false
ISSUE_INDEXER_TYPE = bleve
[git.timeout]
DEFAULT = 360
MIGRATE = 600
MIRROR = 300
CLONE = 300
PULL = 300
GC = 60
[cron]
ENABLED = true
[actions]
; 2C2G 关掉内置 Actions runner 节省内存 (cc-003 tune tiny)
ENABLED = false
[migrations]
ALLOWED_DOMAINS = ""
ALLOW_LOCALNETWORKS = false
EOF
chown "$FORGEJO_USER:$FORGEJO_GROUP" "$APP_INI"
chmod 640 "$APP_INI"
echo " ✅ app.ini 写入 $APP_INI"
else
echo "[2/6] app.ini 已存在, 不覆盖 (改配置请手编 $APP_INI)"
fi
# ─── 5. systemd unit ──────────────────────────────────────────
UNIT="/etc/systemd/system/forgejo.service"
if [ ! -f "$UNIT" ]; then
echo "[3/6] 写 systemd unit $UNIT ..."
cat > "$UNIT" <<EOF
# Forgejo · 国内自托管 git · ZY-SVR-CN01
# Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559
[Unit]
Description=Forgejo (Beyond coding. We forge.) for guanghulab.com
After=network.target
[Service]
RestartSec=5s
Type=simple
User=$FORGEJO_USER
Group=$FORGEJO_GROUP
WorkingDirectory=$FORGEJO_HOME
ExecStart=$FORGEJO_BIN web --config $APP_INI
Restart=on-failure
Environment=USER=$FORGEJO_USER HOME=$FORGEJO_HOME GITEA_WORK_DIR=$FORGEJO_HOME
# 2C2G 内存严控 (tune tier=tiny)
MemoryHigh=350M
MemoryMax=500M
TasksMax=200
LimitNOFILE=4096
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
echo " ✅ unit 写入 $UNIT"
else
echo "[3/6] systemd unit 已在, daemon-reload 一下"
systemctl daemon-reload
fi
# ─── 6. 启动并等待健康 ──────────────────────────────────────
echo "[4/6] 启动 forgejo.service ..."
systemctl enable forgejo.service >/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' </dev/urandom | head -c 24)"
# forgejo admin user create (走 forgejo 二进制, 需要切到 RUN_USER)
sudo -u "$FORGEJO_USER" "$FORGEJO_BIN" \
admin user create \
--config "$APP_INI" \
--username "$ADMIN_USER" \
--password "$ADMIN_PASS" \
--email "$ADMIN_EMAIL" \
--admin \
--must-change-password=false 2>&1 | tail -5 || {
# 已存在则跳过
echo " (用户可能已存在, 继续)"
}
# 落首启凭据文件
umask 077
cat > "$CRED_FILE" <<EOF
# 光湖 Forgejo · 首启凭据 · 抄完后**手动删除本文件**
# 守护: 铸渊 · ICE-GL-ZY001
# 生成时间: $(date -u +%Y-%m-%dT%H:%M:%SZ)
# 域名机: GH-CVM-DOMAIN-PROD-01 ($DOMAIN)
#
# 访问方式 (浏览器):
# https://$DOMAIN/git/
#
# 登录:
# 用户名: $ADMIN_USER
# 密码: $ADMIN_PASS
# 邮箱: $ADMIN_EMAIL
#
# 抄完密码请运行:
# sudo rm $CRED_FILE
EOF
chmod 600 "$CRED_FILE"
umask 022
echo " ✅ 凭据已落 $CRED_FILE (chmod 600, 抄完 sudo rm)"
else
echo "[5/6] 凭据文件已在 ($CRED_FILE), 不覆盖"
fi
# ─── 8. 决策回执 ─────────────────────────────────────────────
echo "[6/6] 写决策回执 ..."
cat > "$SETUP_RECEIPT" <<EOF
{
"_sovereign": "TCS-0002∞ · 国作登字-2026-A-00037559",
"_守护": "铸渊 · ICE-GL-ZY001",
"completed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"forgejo_version": "$FORGEJO_VERSION",
"forgejo_arch": "$FORGEJO_ARCH",
"forgejo_home": "$FORGEJO_HOME",
"http_addr": "127.0.0.1:$FORGEJO_HTTP_PORT",
"domain": "$DOMAIN",
"size_tier": "${SIZE_TIER:-tiny}",
"max_conn": $FORGEJO_MAX_CONN,
"lfs_enabled": $FORGEJO_LFS_ENABLED,
"ssh_git_enabled": $FORGEJO_SSH_ENABLED,
"actions_enabled": false,
"admin_user": "$ADMIN_USER",
"credentials_file": "$CRED_FILE",
"credentials_first_boot": $([ -f "$CRED_FILE" ] && echo "true" || echo "false"),
"systemd_status": "$(systemctl is-active forgejo.service 2>/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 "═══════════════════════════════════════════════════════════"