feat: configure support for generic integrations

Co-authored-by: Gage Orsburn <gageorsburn@live.com>
This commit is contained in:
Aarnav Tale
2024-05-26 19:23:24 -04:00
parent 1a30185047
commit f0e4868252
10 changed files with 289 additions and 148 deletions
+4 -54
View File
@@ -8,11 +8,14 @@ import { resolve } from 'node:path'
import { parse } from 'yaml'
import { checkIntegration, Integration } from '~/integration'
import { HeadscaleConfig, loadConfig } from './headscale'
export interface HeadplaneContext {
headscaleUrl: string
cookieSecret: string
integration: Integration | undefined
config: {
read: boolean
@@ -24,12 +27,6 @@ export interface HeadplaneContext {
write: boolean
}
docker?: {
url: string
sock: boolean
container: string
}
oidc?: {
issuer: string
client: string
@@ -74,9 +71,9 @@ export async function loadContext(): Promise<HeadplaneContext> {
context = {
headscaleUrl,
cookieSecret,
integration: await checkIntegration(),
config: await checkConfig(path, config),
acl: await checkAcl(config),
docker: await checkDocker(),
oidc: await checkOidc(config),
}
@@ -163,53 +160,6 @@ async function checkAcl(config?: HeadscaleConfig) {
}
}
async function checkDocker() {
const path = process.env.DOCKER_SOCK ?? 'unix:///var/run/docker.sock'
let url: URL | undefined
try {
url = new URL(path)
} catch {
return
}
// The API is available as an HTTP endpoint
if (url.protocol === 'tcp:') {
url.protocol = 'http:'
}
// Check if the socket is accessible
if (url.protocol === 'unix:') {
try {
await access(path, constants.R_OK)
} catch {
return
}
}
if (url.protocol === 'http:') {
try {
await fetch(new URL('/v1.30/version', url).href)
} catch {
return
}
}
if (url.protocol !== 'http:' && url.protocol !== 'unix:') {
return
}
if (!process.env.HEADSCALE_CONTAINER) {
return
}
return {
url: url.href,
sock: url.protocol === 'unix:',
container: process.env.HEADSCALE_CONTAINER,
}
}
async function checkOidc(config?: HeadscaleConfig) {
const disableKeyLogin = process.env.DISABLE_API_KEY_LOGIN === 'true'
const rootKey = process.env.ROOT_API_KEY ?? process.env.API_KEY
-76
View File
@@ -1,76 +0,0 @@
import { setTimeout } from 'node:timers/promises'
import { Client } from 'undici'
import { loadContext } from './config/headplane'
import { HeadscaleError, pull } from './headscale'
export async function sighupHeadscale() {
const context = await loadContext()
if (!context.docker) {
return
}
// Supports the DOCKER_SOCK environment variable
const client = context.docker.sock
? new Client('http://localhost', {
socketPath: context.docker.url,
})
: new Client(context.docker.url)
const response = await client.request({
method: 'POST',
path: `/v1.30/containers/${context.docker.container}/kill?signal=SIGHUP`,
})
if (!response.statusCode || response.statusCode !== 204) {
throw new Error('Failed to send SIGHUP to Headscale')
}
}
export async function restartHeadscale() {
const context = await loadContext()
if (!context.docker) {
return
}
// Supports the DOCKER_SOCK environment variable
const client = context.docker.sock
? new Client('http://localhost', {
socketPath: context.docker.url,
})
: new Client(context.docker.url)
const response = await client.request({
method: 'POST',
path: `/v1.30/containers/${context.docker.container}/restart`,
})
if (!response.statusCode || response.statusCode !== 204) {
throw new Error('Failed to restart Headscale')
}
// Wait for Headscale to restart before continuing
let attempts = 0
// eslint-disable-next-line
while (true) {
try {
// Acceptable blank because ROOT_API_KEY is not required
await pull('v1/apikey', context.oidc?.rootKey ?? '')
return
} catch (error) {
// This means the server is up but the API key is invalid
// This can happen if the user only uses ROOT_API_KEY via cookies
if (error instanceof HeadscaleError && error.status === 401) {
break
}
if (attempts > 10) {
throw new Error('Headscale did not restart in time')
}
attempts++
await setTimeout(1000)
}
}
}