feat: add controller self-update mechanism (v0.16.0)
New selfupdate package: version parsing, audit state file, updater with
Gitea registry V2 check, docker pull + compose rewrite + compose up flow.
- API: /api/selfupdate/{status,check,update} with session+bearer auth
- UI: Settings "Verzió és frissítés" card with check/install buttons + JS polling
- Scheduler: periodic check (6h default) + optional daily auto-update
- Notifications: success/failure on post-update startup verification
- Alert: info banner when update available
- docker-compose.yml: add directory bind mount for compose file access
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/metrics"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/selfupdate"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
catalogsync "gitea.dooplex.hu/admin/felhom-controller/internal/sync"
|
||||
@@ -29,11 +30,12 @@ type Router struct {
|
||||
backupMgr *backup.Manager
|
||||
crossDriveRunner *backup.CrossDriveRunner
|
||||
metricsStore *metrics.MetricsStore
|
||||
updater *selfupdate.Updater
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewRouter(cfg *config.Config, sett *settings.Settings, stackMgr *stacks.Manager, syncer *catalogsync.Syncer, cpuCollector *system.CPUCollector, backupMgr *backup.Manager, crossDrive *backup.CrossDriveRunner, metricsStore *metrics.MetricsStore, logger *log.Logger) *Router {
|
||||
return &Router{cfg: cfg, sett: sett, stackMgr: stackMgr, syncer: syncer, cpuCollector: cpuCollector, backupMgr: backupMgr, crossDriveRunner: crossDrive, metricsStore: metricsStore, logger: logger}
|
||||
func NewRouter(cfg *config.Config, sett *settings.Settings, stackMgr *stacks.Manager, syncer *catalogsync.Syncer, cpuCollector *system.CPUCollector, backupMgr *backup.Manager, crossDrive *backup.CrossDriveRunner, metricsStore *metrics.MetricsStore, updater *selfupdate.Updater, logger *log.Logger) *Router {
|
||||
return &Router{cfg: cfg, sett: sett, stackMgr: stackMgr, syncer: syncer, cpuCollector: cpuCollector, backupMgr: backupMgr, crossDriveRunner: crossDrive, metricsStore: metricsStore, updater: updater, logger: logger}
|
||||
}
|
||||
|
||||
type apiResponse struct {
|
||||
@@ -154,6 +156,18 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
case path == "/metrics/sysinfo" && req.Method == http.MethodGet:
|
||||
r.metricsSysInfo(w, req)
|
||||
|
||||
// GET /api/selfupdate/status
|
||||
case path == "/selfupdate/status" && req.Method == http.MethodGet:
|
||||
r.selfupdateStatus(w, req)
|
||||
|
||||
// POST /api/selfupdate/check
|
||||
case path == "/selfupdate/check" && req.Method == http.MethodPost:
|
||||
r.selfupdateCheck(w, req)
|
||||
|
||||
// POST /api/selfupdate/update
|
||||
case path == "/selfupdate/update" && req.Method == http.MethodPost:
|
||||
r.selfupdateTrigger(w, req)
|
||||
|
||||
default:
|
||||
writeJSON(w, http.StatusNotFound, apiResponse{OK: false, Error: "endpoint not found"})
|
||||
}
|
||||
@@ -779,6 +793,36 @@ func extractName(path, suffix string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (r *Router) selfupdateStatus(w http.ResponseWriter, _ *http.Request) {
|
||||
if r.updater == nil {
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: map[string]interface{}{"enabled": false}})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: r.updater.GetStatus()})
|
||||
}
|
||||
|
||||
func (r *Router) selfupdateCheck(w http.ResponseWriter, _ *http.Request) {
|
||||
if r.updater == nil {
|
||||
writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "Self-update not configured"})
|
||||
return
|
||||
}
|
||||
result := r.updater.CheckForUpdate()
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: result})
|
||||
}
|
||||
|
||||
func (r *Router) selfupdateTrigger(w http.ResponseWriter, _ *http.Request) {
|
||||
if r.updater == nil {
|
||||
writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "Self-update not configured"})
|
||||
return
|
||||
}
|
||||
if err := r.updater.TriggerUpdate("manual"); err != nil {
|
||||
writeJSON(w, http.StatusConflict, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
r.logger.Println("[API] Manual self-update triggered")
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Frissítés elindítva"})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
|
||||
Reference in New Issue
Block a user