mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
refactor: switch from go to bun
This commit is contained in:
58
apps/server/src/controllers/driver.controller.ts
Normal file
58
apps/server/src/controllers/driver.controller.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Hono } from "hono";
|
||||
|
||||
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", (c) => {
|
||||
return c.json({
|
||||
Err: "",
|
||||
});
|
||||
})
|
||||
.post("/VolumeDriver.Remove", (c) => {
|
||||
return c.json({
|
||||
Err: "",
|
||||
});
|
||||
})
|
||||
.post("/VolumeDriver.Mount", (c) => {
|
||||
return c.json({
|
||||
Mountpoint: `/mnt/something`,
|
||||
});
|
||||
})
|
||||
.post("/VolumeDriver.Unmount", (c) => {
|
||||
return c.json({
|
||||
Err: "",
|
||||
});
|
||||
})
|
||||
.post("/VolumeDriver.Path", (c) => {
|
||||
return c.json({
|
||||
Mountpoint: `/mnt/something`,
|
||||
});
|
||||
})
|
||||
.post("/VolumeDriver.Get", (c) => {
|
||||
return c.json({
|
||||
Name: "my-volume",
|
||||
Mountpoint: `/mnt/something`,
|
||||
Status: {},
|
||||
});
|
||||
})
|
||||
.post("/VolumeDriver.List", (c) => {
|
||||
return c.json({
|
||||
Volumes: [
|
||||
{
|
||||
Name: "my-volume",
|
||||
Mountpoint: `/mnt/something`,
|
||||
Status: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
31
apps/server/src/controllers/volume.controller.ts
Normal file
31
apps/server/src/controllers/volume.controller.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { type } from "arktype";
|
||||
import { Hono } from "hono";
|
||||
import {
|
||||
listVolumesDescriptor,
|
||||
listVolumesResponse,
|
||||
} from "../descriptors/volume.descriptors";
|
||||
|
||||
export const volumeController = new Hono()
|
||||
.get("/", listVolumesDescriptor, (c) => {
|
||||
const res = listVolumesResponse({
|
||||
volumes: [],
|
||||
});
|
||||
|
||||
if (res instanceof type.errors) {
|
||||
return c.json({ error: "Invalid response format" }, 500);
|
||||
}
|
||||
|
||||
return c.json(res, 200);
|
||||
})
|
||||
.post("/", (c) => {
|
||||
return c.json({ message: "Create a new volume" }, 201);
|
||||
})
|
||||
.get("/:name", (c) => {
|
||||
return c.json({ message: `Details of volume ${c.req.param("name")}` });
|
||||
})
|
||||
.put("/:name", (c) => {
|
||||
return c.json({ message: `Update volume ${c.req.param("name")}` });
|
||||
})
|
||||
.delete("/:name", (c) => {
|
||||
return c.json({ message: `Delete volume ${c.req.param("name")}` });
|
||||
});
|
||||
25
apps/server/src/core/config.ts
Normal file
25
apps/server/src/core/config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { type } from "arktype";
|
||||
import "dotenv/config";
|
||||
|
||||
const envSchema = type({
|
||||
NODE_ENV: type
|
||||
.enumerated("development", "production", "test")
|
||||
.default("development"),
|
||||
DB_FILE_NAME: "string",
|
||||
}).pipe((s) => ({
|
||||
__prod__: s.NODE_ENV === "production",
|
||||
environment: s.NODE_ENV,
|
||||
dbFileName: s.DB_FILE_NAME,
|
||||
}));
|
||||
|
||||
const parseConfig = (env: unknown) => {
|
||||
const result = envSchema(env);
|
||||
|
||||
if (result instanceof type.errors) {
|
||||
throw new Error(`Invalid environment variables: ${result.toString()}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const config = parseConfig(process.env);
|
||||
14
apps/server/src/db/db.ts
Normal file
14
apps/server/src/db/db.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import "dotenv/config";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
||||
import { config } from "../core/config";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const sqlite = new Database(config.dbFileName);
|
||||
|
||||
export const db = drizzle({ client: sqlite, schema });
|
||||
|
||||
export const runDbMigrations = () => {
|
||||
migrate(db, { migrationsFolder: "./drizzle" });
|
||||
};
|
||||
35
apps/server/src/db/schema.ts
Normal file
35
apps/server/src/db/schema.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { type } from "arktype";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
|
||||
const nfsConfigSchema = type({
|
||||
backend: "'nfs'",
|
||||
server: "string",
|
||||
exportPath: "string",
|
||||
port: "number",
|
||||
version: type.enumerated(["3", "4"]),
|
||||
});
|
||||
|
||||
const smbConfigSchema = type({
|
||||
backend: "'smb'",
|
||||
});
|
||||
|
||||
const directoryConfigSchema = type({
|
||||
backend: "'directory'",
|
||||
});
|
||||
|
||||
const configSchema = nfsConfigSchema
|
||||
.or(smbConfigSchema)
|
||||
.or(directoryConfigSchema);
|
||||
|
||||
export const volumesTable = sqliteTable("volumes_table", {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
name: text().notNull().unique(),
|
||||
path: text().notNull(),
|
||||
type: text().notNull(),
|
||||
createdAt: int("created_at").notNull().default(sql`(current_timestamp)`),
|
||||
updatedAt: int("updated_at").notNull().default(sql`(current_timestamp)`),
|
||||
config: text("config", { mode: "json" })
|
||||
.$type<typeof configSchema.inferOut>()
|
||||
.notNull(),
|
||||
});
|
||||
23
apps/server/src/descriptors/general.descriptors.ts
Normal file
23
apps/server/src/descriptors/general.descriptors.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Scalar } from "@scalar/hono-api-reference";
|
||||
import type { Hono } from "hono";
|
||||
import { openAPISpecs } from "hono-openapi";
|
||||
|
||||
export const generalDescriptor = (app: Hono) =>
|
||||
openAPISpecs(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
title: "Ironmount API",
|
||||
version: "1.0.0",
|
||||
description: "API for managing Docker volumes",
|
||||
},
|
||||
servers: [
|
||||
{ url: "http://localhost:3000", description: "Development Server" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const scalarDescriptor = Scalar({
|
||||
title: "Ironmount API Docs",
|
||||
pageTitle: "Ironmount API Docs",
|
||||
url: "/api/v1/openapi.json",
|
||||
});
|
||||
26
apps/server/src/descriptors/volume.descriptors.ts
Normal file
26
apps/server/src/descriptors/volume.descriptors.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { type } from "arktype";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { resolver } from "hono-openapi/arktype";
|
||||
|
||||
export const listVolumesResponse = type({
|
||||
volumes: type({
|
||||
name: "string",
|
||||
mountpoint: "string",
|
||||
createdAt: "string",
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export const listVolumesDescriptor = describeRoute({
|
||||
description: "List all volumes",
|
||||
tags: ["Volumes"],
|
||||
responses: {
|
||||
200: {
|
||||
description: "A list of volumes",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(listVolumesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
48
apps/server/src/index.ts
Normal file
48
apps/server/src/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import { Hono } from "hono";
|
||||
import { logger } from "hono/logger";
|
||||
import { driverController } from "./controllers/driver.controller";
|
||||
import { volumeController } from "./controllers/volume.controller";
|
||||
import { runDbMigrations } from "./db/db";
|
||||
import {
|
||||
generalDescriptor,
|
||||
scalarDescriptor,
|
||||
} from "./descriptors/general.descriptors";
|
||||
|
||||
const driver = new Hono().use(logger()).route("/", driverController);
|
||||
const app = new Hono()
|
||||
.use(logger())
|
||||
.get("healthcheck", (c) => c.json({ status: "ok" }))
|
||||
.basePath("/api/v1")
|
||||
.route("/volumes", volumeController);
|
||||
|
||||
app.get("/openapi.json", generalDescriptor(app));
|
||||
app.get("/docs", scalarDescriptor);
|
||||
|
||||
app.get("/", (c) => {
|
||||
return c.json({ message: "Welcome to the Ironmount API" });
|
||||
});
|
||||
|
||||
const socketPath = "/run/docker/plugins/ironmount.sock";
|
||||
|
||||
(async () => {
|
||||
await fs.mkdir("/run/docker/plugins", { recursive: true });
|
||||
|
||||
runDbMigrations();
|
||||
|
||||
Bun.serve({
|
||||
unix: socketPath,
|
||||
fetch: driver.fetch,
|
||||
});
|
||||
|
||||
Bun.serve({
|
||||
port: 8080,
|
||||
fetch: app.fetch,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Server is running at http://localhost:8080 and unix socket at ${socketPath}`,
|
||||
);
|
||||
})();
|
||||
|
||||
export type AppType = typeof app;
|
||||
Reference in New Issue
Block a user