diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bbab988 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/README.md b/README.md index 53b0c21..fdba573 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,6 @@ docker load -i chongming_latest.tar 运行镜像 ```shell -mkdir -p /data/chongming -docker run -d -p 8000:8000 \ - -v /data/chongming/database.db:/app/database.db \ - -v /data/chongming/uploads:/app/uploads \ - --name chongming chongming:latest +sudo chmod 777 ./deploy.sh +./deploy.sh ``` diff --git a/config.toml b/config.toml index 1e9ef33..0658d0d 100644 --- a/config.toml +++ b/config.toml @@ -1,27 +1,27 @@ [default] +app.debug = false +app.description = "基于 FastAPI 的 chongming 打包脚手架" app.name = "chongming" app.version = "chongming" -app.description = "基于 FastAPI 的 chongming 打包脚手架" -app.debug = false env = "development" prefix = "/api/v1" upload_path = "uploads" [database] +database_path = "./data/database.db" type = "sqlite" -url = "sqlite+aiosqlite:///./database.db" -generate_schemas = true # 生产环境使用迁移工具 +url = "sqlite+aiosqlite:///./data/database.db" [database.sqlite] +connect_args.check_same_thread = false +connect_args.timeout = 15 echo = true echo_pool = true -pool_size = 20 max_overflow = 10 -pool_timeout = 30 -pool_recycle = 300 pool_pre_ping = true -connect_args.timeout = 15 -connect_args.check_same_thread = false +pool_recycle = 300 +pool_size = 20 +pool_timeout = 30 [scheduler] job_store_path = "scheduler_jobs.db" @@ -30,61 +30,61 @@ job_store_path = "scheduler_jobs.db" cache_store_path = "./" [security] -secret_key = "your-secret-key-change-in-production" -algorithm = "HS256" access_token_expire_minutes = 30 -refresh_token_expire_days = 7 +algorithm = "HS256" max_sessions_per_user = 5 +refresh_token_expire_days = 7 +secret_key = "your-secret-key-change-in-production" [development] -env = "development" debug = true +env = "development" [development.server] +access_log = false host = "127.0.0.1" port = 8000 reload = true -access_log = false [development.logging] -level = "DEBUG" file = "logs" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "DEBUG" [development.cors] -allow_origins = ["*"] allow_credentials = true -allow_methods = ["*"] allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["*"] [production] -env = "production" debug = false +env = "production" [production.server] +access_log = false host = "0.0.0.0" port = 8000 reload = false workers = 4 -access_log = false [production.logging] -level = "INFO" -file = "logs" -format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" -max_size = 10485760 # 10MB -backup_count = 5 # 最多保存 10 个日志文件 +backup_count = 5 # 最多保存 10 个日志文件 +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "INFO" +max_size = 10485760 # 10MB [production.cors] -allow_origins = ["https://yourdomain.com", "http://localhost:8000", "http://127.0.0.1:8000"] allow_credentials = true -allow_methods = ["GET", "POST", "PUT", "DELETE"] 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] -type = "module-bank" path = ["resources/app.mbank", "resources/plugins.mbank"] +type = "module-bank" [production.file_system] -type = "svfs" path = "static.svfs" +type = "svfs" diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..b5d1b4b --- /dev/null +++ b/deploy.sh @@ -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 \ No newline at end of file diff --git a/dockerfile b/dockerfile deleted file mode 100644 index e913144..0000000 --- a/dockerfile +++ /dev/null @@ -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"] \ No newline at end of file diff --git a/public/config.toml b/public/config.toml index 44eb5d2..3c5f5be 100644 --- a/public/config.toml +++ b/public/config.toml @@ -1,27 +1,27 @@ [default] +app.debug = false +app.description = "基于 FastAPI 的 chongming 打包脚手架" app.name = "chongming" app.version = "0.1.0" -app.description = "基于 FastAPI 的 chongming 打包脚手架" -app.debug = false env = "production" prefix = "/api/v1" upload_path = "uploads" [database] -type = "sqlite" -url = "sqlite+aiosqlite:///./database.db" -generate_schemas = true # 生产环境使用迁移工具 +generate_schemas = true # 生产环境使用迁移工具 +type = "sqlite" +url = "sqlite+aiosqlite:///./data/database.db" [database.sqlite] +connect_args.check_same_thread = false +connect_args.timeout = 15 echo = false echo_pool = false -pool_size = 20 max_overflow = 10 -pool_timeout = 30 -pool_recycle = 300 pool_pre_ping = true -connect_args.timeout = 15 -connect_args.check_same_thread = false +pool_recycle = 300 +pool_size = 20 +pool_timeout = 30 [scheduler] job_store_path = "scheduler_jobs.db" @@ -30,62 +30,62 @@ job_store_path = "scheduler_jobs.db" cache_store_path = "./" [security] -secret_key = "your-secret-key-change-in-production" -algorithm = "HS256" access_token_expire_minutes = 30 -refresh_token_expire_days = 7 +algorithm = "HS256" max_sessions_per_user = 5 +refresh_token_expire_days = 7 +secret_key = "your-secret-key-change-in-production" [development] -env = "development" debug = true +env = "development" [development.server] +access_log = false host = "127.0.0.1" port = 8001 reload = true -access_log = false [development.logging] -level = "DEBUG" file = "logs" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "DEBUG" [development.cors] -allow_origins = ["*"] allow_credentials = true -allow_methods = ["*"] allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["*"] [production] -env = "production" debug = false +env = "production" [production.server] +access_log = false host = "0.0.0.0" port = 8000 reload = false -workers = 4 -access_log = false timeout_keep_alive = 65 +workers = 4 [production.logging] -level = "INFO" -file = "logs" -format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" -max_size = 10485760 # 10MB -backup_count = 5 # 最多保存 10 个日志文件 +backup_count = 5 # 最多保存 10 个日志文件 +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "INFO" +max_size = 10485760 # 10MB [production.cors] -allow_origins = ["https://yourdomain.com", "http://localhost:8000", "http://127.0.0.1:8000"] allow_credentials = true -allow_methods = ["GET", "POST", "PUT", "DELETE"] 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] -type = "module-bank" path = ["resources/app.mbank", "resources/plugins.mbank"] +type = "module-bank" [production.file_system] -type = "svfs" path = "static.svfs" +type = "svfs" diff --git a/src/chongming-web/public/favicon.ico b/src/chongming-web/public/favicon.ico index df36fcf..79a6135 100644 Binary files a/src/chongming-web/public/favicon.ico and b/src/chongming-web/public/favicon.ico differ diff --git a/src/chongming-web/src/assets/chongming64x64.png b/src/chongming-web/src/assets/chongming64x64.png new file mode 100644 index 0000000..827ca41 Binary files /dev/null and b/src/chongming-web/src/assets/chongming64x64.png differ diff --git a/src/chongming-web/src/assets/logo.png b/src/chongming-web/src/assets/logo.png new file mode 100644 index 0000000..12c681f Binary files /dev/null and b/src/chongming-web/src/assets/logo.png differ diff --git a/src/chongming-web/src/assets/logo.svg b/src/chongming-web/src/assets/logo.svg index 7565660..267be7e 100644 --- a/src/chongming-web/src/assets/logo.svg +++ b/src/chongming-web/src/assets/logo.svg @@ -1 +1,175 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/chongming-web/src/main.ts b/src/chongming-web/src/main.ts index ee64a16..a628d07 100644 --- a/src/chongming-web/src/main.ts +++ b/src/chongming-web/src/main.ts @@ -14,10 +14,10 @@ import { useAuthStore } from './stores/auth' /** * 打包时取消该注释 */ -import { client } from "./api/generated/client.gen" -client.setConfig({ - baseUrl: "/" -}) +// import { client } from "./api/generated/client.gen" +// client.setConfig({ +// baseUrl: "/" +// }) const app = createApp(App) diff --git a/src/chongming-web/src/stores/counter.ts b/src/chongming-web/src/stores/counter.ts deleted file mode 100644 index b6757ba..0000000 --- a/src/chongming-web/src/stores/counter.ts +++ /dev/null @@ -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 } -}) diff --git a/src/chongming/app/__init__.py b/src/chongming/app/__init__.py index 298e548..57250b9 100644 --- a/src/chongming/app/__init__.py +++ b/src/chongming/app/__init__.py @@ -64,8 +64,12 @@ async def lifespan(app: FastAPI): database_config = database.get(database_type, None) if database_config is None: 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( engine, class_=AsyncSession, expire_on_commit=False ) diff --git a/src/chongming/app/core/config.py b/src/chongming/app/core/config.py index ff4aaad..52be9cf 100644 --- a/src/chongming/app/core/config.py +++ b/src/chongming/app/core/config.py @@ -60,6 +60,7 @@ class Database(TypedDict, total=False): sqlite: DatabaseSqlite mysql: dict postgresql: dict + database_path: str class Security(TypedDict): diff --git a/src/chongming/scripts/build.py b/src/chongming/scripts/build.py index c0f43cb..d25d26d 100644 --- a/src/chongming/scripts/build.py +++ b/src/chongming/scripts/build.py @@ -134,6 +134,10 @@ def run_pyinstaller(): "-m", "PyInstaller", "--onefile", + "--noconfirm", + "--optimize", + "2", + "--strip", "--name", "chongming", "--distpath", @@ -173,11 +177,7 @@ def run_pyinstaller(): "--hidden-import", "diskcache", "--hidden-import", - "passlib", - "--hidden-import", - "passlib.context", - "--hidden-import", - "passlib.handlers.bcrypt", + "bcrypt", "--hidden-import", "sqlite_vfs", "--hidden-import", @@ -197,6 +197,10 @@ def run_pyinstaller(): "-m", "PyInstaller", "--onefile", + "--noconfirm", + "--optimize", + "2", + "--strip", "--name", "chongming", "--distpath", @@ -236,11 +240,7 @@ def run_pyinstaller(): "--hidden-import", "diskcache", "--hidden-import", - "passlib", - "--hidden-import", - "passlib.context", - "--hidden-import", - "passlib.handlers.bcrypt", + "bcrypt", "--hidden-import", "sqlite_vfs", "--hidden-import", @@ -309,9 +309,9 @@ async def init_database(): database_config = database.get(database_type, None) if database_config is None: raise ValueError("配置不存在") - + os.makedirs("./build/data", exist_ok=True) engine = create_async_engine( - "sqlite+aiosqlite:///./build/database.db", **database_config + "sqlite+aiosqlite:///./build/data/database.db", **database_config ) async_session_maker = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False diff --git a/src/chongming/scripts/code_content.py b/src/chongming/scripts/code_content.py index 293add0..164f79d 100644 --- a/src/chongming/scripts/code_content.py +++ b/src/chongming/scripts/code_content.py @@ -22,7 +22,8 @@ def generate_markdown_from_py_files(directory, output_file): or file.endswith(".ts") or file.endswith(".json") or file.endswith(".html") - or file == "dockerfile" + or file.endswith(".sh") + or file == "Dockerfile" ): file_path = os.path.join(root, file) if file == "output.md" or file == "package-lock.json":