0d832def7b
- hub/internal/web/templatefetcher.go: raw-template URL now points at the renamed repo (was relying on Gitea's post-rename redirect) - documentation/ (moved here from the felhom-agent repo): fix controller-source path refs (deploy-felhom-compose -> felhom-controller) and the platform repo name (proxmox-controller -> felhom-agent) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const templateRawURL = "https://gitea.dooplex.hu/admin/felhom-controller/raw/branch/main/controller/configs/controller.yaml.example"
|
|
|
|
// TemplateFetcher periodically fetches controller.yaml.example from the Gitea
|
|
// repo and caches it for config generation. Falls back to go:embed default.
|
|
type TemplateFetcher struct {
|
|
username string
|
|
token string
|
|
fetchInterval time.Duration
|
|
logger *log.Logger
|
|
|
|
mu sync.RWMutex
|
|
cachedTemplate string
|
|
lastFetch time.Time
|
|
lastError string
|
|
}
|
|
|
|
// NewTemplateFetcher creates a new TemplateFetcher.
|
|
func NewTemplateFetcher(username, token string, fetchInterval time.Duration, logger *log.Logger) *TemplateFetcher {
|
|
return &TemplateFetcher{
|
|
username: username,
|
|
token: token,
|
|
fetchInterval: fetchInterval,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Run starts the periodic fetch loop. Call in a goroutine.
|
|
// It fetches immediately on start, then every fetchInterval.
|
|
func (tf *TemplateFetcher) Run(ctx context.Context) {
|
|
tf.fetch()
|
|
ticker := time.NewTicker(tf.fetchInterval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
tf.fetch()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (tf *TemplateFetcher) fetch() {
|
|
req, err := http.NewRequest("GET", templateRawURL, nil)
|
|
if err != nil {
|
|
tf.mu.Lock()
|
|
tf.lastError = err.Error()
|
|
tf.mu.Unlock()
|
|
tf.logger.Printf("[WARN] Template fetch: failed to create request: %v", err)
|
|
return
|
|
}
|
|
if tf.username != "" && tf.token != "" {
|
|
req.SetBasicAuth(tf.username, tf.token)
|
|
}
|
|
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
tf.mu.Lock()
|
|
tf.lastError = err.Error()
|
|
tf.mu.Unlock()
|
|
tf.logger.Printf("[WARN] Template fetch: HTTP request failed: %v", err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
tf.mu.Lock()
|
|
tf.lastError = fmt.Sprintf("HTTP %d", resp.StatusCode)
|
|
tf.mu.Unlock()
|
|
tf.logger.Printf("[WARN] Template fetch: unexpected status %d", resp.StatusCode)
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) // 64KB max
|
|
if err != nil {
|
|
tf.mu.Lock()
|
|
tf.lastError = err.Error()
|
|
tf.mu.Unlock()
|
|
tf.logger.Printf("[WARN] Template fetch: read error: %v", err)
|
|
return
|
|
}
|
|
|
|
tf.mu.Lock()
|
|
tf.cachedTemplate = string(body)
|
|
tf.lastFetch = time.Now()
|
|
tf.lastError = ""
|
|
tf.mu.Unlock()
|
|
tf.logger.Printf("[DEBUG] Template fetched (%d bytes)", len(body))
|
|
}
|
|
|
|
// Template returns the cached controller.yaml template.
|
|
// If the cache is empty (never fetched or all failures), returns the go:embed fallback.
|
|
func (tf *TemplateFetcher) Template() string {
|
|
tf.mu.RLock()
|
|
defer tf.mu.RUnlock()
|
|
if tf.cachedTemplate != "" {
|
|
return tf.cachedTemplate
|
|
}
|
|
return defaultControllerTemplate
|
|
}
|