Files
felhom.eu/hub/internal/web/templatefetcher.go
T
admin 0d832def7b fix: update repo-name refs after deploy-felhom-compose -> felhom-controller rename
- 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>
2026-06-08 14:03:13 +02:00

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
}