Architecture Overview
Architecture Overview
Update-Watcher follows a modular plugin architecture. Checkers and notifiers register themselves at startup, and a central runner orchestrates parallel execution.
Project Structure
- main.go
- root.go
- run.go
- setup.go
- watch_*.go
- checker.go
- registry.go
- notifier.go
- registry.go
- runner.go
- config.go
- wizard.go
- terminal.go
- table.go
- cron.go
- install.sh
- uninstall.sh
Key Design Patterns
Registry Pattern
Checkers and notifiers use a registry pattern with factory functions. Each implementation registers itself during package initialization via
init().Checker registry (checker/registry.go):
checker/registry.go
// FactoryFunc creates a Checker from a watcher configuration.
type FactoryFunc func(cfg config.WatcherConfig) (Checker, error)
var registry = map[string]FactoryFunc{}
// Register adds a checker factory to the global registry.
func Register(name string, factory FactoryFunc) {
registry[name] = factory
}Notifier registry (notifier/registry.go):
notifier/registry.go
// Register adds a notifier factory to the global registry.
func Register(name string, factory FactoryFunc)
// RegisterMeta adds display metadata for the setup wizard.
func RegisterMeta(meta NotifierMeta)Blank Imports for Registration
The runner imports all checker and notifier packages as blank imports to trigger their
init() functions.runner/runner.go
// runner/runner.go
import (
_ "github.com/mahype/update-watcher/checker/apt"
_ "github.com/mahype/update-watcher/checker/docker"
// ... all other checker packages
_ "github.com/mahype/update-watcher/notifier/slack"
_ "github.com/mahype/update-watcher/notifier/discord"
// ... all other notifier packages
)Interface-Based Design
Both checkers and notifiers are defined by simple interfaces, allowing new implementations to be added without modifying existing code.
checker/checker.go
// checker/checker.go
type Checker interface {
Name() string
Check(ctx context.Context) (*CheckResult, error)
}notifier/notifier.go
// notifier/notifier.go
type Notifier interface {
Name() string
Send(ctx context.Context, hostname string, results []*checker.CheckResult) error
}Functional Options
The runner uses the functional options pattern for configuration:
runner/runner.go
runner.New(cfg, runner.WithNotify(¬ify), runner.WithOnly("apt"))Data Flow
The execution flow during update-watcher run:
1. cmd/run.go
├── config.Load() # Read YAML, resolve ${ENV_VAR} references
└── runner.New(cfg).Run()
├── For each enabled watcher (parallel):
│ ├── checker.Create(type, cfg) # Registry lookup + factory
│ └── checker.Check(ctx) # Execute check
├── Self-update check # Query GitHub Releases API
├── Aggregate results # Count updates, detect security
└── Notify (sequential):
├── Apply send_policy # Skip if no updates + only-on-updates
└── For each enabled notifier:
├── notifier.Create(type, cfg)
└── notifier.Send(ctx, hostname, results)Key points:
- Checkers run in parallel using
sync.WaitGroupwith a shared mutex for results - Notifiers run sequentially after all checkers complete
- The self-update check always runs (unless
--onlyfilters to a specific checker) - Errors from individual checkers do not abort other checkers
Core Types
CheckResult
checker/checker.go
type CheckResult struct {
CheckerName string `json:"checker_name"`
Updates []Update `json:"updates"`
Summary string `json:"summary"`
CheckedAt time.Time `json:"checked_at"`
Error string `json:"error,omitempty"`
Notes []string `json:"notes,omitempty"`
}Update
checker/checker.go
type Update struct {
Name string `json:"name"`
CurrentVersion string `json:"current_version"`
NewVersion string `json:"new_version"`
Type string `json:"type"` // security, regular, plugin, theme, core, image, distro
Priority string `json:"priority"` // critical, high, normal, low
Source string `json:"source,omitempty"`
Phasing string `json:"phasing,omitempty"`
}RunResult
runner/runner.go
type RunResult struct {
Results []*checker.CheckResult
TotalUpdates int
HasSecurity bool
Errors []error
}Dependencies
| Package | Purpose |
|---|---|
| cobra | CLI framework |
| viper | Configuration management |
| charmbracelet/huh | Interactive TUI forms |
| charmbracelet/lipgloss | Terminal styling |
| goreleaser | Release automation |