mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: system optional capabilities
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
|||||||
updateBackupSchedule,
|
updateBackupSchedule,
|
||||||
getBackupScheduleForVolume,
|
getBackupScheduleForVolume,
|
||||||
runBackupNow,
|
runBackupNow,
|
||||||
|
getSystemInfo,
|
||||||
} from "../sdk.gen";
|
} from "../sdk.gen";
|
||||||
import { queryOptions, type UseMutationOptions, type DefaultError } from "@tanstack/react-query";
|
import { queryOptions, type UseMutationOptions, type DefaultError } from "@tanstack/react-query";
|
||||||
import type {
|
import type {
|
||||||
@@ -90,6 +91,7 @@ import type {
|
|||||||
GetBackupScheduleForVolumeData,
|
GetBackupScheduleForVolumeData,
|
||||||
RunBackupNowData,
|
RunBackupNowData,
|
||||||
RunBackupNowResponse,
|
RunBackupNowResponse,
|
||||||
|
GetSystemInfoData,
|
||||||
} from "../types.gen";
|
} from "../types.gen";
|
||||||
import { client as _heyApiClient } from "../client.gen";
|
import { client as _heyApiClient } from "../client.gen";
|
||||||
|
|
||||||
@@ -1078,3 +1080,23 @@ export const runBackupNowMutation = (
|
|||||||
};
|
};
|
||||||
return mutationOptions;
|
return mutationOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getSystemInfoQueryKey = (options?: Options<GetSystemInfoData>) => createQueryKey("getSystemInfo", options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get system information including available capabilities
|
||||||
|
*/
|
||||||
|
export const getSystemInfoOptions = (options?: Options<GetSystemInfoData>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: async ({ queryKey, signal }) => {
|
||||||
|
const { data } = await getSystemInfo({
|
||||||
|
...options,
|
||||||
|
...queryKey[0],
|
||||||
|
signal,
|
||||||
|
throwOnError: true,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
queryKey: getSystemInfoQueryKey(options),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ import type {
|
|||||||
GetBackupScheduleForVolumeResponses,
|
GetBackupScheduleForVolumeResponses,
|
||||||
RunBackupNowData,
|
RunBackupNowData,
|
||||||
RunBackupNowResponses,
|
RunBackupNowResponses,
|
||||||
|
GetSystemInfoData,
|
||||||
|
GetSystemInfoResponses,
|
||||||
} from "./types.gen";
|
} from "./types.gen";
|
||||||
import { client as _heyApiClient } from "./client.gen";
|
import { client as _heyApiClient } from "./client.gen";
|
||||||
|
|
||||||
@@ -513,3 +515,15 @@ export const runBackupNow = <ThrowOnError extends boolean = false>(
|
|||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get system information including available capabilities
|
||||||
|
*/
|
||||||
|
export const getSystemInfo = <ThrowOnError extends boolean = false>(
|
||||||
|
options?: Options<GetSystemInfoData, ThrowOnError>,
|
||||||
|
) => {
|
||||||
|
return (options?.client ?? _heyApiClient).get<GetSystemInfoResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/api/v1/system/info",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1418,6 +1418,26 @@ export type RunBackupNowResponses = {
|
|||||||
|
|
||||||
export type RunBackupNowResponse = RunBackupNowResponses[keyof RunBackupNowResponses];
|
export type RunBackupNowResponse = RunBackupNowResponses[keyof RunBackupNowResponses];
|
||||||
|
|
||||||
|
export type GetSystemInfoData = {
|
||||||
|
body?: never;
|
||||||
|
path?: never;
|
||||||
|
query?: never;
|
||||||
|
url: "/api/v1/system/info";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetSystemInfoResponses = {
|
||||||
|
/**
|
||||||
|
* System information with enabled capabilities
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
capabilities: {
|
||||||
|
docker: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetSystemInfoResponse = GetSystemInfoResponses[keyof GetSystemInfoResponses];
|
||||||
|
|
||||||
export type ClientOptions = {
|
export type ClientOptions = {
|
||||||
baseUrl: "http://192.168.2.42:4096" | (string & {});
|
baseUrl: "http://192.168.2.42:4096" | (string & {});
|
||||||
};
|
};
|
||||||
|
|||||||
43
apps/client/app/components/ui/alert.tsx
Normal file
43
apps/client/app/components/ui/alert.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
|
||||||
|
));
|
||||||
|
Alert.displayName = "Alert";
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
|
||||||
|
),
|
||||||
|
);
|
||||||
|
AlertTitle.displayName = "AlertTitle";
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
||||||
|
),
|
||||||
|
);
|
||||||
|
AlertDescription.displayName = "AlertDescription";
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription };
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Unplug } from "lucide-react";
|
import { AlertCircle, Unplug } from "lucide-react";
|
||||||
import * as YML from "yaml";
|
import * as YML from "yaml";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { CodeBlock } from "~/components/ui/code-block";
|
import { CodeBlock } from "~/components/ui/code-block";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
||||||
import type { Volume } from "~/lib/types";
|
import type { Volume } from "~/lib/types";
|
||||||
import { getContainersUsingVolumeOptions } from "../../../api-client/@tanstack/react-query.gen";
|
import { getContainersUsingVolumeOptions, getSystemInfoOptions } from "../../../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
volume: Volume;
|
volume: Volume;
|
||||||
@@ -28,6 +29,11 @@ export const DockerTabContent = ({ volume }: Props) => {
|
|||||||
|
|
||||||
const dockerRunCommand = `docker run -v im-${volume.name}:/path/in/container nginx:latest`;
|
const dockerRunCommand = `docker run -v im-${volume.name}:/path/in/container nginx:latest`;
|
||||||
|
|
||||||
|
const { data: systemInfo } = useQuery({
|
||||||
|
...getSystemInfoOptions(),
|
||||||
|
staleTime: 60000, // Cache for 1 minute
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: containersData,
|
data: containersData,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -39,6 +45,7 @@ export const DockerTabContent = ({ volume }: Props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const containers = containersData || [];
|
const containers = containersData || [];
|
||||||
|
const dockerAvailable = systemInfo?.data?.capabilities?.docker ?? true;
|
||||||
|
|
||||||
const getStateClass = (state: string) => {
|
const getStateClass = (state: string) => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@@ -53,6 +60,28 @@ export const DockerTabContent = ({ volume }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 xl:grid-cols-[minmax(0,_1fr)_minmax(0,_1fr)]">
|
<div className="grid gap-4 xl:grid-cols-[minmax(0,_1fr)_minmax(0,_1fr)]">
|
||||||
|
{!dockerAvailable && (
|
||||||
|
<div className="xl:col-span-2">
|
||||||
|
<Alert>
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertTitle>Docker Integration Unavailable</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Docker integration features are currently disabled. To enable them, you need to mount the following paths
|
||||||
|
in your docker-compose.yml:
|
||||||
|
<ul className="mt-2 list-inside list-disc space-y-1 text-sm">
|
||||||
|
<li>
|
||||||
|
<code className="rounded bg-muted px-1 py-0.5">/var/run/docker.sock:/var/run/docker.sock</code>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code className="rounded bg-muted px-1 py-0.5">/run/docker/plugins:/run/docker/plugins</code>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-2 text-sm">After adding these mounts, restart the Ironmount container.</p>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Plug-and-play Docker integration</CardTitle>
|
<CardTitle>Plug-and-play Docker integration</CardTitle>
|
||||||
@@ -83,19 +112,31 @@ export const DockerTabContent = ({ volume }: Props) => {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Containers Using This Volume</CardTitle>
|
<CardTitle>Containers Using This Volume</CardTitle>
|
||||||
<CardDescription>List of Docker containers mounting this volume.</CardDescription>
|
<CardDescription>
|
||||||
|
{dockerAvailable
|
||||||
|
? "List of Docker containers mounting this volume."
|
||||||
|
: "Docker integration is unavailable - enable it to see container information."}
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="space-y-4 text-sm h-full">
|
<CardContent className="space-y-4 text-sm h-full">
|
||||||
{isLoading && <div>Loading containers...</div>}
|
{!dockerAvailable && (
|
||||||
{error && <div className="text-destructive">Failed to load containers: {String(error)}</div>}
|
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||||
{!isLoading && !error && containers.length === 0 && (
|
<AlertCircle className="mb-4 h-5 w-5 text-muted-foreground" />
|
||||||
|
<p className="text-muted-foreground">Docker integration is not available.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{dockerAvailable && isLoading && <div>Loading containers...</div>}
|
||||||
|
{dockerAvailable && error && (
|
||||||
|
<div className="text-destructive">Failed to load containers: {String(error)}</div>
|
||||||
|
)}
|
||||||
|
{dockerAvailable && !isLoading && !error && containers.length === 0 && (
|
||||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||||
<Unplug className="mb-4 h-5 w-5 text-muted-foreground" />
|
<Unplug className="mb-4 h-5 w-5 text-muted-foreground" />
|
||||||
<p className="text-muted-foreground">No Docker containers are currently using this volume.</p>
|
<p className="text-muted-foreground">No Docker containers are currently using this volume.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isLoading && !error && containers.length > 0 && (
|
{dockerAvailable && !isLoading && !error && containers.length > 0 && (
|
||||||
<div className="max-h-130 overflow-y-auto">
|
<div className="max-h-130 overflow-y-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ await Bun.build({
|
|||||||
outdir: "./dist",
|
outdir: "./dist",
|
||||||
target: "bun",
|
target: "bun",
|
||||||
env: "disable",
|
env: "disable",
|
||||||
// sourcemap: "linked",
|
sourcemap: true,
|
||||||
minify: {
|
minify: {
|
||||||
whitespace: true,
|
whitespace: true,
|
||||||
identifiers: true,
|
identifiers: true,
|
||||||
syntax: true,
|
syntax: true,
|
||||||
|
keepNames: true,
|
||||||
},
|
},
|
||||||
external: ["ssh2"],
|
external: ["ssh2"],
|
||||||
});
|
});
|
||||||
|
|||||||
55
apps/server/src/core/capabilities.ts
Normal file
55
apps/server/src/core/capabilities.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import * as fs from "node:fs/promises";
|
||||||
|
import Docker from "dockerode";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
export type SystemCapabilities = {
|
||||||
|
docker: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
let capabilitiesPromise: Promise<SystemCapabilities> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current system capabilities.
|
||||||
|
* On first call, detects all capabilities and caches the promise.
|
||||||
|
* Subsequent calls return the same cached promise, ensuring detection only happens once.
|
||||||
|
*/
|
||||||
|
export async function getCapabilities(): Promise<SystemCapabilities> {
|
||||||
|
if (capabilitiesPromise === null) {
|
||||||
|
// Start detection and cache the promise
|
||||||
|
capabilitiesPromise = detectCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
return capabilitiesPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects which optional capabilities are available in the current environment
|
||||||
|
*/
|
||||||
|
async function detectCapabilities(): Promise<SystemCapabilities> {
|
||||||
|
return {
|
||||||
|
docker: await detectDocker(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<boolean> {
|
||||||
|
try {
|
||||||
|
await fs.access("/var/run/docker.sock");
|
||||||
|
|
||||||
|
const docker = new Docker();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,14 @@ import { Hono } from "hono";
|
|||||||
import { serveStatic } from "hono/bun";
|
import { serveStatic } from "hono/bun";
|
||||||
import { logger as honoLogger } from "hono/logger";
|
import { logger as honoLogger } from "hono/logger";
|
||||||
import { openAPIRouteHandler } from "hono-openapi";
|
import { openAPIRouteHandler } from "hono-openapi";
|
||||||
|
import { getCapabilities } from "./core/capabilities";
|
||||||
import { runDbMigrations } from "./db/db";
|
import { runDbMigrations } from "./db/db";
|
||||||
import { authController } from "./modules/auth/auth.controller";
|
import { authController } from "./modules/auth/auth.controller";
|
||||||
import { requireAuth } from "./modules/auth/auth.middleware";
|
import { requireAuth } from "./modules/auth/auth.middleware";
|
||||||
import { driverController } from "./modules/driver/driver.controller";
|
import { driverController } from "./modules/driver/driver.controller";
|
||||||
import { startup } from "./modules/lifecycle/startup";
|
import { startup } from "./modules/lifecycle/startup";
|
||||||
import { repositoriesController } from "./modules/repositories/repositories.controller";
|
import { repositoriesController } from "./modules/repositories/repositories.controller";
|
||||||
|
import { systemController } from "./modules/system/system.controller";
|
||||||
import { volumeController } from "./modules/volumes/volume.controller";
|
import { volumeController } from "./modules/volumes/volume.controller";
|
||||||
import { backupScheduleController } from "./modules/backups/backups.controller";
|
import { backupScheduleController } from "./modules/backups/backups.controller";
|
||||||
import { handleServiceError } from "./utils/errors";
|
import { handleServiceError } from "./utils/errors";
|
||||||
@@ -41,6 +43,7 @@ const app = new Hono()
|
|||||||
.route("/api/v1/volumes", volumeController.use(requireAuth))
|
.route("/api/v1/volumes", volumeController.use(requireAuth))
|
||||||
.route("/api/v1/repositories", repositoriesController.use(requireAuth))
|
.route("/api/v1/repositories", repositoriesController.use(requireAuth))
|
||||||
.route("/api/v1/backups", backupScheduleController.use(requireAuth))
|
.route("/api/v1/backups", backupScheduleController.use(requireAuth))
|
||||||
|
.route("/api/v1/system", systemController.use(requireAuth))
|
||||||
.get("/assets/*", serveStatic({ root: "./assets/frontend" }))
|
.get("/assets/*", serveStatic({ root: "./assets/frontend" }))
|
||||||
.get("/images/*", serveStatic({ root: "./assets/frontend" }))
|
.get("/images/*", serveStatic({ root: "./assets/frontend" }))
|
||||||
.get("*", serveStatic({ path: "./assets/frontend/index.html" }));
|
.get("*", serveStatic({ path: "./assets/frontend/index.html" }));
|
||||||
@@ -60,15 +63,26 @@ app.onError((err, c) => {
|
|||||||
return c.json({ message }, status);
|
return c.json({ message }, status);
|
||||||
});
|
});
|
||||||
|
|
||||||
const socketPath = "/run/docker/plugins/ironmount.sock";
|
|
||||||
|
|
||||||
await fs.mkdir("/run/docker/plugins", { recursive: true });
|
|
||||||
runDbMigrations();
|
runDbMigrations();
|
||||||
|
|
||||||
Bun.serve({
|
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: socketPath,
|
||||||
fetch: driver.fetch,
|
fetch: driver.fetch,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logger.info(`Docker volume plugin server running at ${socketPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to start Docker volume plugin server: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Bun.serve({
|
Bun.serve({
|
||||||
port: 4096,
|
port: 4096,
|
||||||
@@ -77,6 +91,6 @@ Bun.serve({
|
|||||||
|
|
||||||
startup();
|
startup();
|
||||||
|
|
||||||
logger.info(`Server is running at http://localhost:4096 and unix socket at ${socketPath}`);
|
logger.info(`Server is running at http://localhost:4096`);
|
||||||
|
|
||||||
export type AppType = typeof app;
|
export type AppType = typeof app;
|
||||||
|
|||||||
9
apps/server/src/modules/system/system.controller.ts
Normal file
9
apps/server/src/modules/system/system.controller.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import { systemInfoDto, type SystemInfoDto } from "./system.dto";
|
||||||
|
import { systemService } from "./system.service";
|
||||||
|
|
||||||
|
export const systemController = new Hono().get("/info", systemInfoDto, async (c) => {
|
||||||
|
const info = await systemService.getSystemInfo();
|
||||||
|
|
||||||
|
return c.json<SystemInfoDto>(info, 200);
|
||||||
|
});
|
||||||
28
apps/server/src/modules/system/system.dto.ts
Normal file
28
apps/server/src/modules/system/system.dto.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { type } from "arktype";
|
||||||
|
import { describeRoute, resolver } from "hono-openapi";
|
||||||
|
|
||||||
|
export const capabilitiesSchema = type({
|
||||||
|
docker: "boolean",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const systemInfoResponse = type({
|
||||||
|
capabilities: capabilitiesSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SystemInfoDto = typeof systemInfoResponse.infer;
|
||||||
|
|
||||||
|
export const systemInfoDto = describeRoute({
|
||||||
|
description: "Get system information including available capabilities",
|
||||||
|
tags: ["System"],
|
||||||
|
operationId: "getSystemInfo",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "System information with enabled capabilities",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(systemInfoResponse),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
11
apps/server/src/modules/system/system.service.ts
Normal file
11
apps/server/src/modules/system/system.service.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { getCapabilities } from "../../core/capabilities";
|
||||||
|
|
||||||
|
const getSystemInfo = async () => {
|
||||||
|
return {
|
||||||
|
capabilities: await getCapabilities(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const systemService = {
|
||||||
|
getSystemInfo,
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import Docker from "dockerode";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
|
import { getCapabilities } from "../../core/capabilities";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
import { volumesTable } from "../../db/schema";
|
import { volumesTable } from "../../db/schema";
|
||||||
import { toMessage } from "../../utils/errors";
|
import { toMessage } from "../../utils/errors";
|
||||||
@@ -229,6 +230,13 @@ const getContainersUsingVolume = async (name: string) => {
|
|||||||
throw new NotFoundError("Volume not found");
|
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();
|
const docker = new Docker();
|
||||||
const containers = await docker.listContainers({ all: true });
|
const containers = await docker.listContainers({ all: true });
|
||||||
|
|
||||||
@@ -249,6 +257,10 @@ const getContainersUsingVolume = async (name: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { containers: usingContainers };
|
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 listFiles = async (name: string, subPath?: string) => {
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ services:
|
|||||||
target: development
|
target: development
|
||||||
container_name: ironmount
|
container_name: ironmount
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
privileged: true
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
ports:
|
ports:
|
||||||
- "4096:4097"
|
- "4096:4097"
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
# - /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /run/docker/plugins:/run/docker/plugins
|
# - /run/docker/plugins:/run/docker/plugins
|
||||||
- /proc:/host/proc:ro
|
# - /proc:/host/proc:ro
|
||||||
|
- /var/lib/repositories/:/var/lib/repositories
|
||||||
- ironmount_data:/data
|
- ironmount_data:/data
|
||||||
|
|
||||||
- ./apps/client/app:/app/apps/client/app
|
- ./apps/client/app:/app/apps/client/app
|
||||||
@@ -27,15 +29,16 @@ services:
|
|||||||
target: production
|
target: production
|
||||||
container_name: ironmount
|
container_name: ironmount
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
privileged: true
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
ports:
|
ports:
|
||||||
- "4096:4096"
|
- "4096:4096"
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
# - /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /run/docker/plugins:/run/docker/plugins
|
# - /run/docker/plugins:/run/docker/plugins
|
||||||
- /var/lib/ironmount/volumes/:/var/lib/ironmount/volumes:rslave
|
# - /var/lib/ironmount/volumes/:/var/lib/ironmount/volumes:rslave
|
||||||
|
# - /proc:/host/proc:ro
|
||||||
- /var/lib/repositories/:/var/lib/repositories
|
- /var/lib/repositories/:/var/lib/repositories
|
||||||
- /proc:/host/proc:ro
|
|
||||||
- ironmount_data:/data
|
- ironmount_data:/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Reference in New Issue
Block a user