docs: add per package README for implementation details (AI generated with human review)

This commit is contained in:
yusing
2026-01-08 23:39:19 +08:00
parent 13441286d1
commit e9d7edef12
54 changed files with 13431 additions and 1519 deletions

316
internal/config/README.md Normal file
View File

@@ -0,0 +1,316 @@
# Configuration Management
Centralized YAML configuration management with thread-safe state access and provider initialization.
## Overview
The config package implements the core configuration management system for GoDoxy, handling YAML configuration loading, provider initialization, route loading, and state transitions. It uses atomic pointers for thread-safe state access and integrates all configuration components.
### Primary consumers
- `cmd/main.go` - Initializes configuration state on startup
- `internal/route/provider` - Accesses configuration for route creation
- `internal/api/v1` - Exposes configuration via REST API
- All packages that need to access active configuration
### Non-goals
- Dynamic provider registration after initialization (require config reload)
### Stability
Stable internal package. Public API consists of `State` interface and state management functions.
## Public API
### Exported types
```go
type Config struct {
ACL *acl.Config
AutoCert *autocert.Config
Entrypoint entrypoint.Config
Providers Providers
MatchDomains []string
Homepage homepage.Config
Defaults Defaults
TimeoutShutdown int
}
type Providers struct {
Files []string
Docker map[string]types.DockerProviderConfig
Agents []*agent.AgentConfig
Notification []*notif.NotificationConfig
Proxmox []proxmox.Config
MaxMind *maxmind.Config
}
```
### State interface
```go
type State interface {
Task() *task.Task
Context() context.Context
Value() *Config
EntrypointHandler() http.Handler
ShortLinkMatcher() config.ShortLinkMatcher
AutoCertProvider() server.CertProvider
LoadOrStoreProvider(key string, value types.RouteProvider) (actual types.RouteProvider, loaded bool)
DeleteProvider(key string)
IterProviders() iter.Seq2[string, types.RouteProvider]
StartProviders() error
NumProviders() int
}
```
### Exported functions
```go
func NewState() config.State
```
Creates a new configuration state with empty providers map.
```go
func GetState() config.State
```
Returns the active configuration state. Thread-safe via atomic load.
```go
func SetState(state config.State)
```
Sets the active configuration state. Also updates active configs for ACL, entrypoint, homepage, and autocert.
```go
func HasState() bool
```
Returns true if a state is currently active.
```go
func Value() *config.Config
```
Returns the current configuration values.
```go
func (state *state) InitFromFile(filename string) error
```
Initializes state from a YAML file. Uses default config if file doesn't exist.
```go
func (state *state) Init(data []byte) error
```
Initializes state from raw YAML data. Validates, then initializes MaxMind, Proxmox, providers, AutoCert, notifications, access logger, and entrypoint.
```go
func (state *state) StartProviders() error
```
Starts all route providers concurrently.
```go
func (state *state) IterProviders() iter.Seq2[string, types.RouteProvider]
```
Returns an iterator over all providers.
## Architecture
### Core components
```mermaid
graph TD
A[config.yml] --> B[State]
B --> C{Initialize}
C --> D[Validate YAML]
C --> E[Init MaxMind]
C --> F[Init Proxmox]
C --> G[Load Route Providers]
C --> H[Init AutoCert]
C --> I[Init Notifications]
C --> J[Init Entrypoint]
K[ActiveConfig] -.-> B
subgraph Providers
G --> L[Docker Provider]
G --> M[File Provider]
G --> N[Agent Provider]
end
subgraph State Management
B --> O[xsync.Map Providers]
B --> P[Entrypoint]
B --> Q[AutoCert Provider]
B --> R[task.Task]
end
```
### Initialization pipeline
```mermaid
sequenceDiagram
participant YAML
participant State
participant MaxMind
participant Proxmox
participant Providers
participant AutoCert
participant Notif
participant Entrypoint
YAML->>State: Parse & Validate
par Initialize in parallel
State->>MaxMind: Initialize
State->>Proxmox: Initialize
and
State->>Providers: Load Route Providers
Providers->>State: Store Providers
end
State->>AutoCert: Initialize
State->>Notif: Initialize
State->>Entrypoint: Configure
State->>State: Start Providers
```
### Thread safety model
```go
var stateMu sync.RWMutex
func GetState() config.State {
return config.ActiveState.Load()
}
func SetState(state config.State) {
stateMu.Lock()
defer stateMu.Unlock()
config.ActiveState.Store(state)
}
```
Uses `sync.RWMutex` for write synchronization and `sync/atomic` for read operations.
## Configuration Surface
### Config sources
Configuration is loaded from `config/config.yml`.
### Hot-reloading
Configuration supports hot-reloading via editing `config/config.yml`.
## Dependency and Integration Map
### Internal dependencies
- `internal/acl` - Access control configuration
- `internal/autocert` - SSL certificate management
- `internal/entrypoint` - HTTP entrypoint setup
- `internal/route/provider` - Route providers (Docker, file, agent)
- `internal/maxmind` - GeoIP configuration
- `internal/notif` - Notification providers
- `internal/proxmox` - LXC container management
- `internal/homepage/types` - Dashboard configuration
- `github.com/yusing/goutils/task` - Object lifecycle management
### External dependencies
- `github.com/goccy/go-yaml` - YAML parsing
- `github.com/puzpuzpuz/xsync/v4` - Concurrent map
### Integration points
```go
// API uses config/query to access state
providers := statequery.RouteProviderList()
// Route providers access config state
for _, p := range config.GetState().IterProviders() {
// Process provider
}
```
## Observability
### Logs
- Configuration parsing and validation errors
- Provider initialization results
- Route loading summary
- Full configuration dump (at debug level)
### Metrics
No metrics are currently exposed.
## Security Considerations
- Configuration file permissions should be restricted (contains secrets)
- TLS certificates are loaded from files specified in config
- Agent credentials are passed via configuration
- No secrets are logged (except in debug mode with full config dump)
## Failure Modes and Recovery
| Failure | Behavior | Recovery |
| ----------------------------- | ----------------------------------- | -------------------------- |
| Invalid YAML | Init returns error | Fix YAML syntax |
| Missing required fields | Validation fails | Add required fields |
| Provider initialization fails | Error aggregated and returned | Fix provider configuration |
| Duplicate provider key | Error logged, first provider kept | Rename provider |
| Route loading fails | Error aggregated, other routes load | Fix route configuration |
## Performance Characteristics
- Providers are loaded concurrently
- Routes are loaded concurrently per provider
- State access is lock-free for reads
- Atomic pointer for state swap
## Usage Examples
### Loading configuration
```go
state := config.NewState()
err := state.InitFromFile("config.yml")
if err != nil {
log.Fatal(err)
}
config.SetState(state)
```
### Accessing configuration
```go
if config.HasState() {
cfg := config.Value()
log.Printf("Entrypoint middleware count: %d", len(cfg.Entrypoint.Middlewares))
log.Printf("Docker providers: %d", len(cfg.Providers.Docker))
}
```
### Iterating providers
```go
for name, provider := range config.GetState().IterProviders() {
log.Printf("Provider: %s, Routes: %d", name, provider.NumRoutes())
}
```
### Accessing entrypoint handler
```go
state := config.GetState()
http.Handle("/", state.EntrypointHandler())
```

View File

@@ -0,0 +1,226 @@
# Configuration Query
Read-only access to the active configuration state, including route providers and system statistics.
## Overview
The `internal/config/query` package offers read-only access to the active configuration state. It provides functions to dump route providers, list providers, search for routes, and retrieve system statistics. This package is primarily used by the API layer to expose configuration information.
### Primary consumers
- `internal/api/v1` - REST API endpoints for configuration queries
- `internal/homepage` - Dashboard statistics display
- Operators - CLI tools and debugging interfaces
### Non-goals
- Configuration modification (see `internal/config`)
- Provider lifecycle management
- Dynamic state updates
### Stability
Stable internal package. Functions are simple read-only accessors.
## Public API
### Exported types
```go
type RouteProviderListResponse struct {
ShortName string `json:"short_name"`
FullName string `json:"full_name"`
}
```
```go
type Statistics struct {
Total uint16 `json:"total"`
ReverseProxies types.RouteStats `json:"reverse_proxies"`
Streams types.RouteStats `json:"streams"`
Providers map[string]types.ProviderStats `json:"providers"`
}
```
### Exported functions
```go
func DumpRouteProviders() map[string]types.RouteProvider
```
Returns all route providers as a map keyed by their short name. Thread-safe access via `config.ActiveState.Load()`.
```go
func RouteProviderList() []RouteProviderListResponse
```
Returns a list of route providers with their short and full names. Useful for API responses.
```go
func SearchRoute(alias string) types.Route
```
Searches for a route by alias across all providers. Returns `nil` if not found.
```go
func GetStatistics() Statistics
```
Aggregates statistics from all route providers, including total routes, reverse proxies, streams, and per-provider stats.
## Architecture
### Core components
```
config/query/
├── query.go # Provider and route queries
└── stats.go # Statistics aggregation
```
### Data flow
```mermaid
graph TD
A[API Request] --> B[config/query Functions]
B --> C{Query Type}
C -->|Provider List| D[ActiveState.Load]
C -->|Route Search| E[Iterate Providers]
C -->|Statistics| F[Aggregate from All Providers]
D --> G[Return Provider Data]
E --> H[Return Found Route or nil]
F --> I[Return Statistics]
```
### Thread safety model
All functions use `config.ActiveState.Load()` for thread-safe read access:
```go
func DumpRouteProviders() map[string]types.RouteProvider {
state := config.ActiveState.Load()
entries := make(map[string]types.RouteProvider, state.NumProviders())
for _, p := range state.IterProviders() {
entries[p.ShortName()] = p
}
return entries
}
```
## Configuration Surface
No configuration. This package only reads from the active state.
## Dependency and Integration Map
### Internal dependencies
- `internal/config/types` - `ActiveState` atomic pointer and `State` interface
- `internal/types` - Route provider and route types
### Integration points
```go
// API endpoint uses query functions
func ListProviders(w http.ResponseWriter, r *http.Request) {
providers := statequery.RouteProviderList()
json.NewEncoder(w).Encode(providers)
}
```
## Observability
### Logs
No logging in the query package itself.
### Metrics
No metrics are currently exposed.
## Security Considerations
- Read-only access prevents state corruption
- No sensitive data is exposed beyond what the configuration already contains
- Caller should handle nil state gracefully
## Failure Modes and Recovery
| Failure | Behavior | Recovery |
| -------------------- | -------------------------- | ------------------------------ |
| No active state | Functions return empty/nil | Initialize config first |
| Provider returns nil | Skipped in iteration | Provider should not return nil |
| Route not found | Returns nil | Expected behavior |
## Performance Characteristics
- O(n) where n is number of providers for provider queries
- O(n * m) where m is routes per provider for route search
- O(n) for statistics aggregation
- No locking required (uses atomic load)
## Usage Examples
### Listing all providers
```go
providers := statequery.RouteProviderList()
for _, p := range providers {
fmt.Printf("Short: %s, Full: %s\n", p.ShortName, p.FullName)
}
```
### Getting all providers as a map
```go
providers := statequery.DumpRouteProviders()
for shortName, provider := range providers {
fmt.Printf("%s: %s\n", shortName, provider.String())
}
```
### Searching for a route
```go
route := statequery.SearchRoute("my-service")
if route != nil {
fmt.Printf("Found route: %s\n", route.Alias())
}
```
### Getting system statistics
```go
stats := statequery.GetStatistics()
fmt.Printf("Total routes: %d\n", stats.Total)
fmt.Printf("Reverse proxies: %d\n", stats.ReverseProxies.Total)
for name, providerStats := range stats.Providers {
fmt.Printf("Provider %s: %d routes\n", name, providerStats.RPs.Total)
}
```
### Integration with API
```go
func handleGetProviders(w http.ResponseWriter, r *http.Request) {
providers := statequery.RouteProviderList()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(providers)
}
func handleGetStats(w http.ResponseWriter, r *http.Request) {
stats := statequery.GetStatistics()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(stats)
}
func handleFindRoute(w http.ResponseWriter, r *http.Request) {
alias := r.URL.Query().Get("alias")
route := statequery.SearchRoute(alias)
if route == nil {
http.NotFound(w, r)
return
}
json.NewEncoder(w).Encode(route)
}
```