Volume: {name}
-
-
- {data.status}
+
+ {data.status[0].toUpperCase() + data.status.slice(1)}
diff --git a/apps/client/app/routes/home.tsx b/apps/client/app/routes/home.tsx
index b037558..ee0c1c9 100644
--- a/apps/client/app/routes/home.tsx
+++ b/apps/client/app/routes/home.tsx
@@ -11,6 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
import { VolumeIcon } from "~/components/volume-icon";
import type { Route } from "./+types/home";
+import { StatusDot } from "~/components/status-dot";
export function meta(_: Route.MetaArgs) {
return [
@@ -101,10 +102,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
-
-
-
-
+
))}
diff --git a/apps/client/package.json b/apps/client/package.json
index 435433d..06e9923 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -17,6 +17,7 @@
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tooltip": "^1.2.8",
"@react-router/node": "^7.7.1",
"@react-router/serve": "^7.7.1",
"@tanstack/react-query": "^5.84.2",
diff --git a/apps/server/src/core/config.ts b/apps/server/src/core/config.ts
index 7d68d32..023d737 100644
--- a/apps/server/src/core/config.ts
+++ b/apps/server/src/core/config.ts
@@ -2,16 +2,14 @@ import { type } from "arktype";
import "dotenv/config";
const envSchema = type({
- NODE_ENV: type
- .enumerated("development", "production", "test")
- .default("development"),
+ NODE_ENV: type.enumerated("development", "production", "test").default("development"),
VOLUME_ROOT: "string",
}).pipe((s) => ({
__prod__: s.NODE_ENV === "production",
environment: s.NODE_ENV,
dbFileName: "/data/ironmount.db",
volumeRootHost: s.VOLUME_ROOT,
- volumeRootContainer: "/mnt/volumes",
+ volumeRootContainer: "/mounts",
}));
const parseConfig = (env: unknown) => {
diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts
index a2a015f..c7b5a82 100644
--- a/apps/server/src/index.ts
+++ b/apps/server/src/index.ts
@@ -7,6 +7,7 @@ import { runDbMigrations } from "./db/db";
import { driverController } from "./modules/driver/driver.controller";
import { volumeController } from "./modules/volumes/volume.controller";
import { logger } from "./utils/logger";
+import { startup } from "./modules/lifecycle/startup";
export const generalDescriptor = (app: Hono) =>
openAPISpecs(app, {
@@ -57,6 +58,8 @@ const socketPath = "/run/docker/plugins/ironmount.sock";
fetch: app.fetch,
});
+ await startup();
+
logger.info(`Server is running at http://localhost:8080 and unix socket at ${socketPath}`);
})();
diff --git a/apps/server/src/modules/backends/backend.ts b/apps/server/src/modules/backends/backend.ts
index 9da64be..e0d1137 100644
--- a/apps/server/src/modules/backends/backend.ts
+++ b/apps/server/src/modules/backends/backend.ts
@@ -2,6 +2,7 @@ import type { BackendStatus } from "@ironmount/schemas";
import type { Volume } from "../../db/schema";
import { makeDirectoryBackend } from "./directory/directory-backend";
import { makeNfsBackend } from "./nfs/nfs-backend";
+import { config } from "../../core/config";
export type VolumeBackend = {
mount: () => Promise
;
@@ -10,17 +11,17 @@ export type VolumeBackend = {
};
export const createVolumeBackend = (volume: Volume): VolumeBackend => {
- const { config, path } = volume;
+ const path = `${config.volumeRootContainer}/${volume.name}/_data`;
- switch (config.backend) {
+ switch (volume.config.backend) {
case "nfs": {
- return makeNfsBackend(config, path);
+ return makeNfsBackend(volume.config, path);
}
case "directory": {
- return makeDirectoryBackend(config, path);
+ return makeDirectoryBackend(volume.config, path);
}
default: {
- throw new Error(`Backend ${config.backend} not implemented`);
+ throw new Error(`Backend ${volume.config.backend} not implemented`);
}
}
};
diff --git a/apps/server/src/modules/backends/nfs/nfs-backend.ts b/apps/server/src/modules/backends/nfs/nfs-backend.ts
index 4f2d70d..dbdd8e9 100644
--- a/apps/server/src/modules/backends/nfs/nfs-backend.ts
+++ b/apps/server/src/modules/backends/nfs/nfs-backend.ts
@@ -69,6 +69,11 @@ const unmount = async (path: string) => {
logger.error(`Error unmounting NFS volume: ${stderr}`);
return reject(new Error(`Failed to unmount NFS volume: ${stderr}`));
}
+
+ fs.rmdir(path).catch((rmdirError) => {
+ logger.error(`Failed to remove directory ${path}:`, rmdirError);
+ });
+
logger.info(`NFS volume unmounted successfully: ${stdout}`);
resolve();
});
diff --git a/apps/server/src/modules/lifecycle/startup.ts b/apps/server/src/modules/lifecycle/startup.ts
new file mode 100644
index 0000000..a595993
--- /dev/null
+++ b/apps/server/src/modules/lifecycle/startup.ts
@@ -0,0 +1,22 @@
+import { eq } from "drizzle-orm";
+import { db } from "../../db/db";
+import { logger } from "../../utils/logger";
+import { volumesTable } from "../../db/schema";
+import { createVolumeBackend } from "../backends/backend";
+
+export const startup = async () => {
+ logger.info("Mounting all volumes...");
+
+ const volumes = await db.query.volumesTable.findMany({ where: eq(volumesTable.status, "mounted") });
+
+ for (const volume of volumes) {
+ try {
+ const backend = createVolumeBackend(volume);
+ await backend.mount();
+ logger.info(`Mounted volume ${volume.name} successfully`);
+ } catch (error) {
+ logger.error(`Failed to mount volume ${volume.name}:`, error);
+ await db.update(volumesTable).set({ status: "unmounted" }).where(eq(volumesTable.name, volume.name));
+ }
+ }
+};
diff --git a/apps/server/src/modules/volumes/volume.controller.ts b/apps/server/src/modules/volumes/volume.controller.ts
index 83bc936..bc69e98 100644
--- a/apps/server/src/modules/volumes/volume.controller.ts
+++ b/apps/server/src/modules/volumes/volume.controller.ts
@@ -17,7 +17,6 @@ import {
unmountVolumeDto,
} from "./volume.dto";
import { volumeService } from "./volume.service";
-import { logger } from "../../utils/logger";
export const volumeController = new Hono()
.get("/", listVolumesDto, async (c) => {
diff --git a/bun.lock b/bun.lock
index 5996b6f..2c613ed 100644
--- a/bun.lock
+++ b/bun.lock
@@ -19,6 +19,7 @@
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tooltip": "^1.2.8",
"@react-router/node": "^7.7.1",
"@react-router/serve": "^7.7.1",
"@tanstack/react-query": "^5.84.2",
@@ -295,6 +296,8 @@
"@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="],
+ "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="],
+
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
diff --git a/docker-compose.yml b/docker-compose.yml
index 04148e3..0f50c59 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -23,5 +23,5 @@ services:
- ./apps/server/src:/app/apps/server/src
- ./data:/data
-
+
- /home/nicolas/ironmount/tmp:/mounts:rshared