init
This commit is contained in:
34
specs/001-config-bootstrap/checklists/requirements.md
Normal file
34
specs/001-config-bootstrap/checklists/requirements.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: 配置与骨架
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2025-11-13
|
||||
**Feature**: [/home/rogee/Projects/any-hub/specs/001-config-bootstrap/spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
32
specs/001-config-bootstrap/contracts/cli-flags.md
Normal file
32
specs/001-config-bootstrap/contracts/cli-flags.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# CLI Contract: 配置与骨架
|
||||
|
||||
## Command Overview
|
||||
```
|
||||
any-hub [--config <path>] [--check-config] [--version]
|
||||
```
|
||||
|
||||
## Flags
|
||||
| Flag | Type | Default | Description | Behavior |
|
||||
|------|------|---------|-------------|----------|
|
||||
| `--config, -c` | string | `./config.toml` | 指定配置文件路径;优先级高于 `ANY_HUB_CONFIG` | 解析后记录在日志字段 `configPath`,路径不存在则退出并提示 |
|
||||
| `--check-config` | bool | false | 启用只校验模式,不启动 HTTP 服务 | 运行完整加载+校验链路;成功退出码 0,失败非 0 |
|
||||
| `--version` | bool | false | 输出版本信息并退出 | 打印语义化版本(含 commit/hash),忽略其他标志 |
|
||||
|
||||
## Exit Codes
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | 操作成功(验证通过或正常退出) |
|
||||
| 1 | 配置解析/校验失败 |
|
||||
| 2 | CLI 参数错误(未知标志、冲突) |
|
||||
|
||||
## Logging Guarantees
|
||||
- 每条日志包含:`timestamp`, `level`, `action` (`check_config`, `startup`, `version`), `configPath`, `result`, `hub`(若适用), `domain`(若适用).
|
||||
- 当写文件失败时会降级到 stdout,并再记录一条 `action=logger_fallback` 的警告。
|
||||
|
||||
## Sample Interactions
|
||||
1. `any-hub --check-config --config /etc/any-hub.toml`
|
||||
- 校验文件,通过则输出 `configuration_valid`,否则列出字段错误。
|
||||
2. `any-hub --version`
|
||||
- 输出 `any-hub version 0.1.0 (commit abc1234)` 并退出。
|
||||
3. `ANY_HUB_CONFIG=/etc/any-hub.toml any-hub`
|
||||
- 若未显式传 `--config`,程序读取环境变量路径并记录来源。
|
||||
62
specs/001-config-bootstrap/data-model.md
Normal file
62
specs/001-config-bootstrap/data-model.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Data Model: 配置与骨架
|
||||
|
||||
## Entities
|
||||
|
||||
### GlobalConfig
|
||||
- **Description**: 控制 any-hub 全局行为的配置段,位于 `config.toml` 根部。
|
||||
- **Fields**:
|
||||
- `LogLevel` (string, required, enum: trace/debug/info/warn/error)
|
||||
- `LogFilePath` (string, optional, default "")
|
||||
- `LogMaxSize` (int, optional, default 100, MB)
|
||||
- `LogMaxBackups` (int, optional, default 10)
|
||||
- `LogCompress` (bool, optional, default true)
|
||||
- `StoragePath` (string, required, must be writable directory)
|
||||
- `CacheTTL` (duration seconds, optional, default 86400)
|
||||
- `MaxMemoryCacheSize` (bytes, optional, default 268435456)
|
||||
- `MaxRetries` (int >=0, default 3)
|
||||
- `InitialBackoff` (duration, default 1s)
|
||||
- `UpstreamTimeout` (duration, default 30s)
|
||||
- **Validation Rules**: 路径必须存在或可创建;数值必须 >0;LogLevel 必须匹配允许枚举。
|
||||
- **Relationships**: 被 `Config` 聚合并为 `HubConfig` 提供默认值。
|
||||
|
||||
### HubConfig
|
||||
- **Description**: 描述单个代理仓库实例。
|
||||
- **Fields**:
|
||||
- `Name` (string, required, unique)
|
||||
- `Domain` (string, required, FQDN)
|
||||
- `Port` (int, required, 1-65535)
|
||||
- `Upstream` (string, required, http/https URL)
|
||||
- `Proxy` (string, optional, URL)
|
||||
- `CacheTTL` (duration, optional, overrides global)
|
||||
- `EnableHeadCheck` (bool, optional, default true)
|
||||
- **Validation Rules**: `Name` 必须唯一;`Domain` + `Port` 组合不得冲突;URL 必须可解析。
|
||||
- **Relationships**: 属于 `Config`,在运行时用于初始化路由、缓存目录 `StoragePath/<Name>`。
|
||||
|
||||
### Config (Root)
|
||||
- **Description**: 聚合 `GlobalConfig` 与一个或多个 `HubConfig` 条目。
|
||||
- **Fields**:
|
||||
- `Global` (`GlobalConfig`, required)
|
||||
- `Hubs` (`[]HubConfig`, min length 1)
|
||||
- **Validation Rules**: 至少一个 Hub;Hub 列表中所有必填字段存在;引用 `StoragePath` 时需组合 `Hub.Name` 生成可写路径。
|
||||
- **State Transitions**:
|
||||
1. **Loaded**: 从 TOML 解析到结构体。
|
||||
2. **Validated**: 完成默认值填充 + 规则校验。
|
||||
3. **Active**: 提供给 CLI/服务器初始化。
|
||||
|
||||
### CLIFlagSet
|
||||
- **Description**: 运行入口解析到的参数集合。
|
||||
- **Fields**:
|
||||
- `ConfigPath` (string, default `./config.toml`)
|
||||
- `CheckOnly` (bool, set by `--check-config`)
|
||||
- `ShowVersion` (bool, set by `--version`)
|
||||
- **Validation Rules**: 当 `ConfigPath` 指向不存在文件时须立即报错;`CheckOnly` 与正常启动互斥(若为 true 则不进入 server)。
|
||||
- **Relationships**: 在主函数中决定执行路径(验证 / 启动 / 版本输出)。
|
||||
|
||||
### LogSinkConfig
|
||||
- **Description**: 运行期日志输出策略,来源于 `GlobalConfig`。
|
||||
- **Fields**:
|
||||
- `Level` (string, required)
|
||||
- `Output` (enum: stdout|file)
|
||||
- `FilePath`, `MaxSize`, `MaxBackups`, `Compress`(当 Output=file 时必填)
|
||||
- **Validation Rules**: 当选择 file 时需验证写权限;level 必须映射到 Logrus 支持的等级。
|
||||
- **Relationships**: `initLogger(cfg)` 根据该实体创建 logger,并将字段注入所有日志条目。
|
||||
69
specs/001-config-bootstrap/plan.md
Normal file
69
specs/001-config-bootstrap/plan.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Implementation Plan: 配置与骨架
|
||||
|
||||
**Branch**: `001-config-bootstrap` | **Date**: 2025-11-13 | **Spec**: [spec.md](./spec.md)
|
||||
**Input**: Feature specification from `/specs/001-config-bootstrap/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
Phase 0 delivers a reliable bootstrap for any-hub: a validated `internal/config` loader with defaults/tests, a CLI entry (`cmd/any-hub`) that accepts `--config`, `--check-config`, `--version`, and a Logrus + Lumberjack logging stack that honors config-provided level/output. The approach is to codify the configuration schema (global + `[[Hub]]`), implement strict validation with helpful errors, ensure CLI paths separate validation vs. runtime, and guarantee consistent structured logging for both success and failure flows.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Go 1.25+ (静态链接,单二进制交付)
|
||||
**Primary Dependencies**: Fiber v3(HTTP 服务)、Viper(配置)、Logrus + Lumberjack(结构化日志 & 滚动)、标准库 `net/http`/`io`
|
||||
**Storage**: 本地文件系统缓存目录 `StoragePath/<Hub>/<path>` + `.meta` 元数据
|
||||
**Testing**: `go test ./...`,使用 `httptest`、临时目录和自建上游伪服务验证配置/缓存/代理路径
|
||||
**Target Platform**: Linux/Unix CLI 进程,由 systemd/supervisor 管理,匿名下游客户端
|
||||
**Project Type**: 单 Go 项目(`cmd/` 入口 + `internal/*` 包)
|
||||
**Performance Goals**: 缓存命中直接返回;回源路径需流式转发,单请求常驻内存 <256MB;命中/回源日志可追踪
|
||||
**Constraints**: 禁止 Web UI 或账号体系;所有行为受单一 TOML 配置控制;每个 Hub 需独立 Domain/Port 绑定;仅匿名访问
|
||||
**Scale/Scope**: 支撑 Docker/NPM/Go/PyPI 等多仓代理,面向弱网及离线缓存复用场景
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- Feature 聚焦“轻量、匿名、CLI 多仓代理”,未引入 Web UI、账号体系或与代理无关的能力。
|
||||
- 仅使用 Go + 宪法指定依赖(Fiber/Viper/Logrus/Lumberjack + 标准库);无新增第三方库需求。
|
||||
- 行为完全由 `config.toml` 控制,计划中定义所有默认值、优先级(标志 > 环境变量 > 默认)、以及迁移策略。
|
||||
- CLI 与配置实现维持缓存优先 + 流式回源的大前提,并输出命中/回源/失败日志字段,满足可观测原则。
|
||||
- 计划明确覆盖配置解析、必填字段校验、Host Header 约束、中文注释与 `go test` 需求,满足测试 Gate。
|
||||
|
||||
**Post-design re-check (Phase 1)**: 设计阶段确认各文档与合同未偏离宪法约束,仍无新增依赖、UI 或多配置源,GATE 维持通过。
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/[###-feature]/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
```text
|
||||
cmd/any-hub/main.go # CLI 入口、参数解析
|
||||
internal/config/ # TOML 加载、默认值、校验
|
||||
internal/server/ # Fiber 服务、路由、中间件
|
||||
internal/cache/ # 磁盘/内存缓存与 .meta 管理
|
||||
internal/proxy/ # 上游访问、缓存策略、流式复制
|
||||
configs/ # 示例 config.toml(如需)
|
||||
tests/ # `go test` 下的单元/集成测试,用临时目录
|
||||
```
|
||||
|
||||
**Structure Decision**: 采用单 Go 项目结构,特性代码应放入上述现有目录;如需新增包或目录,必须解释其与 `internal/*` 的关系并给出后续维护策略。
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| _None_ | — | — |
|
||||
40
specs/001-config-bootstrap/quickstart.md
Normal file
40
specs/001-config-bootstrap/quickstart.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Quickstart: 配置与骨架
|
||||
|
||||
## 前置条件
|
||||
- 已安装 Go 1.25+,并能运行 `go test ./...`。
|
||||
- 提供一份最小 `config.toml`,包含全局设置与至少一个 `[[Hub]]`。
|
||||
- 当前分支:`001-config-bootstrap`。
|
||||
|
||||
## 步骤
|
||||
1. **编写配置文件**
|
||||
- 复制 `config.example.toml`(若无则创建)到项目根目录 `config.toml`。
|
||||
- 填写必填字段:`StoragePath`、`[[Hub]]` 的 `Name/Domain/Port/Upstream`。
|
||||
2. **运行配置校验**
|
||||
- 执行 `go run ./cmd/any-hub --check-config --config ./config.toml`。
|
||||
- 观察输出:成功时显示“配置验证通过”;失败时按日志提示修复字段。
|
||||
3. **启动 CLI**
|
||||
- `go run ./cmd/any-hub --config ./config.toml`。
|
||||
- 启动日志会打印版本、配置路径、监听端口。
|
||||
4. **查看版本**
|
||||
- `go run ./cmd/any-hub --version`。
|
||||
- 确认输出如 `any-hub version 0.1.0 (commit abc1234)`。
|
||||
5. **检查日志**
|
||||
- 若配置写入文件:查看 `LogFilePath` 位置的滚动日志。
|
||||
- 默认 stdout:在终端确认日志字段(level, action, hub, domain, result)。
|
||||
|
||||
## 验证清单
|
||||
- [ ] `--check-config` 能阻断非法配置并返回非零退出码。
|
||||
- [ ] CLI 成功读取默认或自定义配置路径。
|
||||
- [ ] `--version` 立即输出版本信息。
|
||||
- [ ] 日志初始化遵循配置的级别与输出。
|
||||
|
||||
## 常见错误排查
|
||||
- 出现 `Hub[docker].Domain: Domain 不允许包含协议头`:检查 Hub 配置中是否误写 `http://` 前缀。
|
||||
- 出现 `Global.StoragePath: 不能为空`:确保 `config.toml` 中填写路径或使用默认值。
|
||||
- 若日志提示 `logger_fallback`,说明文件不可写,删除 `LogFilePath` 或放宽目录权限。
|
||||
|
||||
## 日志字段说明
|
||||
- `action`: 表示当前操作(`check_config`、`startup` 等)。
|
||||
- `configPath`: 实际使用的配置文件路径,方便验证 flag/环境变量生效。
|
||||
- `result`: 操作结果(`ok`、`error`)。
|
||||
- `hub` / `domain` / `cacheHit`: 由 `internal/logging.RequestFields` 提供,后续代理请求会自动填充。
|
||||
26
specs/001-config-bootstrap/research.md
Normal file
26
specs/001-config-bootstrap/research.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Research: 配置与骨架
|
||||
|
||||
## 配置加载与校验
|
||||
- **Decision**: 使用 Viper 读取单一 `config.toml`,解析到 `GlobalConfig` + `[]HubConfig` 结构,先合并默认值再执行字段级校验(必填、范围、路径可写),校验失败即返回结构化错误并阻断启动。
|
||||
- **Rationale**: 单一入口与结构化错误能满足宪法“单一控制平面 + 阻断非法配置”的要求,也方便 `--check-config` 模式直接复用相同逻辑。
|
||||
- **Alternatives considered**: 直接手写 `encoding/toml` 解析(缺乏灵活默认/环境覆盖),多文件拆分(违反宪法单一配置要求)。
|
||||
|
||||
## CLI 标志与执行模式
|
||||
- **Decision**: 使用 `pflag`/`flag` 解析 `--config`, `--check-config`, `--version`,解析顺序:标志 > `ANY_HUB_CONFIG` 环境变量 > 默认路径;`--check-config` 触发只读模式,`--version` 输出后立即退出。
|
||||
- **Rationale**: 可预测的优先级防止多来源冲突;独立模式让 CI 可执行校验并快速确认版本,贴合用户故事 1/2。
|
||||
- **Alternatives considered**: 将 `--check-config` 拆成单独子命令(增加学习成本)、允许多配置文件(违背宪法)。
|
||||
|
||||
## 日志初始化策略
|
||||
- **Decision**: 统一在 CLI 启动与校验路径调用 `initLogger(cfg)`,基于 Logrus + Lumberjack:默认 stdout,若配置文件路径可写则启用滚动文件,同时注入字段(hub、domain、action、configPath、result)。
|
||||
- **Rationale**: 结构化日志 + 滚动文件满足宪法可观测性;单初始化路径减少重复配置并确保 `--check-config` 也有可追踪日志。
|
||||
- **Alternatives considered**: log/slog(失去与现有宪法保持一致的依赖);多 logger 实例(复杂度高且容易导致字段缺失)。
|
||||
|
||||
## 测试与验证覆盖
|
||||
- **Decision**: 编写 `internal/config` 单元测试覆盖:必填字段缺失、类型错误、默认值填充、`[[Hub]]` 唯一约束;CLI 集成测试使用 `httptest`/临时目录模拟不同标志组合;日志初始化通过接口注入 writer 以断言字段。
|
||||
- **Rationale**: 完成宪法要求的配置/缓存/路由主路径测试,确保 Phase 0 可在 `go test ./...` 下复现;临时目录避免污染本地环境。
|
||||
- **Alternatives considered**: 仅手动测试(无法满足 Gate)、依赖真实文件系统路径(降低可靠性)。
|
||||
|
||||
## 依赖与最佳实践
|
||||
- **Decision**: 保持依赖集为 Go 标准库 + Viper + Logrus + Lumberjack + pflag(随 Viper 引入),不新增网络/数据库库;使用 `fsnotify` 仅作为 Viper 间接依赖,不启用热加载。
|
||||
- **Rationale**: 满足宪法“受控依赖”原则并保持二进制小巧;禁用热加载可降低错配风险并符合单一配置理念。
|
||||
- **Alternatives considered**: 引入 Cobra CLI(依赖过重且 Phase 0 命令简单)、使用 zap 日志(与既定依赖不符)。
|
||||
96
specs/001-config-bootstrap/spec.md
Normal file
96
specs/001-config-bootstrap/spec.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Feature Specification: 配置与骨架
|
||||
|
||||
**Feature Branch**: `001-config-bootstrap`
|
||||
**Created**: 2025-11-13
|
||||
**Status**: Draft
|
||||
**Input**: User description: ":配置与骨架 - 实现 internal/config,覆盖 TOML 加载、默认值、校验及测试。 - 初始化 cmd/any-hub,解析 --config、--check-config、--version。 - 建立 logrus 初始化流程,完成基础日志输出。"
|
||||
|
||||
> 宪法对齐(v1.0.0):
|
||||
> - 保持“轻量、匿名、CLI 多仓代理”定位:不得引入 Web UI、账号体系或与代理无关的范围。
|
||||
> - 方案必须基于 Go 1.25+ 单二进制,依赖仅限 Fiber、Viper、Logrus/Lumberjack 及必要标准库。
|
||||
> - 所有行为由单一 `config.toml` 控制;若需新配置项,需在规范中说明字段、默认值与迁移策略。
|
||||
> - 设计需维护缓存优先 + 流式传输路径,并描述命中/回源/失败时的日志与观测需求。
|
||||
> - 验收必须包含配置解析、缓存读写、Host Header 绑定等测试与中文注释交付约束。
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - 运维人员校验配置 (Priority: P1)
|
||||
|
||||
运维人员需要在启动 any-hub 之前验证 `config.toml` 是否完整、字段合法,以便在 CI/CD 和本地快速发现错误配置,避免生产中断。
|
||||
|
||||
**Why this priority**: 若配置错误无法在启动前阻断,将直接导致多仓代理不可用,是 Phase 0 的最小可行能力。
|
||||
|
||||
**Independent Test**: 提供一份带有缺失字段或非法取值的配置,通过 `--check-config` 运行,命令应返回错误并指出问题;提供一份合格配置则应返回成功状态。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 配置文件缺少必填字段,**When** 运维运行 `any-hub --check-config --config broken.toml`,**Then** 命令立即失败并展示缺失字段及修复指引。
|
||||
2. **Given** 配置文件全部合法,**When** 运维运行 `any-hub --check-config --config valid.toml`,**Then** 命令成功退出并记录“配置有效”的日志。
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - CLI 操作者加载配置并启动 (Priority: P1)
|
||||
|
||||
CLI 操作者需要使用 `--config` 标志加载默认或指定路径的 `config.toml`,在无额外 UI 的环境中完成 any-hub 启动,并通过 `--version` 确认二进制身份。
|
||||
|
||||
**Why this priority**: Phase 0 要求可运行的入口;若 CLI 无法正确加载配置或识别版本,后续阶段均无法进行。
|
||||
|
||||
**Independent Test**: 准备默认路径 `config.toml` 与自定义路径 `custom.toml`,分别运行 `any-hub` 并确认应用读取正确文件、输出版本信息、在缺失文件时提供明确错误。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 默认路径存在配置文件,**When** 操作者直接运行 `any-hub`,**Then** 程序打印版本信息、载入默认配置并开始初始化。
|
||||
2. **Given** 自定义配置路径,**When** 操作者运行 `any-hub --config /tmp/custom.toml`,**Then** 程序使用该路径并在日志中记录“正在读取 /tmp/custom.toml”。
|
||||
3. **Given** 用户想快速确认版本,**When** 运行 `any-hub --version`,**Then** 输出语义化版本并立即退出。
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - 观察日志确保运行健康 (Priority: P2)
|
||||
|
||||
SRE 希望 any-hub 在启动和运行阶段输出结构化、可预测的日志,用以判断配置来源、监听端口和缓存策略,支持文件或 stdout 并尊重配置中的日志级别。
|
||||
|
||||
**Why this priority**: Phase 0 需具备排障手段;若无一致日志,无法判断配置成效或升级问题。
|
||||
|
||||
**Independent Test**: 通过配置项将日志写入文件与 stdout,触发一次成功的配置校验和一次失败案例,验证日志包含时间、级别、Hub 名称/域名、动作结果等字段。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 配置中启用文件日志,**When** 程序启动,**Then** 在文件中生成带时间戳、级别、消息字段的日志并滚动管理。
|
||||
2. **Given** 配置校验失败,**When** 运维重试 `--check-config`,**Then** 日志包含校验错误详情和“阻止启动”提示,帮助快速定位问题。
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- 配置文件缺失或路径不可读时,需要输出可操作指引(例如“请提供 --config 或在当前目录创建 config.toml”)。
|
||||
- TOML 语法错误或类型不匹配时,必须指出具体行列与字段,避免笼统报错。
|
||||
- 日志路径无写权限或磁盘填满时,应用需自动退回 stdout 并提示权限/容量问题。
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: 系统必须提供 `--config` 标志,默认读取工作目录下的 `config.toml`,并在日志中记录最终使用的路径。
|
||||
- **FR-002**: 系统必须提供 `--check-config` 模式,在不启动服务器的情况下完成配置加载、默认值合并与字段校验,并以退出码区分成功/失败。
|
||||
- **FR-003**: 系统必须提供 `--version` 标志,输出语义化版本号(含 Git 提交或构建信息)后立即退出且不执行其他逻辑。
|
||||
- **FR-004**: 配置加载器必须支持全局与 `[[Hub]]` 段的必填字段校验,自动补齐默认值并返回结构化错误,且覆盖单元测试。
|
||||
- **FR-005**: 日志初始化流程必须尊重配置中的级别/输出位置,支持 stdout 及文件滚动策略,且在任何模式(校验/启动)中启用结构化日志字段。
|
||||
- **FR-006**: 当配置合法时,系统必须输出一条“配置验证通过”或“启动完成”的信息;当配置非法时,必须输出明确的字段及原因,方便用户修复。
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Configuration Profile**: 由全局段(日志、缓存、重试、内存限制)及一个或多个 Hub 条目组成,需保证 `Name/Domain/Port/Upstream` 等必填项存在,并允许可选字段继承或覆盖默认值。
|
||||
- **CLI Flag Set**: 运行入口可解析 `--config`、`--check-config`、`--version` 等标志;不同组合需决定流程(例如校验模式不启动服务器、版本模式直接退出)。
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: 100% 的非法 `config.toml` 在 `--check-config` 阶段被阻断,并输出至少一条包含字段名与错误原因的日志。
|
||||
- **SC-002**: 在默认与自定义路径情况下,`any-hub` 启动时间不超过 2 秒即可完成配置加载并打印版本信息。
|
||||
- **SC-003**: 至少 95% 的启动/校验日志包含时间、级别、动作、配置路径等标准字段,可直接用于排障。
|
||||
- **SC-004**: CLI 操作者能够在 1 次运行内确认版本号与配置状态,相关指令的使用步骤在 README/DEVELOPMENT 中有据可查。
|
||||
|
||||
## Assumptions
|
||||
|
||||
- 默认配置文件路径为仓库根目录或部署目录下的 `config.toml`;若环境变量 `ANY_HUB_CONFIG` 已存在,则 CLI 标志优先级高于环境变量。
|
||||
- 日志默认输出到 stdout,且仅在配置显式开启时写入文件,避免初期部署的磁盘依赖。
|
||||
- 版本号信息可通过构建流程注入,若缺失则回退为“unknown”。
|
||||
152
specs/001-config-bootstrap/tasks.md
Normal file
152
specs/001-config-bootstrap/tasks.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
|
||||
description: "Task list for feature 001-config-bootstrap"
|
||||
---
|
||||
|
||||
# Tasks: 配置与骨架
|
||||
|
||||
**Input**: Design documents from `/specs/001-config-bootstrap/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: 宪法 v1.0.0 要求覆盖配置解析、CLI 流程与日志可观测性,本任务列表默认包含相应单元/集成测试。
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: Ensure repository has the baseline artifacts and documentation required for Phase 0 delivery.
|
||||
|
||||
- [X] T001 Create canonical sample config包含全局段与单个 Hub 的默认值在 `configs/config.example.toml`
|
||||
- [X] T002 Document Phase 0 bootstrap prerequisites(Go 版本、依赖、命令)于 `DEVELOPMENT.md`
|
||||
- [X] T003 [P] Link quickstart入口与 CLI 使用章节到 `README.md`,方便新成员找到 `--check-config`/`--version`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: Provide shared config/CLI scaffolding and fixtures required by all user stories.
|
||||
|
||||
- [X] T004 定义 `Config`/`GlobalConfig`/`HubConfig` 结构与默认常量于 `internal/config/types.go`
|
||||
- [X] T005 实现基础加载器(读取 TOML、合并默认值)骨架于 `internal/config/loader.go`
|
||||
- [X] T006 [P] 抽象校验错误类型与帮助函数(含字段路径)于 `internal/config/errors.go`
|
||||
- [X] T007 建立 `internal/config/testdata/{valid,missing}.toml` 及通用测试 helper 于 `internal/config/test_helpers_test.go`
|
||||
- [X] T008 为 CLI 提供统一 flag 解析入口(占位逻辑)于 `cmd/any-hub/main.go`
|
||||
|
||||
**Checkpoint**: Config 模型、加载骨架与 CLI flag 入口完成后,用户故事可并行推进。
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - 运维人员校验配置 (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: 任何非法 `config.toml` 都能在 `--check-config` 阶段被阻断,同时输出明确字段与修复建议。
|
||||
|
||||
**Independent Test**: 使用 `go test ./cmd/any-hub -run TestCheckConfig*` 和 `go test ./internal/config`,验证缺失字段/类型错误会失败且成功配置通过。
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [X] T009 [P] [US1] 编写缺失字段/类型错误用例,确保 `LoadConfig` 返回结构化错误(`internal/config/loader_test.go`)
|
||||
- [X] T010 [P] [US1] 编写 CLI 集成测试:执行 `--check-config` 并断言退出码/日志(`cmd/any-hub/main_test.go`)
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T011 [US1] 完成默认值合并与字段级校验逻辑(路径/数值/唯一性)于 `internal/config/validation.go`
|
||||
- [X] T012 [US1] 将 `--check-config` flag 与新校验逻辑接线,并返回对应退出码于 `cmd/any-hub/main.go`
|
||||
- [X] T013 [US1] 定义用户可读的错误消息与中文注释,更新 `internal/config/errors.go`
|
||||
- [X] T014 [US1] 更新 `quickstart.md` 与 `README.md`,列出 `--check-config` 使用示例与常见错误修复
|
||||
|
||||
**Checkpoint**: `any-hub --check-config` 可以独立验证配置且输出标准日志。
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - CLI 操作者加载配置并启动 (Priority: P1)
|
||||
|
||||
**Goal**: CLI 能根据 flag/环境/默认顺序加载配置,启动流程打印版本与配置来源,并提供 `--version` 快速查询。
|
||||
|
||||
**Independent Test**: 通过 `cmd/any-hub/main_test.go` 的 flag 优先级测试以及 `cmd/any-hub/version_test.go` 的版本输出测试验证。
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [X] T015 [P] [US2] 编写 flag vs. 环境变量 vs. 默认路径的优先级测试(`cmd/any-hub/main_test.go`)
|
||||
- [X] T016 [P] [US2] 为 `--version` 输出添加测试,断言语义化字符串与退出行为(`cmd/any-hub/version_test.go`)
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T017 [US2] 实现配置路径解析顺序(flag > `ANY_HUB_CONFIG` > 默认),并在日志中记录来源(`cmd/any-hub/main.go`)
|
||||
- [X] T018 [US2] 实装 `--version` 逻辑(含构建信息注入)于 `cmd/any-hub/version.go`
|
||||
- [X] T019 [US2] 在正常启动路径中输出版本号、监听端口与配置路径(结构化日志)于 `cmd/any-hub/main.go`
|
||||
- [X] T020 [US2] 更新 `DEVELOPMENT.md` 与 `README.md` 的 CLI 章节,描述 flag 组合与退出码
|
||||
|
||||
**Checkpoint**: `any-hub` 可直接启动/加载配置,`--version` 即时返回信息。
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - 观察日志确保运行健康 (Priority: P2)
|
||||
|
||||
**Goal**: 启动与校验流程都能输出结构化日志,支持 stdout/文件滚动,并在写文件失败时自动回退。
|
||||
|
||||
**Independent Test**: 通过 `internal/logging/logger_test.go`(模拟文件权限/滚动策略)与 `cmd/any-hub/logging_integration_test.go`(验证 stdout 回退)。
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
- [X] T021 [P] [US3] 创建 logger 单元测试:验证级别/输出配置与字段注入(`internal/logging/logger_test.go`)
|
||||
- [X] T022 [P] [US3] 编写集成测试覆盖文件不可写时的 stdout 回退(`cmd/any-hub/logging_integration_test.go`)
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T023 [US3] 新增 `internal/logging/logger.go`,根据配置初始化 Logrus + Lumberjack,并暴露 `InitLogger`
|
||||
- [X] T024 [US3] 在 `cmd/any-hub/main.go` 的校验与启动路径调用 `InitLogger`,并注入 `action/configPath/result` 字段
|
||||
|
||||
- [X] T025 [US3] 添加日志字段构建与公共 helper(`internal/logging/fields.go`),确保包含 hub/domain/命中状态
|
||||
- [X] T026 [US3] 更新 `quickstart.md`/`DEVELOPMENT.md`,记录日志配置字段与排障步骤
|
||||
|
||||
**Checkpoint**: 日志可根据配置切换输出,并提供足够字段支持排障。
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Finishing touches that ensure maintainability and documentation alignment.
|
||||
|
||||
- [X] T027 运行 `gofmt`/`go test ./...` 并将结果记录到 `DEVELOPMENT.md` 的验证段
|
||||
- [X] T028 为关键结构与算法补充中文注释,覆盖 `internal/config`、`cmd/any-hub`、`internal/logging`
|
||||
- [X] T029 [P] 更新 `CHANGELOG.md`(若存在)与 `README.md`,概述 Phase 0 能力及后续路线
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
- Phase 1 → Phase 2 → User Stories (US1/US2/US3) → Phase 6
|
||||
|
||||
### User Story Dependencies
|
||||
- US1 和 US2 都依赖 Phase 2;US3 依赖 US1/US2 的 CLI/logging 接口完成后再启
|
||||
- US1 是 MVP(配置校验),需先完成以解锁后续部署
|
||||
|
||||
### Parallel Execution Examples
|
||||
- **US1**: T009 与 T010 可并行编写测试;实现任务 T011/T012 需等测试框架 ready
|
||||
- **US2**: T015 与 T016 可同测;T017/T018 可并行后共同驱动 T019
|
||||
- **US3**: T021 与 T022 可并行;T023/T025 可并行后统一在 T024 接线
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
1. 完成 Phase 1-2
|
||||
2. 交付 US1(配置校验 + CLI check),并通过 quickstart 测试
|
||||
3. 可在此阶段发布 CLI 校验版本,供 CI 使用
|
||||
|
||||
### Incremental Delivery
|
||||
1. MVP(US1)完成后,向 CLI 启动/版本(US2)演进
|
||||
2. 最后实现日志观测(US3),再进入 Polish
|
||||
|
||||
### Parallel Team Strategy
|
||||
- 团队 A:聚焦 `internal/config`(US1)
|
||||
- 团队 B:并行处理 CLI flag/版本(US2)
|
||||
- 团队 C:在 CLI 接口稳定后实现日志(US3)
|
||||
34
specs/002-fiber-single-proxy/checklists/requirements.md
Normal file
34
specs/002-fiber-single-proxy/checklists/requirements.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: HTTP 服务与单仓代理
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2025-11-13
|
||||
**Feature**: [/home/rogee/Projects/any-hub/specs/002-fiber-single-proxy/spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
34
specs/002-fiber-single-proxy/contracts/http-proxy.md
Normal file
34
specs/002-fiber-single-proxy/contracts/http-proxy.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Contract: Host 路由 + 缓存代理
|
||||
|
||||
## Request Flow
|
||||
1. Client sends HTTP request to `http://<listen-host>:<port>/<path>` with `Host: <hub-domain>`.
|
||||
2. Fiber middleware resolves HubRoute; if missing → 404 JSON `{ "error": "host_unmapped" }`.
|
||||
3. Proxy handler checks disk cache key = `<hub-name>/<path>`.
|
||||
4. Cache hit (Valid): stream file to client with stored headers.
|
||||
5. Cache stale/miss: build upstream request `Upstream + path`, attach `If-None-Match`/`If-Modified-Since` when available, stream response back; on 200 store body to cache.
|
||||
|
||||
## Required Headers
|
||||
- Host (required) → determines Hub.
|
||||
- `X-Any-Hub-Upstream` (response) – actual upstream URL (debugging).
|
||||
- `X-Any-Hub-Cache-Hit` (response) – `true/false`.
|
||||
- `X-Request-ID` (response) – correlation id for logs.
|
||||
|
||||
## Error Codes
|
||||
| Scenario | Status | Body |
|
||||
|----------|--------|------|
|
||||
| Host 未配置 | 404 | `{ "error": "host_unmapped" }` |
|
||||
| 上游 4xx/5xx | mirror | 上游 body 原样透传 |
|
||||
| 缓存写入失败 | 502 | `{ "error": "cache_write_failed" }` |
|
||||
|
||||
## Logging Fields
|
||||
```
|
||||
{
|
||||
"action": "proxy",
|
||||
"hub": "docker",
|
||||
"domain": "docker.hub.local",
|
||||
"cache_hit": true,
|
||||
"upstream_status": 200,
|
||||
"elapsed_ms": 123,
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
33
specs/002-fiber-single-proxy/data-model.md
Normal file
33
specs/002-fiber-single-proxy/data-model.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Data Model: HTTP 服务与单仓代理
|
||||
|
||||
## Entities
|
||||
|
||||
### HubRoute
|
||||
- **Description**: Host/端口到上游仓库的映射,供 Fiber 路由和 Proxy handler 使用。
|
||||
- **Fields**: `Name` (string, unique), `Domain` (string, FQDN), `Port` (int, 1-65535), `Upstream` (URL), `Proxy` (URL, optional), `CacheTTL` (duration override), `EnableHeadCheck` (bool).
|
||||
- **Validation**: Name 唯一;Domain 不含协议/路径;Upstream 必须 http/https。
|
||||
- **Relationships**: 由 config 加载到 `HubRegistry`;与 CacheEntry、ProxyRequest 通过 `Name` 关联。
|
||||
|
||||
### HubRegistry
|
||||
- **Description**: 运行期内存结构,按 (Port, Host) 查找 HubRoute。
|
||||
- **Fields**: `routes map[key]HubRoute`, key = `port:host`;支持默认端口匹配。
|
||||
- **Operations**: `Lookup(host, port)`, `List()`;返回结果提供给 Fiber 中间件。
|
||||
|
||||
### CacheEntry
|
||||
- **Description**: 表示磁盘缓存的一个对象(正文 + 元数据)。
|
||||
- **Fields**: `HubName`, `Path`, `FilePath`, `MetaPath`, `ETag`, `LastModified`, `StoredAt`, `TTL`, `Size`, `Checksum`。
|
||||
- **State**:
|
||||
1. `Empty`: 未缓存
|
||||
2. `Valid`: TTL 内、可直接返回
|
||||
3. `Stale`: TTL 过期,需 revalidate
|
||||
4. `Invalid`: 写入失败或上游错误,需删除
|
||||
|
||||
### ProxyRequest
|
||||
- **Description**: 一次请求生命周期的可观测性数据。
|
||||
- **Fields**: `ID`, `HubName`, `Host`, `Path`, `Method`, `CacheHit` (bool), `UpstreamStatus`, `LatencyMs`, `Error`。
|
||||
- **Usage**: 记录日志/metrics,未来可扩展 tracing。
|
||||
|
||||
### SampleConfig
|
||||
- **Description**: 示例配置集(Docker/NPM)。
|
||||
- **Fields**: `Type` (docker|npm), `Domain`, `Port`, `Upstream`, `Proxy`, `Notes`。
|
||||
- **Purpose**: quickstart & integration tests复用,证明真实配置长什么样。
|
||||
69
specs/002-fiber-single-proxy/plan.md
Normal file
69
specs/002-fiber-single-proxy/plan.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Implementation Plan: HTTP 服务与单仓代理
|
||||
|
||||
**Branch**: `002-fiber-single-proxy` | **Date**: 2025-11-13 | **Spec**: [spec.md](./spec.md)
|
||||
**Input**: Feature specification from `/specs/002-fiber-single-proxy/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver a Phase 1-ready proxy: Fiber HTTP server routing by Host→Hub, disk cache with TTL + conditional revalidation, and runnable Docker/NPM samples backed by integration tests. Core work spans server/router scaffolding, cache/proxy pipeline, observability, and documentation to prove the feature end to end.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Go 1.25+ (静态链接,单二进制交付)
|
||||
**Primary Dependencies**: Fiber v3(HTTP 服务)、Viper(配置)、Logrus + Lumberjack(结构化日志 & 滚动)、标准库 `net/http`/`io`
|
||||
**Storage**: 本地文件系统缓存目录 `StoragePath/<Hub>/<path>` + `.meta` 元数据
|
||||
**Testing**: `go test ./...`,配合 `httptest`、fake upstream server、临时目录验证路由/缓存/集成路径
|
||||
**Target Platform**: Linux/Unix CLI 进程,由 systemd/supervisor 管理,匿名下游客户端
|
||||
**Project Type**: 单 Go 项目(`cmd/` 入口 + `internal/*` 包)
|
||||
**Performance Goals**: 缓存命中路径需低延迟(相对首个请求减少 ≥70%),回源路径流式传输并限制常驻内存 <256MB,日志可追踪命中/回源/错误
|
||||
**Constraints**: 禁止 Web/UI、账号体系、额外配置源;必须通过 `config.toml` 驱动 Host、端口、缓存策略;请求匿名;不可新增未审核依赖
|
||||
**Scale/Scope**: Phase 1 仅实现单 Hub 路由 + Docker/NPM 示例;后续多 Hub、多协议将基于此扩展
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- 方案遵守“轻量 CLI 代理”定位,不引入 UI/账号/外部控制面。
|
||||
- 继续使用 Go + Fiber/Viper/Logrus/Lumberjack + 标准库,Helm/DB/Web 依赖均不在 scope。
|
||||
- 仅依赖 `config.toml` 说明 Host/端口/缓存字段;新增字段需写入配置 schema 与文档。
|
||||
- 缓存策略保持“命中即返回、未命中流式回源”,并要求结构化日志记录 `cache_hit`、上游状态等字段。
|
||||
- 计划包含配置解析、Host Header 路由、缓存逻辑、示例及测试,满足宪法强制测试/文档 Gate。
|
||||
|
||||
**Post-design re-check (Phase 1)**: 设计阶段将审查 server/cache/示例文档,确认未引入新依赖且 quickstart/contract 体现在 spec/plan/task 中;若出现额外库或多配置源,需重新申请豁免。
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/002-fiber-single-proxy/
|
||||
├── plan.md # 当前文件
|
||||
├── research.md # Phase 0 研究决策
|
||||
├── data-model.md # 实体/关系/约束
|
||||
├── quickstart.md # 演练 Docker/NPM 示例步骤
|
||||
├── contracts/ # CLI/Fiber/缓存交付合同(md 或 OpenAPI)
|
||||
└── tasks.md # Phase 2 工作拆解(/speckit.tasks 输出)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
```text
|
||||
cmd/any-hub/ # CLI 入口,启动 server/cache
|
||||
internal/server/ # Fiber app、Host Registry、路由/中间件
|
||||
internal/cache/ # 磁盘缓存/元数据/TTL
|
||||
internal/proxy/ # 回源/条件请求/流式复制
|
||||
internal/logging/ # 结构化日志字段
|
||||
configs/ # 示例 config (docker|npm)
|
||||
tests/integration/ # upstream stub + e2e 测试
|
||||
```
|
||||
|
||||
**Structure Decision**: 仍按单仓库单二进制;新增 `internal/server`, `internal/cache`, `tests/integration` 目录必须保持与 `internal/proxy` 清晰边界,所有引用通过包级接口注入,避免循环依赖。
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| _None_ | — | — |
|
||||
52
specs/002-fiber-single-proxy/quickstart.md
Normal file
52
specs/002-fiber-single-proxy/quickstart.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Quickstart: HTTP 服务与单仓代理
|
||||
|
||||
## 前置条件
|
||||
- Go 1.25+,可运行 `go run` 与 `go test`。
|
||||
- 访问 Docker Hub 或准备本地 fake upstream(见 tests/integration)。
|
||||
- 端口 5000 可用。
|
||||
|
||||
## 步骤
|
||||
1. **准备配置**
|
||||
```bash
|
||||
cp configs/docker.sample.toml config.toml
|
||||
```
|
||||
修改 `[[Hub]]` 中的 `Domain`(例如 `docker.hub.local`),并确保 `/etc/hosts` 映射到 `127.0.0.1`。
|
||||
2. **启动代理**
|
||||
```bash
|
||||
go run ./cmd/any-hub --config ./config.toml
|
||||
```
|
||||
终端将打印 `action=startup` 日志。
|
||||
3. **发起请求并观察路由**
|
||||
```bash
|
||||
curl -H "Host: docker.hub.local" http://127.0.0.1:5000/v2/
|
||||
```
|
||||
响应中会包含 `X-Any-Hub-Upstream`、`X-Any-Hub-Cache-Hit: false` 与 `X-Request-ID`,日志记录 `action=proxy`、`hub=docker`、`upstream_status=200`。
|
||||
4. **验证 Host 未配置时的 404**
|
||||
```bash
|
||||
curl -i -H "Host: unknown.hub.local" http://127.0.0.1:5000/v2/
|
||||
```
|
||||
代理会返回 `404 {"error":"host_unmapped"}`,日志显示 `action=host_lookup`。
|
||||
5. **切换 NPM 示例**
|
||||
```bash
|
||||
cp configs/npm.sample.toml config.toml
|
||||
npm config set registry http://127.0.0.1:5000 --global
|
||||
npm view lodash --registry http://127.0.0.1:5000 --fetch-timeout=60000
|
||||
```
|
||||
6. **运行路由集成测试**
|
||||
```bash
|
||||
go test ./tests/integration -run HostRouting -v
|
||||
```
|
||||
7. **使用示例配置快速体验**
|
||||
```bash
|
||||
# Docker Hub 示例
|
||||
./scripts/demo-proxy.sh docker
|
||||
|
||||
# NPM 示例 (监听 5001)
|
||||
./scripts/demo-proxy.sh npm
|
||||
```
|
||||
脚本会直接引用 `configs/docker.sample.toml` / `configs/npm.sample.toml` 并启动代理,确保对应域名已指向本机。
|
||||
|
||||
## 故障排查
|
||||
- `host_unmapped`: 检查 Host 头与 `config.toml` 中 Domain 是否一致。
|
||||
- `cache_write_failed`: 确认 `StoragePath` 可写,并有足够磁盘空间。
|
||||
- 上游 401/403: 公共仓库可能需要额外匿名访问 header;可在配置中新增自定义 header(future work)。
|
||||
26
specs/002-fiber-single-proxy/research.md
Normal file
26
specs/002-fiber-single-proxy/research.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Research: HTTP 服务与单仓代理
|
||||
|
||||
## Host → Hub 路由策略
|
||||
- **Decision**: 使用 Fiber 自定义中间件读取 `Host` Header 与监听端口,查询预构建的 `HubRegistry`,命中才放行,未命中返回 404 并记录 `host_unmapped`。
|
||||
- **Rationale**: 保持单一入口、避免误路由带来安全风险,同时兼容将来多 Hub 扩展。
|
||||
- **Alternatives considered**: 依赖 Fiber 原生多 App(导致重复配置/资源浪费);将未知 Host 回退到默认 Hub(难以追踪错误)。
|
||||
|
||||
## 缓存与条件回源
|
||||
- **Decision**: 采用磁盘文件 + `.meta` 元数据,写入时使用临时文件 + rename;记录 `ETag/Last-Modified/StoredAt`,并在重验证时携带 `If-None-Match`/`If-Modified-Since`。
|
||||
- **Rationale**: 磁盘缓存简单可靠,临时文件避免半写入;条件请求可显著降低带宽,满足 SC-001/SC-004。
|
||||
- **Alternatives considered**: 内存缓存(受限于 256MB);数据库存储(超出 Phase 1 范畴)。
|
||||
|
||||
## 上游 HTTP 客户端
|
||||
- **Decision**: 共享 `http.Client`,启用 `Transport` 连接复用、超时/Proxy/Retry 设置来自 config;在 `internal/proxy/upstream.go` 中封装请求构造。
|
||||
- **Rationale**: 避免为每个请求重建 client,提高吞吐并可统一超时;复用 config 中 Proxy 设定。
|
||||
- **Alternatives considered**: 每 Hub 创建独立 client(增加内存占用)、第三方 HTTP 库(违背依赖约束)。
|
||||
|
||||
## 示例仓库选择
|
||||
- **Decision**: 默认提供 Docker Hub 示例(manifest + layer)和 NPM 示例(package metadata);在 quickstart 中支持切换到本地 fake upstream(tests/integration/stub)。
|
||||
- **Rationale**: Docker/NPM 覆盖二进制/JSON 两类典型仓库;fake upstream 便于离线/CI;满足 spec 对示例的要求。
|
||||
- **Alternatives considered**: 只做 Docker(覆盖面不足)、引入真实 registry 模拟器(复杂度高)。
|
||||
|
||||
## 观测性与性能指标
|
||||
- **Decision**: 在 proxy handler 中记录 `hub`, `domain`, `cache_hit`, `upstream_status`, `elapsed_ms`; 通过 `logrus.WithFields` 输出 JSON;同时统计请求计数(后续可暴露 metrics)。
|
||||
- **Rationale**: 满足 FR-006 / SC-001/002;方便后续扩展 metrics;JSON 便于日志采集。
|
||||
- **Alternatives considered**: 文本日志(难以机器解析);延迟到未来阶段再实现(不符成功标准)。
|
||||
108
specs/002-fiber-single-proxy/spec.md
Normal file
108
specs/002-fiber-single-proxy/spec.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Feature Specification: HTTP 服务与单仓代理
|
||||
|
||||
**Feature Branch**: `002-fiber-single-proxy`
|
||||
**Created**: 2025-11-13
|
||||
**Status**: Draft
|
||||
**Input**: User description: "HTTP 服务与单仓代理 - 使用 Fiber 搭建 HTTP 服务,支持基于 Host 的路由到单一 Hub。 - 实现文件缓存模块(读写、TTL 检查),完成命中/回源流程。 - 提供 Docker Hub/NPM 任一仓库的最小可用代理,并通过集成测试验证。"
|
||||
|
||||
> 宪法对齐(v1.0.0):
|
||||
> - 保持“轻量、匿名、CLI 多仓代理”定位:不得引入 Web UI、账号体系或与代理无关的范围。
|
||||
> - 方案必须基于 Go 1.25+ 单二进制,依赖仅限 Fiber、Viper、Logrus/Lumberjack 及必要标准库。
|
||||
> - 所有行为由单一 `config.toml` 控制;若需新配置项,需在规范中说明字段、默认值与迁移策略。
|
||||
> - 设计需维护缓存优先 + 流式传输路径,并描述命中/回源/失败时的日志与观测需求。
|
||||
> - 验收必须包含配置解析、缓存读写、Host Header 绑定等测试与中文注释交付约束。
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Host 路由下的单仓访问 (Priority: P1)
|
||||
|
||||
企业内开发者希望通过 `docker.hub.local` 或 `npm.hub.local` 这样的 Host 头访问本地代理,系统需要根据 Host 定位唯一 Hub,并把请求透明转发至上游。
|
||||
|
||||
**Why this priority**: 没有稳定的 HTTP 入口和 Host 路由,就无法承载任何代理能力,是 Phase 1 的核心目标。
|
||||
|
||||
**Independent Test**: 启动 any-hub,准备含单一 Hub 的配置,使用 `curl -H "Host: docker.hub.local" http://127.0.0.1:5000/v2/_catalog`,验证请求进入正确的 Handler 并记录结构化日志。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 配置声明 Hub `docker` 监听端口 5000,**When** 客户端携带 `Host: docker.hub.local` 访问,**Then** Fiber 将请求路由到 docker Hub,并构造正确的上游 URL。
|
||||
2. **Given** 未声明的 Host,**When** 客户端请求,**Then** 立即返回 404 并在日志中标记 `host_unmapped`。
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - 磁盘缓存与回源流程 (Priority: P1)
|
||||
|
||||
CI/CD 任务需要重复下载相同镜像或包,期望 any-hub 能在本地缓存结果:命中时直接返回,过期或未命中时回源并刷新缓存,同时保持流式传输。
|
||||
|
||||
**Why this priority**: 缓存是代理节省带宽与加速的唯一方式;缺失会让 Phase 1 成为普通转发层。
|
||||
|
||||
**Independent Test**: 使用集成测试模拟上游服务器,第一次请求写入缓存,第二次命中缓存并快速返回;设置 TTL 过期后触发 revalidate。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 缓存文件存在且未过期,**When** 再次请求相同路径,**Then** 直接从磁盘流式返回,并记录 `cache_hit=true`。
|
||||
2. **Given** 缓存过期,**When** 新请求到达,**Then** 先向上游发送带条件的请求;若上游 304,则回退本地缓存,若 200 则边回源边写磁盘与客户端。
|
||||
3. **Given** 回源失败或磁盘写入错误,**Then** 系统返回合理的 5xx 并记录 `cache_hit=false` 与错误原因。
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - 最小 Docker/NPM 代理样例 (Priority: P2)
|
||||
|
||||
平台运维需要一份可运行的示例,让团队快速验证 Docker 或 NPM 仓库能通过 any-hub 获取常见资源,并在 CI 中运行端到端测试确保回源逻辑可靠。
|
||||
|
||||
**Why this priority**: 实际仓库样例可以验证配置、日志、缓存整体流程,也为后续多仓扩展提供可复制模板。
|
||||
|
||||
**Independent Test**: 提供 `configs/docker.sample.toml` 或 `configs/npm.sample.toml`,在集成测试中启动临时上游(可模拟 docker registry 或 npm registry),通过 HTTP 调用完成一次拉取并校验缓存目录生成。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 示例配置启用 Docker Hub,**When** 运行 quickstart 脚本,**Then** 可以从真实或模拟上游下载一个 manifest 并写入缓存目录。
|
||||
2. **Given** 示例配置选择 NPM,**When** 执行 `npm view foo` 指向代理,**Then** CLI 能收到正确响应,日志显示命中/回源信息。
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- 配置监听端口但 Host 头缺失或大小写异常:必须直接返回 404,并在日志中记录 `host_unmapped` 字段,禁止回退默认 Hub。
|
||||
- 大文件下载中途中断:需要保证写入临时文件并在失败时清理,避免污染缓存。
|
||||
- TTL 设为 0(永远回源)或非常大:需要解释行为并防止整数溢出。
|
||||
- 上游返回 4xx/5xx:缓存不得写入,同时应透传状态码。
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: 系统必须提供基于 Fiber 的 HTTP Server,监听配置声明的端口,并按照 `Host` → `Hub` 的映射路由所有请求。
|
||||
- **FR-002**: 对于匹配的 Hub,请求路径、查询、方法、Headers 必须重新组装为上游 URL,并保持匿名代理(不注入用户标识)。
|
||||
- **FR-003**: 缓存模块必须在磁盘上以 `StoragePath/<Hub>/<path>` 结构保存内容,并为每个条目维护元数据(TTL、ETag/Last-Modified、写入时间)。
|
||||
- **FR-004**: 命中缓存时必须流式读取磁盘并返回;未命中或过期时需边回源边写入磁盘,并在完成前向客户端持续输出,避免全量加载内存。
|
||||
- **FR-005**: 系统必须支持条件请求:若缓存存在 ETag/Last-Modified,回源时附带 `If-None-Match`/`If-Modified-Since`,收到 304 时回退缓存。
|
||||
- **FR-006**: 任一请求都要记录结构化日志字段(hub、domain、cache_hit、upstream_status、elapsed_ms),并在错误时附带原因。
|
||||
- **FR-007**: 提供至少一个 Docker 或 NPM 的示例配置与 quickstart 说明,包含端到端集成测试脚本,证明可从代理获取真实或模拟数据。
|
||||
- **FR-008**: 所有配置项(端口、Host、TTL、缓存目录)必须在 `config.toml` 中声明,CLI 不引入新的隐式参数。
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **HubRoute**: 映射 Host/端口到具体上游信息,字段包括 `Name`, `Domain`, `Port`, `Upstream`, `Proxy`, `CacheTTL`。
|
||||
- **CacheEntry**: 表示磁盘缓存文件与 `.meta` 元数据(ETag、Last-Modified、StoredAt、Size、Checksum)。
|
||||
- **ProxyRequest**: 记录一次代理请求生命周期(原始 URL、Host、缓存命中状态、上游响应码、耗时)。
|
||||
- **SampleConfig**: 示例配置集合,用于定义 Docker/NPM Hub 所需的字段和默认值。
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: 针对同一资源的第二次请求延迟较首次下降 ≥70%,且日志显示 `cache_hit=true`。
|
||||
- **SC-002**: Host 路由能在 100% 测试用例中将请求映射到正确 Hub,未配置的 Host 返回 404,误路由率为 0。
|
||||
- **SC-003**: 在示例配置下,端到端集成测试成功率达到 100%,并能在 2 分钟内完成一次 docker 或 npm 包的完整拉取。
|
||||
- **SC-004**: 缓存目录在异常情况下不产生损坏文件,测试覆盖包括中断写入、上游错误、TTL 过期等至少 5 个边界场景。
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Phase 1 仅需支持单一 Hub 路由;多 Hub 并行将在后续阶段扩展。
|
||||
- 上游仓库需支持 HTTP/HTTPS GET/HEAD,暂不支持 WebSocket、Chunked 上传等复杂协议。
|
||||
- 示例代理默认指向公共 Docker Hub;若网络受限,可在 quickstart 中改为模拟上游。
|
||||
- 磁盘缓存容量和清理策略仍沿用全局配置,不在本次迭代扩展淘汰算法。
|
||||
|
||||
## Clarifications
|
||||
|
||||
### Session 2025-11-13
|
||||
|
||||
- Q: Host 头缺失或未匹配时应如何处理? → A: 一律返回 404 并记录 `host_unmapped`,不允许回退默认 Hub。
|
||||
149
specs/002-fiber-single-proxy/tasks.md
Normal file
149
specs/002-fiber-single-proxy/tasks.md
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
|
||||
description: "Task list for HTTP 服务与单仓代理"
|
||||
---
|
||||
|
||||
# Tasks: HTTP 服务与单仓代理
|
||||
|
||||
**Input**: Design documents from `/specs/002-fiber-single-proxy/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: 宪法 v1.0.0 要求覆盖配置解析、Host Header 路由、缓存读写、条件回源与示例集成测试,本任务清单默认包含相应测试项。
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: 准备文档与目录结构,确保团队对 Phase 1 范围有统一认知。
|
||||
|
||||
- [X] T001 更新 `DEVELOPMENT.md` 的 Phase 1 章节,描述 HTTP 服务/缓存迭代目标
|
||||
- [X] T002 在 `README.md` 添加 “单仓代理 (Phase 1)” 小节并链接到 spec/plan
|
||||
- [X] T003 [P] 创建 `internal/server/` 与 `internal/cache/` 目录说明文件(`doc.go`)概述职责
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: 构建 Host→Hub 注册表、HTTP 客户端和缓存存储基础,所有用户故事依赖这些能力。
|
||||
|
||||
- [X] T004 实现 `internal/server/hub_registry.go`,从配置构建 Host/端口→Hub 映射并提供查询 API
|
||||
- [X] T005 [P] 在 `internal/server/http_client.go` 创建共享上游 HTTP 客户端(含超时、Proxy、Header 透传)
|
||||
- [X] T006 设计 `internal/cache/store.go` 接口 + 文件布局(`StoragePath/<hub>/<path>` 与 `.meta`)
|
||||
- [X] T007 [P] 在 `tests/integration/upstream_stub_test.go` 搭建可复用的模拟上游服务器(Docker/NPM 两种路径)
|
||||
|
||||
**Checkpoint**: Registry、HTTP 客户端、缓存存储与测试桩可用,方可进入用户故事实现。
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - Host 路由下的单仓访问 (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Fiber 服务可根据 Host/端口匹配到唯一 Hub,并将请求透明转发。
|
||||
|
||||
**Independent Test**: 使用 `curl -H "Host: docker.hub.local"` 对本地服务发起请求,结合 httptest 断言正确 Hub、上游 URL 和日志字段。
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [X] T008 [P] [US1] 编写路由单元测试:`internal/server/router_test.go` 覆盖 Host 命中/未配置/默认逻辑
|
||||
- [X] T009 [P] [US1] 添加集成测试:`tests/integration/host_routing_test.go` 验证端口+Host 组合处理
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T010 [US1] 构建 Fiber App & 中间件(请求日志、错误捕获)于 `internal/server/router.go`
|
||||
- [X] T011 [US1] 在 `cmd/any-hub/main.go` 接线 server 启动逻辑,传入 registry + HTTP 客户端
|
||||
- [X] T012 [US1] 为 Host 未命中添加 404 响应与日志字段(`internal/server/router.go`)
|
||||
- [X] T013 [US1] 更新 `quickstart.md`,记录如何使用 Host 头访问单仓代理
|
||||
|
||||
**Checkpoint**: CLI 可启动 HTTP 服务并完成 Host→Hub 路由,日志含 action/host 字段。
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - 磁盘缓存与回源流程 (Priority: P1)
|
||||
|
||||
**Goal**: 在磁盘上缓存上游响应,支持 TTL、条件请求与流式读写。
|
||||
|
||||
**Independent Test**: 使用模拟上游写入缓存后再次请求,观察命中与 304 流程;覆盖写入失败、上游错误等场景。
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [X] T014 [P] [US2] 为 `internal/cache/store_test.go` 添加命中/未命中/TTL 过期测试
|
||||
- [X] T015 [P] [US2] 在 `tests/integration/cache_flow_test.go` 编写端到端测试(首次写入、304 回退、上游失败)
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T016 [US2] 在 `internal/cache/store.go` 实现读/写/元数据 API,并处理并发写入/临时文件
|
||||
- [X] T017 [US2] 在 `internal/proxy/handler.go` 中实现缓存流程(命中→读磁盘,未命中→回源→写缓存→流式返回)
|
||||
- [X] T018 [US2] 支持条件请求 Header(`If-None-Match`/`If-Modified-Since`)并处理 304(`internal/proxy/upstream.go`)
|
||||
- [X] T019 [US2] 扩展日志字段,记录 `cache_hit`、`upstream_status`、`elapsed_ms`(`internal/logging/fields.go` + `internal/proxy/handler.go`)
|
||||
- [X] T020 [US2] 在 `config.example.toml` 增加缓存路径/TTL 示例,并更新 `DEVELOPMENT.md` 的缓存调优段落
|
||||
|
||||
**Checkpoint**: 缓存命中率可在日志中观察;回源路径流式返回并具备条件请求能力。
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - 最小 Docker/NPM 代理样例 (Priority: P2)
|
||||
|
||||
**Goal**: 提供可复制的示例配置与 quickstart 脚本,验证 Docker 或 NPM 仓库的代理行为。
|
||||
|
||||
**Independent Test**: 运行文档中的 quickstart,对真实或模拟上游完成一次包/镜像拉取,并验证缓存目录写入。
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
- [X] T021 [P] [US3] 编写示例集成测试 `tests/integration/docker_sample_test.go`(或 `npm_sample_test.go`)验证端到端流程
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T022 [US3] 添加 `configs/docker.sample.toml` 与 `configs/npm.sample.toml`,注释必要字段
|
||||
- [X] T023 [US3] 在 `quickstart.md` 新增示例步骤(Docker/NPM)、常见问题与日志示例
|
||||
- [X] T024 [US3] 准备脚本或 Make 目标 `scripts/demo-proxy.sh` 运行示例配置
|
||||
- [X] T025 [US3] 补充 README “示例代理” 章节链接 demo 脚本与 quickstart
|
||||
|
||||
**Checkpoint**: 示例配置 & 文档可指导用户完成最小代理体验,并由自动化测试验证。
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: 收尾文档、CI 与质量保证,确保 Phase 1 可长期维护。
|
||||
|
||||
- [X] T026 在 `cmd/any-hub/main.go` 与 `internal/*` 新增中文注释,解释 server/cache 关键流程
|
||||
- [X] T027 [P] 增加 `tests/integration/interrupt_test.go`,验证下载中断后的缓存清理
|
||||
- [X] T028 更新 `CHANGELOG.md` 与 `README.md`,记录 Phase 1 完成情况
|
||||
- [X] T029 运行 `gofmt`、`go test ./...`,并在 `DEVELOPMENT.md` 记录验证结果与命令
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
- Phase 1 → Phase 2 → User Stories (US1/US2/US3) → Phase 6
|
||||
|
||||
### User Story Dependencies
|
||||
- US1 (Host 路由) 依赖 Phase 2 完整;US2 依赖 US1 提供的 Fiber/Proxy 框架;US3 依赖 US1+US2 的代理能力
|
||||
|
||||
### Parallel Execution Examples
|
||||
- **US1**: T008 与 T009(测试)可并行;T010/T011 实现后可由 T012/T013 跟进
|
||||
- **US2**: T014/T015 可并行;缓存实现 T016 可与日志扩展 T019 同步推进
|
||||
- **US3**: T022(配置)与 T024(脚本)可并行,测试 T021 需在示例完成后执行
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
1. 完成 Phase 1-2
|
||||
2. 交付 Host 路由 + HTTP 服务(US1),即可演示基础代理能力
|
||||
|
||||
### Incremental Delivery
|
||||
1. 在 MVP 基础上继续 US2(缓存)→ US3(示例)
|
||||
2. 每个阶段完成后运行 quickstart + 集成测试,确保可独立交付
|
||||
|
||||
### Parallel Team Strategy
|
||||
- 团队 A:负责 server/router(US1)
|
||||
- 团队 B:负责 cache/proxy(US2)
|
||||
- 团队 C:负责示例配置与文档(US3)
|
||||
34
specs/003-hub-auth-fields/checklists/requirements.md
Normal file
34
specs/003-hub-auth-fields/checklists/requirements.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: Hub 配置凭证字段
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2025-11-14
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
31
specs/003-hub-auth-fields/contracts/config-schema.md
Normal file
31
specs/003-hub-auth-fields/contracts/config-schema.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Config Schema Contract
|
||||
|
||||
```toml
|
||||
# Global section
|
||||
ListenPort = 5000
|
||||
LogLevel = "info"
|
||||
StoragePath = "./storage"
|
||||
CacheTTL = 86400
|
||||
# ... existing global keys ...
|
||||
|
||||
[[Hub]]
|
||||
Name = "docker"
|
||||
Domain = "docker.hub.local"
|
||||
Upstream = "https://registry-1.docker.io"
|
||||
Proxy = ""
|
||||
Username = "" # optional
|
||||
Password = "" # optional
|
||||
Type = "docker" # required, must be docker|npm|go
|
||||
CacheTTL = 43200 # optional override
|
||||
```
|
||||
```
|
||||
|
||||
| Field | Required | Type | Notes |
|
||||
|----------------------|----------|---------|-------|
|
||||
| `ListenPort` | Yes | int | 单端口监听,范围 1-65535 |
|
||||
| `Username`/`Password`| No | string | 缺省表示匿名;若任一非空则视为 credentialed,日志仅显示掩码 |
|
||||
| `Type` | Yes | enum | 当前支持 `docker`/`npm`/`go`,输入其他值时报错 |
|
||||
|
||||
**Validation Contract**
|
||||
1. `any-hub --check-config` 必须在缺失 `Type` 或非法 `ListenPort` 时返回非 0。
|
||||
2. CLI 日志需输出 `hub_type` 与 `auth_mode` 字段,以便运维确认配置是否生效。
|
||||
33
specs/003-hub-auth-fields/data-model.md
Normal file
33
specs/003-hub-auth-fields/data-model.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Data Model: Hub 配置凭证字段
|
||||
|
||||
## HubConfig
|
||||
- **Fields**:
|
||||
- `Name` (string, required, unique)
|
||||
- `Domain` (string, required, Host header used for路由)
|
||||
- `Upstream` (URL, required)
|
||||
- `Proxy` (URL, optional)
|
||||
- `Username` (string, optional, may be empty)
|
||||
- `Password` (string, optional, may be empty)
|
||||
- `Type` (enum: docker | npm | go, required)
|
||||
- `CacheTTL` (duration override, optional)
|
||||
- **Relationships**: 属于全局 `Config`,在运行期被 `HubRegistry` 索引。
|
||||
- **Validation**: `Username` 与 `Password` 要么同时缺省要么同时提供;`Type` 仅允许受支持的值。
|
||||
|
||||
## GlobalConfig
|
||||
- **Fields**:
|
||||
- `ListenPort` (int, required, 1-65535)
|
||||
- 现有全局字段(LogLevel、StoragePath、CacheTTL、MaxRetries 等)保持不变
|
||||
- **Relationships**: CLI 仅在 `ListenPort` 启动一次 Fiber 服务;HubRegistry 使用该端口验证 Host:port。
|
||||
|
||||
## AuthProfile (runtime)
|
||||
- **Fields**:
|
||||
- `HubName`
|
||||
- `AuthMode` (anonymous | credentialed)
|
||||
- `LastAttempt` timestamp
|
||||
- `Status` (success | failed)
|
||||
- **Purpose**: 供日志/metrics 输出,帮助追踪凭证是否生效。
|
||||
- **State transitions**: anonymous→credentialed 当检测到配置凭证;credentialed→anonymous 当凭证校验失败且被禁用(未来扩展)。
|
||||
|
||||
## HubType (enum)
|
||||
- **Values**: docker, npm, go(后续 apt/yum/composer 等)
|
||||
- **Usage**: 决定请求头/日志字段/未来仓库特定策略;存储于 HubConfig。
|
||||
69
specs/003-hub-auth-fields/plan.md
Normal file
69
specs/003-hub-auth-fields/plan.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Implementation Plan: Hub 配置凭证字段
|
||||
|
||||
**Branch**: `003-hub-auth-fields` | **Date**: 2025-11-14 | **Spec**: [/home/rogee/Projects/any-hub/specs/003-hub-auth-fields/spec.md](./spec.md)
|
||||
**Input**: Feature specification from `/specs/003-hub-auth-fields/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
交付单端口 Host 路由代理的配置增强:
|
||||
1. 将监听端口提升到全局 `ListenPort` 字段,所有 Hub 共享同一 Fiber 端口并严格依赖 Host 头;旧的 `[[Hub]].Port` 需在配置校验阶段报错并提供迁移指引。
|
||||
2. 为 Hub 添加可选 `Username`/`Password` 字段,由 CLI 在回源时自动注入 Authorization header,同时在日志中只暴露掩码。
|
||||
3. 为 Hub 添加必填 `Type` 枚举(当前支持 docker/npm/go),驱动日志字段与未来协议特定策略,并保留扩展钩子。
|
||||
4. 更新示例配置、quickstart、文档与测试,确保凭证/类型/单端口行为可独立验证。
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Go 1.25+(静态链接单二进制)
|
||||
**Primary Dependencies**: Fiber v3(HTTP 服务)、Viper(配置加载/校验)、Logrus + Lumberjack(结构化日志 & 滚动)、标准库 `net/http`/`io`(代理回源)
|
||||
**Storage**: 本地 `StoragePath/<Hub>/<path>` + `.meta` 元数据缓存布局
|
||||
**Testing**: `GOCACHE=/tmp/go-build go test ./...`,基于 `httptest` + upstream stub + 临时目录验证配置、代理、缓存、Host 路由
|
||||
**Target Platform**: Linux/Unix CLI 进程,由 systemd/supervisor 管理,匿名下游客户端
|
||||
**Project Type**: 单 Go 模块(`cmd/any-hub` 入口 + `internal/config|server|cache|proxy`)
|
||||
**Performance Goals**: 回源路径仍需流式复制并保持单请求 <256MB;凭证注入不可降低缓存命中率;单端口模式吞吐与现状持平
|
||||
**Constraints**: 禁止新增依赖;所有行为受单一 `config.toml` 控制;凭证不得写入日志;需提供向后兼容的迁移提示
|
||||
**Scale/Scope**: 当前支持 docker/npm/go 三类仓库,未来可扩展 apt/yum/composer/go proxy 等;单端口 Host 路由需支撑数十 Hub 并保持透明体验
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- 方案继续服务“轻量 CLI 多仓代理”使命,未引入 UI/账号体系或附加系统。
|
||||
- 仍使用 Go + Fiber/Viper/Logrus/Lumberjack/标准库,无新增依赖。
|
||||
- 所有新增能力均由 `config.toml` 驱动,且设计了默认值/校验/迁移提示。
|
||||
- 缓存优先 + 流式回源路径保持不变;凭证注入仅影响上游请求头。
|
||||
- 计划列出了配置解析、代理凭证、Host 路由等必备测试,并要求中文注释与 quickstart 更新。
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/003-hub-auth-fields/
|
||||
├── plan.md # 当前文件
|
||||
├── research.md # Phase 0 输出
|
||||
├── data-model.md # Phase 1 输出
|
||||
├── quickstart.md # Phase 1 输出
|
||||
├── contracts/ # Phase 1 输出
|
||||
└── tasks.md # Phase 2 输出(由 /speckit.tasks 生成)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
```text
|
||||
cmd/any-hub/main.go # CLI 入口、单端口监听
|
||||
internal/config/ # TOML schema + 校验 + 默认值
|
||||
internal/server/ # Host Registry、Fiber 启动
|
||||
internal/cache/ # 缓存方案(需保持兼容)
|
||||
internal/proxy/ # 凭证注入、类型策略、流式代理
|
||||
configs/ # 示例 config(docker/npm/go)
|
||||
tests/ # 单元/集成测试 (httptest、stubs)
|
||||
```
|
||||
|
||||
**Structure Decision**: 延续既有模块划分;单端口逻辑集中在 `internal/server`,凭证/类型侧重 `internal/config` + `internal/proxy`,必要时可新增 `internal/server/types` 等子包但需保持依赖有向无环。
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| _None_ | — | — |
|
||||
52
specs/003-hub-auth-fields/quickstart.md
Normal file
52
specs/003-hub-auth-fields/quickstart.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Quickstart: Hub 凭证与类型配置
|
||||
|
||||
## 前置检查
|
||||
|
||||
| 字段 | 说明 | 验证方式 |
|
||||
|------|------|----------|
|
||||
| `ListenPort` | 全局唯一监听端口,所有 Hub 共用 | `any-hub --check-config` 会输出 `listen_port=<port>` |
|
||||
| `Username`/`Password` | 可选凭证字段,两个字段必须同时出现 | 成功日志含 `auth_mode=credentialed`;缺失时为 `anonymous` |
|
||||
| `Type` | 仓库类型,当前支持 `docker`/`npm`/`go` | 错误输入将导致 `hub_type_invalid` 报错 |
|
||||
|
||||
**基本命令**
|
||||
|
||||
```bash
|
||||
any-hub --check-config --config hub-auth.toml
|
||||
any-hub --config hub-auth.toml
|
||||
```
|
||||
|
||||
1. **准备配置**
|
||||
```bash
|
||||
cp configs/config.example.toml hub-auth.toml
|
||||
```
|
||||
- 在全局段添加 `ListenPort = 5000` 并移除 `[[Hub]]` 中的 `Port` 字段。
|
||||
- 为需要解锁 rate-limit 的 Hub 写入 `Username`/`Password`,保持小写字符串。
|
||||
- 设置 `Type` 为 `docker`、`npm` 或 `go`。其它值会被 `any-hub --check-config` 拒绝,并在日志中提示 `hub_type_unsupported`。若需扩展新的仓库类型,请遵循 plan.md 中的“类型扩展策略”提交补丁。
|
||||
|
||||
2. **运行校验**
|
||||
```bash
|
||||
any-hub --check-config --config hub-auth.toml
|
||||
```
|
||||
预期日志:`{"action":"check_config","credentials":["docker:credentialed"],"listen_port":5000,"result":"ok"}`(无凭证的 Hub 会显示 `hub:anonymous`)。
|
||||
|
||||
3. **启动代理**
|
||||
```bash
|
||||
any-hub --config hub-auth.toml
|
||||
```
|
||||
CLI 只监听 `ListenPort` 指定的端口,所有 Hub 通过 Host 头路由。
|
||||
|
||||
4. **验证凭证透传**
|
||||
```bash
|
||||
# Docker CLI 方式
|
||||
curl -H "Host: docker.hub.local" http://127.0.0.1:5000/v2/
|
||||
|
||||
# NPM 方式(匿名客户端,不需要 .npmrc 凭证)
|
||||
npm --registry http://127.0.0.1:5000 --always-auth=false view lodash
|
||||
```
|
||||
- 代理会在回源请求中自动注入 `Username`/`Password`,而下游 `curl`/`npm` 无须提供任何 Authorization header。
|
||||
|
||||
5. **观察日志**
|
||||
- 每条请求日志包含 `hub`, `hub_type`, `auth_mode`, `upstream_status`。
|
||||
- 若凭证无效,会出现 `error=auth_failed`,需更新配置并重启。
|
||||
- `config.toml` 保存明文凭证,部署时请配合 `chmod 600` 或密钥注入工具限制读取范围。
|
||||
- 建议执行 `tail -n 20 logs/any-hub.log | jq '.auth_mode,.hub_type,.cache_hit'`,确认命中缓存与凭证状态。
|
||||
22
specs/003-hub-auth-fields/research.md
Normal file
22
specs/003-hub-auth-fields/research.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Research: Hub 配置凭证字段
|
||||
|
||||
## Credential Handling in config.toml
|
||||
- **Decision**: 允许在 `[[Hub]]` 中以明文写入 `Username`/`Password`,CLI 读取后立即掩码写日志,并鼓励与外部 Secret 管理(环境变量/Ansible Vault)组合。
|
||||
- **Rationale**: 代理以 CLI 形式部署,最简单可靠的方式是继续沿用 TOML;通过 `config --check-config` 校验与日志掩码即可满足大多数场景。
|
||||
- **Alternatives considered**:
|
||||
- 单独的凭证文件:需要额外路径与权限管理,易造成部署复杂化。
|
||||
- 环境变量注入:缺乏结构化校验,且与当前 TOML 驱动原则冲突。
|
||||
|
||||
## Hub Type 枚举与扩展
|
||||
- **Decision**: 首批仅支持 `docker`、`npm`、`go`,在 `internal/config` 中实现枚举校验,并在 `internal/proxy`/`internal/server` 中通过 `switch type` 执行类型特定逻辑;保留 `default` 分支抛出“未支持类型”错误,为 apt/yum/composer 等新增项预留 hook。
|
||||
- **Rationale**: 明确 types 可让日志/策略按仓库协议定制,同时使用枚举校验可避免错误输入;`switch` + 独立 helper 便于未来拆分成策略表。
|
||||
- **Alternatives considered**:
|
||||
- 自定义字符串+运行期反射:增加复杂度且容易出错。
|
||||
- 大型插件机制:当前范围只需声明式扩展,无需插件框架。
|
||||
|
||||
## 单端口 Host 路由迁移
|
||||
- **Decision**: 将监听端口移至全局(如 `ListenPort`),Fiber 仅在该端口启动一次;Hub 级 `Port` 字段在配置加载阶段报错提示迁移。路由层严格依赖 `Host`/`Host:port` 解析,必要时通过 `SERVER_PORT` 环境变量或 CLI flag override。
|
||||
- **Rationale**: 单端口可简化暴露面并符合“Host 区分仓库”的目标;在 `internal/server` 中已有 Host registry,可直接复用。
|
||||
- **Alternatives considered**:
|
||||
- 同时保留 per-Hub 端口:违背“统一端口”诉求且增加监听资源。
|
||||
- 动态多端口监听:需要额外 goroutine/生命周期管理,复杂度高。
|
||||
114
specs/003-hub-auth-fields/spec.md
Normal file
114
specs/003-hub-auth-fields/spec.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Feature Specification: Hub 配置凭证字段
|
||||
|
||||
**Feature Branch**: `003-hub-auth-fields`
|
||||
**Created**: 2025-11-14
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Hub 配置中添加 Username/Password/type类型 三个字段,user:password是为了保证上游需要认证时,下游无需认证即可获取,如docker需要登录后可以解除rate-limit;type用于指定代理的仓库类型,用于区分镜像仓库类型为不同类型的仓库进行定制化操作。当前分支名需要003开头。"
|
||||
|
||||
> 宪法对齐(v1.0.0):
|
||||
> - 保持“轻量、匿名、CLI 多仓代理”定位:不得引入 Web UI、账号体系或与代理无关的范围。
|
||||
> - 方案必须基于 Go 1.25+ 单二进制,依赖仅限 Fiber、Viper、Logrus/Lumberjack 及必要标准库。
|
||||
> - 所有行为由单一 `config.toml` 控制;若需新配置项,需在规范中说明字段、默认值与迁移策略。
|
||||
> - 设计需维护缓存优先 + 流式传输路径,并描述命中/回源/失败时的日志与观测需求。
|
||||
> - 验收必须包含配置解析、缓存读写、Host Header 绑定等测试与中文注释交付约束。
|
||||
|
||||
## Clarifications
|
||||
|
||||
### Session 2025-11-14
|
||||
|
||||
- Q: 当 `Username` 与 `Password` 只提供其中一个时应如何处理? → A: 在 `config --check-config` 阶段直接判定为配置错误,要求两个字段要么同时提供要么一起缺省。
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
<!--
|
||||
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
|
||||
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
|
||||
you should still have a viable MVP (Minimum Viable Product) that delivers value.
|
||||
|
||||
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
|
||||
Think of each story as a standalone slice of functionality that can be:
|
||||
- Developed independently
|
||||
- Tested independently
|
||||
- Deployed independently
|
||||
- Demonstrated to users independently
|
||||
-->
|
||||
|
||||
### User Story 1 - 配置上游凭证 (Priority: P1)
|
||||
|
||||
运维人员需要在 `config.toml` 中为特定 Hub 写入上游所需的 `Username`/`Password`,以便在代理启动后自动携带凭证,解除匿名拉取的速率限制或访问受保护的仓库。
|
||||
|
||||
**Why this priority**: 没有可配置的凭证字段就无法满足受限仓库或需要登录的上游,代理将丧失关键价值。
|
||||
|
||||
**Independent Test**: 仅依赖配置与 CLI 启动,即可通过加载带有不同凭证的配置文件并观察日志/校验错误来验证。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** Hub 缺省 `Username`/`Password`,**When** CLI 读取配置,**Then** 字段可为空并保持当前匿名行为。
|
||||
2. **Given** Hub 提供 `Username=foo`、`Password=bar`,**When** CLI 运行 `--check-config`,**Then** 配置校验通过且凭证不会在日志中明文打印。
|
||||
3. **Given** Hub 配置凭证,**When** upstream 需要 Basic/Bearer 登录,**Then** 代理转发请求时自动附带凭证,终端可观察到 rate-limit 不再触发。
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - 下游透明体验 (Priority: P1)
|
||||
|
||||
下游开发者希望继续使用匿名方式访问代理,而代理应在后台代为认证;同一个代理可针对不同 Hub 自动切换凭证与仓库类型。
|
||||
|
||||
**Why this priority**: 透明体验是“单点代理”的核心卖点;若仍需下游显式登录,就无法简化 CI/CD。
|
||||
|
||||
**Independent Test**: 使用 curl/npm/docker 命令只指定 Host/端口即可验证,无需改动其他系统。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** Hub 已配置凭证,**When** 下游客户端不带任何 Authorization header,**Then** 代理回源时自行插入 `Username`/`Password` 并返回 200 响应。
|
||||
2. **Given** 多个 Hub 配置了不同仓库类型,**When** 同一 CLI 进程同时监听多个端口,**Then** 每个端口都能在日志中打印正确的 `hub`、`type`、`auth_mode` 字段。
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - 仓库类型适配 (Priority: P2)
|
||||
|
||||
平台需要在配置中声明 `Type`(如 `docker`、`npm`、`generic-http`),以区分仓库协议并触发未来的类型特定策略(Header 处理、元数据)。
|
||||
|
||||
**Why this priority**: 明确的仓库类型是后续分支(镜像仓库 vs. 包仓库)实施自定义逻辑的前提;越早保存该字段,越容易扩展。
|
||||
|
||||
**Independent Test**: 通过配置不同的 Type 值,启动 CLI 并验证日志/metrics 中输出的类型以及针对未知类型的回退策略。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** Hub `Type=docker`、`npm` 或 `go`,**When** 代理启动,**Then** 日志/配置校验会表明使用对应策略,非法值或缺失将被拒绝并提示可选列表。
|
||||
2. **Given** Hub 未按要求设置 `Type`,**When** CLI 运行或 `--check-config`,**Then** 返回校验错误并指明必须从受支持列表中选择(docker/npm/go,未来扩展)。
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- 当 `Username` 或 `Password` 单独出现时,系统必须在配置校验阶段阻止并提示需同时提供。
|
||||
- 当 `Type` 与实际上游协议不符(如标记为 docker 但 upstream 为 npm)时,需确保仍可 fallback 到透传并输出警告。
|
||||
- 凭证旋转:在 CLI 重启前如何避免旧凭证仍被缓存?需记录文档告知必须重启或触发热加载。
|
||||
- 凭证日志安全:所有日志中禁止打印纯文本密码,且结构化日志只显示是否启用凭证。
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: Hub 配置必须支持新增 `Username`、`Password` 字段,字段为完全可选,若未配置则保持匿名透传;若任意一项填写则必须两项同时提供(允许为空字符串),否则 `config --check-config` 需报错并拒绝启动。
|
||||
- **FR-002**: CLI 在读取配置后必须将凭证存入运行期内存,不得在 `logger` 输出中展示明文(只可显示存在性或掩码)。
|
||||
- **FR-003**: 代理回源请求必须根据 Hub 配置决定是否附加 Authorization header;缺省模式保持现状(匿名)。
|
||||
- **FR-004**: 当 upstream 返回 401/429 且 Hub 配置了凭证,系统必须重试一次(遵循既有重试策略)并记录“凭证认证失败”的错误字段,便于排障。
|
||||
- **FR-005**: Hub 配置需新增必填 `Type` 字段,短期支持值列表(`docker`, `npm`, `go`),配置校验必须拒绝缺失值或列表外值,并提示“仅支持 docker/npm/go,未来将扩展 apt/yum/composer 等”。
|
||||
- **FR-006**: CLI/日志/metrics 必须输出 `hub_type`、`auth_mode`(anonymous/credentialed)等字段,方便观察不同仓库行为,并在新增类型时无需额外字段即可扩展。
|
||||
- **FR-007**: 配置校验必须阻止缺失 `Type` 的 Hub,通过显式错误消息引导用户选择受支持值;内部设计需保留枚举扩展点,便于未来加入 apt/yum/composer/go proxy 等类型。
|
||||
- **FR-008**: 文档与示例配置需覆盖凭证字段的写法、敏感信息处理方式及最佳实践(例如不要提交到 Git),并说明 type 字段的当前支持列表及扩展策略。
|
||||
- **FR-009**: 测试覆盖需包括:配置解析(含非法组合)、凭证透传的集成测试、以及 Type 值驱动的分支逻辑。
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **HubConfig**: 描述单个代理 Hub 的所有属性,新增 `Username`、`Password`(可选字符串,按 Hub 粒度保存)、`Type`(枚举,默认 `generic-http`)。与 HubRegistry 绑定。
|
||||
- **AuthProfile**: 运行期派生实体,包含凭证存在性、认证模式、最后一次认证状态等,并与日志/metrics 关联。
|
||||
- **HubType**: 表示仓库协议类别(docker、npm、generic-http 等),驱动特定 Header、缓存策略或 future features。
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: 100% 的受保护仓库(需要 Basic/Bearer 凭证)可在下游匿名请求下成功响应,且无需再配置下游凭证。
|
||||
- **SC-002**: 代理日志中 `auth_mode=credentialed` 的请求,其 401/429 错误率较匿名模式下降 ≥80%(以相同上游的历史基线为对照)。
|
||||
- **SC-003**: 所有 Hub 配置在 `go test ./internal/config` 覆盖下均能校验通过,新增字段的解析、默认值和错误路径达到 100% 单元测试覆盖。
|
||||
- **SC-004**: 文档与 quickstart 示例更新完成后,内部演练中 3 名运维工程师可在 15 分钟内配置并验证 Docker、NPM 仓库的登录代理。
|
||||
155
specs/003-hub-auth-fields/tasks.md
Normal file
155
specs/003-hub-auth-fields/tasks.md
Normal file
@@ -0,0 +1,155 @@
|
||||
---
|
||||
|
||||
description: "Tasks for Hub 配置凭证字段"
|
||||
---
|
||||
|
||||
# Tasks: Hub 配置凭证字段
|
||||
|
||||
**Input**: Design documents from `/specs/003-hub-auth-fields/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: 宪法 v1.0.0 要求覆盖配置解析、缓存读写、代理命中/回源与 Host Header 绑定,此清单在相关阶段加入对应测试任务。
|
||||
|
||||
**Organization**: Tasks are grouped by user story以支持独立实施与验证。
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: 可并行执行(触达不同文件/无直接依赖)
|
||||
- **[Story]**: 指明所属用户故事(Setup/Foundational/Polish 阶段不写)
|
||||
- 描述中必须包含精确文件路径
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: 记录迁移策略、同步文档,让团队在单端口与凭证范围上达成一致。
|
||||
|
||||
- [X] T001 将全局 `ListenPort`/Hub 凭证迁移指南写入 `DEVELOPMENT.md` 与 `README.md`,提醒去除 `[[Hub]].Port`
|
||||
- [X] T002 补充 `CHANGELOG.md` 与 `specs/003-hub-auth-fields/quickstart.md` 的前置说明,列出新的配置字段及基本验证命令
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: 改造配置与 server 启动流程,确保所有 Hub 共用单端口并具备新字段;完成前禁止进入任何用户故事。
|
||||
|
||||
- [X] T003 更新 `internal/config/types.go` / `internal/config/loader.go`,为 `GlobalConfig` 添加 `ListenPort`,为 `HubConfig` 添加 `Username`/`Password`/`Type`,并删除 Hub 级 `Port`
|
||||
- [X] T004 扩充 `internal/config/validation.go` 与 `internal/config/config_test.go`,覆盖 `ListenPort` 范围校验、Type 枚举校验及缺失 Type 报错
|
||||
- [X] T005 重构 `cmd/any-hub/main.go` 与 `internal/server/router.go`,仅根据全局 `ListenPort` 启动 Fiber,并在加载阶段检测到 Hub 级 `Port` 时报告迁移错误
|
||||
- [X] T006 [P] 更新 `internal/server/hub_registry.go` 及 `internal/server/hub_registry_test.go`,让 Host Registry 仅依赖 Host/Host:port 组合,去除对 per-Hub 端口的引用
|
||||
- [X] T007 [P] 调整 `configs/config.example.toml`、`configs/docker.sample.toml`、`configs/npm.sample.toml`,移除 Hub 端口字段并添加 `ListenPort`/`Type` 示例
|
||||
|
||||
**Checkpoint**: 单端口 + 新字段的配置/路由路径已可运行,各用户故事可并行推进。
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - 配置上游凭证 (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Hub 可配置上游凭证,CLI 回源时自动附带 Authorization,日志只输出掩码。
|
||||
|
||||
**Independent Test**: 使用带凭证的 `config.toml` 运行 `any-hub --check-config` 与 `go test ./tests/integration -run CredentialProxy`,无须下游凭证即可解除 rate-limit。
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [X] T008 [P] [US1] 在 `tests/integration/credential_proxy_test.go` 构建带 Basic Auth 的 upstream stub,验证“无凭证失败→配置凭证成功”的流程
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T009 [US1] 在 `internal/config/types.go` 与 `cmd/any-hub/main.go` 中保存可选凭证,并在所有 `logrus` 输出中仅显示掩码/存在性
|
||||
- [X] T010 [US1] 修改 `internal/proxy/handler.go`,依据 Hub 凭证自动附加 Authorization header,并在 401/429 时执行一次受控重试与错误字段记录
|
||||
- [X] T011 [US1] 更新 `README.md` 与 `quickstart.md`,新增凭证写法、敏感信息注意事项以及 `any-hub --check-config` 示例
|
||||
|
||||
**Checkpoint**: 代理可凭借配置凭证访问受限仓库,下游仍保持匿名体验。
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - 下游透明体验 (Priority: P1)
|
||||
|
||||
**Goal**: 确保下游无需配置凭证即可访问,日志/观测字段体现 `auth_mode`、`hub_type` 与上游结果。
|
||||
|
||||
**Independent Test**: 运行 `tests/integration/credential_proxy_test.go` 的匿名客户端用例,并以 `npm --registry http://127.0.0.1:<ListenPort>` 手动验证日志输出。
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [X] T012 [P] [US2] 扩展 `tests/integration/credential_proxy_test.go`,加入“不带 Authorization 仍命中凭证”与日志字段断言
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T013 [US2] 在 `internal/logging/fields.go` 与 `internal/proxy/handler.go` 中输出 `hub_type`、`auth_mode`、`upstream_status`,并确保缓存命中/回源路径均记录
|
||||
- [X] T014 [US2] 在 `tests/integration/upstream_stub_test.go` 及 `quickstart.md` 中补充匿名客户端示例命令,指导如何验证透明代理
|
||||
|
||||
**Checkpoint**: 日志、quickstart 与匿名客户端流程可完整验证下游体验。
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - 仓库类型适配 (Priority: P2)
|
||||
|
||||
**Goal**: 强制声明 Hub `Type`(docker/npm/go),日志与运行期可识别类型,为未来扩展留接口。
|
||||
|
||||
**Independent Test**: 使用覆盖三种 Type 的配置运行 `any-hub --check-config` 与 `go test ./internal/config`,非法/缺失 Type 会被拒绝,日志中能看到正确的 `hub_type`。
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
- [X] T015 [P] [US3] 在 `internal/config/config_test.go` 新增表驱动测试,覆盖合法 Type、非法 Type、缺失 Type 的报错/提示
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T016 [US3] 在 `internal/proxy/handler.go` 与 `internal/server/router.go` 加入 `switch Type`,目前仅设置日志/标签,并对未支持类型抛出明确错误
|
||||
- [X] T017 [US3] 更新 `quickstart.md` 及 `configs/*.sample.toml`,列出 Type 可选值与未来扩展策略
|
||||
|
||||
**Checkpoint**: Hub 类型被强制校验,日志和示例配置均反映正确值。
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: 统一测试、文档及残留引用,确保产物满足宪法门槛。
|
||||
|
||||
- [X] T018 [P] 运行 `gofmt ./cmd ./internal ./tests` 与 `GOCACHE=/tmp/go-build go test ./...`,并把命令写入 `DEVELOPMENT.md`
|
||||
- [X] T019 清理 `DEVELOPMENT.md`、`README.md`、`specs/003-hub-auth-fields/plan.md` 中残留的 Hub 端口描述,确保只推荐全局 `ListenPort`
|
||||
- [X] T020 在 `CHANGELOG.md` 与 `quickstart.md` 中记录演练结果(docker + npm),并附一次手动验证日志
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
1. Setup → Foundational → 所有用户故事 → Polish
|
||||
2. Foundational 完成前,任何用户故事不得启动。
|
||||
3. US2 依赖 US1 产出的凭证注入,但可以在实现层并行。
|
||||
4. US3 仅依赖 Foundational,可与 US1/US2 并行实施。
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- US1 完成后,即可单独交付 MVP。
|
||||
- US2 依赖 US1 的日志/凭证输出,但测试可提前编写。
|
||||
- US3 与其他故事仅在配置层相互作用,无硬依赖。
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
- T006 与 T007 可由不同成员分别处理(registry vs. 示例配置)。
|
||||
- T008/T012/T015 测试任务都可在实现前准备并行执行。
|
||||
- 不同用户故事的实现(T009- T017)可由独立小组并行推进。
|
||||
- Polish 阶段 T018 与 T019/T020 可交由不同成员并行完成。
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1)
|
||||
1. 完成 Setup + Foundational,确认单端口与配置路径无误。
|
||||
2. 实施 US1(凭证字段 + 代理注入)并通过集成测试。
|
||||
3. 以此为最小可交付版本,供受限仓库使用。
|
||||
|
||||
### Incremental Delivery
|
||||
1. Increment 1: Setup + Foundational + US1 → 解锁凭证代理。
|
||||
2. Increment 2: US2 → 强化透明体验与观测性。
|
||||
3. Increment 3: US3 → 引入 Type 校验及扩展思路。
|
||||
4. Polish: 全量测试、文档与 quickstart 验证。
|
||||
|
||||
### Parallel Team Strategy
|
||||
- Team A:负责 Foundational + US1。
|
||||
- Team B:在 Foundational 完成后并行 US2(日志 + quickstart)。
|
||||
- Team C:并行 US3(Type 校验与示例)。
|
||||
- Polish 阶段由值班成员统一收尾。
|
||||
Reference in New Issue
Block a user