mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-22 16:58:54 +02:00
docs: add per package README for implementation details (AI generated with human review)
This commit is contained in:
316
internal/config/README.md
Normal file
316
internal/config/README.md
Normal 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())
|
||||
```
|
||||
226
internal/config/query/README.md
Normal file
226
internal/config/query/README.md
Normal 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)
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user