package web import ( "context" "fmt" "io" "log" "net/http" "sync" "time" ) const templateRawURL = "https://gitea.dooplex.hu/admin/deploy-felhom-compose/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 }