Power off all DDEV projects on app quit

Sites were previously left running in the background after closing the
app. before-quit now defers quitting until `ddev poweroff` finishes (or
times out), stopping every running project's containers in one shot.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
reaper
2026-08-08 19:58:00 -05:00
co-authored by Claude Sonnet 5
parent d014581eea
commit 68a5f80c31
2 changed files with 33 additions and 4 deletions
+20
View File
@@ -155,3 +155,23 @@ export function killAllRunningCommands(): void {
}
running.clear()
}
// Stops every running project's containers in one shot (equivalent to
// `ddev stop` on each of them, but faster) so nothing is left running in the
// background after the app quits. Resolves rather than rejects on any
// failure — a missing/unresponsive ddev or docker shouldn't block quitting —
// and the timeout guards against poweroff hanging if the docker daemon is
// stuck, which would otherwise stall app exit indefinitely.
export function powerOffAllProjects(): Promise<void> {
return new Promise((resolve) => {
const child = spawn('ddev', ['poweroff'], { env: ENV_WITH_DDEV_PATH, stdio: 'ignore' })
const timeout = setTimeout(() => child.kill(), 10_000)
const finish = (): void => {
clearTimeout(timeout)
resolve()
}
child.on('error', finish)
child.on('close', finish)
})
}
+13 -4
View File
@@ -9,7 +9,7 @@ import { registerAddonsIpc } from './ipc/addons'
import { registerLogsIpc } from './ipc/logs'
import { registerCreateIpc } from './ipc/create'
import { registerWindowIpc } from './ipc/window'
import { killAllRunningCommands } from './commandRunner'
import { killAllRunningCommands, powerOffAllProjects } from './commandRunner'
import { warmupDdevImagesIfNeeded } from './imageWarmup'
function createWindow(): void {
@@ -89,10 +89,19 @@ app.on('window-all-closed', () => {
}
})
// Kill any still-running ddev processes (e.g. an open `ddev logs -f` stream)
// so they don't linger as orphans after the app exits.
app.on('before-quit', () => {
// Kill any still-running ddev processes (e.g. an open `ddev logs -f` stream),
// then power off every running project so sites don't keep running in the
// background after the app exits. Quit is deferred until poweroff finishes
// (or times out), so this intercepts the first before-quit and re-fires
// app.quit() itself once cleanup is done — the isQuitting guard stops that
// from looping back into this handler.
let isQuitting = false
app.on('before-quit', (event) => {
if (isQuitting) return
event.preventDefault()
isQuitting = true
killAllRunningCommands()
void powerOffAllProjects().finally(() => app.quit())
})
// In this file you can include the rest of your app's specific main process