javaeeduke commited on
Commit
0d5747a
·
verified ·
1 Parent(s): 660ceda

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +41 -94
Dockerfile CHANGED
@@ -1,26 +1,13 @@
1
- FROM node:22-alpine
2
-
3
- # sqlite: 一致性备份用;rclone: 连接 Google Drive
4
- RUN apk add --no-cache sqlite rclone
5
-
6
- WORKDIR /app
7
- RUN npm install -g omniroute
8
-
9
- ENV PORT=7860
10
- ENV HOST=0.0.0.0
11
- ENV NODE_ENV=production
12
-
13
- EXPOSE 7860
14
-
15
- # ───────── 下载 + 反向代理服务(监听 7860)─────────
16
  RUN cat > /app/download_server.js << 'EOF'
17
  const http = require('http');
18
  const fs = require('fs');
19
  const path = require('path');
 
20
 
21
- const PORT = 7860; // HF 唯一暴露端口
22
- const UPSTREAM_PORT = 8860; // omniroute 内部端口
23
  const ALLOWED_DIRS = ['/data', '/root/.omniroute'];
 
24
 
25
  function safeResolvePath(filename) {
26
  if (!filename || filename.includes('/') || filename.includes('\\') || filename.includes('..')) return null;
@@ -31,10 +18,45 @@ function safeResolvePath(filename) {
31
  return null;
32
  }
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  const server = http.createServer((req, res) => {
35
  const url = new URL(req.url, `http://localhost:${PORT}`);
36
  const route = url.pathname;
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  if (route === '/list') {
39
  const result = {};
40
  for (const dir of ALLOWED_DIRS) {
@@ -65,11 +87,10 @@ const server = http.createServer((req, res) => {
65
  'Content-Length': stat.size,
66
  });
67
  fs.createReadStream(fullPath).pipe(res);
68
- console.log(`[download] ${fullPath} (${stat.size} bytes)`);
69
  return;
70
  }
71
 
72
- // 其余请求反向代理给 omniroute (8860)
73
  const proxyReq = http.request(
74
  { hostname: '127.0.0.1', port: UPSTREAM_PORT, path: req.url, method: req.method, headers: req.headers },
75
  proxyRes => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }
@@ -82,80 +103,6 @@ const server = http.createServer((req, res) => {
82
  });
83
 
84
  server.listen(PORT, '0.0.0.0', () => {
85
- console.log(`[server] listening ${PORT}; routes /list /download, proxy -> ${UPSTREAM_PORT}`);
86
  });
87
  EOF
88
-
89
- # ───────── 启动脚本 ─────────
90
- RUN cat > /app/entrypoint.sh << 'EOF'
91
- #!/bin/sh
92
- set -u
93
-
94
- mkdir -p /root/.omniroute /data /root/.config/rclone
95
-
96
- # ── 写入 rclone 配置(来自 HF Secret: RCLONE_CONF)──
97
- if [ -n "${RCLONE_CONF:-}" ]; then
98
- printf '%s\n' "$RCLONE_CONF" > /root/.config/rclone/rclone.conf
99
- echo "✅ 已写入 rclone 配置"
100
- else
101
- echo "⚠️ 未设置 RCLONE_CONF,Google Drive 备份将不可用"
102
- fi
103
-
104
- # ── 固定加密 key(来自 HF Secret: STORAGE_ENCRYPTION_KEY)──
105
- # 不固定的话,重启会生成新 key,导致恢复的旧库无法解密!
106
- if [ -n "${STORAGE_ENCRYPTION_KEY:-}" ]; then
107
- echo "STORAGE_ENCRYPTION_KEY=$STORAGE_ENCRYPTION_KEY" > /root/.omniroute/.env
108
- echo "✅ 已写入固定 STORAGE_ENCRYPTION_KEY"
109
- else
110
- echo "⚠️ 未设置 STORAGE_ENCRYPTION_KEY,重启后恢复的库可能解不开!"
111
- fi
112
-
113
- # Google Drive 备份目录:远程名 om,文件夹 om-backup
114
- GD_REMOTE="om:om-backup"
115
-
116
- # ── 开机:从 Google Drive 拉取备份覆盖恢复 ──
117
- if [ -n "${RCLONE_CONF:-}" ]; then
118
- if rclone copyto "$GD_REMOTE/omni_storage.sqlite" /root/.omniroute/storage.sqlite --no-traverse 2>/dev/null; then
119
- echo "✅ 从 GDrive 恢复 storage.sqlite"
120
- # 恢复后清掉残留 WAL,避免脏数据覆盖
121
- rm -f /root/.omniroute/storage.sqlite-wal /root/.omniroute/storage.sqlite-shm
122
- else
123
- echo "⚠️ GDrive 无 storage 备份,跳过恢复"
124
- fi
125
- if rclone copyto "$GD_REMOTE/omni_settings.json" /root/.omniroute/settings.json --no-traverse 2>/dev/null; then
126
- echo "✅ 从 GDrive 恢复 settings.json"
127
- else
128
- echo "⚠️ GDrive 无 settings 备份,跳过恢复"
129
- fi
130
- fi
131
-
132
- # ── 每 60 秒:一致性快照 + 覆盖上传到 Google Drive ──
133
- (while true; do
134
- sleep 60
135
- if [ -f /root/.omniroute/storage.sqlite ]; then
136
- if sqlite3 /root/.omniroute/storage.sqlite ".backup '/data/omni_storage.sqlite'" 2>/dev/null; then
137
- if [ -n "${RCLONE_CONF:-}" ]; then
138
- rclone copyto /data/omni_storage.sqlite "$GD_REMOTE/omni_storage.sqlite" 2>/dev/null \
139
- && echo "💾 [backup] storage.sqlite → GDrive(覆盖)" \
140
- || echo "⚠️ [backup] GDrive 上传失败"
141
- fi
142
- fi
143
- fi
144
- if [ -f /root/.omniroute/settings.json ]; then
145
- cp /root/.omniroute/settings.json /data/omni_settings.json
146
- if [ -n "${RCLONE_CONF:-}" ]; then
147
- rclone copyto /data/omni_settings.json "$GD_REMOTE/omni_settings.json" 2>/dev/null \
148
- && echo "💾 [backup] settings.json → GDrive(覆盖)"
149
- fi
150
- fi
151
- done) &
152
-
153
- # ── 启动下载+代理服务(7860)──
154
- node /app/download_server.js &
155
-
156
- # ── 前台启动 omniroute(内部 8860)──
157
- exec env PORT=8860 omniroute
158
- EOF
159
- RUN chmod +x /app/entrypoint.sh
160
-
161
- CMD ["/app/entrypoint.sh"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  RUN cat > /app/download_server.js << 'EOF'
2
  const http = require('http');
3
  const fs = require('fs');
4
  const path = require('path');
5
+ const { execFile } = require('child_process');
6
 
7
+ const PORT = 7860;
8
+ const UPSTREAM_PORT = 8860;
9
  const ALLOWED_DIRS = ['/data', '/root/.omniroute'];
10
+ const GD_REMOTE = 'om:om-backup';
11
 
12
  function safeResolvePath(filename) {
13
  if (!filename || filename.includes('/') || filename.includes('\\') || filename.includes('..')) return null;
 
18
  return null;
19
  }
20
 
21
+ // 执行一次:sqlite 一致性快照 -> 上传 Google Drive
22
+ function runBackup(cb) {
23
+ const src = '/root/.omniroute/storage.sqlite';
24
+ const tmp = '/data/omni_storage.sqlite';
25
+ if (!fs.existsSync(src)) return cb(new Error('storage.sqlite 不存在'));
26
+
27
+ execFile('sqlite3', [src, `.backup '${tmp}'`], (e1) => {
28
+ if (e1) return cb(new Error('sqlite 快照失败: ' + e1.message));
29
+ execFile('rclone', ['copyto', tmp, `${GD_REMOTE}/omni_storage.sqlite`], (e2) => {
30
+ if (e2) return cb(new Error('rclone 上传失败: ' + e2.message));
31
+ // settings.json 一并备份(存在才传)
32
+ const setSrc = '/root/.omniroute/settings.json';
33
+ if (fs.existsSync(setSrc)) {
34
+ execFile('rclone', ['copyto', setSrc, `${GD_REMOTE}/omni_settings.json`], () => cb(null));
35
+ } else {
36
+ cb(null);
37
+ }
38
+ });
39
+ });
40
+ }
41
+
42
  const server = http.createServer((req, res) => {
43
  const url = new URL(req.url, `http://localhost:${PORT}`);
44
  const route = url.pathname;
45
 
46
+ // ── 手动触发备份 ──
47
+ if (route === '/backup') {
48
+ runBackup((err) => {
49
+ if (err) {
50
+ res.writeHead(500, { 'Content-Type': 'application/json' });
51
+ res.end(JSON.stringify({ ok: false, error: err.message }));
52
+ } else {
53
+ res.writeHead(200, { 'Content-Type': 'application/json' });
54
+ res.end(JSON.stringify({ ok: true, msg: 'backup -> GDrive 成功', time: new Date().toISOString() }));
55
+ }
56
+ });
57
+ return;
58
+ }
59
+
60
  if (route === '/list') {
61
  const result = {};
62
  for (const dir of ALLOWED_DIRS) {
 
87
  'Content-Length': stat.size,
88
  });
89
  fs.createReadStream(fullPath).pipe(res);
 
90
  return;
91
  }
92
 
93
+ // 其余反向代理给 omniroute
94
  const proxyReq = http.request(
95
  { hostname: '127.0.0.1', port: UPSTREAM_PORT, path: req.url, method: req.method, headers: req.headers },
96
  proxyRes => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }
 
103
  });
104
 
105
  server.listen(PORT, '0.0.0.0', () => {
106
+ console.log(`[server] listening ${PORT}; routes /list /download /backup, proxy -> ${UPSTREAM_PORT}`);
107
  });
108
  EOF