feat: add resilient container restart controls

This commit is contained in:
admin committed 2026-09-07 13:27:27 +02:00
1 parent 1bbb14470c
commit 7bb33e528c
13 files changed
+747 -28

No files matched your search

+44 -5
View File
@@ -22,6 +22,16 @@ import (
var ErrCredentialMissing = errors.New("Update-Zugang ist noch nicht eingerichtet")
const (
agentUnavailableRetryInterval = 2 * time.Second
agentAvailableCheckInterval = time.Minute
)
type updateAgent interface {
Start(context.Context, updateprotocol.Request) error
Status(context.Context) (updateprotocol.Status, error)
}
type State struct {
CurrentVersion string
LatestVersion string
@@ -46,9 +56,10 @@ type Manager struct {
logger *slog.Logger
apiURL string
interval time.Duration
agent *AgentClient
agent updateAgent
httpClient *http.Client
checkMu sync.Mutex
requestMu sync.Mutex
stateMu sync.RWMutex
state State
checkSignal chan struct{}
@@ -87,8 +98,8 @@ func New(database *store.Store, cipher *secure.Cipher, apiURL, agentSocket strin
}
func (m *Manager) Start(ctx context.Context) {
go m.monitorAgent(ctx, agentUnavailableRetryInterval, agentAvailableCheckInterval)
go func() {
m.refreshAgentStatus(ctx)
initial := time.NewTimer(15 * time.Second)
defer initial.Stop()
var ticker *time.Ticker
@@ -121,7 +132,28 @@ func (m *Manager) backgroundCheck(ctx context.Context) {
if err := m.CheckNow(checkCtx); err != nil && !errors.Is(err, ErrCredentialMissing) && !errors.Is(err, context.Canceled) {
m.logger.Warn("software update check failed", "error", err)
}
m.refreshAgentStatus(ctx)
}
func (m *Manager) monitorAgent(ctx context.Context, unavailableInterval, availableInterval time.Duration) {
for {
m.refreshAgentStatus(ctx)
delay := unavailableInterval
if m.State().AgentAvailable {
delay = availableInterval
}
timer := time.NewTimer(delay)
select {
case <-ctx.Done():
if !timer.Stop() {
select {
case <-timer.C:
default:
}
}
return
case <-timer.C:
}
}
}
func (m *Manager) TriggerCheck() {
@@ -190,6 +222,9 @@ func (m *Manager) CheckNow(ctx context.Context) error {
}
func (m *Manager) RequestUpdate(ctx context.Context, version string) error {
m.requestMu.Lock()
defer m.requestMu.Unlock()
version = strings.TrimSpace(version)
state := m.State()
if !validReleaseVersion(version) || version != state.LatestVersion {
@@ -198,12 +233,16 @@ func (m *Manager) RequestUpdate(ctx context.Context, version string) error {
if comparison, err := compareVersions(state.CurrentVersion, version); err != nil || comparison >= 0 {
return errors.New("die ausgewählte Version ist nicht neuer als die laufende Version")
}
if !state.AgentAvailable {
status, err := m.UpdateStatus(ctx)
if err != nil {
return errors.New("Update-Agent ist nicht erreichbar")
}
if !state.AgentCompatible {
if !agentCompatible(status) {
return errors.New("Web-App und Update-Agent müssen zuerst gemeinsam mit Docker Compose aktualisiert werden")
}
if status.Busy() {
return errors.New("ein Software-Update läuft bereits")
}
settings, err := m.store.GetSoftwareUpdateSettings(ctx)
if err != nil {
return err