149 lines
4.3 KiB
Markdown
149 lines
4.3 KiB
Markdown
# jianda-proxy CLI 工具打包设计
|
||
|
||
**日期**:2026-05-28
|
||
**状态**:已批准
|
||
|
||
## 背景
|
||
|
||
jianda-proxy 是一个 HTTP 代理工具,用于解决腾讯云 ICP 拦截导致 Git LFS 下载失败的问题。目前是一个单文件 Python 脚本(`lfs-proxy.py`),配置硬编码,直接 `python3` 运行。
|
||
|
||
目标:将其打包为标准的 Python CLI 工具,支持 `uvx` 运行、`uv tool install` 安装、后台运行、CLI 管理生命周期,且跨平台(Windows + Mac)。
|
||
|
||
## 决策
|
||
|
||
- **工具名称**:`jianda-proxy`
|
||
- **配置格式**:INI(标准库 `configparser`,零外部依赖)
|
||
- **后台运行**:纯标准库,Unix 用 `os.fork()`,Windows 用 `subprocess` detached
|
||
- **配置管理**:CLI `config` 子命令
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
jianda-proxy/
|
||
├── pyproject.toml
|
||
├── src/
|
||
│ └── jianda_proxy/
|
||
│ ├── __init__.py
|
||
│ ├── cli.py # CLI 入口,argparse 子命令
|
||
│ ├── proxy.py # 代理逻辑
|
||
│ ├── config.py # INI 配置管理
|
||
│ └── daemon.py # 跨平台后台运行管理
|
||
├── docs/
|
||
│ └── superpowers/specs/
|
||
└── README.md
|
||
```
|
||
|
||
### pyproject.toml
|
||
|
||
- 零外部依赖
|
||
- Python >= 3.8
|
||
- 入口点:`jianda-proxy = "jianda_proxy.cli:main"`
|
||
- 构建后端:`hatchling`
|
||
|
||
## 打包与分发
|
||
|
||
- `uv tool install .`:本地安装
|
||
- `uvx jianda-proxy`:临时运行(需发布到 PyPI 后)
|
||
- `python -m jianda_proxy`:作为模块运行
|
||
|
||
## 配置管理
|
||
|
||
### 配置文件位置
|
||
|
||
- Mac/Linux:`~/.config/jianda-proxy/config.ini`
|
||
- Windows:`%APPDATA%\jianda-proxy\config.ini`
|
||
|
||
通过 `os.path.expanduser` 和 `os.environ` 手动拼路径,不引入 `platformdirs`。
|
||
|
||
### 配置格式
|
||
|
||
```ini
|
||
[proxy]
|
||
remote_domain = git.zz.com
|
||
upstream_host = 62.234.191.215
|
||
upstream_port = 3000
|
||
listen_port = 13000
|
||
listen_host = 127.0.0.1
|
||
```
|
||
|
||
### config 子命令
|
||
|
||
| 命令 | 说明 |
|
||
|------|------|
|
||
| `config set <key> <value>` | 设置配置项 |
|
||
| `config get <key>` | 查看单个配置项 |
|
||
| `config list` | 列出所有配置 |
|
||
| `config path` | 显示配置文件路径 |
|
||
|
||
首次使用 `start` 时,如果配置文件不存在,用默认值自动创建。
|
||
|
||
## CLI 命令
|
||
|
||
```
|
||
jianda-proxy start [--port PORT] [--foreground]
|
||
jianda-proxy stop
|
||
jianda-proxy status
|
||
jianda-proxy config set <key> <value>
|
||
jianda-proxy config get <key>
|
||
jianda-proxy config list
|
||
jianda-proxy config path
|
||
```
|
||
|
||
`start --foreground`:前台运行,日志直接输出到终端,方便调试。
|
||
|
||
## 后台运行与生命周期
|
||
|
||
### start
|
||
|
||
1. 检查端口是否可用
|
||
2. 检查是否已有进程运行(PID 文件存在且进程存活)
|
||
3. 如果已有进程:提示错误并退出
|
||
4. 如果 PID 文件存在但进程已死:清理后继续
|
||
5. Unix:`os.fork()` 后父进程退出
|
||
6. Windows:`subprocess.Popen` with `CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS`
|
||
7. 写 PID 文件
|
||
8. 日志写到 `~/.config/jianda-proxy/proxy.log`(RotatingFileHandler,5MB,3 个备份)
|
||
|
||
### stop
|
||
|
||
1. 读取 PID 文件
|
||
2. 检查进程是否存在
|
||
3. 发送 SIGTERM(Unix)/ taskkill(Windows)
|
||
4. 等待最多 3 秒
|
||
5. 如果还活着:SIGKILL / 强制终止
|
||
6. 删除 PID 文件
|
||
|
||
### status
|
||
|
||
1. 读取 PID 文件
|
||
2. 检查进程是否存在
|
||
3. 显示:运行状态、PID、端口、运行时间
|
||
|
||
### PID 文件
|
||
|
||
位置:和配置文件同目录 `~/.config/jianda-proxy/daemon.pid`。
|
||
|
||
## 跨平台细节
|
||
|
||
| 操作 | Unix | Windows |
|
||
|------|------|---------|
|
||
| 后台启动 | `os.fork()` | `subprocess.Popen` + detached flags |
|
||
| 进程检查 | `os.kill(pid, 0)` | `ctypes` 调用 `kernel32.OpenProcess` + `GetExitCodeProcess` |
|
||
| 优雅停止 | `SIGTERM` | `taskkill /PID` |
|
||
| 强制停止 | `SIGKILL` | `taskkill /F /PID` |
|
||
| 配置目录 | `~/.config/` | `%APPDATA%` |
|
||
|
||
## 错误处理
|
||
|
||
- 端口被占用:提示具体端口和可能原因
|
||
- 配置无效(IP 格式错误等):启动前验证
|
||
- 权限不足:提示需要的权限
|
||
- 后台进程异常退出:`status` 检测到后提示查看日志
|
||
|
||
## 模块职责
|
||
|
||
- **cli.py**:argparse 定义、子命令分发、入口函数
|
||
- **config.py**:读写配置文件、默认值管理、配置路径计算
|
||
- **proxy.py**:HTTP 代理服务器逻辑(从现有 `lfs-proxy.py` 提取)
|
||
- **daemon.py**:PID 文件管理、跨平台进程检查、进程终止
|