added fix for deployment race condition

This commit is contained in:
2026-02-14 19:17:01 +01:00
parent 0be798af5d
commit a8096faf59
5 changed files with 55 additions and 21 deletions
+3 -2
View File
@@ -148,13 +148,14 @@ controller/
- **Auto-generated**: DB passwords, secret keys (shown as "✓ Generated")
- **User input**: HDD path, admin password, language, etc.
- **"🎲 Generálás"** button next to password fields
3. Clicks "Telepítés" → controller:
3. Clicks "Telepítés" → `checkBeforeDeploy()` JS guard fetches live state from API first (prevents deploying if already deployed from another tab). Then controller:
- **Memory validation**: checks `mem_request` against available system RAM (see below)
- Validates all required fields (password fields must be explicitly filled or generated)
- Generates auto-secrets (DB passwords, hex keys)
- Saves `app.yaml` (env vars + locked fields list)
- **Updates in-memory state immediately** (so UI shows "deployed" during slow compose ops)
- Runs `docker compose up -d` with env vars injected
- Updates in-memory state immediately (no stale "Telepítés" button)
- On failure: reverts both in-memory state and disk (app.yaml `deployed: false`)
4. **Progress UI** replaces the form with a 3-step progress panel:
- ✅ "Konfiguráció mentve" — shown immediately after API success
- ⏳ "Konténer(ek) indítása..." → ✅ when containers are up
+21 -13
View File
@@ -183,19 +183,9 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
m.checkLocalImages(req.StackName, stackDir)
}
// Run docker compose up -d
start := time.Now()
_, composeErr := m.composeExecWithEnv(stackDir, env, "up", "-d")
if composeErr != nil {
m.logger.Printf("[ERROR] Stack %s deploy failed after %.1fs: %v", req.StackName, time.Since(start).Seconds(), composeErr)
// Deployment failed — keep app.yaml for debugging but mark as not deployed
appCfg.Deployed = false
_ = SaveAppConfig(stackDir, appCfg)
return "", fmt.Errorf("docker compose up failed: %w", composeErr)
}
// Update in-memory stack state immediately so the UI reflects the deployment
// without waiting for the next ScanStacks() cycle.
// Update in-memory stack state BEFORE compose up so the UI reflects
// "deployed" immediately (compose up can take 30-60s for image pulls).
// If compose up fails, we revert both disk and in-memory state below.
m.mu.Lock()
if s, ok := m.stacks[req.StackName]; ok {
s.Deployed = true
@@ -203,6 +193,24 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
}
m.mu.Unlock()
// Run docker compose up -d
start := time.Now()
_, composeErr := m.composeExecWithEnv(stackDir, env, "up", "-d")
if composeErr != nil {
m.logger.Printf("[ERROR] Stack %s deploy failed after %.1fs: %v", req.StackName, time.Since(start).Seconds(), composeErr)
// Revert in-memory state
m.mu.Lock()
if s, ok := m.stacks[req.StackName]; ok {
s.Deployed = false
s.AppConfig = nil
}
m.mu.Unlock()
// Revert disk state — keep app.yaml for debugging but mark as not deployed
appCfg.Deployed = false
_ = SaveAppConfig(stackDir, appCfg)
return "", fmt.Errorf("docker compose up failed: %w", composeErr)
}
m.logger.Printf("[INFO] Stack %s deployed successfully (took %.1fs)", req.StackName, time.Since(start).Seconds())
// Post-deploy container state check (async, non-blocking)
+15 -2
View File
@@ -42,6 +42,19 @@ const layoutTmpl = `
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
alert('Ez az alkalmazás már telepítve van.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
@@ -189,7 +202,7 @@ const dashboardTmpl = `
{{if .Protected}}
<span class="badge badge-protected">Védett</span>
{{else if not .Deployed}}
<a href="/stacks/{{.Name}}/deploy" class="btn btn-sm btn-primary">Telepítés</a>
<a href="/stacks/{{.Name}}/deploy" class="btn btn-sm btn-primary" onclick="return checkBeforeDeploy(event, '{{.Name}}')">Telepítés</a>
{{else}}
{{if isOperational .State}}
<button class="btn btn-sm btn-warning" onclick="stackAction('{{.Name}}', 'restart')">↻</button>
@@ -267,7 +280,7 @@ const stacksTmpl = `
{{if .Protected}}
<span class="badge badge-protected">Védett rendszerkomponens</span>
{{else if not .Deployed}}
<a href="/stacks/{{.Name}}/deploy" class="btn btn-primary">Telepítés</a>
<a href="/stacks/{{.Name}}/deploy" class="btn btn-primary" onclick="return checkBeforeDeploy(event, '{{.Name}}')">Telepítés</a>
<a href="{{appPageURL .Meta.Slug}}" class="btn btn-outline">Részletek</a>
{{else}}
{{if isOperational .State}}