mirror of
https://github.com/tale/headplane.git
synced 2026-08-12 22:56:53 +00:00
feat: cleanup removal of old ssh plexer and logic
we also have added the necessary logic to auto prune ephemeral nodes because headscale doesn't seem to automatically remove them. this change made use of a database which is now stored in the persistent headplane directory.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import { dirname } from 'node:path';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
||||
import log from '~/utils/log';
|
||||
|
||||
export async function createDbClient(path: string) {
|
||||
try {
|
||||
await mkdir(dirname(path), { recursive: true });
|
||||
} catch (error) {
|
||||
log.error(
|
||||
'server',
|
||||
'Failed to create directory for database at %s: %s',
|
||||
path,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
throw new Error(`Could not create directory for database at ${path}`);
|
||||
}
|
||||
|
||||
const db = drizzle(path);
|
||||
migrate(db, {
|
||||
migrationsFolder: './drizzle',
|
||||
});
|
||||
|
||||
return db;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { eq, isNotNull } from 'drizzle-orm';
|
||||
import { LoaderFunctionArgs } from 'react-router';
|
||||
import { Machine } from '~/types';
|
||||
import log from '~/utils/log';
|
||||
import { LoadContext } from '..';
|
||||
import { ephemeralNodes } from './schema';
|
||||
|
||||
export async function pruneEphemeralNodes({
|
||||
context,
|
||||
request,
|
||||
}: LoaderFunctionArgs<LoadContext>) {
|
||||
const session = await context.sessions.auth(request);
|
||||
const ephemerals = await context.db
|
||||
.select()
|
||||
.from(ephemeralNodes)
|
||||
.where(isNotNull(ephemeralNodes.node_key));
|
||||
|
||||
if (ephemerals.length === 0) {
|
||||
log.debug('api', 'No ephemeral nodes to prune');
|
||||
return;
|
||||
}
|
||||
|
||||
const { nodes } = await context.client.get<{ nodes: Machine[] }>(
|
||||
'v1/node',
|
||||
session.get('api_key')!,
|
||||
);
|
||||
|
||||
const toPrune = nodes.filter((node) => {
|
||||
if (node.online) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ephemerals.some((ephemeral) => node.nodeKey === ephemeral.node_key);
|
||||
});
|
||||
|
||||
if (toPrune.length === 0) {
|
||||
log.debug('api', 'No SSH nodes to prune');
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete from the Headscale nodes list and then from the database
|
||||
const promises = toPrune.map((node) => {
|
||||
return async () => {
|
||||
log.info('api', `Pruning node ${node.name}`);
|
||||
await context.client.delete(
|
||||
`v1/node/${node.id}`,
|
||||
session.get('api_key')!,
|
||||
);
|
||||
|
||||
await context.db
|
||||
.delete(ephemeralNodes)
|
||||
.where(eq(ephemeralNodes.node_key, node.nodeKey));
|
||||
log.info('api', `Node ${node.name} pruned successfully`);
|
||||
};
|
||||
});
|
||||
|
||||
await Promise.all(promises.map((p) => p()));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const ephemeralNodes = sqliteTable('ephemeral_nodes', {
|
||||
auth_key: text('auth_key').primaryKey(),
|
||||
node_key: text('node_key'),
|
||||
});
|
||||
|
||||
export type EphemeralNode = typeof ephemeralNodes.$inferSelect;
|
||||
export type EphemeralNodeInsert = typeof ephemeralNodes.$inferInsert;
|
||||
Reference in New Issue
Block a user