mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-08-25 10:10:02 +08:00
feat(core): 支持PyInstaller打包并优化资源路径
- 修改app.py以支持PyInstaller打包后的资源路径 - 更新session.py以支持APP_DATA_DIR环境变量 - 增强webui.py以设置打包后的数据目录 - 添加pyproject.toml的PyInstaller依赖组 - 新增构建脚本和GitHub Actions工作流
This commit is contained in:
105
.github/workflows/build.yml
vendored
Normal file
105
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
name: 多平台打包发布
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: '版本号 (如 v1.0.0)'
|
||||||
|
required: false
|
||||||
|
default: 'dev'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: 打包 ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
artifact_name: codex-register.exe
|
||||||
|
asset_name: codex-register-windows-x64.exe
|
||||||
|
- os: ubuntu-latest
|
||||||
|
artifact_name: codex-register
|
||||||
|
asset_name: codex-register-linux-x64
|
||||||
|
- os: macos-latest
|
||||||
|
artifact_name: codex-register
|
||||||
|
asset_name: codex-register-macos-arm64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 检出代码
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 设置 Python 3.11
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: 安装依赖
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt pyinstaller
|
||||||
|
|
||||||
|
- name: 打包
|
||||||
|
run: |
|
||||||
|
pyinstaller codex_register.spec --clean --noconfirm
|
||||||
|
|
||||||
|
- name: 上传构建产物
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.asset_name }}
|
||||||
|
path: dist/${{ matrix.artifact_name }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: 创建发布
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 下载所有构建产物
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
- name: 整理文件
|
||||||
|
run: |
|
||||||
|
mkdir -p release
|
||||||
|
find dist/ -type f | while read f; do
|
||||||
|
name=$(basename "$f")
|
||||||
|
cp "$f" "release/$name"
|
||||||
|
done
|
||||||
|
ls -lh release/
|
||||||
|
|
||||||
|
- name: 创建 GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: release/*
|
||||||
|
generate_release_notes: true
|
||||||
|
body: |
|
||||||
|
## OpenAI/Codex CLI 自动注册系统
|
||||||
|
|
||||||
|
### 下载说明
|
||||||
|
| 平台 | 文件 |
|
||||||
|
|------|------|
|
||||||
|
| Windows x64 | `codex-register-windows-x64.exe` |
|
||||||
|
| Linux x64 | `codex-register-linux-x64` |
|
||||||
|
| macOS ARM64 | `codex-register-macos-arm64` |
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
```bash
|
||||||
|
# Linux/macOS 需要先赋予执行权限
|
||||||
|
chmod +x codex-register-*
|
||||||
|
|
||||||
|
# 启动 Web UI
|
||||||
|
./codex-register
|
||||||
|
|
||||||
|
# 指定端口
|
||||||
|
./codex-register --port 8080
|
||||||
|
```
|
||||||
20
build.bat
Normal file
20
build.bat
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@echo off
|
||||||
|
REM Windows 打包脚本
|
||||||
|
|
||||||
|
echo === 构建平台: Windows ===
|
||||||
|
|
||||||
|
REM 安装打包依赖
|
||||||
|
pip install pyinstaller --quiet
|
||||||
|
|
||||||
|
REM 执行打包
|
||||||
|
pyinstaller codex_register.spec --clean --noconfirm
|
||||||
|
|
||||||
|
IF EXIST dist\codex-register.exe (
|
||||||
|
FOR /F "tokens=*" %%i IN ('powershell -Command "[System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture"') DO SET ARCH=%%i
|
||||||
|
SET OUTPUT=dist\codex-register-windows-%ARCH%.exe
|
||||||
|
MOVE dist\codex-register.exe "%OUTPUT%"
|
||||||
|
echo === 构建完成: %OUTPUT% ===
|
||||||
|
) ELSE (
|
||||||
|
echo === 构建失败,未找到输出文件 ===
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
49
build.sh
Normal file
49
build.sh
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 跨平台打包脚本(在各平台上分别运行)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
OS=$(uname -s)
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
|
case "$OS" in
|
||||||
|
Darwin)
|
||||||
|
PLATFORM="macos"
|
||||||
|
EXT=""
|
||||||
|
;;
|
||||||
|
Linux)
|
||||||
|
PLATFORM="linux"
|
||||||
|
EXT=""
|
||||||
|
;;
|
||||||
|
MINGW*|CYGWIN*|MSYS*)
|
||||||
|
PLATFORM="windows"
|
||||||
|
EXT=".exe"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
PLATFORM="$OS"
|
||||||
|
EXT=""
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
OUTPUT_NAME="codex-register-${PLATFORM}-${ARCH}${EXT}"
|
||||||
|
|
||||||
|
echo "=== 构建平台: ${PLATFORM} (${ARCH}) ==="
|
||||||
|
echo "=== 输出文件: dist/${OUTPUT_NAME} ==="
|
||||||
|
|
||||||
|
# 安装打包依赖
|
||||||
|
pip install pyinstaller --quiet 2>/dev/null || \
|
||||||
|
uv run --with pyinstaller pyinstaller --version > /dev/null 2>&1
|
||||||
|
|
||||||
|
# 执行打包(优先用 uv,回退到直接调用)
|
||||||
|
if command -v uv &>/dev/null; then
|
||||||
|
uv run --with pyinstaller pyinstaller codex_register.spec --clean --noconfirm
|
||||||
|
else
|
||||||
|
pyinstaller codex_register.spec --clean --noconfirm
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 重命名输出文件
|
||||||
|
mv dist/codex-register${EXT} dist/${OUTPUT_NAME} 2>/dev/null || \
|
||||||
|
mv "dist/codex-register" "dist/${OUTPUT_NAME}" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "=== 构建完成: dist/${OUTPUT_NAME} ==="
|
||||||
|
ls -lh dist/${OUTPUT_NAME}
|
||||||
@@ -22,7 +22,6 @@ dev = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
codex-register = "cli:main"
|
|
||||||
codex-webui = "webui:main"
|
codex-webui = "webui:main"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
@@ -31,3 +30,8 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src"]
|
packages = ["src"]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pyinstaller>=6.19.0",
|
||||||
|
]
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ class DatabaseSessionManager:
|
|||||||
|
|
||||||
def __init__(self, database_url: str = None):
|
def __init__(self, database_url: str = None):
|
||||||
if database_url is None:
|
if database_url is None:
|
||||||
# 默认使用项目根目录下的 SQLite 数据库
|
# 优先使用 APP_DATA_DIR 环境变量(PyInstaller 打包后由 webui.py 设置)
|
||||||
db_path = os.path.join(
|
data_dir = os.environ.get('APP_DATA_DIR') or os.path.join(
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
||||||
'data',
|
'data'
|
||||||
'database.db'
|
|
||||||
)
|
)
|
||||||
|
db_path = os.path.join(data_dir, 'database.db')
|
||||||
# 确保目录存在
|
# 确保目录存在
|
||||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
database_url = f"sqlite:///{db_path}"
|
database_url = f"sqlite:///{db_path}"
|
||||||
|
|
||||||
self.database_url = database_url
|
self.database_url = database_url
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ FastAPI 应用主文件
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -21,11 +22,15 @@ from .task_manager import task_manager
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# 获取项目根目录
|
# 获取项目根目录
|
||||||
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
# PyInstaller 打包后静态资源在 sys._MEIPASS,开发时在源码根目录
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
_RESOURCE_ROOT = Path(sys._MEIPASS)
|
||||||
|
else:
|
||||||
|
_RESOURCE_ROOT = Path(__file__).parent.parent.parent
|
||||||
|
|
||||||
# 静态文件和模板目录
|
# 静态文件和模板目录
|
||||||
STATIC_DIR = PROJECT_ROOT / "static"
|
STATIC_DIR = _RESOURCE_ROOT / "static"
|
||||||
TEMPLATES_DIR = PROJECT_ROOT / "templates"
|
TEMPLATES_DIR = _RESOURCE_ROOT / "templates"
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> FastAPI:
|
def create_app() -> FastAPI:
|
||||||
|
|||||||
33
webui.py
33
webui.py
@@ -8,8 +8,16 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# 添加项目根目录到 Python 路径
|
# 添加项目根目录到 Python 路径
|
||||||
|
# PyInstaller 打包后 __file__ 在临时解压目录,需要用 sys.executable 所在目录作为数据目录
|
||||||
|
import os
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包后:使用可执行文件所在目录
|
||||||
|
project_root = Path(sys.executable).parent
|
||||||
|
_src_root = Path(sys._MEIPASS)
|
||||||
|
else:
|
||||||
project_root = Path(__file__).parent
|
project_root = Path(__file__).parent
|
||||||
sys.path.insert(0, str(project_root))
|
_src_root = project_root
|
||||||
|
sys.path.insert(0, str(_src_root))
|
||||||
|
|
||||||
from src.core.utils import setup_logging
|
from src.core.utils import setup_logging
|
||||||
from src.database.init_db import initialize_database
|
from src.database.init_db import initialize_database
|
||||||
@@ -18,6 +26,16 @@ from src.config.settings import get_settings
|
|||||||
|
|
||||||
def setup_application():
|
def setup_application():
|
||||||
"""设置应用程序"""
|
"""设置应用程序"""
|
||||||
|
# 确保数据目录和日志目录在可执行文件所在目录(打包后也适用)
|
||||||
|
data_dir = project_root / "data"
|
||||||
|
logs_dir = project_root / "logs"
|
||||||
|
data_dir.mkdir(exist_ok=True)
|
||||||
|
logs_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# 将数据目录路径注入环境变量,供数据库配置使用
|
||||||
|
os.environ.setdefault("APP_DATA_DIR", str(data_dir))
|
||||||
|
os.environ.setdefault("APP_LOGS_DIR", str(logs_dir))
|
||||||
|
|
||||||
# 初始化数据库(必须先于获取设置)
|
# 初始化数据库(必须先于获取设置)
|
||||||
try:
|
try:
|
||||||
initialize_database()
|
initialize_database()
|
||||||
@@ -28,23 +46,16 @@ def setup_application():
|
|||||||
# 获取配置(需要数据库已初始化)
|
# 获取配置(需要数据库已初始化)
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
# 配置日志
|
# 配置日志(日志文件写到实际 logs 目录)
|
||||||
|
log_file = str(logs_dir / Path(settings.log_file).name)
|
||||||
setup_logging(
|
setup_logging(
|
||||||
log_level=settings.log_level,
|
log_level=settings.log_level,
|
||||||
log_file=settings.log_file
|
log_file=log_file
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.info("数据库初始化完成")
|
logger.info("数据库初始化完成")
|
||||||
|
|
||||||
# 检查数据目录
|
|
||||||
data_dir = project_root / "data"
|
|
||||||
data_dir.mkdir(exist_ok=True)
|
|
||||||
logger.info(f"数据目录: {data_dir}")
|
logger.info(f"数据目录: {data_dir}")
|
||||||
|
|
||||||
# 检查日志目录
|
|
||||||
logs_dir = project_root / "logs"
|
|
||||||
logs_dir.mkdir(exist_ok=True)
|
|
||||||
logger.info(f"日志目录: {logs_dir}")
|
logger.info(f"日志目录: {logs_dir}")
|
||||||
|
|
||||||
logger.info("应用程序设置完成")
|
logger.info("应用程序设置完成")
|
||||||
|
|||||||
Reference in New Issue
Block a user