feat: schéma Drizzle/SQLite (machines, snapshots, executions)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 20:58:50 +02:00
parent feb136ffc1
commit 5aa4acdf87
7 changed files with 396 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./server/db/schema.ts",
out: "./server/db/migrations",
});
+15
View File
@@ -0,0 +1,15 @@
// server/db/client.ts
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { mkdirSync } from "node:fs";
import { dirname } from "node:path";
import { env } from "../env.js";
import * as schema from "./schema.js";
mkdirSync(dirname(env.dbPath), { recursive: true });
const sqlite = new Database(env.dbPath);
sqlite.pragma("journal_mode = WAL");
sqlite.pragma("foreign_keys = ON");
export const db = drizzle(sqlite, { schema });
export { schema };
+7
View File
@@ -0,0 +1,7 @@
// server/db/migrate.ts
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { db } from "./client.js";
export function runMigrations(): void {
migrate(db, { migrationsFolder: "./server/db/migrations" });
}
@@ -0,0 +1,38 @@
CREATE TABLE `executions` (
`id` text PRIMARY KEY NOT NULL,
`machine_id` text NOT NULL,
`action` text NOT NULL,
`mode` text DEFAULT 'manual' NOT NULL,
`started_at` text NOT NULL,
`finished_at` text,
`status` text NOT NULL,
`result_json` text,
`report_path` text,
`raw_log_path` text,
FOREIGN KEY (`machine_id`) REFERENCES `machines`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `machines` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`hostname` text NOT NULL,
`port` integer DEFAULT 22 NOT NULL,
`os_family` text DEFAULT 'unknown' NOT NULL,
`username` text NOT NULL,
`enc_password` text NOT NULL,
`enc_sudo_password` text,
`apt_proxy_mode` text DEFAULT 'direct' NOT NULL,
`apt_proxy_url` text,
`status` text DEFAULT 'unknown' NOT NULL,
`last_checked_at` text,
`created_at` text NOT NULL
);
--> statement-breakpoint
CREATE TABLE `snapshots` (
`id` text PRIMARY KEY NOT NULL,
`machine_id` text NOT NULL,
`checked_at` text NOT NULL,
`status` text NOT NULL,
`payload_json` text NOT NULL,
FOREIGN KEY (`machine_id`) REFERENCES `machines`(`id`) ON UPDATE no action ON DELETE cascade
);
@@ -0,0 +1,277 @@
{
"version": "6",
"dialect": "sqlite",
"id": "6aec3f17-e17f-4e7c-950c-c11592a58541",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"executions": {
"name": "executions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"machine_id": {
"name": "machine_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"action": {
"name": "action",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"mode": {
"name": "mode",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'manual'"
},
"started_at": {
"name": "started_at",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"finished_at": {
"name": "finished_at",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"result_json": {
"name": "result_json",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"report_path": {
"name": "report_path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"raw_log_path": {
"name": "raw_log_path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"executions_machine_id_machines_id_fk": {
"name": "executions_machine_id_machines_id_fk",
"tableFrom": "executions",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"machines": {
"name": "machines",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"hostname": {
"name": "hostname",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"port": {
"name": "port",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 22
},
"os_family": {
"name": "os_family",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'unknown'"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"enc_password": {
"name": "enc_password",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"enc_sudo_password": {
"name": "enc_sudo_password",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"apt_proxy_mode": {
"name": "apt_proxy_mode",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'direct'"
},
"apt_proxy_url": {
"name": "apt_proxy_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'unknown'"
},
"last_checked_at": {
"name": "last_checked_at",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"snapshots": {
"name": "snapshots",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"machine_id": {
"name": "machine_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"checked_at": {
"name": "checked_at",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"payload_json": {
"name": "payload_json",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"snapshots_machine_id_machines_id_fk": {
"name": "snapshots_machine_id_machines_id_fk",
"tableFrom": "snapshots",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1780599514478,
"tag": "0000_brainy_dakota_north",
"breakpoints": true
}
]
}
+39
View File
@@ -0,0 +1,39 @@
// server/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
export const machines = sqliteTable("machines", {
id: text("id").primaryKey(),
name: text("name").notNull(),
hostname: text("hostname").notNull(),
port: integer("port").notNull().default(22),
osFamily: text("os_family").notNull().default("unknown"),
username: text("username").notNull(),
encPassword: text("enc_password").notNull(),
encSudoPassword: text("enc_sudo_password"),
aptProxyMode: text("apt_proxy_mode").notNull().default("direct"),
aptProxyUrl: text("apt_proxy_url"),
status: text("status").notNull().default("unknown"),
lastCheckedAt: text("last_checked_at"),
createdAt: text("created_at").notNull(),
});
export const snapshots = sqliteTable("snapshots", {
id: text("id").primaryKey(),
machineId: text("machine_id").notNull().references(() => machines.id, { onDelete: "cascade" }),
checkedAt: text("checked_at").notNull(),
status: text("status").notNull(),
payloadJson: text("payload_json").notNull(),
});
export const executions = sqliteTable("executions", {
id: text("id").primaryKey(),
machineId: text("machine_id").notNull().references(() => machines.id, { onDelete: "cascade" }),
action: text("action").notNull(),
mode: text("mode").notNull().default("manual"),
startedAt: text("started_at").notNull(),
finishedAt: text("finished_at"),
status: text("status").notNull(),
resultJson: text("result_json"),
reportPath: text("report_path"),
rawLogPath: text("raw_log_path"),
});