-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_docker.py
More file actions
230 lines (180 loc) · 6.54 KB
/
build_docker.py
File metadata and controls
230 lines (180 loc) · 6.54 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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/env python3
"""
构建脚本:本地构建 wheel,并生成包含 Docker 部署所需文件的压缩包。
使用方法:
python build_docker.py [--no-package] [--format tar.gz]
"""
import argparse
import os
import shutil
import subprocess
import sys
import tarfile
import zipfile
from datetime import datetime
from pathlib import Path
PACKAGE_FILES = [
"Dockerfile",
"docker-compose.yml",
".dockerignore",
"docker-entrypoint.sh",
"README.md",
"configs",
"dist",
]
PROJECT_ROOT = Path(__file__).parent.resolve()
DIST_DIR = PROJECT_ROOT / "dist"
def print_step(message: str) -> None:
"""打印步骤信息"""
print(f"\n{'=' * 60}")
print(f" {message}")
print(f"{'=' * 60}\n")
def check_uv() -> None:
"""检查 uv 是否可用"""
try:
result = subprocess.run(
["uv", "--version"],
capture_output=True,
text=True,
check=True,
)
print(f"✓ 找到 uv: {result.stdout.strip()}")
except (subprocess.CalledProcessError, FileNotFoundError):
print("✗ 错误: 未找到 uv,请先安装 uv")
sys.exit(1)
def build_wheel() -> None:
"""本地构建 wheel"""
print_step("构建 wheel")
if DIST_DIR.exists():
shutil.rmtree(DIST_DIR)
subprocess.run(["uv", "build", "--wheel"], cwd=PROJECT_ROOT, check=True)
wheel_files = sorted(DIST_DIR.glob("*.whl"))
if not wheel_files:
print("✗ 错误: 未生成 wheel 文件")
sys.exit(1)
for wheel in wheel_files:
print(f" ✓ 生成: {wheel.name}")
def prepare_package_directory() -> Path:
"""准备打包目录,复制所有必需文件"""
print_step("准备打包目录")
package_dir = PROJECT_ROOT / "docker-package"
if package_dir.exists():
shutil.rmtree(package_dir)
package_dir.mkdir(parents=True, exist_ok=True)
print(f" 创建打包目录: {package_dir}")
copied_count = 0
for item in PACKAGE_FILES:
source = PROJECT_ROOT / item
dest = package_dir / item
if not source.exists():
print(f" ⚠ 警告: {item} 不存在,跳过")
continue
if source.is_dir():
shutil.copytree(source, dest, dirs_exist_ok=True)
print(f" ✓ 复制目录: {item}")
else:
shutil.copy2(source, dest)
print(f" ✓ 复制文件: {item}")
copied_count += 1
(package_dir / "storage").mkdir(exist_ok=True)
print(" ✓ 创建目录: storage/")
readme_path = package_dir / "DEPLOY.md"
readme_path.write_text(_deployment_readme(), encoding="utf-8")
print(" ✓ 创建文件: DEPLOY.md")
print(f"\n✓ 打包目录准备完成,共 {copied_count + 2} 个项目")
return package_dir
def create_package(package_dir: Path, output_format: str = "tar.gz") -> Path:
"""创建压缩包"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
package_name = f"simprint-extension-sync-server-docker-{timestamp}"
if output_format == "zip":
archive_path = PROJECT_ROOT / f"{package_name}.zip"
print_step(f"创建 ZIP 压缩包: {archive_path.name}")
with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(package_dir):
dirs[:] = [d for d in dirs if not d.startswith(".")]
for file in files:
file_path = Path(root) / file
arcname = file_path.relative_to(package_dir)
zipf.write(file_path, arcname)
print(f" 添加: {arcname}")
elif output_format == "tar.gz":
archive_path = PROJECT_ROOT / f"{package_name}.tar.gz"
print_step(f"创建 TAR.GZ 压缩包: {archive_path.name}")
with tarfile.open(archive_path, "w:gz") as tar:
for root, dirs, files in os.walk(package_dir):
dirs[:] = [d for d in dirs if not d.startswith(".")]
for file in files:
file_path = Path(root) / file
arcname = file_path.relative_to(package_dir)
tar.add(file_path, arcname=arcname, recursive=False)
print(f" 添加: {arcname}")
else:
raise ValueError(f"不支持的压缩格式: {output_format}")
size_mb = archive_path.stat().st_size / (1024 * 1024)
print(f"\n✓ 压缩包创建成功: {archive_path.name} ({size_mb:.2f} MB)")
return archive_path
def _deployment_readme() -> str:
created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return f"""# Simprint Extension Sync Server Docker 部署包
本压缩包包含构建和运行 Docker 容器所需文件。
## 包含内容
- Dockerfile、docker-compose.yml、.dockerignore
- docker-entrypoint.sh
- configs/
- dist/*.whl
- storage/(SQLite 持久化目录)
## 使用方法
1. 解压到目标目录
2. 复制配置模板并修改:
```bash
cp configs/config.example.yaml configs/config.prod.yaml
cp configs/extensions.example.yaml configs/extensions.yaml
```
3. 编辑 `configs/config.prod.yaml`,至少设置:
- `backend.api_url`
- `backend.api_key_id` / `backend.api_key_secret`(如后端要求认证)
4. 构建并启动:
```bash
docker compose up -d --build
```
5. 验证:
```bash
curl http://127.0.0.1:8080/health
```
生成时间: {created_at}
"""
def main() -> None:
parser = argparse.ArgumentParser(
description="生成 Simprint Extension Sync Server Docker 部署包"
)
parser.add_argument(
"--no-package",
action="store_true",
help="不生成压缩包,只准备 docker-package 目录",
)
parser.add_argument(
"--format",
choices=["zip", "tar.gz"],
default="tar.gz",
help="压缩包格式 (默认: tar.gz)",
)
args = parser.parse_args()
print_step("Simprint Extension Sync Server Docker 打包脚本")
print(f"项目目录: {PROJECT_ROOT}")
check_uv()
build_wheel()
package_dir = prepare_package_directory()
if args.no_package:
print_step("打包目录已准备完成")
print(f"目录位置: {package_dir}")
return
archive_path = create_package(package_dir, args.format)
print_step("清理临时文件")
shutil.rmtree(package_dir)
print(" ✓ 已清理临时打包目录")
print_step("打包完成!")
print(f"压缩包位置: {archive_path}")
print("现在可以将压缩包上传到服务器,准备 YAML 配置后执行: docker compose up -d --build")
if __name__ == "__main__":
main()