159 lines
4.1 KiB
Bash
159 lines
4.1 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# 部署脚本:在远程服务器上跑一次
|
||
|
|
# 用法: bash /root/deploy_news.sh
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
# === 配置 ===
|
||
|
|
GITEA_URL="http://124.223.26.33:3000/xiaji/diary-news.git"
|
||
|
|
APP_DIR="/srv/news"
|
||
|
|
DOMAIN="" # 留空走 IP
|
||
|
|
|
||
|
|
log() { echo "[$(date +'%H:%M:%S')] $*"; }
|
||
|
|
fail() { echo "[FAIL] $*" >&2; exit 1; }
|
||
|
|
|
||
|
|
# === 1. 系统初始化 ===
|
||
|
|
log "1/8 系统更新 + 基础包"
|
||
|
|
export DEBIAN_FRONTEND=noninteractive
|
||
|
|
apt-get update -y
|
||
|
|
apt-get install -y --no-install-recommends curl git ufw fail2ban openssl ca-certificates
|
||
|
|
|
||
|
|
# === 2. 创建非 root 用户(后续切过去) ===
|
||
|
|
if ! id news &>/dev/null; then
|
||
|
|
log "2/8 创建 news 用户"
|
||
|
|
adduser --disabled-password --gecos "" news
|
||
|
|
echo "news ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/news
|
||
|
|
fi
|
||
|
|
|
||
|
|
# === 3. Docker ===
|
||
|
|
if ! command -v docker &>/dev/null; then
|
||
|
|
log "3/8 安装 Docker"
|
||
|
|
curl -fsSL https://get.docker.com -o /tmp/get-docker.sh
|
||
|
|
sh /tmp/get-docker.sh
|
||
|
|
usermod -aG docker news
|
||
|
|
fi
|
||
|
|
docker --version
|
||
|
|
|
||
|
|
# === 4. 防火墙 ===
|
||
|
|
log "4/8 防火墙"
|
||
|
|
SSHD_PORT="${SSHD_PORT:-22}" # 自定义 SSH 端口(默认 22)
|
||
|
|
ufw --force reset
|
||
|
|
ufw default deny incoming
|
||
|
|
ufw default allow outgoing
|
||
|
|
ufw allow "${SSHD_PORT}/tcp" comment 'ssh'
|
||
|
|
ufw allow 80/tcp comment 'http'
|
||
|
|
ufw allow 443/tcp comment 'https'
|
||
|
|
ufw --force enable
|
||
|
|
ufw status verbose
|
||
|
|
|
||
|
|
# === 5. 拉代码 ===
|
||
|
|
log "5/8 拉代码到 $APP_DIR"
|
||
|
|
mkdir -p "$APP_DIR"
|
||
|
|
if [ ! -d "$APP_DIR/.git" ]; then
|
||
|
|
sudo -u news git clone "$GITEA_URL" "$APP_DIR"
|
||
|
|
else
|
||
|
|
cd "$APP_DIR" && sudo -u news git pull --rebase
|
||
|
|
fi
|
||
|
|
chown -R news:news "$APP_DIR"
|
||
|
|
|
||
|
|
# === 6. 写 .env(自动生成密码) ===
|
||
|
|
log "6/8 生成 .env"
|
||
|
|
if [ ! -f "$APP_DIR/.env" ]; then
|
||
|
|
cat > "$APP_DIR/.env" <<EOF
|
||
|
|
TZ=Asia/Hong_Kong
|
||
|
|
LOG_LEVEL=INFO
|
||
|
|
|
||
|
|
POSTGRES_USER=news
|
||
|
|
POSTGRES_PASSWORD=$(openssl rand -hex 24)
|
||
|
|
POSTGRES_DB=news
|
||
|
|
POSTGRES_HOST=postgres
|
||
|
|
POSTGRES_PORT=5432
|
||
|
|
|
||
|
|
REDIS_HOST=redis
|
||
|
|
REDIS_PORT=6379
|
||
|
|
REDIS_PASSWORD=$(openssl rand -hex 24)
|
||
|
|
REDIS_DB=0
|
||
|
|
|
||
|
|
JWT_SECRET=$(openssl rand -hex 64)
|
||
|
|
JWT_ALGORITHM=HS256
|
||
|
|
ACCESS_TOKEN_TTL_MIN=60
|
||
|
|
REFRESH_TOKEN_TTL_DAY=14
|
||
|
|
|
||
|
|
TENCENTCLOUD_SECRET_ID=__FILL_ME__
|
||
|
|
TENCENTCLOUD_SECRET_KEY=__FILL_ME__
|
||
|
|
TENCENTCLOUD_REGION=ap-hongkong
|
||
|
|
TENCENT_TMT_ENDPOINT=tmt.tencentcloudapi.com
|
||
|
|
TENCENT_TMT_QUOTA_MONTH=5000000
|
||
|
|
TENCENT_TMT_QUOTA_BUFFER=0.05
|
||
|
|
TENCENT_TMT_MAX_CHARS_PER_REQ=4500
|
||
|
|
|
||
|
|
LOCAL_TRANSLATE_ENABLED=false
|
||
|
|
LOCAL_TRANSLATE_MODEL=nllb-200-distilled-600M
|
||
|
|
LOCAL_TRANSLATE_DEVICE=cpu
|
||
|
|
|
||
|
|
FETCH_GLOBAL_QPS=4
|
||
|
|
FETCH_TIMEOUT=20
|
||
|
|
FETCH_FAIL_PAUSE_THRESHOLD=3
|
||
|
|
FETCH_MAX_RETRIES=2
|
||
|
|
|
||
|
|
DOMAIN=$DOMAIN
|
||
|
|
ACME_EMAIL=
|
||
|
|
EOF
|
||
|
|
chown news:news "$APP_DIR/.env"
|
||
|
|
chmod 600 "$APP_DIR/.env"
|
||
|
|
log "已生成 .env(随机密码已写入,TENCENTCLOUD_SECRET 仍待填)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# === 7. 启动 docker compose ===
|
||
|
|
log "7/8 docker compose up"
|
||
|
|
cd "$APP_DIR"
|
||
|
|
# news 用户需要 docker 组权限,用 sg 临时切换(写成函数避免引号问题)
|
||
|
|
dc() { sg docker -c "docker compose $*"; }
|
||
|
|
|
||
|
|
dc up -d --build
|
||
|
|
|
||
|
|
# 等健康
|
||
|
|
log " 等待 postgres / redis healthy..."
|
||
|
|
for i in {1..30}; do
|
||
|
|
if dc exec -T postgres pg_isready -U news -d news &>/dev/null; then
|
||
|
|
break
|
||
|
|
fi
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
for i in {1..30}; do
|
||
|
|
if dc exec -T redis redis-cli -a "$(grep ^REDIS_PASSWORD $APP_DIR/.env | cut -d= -f2)" ping 2>/dev/null | grep -q PONG; then
|
||
|
|
break
|
||
|
|
fi
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
|
||
|
|
# === 8. 初始化 ===
|
||
|
|
log "8/8 数据库迁移 + 用户 + 源"
|
||
|
|
dc exec -T api alembic upgrade head
|
||
|
|
|
||
|
|
# 创建 owner(从 env 或自动生成,避免后台跑卡在 read)
|
||
|
|
if [ -n "${OWNER_PASS:-}" ]; then
|
||
|
|
log " 使用环境变量 OWNER_PASS"
|
||
|
|
else
|
||
|
|
OWNER_PASS="$(openssl rand -hex 12)"
|
||
|
|
log " 自动生成 owner 密码(写入 /root/.owner_pass): $OWNER_PASS"
|
||
|
|
fi
|
||
|
|
dc exec -T api python -m app.scripts.create_user --username owner --password "$OWNER_PASS" || true
|
||
|
|
echo "$OWNER_PASS" > /root/.owner_pass
|
||
|
|
chmod 600 /root/.owner_pass
|
||
|
|
|
||
|
|
# 种子
|
||
|
|
dc exec -T api python -m app.scripts.seed_sources
|
||
|
|
|
||
|
|
# 健康检查
|
||
|
|
log " 健康检查"
|
||
|
|
sleep 3
|
||
|
|
curl -s http://localhost/api/v1/healthz && echo
|
||
|
|
echo
|
||
|
|
echo "================================================"
|
||
|
|
echo " 部署完成!"
|
||
|
|
echo " 访问: http://$(curl -s ifconfig.me)/"
|
||
|
|
echo " 账号: owner"
|
||
|
|
echo " 密码: 写入到 /root/.owner_pass (chmod 600)"
|
||
|
|
echo " 后续: docker compose -f $APP_DIR/docker-compose.yml logs -f"
|
||
|
|
echo "================================================"
|