add tailscale node delete!
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-08-07 10:23:44 +09:00
parent 46da0a3f37
commit c90e8d5745
2 changed files with 29 additions and 1 deletions
+10
View File
@@ -20,6 +20,16 @@ adminRoutes.get('/admin/tailscale/nodes', async (c) => {
})
})
adminRoutes.delete('/admin/tailscale/nodes/:nodeId', async (c) => {
const nodeId = c.req.param('nodeId').trim()
if (!nodeId) {
return c.json({ error: 'node_id_required' }, 400)
}
await headscaleNodeDiscovery.deleteNode(nodeId)
return c.json({ deleted: true, nodeId })
})
adminRoutes.get('/admin/tailscale/routes', async (c) => {
return c.json({
routes: await headscaleNodeDiscovery.listRouteStatus(),
+19 -1
View File
@@ -71,6 +71,23 @@ class HeadscaleNodeDiscovery {
})
}
async deleteNode(nodeId: string): Promise<void> {
if (!config.headscaleApiKey) {
throw new Error('HEADSCALE_API_KEY is not configured')
}
const encodedNodeId = encodeURIComponent(nodeId)
let response = await this.fetchHeadscale(`/api/v1/node/${encodedNodeId}`, { method: 'DELETE' })
if (response.status === 404) {
response = await this.fetchHeadscale(`/api/v1/machine/${encodedNodeId}`, { method: 'DELETE' })
}
if (!response.ok) {
const detail = await response.text().catch(() => '')
throw new Error(`Headscale node deletion failed (${response.status})${detail ? `: ${detail}` : ''}`)
}
}
async listRouteStatus(): Promise<HeadscaleRouteStatus[]> {
const nodes = await this.listNodes()
return nodes.flatMap((node) => {
@@ -141,9 +158,10 @@ class HeadscaleNodeDiscovery {
return this.extractNodeList(await response.json().catch(() => null))
}
private fetchHeadscale(path: string) {
private fetchHeadscale(path: string, init?: RequestInit) {
return fetch(`${config.headscaleBaseUrl}${path}`, {
headers: { authorization: `Bearer ${config.headscaleApiKey}` },
...init,
})
}