diff --git a/.env.example b/.env.example deleted file mode 100644 index 0d0f221..0000000 --- a/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -ROOT_API_KEY=abcdefghijklmnopqrstuvwxyz -COOKIE_SECRET=abcdefghijklmnopqrstuvwxyz -DISABLE_API_KEY_LOGIN=true -HEADSCALE_CONTAINER=headscale -HOST=0.0.0.0 -PORT=3000 -CONFIG_FILE=/etc/headscale/config.yaml diff --git a/app/root.tsx b/app/root.tsx index 2a3b2b6..71ebcad 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,8 +1,4 @@ -import type { - LinksFunction, - LoaderFunctionArgs, - MetaFunction, -} from 'react-router'; +import type { LinksFunction, MetaFunction } from 'react-router'; import { Links, Meta, @@ -18,8 +14,6 @@ import ToastProvider from '~/components/ToastProvider'; import stylesheet from '~/tailwind.css?url'; import { LiveDataProvider } from '~/utils/live-data'; import { useToastQueue } from '~/utils/toast'; -import { LoadContext } from './server'; -import log from './utils/log'; export const meta: MetaFunction = () => [ { title: 'Headplane' }, diff --git a/app/routes/dns/dns-actions.ts b/app/routes/dns/dns-actions.ts index a0e658f..3655e09 100644 --- a/app/routes/dns/dns-actions.ts +++ b/app/routes/dns/dns-actions.ts @@ -1,6 +1,5 @@ import { ActionFunctionArgs, data } from 'react-router'; import { LoadContext } from '~/server'; -import { hp_getIntegration } from '~/utils/integration/loader'; export async function dnsAction({ request, @@ -51,7 +50,7 @@ async function renameTailnet(formData: FormData, context: LoadContext) { }, ]); - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } async function toggleMagic(formData: FormData, context: LoadContext) { @@ -67,7 +66,7 @@ async function toggleMagic(formData: FormData, context: LoadContext) { }, ]); - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } async function removeNs(formData: FormData, context: LoadContext) { @@ -100,7 +99,7 @@ async function removeNs(formData: FormData, context: LoadContext) { ]); } - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } async function addNs(formData: FormData, context: LoadContext) { @@ -135,7 +134,7 @@ async function addNs(formData: FormData, context: LoadContext) { ]); } - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } async function removeDomain(formData: FormData, context: LoadContext) { @@ -153,7 +152,7 @@ async function removeDomain(formData: FormData, context: LoadContext) { }, ]); - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } async function addDomain(formData: FormData, context: LoadContext) { @@ -173,7 +172,7 @@ async function addDomain(formData: FormData, context: LoadContext) { }, ]); - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } async function removeRecord(formData: FormData, context: LoadContext) { @@ -196,7 +195,7 @@ async function removeRecord(formData: FormData, context: LoadContext) { }, ]); - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } async function addRecord(formData: FormData, context: LoadContext) { @@ -219,5 +218,5 @@ async function addRecord(formData: FormData, context: LoadContext) { }, ]); - await hp_getIntegration()?.onConfigChange(); + await context.integration?.onConfigChange(context.client); } diff --git a/app/server/README.md b/app/server/README.md index a14d0b7..6cc9653 100644 --- a/app/server/README.md +++ b/app/server/README.md @@ -8,6 +8,12 @@ many side-effects (in this case, importing a module may run code). server ├── index.ts: Loads everything and starts the web server. ├── config/ +│ ├── integration/ +│ │ ├── abstract.ts: Defines the abstract class for integrations. +│ │ ├── docker.ts: Contains the Docker integration. +│ │ ├── index.ts: Determines the correct integration to use (if any). +│ │ ├── kubernetes.ts: Contains the Kubernetes integration. +│ │ ├── proc.ts: Contains the Proc integration. │ ├── env.ts: Checks the environment variables for custom overrides. │ ├── loader.ts: Checks the configuration file and coalesces with ENV. │ ├── schema.ts: Defines the schema for the Headplane configuration. diff --git a/app/utils/integration/abstract.ts b/app/server/config/integration/abstract.ts similarity index 68% rename from app/utils/integration/abstract.ts rename to app/server/config/integration/abstract.ts index c0b0ff7..98c80a1 100644 --- a/app/utils/integration/abstract.ts +++ b/app/server/config/integration/abstract.ts @@ -1,3 +1,5 @@ +import type { ApiClient } from '~/server/headscale/api-client'; + export abstract class Integration { protected context: NonNullable; constructor(context: T) { @@ -9,6 +11,6 @@ export abstract class Integration { } abstract isAvailable(): Promise | boolean; - abstract onConfigChange(): Promise | void; + abstract onConfigChange(client: ApiClient): Promise | void; abstract get name(): string; } diff --git a/app/utils/integration/docker.ts b/app/server/config/integration/docker.ts similarity index 66% rename from app/utils/integration/docker.ts rename to app/server/config/integration/docker.ts index e53be6d..e7e889f 100644 --- a/app/utils/integration/docker.ts +++ b/app/server/config/integration/docker.ts @@ -1,9 +1,9 @@ import { constants, access } from 'node:fs/promises'; import { setTimeout } from 'node:timers/promises'; import { Client } from 'undici'; -import { HeadscaleError, healthcheck, pull } from '~/utils/headscale'; -import { HeadplaneConfig } from '~server/context/parser'; -import log from '~server/utils/log'; +import { ApiClient } from '~/server/headscale/api-client'; +import log from '~/utils/log'; +import type { HeadplaneConfig } from '../schema'; import { Integration } from './abstract'; type T = NonNullable['docker']; @@ -17,21 +17,25 @@ export default class DockerIntegration extends Integration { async isAvailable() { if (this.context.container_name.length === 0) { - log.error('INTG', 'Docker container name is empty'); + log.error('config', 'Docker container name is empty'); return false; } - log.info('INTG', 'Using container: %s', this.context.container_name); + log.info('config', 'Using container: %s', this.context.container_name); let url: URL | undefined; try { url = new URL(this.context.socket); } catch { - log.error('INTG', 'Invalid Docker socket path: %s', this.context.socket); + log.error( + 'config', + 'Invalid Docker socket path: %s', + this.context.socket, + ); return false; } if (url.protocol !== 'tcp:' && url.protocol !== 'unix:') { - log.error('INTG', 'Invalid Docker socket protocol: %s', url.protocol); + log.error('config', 'Invalid Docker socket protocol: %s', url.protocol); return false; } @@ -42,11 +46,11 @@ export default class DockerIntegration extends Integration { const fetchU = url.href.replace(url.protocol, 'http:'); try { - log.info('INTG', 'Checking API: %s', fetchU); + log.info('config', 'Checking API: %s', fetchU); await fetch(new URL('/v1.30/version', fetchU).href); } catch (error) { - log.error('INTG', 'Failed to connect to Docker API: %s', error); - log.debug('INTG', 'Connection error: %o', error); + log.error('config', 'Failed to connect to Docker API: %s', error); + log.debug('config', 'Connection error: %o', error); return false; } @@ -56,11 +60,11 @@ export default class DockerIntegration extends Integration { // Check if the socket is accessible if (url.protocol === 'unix:') { try { - log.info('INTG', 'Checking socket: %s', url.pathname); + log.info('config', 'Checking socket: %s', url.pathname); await access(url.pathname, constants.R_OK); } catch (error) { - log.error('INTG', 'Failed to access Docker socket: %s', url.pathname); - log.debug('INTG', 'Access error: %o', error); + log.error('config', 'Failed to access Docker socket: %s', url.pathname); + log.debug('config', 'Access error: %o', error); return false; } @@ -72,17 +76,17 @@ export default class DockerIntegration extends Integration { return this.client !== undefined; } - async onConfigChange() { + async onConfigChange(client: ApiClient) { if (!this.client) { return; } - log.info('INTG', 'Restarting Headscale via Docker'); + log.info('config', 'Restarting Headscale via Docker'); let attempts = 0; while (attempts <= this.maxAttempts) { log.debug( - 'INTG', + 'config', 'Restarting container: %s (attempt %d)', this.context.container_name, attempts, @@ -111,19 +115,15 @@ export default class DockerIntegration extends Integration { attempts = 0; while (attempts <= this.maxAttempts) { try { - log.debug('INTG', 'Checking Headscale status (attempt %d)', attempts); - await healthcheck(); - log.info('INTG', 'Headscale is up and running'); + log.debug('config', 'Checking Headscale status (attempt %d)', attempts); + const status = await client.healthcheck(); + if (status === false) { + throw new Error('Headscale is not running'); + } + + log.info('config', 'Headscale is up and running'); return; } catch (error) { - if (error instanceof HeadscaleError && error.status === 401) { - break; - } - - if (error instanceof HeadscaleError && error.status === 404) { - break; - } - if (attempts < this.maxAttempts) { attempts++; await setTimeout(1000); @@ -131,7 +131,7 @@ export default class DockerIntegration extends Integration { } log.error( - 'INTG', + 'config', 'Missed restart deadline for %s', this.context.container_name, ); diff --git a/app/server/config/integration/index.ts b/app/server/config/integration/index.ts new file mode 100644 index 0000000..69a7ef2 --- /dev/null +++ b/app/server/config/integration/index.ts @@ -0,0 +1,62 @@ +import { HeadplaneConfig } from '~/server/config/schema'; +import log from '~/utils/log'; +import dockerIntegration from './docker'; +import kubernetesIntegration from './kubernetes'; +import procIntegration from './proc'; + +export async function loadIntegration(context: HeadplaneConfig['integration']) { + const integration = getIntegration(context); + if (!integration) { + return; + } + + try { + const res = await integration.isAvailable(); + if (!res) { + log.error('config', 'Integration %s is not available', integration); + return; + } + } catch (error) { + log.error( + 'config', + 'Failed to load integration %s: %s', + integration, + error, + ); + log.debug('config', 'Loading error: %o', error); + return; + } + + return integration; +} + +function getIntegration(integration: HeadplaneConfig['integration']) { + const docker = integration?.docker; + const k8s = integration?.kubernetes; + const proc = integration?.proc; + + if (!docker?.enabled && !k8s?.enabled && !proc?.enabled) { + log.debug('config', 'No integrations enabled'); + return; + } + + if (docker?.enabled && k8s?.enabled && proc?.enabled) { + log.error('config', 'Multiple integrations enabled, please pick one only'); + return; + } + + if (docker?.enabled) { + log.info('config', 'Using Docker integration'); + return new dockerIntegration(integration?.docker); + } + + if (k8s?.enabled) { + log.info('config', 'Using Kubernetes integration'); + return new kubernetesIntegration(integration?.kubernetes); + } + + if (proc?.enabled) { + log.info('config', 'Using Proc integration'); + return new procIntegration(integration?.proc); + } +} diff --git a/app/utils/integration/kubernetes.ts b/app/server/config/integration/kubernetes.ts similarity index 64% rename from app/utils/integration/kubernetes.ts rename to app/server/config/integration/kubernetes.ts index 6462b6d..707088c 100644 --- a/app/utils/integration/kubernetes.ts +++ b/app/server/config/integration/kubernetes.ts @@ -4,9 +4,9 @@ import { join, resolve } from 'node:path'; import { kill } from 'node:process'; import { setTimeout } from 'node:timers/promises'; import { Config, CoreV1Api, KubeConfig } from '@kubernetes/client-node'; -import { HeadscaleError, healthcheck } from '~/utils/headscale'; -import { HeadplaneConfig } from '~server/context/parser'; -import log from '~server/utils/log'; +import { ApiClient } from '~/server/headscale/api-client'; +import log from '~/utils/log'; +import { HeadplaneConfig } from '../schema'; import { Integration } from './abstract'; // TODO: Upgrade to the new CoreV1Api from @kubernetes/client-node @@ -21,16 +21,16 @@ export default class KubernetesIntegration extends Integration { async isAvailable() { if (platform() !== 'linux') { - log.error('INTG', 'Kubernetes is only available on Linux'); + log.error('config', 'Kubernetes is only available on Linux'); return false; } const svcRoot = Config.SERVICEACCOUNT_ROOT; try { - log.debug('INTG', 'Checking Kubernetes service account at %s', svcRoot); + log.debug('config', 'Checking Kubernetes service account at %s', svcRoot); const files = await readdir(svcRoot); if (files.length === 0) { - log.error('INTG', 'Kubernetes service account not found'); + log.error('config', 'Kubernetes service account not found'); return false; } @@ -41,17 +41,17 @@ export default class KubernetesIntegration extends Integration { Config.SERVICEACCOUNT_NAMESPACE_PATH, ]; - log.debug('INTG', 'Looking for %s', expectedFiles.join(', ')); + log.debug('config', 'Looking for %s', expectedFiles.join(', ')); if (!expectedFiles.every((file) => mappedFiles.has(file))) { - log.error('INTG', 'Malformed Kubernetes service account'); + log.error('config', 'Malformed Kubernetes service account'); return false; } } catch (error) { - log.error('INTG', 'Failed to access %s: %s', svcRoot, error); + log.error('config', 'Failed to access %s: %s', svcRoot, error); return false; } - log.debug('INTG', 'Reading Kubernetes service account at %s', svcRoot); + log.debug('config', 'Reading Kubernetes service account at %s', svcRoot); const namespace = await readFile( Config.SERVICEACCOUNT_NAMESPACE_PATH, 'utf8', @@ -59,39 +59,39 @@ export default class KubernetesIntegration extends Integration { // Some very ugly nesting but it's necessary if (this.context.validate_manifest === false) { - log.warn('INTG', 'Skipping strict Pod status check'); + log.warn('config', 'Skipping strict Pod status check'); } else { const pod = this.context.pod_name; if (!pod) { - log.error('INTG', 'Missing POD_NAME variable'); + log.error('config', 'Missing POD_NAME variable'); return false; } if (pod.trim().length === 0) { - log.error('INTG', 'Pod name is empty'); + log.error('config', 'Pod name is empty'); return false; } log.debug( - 'INTG', + 'config', 'Checking Kubernetes pod %s in namespace %s', pod, namespace, ); try { - log.debug('INTG', 'Attempgin to get cluster KubeConfig'); + log.debug('config', 'Attempgin to get cluster KubeConfig'); const kc = new KubeConfig(); kc.loadFromCluster(); const cluster = kc.getCurrentCluster(); if (!cluster) { - log.error('INTG', 'Malformed kubeconfig'); + log.error('config', 'Malformed kubeconfig'); return false; } log.info( - 'INTG', + 'config', 'Service account connected to %s (%s)', cluster.name, cluster.server, @@ -100,14 +100,14 @@ export default class KubernetesIntegration extends Integration { const kCoreV1Api = kc.makeApiClient(CoreV1Api); log.info( - 'INTG', + 'config', 'Checking pod %s in namespace %s (%s)', pod, namespace, kCoreV1Api.basePath, ); - log.debug('INTG', 'Reading pod info for %s', pod); + log.debug('config', 'Reading pod info for %s', pod); const { response, body } = await kCoreV1Api.readNamespacedPod( pod, namespace, @@ -115,36 +115,39 @@ export default class KubernetesIntegration extends Integration { if (response.statusCode !== 200) { log.error( - 'INTG', + 'config', 'Failed to read pod info: http %d', response.statusCode, ); return false; } - log.debug('INTG', 'Got pod info: %o', body.spec); + log.debug('config', 'Got pod info: %o', body.spec); const shared = body.spec?.shareProcessNamespace; if (shared === undefined) { - log.error('INTG', 'Pod does not have spec.shareProcessNamespace set'); + log.error( + 'config', + 'Pod does not have spec.shareProcessNamespace set', + ); return false; } if (!shared) { log.error( - 'INTG', + 'config', 'Pod has set but disabled spec.shareProcessNamespace', ); return false; } - log.info('INTG', 'Pod %s enabled shared processes', pod); + log.info('config', 'Pod %s enabled shared processes', pod); } catch (error) { - log.error('INTG', 'Failed to read pod info: %s', error); + log.error('config', 'Failed to read pod info: %s', error); return false; } } - log.debug('INTG', 'Looking for namespaced process in /proc'); + log.debug('config', 'Looking for namespaced process in /proc'); const dir = resolve('/proc'); try { const subdirs = await readdir(dir); @@ -157,13 +160,13 @@ export default class KubernetesIntegration extends Integration { const path = join('/proc', dir, 'cmdline'); try { - log.debug('INTG', 'Reading %s', path); + log.debug('config', 'Reading %s', path); const data = await readFile(path, 'utf8'); if (data.includes('headscale')) { return pid; } } catch (error) { - log.debug('INTG', 'Failed to read %s: %s', path, error); + log.debug('config', 'Failed to read %s: %s', path, error); } }); @@ -176,10 +179,10 @@ export default class KubernetesIntegration extends Integration { } } - log.debug('INTG', 'Found Headscale processes: %o', pids); + log.debug('config', 'Found Headscale processes: %o', pids); if (pids.length > 1) { log.error( - 'INTG', + 'config', 'Found %d Headscale processes: %s', pids.length, pids.join(', '), @@ -188,49 +191,45 @@ export default class KubernetesIntegration extends Integration { } if (pids.length === 0) { - log.error('INTG', 'Could not find Headscale process'); + log.error('config', 'Could not find Headscale process'); return false; } this.pid = pids[0]; - log.info('INTG', 'Found Headscale process with PID: %d', this.pid); + log.info('config', 'Found Headscale process with PID: %d', this.pid); return true; } catch { - log.error('INTG', 'Failed to read /proc'); + log.error('config', 'Failed to read /proc'); return false; } } - async onConfigChange() { + async onConfigChange(client: ApiClient) { if (!this.pid) { return; } try { - log.info('INTG', 'Sending SIGTERM to Headscale'); + log.info('config', 'Sending SIGTERM to Headscale'); kill(this.pid, 'SIGTERM'); } catch (error) { - log.error('INTG', 'Failed to send SIGTERM to Headscale: %s', error); - log.debug('INTG', 'kill(1) error: %o', error); + log.error('config', 'Failed to send SIGTERM to Headscale: %s', error); + log.debug('config', 'kill(1) error: %o', error); } await setTimeout(1000); let attempts = 0; while (attempts <= this.maxAttempts) { try { - log.debug('INTG', 'Checking Headscale status (attempt %d)', attempts); - await healthcheck(); - log.info('INTG', 'Headscale is up and running'); + log.debug('config', 'Checking Headscale status (attempt %d)', attempts); + const status = await client.healthcheck(); + if (status === false) { + throw new Error('Headscale is not running'); + } + + log.info('config', 'Headscale is up and running'); return; } catch (error) { - if (error instanceof HeadscaleError && error.status === 401) { - break; - } - - if (error instanceof HeadscaleError && error.status === 404) { - break; - } - if (attempts < this.maxAttempts) { attempts++; await setTimeout(1000); @@ -238,7 +237,7 @@ export default class KubernetesIntegration extends Integration { } log.error( - 'INTG', + 'config', 'Missed restart deadline for Headscale (pid %d)', this.pid, ); diff --git a/app/utils/integration/proc.ts b/app/server/config/integration/proc.ts similarity index 62% rename from app/utils/integration/proc.ts rename to app/server/config/integration/proc.ts index 3e9cef8..7bcaf03 100644 --- a/app/utils/integration/proc.ts +++ b/app/server/config/integration/proc.ts @@ -3,9 +3,9 @@ import { platform } from 'node:os'; import { join, resolve } from 'node:path'; import { kill } from 'node:process'; import { setTimeout } from 'node:timers/promises'; -import { HeadscaleError, healthcheck } from '~/utils/headscale'; -import { HeadplaneConfig } from '~server/context/parser'; -import log from '~server/utils/log'; +import { ApiClient } from '~/server/headscale/api-client'; +import log from '~/utils/log'; +import { HeadplaneConfig } from '../schema'; import { Integration } from './abstract'; type T = NonNullable['proc']; @@ -19,11 +19,11 @@ export default class ProcIntegration extends Integration { async isAvailable() { if (platform() !== 'linux') { - log.error('INTG', '/proc is only available on Linux'); + log.error('config', '/proc is only available on Linux'); return false; } - log.debug('INTG', 'Checking /proc for Headscale process'); + log.debug('config', 'Checking /proc for Headscale process'); const dir = resolve('/proc'); try { const subdirs = await readdir(dir); @@ -36,13 +36,13 @@ export default class ProcIntegration extends Integration { const path = join('/proc', dir, 'cmdline'); try { - log.debug('INTG', 'Reading %s', path); + log.debug('config', 'Reading %s', path); const data = await readFile(path, 'utf8'); if (data.includes('headscale')) { return pid; } } catch (error) { - log.error('INTG', 'Failed to read %s: %s', path, error); + log.error('config', 'Failed to read %s: %s', path, error); } }); @@ -55,10 +55,10 @@ export default class ProcIntegration extends Integration { } } - log.debug('INTG', 'Found Headscale processes: %o', pids); + log.debug('config', 'Found Headscale processes: %o', pids); if (pids.length > 1) { log.error( - 'INTG', + 'config', 'Found %d Headscale processes: %s', pids.length, pids.join(', '), @@ -67,49 +67,46 @@ export default class ProcIntegration extends Integration { } if (pids.length === 0) { - log.error('INTG', 'Could not find Headscale process'); + log.error('config', 'Could not find Headscale process'); return false; } this.pid = pids[0]; - log.info('INTG', 'Found Headscale process with PID: %d', this.pid); + log.info('config', 'Found Headscale process with PID: %d', this.pid); return true; } catch { - log.error('INTG', 'Failed to read /proc'); + log.error('config', 'Failed to read /proc'); return false; } } - async onConfigChange() { + async onConfigChange(client: ApiClient) { if (!this.pid) { return; } try { - log.info('INTG', 'Sending SIGTERM to Headscale'); + log.info('config', 'Sending SIGTERM to Headscale'); kill(this.pid, 'SIGTERM'); } catch (error) { - log.error('INTG', 'Failed to send SIGTERM to Headscale: %s', error); - log.debug('INTG', 'kill(1) error: %o', error); + log.error('config', 'Failed to send SIGTERM to Headscale: %s', error); + log.debug('config', 'kill(1) error: %o', error); } await setTimeout(1000); let attempts = 0; while (attempts <= this.maxAttempts) { try { - log.debug('INTG', 'Checking Headscale status (attempt %d)', attempts); - await healthcheck(); - log.info('INTG', 'Headscale is up and running'); + log.debug('config', 'Checking Headscale status (attempt %d)', attempts); + const status = await client.healthcheck(); + if (status === false) { + log.error('config', 'Headscale is not running'); + return; + } + + log.info('config', 'Headscale is up and running'); return; } catch (error) { - if (error instanceof HeadscaleError && error.status === 401) { - break; - } - - if (error instanceof HeadscaleError && error.status === 404) { - break; - } - if (attempts < this.maxAttempts) { attempts++; await setTimeout(1000); @@ -117,7 +114,7 @@ export default class ProcIntegration extends Integration { } log.error( - 'INTG', + 'config', 'Missed restart deadline for Headscale (pid %d)', this.pid, ); diff --git a/app/server/headscale/api-client.ts b/app/server/headscale/api-client.ts index b916aaa..a64346e 100644 --- a/app/server/headscale/api-client.ts +++ b/app/server/headscale/api-client.ts @@ -34,10 +34,7 @@ export class ResponseError extends Error { } } -// Represents an error that occurred during a request -// class RequestError extends Error { - -class ApiClient { +export class ApiClient { private agent: Agent; private base: string; diff --git a/app/server/index.ts b/app/server/index.ts index e3535ab..df070bf 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -4,6 +4,7 @@ import { createHonoServer } from 'react-router-hono-server/node'; import type { WebSocket } from 'ws'; import log from '~/utils/log'; import { configureConfig, configureLogger, envVariables } from './config/env'; +import { loadIntegration } from './config/integration'; import { loadConfig } from './config/loader'; import { createApiClient } from './headscale/api-client'; import { loadHeadscaleConfig } from './headscale/config-loader'; @@ -61,6 +62,7 @@ const appLoadContext = { config.server.agent.ttl, ), + integration: await loadIntegration(config.integration), oidc: config.oidc ? await createOidcClient(config.oidc) : undefined, }; diff --git a/app/utils/integration/loader.ts b/app/utils/integration/loader.ts deleted file mode 100644 index b6482bb..0000000 --- a/app/utils/integration/loader.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { HeadplaneConfig } from '~/server/config/schema'; -import log from '~/utils/log'; -import { Integration } from './abstract'; -// import dockerIntegration from './docker'; -// import kubernetesIntegration from './kubernetes'; -// import procIntegration from './proc'; - -const runtimeIntegration: Integration | undefined = undefined; - -export function hp_getIntegration() { - return runtimeIntegration; -} - -export async function hp_loadIntegration( - context: HeadplaneConfig['integration'], -) { - // const integration = getIntegration(context); - // if (!integration) { - // return; - // } - // try { - // const res = await integration.isAvailable(); - // if (!res) { - // log.error('INTG', 'Integration %s is not available', integration); - // return; - // } - // } catch (error) { - // log.error('INTG', 'Failed to load integration %s: %s', integration, error); - // log.debug('INTG', 'Loading error: %o', error); - // return; - // } - // runtimeIntegration = integration; -} - -function getIntegration(integration: HeadplaneConfig['integration']) { - const docker = integration?.docker; - const k8s = integration?.kubernetes; - const proc = integration?.proc; - - if (!docker?.enabled && !k8s?.enabled && !proc?.enabled) { - log.debug('INTG', 'No integrations enabled'); - return; - } - - if (docker?.enabled && k8s?.enabled && proc?.enabled) { - log.error('INTG', 'Multiple integrations enabled, please pick one only'); - return; - } - - // if (docker?.enabled) { - // log.info('INTG', 'Using Docker integration'); - // return new dockerIntegration(integration?.docker); - // } - - // if (k8s?.enabled) { - // log.info('INTG', 'Using Kubernetes integration'); - // return new kubernetesIntegration(integration?.kubernetes); - // } - - // if (proc?.enabled) { - // log.info('INTG', 'Using Proc integration'); - // return new procIntegration(integration?.proc); - // } -} - -// IMPORTANT THIS IS A SIDE EFFECT ON INIT -// TODO: Switch this to the new singleton system -// const context = hp_getConfig(); -// hp_loadIntegration(context.integration);