mirror of
https://github.com/tale/headplane.git
synced 2026-08-10 05:56:52 +00:00
feat: rebuild browser ssh from the ground up
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
import { eq, isNotNull } from "drizzle-orm";
|
||||
|
||||
import { nodesResource } from "~/server/headscale/live-store";
|
||||
import log from "~/utils/log";
|
||||
|
||||
import type { Route } from "../../layout/+types/app";
|
||||
import { ephemeralNodes } from "./schema";
|
||||
|
||||
export async function pruneEphemeralNodes({ context, request }: Route.LoaderArgs) {
|
||||
const principal = await context.auth.require(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 apiKey = context.auth.getHeadscaleApiKey(principal);
|
||||
const api = context.hsApi.getRuntimeClient(apiKey);
|
||||
const nodes = await api.getNodes();
|
||||
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.debug("api", `Pruning node ${node.name}`);
|
||||
await api.deleteNode(node.id);
|
||||
|
||||
await context.db.delete(ephemeralNodes).where(eq(ephemeralNodes.node_key, node.nodeKey));
|
||||
log.debug("api", `Node ${node.name} pruned successfully`);
|
||||
};
|
||||
});
|
||||
|
||||
await Promise.all(promises.map((p) => p()));
|
||||
|
||||
if (toPrune.length > 0) {
|
||||
await context.hsLive.refresh(nodesResource, api);
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,6 @@ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
|
||||
import { HostInfo } from "~/types";
|
||||
|
||||
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;
|
||||
|
||||
export const hostInfo = sqliteTable("host_info", {
|
||||
host_id: text("host_id").primaryKey(),
|
||||
payload: text("payload", { mode: "json" }).$type<HostInfo>(),
|
||||
|
||||
+9
-16
@@ -10,7 +10,7 @@ import { HostInfo } from "~/types";
|
||||
import log from "~/utils/log";
|
||||
|
||||
import { HeadplaneConfig } from "./config/config-schema";
|
||||
import { ephemeralNodes, hostInfo } from "./db/schema";
|
||||
import { hostInfo } from "./db/schema";
|
||||
import { RuntimeApiClient } from "./headscale/api/endpoints";
|
||||
|
||||
export interface AgentManager {
|
||||
@@ -266,6 +266,10 @@ export async function createAgentManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prunes any offline nodes marked as ephemeral. This is due to a Headscale
|
||||
* bug where ephemeral nodes wouldn't be automatically removed on disconnect.
|
||||
*/
|
||||
async function pruneStaleHostInfo() {
|
||||
try {
|
||||
const nodes = await apiClient.getNodes();
|
||||
@@ -294,23 +298,12 @@ export async function createAgentManager(
|
||||
|
||||
async function pruneEphemeralNodes() {
|
||||
try {
|
||||
const rows = await db.select().from(ephemeralNodes);
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = await apiClient.getNodes();
|
||||
const activeKeys = new Set(nodes.map((n) => n.nodeKey));
|
||||
const toPrune = nodes.filter((n) => n.preAuthKey?.ephemeral && !n.online);
|
||||
|
||||
for (const row of rows) {
|
||||
if (!row.node_key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!activeKeys.has(row.node_key)) {
|
||||
await db.delete(ephemeralNodes).where(inArray(ephemeralNodes.auth_key, [row.auth_key]));
|
||||
log.info("agent", "Pruned ephemeral SSH node %s", row.node_key);
|
||||
}
|
||||
for (const node of toPrune) {
|
||||
await apiClient.deleteNode(node.id);
|
||||
log.info("agent", "Pruned offline ephemeral node %s", node.givenName);
|
||||
}
|
||||
} catch (error) {
|
||||
log.debug(
|
||||
|
||||
Reference in New Issue
Block a user