Files
2025-08-28 22:55:26 -04:00

224 lines
6.6 KiB
TypeScript

import { type } from 'arktype';
const stringToBool = type('string | boolean').pipe((v) => {
if (typeof v === 'string') {
if (v === '1' || v === 'true' || v === 'yes') {
return true;
}
if (v === '0' || v === 'false' || v === 'no') {
return false;
}
throw new Error(`Invalid string value for boolean: ${v}`);
}
return Boolean(v);
});
const serverConfig = type({
host: 'string.ip',
port: type('string | number.integer').pipe((v) => Number(v)),
data_path: 'string = "/var/lib/headplane/"',
cookie_secret: '(32 <= string <= 32)?',
cookie_secret_path: 'string?',
cookie_secure: stringToBool,
})
.narrow((obj: Record<string, unknown>, ctx: any) => {
const hasVal = obj.cookie_secret != null && `${obj.cookie_secret}` !== '';
const hasPath =
obj.cookie_secret_path != null && obj.cookie_secret_path !== '';
if (hasVal && hasPath)
return ctx.reject(
`Only one of "cookie_secret" or "cookie_secret_path" may be set.`,
);
if (!hasVal && !hasPath)
return ctx.reject(
`Either "cookie_secret" or "cookie_secret_path" must be provided for cookie_secret.`,
);
return true;
})
.onDeepUndeclaredKey('reject');
const partialServerConfig = type({
host: 'string.ip?',
port: type('string | number.integer')
.pipe((v) => Number(v))
.optional(),
data_path: 'string = "/var/lib/headplane/"',
cookie_secret: '32 <= string <= 32?',
cookie_secret_path: 'string?',
cookie_secure: stringToBool.optional(),
});
const oidcConfig = type({
issuer: 'string.url',
client_id: 'string',
client_secret: 'string?',
client_secret_path: 'string?',
token_endpoint_auth_method:
'"client_secret_basic" | "client_secret_post" | "client_secret_jwt"',
redirect_uri: 'string.url?',
user_storage_file: 'string = "/var/lib/headplane/users.json"',
disable_api_key_login: stringToBool,
headscale_api_key: 'string?',
headscale_api_key_path: 'string?',
profile_picture_source: '"oidc" | "gravatar" = "oidc"',
strict_validation: stringToBool.default(true),
scope: 'string = "openid email profile"',
extra_params: 'Record<string, string>?',
authorization_endpoint: 'string.url?',
token_endpoint: 'string.url?',
userinfo_endpoint: 'string.url?',
})
.narrow((obj: Record<string, unknown>, ctx: any) => {
const hasVal =
obj.headscale_api_key != null && `${obj.headscale_api_key}` !== '';
const hasPath =
obj.headscale_api_key_path != null && obj.headscale_api_key_path !== '';
if (hasVal && hasPath)
return ctx.reject(
`Only one of "headscale_api_key" or "headscale_api_key_path" may be set.`,
);
if (!hasVal && !hasPath)
return ctx.reject(
`Either "headscale_api_key" or "headscale_api_key_path" must be provided.`,
);
return true;
})
.onDeepUndeclaredKey('reject');
const partialOidcConfig = type({
issuer: 'string.url?',
client_id: 'string?',
client_secret: 'string?',
client_secret_path: 'string?',
token_endpoint_auth_method:
'"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
redirect_uri: 'string.url?',
user_storage_file: 'string?',
disable_api_key_login: stringToBool.optional(),
headscale_api_key: 'string?',
headscale_api_key_path: 'string?',
profile_picture_source: '("oidc" | "gravatar")?',
strict_validation: stringToBool.default(true),
scope: 'string?',
extra_params: 'Record<string, string>?',
authorization_endpoint: 'string.url?',
token_endpoint: 'string.url?',
userinfo_endpoint: 'string.url?',
});
const headscaleConfig = type({
url: type('string.url').pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v)),
tls_cert_path: 'string?',
public_url: 'string.url?',
config_path: 'string?',
config_strict: stringToBool,
dns_records_path: 'string?',
}).onDeepUndeclaredKey('reject');
const partialHeadscaleConfig = type({
url: type('string.url')
.pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v))
.optional(),
tls_cert_path: 'string?',
public_url: 'string.url?',
config_path: 'string?',
config_strict: stringToBool.optional(),
dns_records_path: 'string?',
});
const agentConfig = type({
enabled: stringToBool.default(false),
host_name: 'string = "headplane-agent"',
pre_authkey: 'string?',
pre_authkey_path: 'string?',
cache_ttl: 'number.integer = 180000',
cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
executable_path: 'string = "/usr/libexec/headplane/agent"',
work_dir: 'string = "/var/lib/headplane/agent"',
})
.narrow((obj: Record<string, unknown>, ctx: any) => {
const hasVal = obj.pre_authkey != null && `${obj.pre_authkey}` !== '';
const hasPath = obj.pre_authkey_path != null && obj.pre_authkey_path !== '';
if (hasVal && hasPath)
return ctx.reject(
`Only one of "pre_authkey" or "pre_authkey_path" may be set.`,
);
if (!hasVal && !hasPath)
return ctx.reject(
`Either "pre_authkey" or "pre_authkey_path" must be provided.`,
);
return true;
})
.onDeepUndeclaredKey('reject');
const partialAgentConfig = type({
enabled: stringToBool.default(false),
host_name: 'string = "headplane-agent"',
pre_authkey: 'string?',
pre_authkey_path: 'string?',
cache_ttl: 'number.integer = 180000',
cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
executable_path: 'string = "/usr/libexec/headplane/agent"',
work_dir: 'string = "/var/lib/headplane/agent"',
});
const dockerConfig = type({
enabled: stringToBool,
container_name: 'string = ""',
container_label: 'string = "me.tale.headplane.target=headscale"',
socket: 'string = "unix:///var/run/docker.sock"',
});
const partialDockerConfig = type({
enabled: stringToBool,
container_name: 'string | undefined',
container_label: 'string | undefined',
socket: 'string | undefined',
}).partial();
const kubernetesConfig = type({
enabled: stringToBool,
pod_name: 'string',
validate_manifest: stringToBool,
});
const procConfig = type({
enabled: stringToBool,
});
const integrationConfig = type({
'docker?': dockerConfig,
'kubernetes?': kubernetesConfig,
'proc?': procConfig,
'agent?': agentConfig,
});
const partialIntegrationConfig = type({
'docker?': partialDockerConfig,
'kubernetes?': kubernetesConfig.partial(),
'proc?': procConfig.partial(),
'agent?': partialAgentConfig,
}).partial();
export const headplaneConfig = type({
debug: stringToBool,
server: serverConfig,
'oidc?': oidcConfig,
'integration?': integrationConfig,
headscale: headscaleConfig,
}).onDeepUndeclaredKey('delete');
export const partialHeadplaneConfig = type({
debug: stringToBool,
server: partialServerConfig,
'oidc?': partialOidcConfig,
'integration?': partialIntegrationConfig,
headscale: partialHeadscaleConfig,
}).partial();
export type HeadplaneConfig = typeof headplaneConfig.infer;
export type PartialHeadplaneConfig = typeof partialHeadplaneConfig.infer;