feat: initialize project structure and configuration

- Create backend directory structure (app/models, app/schemas, app/services, app/api, app/tasks, tests)
- Create frontend directory structure (src/router, src/views, src/components, src/api, src/stores)
- Create data directories (ssh_keys, repos)
- Add requirements.txt with FastAPI, SQLAlchemy, Pydantic, and testing dependencies
- Add frontend package.json with Vue 3, Vue Router, Pinia, and Element Plus
- Add .env.example with configuration template
- Add .gitignore for Python, data directories, and frontend
- Add pytest conftest.py with test fixtures for database and environment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
panw
2026-03-30 14:57:51 +08:00
commit f720de6b58
14 changed files with 4310 additions and 0 deletions

12
.env.example Normal file
View File

@@ -0,0 +1,12 @@
# AES-256 加密密钥 (32字节 base64编码)
GM_ENCRYPT_KEY=change-this-to-a-32-byte-base64-key
# API 认证 Token
GM_API_TOKEN=change-this-api-token
# 数据目录
GM_DATA_DIR=./data
# 服务器配置
GM_HOST=0.0.0.0
GM_PORT=8000

30
.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
.venv/
# 数据
data/
*.db
*.db-journal
# 环境变量
.env
# IDE
.idea/
.vscode/
*.swp
# 前端
frontend/node_modules/
frontend/dist/
frontend/.vite/
# 日志
*.log

0
backend/app/__init__.py Normal file
View File

View File

View File

View File

View File

View File

14
backend/requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
fastapi==0.109.0
uvicorn[standard]==0.27.0
sqlalchemy==2.0.25
pydantic==2.5.3
pydantic-settings==2.1.0
python-multipart==0.0.6
apscheduler==3.10.4
paramiko==3.4.0
gitpython==3.1.40
cryptography==41.0.7
requests==2.31.0
pytest==7.4.4
pytest-asyncio==0.23.3
httpx==0.26.0

View File

55
backend/tests/conftest.py Normal file
View File

@@ -0,0 +1,55 @@
import os
import sys
import tempfile
import pytest
from pathlib import Path
# Add backend to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.models import Base
@pytest.fixture(scope="function")
def db_path(tmp_path):
"""临时数据库路径."""
return tmp_path / "test.db"
@pytest.fixture(scope="function")
def db_engine(db_path):
"""临时数据库引擎."""
engine = create_engine(f"sqlite:///{db_path}", connect_args={"check_same_thread": False})
Base.metadata.create_all(engine)
yield engine
engine.dispose()
@pytest.fixture(scope="function")
def db_session(db_engine):
"""临时数据库会话."""
SessionLocal = sessionmaker(bind=db_engine, autocommit=False, autoflush=False)
session = SessionLocal()
yield session
session.close()
@pytest.fixture(scope="function")
def test_encrypt_key():
"""测试加密密钥."""
import base64
return base64.b64encode(b'test-key-32-bytes-long-1234567890').decode()
@pytest.fixture(scope="function")
def test_env_vars(db_path, test_encrypt_key, monkeypatch):
"""设置测试环境变量."""
monkeypatch.setenv("GM_ENCRYPT_KEY", test_encrypt_key)
monkeypatch.setenv("GM_API_TOKEN", "test-token")
monkeypatch.setenv("GM_DATA_DIR", str(db_path.parent))
return {
"GM_ENCRYPT_KEY": test_encrypt_key,
"GM_API_TOKEN": "test-token",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,378 @@
# Git Repo Manager - 设计文档
## 概述
一个面向小团队3-10人的 Git 仓库本地同步管理工具。通过 Web 界面管理多个 Gitea 服务器,支持将远程仓库批量同步到本地,提供提交历史查看和定时自动同步功能。首版聚焦 Gitea 服务器支持。
## 技术选型
| 层 | 技术 | 理由 |
|----|------|------|
| 后端 | Python FastAPI | 开发快速,异步支持好 |
| 前端 | Vue 3 + Element Plus + Pinia | 中文生态完善,适合管理后台 |
| 数据库 | SQLite + SQLAlchemy ORM | 零部署,适合小团队 |
| 调度 | APScheduler | Python 生态成熟方案 |
| SSH | paramiko | 纯 Python SSH 实现 |
| HTTP 客户端 | Axios | 前端请求拦截器 |
| 部署 | 单进程 uvicorn | 一个命令启动全部功能 |
## 架构设计
### 整体架构
单体 FastAPI 应用,内嵌前端静态资源,单进程运行:
```
┌─────────────────────────────────────────────┐
│ FastAPI 单进程 │
│ │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ REST API │ │ Scheduler │ │ Git Engine │ │
│ │ /api/v1 │ │ APSched │ │ git+SSH │ │
│ └────┬─────┘ └─────┬────┘ └─────┬──────┘ │
│ │ │ │ │
│ ┌────┴──────────────┴──────────────┴──────┐ │
│ │ Service Layer │ │
│ │ ServerService / RepoService / SyncSvc │ │
│ └────────────────┬────────────────────────┘ │
│ │ │
│ ┌────────────────┴────────────────────────┐ │
│ │ SQLAlchemy + SQLite (data + keys) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Vue 3 SPA (打包后静态托管) │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```
### 项目目录结构
```
git-manager/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI 入口,挂载路由和静态文件
│ │ ├── config.py # 配置管理,读取环境变量
│ │ ├── database.py # SQLite 连接,会话管理
│ │ ├── security.py # 加解密工具API 认证
│ │ ├── models/ # SQLAlchemy 数据模型
│ │ │ ├── __init__.py
│ │ │ ├── server.py # Server 模型
│ │ │ ├── repo.py # Repo 模型
│ │ │ ├── ssh_key.py # SshKey 模型
│ │ │ └── sync_log.py # SyncLog 模型
│ │ ├── schemas/ # Pydantic 请求/响应模型
│ │ │ ├── __init__.py
│ │ │ ├── server.py
│ │ │ ├── repo.py
│ │ │ ├── ssh_key.py
│ │ │ ├── sync_log.py
│ │ │ └── common.py # 标准响应格式
│ │ ├── services/ # 业务逻辑层
│ │ │ ├── __init__.py
│ │ │ ├── server_service.py
│ │ │ ├── repo_service.py
│ │ │ ├── sync_service.py
│ │ │ └── ssh_key_service.py
│ │ ├── api/ # API 路由
│ │ │ ├── __init__.py
│ │ │ ├── deps.py # 依赖注入DB 会话、认证)
│ │ │ ├── servers.py
│ │ │ ├── repos.py
│ │ │ ├── ssh_keys.py
│ │ │ ├── sync_logs.py
│ │ │ ├── schedules.py
│ │ │ └── status.py
│ │ └── tasks/ # 定时任务
│ │ ├── __init__.py
│ │ └── sync_task.py
│ ├── init_db.py # 数据库初始化脚本
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── App.vue
│ │ ├── main.js
│ │ ├── router/
│ │ │ └── index.js
│ │ ├── views/ # 页面组件
│ │ │ ├── Dashboard.vue
│ │ │ ├── Servers.vue
│ │ │ ├── Repos.vue
│ │ │ ├── SyncLogs.vue
│ │ │ ├── SshKeys.vue
│ │ │ └── Settings.vue
│ │ ├── components/ # 通用组件
│ │ │ ├── ServerForm.vue
│ │ │ ├── RepoSyncStatus.vue
│ │ │ └── CommitHistory.vue
│ │ ├── api/ # API 调用封装
│ │ │ ├── index.js # Axios 实例,拦截器
│ │ │ ├── servers.js
│ │ │ ├── repos.js
│ │ │ ├── sshKeys.js
│ │ │ └── syncLogs.js
│ │ └── stores/ # Pinia 状态管理
│ │ ├── servers.js
│ │ ├── repos.js
│ │ └── app.js
│ ├── index.html
│ ├── vite.config.js
│ └── package.json
└── data/ # 运行时数据目录
├── git_manager.db # SQLite 数据库
├── ssh_keys/ # SSH 密钥文件(加密存储)
└── repos/ # 本地仓库镜像
└── {server_name}/
└── {repo_full_name}/
```
## API 设计
### 设计原则
- 统一 `/api/v1/` 前缀,版本化管理
- 标准 JSON 响应格式:`{"code": 0, "data": {}, "message": "success"}`
- Bearer Token 认证:`Authorization: Bearer <token>`
- Swagger 文档自动生成(`/docs`
### 接口列表
#### 服务器管理
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/servers` | 添加 Gitea 服务器 |
| GET | `/api/v1/servers` | 服务器列表 |
| GET | `/api/v1/servers/{id}` | 服务器详情 |
| PUT | `/api/v1/servers/{id}` | 更新服务器配置 |
| DELETE | `/api/v1/servers/{id}` | 删除服务器 |
| POST | `/api/v1/servers/{id}/test` | 测试连接 + SSH 密钥有效性 |
| POST | `/api/v1/servers/{id}/sync` | 触发该服务器所有仓库同步 |
#### 仓库管理
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/servers/{id}/repos` | 获取服务器仓库列表(调 Gitea API |
| GET | `/api/v1/repos` | 所有本地已同步的仓库 |
| GET | `/api/v1/repos/{id}` | 仓库详情 |
| POST | `/api/v1/repos/{id}/sync` | 手动同步单个仓库 |
| GET | `/api/v1/repos/{id}/commits` | 仓库提交历史 |
#### SSH 密钥管理
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/ssh-keys` | 上传私钥 |
| GET | `/api/v1/ssh-keys` | 密钥列表 |
| DELETE | `/api/v1/ssh-keys/{id}` | 删除密钥 |
| POST | `/api/v1/ssh-keys/{id}/test` | 测试密钥有效性 |
#### 同步记录
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/sync-logs` | 同步日志列表(支持分页和状态筛选) |
| GET | `/api/v1/sync-logs/{id}` | 同步详情 |
#### 调度配置
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/schedules` | 查看调度配置 |
| PUT | `/api/v1/schedules` | 修改调度配置 |
#### 系统状态
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/status` | 系统运行状态、磁盘空间、仓库统计 |
## 数据模型
### ER 关系
```
ssh_keys 1 ──── N servers
servers 1 ──── N repos
repos 1 ──── N sync_logs
```
### ssh_keys 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER PK | 自增主键 |
| name | VARCHAR(100) | 密钥名称 |
| private_key | TEXT | AES-256 加密后的私钥内容 |
| fingerprint | VARCHAR(64) | 密钥指纹(用于展示) |
| created_at | DATETIME | 创建时间 |
### servers 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER PK | 自增主键 |
| name | VARCHAR(100) | 服务器名称 |
| url | VARCHAR(500) | Gitea 服务器地址(如 https://gitea.example.com |
| api_token | TEXT | Gitea API TokenAES-256 加密存储) |
| ssh_key_id | INTEGER FK | 关联 ssh_keys.id |
| sync_enabled | BOOLEAN | 是否启用自动同步 |
| schedule_cron | VARCHAR(50) | 定时表达式(如 `0 */2 * * *` |
| local_path | VARCHAR(500) | 本地仓库存储路径 |
| status | VARCHAR(20) | 连接状态connected / disconnected / untested |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
### repos 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER PK | 自增主键 |
| server_id | INTEGER FK | 关联 servers.id |
| name | VARCHAR(200) | 仓库名称 |
| full_name | VARCHAR(300) | 完整路径owner/repo |
| clone_url | VARCHAR(500) | SSH 克隆地址 |
| local_path | VARCHAR(500) | 本地镜像路径 |
| last_sync_at | DATETIME | 最后同步时间 |
| status | VARCHAR(20) | 状态pending / syncing / synced / failed |
| created_at | DATETIME | 创建时间 |
### sync_logs 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER PK | 自增主键 |
| repo_id | INTEGER FK | 关联 repos.id |
| status | VARCHAR(20) | synced / failed |
| started_at | DATETIME | 开始时间 |
| finished_at | DATETIME | 结束时间 |
| commits_count | INTEGER | 本次同步新增提交数 |
| error_msg | TEXT | 失败时的错误信息 |
| created_at | DATETIME | 创建时间 |
## 同步流程
### 首次同步clone
```
用户添加服务器 → 调 Gitea API 获取仓库列表
→ 对每个仓库:
1. 生成 local_path: data/repos/{server_name}/{full_name}
2. 解密 SSH 私钥到内存
3. git clone --mirror <clone_url> <local_path>
4. 记录 sync_log状态 synced提交数
5. 更新 repo.status = synced
```
### 增量同步fetch
```
定时任务触发 / 手动触发
→ 遍历该服务器下所有仓库:
1. repo.status = syncing
2. 解密 SSH 私钥到内存
3. git fetch --all
4. 对比 refs 变化,计算新增提交数
5. 记录 sync_log
6. repo.status = synced / failed
```
## 前端页面
### 页面布局
```
┌──────────────────────────────────────────────────┐
│ Git Manager [状态] [≡] │
├────────┬─────────────────────────────────────────┤
│ 仪表盘 │ │
│ 服务器 │ 主内容区 │
│ 仓库 │ │
│ 同步记录 │ │
│ SSH密钥 │ │
│ 系统设置 │ │
├────────┴─────────────────────────────────────────┤
│ 上次同步: 2026-03-30 14:00 | 下次: 16:00 | 共 42 仓库 │
└──────────────────────────────────────────────────┘
```
### 页面功能
**仪表盘:** 服务器数量、仓库总数、同步状态统计synced/failed/syncing、最近 5 条同步记录、磁盘占用。
**服务器管理:** 添加/编辑/删除 Gitea 服务器,配置 URL、API Token、关联 SSH 密钥、同步计划Cron 表达式),"测试连接"按钮验证配置有效性。
**仓库列表:** 按服务器分组展示,显示同步状态(标签颜色区分)、最后同步时间。支持搜索过滤。每行有"手动同步"按钮。
**同步记录:** 时间线形式展示同步日志,支持按状态(成功/失败)筛选,展开查看新增提交数和错误详情。
**SSH 密钥:** 上传私钥文件或粘贴内容,显示密钥指纹和关联服务器数,删除时检查是否有关联服务器。
**系统设置:** API Token 管理、全局仓库存储根路径、调度全局开关(暂停/恢复所有定时任务)。
## 安全设计
| 项 | 方案 |
|----|------|
| 私钥存储 | AES-256 加密后存入 SQLite加密密钥从环境变量 `GM_ENCRYPT_KEY` 读取 |
| Gitea Token | 同样 AES-256 加密存储 |
| API 认证 | Bearer Token配置在环境变量 `GM_API_TOKEN` |
| SSH 连接 | paramiko 使用内存中的解密私钥,不写入磁盘临时文件 |
| 输入校验 | Pydantic 模型自动校验所有 API 入参 |
## 环境变量
| 变量 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| GM_ENCRYPT_KEY | 是 | - | AES-256 加密密钥32字节 |
| GM_API_TOKEN | 是 | - | API 认证 Token |
| GM_DATA_DIR | 否 | ./data | 数据存储根目录 |
| GM_HOST | 否 | 0.0.0.0 | 监听地址 |
| GM_PORT | 否 | 8000 | 监听端口 |
## 部署方式
```bash
# 安装后端依赖
pip install -r backend/requirements.txt
# 构建前端
cd frontend && npm install && npm run build
# 构建产物自动输出到 backend/app/static/
# 配置环境变量
export GM_ENCRYPT_KEY=<your-encryption-key>
export GM_API_TOKEN=<your-api-token>
# 初始化数据库
python -m backend.init_db
# 启动服务
uvicorn backend.app.main:app --host 0.0.0.0 --port 8000
```
## MVP 交付范围
### 包含
- 多 Gitea 服务器管理(增删改查)
- SSH 密钥上传和管理
- 拉取服务器所有仓库列表
- 仓库 clone/fetch 同步
- 手动触发同步
- 定时自动同步APScheduler + Cron
- 同步历史记录查看
- 提交历史查看
- REST API版本化供外部集成
- API Token 认证
- Vue 3 Web 管理界面
### 不包含(后续迭代)
- 多用户 / 权限系统
- Webhook 实时推送
- GitLab / GitHub 支持
- Docker 容器化部署
- 仓库内容浏览(文件树)

22
frontend/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "git-manager-frontend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.4.15",
"vue-router": "^4.2.5",
"pinia": "^2.1.7",
"axios": "^1.6.5",
"element-plus": "^2.5.4",
"@element-plus/icons-vue": "^2.3.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.3",
"vite": "^5.0.11"
}
}