diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ee1a804 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +### 0.1.2 (May 1, 2024) + +- Added support for renaming, expiring, removing, and managing the routes of a machine. +- Implemented an expiry check for machines which now reflect on the machine table. +- Fixed an issue where `HEADSCALE_CONTAINER` was needed to start even without the Docker integration. +- Removed the requirement for the root `API_KEY` unless OIDC was being used for authentication. +- Switched to [React Aria](https://react-spectrum.adobe.com/react-aria/) for better accessibility support. +- Cleaned up various different UI inconsistencies and copied components that could've been abstracted. +- Added a changelog for any new versions going forward. diff --git a/app/root.tsx b/app/root.tsx index 1c736d2..c5b9849 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -33,14 +33,6 @@ export async function loader() { throw new Error('The COOKIE_SECRET environment variable is required') } - if (!process.env.API_KEY) { - throw new Error('The API_KEY environment variable is required') - } - - if (!process.env.HEADSCALE_CONTAINER) { - throw new Error('The HEADSCALE_CONTAINER environment variable is required') - } - // eslint-disable-next-line unicorn/no-null return null } diff --git a/app/utils/config.ts b/app/utils/config.ts index 34781e4..341dbc7 100644 --- a/app/utils/config.ts +++ b/app/utils/config.ts @@ -233,6 +233,7 @@ async function getOidcConfig() { let issuer = process.env.OIDC_ISSUER let client = process.env.OIDC_CLIENT_ID let secret = process.env.OIDC_CLIENT_SECRET + const rootKey = process.env.API_KEY if (!issuer || !client || !secret) { const config = await getConfig() @@ -250,6 +251,10 @@ async function getOidcConfig() { return } + if (!rootKey) { + throw new Error('Cannot use OIDC without the root API_KEY variable set') + } + return { issuer, client, secret } } diff --git a/app/utils/docker.ts b/app/utils/docker.ts index af805f5..b527bf9 100644 --- a/app/utils/docker.ts +++ b/app/utils/docker.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ + /* eslint-disable no-await-in-loop */ /* eslint-disable no-constant-condition */ import { setTimeout } from 'node:timers/promises' @@ -6,7 +6,7 @@ import { setTimeout } from 'node:timers/promises' import { Client } from 'undici' import { getContext } from './config' -import { pull } from './headscale' +import { HeadscaleError, pull } from './headscale' export async function sighupHeadscale() { const context = await getContext() @@ -61,9 +61,16 @@ export async function restartHeadscale() { let attempts = 0 while (true) { try { - await pull('v1/apikey', process.env.API_KEY!) + // Acceptable blank because API_KEY is not required + await pull('v1/apikey', process.env.API_KEY ?? '') return - } catch { + } catch (error) { + // This means the server is up but the API key is invalid + // This can happen if the user only uses API_KEY via cookies + if (error instanceof HeadscaleError && error.status === 401) { + break + } + if (attempts > 10) { throw new Error('Headscale did not restart in time') } diff --git a/app/utils/headscale.ts b/app/utils/headscale.ts index 13d6c7a..d636305 100644 --- a/app/utils/headscale.ts +++ b/app/utils/headscale.ts @@ -12,7 +12,7 @@ export class HeadscaleError extends Error { export class FatalError extends Error { constructor() { - super('The Headscale server is not accessible or the API_KEY is invalid.') + super('The Headscale server is not accessible or the supplied API key is invalid') this.name = 'FatalError' } } diff --git a/docs/Advanced-Integration.md b/docs/Advanced-Integration.md index 1cc883e..5576b02 100644 --- a/docs/Advanced-Integration.md +++ b/docs/Advanced-Integration.md @@ -68,9 +68,8 @@ services: ports: - '3000:3000' environment: - # These are always required for Headplane to work + # This is always required for Headplane to work COOKIE_SECRET: 'abcdefghijklmnopqrstuvwxyz' - API_KEY: 'abcdefghijklmnopqrstuvwxyz' HEADSCALE_CONTAINER: 'headscale' DISABLE_API_KEY_LOGIN: 'true' @@ -82,6 +81,11 @@ services: OIDC_CLIENT_ID: 'headscale' OIDC_ISSUER: 'https://sso.example.com' OIDC_CLIENT_SECRET: 'super_secret_client_secret' + + # This NEEDS to be set with OIDC, regardless of what's in the config + # This needs to be a very long-lived (999 day) API key used to create + # shorter ones for OIDC and allow the OIDC functionality to work + API_KEY: 'abcdefghijklmnopqrstuvwxyz' ``` > For a breakdown of each configuration variable, please refer to the [Configuration](/docs/Configuration.md) guide. diff --git a/docs/Basic-Integration.md b/docs/Basic-Integration.md index 7514554..1cc745b 100644 --- a/docs/Basic-Integration.md +++ b/docs/Basic-Integration.md @@ -28,13 +28,17 @@ services: - '3000:3000' environment: HEADSCALE_URL: 'http://headscale:8080' - API_KEY: 'abcdefghijklmnopqrstuvwxyz' COOKIE_SECRET: 'abcdefghijklmnopqrstuvwxyz' + + # These are all optional! HEADSCALE_CONTAINER: 'headscale' + API_KEY: 'abcdefghijklmnopqrstuvwxyz' OIDC_CLIENT_ID: 'headscale' OIDC_ISSUER: 'https://sso.example.com' OIDC_CLIENT_SECRET: 'super_secret_client_secret' DISABLE_API_KEY_LOGIN: 'true' + + # These are the default values HOST: '0.0.0.0' PORT: '3000' ``` diff --git a/docs/Configuration.md b/docs/Configuration.md index 7ba0876..79c6c1c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -4,9 +4,8 @@ You can configure Headplane using environment variables. #### Required Variables -- **`HEADSCALE_URL`**: The public URL of your Headscale server. -- **`API_KEY`**: An API key used to issue new ones for sessions (keep expiry fairly long). - **`COOKIE_SECRET`**: A secret used to sign cookies (use a relatively long and random string). +- **`HEADSCALE_URL`**: The public URL of your Headscale server (not required if using the configuration file). #### Optional Variables @@ -24,6 +23,7 @@ If you use the Headscale configuration integration, these are not required. - **`OIDC_ISSUER`**: The issuer URL of your OIDC provider. - **`OIDC_CLIENT_ID`**: The client ID of your OIDC provider. - **`OIDC_CLIENT_SECRET`**: The client secret of your OIDC provider. +- **`API_KEY`**: An API key used to issue new ones for sessions (keep expiry fairly long). - **`DISABLE_API_KEY_LOGIN`**: If you want to disable API key login, set this to `true`. Here's what an example with Authelia would look like if you used the same client for both Headscale and Headplane.