From 361311285afb33a395d92bbfddaf6482a4301573 Mon Sep 17 00:00:00 2001 From: Nico <47644445+nicotsx@users.noreply.github.com> Date: Sun, 14 Dec 2025 11:07:32 +0100 Subject: [PATCH] refactor: remove docker volum plugin functionnality (#137) --- AGENTS.md | 33 +---- README.md | 91 ------------ app/client/hooks/use-system-info.ts | 2 +- .../modules/volumes/routes/volume-details.tsx | 21 --- app/client/modules/volumes/tabs/docker.tsx | 133 ------------------ app/server/core/capabilities.ts | 36 ----- app/server/core/constants.ts | 1 - app/server/index.ts | 23 +-- .../modules/driver/driver.controller.ts | 114 --------------- app/server/modules/lifecycle/shutdown.ts | 7 - app/server/modules/system/system.dto.ts | 1 - .../modules/volumes/volume.controller.ts | 8 -- app/server/modules/volumes/volume.dto.ts | 32 ----- app/server/modules/volumes/volume.service.ts | 46 ------ package.json | 2 - 15 files changed, 5 insertions(+), 545 deletions(-) delete mode 100644 app/client/modules/volumes/tabs/docker.tsx delete mode 100644 app/server/modules/driver/driver.controller.ts diff --git a/AGENTS.md b/AGENTS.md index 136692d0..6ba3fcd9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,6 @@ Zerobyte is a backup automation tool built on top of Restic that provides a web - **Styling**: Tailwind CSS v4 + Radix UI components - **Architecture**: Unified application structure (not a monorepo) - **Code Quality**: Biome (formatter & linter) -- **Containerization**: Docker with multi-stage builds ## Repository Structure @@ -84,9 +83,7 @@ The server follows a modular service-oriented architecture: **Entry Point**: `app/server/index.ts` -- Initializes servers using `react-router-hono-server`: - 1. Main API server on port 4096 (REST API + serves static frontend) - 2. Docker volume plugin server on Unix socket `/run/docker/plugins/zerobyte.sock` (optional, if Docker is available) +- Initializes main API server on port 4096 (REST API + serves static frontend) **Modules** (`app/server/modules/`): Each module follows a controller � service � database pattern: @@ -116,7 +113,7 @@ Cron-based background jobs managed by the Scheduler: **Core** (`app/server/core/`): - `scheduler.ts` - Job scheduling system using node-cron -- `capabilities.ts` - Detects available system features (Docker support, etc.) +- `capabilities.ts` - Detects available system features - `constants.ts` - Application-wide constants **Utils** (`app/server/utils/`): @@ -188,17 +185,6 @@ Zerobyte is a wrapper around Restic for backup operations. Key integration point - Dynamically generates rclone config and passes via environment variables - Supports backends like Dropbox, Google Drive, OneDrive, Backblaze B2, etc. -## Docker Volume Plugin - -When Docker socket is available (`/var/run/docker.sock`), Zerobyte registers as a Docker volume plugin: - -**Plugin Location**: `/run/docker/plugins/zerobyte.sock` -**Implementation**: `app/server/modules/driver/driver.controller.ts` - -This allows other containers to mount Zerobyte volumes using Docker. - -The plugin implements the Docker Volume Plugin API v1. - ## Environment & Configuration **Runtime Environment Variables**: @@ -212,7 +198,7 @@ The plugin implements the Docker Volume Plugin API v1. **Capabilities Detection**: On startup, the server detects available capabilities (see `core/capabilities.ts`): -- **Docker**: Requires `/var/run/docker.sock` access +- **rclone**: Requires `/root/.config/rclone` directory access - System will gracefully degrade if capabilities are unavailable ## Common Workflows @@ -252,16 +238,3 @@ On startup, the server detects available capabilities (see `core/capabilities.ts - **Security**: Restic password file has 0600 permissions - never expose it - **Mounting**: Requires privileged container or CAP_SYS_ADMIN for FUSE mounts - **API Documentation**: OpenAPI spec auto-generated at `/api/v1/openapi.json`, docs at `/api/v1/docs` - -## Docker Development Setup - -The `docker-compose.yml` defines two services: - -- `zerobyte-dev` - Development with hot reload (uses `development` stage) -- `zerobyte-prod` - Production build (uses `production` stage) - -Both mount: - -- `/var/lib/zerobyte` for persistent data -- `/dev/fuse` device for FUSE mounting -- Optionally `/var/run/docker.sock` for Docker plugin functionality diff --git a/README.md b/README.md index 257edb03..8bf2b7bb 100644 --- a/README.md +++ b/README.md @@ -196,97 +196,6 @@ Zerobyte allows you to easily restore your data from backups. To restore data, n ![Preview](https://github.com/nicotsx/zerobyte/blob/main/screenshots/restoring.png?raw=true) -## Propagating mounts to host - -Zerobyte is capable of propagating mounted volumes from within the container to the host system. This is particularly useful when you want to access the mounted data directly from the host to use it with other applications or services. - -In order to enable this feature, you need to change your bind mount `/var/lib/zerobyte` to use the `:rshared` flag. Here is an example of how to set this up in your `docker-compose.yml` file: - -```diff -services: - zerobyte: - image: ghcr.io/nicotsx/zerobyte:v0.18 - container_name: zerobyte - restart: unless-stopped - ports: - - "4096:4096" - devices: - - /dev/fuse:/dev/fuse - environment: - - TZ=Europe/Paris - volumes: - - /etc/localtime:/etc/localtime:ro -- - /var/lib/zerobyte:/var/lib/zerobyte -+ - /var/lib/zerobyte:/var/lib/zerobyte:rshared -``` - -Restart the Zerobyte container to apply the changes: - -```bash -docker compose down -docker compose up -d -``` - -## Docker plugin - -Zerobyte can also be used as a Docker volume plugin, allowing you to mount your volumes directly into other Docker containers. This enables seamless integration with your containerized applications. - -In order to enable this feature, you need to run Zerobyte with several items shared from the host. Here is an example of how to set this up in your `docker-compose.yml` file: - -```diff -services: - zerobyte: - image: ghcr.io/nicotsx/zerobyte:v0.18 - container_name: zerobyte - restart: unless-stopped - cap_add: - - SYS_ADMIN - ports: - - "4096:4096" - devices: - - /dev/fuse:/dev/fuse - environment: - - TZ=Europe/Paris - volumes: - - /etc/localtime:/etc/localtime:ro -- - /var/lib/zerobyte:/var/lib/zerobyte -+ - /var/lib/zerobyte:/var/lib/zerobyte:rshared -+ - /run/docker/plugins:/run/docker/plugins -+ - /var/run/docker.sock:/var/run/docker.sock -``` - -Restart the Zerobyte container to apply the changes: - -```bash -docker compose down -docker compose up -d -``` - -Your Zerobyte volumes will now be available as Docker volumes that you can mount into other containers using the `--volume` flag: - -```bash -docker run -v zb-abc12:/path/in/container nginx:latest -``` - -Or using Docker Compose: - -```yaml -services: - myservice: - image: nginx:latest - volumes: - - zb-abc12:/path/in/container -volumes: - zb-abc12: - external: true -``` - -The volume name format is `zb-` where `` is the unique identifier shown on the volume's Docker tab in Zerobyte. This short ID remains stable even if you rename the volume. You can verify that the volume is available by running: - -```bash -docker volume ls -``` - ## Third-Party Software This project includes the following third-party software components: diff --git a/app/client/hooks/use-system-info.ts b/app/client/hooks/use-system-info.ts index 852a9015..47046bae 100644 --- a/app/client/hooks/use-system-info.ts +++ b/app/client/hooks/use-system-info.ts @@ -10,7 +10,7 @@ export function useSystemInfo() { }); return { - capabilities: data?.capabilities ?? { docker: false, rclone: false }, + capabilities: data?.capabilities ?? { rclone: false }, isLoading, error, systemInfo: data, diff --git a/app/client/modules/volumes/routes/volume-details.tsx b/app/client/modules/volumes/routes/volume-details.tsx index 082589c9..61131a9f 100644 --- a/app/client/modules/volumes/routes/volume-details.tsx +++ b/app/client/modules/volumes/routes/volume-details.tsx @@ -21,9 +21,6 @@ import { cn } from "~/client/lib/utils"; import type { Route } from "./+types/volume-details"; import { VolumeInfoTabContent } from "../tabs/info"; import { FilesTabContent } from "../tabs/files"; -import { DockerTabContent } from "../tabs/docker"; -import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip"; -import { useSystemInfo } from "~/client/hooks/use-system-info"; import { getVolume } from "~/client/api-client"; import type { VolumeStatus } from "~/client/lib/types"; import { @@ -74,8 +71,6 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) { initialData: loaderData, }); - const { capabilities } = useSystemInfo(); - const deleteVol = useMutation({ ...deleteVolumeMutation(), onSuccess: () => { @@ -127,7 +122,6 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) { } const { volume, statfs } = data; - const dockerAvailable = capabilities.docker; return ( <> @@ -170,16 +164,6 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) { Configuration Files - - - - Docker - - - -

Enable Docker support to access this tab.

-
-
@@ -187,11 +171,6 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) { - {dockerAvailable && ( - - - - )} diff --git a/app/client/modules/volumes/tabs/docker.tsx b/app/client/modules/volumes/tabs/docker.tsx deleted file mode 100644 index c03e5687..00000000 --- a/app/client/modules/volumes/tabs/docker.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { Unplug } from "lucide-react"; -import * as YML from "yaml"; -import { getContainersUsingVolumeOptions } from "~/client/api-client/@tanstack/react-query.gen"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; -import { CodeBlock } from "~/client/components/ui/code-block"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table"; -import type { Volume } from "~/client/lib/types"; - -type Props = { - volume: Volume; -}; - -export const DockerTabContent = ({ volume }: Props) => { - const yamlString = YML.stringify({ - services: { - nginx: { - image: "nginx:latest", - volumes: [`zb-${volume.shortId}:/path/in/container`], - }, - }, - volumes: { - [`zb-${volume.shortId}`]: { - external: true, - }, - }, - }); - - const dockerRunCommand = `docker run -v zb-${volume.shortId}:/path/in/container nginx:latest`; - - const { - data: containersData, - isLoading, - error, - } = useQuery({ - ...getContainersUsingVolumeOptions({ path: { name: volume.name } }), - refetchInterval: 10000, - refetchOnWindowFocus: true, - }); - - const containers = containersData || []; - - const getStateClass = (state: string) => { - switch (state) { - case "running": - return "bg-green-100 text-green-800"; - case "exited": - return "bg-orange-100 text-orange-800"; - default: - return "bg-gray-100 text-gray-800"; - } - }; - - return ( -
- - - Plug-and-play Docker integration - - This volume can be used in your Docker Compose files by referencing it as an external volume. The example - demonstrates how to mount the volume to a service (nginx in this case). Make sure to adjust the path inside - the container to fit your application's needs - - - -
-
-
- -
-
- Alternatively, you can use the following command to run a Docker container with the volume mounted -
-
- -
-
-
-
-
- -
- - - Containers Using This Volume - List of Docker containers mounting this volume. - - - - {isLoading &&
Loading containers...
} - {error &&
Failed to load containers: {String(error)}
} - {!isLoading && !error && containers.length === 0 && ( -
- -

No Docker containers are currently using this volume.

-
- )} - {!isLoading && !error && containers.length > 0 && ( -
- - - - Name - ID - State - Image - - - - {containers.map((container) => ( - - {container.name} - {container.id.slice(0, 12)} - - - {container.state} - - - {container.image} - - ))} - -
-
- )} -
-
-
-
- ); -}; diff --git a/app/server/core/capabilities.ts b/app/server/core/capabilities.ts index 14d62964..343e6d4a 100644 --- a/app/server/core/capabilities.ts +++ b/app/server/core/capabilities.ts @@ -1,9 +1,7 @@ import * as fs from "node:fs/promises"; -import Docker from "dockerode"; import { logger } from "../utils/logger"; export type SystemCapabilities = { - docker: boolean; rclone: boolean; }; @@ -28,44 +26,10 @@ export async function getCapabilities(): Promise { */ async function detectCapabilities(): Promise { return { - docker: await detectDocker(), rclone: await detectRclone(), }; } -export const parseDockerHost = (dockerHost?: string) => { - const match = dockerHost?.match(/^(ssh|http|https):\/\/([^:]+)(?::(\d+))?$/); - if (match) { - const protocol = match[1] as "ssh" | "http" | "https"; - const host = match[2]; - const port = match[3] ? parseInt(match[3], 10) : undefined; - return { protocol, host, port }; - } - - return {}; -}; - -/** - * Checks if Docker is available by: - * 1. Checking if /var/run/docker.sock exists and is accessible - * 2. Attempting to ping the Docker daemon - */ -async function detectDocker(): Promise { - try { - const docker = new Docker(parseDockerHost(process.env.DOCKER_HOST)); - await docker.ping(); - - logger.info("Docker capability: enabled"); - return true; - } catch (_) { - logger.warn( - "Docker capability: disabled. " + - "To enable: mount /var/run/docker.sock and /run/docker/plugins in docker-compose.yml", - ); - return false; - } -} - /** * Checks if rclone is available by: * 1. Checking if /root/.config/rclone directory exists and is accessible diff --git a/app/server/core/constants.ts b/app/server/core/constants.ts index f0e54235..efc2b8c9 100644 --- a/app/server/core/constants.ts +++ b/app/server/core/constants.ts @@ -3,6 +3,5 @@ export const VOLUME_MOUNT_BASE = "/var/lib/zerobyte/volumes"; export const REPOSITORY_BASE = "/var/lib/zerobyte/repositories"; export const DATABASE_URL = "/var/lib/zerobyte/data/ironmount.db"; export const RESTIC_PASS_FILE = "/var/lib/zerobyte/data/restic.pass"; -export const SOCKET_PATH = "/run/docker/plugins/zerobyte.sock"; export const REQUIRED_MIGRATIONS = ["v0.14.0"]; diff --git a/app/server/index.ts b/app/server/index.ts index 09e8045b..4e4516a5 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -1,14 +1,11 @@ import { createHonoServer } from "react-router-hono-server/bun"; -import * as fs from "node:fs/promises"; import { Scalar } from "@scalar/hono-api-reference"; import { Hono } from "hono"; import { logger as honoLogger } from "hono/logger"; import { openAPIRouteHandler } from "hono-openapi"; -import { getCapabilities } from "./core/capabilities"; import { runDbMigrations } from "./db/db"; import { authController } from "./modules/auth/auth.controller"; import { requireAuth } from "./modules/auth/auth.middleware"; -import { driverController } from "./modules/driver/driver.controller"; import { startup } from "./modules/lifecycle/startup"; import { migrateToShortIds } from "./modules/lifecycle/migration"; import { repositoriesController } from "./modules/repositories/repositories.controller"; @@ -20,7 +17,7 @@ import { notificationsController } from "./modules/notifications/notifications.c import { handleServiceError } from "./utils/errors"; import { logger } from "./utils/logger"; import { shutdown } from "./modules/lifecycle/shutdown"; -import { REQUIRED_MIGRATIONS, SOCKET_PATH } from "./core/constants"; +import { REQUIRED_MIGRATIONS } from "./core/constants"; import { validateRequiredMigrations } from "./modules/lifecycle/checkpoint"; export const generalDescriptor = (app: Hono) => @@ -41,7 +38,6 @@ export const scalarDescriptor = Scalar({ url: "/api/v1/openapi.json", }); -const driver = new Hono().use(honoLogger()).route("/", driverController); const app = new Hono() .use(honoLogger()) .get("healthcheck", (c) => c.json({ status: "ok" })) @@ -73,23 +69,6 @@ runDbMigrations(); await migrateToShortIds(); await validateRequiredMigrations(REQUIRED_MIGRATIONS); -const { docker } = await getCapabilities(); - -if (docker) { - try { - await fs.mkdir("/run/docker/plugins", { recursive: true }); - - Bun.serve({ - unix: SOCKET_PATH, - fetch: driver.fetch, - }); - - logger.info(`Docker volume plugin server running at ${SOCKET_PATH}`); - } catch (error) { - logger.error(`Failed to start Docker volume plugin server: ${error}`); - } -} - startup(); logger.info(`Server is running at http://localhost:4096`); diff --git a/app/server/modules/driver/driver.controller.ts b/app/server/modules/driver/driver.controller.ts deleted file mode 100644 index 72a1feb6..00000000 --- a/app/server/modules/driver/driver.controller.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Hono } from "hono"; -import { volumeService } from "../volumes/volume.service"; -import { getVolumePath } from "../volumes/helpers"; -import { eq } from "drizzle-orm"; -import { db } from "../../db/db"; -import { volumesTable } from "../../db/schema"; - -export const driverController = new Hono() - .post("/VolumeDriver.Capabilities", (c) => { - return c.json({ - Capabilities: { - Scope: "global", - }, - }); - }) - .post("/Plugin.Activate", (c) => { - return c.json({ - Implements: ["VolumeDriver"], - }); - }) - .post("/VolumeDriver.Create", (_) => { - throw new Error("Volume creation is not supported via the driver"); - }) - .post("/VolumeDriver.Remove", (c) => { - return c.json({ - Err: "", - }); - }) - .post("/VolumeDriver.Mount", async (c) => { - const body = await c.req.json(); - - if (!body.Name) { - return c.json({ Err: "Volume name is required" }, 400); - } - - const shortId = body.Name.replace(/^zb-/, ""); - - const volume = await db.query.volumesTable.findFirst({ - where: eq(volumesTable.shortId, shortId), - }); - - if (!volume) { - return c.json({ Err: `Volume with shortId ${shortId} not found` }, 404); - } - - return c.json({ - Mountpoint: getVolumePath(volume), - }); - }) - .post("/VolumeDriver.Unmount", (c) => { - return c.json({ - Err: "", - }); - }) - .post("/VolumeDriver.Path", async (c) => { - const body = await c.req.json(); - - if (!body.Name) { - return c.json({ Err: "Volume name is required" }, 400); - } - - const shortId = body.Name.replace(/^zb-/, ""); - - const volume = await db.query.volumesTable.findFirst({ - where: eq(volumesTable.shortId, shortId), - }); - - if (!volume) { - return c.json({ Err: `Volume with shortId ${shortId} not found` }, 404); - } - - return c.json({ - Mountpoint: getVolumePath(volume), - }); - }) - .post("/VolumeDriver.Get", async (c) => { - const body = await c.req.json(); - - if (!body.Name) { - return c.json({ Err: "Volume name is required" }, 400); - } - - const shortId = body.Name.replace(/^zb-/, ""); - - const volume = await db.query.volumesTable.findFirst({ - where: eq(volumesTable.shortId, shortId), - }); - - if (!volume) { - return c.json({ Err: `Volume with shortId ${shortId} not found` }, 404); - } - - return c.json({ - Volume: { - Name: `zb-${volume.shortId}`, - Mountpoint: getVolumePath(volume), - Status: {}, - }, - Err: "", - }); - }) - .post("/VolumeDriver.List", async (c) => { - const volumes = await volumeService.listVolumes(); - - const res = volumes.map((volume) => ({ - Name: `zb-${volume.shortId}`, - Mountpoint: getVolumePath(volume), - Status: {}, - })); - - return c.json({ - Volumes: res, - }); - }); diff --git a/app/server/modules/lifecycle/shutdown.ts b/app/server/modules/lifecycle/shutdown.ts index cb1e155e..13a38bfc 100644 --- a/app/server/modules/lifecycle/shutdown.ts +++ b/app/server/modules/lifecycle/shutdown.ts @@ -3,18 +3,11 @@ 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")), }); diff --git a/app/server/modules/system/system.dto.ts b/app/server/modules/system/system.dto.ts index 41687198..b74f622f 100644 --- a/app/server/modules/system/system.dto.ts +++ b/app/server/modules/system/system.dto.ts @@ -2,7 +2,6 @@ import { type } from "arktype"; import { describeRoute, resolver } from "hono-openapi"; export const capabilitiesSchema = type({ - docker: "boolean", rclone: "boolean", }); diff --git a/app/server/modules/volumes/volume.controller.ts b/app/server/modules/volumes/volume.controller.ts index 94c0818d..7c97260d 100644 --- a/app/server/modules/volumes/volume.controller.ts +++ b/app/server/modules/volumes/volume.controller.ts @@ -4,7 +4,6 @@ import { createVolumeBody, createVolumeDto, deleteVolumeDto, - getContainersDto, getVolumeDto, healthCheckDto, type ListVolumesDto, @@ -18,7 +17,6 @@ import { updateVolumeDto, type CreateVolumeDto, type GetVolumeDto, - type ListContainersDto, type UpdateVolumeDto, type ListFilesDto, browseFilesystemDto, @@ -74,12 +72,6 @@ export const volumeController = new Hono() return c.json(response, 200); }) - .get("/:name/containers", getContainersDto, async (c) => { - const { name } = c.req.param(); - const { containers } = await volumeService.getContainersUsingVolume(name); - - return c.json(containers, 200); - }) .put("/:name", updateVolumeDto, validator("json", updateVolumeBody), async (c) => { const { name } = c.req.param(); const body = c.req.valid("json"); diff --git a/app/server/modules/volumes/volume.dto.ts b/app/server/modules/volumes/volume.dto.ts index 03d61987..bb76f4c8 100644 --- a/app/server/modules/volumes/volume.dto.ts +++ b/app/server/modules/volumes/volume.dto.ts @@ -262,38 +262,6 @@ export const healthCheckDto = describeRoute({ }, }); -/** - * Get containers using a volume - */ -const containerSchema = type({ - id: "string", - name: "string", - state: "string", - image: "string", -}); - -export const listContainersResponse = containerSchema.array(); -export type ListContainersDto = typeof listContainersResponse.infer; - -export const getContainersDto = describeRoute({ - description: "Get containers using a volume by name", - operationId: "getContainersUsingVolume", - tags: ["Volumes"], - responses: { - 200: { - description: "List of containers using the volume", - content: { - "application/json": { - schema: resolver(listContainersResponse), - }, - }, - }, - 404: { - description: "Volume not found", - }, - }, -}); - /** * List files in a volume */ diff --git a/app/server/modules/volumes/volume.service.ts b/app/server/modules/volumes/volume.service.ts index 3f415c0b..08ed9279 100644 --- a/app/server/modules/volumes/volume.service.ts +++ b/app/server/modules/volumes/volume.service.ts @@ -1,11 +1,9 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; -import Docker from "dockerode"; import { and, eq, ne } from "drizzle-orm"; import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced"; import slugify from "slugify"; -import { getCapabilities, parseDockerHost } from "../../core/capabilities"; import { db } from "../../db/db"; import { volumesTable } from "../../db/schema"; import { cryptoUtils } from "../../utils/crypto"; @@ -282,49 +280,6 @@ const checkHealth = async (name: string) => { return { status, error }; }; -const getContainersUsingVolume = async (name: string) => { - const volume = await db.query.volumesTable.findFirst({ - where: eq(volumesTable.name, name), - }); - - if (!volume) { - throw new NotFoundError("Volume not found"); - } - - const { docker } = await getCapabilities(); - if (!docker) { - logger.debug("Docker capability not available, returning empty containers list"); - return { containers: [] }; - } - - try { - const docker = new Docker(parseDockerHost(process.env.DOCKER_HOST)); - - const containers = await docker.listContainers({ all: true }); - - const usingContainers = []; - for (const info of containers) { - const container = docker.getContainer(info.Id); - const inspect = await container.inspect(); - const mounts = inspect.Mounts || []; - const usesVolume = mounts.some((mount) => mount.Type === "volume" && mount.Name === `zb-${volume.shortId}`); - if (usesVolume) { - usingContainers.push({ - id: inspect.Id, - name: inspect.Name, - state: inspect.State.Status, - image: inspect.Config.Image, - }); - } - } - - return { containers: usingContainers }; - } catch (error) { - logger.error(`Failed to get containers using volume: ${toMessage(error)}`); - return { containers: [] }; - } -}; - const listFiles = async (name: string, subPath?: string) => { const volume = await db.query.volumesTable.findFirst({ where: eq(volumesTable.name, name), @@ -443,7 +398,6 @@ export const volumeService = { testConnection, unmountVolume, checkHealth, - getContainersUsingVolume, listFiles, browseFilesystem, }; diff --git a/package.json b/package.json index a7872574..e6d2fcb9 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "cron-parser": "^5.4.0", "date-fns": "^4.1.0", "dither-plugin": "^1.1.1", - "dockerode": "^4.0.9", "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", "es-toolkit": "^1.42.0", @@ -72,7 +71,6 @@ "@tailwindcss/vite": "^4.1.17", "@tanstack/react-query-devtools": "^5.91.1", "@types/bun": "^1.3.3", - "@types/dockerode": "^3.3.47", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3",