e0f54ef320
Add `tomswall agent`: it pulls this device's compiled config from tomswallapi, differentially applies it, and reports the applied generation. It caches the last known-good config and, when the control plane is unreachable, keeps applying that cache — it never fails closed. - internal/agent: rendered-config types, HTTP client (fetch + status report), on-disk cache, on-device DNS resolver for dns sets (honors the device's configured resolver, fail-safe on lookup failure), and the pull-apply-report loop behind a mockable Applier. - Translate the interface-agnostic, address-matched rendered model into native tomswall config using the "all:<cidr>" any-interface source/dest form, reusing the existing differential engine. Named-set members are inlined as concrete addresses (native nft set references are a tracked follow-up). - cmd/tomswall: wire the `agent` subcommand (flags + TOMSWALL_* env, --once). - Unit tests: translation, cache, and the don't-fail-closed fallback loop. - Add DESIGN.md documenting the control-plane architecture.
37 lines
815 B
Go
37 lines
815 B
Go
package agent
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Cache persists the last known-good rendered config to disk so the agent can
|
|
// keep applying it when the control plane is unreachable (never fail closed).
|
|
type Cache struct {
|
|
Path string
|
|
}
|
|
|
|
// Write atomically stores the raw config bytes.
|
|
func (c Cache) Write(raw []byte) error {
|
|
if err := os.MkdirAll(filepath.Dir(c.Path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
tmp := c.Path + ".tmp"
|
|
if err := os.WriteFile(tmp, raw, 0o600); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, c.Path)
|
|
}
|
|
|
|
// Read returns the cached config, or (nil, nil) when no cache exists yet.
|
|
func (c Cache) Read() (*RenderedConfig, error) {
|
|
raw, err := os.ReadFile(c.Path)
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ParseRendered(raw)
|
|
}
|