From bf33b15b3eef09296dbaf89637a707eca4233993 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Mon, 10 Nov 2025 07:08:51 +0100 Subject: [PATCH] fix: cleanup volumes on shutdown --- apps/server/src/core/constants.ts | 1 + apps/server/src/index.ts | 21 +++++++++++--- apps/server/src/modules/lifecycle/shutdown.ts | 28 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 apps/server/src/modules/lifecycle/shutdown.ts diff --git a/apps/server/src/core/constants.ts b/apps/server/src/core/constants.ts index 4d21c5e..5dec36e 100644 --- a/apps/server/src/core/constants.ts +++ b/apps/server/src/core/constants.ts @@ -3,3 +3,4 @@ export const VOLUME_MOUNT_BASE = "/var/lib/ironmount/volumes"; export const REPOSITORY_BASE = "/var/lib/ironmount/repositories"; export const DATABASE_URL = "/var/lib/ironmount/data/ironmount.db"; export const RESTIC_PASS_FILE = "/var/lib/ironmount/data/restic.pass"; +export const SOCKET_PATH = "/run/docker/plugins/ironmount.sock"; diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 70f6dae..13d8edc 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -17,6 +17,8 @@ import { backupScheduleController } from "./modules/backups/backups.controller"; import { eventsController } from "./modules/events/events.controller"; import { handleServiceError } from "./utils/errors"; import { logger } from "./utils/logger"; +import { shutdown } from "./modules/lifecycle/shutdown"; +import { SOCKET_PATH } from "./core/constants"; export const generalDescriptor = (app: Hono) => openAPIRouteHandler(app, { @@ -70,17 +72,15 @@ runDbMigrations(); const { docker } = await getCapabilities(); if (docker) { - const socketPath = "/run/docker/plugins/ironmount.sock"; - try { await fs.mkdir("/run/docker/plugins", { recursive: true }); Bun.serve({ - unix: socketPath, + unix: SOCKET_PATH, fetch: driver.fetch, }); - logger.info(`Docker volume plugin server running at ${socketPath}`); + logger.info(`Docker volume plugin server running at ${SOCKET_PATH}`); } catch (error) { logger.error(`Failed to start Docker volume plugin server: ${error}`); } @@ -96,3 +96,16 @@ startup(); logger.info(`Server is running at http://localhost:4096`); export type AppType = typeof app; + +process.on("SIGTERM", async () => { + logger.info("SIGTERM received, starting graceful shutdown..."); + + await shutdown(); + process.exit(0); +}); + +process.on("SIGINT", async () => { + logger.info("SIGINT received, starting graceful shutdown..."); + await shutdown(); + process.exit(0); +}); diff --git a/apps/server/src/modules/lifecycle/shutdown.ts b/apps/server/src/modules/lifecycle/shutdown.ts new file mode 100644 index 0000000..cb1e155 --- /dev/null +++ b/apps/server/src/modules/lifecycle/shutdown.ts @@ -0,0 +1,28 @@ +import { Scheduler } from "../../core/scheduler"; +import { eq, or } from "drizzle-orm"; +import { db } from "../../db/db"; +import { volumesTable } from "../../db/schema"; +import { logger } from "../../utils/logger"; +import { SOCKET_PATH } from "../../core/constants"; +import { createVolumeBackend } from "../backends/backend"; + +export const shutdown = async () => { + await Scheduler.stop(); + + await Bun.file(SOCKET_PATH) + .delete() + .catch(() => { + // Ignore errors if the socket file does not exist + }); + + const volumes = await db.query.volumesTable.findMany({ + where: or(eq(volumesTable.status, "mounted")), + }); + + for (const volume of volumes) { + const backend = createVolumeBackend(volume); + const { status, error } = await backend.unmount(); + + logger.info(`Volume ${volume.name} unmount status: ${status}${error ? `, error: ${error}` : ""}`); + } +};