feat: add config apply endpoint and config hash in reports

- POST /api/config/apply: accepts YAML body from Hub, validates and
  writes controller.yaml atomically (tmp+rename)
- GET /api/config/hash: returns SHA256 hash of current config file
- Report payload now includes config_hash field for Hub comparison
- Config endpoints use same dual auth as self-update (session OR Bearer)
- config.LoadFromBytes() for validation without file I/O
- config.FileHash() helper for SHA256 computation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 16:13:35 +01:00
parent dc5209288b
commit 85d1f2f673
5 changed files with 109 additions and 7 deletions
+26
View File
@@ -1,6 +1,8 @@
package config
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"strings"
@@ -170,6 +172,30 @@ func Load(path string) (*Config, error) {
return cfg, nil
}
// LoadFromBytes parses YAML config from raw bytes (for validation without file I/O).
func LoadFromBytes(data []byte) (*Config, error) {
expanded := os.ExpandEnv(string(data))
cfg := &Config{}
if err := yaml.Unmarshal([]byte(expanded), cfg); err != nil {
return nil, fmt.Errorf("parsing config: %w", err)
}
applyDefaults(cfg)
if err := validate(cfg); err != nil {
return nil, err
}
return cfg, nil
}
// FileHash returns the SHA256 hex digest of the config file at the given path.
func FileHash(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
h := sha256.Sum256(data)
return hex.EncodeToString(h[:]), nil
}
func applyDefaults(cfg *Config) {
d := func(val *string, def string) {
if *val == "" {