mirror of
https://github.com/tale/headplane.git
synced 2026-08-29 16:37:10 +00:00
feat: add info route
This commit is contained in:
@@ -5,6 +5,9 @@ export default [
|
|||||||
index('routes/util/redirect.ts'),
|
index('routes/util/redirect.ts'),
|
||||||
route('/healthz', 'routes/util/healthz.ts'),
|
route('/healthz', 'routes/util/healthz.ts'),
|
||||||
|
|
||||||
|
// API Routes
|
||||||
|
...prefix('/api', [route('/info', 'routes/util/info.ts')]),
|
||||||
|
|
||||||
// Authentication Routes
|
// Authentication Routes
|
||||||
route('/login', 'routes/auth/login/page.tsx'),
|
route('/login', 'routes/auth/login/page.tsx'),
|
||||||
route('/logout', 'routes/auth/logout.ts'),
|
route('/logout', 'routes/auth/logout.ts'),
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { versions } from 'node:process';
|
||||||
|
import { data } from 'react-router';
|
||||||
|
import type { Route } from './+types/info';
|
||||||
|
|
||||||
|
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||||
|
if (context.config.server.info_secret == null) {
|
||||||
|
throw data(
|
||||||
|
{
|
||||||
|
status: 'Forbidden',
|
||||||
|
},
|
||||||
|
403,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bearer = request.headers.get('Authorization') ?? '';
|
||||||
|
if (!bearer.startsWith('Bearer ')) {
|
||||||
|
throw data(
|
||||||
|
{
|
||||||
|
status: 'Unauthorized',
|
||||||
|
},
|
||||||
|
401,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = bearer.slice('Bearer '.length).trim();
|
||||||
|
if (token !== context.config.server.info_secret) {
|
||||||
|
throw data(
|
||||||
|
{
|
||||||
|
status: 'Forbidden',
|
||||||
|
},
|
||||||
|
403,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fake API key for healthcheck
|
||||||
|
const api = context.hsApi.getRuntimeClient('fake-api-key');
|
||||||
|
const healthy = await api.isHealthy();
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
status: healthy ? 'OK' : 'ERROR',
|
||||||
|
headplane_version: __VERSION__,
|
||||||
|
headscale_canonical_version: healthy ? context.hsApi.apiVersion : 'unknown',
|
||||||
|
internal_versions: {
|
||||||
|
node: versions.node,
|
||||||
|
v8: versions.v8,
|
||||||
|
uv: versions.uv,
|
||||||
|
zlib: versions.zlib,
|
||||||
|
openssl: versions.openssl,
|
||||||
|
libc: versions.libc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(body), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ const serverConfig = type({
|
|||||||
port: 'number.integer = 3000',
|
port: 'number.integer = 3000',
|
||||||
base_url: 'string.url?',
|
base_url: 'string.url?',
|
||||||
data_path: 'string.lower = "/var/lib/headplane/"',
|
data_path: 'string.lower = "/var/lib/headplane/"',
|
||||||
|
info_secret: 'string?',
|
||||||
|
|
||||||
cookie_secret: '(32 <= string <= 32)',
|
cookie_secret: '(32 <= string <= 32)',
|
||||||
cookie_secure: 'boolean = true',
|
cookie_secure: 'boolean = true',
|
||||||
@@ -29,6 +30,7 @@ const partialServerConfig = type({
|
|||||||
port: 'number.integer?',
|
port: 'number.integer?',
|
||||||
base_url: 'string.url?',
|
base_url: 'string.url?',
|
||||||
data_path: 'string.lower?',
|
data_path: 'string.lower?',
|
||||||
|
info_secret: 'string?',
|
||||||
|
|
||||||
cookie_secret: '(32 <= string <= 32)?',
|
cookie_secret: '(32 <= string <= 32)?',
|
||||||
cookie_secure: 'boolean?',
|
cookie_secure: 'boolean?',
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ server:
|
|||||||
# PLEASE ensure this directory is mounted if running in Docker.
|
# PLEASE ensure this directory is mounted if running in Docker.
|
||||||
data_path: "/var/lib/headplane"
|
data_path: "/var/lib/headplane"
|
||||||
|
|
||||||
|
# The info secret is optional and allows access to certain debug endpoints
|
||||||
|
# that may expose sensitive information about your Headplane instance.
|
||||||
|
#
|
||||||
|
# As of now, this protects the /api/info endpoint which exposes details about
|
||||||
|
# the Headplane and Headscale versions in use. In the future, more endpoints
|
||||||
|
# may be protected by this secret.
|
||||||
|
#
|
||||||
|
# If not set, these endpoints will be disabled.
|
||||||
|
# info_secret: "<change_me_to_something_secure!>"
|
||||||
|
|
||||||
# Headscale specific settings to allow Headplane to talk
|
# Headscale specific settings to allow Headplane to talk
|
||||||
# to Headscale and access deep integration features
|
# to Headscale and access deep integration features
|
||||||
headscale:
|
headscale:
|
||||||
|
|||||||
Reference in New Issue
Block a user