Solvaxis commited on
Commit
676f00f
·
1 Parent(s): 8696432

first commit

Browse files
Files changed (4) hide show
  1. Dockerfile +70 -0
  2. README.md +5 -4
  3. launch.sh +56 -0
  4. sync_data.sh +362 -0
Dockerfile ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:19.1.0-alpine3.16
2
+
3
+ # 设置应用目录
4
+ ARG APP_HOME=/home/node/app
5
+
6
+ # 安装系统依赖
7
+ RUN apk add --no-cache gcompat tini git python3 py3-pip bash dos2unix findutils tar curl
8
+
9
+ # 安装HuggingFace Hub
10
+ RUN pip3 install --no-cache-dir huggingface_hub
11
+
12
+ # 确保正确处理内核信号
13
+ ENTRYPOINT [ "tini", "--" ]
14
+
15
+ # 创建应用目录
16
+ WORKDIR ${APP_HOME}
17
+
18
+ # 设置NODE_ENV为production
19
+ ENV NODE_ENV=production
20
+
21
+ # 设置登录凭证环境变量
22
+ ENV USERNAME="admin"
23
+ ENV PASSWORD="password"
24
+
25
+ # 克隆官方SillyTavern仓库(最新版本)
26
+ RUN git clone https://github.com/SillyTavern/SillyTavern.git .
27
+
28
+ # 安装依赖
29
+ RUN echo "*** 安装npm包 ***" && \
30
+ npm install && npm cache clean --force
31
+
32
+ # 添加启动脚本和数据同步脚本
33
+ COPY launch.sh sync_data.sh ./
34
+ RUN chmod +x launch.sh sync_data.sh && \
35
+ dos2unix launch.sh sync_data.sh
36
+
37
+ # 安装生产依赖
38
+ RUN echo "*** 安装生产npm包 ***" && \
39
+ npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force
40
+
41
+ # 创建配置目录
42
+ RUN mkdir -p "config" || true && \
43
+ rm -f "config.yaml" || true && \
44
+ ln -s "./config/config.yaml" "config.yaml" || true
45
+
46
+ # 清理不必要的文件
47
+ RUN echo "*** 清理 ***" && \
48
+ mv "./docker/docker-entrypoint.sh" "./" && \
49
+ rm -rf "./docker" && \
50
+ echo "*** 使docker-entrypoint.sh可执行 ***" && \
51
+ chmod +x "./docker-entrypoint.sh" && \
52
+ echo "*** 转换行尾为Unix格式 ***" && \
53
+ dos2unix "./docker-entrypoint.sh" || true
54
+
55
+ # 修改入口脚本,添加自定义启动脚本
56
+ RUN sed -i 's/# Start the server/.\/launch.sh/g' docker-entrypoint.sh
57
+
58
+ # 创建临时备份目录和数据目录
59
+ RUN mkdir -p /tmp/sillytavern_backup && \
60
+ mkdir -p ${APP_HOME}/data
61
+
62
+ # 设置权限
63
+ RUN chmod -R 777 ${APP_HOME} && \
64
+ chmod -R 777 /tmp/sillytavern_backup
65
+
66
+ # 暴露端口
67
+ EXPOSE 8000
68
+
69
+ # 启动命令
70
+ CMD [ "./docker-entrypoint.sh" ]
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
- title: St
3
- emoji: 🏃
4
- colorFrom: purple
5
- colorTo: yellow
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: SillyTavern
3
+ emoji: 🌍
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: docker
7
  pinned: false
8
+ app_port: 8000
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
launch.sh ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ BASE=/home/node/app
4
+ USERNAME=$(printenv USERNAME)
5
+ PASSWORD=$(printenv PASSWORD)
6
+ HF_TOKEN=$(printenv HF_TOKEN)
7
+ DATASET_ID=$(printenv DATASET_ID)
8
+ SYNC_INTERVAL=$(printenv SYNC_INTERVAL)
9
+
10
+ # 如果没有设置用户名和密码,使用默认值
11
+ if [ -z "${USERNAME}" ]; then
12
+ USERNAME="admin"
13
+ fi
14
+
15
+ if [ -z "${PASSWORD}" ]; then
16
+ PASSWORD="password"
17
+ fi
18
+
19
+ echo
20
+ echo "用户名: ${USERNAME}"
21
+ echo "密码: ${PASSWORD}"
22
+ echo
23
+
24
+ # 确保配置目录存在
25
+ mkdir -p "${BASE}/config"
26
+
27
+ # 如果配置文件不存在,从默认目录复制
28
+ if [ ! -e "${BASE}/config/config.yaml" ]; then
29
+ echo "配置文件不存在,从默认目录复制: config.yaml"
30
+ cp -r "${BASE}/default/config.yaml" "${BASE}/config/config.yaml"
31
+ fi
32
+
33
+ # 修改配置文件中的用户名和密码
34
+ sed -i "s/username: .*/username: \"${USERNAME}\"/" ${BASE}/config/config.yaml
35
+ sed -i "s/password: .*/password: \"${PASSWORD}\"/" ${BASE}/config/config.yaml
36
+
37
+ # 启用基本认证模式,禁用白名单模式
38
+ sed -i "s/whitelistMode: true/whitelistMode: false/" ${BASE}/config/config.yaml
39
+ sed -i "s/basicAuthMode: false/basicAuthMode: true/" ${BASE}/config/config.yaml
40
+ sed -i "s/enableUserAccounts: false/enableUserAccounts: true/" ${BASE}/config/config.yaml
41
+
42
+ # 显示配置文件内容以便验证
43
+ echo "配置文件内容:"
44
+ cat ${BASE}/config/config.yaml
45
+
46
+ # 启动数据同步服务(如果提供了必要的环境变量)
47
+ if [ ! -z "${HF_TOKEN}" ] && [ ! -z "${DATASET_ID}" ]; then
48
+ echo "启动数据同步服务..."
49
+ nohup ${BASE}/sync_data.sh > ${BASE}/sync_data.log 2>&1 &
50
+ echo "数据同步服务已在后台启动"
51
+ else
52
+ echo "未提供HF_TOKEN或DATASET_ID,不启动数据同步服务"
53
+ fi
54
+
55
+ # 正常启动服务器
56
+ exec node server.js --listen "$@"
sync_data.sh ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ # 添加更详细的日志
4
+ log_info() {
5
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1"
6
+ }
7
+
8
+ log_error() {
9
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1"
10
+ }
11
+
12
+ # 检查环境变量
13
+ if [ -z "$HF_TOKEN" ] || [ -z "$DATASET_ID" ]; then
14
+ log_error "未启用备份功能 - 缺少HF_TOKEN或DATASET_ID环境变量"
15
+ log_info "HF_TOKEN=${HF_TOKEN:0:3}... DATASET_ID=${DATASET_ID}"
16
+ exit 0
17
+ fi
18
+
19
+ # 创建临时目录
20
+ TEMP_DIR="/tmp/sillytavern_backup"
21
+ DATA_DIR="/home/node/app/data"
22
+
23
+ # 确保目录存在并有正确权限
24
+ mkdir -p $TEMP_DIR
25
+ chmod -R 777 $TEMP_DIR
26
+ mkdir -p $DATA_DIR
27
+ chmod -R 777 $DATA_DIR
28
+
29
+ log_info "临时目录: $TEMP_DIR"
30
+ log_info "数据目录: $DATA_DIR"
31
+ log_info "HF_TOKEN: ${HF_TOKEN:0:5}..."
32
+ log_info "DATASET_ID: $DATASET_ID"
33
+
34
+ # 安装python和huggingface_hub
35
+ if ! command -v python3 > /dev/null 2>&1; then
36
+ log_info "正在安装Python..."
37
+ apk add --no-cache python3 py3-pip
38
+ else
39
+ log_info "Python3已安装: $(python3 --version)"
40
+ fi
41
+
42
+ # 确保pip已安装
43
+ if ! command -v pip3 > /dev/null 2>&1; then
44
+ log_info "正在安装pip..."
45
+ apk add --no-cache py3-pip
46
+ else
47
+ log_info "Pip3已安装: $(pip3 --version)"
48
+ fi
49
+
50
+ # 安装或更新huggingface_hub
51
+ log_info "正在安装/更新huggingface_hub..."
52
+ pip3 install --no-cache-dir --upgrade huggingface_hub
53
+ log_info "huggingface_hub安装完成"
54
+
55
+ # 测试huggingface_hub是否正常工作
56
+ if ! python3 -c "import huggingface_hub; print('huggingface_hub版本:', huggingface_hub.__version__)" > /dev/null 2>&1; then
57
+ log_error "huggingface_hub导入失败,正在重试安装..."
58
+ pip3 install --no-cache-dir huggingface_hub
59
+ fi
60
+
61
+ # 测试权限是否正常
62
+ touch "${TEMP_DIR}/test_file" && rm "${TEMP_DIR}/test_file"
63
+ if [ $? -ne 0 ]; then
64
+ log_error "临时目录权限测试失败,正在修复权限..."
65
+ chmod -R 777 $TEMP_DIR
66
+ fi
67
+
68
+ # 测试与HuggingFace API的连接
69
+ log_info "正在测试与HuggingFace API的连接..."
70
+ python3 -c "
71
+ from huggingface_hub import HfApi
72
+ try:
73
+ api = HfApi(token='$HF_TOKEN')
74
+ user_info = api.whoami()
75
+ print(f'成功连接到HuggingFace API,用户: {user_info}')
76
+ except Exception as e:
77
+ print(f'连接HuggingFace API失败: {str(e)}')
78
+ exit(1)
79
+ "
80
+ if [ $? -ne 0 ]; then
81
+ log_error "HuggingFace API连接测试失败,请检查令牌是否有效"
82
+ else
83
+ log_info "HuggingFace API连接测试成功"
84
+ fi
85
+
86
+ # 生成唯一的测试文件名
87
+ TEST_FILE_NAME="test_file_$(date +%s)"
88
+
89
+ # 测试数据集权限
90
+ log_info "正在测试Dataset权限..."
91
+ python3 -c "
92
+ from huggingface_hub import HfApi
93
+ try:
94
+ api = HfApi(token='$HF_TOKEN')
95
+
96
+ # 创建本地测试文件
97
+ with open('$TEMP_DIR/test_file', 'w') as f:
98
+ f.write('test')
99
+
100
+ # 上传测试文件
101
+ test_file_name = '$TEST_FILE_NAME'
102
+ print(f'正在上传测试文件: {test_file_name}')
103
+
104
+ api.upload_file(
105
+ path_or_fileobj='$TEMP_DIR/test_file',
106
+ path_in_repo=test_file_name,
107
+ repo_id='$DATASET_ID',
108
+ repo_type='dataset'
109
+ )
110
+ print('成功上传测试文件到Dataset')
111
+
112
+ # 删除已上传的测试文件
113
+ print('正在删除测试文件...')
114
+ api.delete_file(
115
+ path_in_repo=test_file_name,
116
+ repo_id='$DATASET_ID',
117
+ repo_type='dataset'
118
+ )
119
+ print('已成功删除测试文件')
120
+
121
+ except Exception as e:
122
+ print(f'Dataset权限测试失败: {str(e)}')
123
+ exit(1)
124
+ "
125
+ if [ $? -ne 0 ]; then
126
+ log_error "Dataset权限测试失败,请检查DATASET_ID是否正确且有写入权限"
127
+ else
128
+ log_info "Dataset权限测试成功,测试文件已清理"
129
+ fi
130
+
131
+ # 确保本地测试文件被删除
132
+ rm -f "$TEMP_DIR/test_file"
133
+
134
+ # 上传备份
135
+ upload_backup() {
136
+ file_path="$1"
137
+ file_name="$2"
138
+
139
+ if [ ! -f "$file_path" ]; then
140
+ log_error "备份文件不存在: $file_path"
141
+ return 1
142
+ fi
143
+
144
+ log_info "开始上传备份: $file_name ($(du -h $file_path | cut -f1))"
145
+
146
+ python3 -c "
147
+ from huggingface_hub import HfApi
148
+ import sys
149
+ import os
150
+ import time
151
+
152
+ def manage_backups(api, repo_id, max_files=10):
153
+ try:
154
+ files = api.list_repo_files(repo_id=repo_id, repo_type='dataset')
155
+ backup_files = [f for f in files if f.startswith('sillytavern_backup_') and f.endswith('.tar.gz')]
156
+ backup_files.sort()
157
+
158
+ if len(backup_files) >= max_files:
159
+ files_to_delete = backup_files[:(len(backup_files) - max_files + 1)]
160
+ for file_to_delete in files_to_delete:
161
+ try:
162
+ api.delete_file(path_in_repo=file_to_delete, repo_id=repo_id, repo_type='dataset')
163
+ print(f'已删除旧备份: {file_to_delete}')
164
+ except Exception as e:
165
+ print(f'删除 {file_to_delete} 时出错: {str(e)}')
166
+ except Exception as e:
167
+ print(f'管理备份文件时出错: {str(e)}')
168
+
169
+ token='$HF_TOKEN'
170
+ repo_id='$DATASET_ID'
171
+
172
+ try:
173
+ api = HfApi(token=token)
174
+
175
+ # 检查文件大小
176
+ file_size = os.path.getsize('$file_path')
177
+ print(f'备份文件大小: {file_size / (1024*1024):.2f} MB')
178
+
179
+ # 确认Dataset存在
180
+ try:
181
+ dataset_info = api.dataset_info(repo_id=repo_id)
182
+ print(f'Dataset信息: {dataset_info.id}')
183
+ except Exception as e:
184
+ print(f'获取Dataset信息失败: {str(e)}')
185
+
186
+ start_time = time.time()
187
+ print(f'开始上传: {start_time}')
188
+
189
+ # 上传文件
190
+ api.upload_file(
191
+ path_or_fileobj='$file_path',
192
+ path_in_repo='$file_name',
193
+ repo_id=repo_id,
194
+ repo_type='dataset'
195
+ )
196
+
197
+ end_time = time.time()
198
+ print(f'上传完成,耗时: {end_time - start_time:.2f} 秒')
199
+ print(f'成功上传 $file_name')
200
+
201
+ # 管理备份
202
+ manage_backups(api, repo_id)
203
+ except Exception as e:
204
+ print(f'上传文件时出错: {str(e)}')
205
+ sys.exit(1)
206
+ "
207
+ if [ $? -ne 0 ]; then
208
+ log_error "备份上传失败"
209
+ return 1
210
+ else
211
+ log_info "备份上传成功"
212
+ return 0
213
+ fi
214
+ }
215
+
216
+ # 下载最新备份
217
+ download_latest_backup() {
218
+ log_info "开始下载最新备份..."
219
+
220
+ python3 -c "
221
+ from huggingface_hub import HfApi
222
+ import sys
223
+ import os
224
+ import tarfile
225
+ import tempfile
226
+ import time
227
+
228
+ try:
229
+ api = HfApi(token='$HF_TOKEN')
230
+ print('已创建API实例')
231
+
232
+ # 列出仓库文件
233
+ try:
234
+ files = api.list_repo_files(repo_id='$DATASET_ID', repo_type='dataset')
235
+ print(f'仓库文件数量: {len(files)}')
236
+ except Exception as e:
237
+ print(f'列出仓库文件失败: {str(e)}')
238
+ sys.exit(1)
239
+
240
+ backup_files = [f for f in files if f.startswith('sillytavern_backup_') and f.endswith('.tar.gz')]
241
+ print(f'找到备份文件数量: {len(backup_files)}')
242
+
243
+ if not backup_files:
244
+ print('未找到备份文件')
245
+ sys.exit(0)
246
+
247
+ # 按名称排序(实际上是按时间戳排序)
248
+ latest_backup = sorted(backup_files)[-1]
249
+ print(f'最新备份文件: {latest_backup}')
250
+
251
+ with tempfile.TemporaryDirectory() as temp_dir:
252
+ print(f'创建临时目录: {temp_dir}')
253
+
254
+ start_time = time.time()
255
+ print(f'开始下载: {start_time}')
256
+
257
+ # 下载文件
258
+ try:
259
+ filepath = api.hf_hub_download(
260
+ repo_id='$DATASET_ID',
261
+ filename=latest_backup,
262
+ repo_type='dataset',
263
+ local_dir=temp_dir
264
+ )
265
+ print(f'文件下载到: {filepath}')
266
+ except Exception as e:
267
+ print(f'下载文件失败: {str(e)}')
268
+ sys.exit(1)
269
+
270
+ end_time = time.time()
271
+ print(f'下载完成,耗时: {end_time - start_time:.2f} 秒')
272
+
273
+ if filepath and os.path.exists(filepath):
274
+ # 确保目标目录存在
275
+ os.makedirs('$DATA_DIR', exist_ok=True)
276
+
277
+ # 检查文件权限
278
+ print(f'文件权限: {oct(os.stat(filepath).st_mode)[-3:]}')
279
+
280
+ # 解压文件
281
+ try:
282
+ with tarfile.open(filepath, 'r:gz') as tar:
283
+ print('开始解压文件...')
284
+ tar.extractall('$DATA_DIR')
285
+ print('文件解压完成')
286
+ except Exception as e:
287
+ print(f'解压文件失败: {str(e)}')
288
+ sys.exit(1)
289
+
290
+ print(f'成功从 {latest_backup} 恢复备份')
291
+ else:
292
+ print('下载的文件路径无效')
293
+ sys.exit(1)
294
+ except Exception as e:
295
+ print(f'下载备份过程中出错: {str(e)}')
296
+ sys.exit(1)
297
+ "
298
+ if [ $? -ne 0 ]; then
299
+ log_error "备份下载失败"
300
+ return 1
301
+ else
302
+ log_info "备份下载成功"
303
+ return 0
304
+ fi
305
+ }
306
+
307
+ # 首次启动时下载最新备份
308
+ log_info "正在从HuggingFace下载最新备份..."
309
+ download_latest_backup
310
+
311
+ # 同步函数
312
+ sync_data() {
313
+ log_info "数据同步服务已启动"
314
+
315
+ while true; do
316
+ log_info "开始同步进程,时间: $(date)"
317
+
318
+ if [ -d "$DATA_DIR" ]; then
319
+ timestamp=$(date +%Y%m%d_%H%M%S)
320
+ backup_file="sillytavern_backup_${timestamp}.tar.gz"
321
+ backup_path="${TEMP_DIR}/${backup_file}"
322
+
323
+ log_info "创建备份文件: $backup_path"
324
+
325
+ # 检查数据目录内容
326
+ file_count=$(find "$DATA_DIR" -type f | wc -l)
327
+ log_info "数据目录文件数量: $file_count"
328
+
329
+ if [ "$file_count" -eq 0 ]; then
330
+ log_info "数据目录为空,跳过备份"
331
+ else
332
+ # 压缩数据目录
333
+ tar -czf "$backup_path" -C "$DATA_DIR" .
334
+ if [ $? -ne 0 ]; then
335
+ log_error "创建压缩文件失败"
336
+ else
337
+ log_info "压缩文件创建成功: $(du -h $backup_path | cut -f1)"
338
+
339
+ # 上传备份
340
+ log_info "正在上传备份到HuggingFace..."
341
+ upload_backup "$backup_path" "$backup_file"
342
+
343
+ # 删除临时备份文件
344
+ rm -f "$backup_path"
345
+ log_info "已删除临时备份文件"
346
+ fi
347
+ fi
348
+ else
349
+ log_error "数据目录不存在: $DATA_DIR"
350
+ mkdir -p "$DATA_DIR"
351
+ chmod -R 777 "$DATA_DIR"
352
+ fi
353
+
354
+ # 设置同步间隔
355
+ SYNC_INTERVAL=${SYNC_INTERVAL:-3600}
356
+ log_info "下次同步将在 ${SYNC_INTERVAL} 秒后进行..."
357
+ sleep $SYNC_INTERVAL
358
+ done
359
+ }
360
+
361
+ # 启动同步进程
362
+ sync_data