modify: Docker 部署脚本更新

This commit is contained in:
2026-04-27 11:01:33 +08:00
parent 3fbd7764af
commit 7b6ac7e1b9
16 changed files with 411 additions and 117 deletions
+57
View File
@@ -0,0 +1,57 @@
FROM debian:bookworm-slim AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates && \
rm -rf /var/lib/apt/lists/*
COPY build/chongming /tmp/chongming
RUN mkdir -p /tmp/rootfs/app && \
# === 临时目录 ===
mkdir -p /tmp/rootfs/tmp && \
chmod 1777 /tmp/rootfs/tmp && \
# === 数据目录 ===
mkdir -p /tmp/rootfs/app/data && \
chmod 777 /tmp/rootfs/app/data && \
# === 解释器目录 ===
mkdir -p /tmp/rootfs/lib64 && \
mkdir -p /tmp/rootfs/lib/x86_64-linux-gnu && \
cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
ln -s /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /tmp/rootfs/lib64/ld-linux-x86-64.so.2 && \
# === 依赖库 ===
cp /lib/x86_64-linux-gnu/libdl.so.2 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
cp /lib/x86_64-linux-gnu/libz.so.1 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
cp /lib/x86_64-linux-gnu/libpthread.so.0 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
cp /lib/x86_64-linux-gnu/librt.so.1 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
cp /lib/x86_64-linux-gnu/libm.so.6 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
cp /lib/x86_64-linux-gnu/libutil.so.1 /tmp/rootfs/lib/x86_64-linux-gnu/ && \
cp /lib/x86_64-linux-gnu/libnss_dns.so.2 /tmp/rootfs/lib/x86_64-linux-gnu/ 2>/dev/null || true && \
cp /lib/x86_64-linux-gnu/libnss_files.so.2 /tmp/rootfs/lib/x86_64-linux-gnu/ 2>/dev/null || true && \
cp /lib/x86_64-linux-gnu/libresolv.so.2 /tmp/rootfs/lib/x86_64-linux-gnu/ 2>/dev/null || true && \
# === 时区数据 ===
mkdir -p /tmp/rootfs/usr/share/zoneinfo && \
cp -r /usr/share/zoneinfo/Asia /tmp/rootfs/usr/share/zoneinfo/ && \
cp /usr/share/zoneinfo/UTC /tmp/rootfs/usr/share/zoneinfo/ && \
# === 证书和 DNS ===
mkdir -p /tmp/rootfs/etc/ssl/certs && \
cp /etc/ssl/certs/ca-certificates.crt /tmp/rootfs/etc/ssl/certs/ && \
echo "hosts: files dns" > /tmp/rootfs/etc/nsswitch.conf && \
# === /proc /sys /dev ===
mkdir -p /tmp/rootfs/proc /tmp/rootfs/sys /tmp/rootfs/dev
FROM scratch
COPY --from=builder /tmp/rootfs/ /
COPY build/chongming /app/chongming
COPY build/config.toml /app/config.toml
COPY build/resources/ /app/resources/
COPY build/static.svfs /app/static.svfs
COPY build/data /app/data
WORKDIR /app
EXPOSE 8000
CMD ["./chongming"]
+2 -5
View File
@@ -76,9 +76,6 @@ docker load -i chongming_latest.tar
运行镜像 运行镜像
```shell ```shell
mkdir -p /data/chongming sudo chmod 777 ./deploy.sh
docker run -d -p 8000:8000 \ ./deploy.sh
-v /data/chongming/database.db:/app/database.db \
-v /data/chongming/uploads:/app/uploads \
--name chongming chongming:latest
``` ```
+28 -28
View File
@@ -1,27 +1,27 @@
[default] [default]
app.debug = false
app.description = "基于 FastAPI 的 chongming 打包脚手架"
app.name = "chongming" app.name = "chongming"
app.version = "chongming" app.version = "chongming"
app.description = "基于 FastAPI 的 chongming 打包脚手架"
app.debug = false
env = "development" env = "development"
prefix = "/api/v1" prefix = "/api/v1"
upload_path = "uploads" upload_path = "uploads"
[database] [database]
database_path = "./data/database.db"
type = "sqlite" type = "sqlite"
url = "sqlite+aiosqlite:///./database.db" url = "sqlite+aiosqlite:///./data/database.db"
generate_schemas = true # 生产环境使用迁移工具
[database.sqlite] [database.sqlite]
connect_args.check_same_thread = false
connect_args.timeout = 15
echo = true echo = true
echo_pool = true echo_pool = true
pool_size = 20
max_overflow = 10 max_overflow = 10
pool_timeout = 30
pool_recycle = 300
pool_pre_ping = true pool_pre_ping = true
connect_args.timeout = 15 pool_recycle = 300
connect_args.check_same_thread = false pool_size = 20
pool_timeout = 30
[scheduler] [scheduler]
job_store_path = "scheduler_jobs.db" job_store_path = "scheduler_jobs.db"
@@ -30,61 +30,61 @@ job_store_path = "scheduler_jobs.db"
cache_store_path = "./" cache_store_path = "./"
[security] [security]
secret_key = "your-secret-key-change-in-production"
algorithm = "HS256"
access_token_expire_minutes = 30 access_token_expire_minutes = 30
refresh_token_expire_days = 7 algorithm = "HS256"
max_sessions_per_user = 5 max_sessions_per_user = 5
refresh_token_expire_days = 7
secret_key = "your-secret-key-change-in-production"
[development] [development]
env = "development"
debug = true debug = true
env = "development"
[development.server] [development.server]
access_log = false
host = "127.0.0.1" host = "127.0.0.1"
port = 8000 port = 8000
reload = true reload = true
access_log = false
[development.logging] [development.logging]
level = "DEBUG"
file = "logs" file = "logs"
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
level = "DEBUG"
[development.cors] [development.cors]
allow_origins = ["*"]
allow_credentials = true allow_credentials = true
allow_methods = ["*"]
allow_headers = ["*"] allow_headers = ["*"]
allow_methods = ["*"]
allow_origins = ["*"]
[production] [production]
env = "production"
debug = false debug = false
env = "production"
[production.server] [production.server]
access_log = false
host = "0.0.0.0" host = "0.0.0.0"
port = 8000 port = 8000
reload = false reload = false
workers = 4 workers = 4
access_log = false
[production.logging] [production.logging]
level = "INFO" backup_count = 5 # 最多保存 10 个日志文件
file = "logs" file = "logs"
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
max_size = 10485760 # 10MB level = "INFO"
backup_count = 5 # 最多保存 10 个日志文件 max_size = 10485760 # 10MB
[production.cors] [production.cors]
allow_origins = ["https://yourdomain.com", "http://localhost:8000", "http://127.0.0.1:8000"]
allow_credentials = true allow_credentials = true
allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_headers = ["Authorization", "Content-Type"] allow_headers = ["Authorization", "Content-Type"]
allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_origins = ["https://yourdomain.com", "http://localhost:8000", "http://127.0.0.1:8000"]
[production.module_system] [production.module_system]
type = "module-bank"
path = ["resources/app.mbank", "resources/plugins.mbank"] path = ["resources/app.mbank", "resources/plugins.mbank"]
type = "module-bank"
[production.file_system] [production.file_system]
type = "svfs"
path = "static.svfs" path = "static.svfs"
type = "svfs"
Executable
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
# deploy.sh - chongming 快速部署脚本
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 配置
IMAGE="chongming:latest"
CONTAINER="chongming"
PORT=8000
DATA_DIR="${HOME}/chongming-data"
# 检查 docker
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装,请先安装 Docker"
exit 1
fi
# 检查镜像
if ! docker image inspect "$IMAGE" &> /dev/null; then
log_error "镜像 $IMAGE 不存在,请先构建: docker build -t $IMAGE ."
exit 1
fi
# 1. 停止并删除旧容器
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
log_warn "发现旧容器,正在停止并删除..."
docker stop "$CONTAINER" 2>/dev/null || true
docker rm "$CONTAINER" 2>/dev/null || true
fi
# 2. 创建目录
log_info "创建数据目录..."
mkdir -p "$DATA_DIR"/{data,uploads,logs}
chmod 755 "$DATA_DIR"/{data,uploads,logs}
# 3. 复制初始数据(仅在目录为空时)
if [ -z "$(ls -A $DATA_DIR/data 2>/dev/null)" ]; then
log_info "首次部署,从镜像中提取初始数据库..."
TMP_CONTAINER="chongming-tmp-$$"
docker create --name "$TMP_CONTAINER" "$IMAGE" &> /dev/null
docker cp "$TMP_CONTAINER:/app/data/." "$DATA_DIR/data/" &> /dev/null
docker rm "$TMP_CONTAINER" &> /dev/null
log_info "初始数据库已就绪"
else
log_info "数据库已存在,跳过初始化"
fi
# 检查端口是否被占用
if ss -tlnp | grep -q ":${PORT}"; then
log_error "端口 ${PORT} 已被占用,请修改 PORT 变量或释放端口"
exit 1
fi
# 4. 启动容器
log_info "启动容器..."
docker run -d \
-p "$PORT:8000" \
-v "$DATA_DIR/data:/app/data" \
-v "$DATA_DIR/uploads:/app/uploads" \
-v "$DATA_DIR/logs:/app/logs" \
--restart unless-stopped \
--name "$CONTAINER" \
"$IMAGE"
# 5. 等待启动
log_info "等待应用启动..."
sleep 3
# 6. 检查状态
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
log_info "=========================================="
log_info "✅ 部署成功!"
log_info "=========================================="
log_info "应用地址: http://localhost:$PORT"
log_info "API 文档: http://localhost:$PORT/docs"
log_info "数据目录: $DATA_DIR"
log_info ""
log_info "查看日志: docker logs -f $CONTAINER"
log_info "停止服务: docker stop $CONTAINER"
log_info "重启服务: docker restart $CONTAINER"
log_info "=========================================="
else
log_error "容器启动失败,查看日志:"
docker logs "$CONTAINER" 2>/dev/null || true
exit 1
fi
-23
View File
@@ -1,23 +0,0 @@
# 使用 Debian 的极简版,确保有基础的 glibc(推荐)
FROM debian:bookworm-slim
# 可选:设置时区、安装 ca-certificates 等
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 复制打包好的可执行文件
COPY build/chongming /app/
# 复制运行时需要的静态资源、配置文件、.mbank 等
COPY build/config.toml /app/
COPY build/resources/ /app/resources/
COPY build/static.svfs /app/
# 暴露端口
EXPOSE 8000
# 启动
CMD ["./chongming"]
+30 -30
View File
@@ -1,27 +1,27 @@
[default] [default]
app.debug = false
app.description = "基于 FastAPI 的 chongming 打包脚手架"
app.name = "chongming" app.name = "chongming"
app.version = "0.1.0" app.version = "0.1.0"
app.description = "基于 FastAPI 的 chongming 打包脚手架"
app.debug = false
env = "production" env = "production"
prefix = "/api/v1" prefix = "/api/v1"
upload_path = "uploads" upload_path = "uploads"
[database] [database]
type = "sqlite" generate_schemas = true # 生产环境使用迁移工具
url = "sqlite+aiosqlite:///./database.db" type = "sqlite"
generate_schemas = true # 生产环境使用迁移工具 url = "sqlite+aiosqlite:///./data/database.db"
[database.sqlite] [database.sqlite]
connect_args.check_same_thread = false
connect_args.timeout = 15
echo = false echo = false
echo_pool = false echo_pool = false
pool_size = 20
max_overflow = 10 max_overflow = 10
pool_timeout = 30
pool_recycle = 300
pool_pre_ping = true pool_pre_ping = true
connect_args.timeout = 15 pool_recycle = 300
connect_args.check_same_thread = false pool_size = 20
pool_timeout = 30
[scheduler] [scheduler]
job_store_path = "scheduler_jobs.db" job_store_path = "scheduler_jobs.db"
@@ -30,62 +30,62 @@ job_store_path = "scheduler_jobs.db"
cache_store_path = "./" cache_store_path = "./"
[security] [security]
secret_key = "your-secret-key-change-in-production"
algorithm = "HS256"
access_token_expire_minutes = 30 access_token_expire_minutes = 30
refresh_token_expire_days = 7 algorithm = "HS256"
max_sessions_per_user = 5 max_sessions_per_user = 5
refresh_token_expire_days = 7
secret_key = "your-secret-key-change-in-production"
[development] [development]
env = "development"
debug = true debug = true
env = "development"
[development.server] [development.server]
access_log = false
host = "127.0.0.1" host = "127.0.0.1"
port = 8001 port = 8001
reload = true reload = true
access_log = false
[development.logging] [development.logging]
level = "DEBUG"
file = "logs" file = "logs"
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
level = "DEBUG"
[development.cors] [development.cors]
allow_origins = ["*"]
allow_credentials = true allow_credentials = true
allow_methods = ["*"]
allow_headers = ["*"] allow_headers = ["*"]
allow_methods = ["*"]
allow_origins = ["*"]
[production] [production]
env = "production"
debug = false debug = false
env = "production"
[production.server] [production.server]
access_log = false
host = "0.0.0.0" host = "0.0.0.0"
port = 8000 port = 8000
reload = false reload = false
workers = 4
access_log = false
timeout_keep_alive = 65 timeout_keep_alive = 65
workers = 4
[production.logging] [production.logging]
level = "INFO" backup_count = 5 # 最多保存 10 个日志文件
file = "logs" file = "logs"
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
max_size = 10485760 # 10MB level = "INFO"
backup_count = 5 # 最多保存 10 个日志文件 max_size = 10485760 # 10MB
[production.cors] [production.cors]
allow_origins = ["https://yourdomain.com", "http://localhost:8000", "http://127.0.0.1:8000"]
allow_credentials = true allow_credentials = true
allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_headers = ["Authorization", "Content-Type"] allow_headers = ["Authorization", "Content-Type"]
allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_origins = ["https://yourdomain.com", "http://localhost:8000", "http://127.0.0.1:8000"]
[production.module_system] [production.module_system]
type = "module-bank"
path = ["resources/app.mbank", "resources/plugins.mbank"] path = ["resources/app.mbank", "resources/plugins.mbank"]
type = "module-bank"
[production.file_system] [production.file_system]
type = "svfs"
path = "static.svfs" path = "static.svfs"
type = "svfs"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 204 KiB

+4 -4
View File
@@ -14,10 +14,10 @@ import { useAuthStore } from './stores/auth'
/** /**
* 打包时取消该注释 * 打包时取消该注释
*/ */
import { client } from "./api/generated/client.gen" // import { client } from "./api/generated/client.gen"
client.setConfig({ // client.setConfig({
baseUrl: "/" // baseUrl: "/"
}) // })
const app = createApp(App) const app = createApp(App)
-12
View File
@@ -1,12 +0,0 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
+5 -1
View File
@@ -64,8 +64,12 @@ async def lifespan(app: FastAPI):
database_config = database.get(database_type, None) database_config = database.get(database_type, None)
if database_config is None: if database_config is None:
raise ValueError("配置不存在") raise ValueError("配置不存在")
database_path = database.get("database_path", None)
if database_path:
Path(database_path).parent.mkdir(parents=True, exist_ok=True)
engine_config = {k: v for k, v in database_config.items() if k != "database_path"}
engine = create_async_engine(database["url"], **database_config) engine = create_async_engine(database["url"], **engine_config)
async_session_maker = async_sessionmaker( async_session_maker = async_sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False engine, class_=AsyncSession, expire_on_commit=False
) )
+1
View File
@@ -60,6 +60,7 @@ class Database(TypedDict, total=False):
sqlite: DatabaseSqlite sqlite: DatabaseSqlite
mysql: dict mysql: dict
postgresql: dict postgresql: dict
database_path: str
class Security(TypedDict): class Security(TypedDict):
+12 -12
View File
@@ -134,6 +134,10 @@ def run_pyinstaller():
"-m", "-m",
"PyInstaller", "PyInstaller",
"--onefile", "--onefile",
"--noconfirm",
"--optimize",
"2",
"--strip",
"--name", "--name",
"chongming", "chongming",
"--distpath", "--distpath",
@@ -173,11 +177,7 @@ def run_pyinstaller():
"--hidden-import", "--hidden-import",
"diskcache", "diskcache",
"--hidden-import", "--hidden-import",
"passlib", "bcrypt",
"--hidden-import",
"passlib.context",
"--hidden-import",
"passlib.handlers.bcrypt",
"--hidden-import", "--hidden-import",
"sqlite_vfs", "sqlite_vfs",
"--hidden-import", "--hidden-import",
@@ -197,6 +197,10 @@ def run_pyinstaller():
"-m", "-m",
"PyInstaller", "PyInstaller",
"--onefile", "--onefile",
"--noconfirm",
"--optimize",
"2",
"--strip",
"--name", "--name",
"chongming", "chongming",
"--distpath", "--distpath",
@@ -236,11 +240,7 @@ def run_pyinstaller():
"--hidden-import", "--hidden-import",
"diskcache", "diskcache",
"--hidden-import", "--hidden-import",
"passlib", "bcrypt",
"--hidden-import",
"passlib.context",
"--hidden-import",
"passlib.handlers.bcrypt",
"--hidden-import", "--hidden-import",
"sqlite_vfs", "sqlite_vfs",
"--hidden-import", "--hidden-import",
@@ -309,9 +309,9 @@ async def init_database():
database_config = database.get(database_type, None) database_config = database.get(database_type, None)
if database_config is None: if database_config is None:
raise ValueError("配置不存在") raise ValueError("配置不存在")
os.makedirs("./build/data", exist_ok=True)
engine = create_async_engine( engine = create_async_engine(
"sqlite+aiosqlite:///./build/database.db", **database_config "sqlite+aiosqlite:///./build/data/database.db", **database_config
) )
async_session_maker = async_sessionmaker( async_session_maker = async_sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False engine, class_=AsyncSession, expire_on_commit=False
+2 -1
View File
@@ -22,7 +22,8 @@ def generate_markdown_from_py_files(directory, output_file):
or file.endswith(".ts") or file.endswith(".ts")
or file.endswith(".json") or file.endswith(".json")
or file.endswith(".html") or file.endswith(".html")
or file == "dockerfile" or file.endswith(".sh")
or file == "Dockerfile"
): ):
file_path = os.path.join(root, file) file_path = os.path.join(root, file)
if file == "output.md" or file == "package-lock.json": if file == "output.md" or file == "package-lock.json":