-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·203 lines (183 loc) · 7.53 KB
/
deploy.sh
File metadata and controls
executable file
·203 lines (183 loc) · 7.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/env bash
# =============================================================================
# Copilot Hub — 生产部署脚本
#
# 用法:
# bash deploy.sh # 完整部署(编译前端 + 同步 + 安装 service)
# bash deploy.sh --no-build # 跳过前端编译(仅更新后端代码时)
# bash deploy.sh --no-service # 不安装/重启 systemd service(仅同步文件)
#
# 部署流程:
# 1. 锁定依赖(uv export → requirements.txt)
# 2. 编译前端 → static/
# 3. rsync 同步到 DEPLOY_DIR(跳过 DB、密钥、venv 等)
# 4. 首次部署:自动迁移 DB 和 .secret_key
# 5. uv sync --no-dev 安装/更新 Python 依赖
# 6. 安装 systemd 用户级 service 并启动
#
# 部署目录默认 ~/.local/opt/copilot-hub,可通过环境变量覆盖:
# DEPLOY_DIR=/srv/copilot-hub bash deploy.sh
#
# 依赖:uv、node/npm、rsync、systemd(用户级)
# =============================================================================
set -euo pipefail
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SRC_DIR"
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/.local/opt/copilot-hub}"
SERVICE_NAME="copilot-hub"
SKIP_BUILD=false
SKIP_SERVICE=false
for arg in "$@"; do
[[ "$arg" == "--no-build" ]] && SKIP_BUILD=true
[[ "$arg" == "--no-service" ]] && SKIP_SERVICE=true
done
GRN='\033[0;32m'; BLU='\033[0;34m'; YLW='\033[1;33m'; RED='\033[0;31m'; RST='\033[0m'
echo -e "${GRN}==> Copilot Hub 生产部署${RST}"
echo -e " 源目录: ${YLW}${SRC_DIR}${RST}"
echo -e " 目标目录: ${YLW}${DEPLOY_DIR}${RST}"
# 检查 uv
if ! command -v uv &>/dev/null; then
echo -e "${RED}未找到 uv,请先安装: curl -LsSf https://astral.sh/uv/install.sh | sh${RST}"
exit 1
fi
# ── 1. 锁定依赖(保证 requirements.txt 与 pyproject.toml 一致)──────────────
echo -e "${BLU}--> 锁定 Python 依赖...${RST}"
uv export --no-dev --no-hashes -o requirements.txt --quiet
echo -e " requirements.txt 已更新"
# ── 2. 编译前端 ───────────────────────────────────────────────────────────────
if [[ "$SKIP_BUILD" == false ]]; then
echo -e "${BLU}--> 编译前端...${RST}"
cd frontend
if command -v pnpm &>/dev/null; then
pnpm install --frozen-lockfile --silent
pnpm run build
else
npm install --prefer-offline --silent
npm run build
fi
cd "$SRC_DIR"
rm -f .vite_proxy # 生产模式不走 Vite proxy
echo -e " 前端编译完成 → static/"
else
echo -e " ${YLW}跳过前端编译${RST}"
fi
# ── 3. 同步文件 ───────────────────────────────────────────────────────────────
echo -e "${BLU}--> 同步文件到 ${DEPLOY_DIR} ...${RST}"
mkdir -p "$DEPLOY_DIR"
rsync -a --delete \
--exclude='.venv' \
--exclude='__pycache__' \
--exclude='*.pyc' \
--exclude='.pytest_cache' \
--exclude='tests/' \
--exclude='frontend/node_modules' \
--exclude='copilot_hub.db' \
--exclude='.secret_key' \
--exclude='.vite_proxy' \
--exclude='dev.sh' \
--exclude='deploy.sh' \
--exclude='backups/' \
"$SRC_DIR/" "$DEPLOY_DIR/"
echo -e " 文件同步完成"
# ── 4. 数据库安全保护 + 首次部署迁移 ──────────────────────────────────────────
DB_SRC="$SRC_DIR/copilot_hub.db"
DB_DST="$DEPLOY_DIR/copilot_hub.db"
KEY_SRC="$SRC_DIR/.secret_key"
KEY_DST="$DEPLOY_DIR/.secret_key"
FIRST_DEPLOY=false
# 安全检查:如果目标 DB 已存在且有数据,绝不覆盖
if [[ -s "$DB_DST" ]]; then
DB_SIZE=$(stat -c%s "$DB_DST")
echo -e " ${GRN}✅ 已存在生产数据库(${DB_SIZE} bytes)— 跳过,不会覆盖${RST}"
# 部署时自动备份生产数据库
BACKUP_DIR="$DEPLOY_DIR/backups"
mkdir -p "$BACKUP_DIR"
DEPLOY_TS=$(date +%s)
BACKUP_FILE="${BACKUP_DIR}/copilot_hub_${DEPLOY_TS}_deploy.db"
cp "$DB_DST" "$BACKUP_FILE"
echo -e " ${GRN}已自动备份生产数据库 → ${BACKUP_FILE##*/}${RST}"
# 将 deploy 备份注册到 database_backups 表(供管理面板显示和统一裁剪)
BSIZE=$(stat -c%s "$BACKUP_FILE" 2>/dev/null || echo 0)
python3 -c "
import sqlite3, sys
db='$DB_DST'; fn='copilot_hub_${DEPLOY_TS}_deploy.db'; ts=$DEPLOY_TS; sz=$BSIZE
try:
c=sqlite3.connect(db)
c.execute('INSERT OR IGNORE INTO database_backups(filename,size_bytes,trigger,created_at) VALUES(?,?,\"deploy\",?)',(fn,sz,ts))
c.commit(); c.close()
except Exception as e: print('Warning: could not register backup in DB:', e, file=sys.stderr)
" 2>&1 || true
# 保留最近 10 份 deploy 备份(文件层面兜底)
ls -t "${BACKUP_DIR}"/copilot_hub_*_deploy.db 2>/dev/null | tail -n +11 | xargs -r rm --
echo -e " 备份目录已清理(保留最近 10 份 deploy 备份)"
else
FIRST_DEPLOY=true
if [[ -f "$DB_SRC" && -s "$DB_SRC" ]]; then
cp "$DB_SRC" "$DB_DST"
echo -e " ${GRN}首次部署:已迁移数据库${RST} ($DB_SRC → $DB_DST)"
else
echo -e " 首次部署:DB 将在启动时自动创建(空库)"
fi
fi
if [[ ! -f "$KEY_DST" ]]; then
if [[ -f "$KEY_SRC" ]]; then
cp "$KEY_SRC" "$KEY_DST"
chmod 600 "$KEY_DST"
echo -e " ${GRN}已迁移加密密钥${RST} (.secret_key)"
else
echo -e " 加密密钥将在启动时自动生成(新密钥)"
fi
fi
# ── 5. Python 环境(uv)──────────────────────────────────────────────────────
echo -e "${BLU}--> 安装 Python 依赖 (uv)...${RST}"
cd "$DEPLOY_DIR"
uv sync --no-dev --quiet
echo -e " Python 依赖就绪: ${DEPLOY_DIR}/.venv"
cd "$SRC_DIR"
# ── 6. 安装 systemd 用户 service ─────────────────────────────────────────────
if [[ "$SKIP_SERVICE" == false ]]; then
echo -e "${BLU}--> 安装 systemd 用户 service...${RST}"
SYSTEMD_DIR="$HOME/.config/systemd/user"
mkdir -p "$SYSTEMD_DIR"
cat > "$SYSTEMD_DIR/${SERVICE_NAME}.service" << EOF
[Unit]
Description=Copilot Hub - GitHub Copilot 账号共享代理
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=${DEPLOY_DIR}
ExecStart=${DEPLOY_DIR}/.venv/bin/uvicorn main:app \\
--host 0.0.0.0 \\
--port 8000 \\
--workers 1 \\
--loop uvloop \\
--log-level info \\
--access-log
Restart=on-failure
RestartSec=5
Environment="DB_PATH=${DEPLOY_DIR}/copilot_hub.db"
Environment="SESSION_AFFINITY_TTL=1800"
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable "$SERVICE_NAME" --quiet
systemctl --user restart "$SERVICE_NAME"
sleep 2
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
echo ""
echo -e "${GRN}✅ 部署成功!${RST}"
echo -e " 访问地址: ${GRN}http://0.0.0.0:8000${RST}"
echo -e " 查看日志: journalctl --user -u ${SERVICE_NAME} -f"
echo -e " 停止服务: systemctl --user stop ${SERVICE_NAME}"
echo -e " 重启服务: systemctl --user restart ${SERVICE_NAME}"
else
journalctl --user -u "$SERVICE_NAME" --no-pager -n 30
exit 1
fi
else
echo ""
echo -e "${GRN}✅ 文件部署完成(未安装 service)${RST}"
echo -e " 手动启动: cd ${DEPLOY_DIR} && uv run uvicorn main:app --host 0.0.0.0 --port 8000 --loop uvloop"
fi