refactor: switch from go to bun

This commit is contained in:
Nicolas Meienberger
2025-08-31 17:32:00 +02:00
parent a0be690eb9
commit a16fc37b44
76 changed files with 4283 additions and 4173 deletions

View 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: {},
},
],
});
});

View 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")}` });
});

View 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
View 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" });
};

View 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(),
});

View 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",
});

View 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
View 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;