Compare commits

...

8 Commits

Author SHA1 Message Date
Aarnav Tale 41ca4c40ad chore: v0.5.3 2025-03-01 10:37:18 -05:00
Aarnav Tale f7c9a14e92 chore: temporarily halt full form validation 2025-03-01 10:36:58 -05:00
Aarnav Tale 6e514a9064 fix: handle oidc scope types correctly 2025-03-01 10:36:58 -05:00
Aarnav Tale 631ea15895 docs: warn on breaking change 2025-03-01 10:36:58 -05:00
nerdlich 762bc6a793 fix: cosmetic docs/config fixes (#112)
* fix config path

* fix typo
2025-03-01 10:30:15 -05:00
Aarnav Tale 6c83e96341 chore: v0.5.2 2025-02-28 13:07:14 -05:00
Aarnav Tale 03e6a26b3a fix: don't reload the prod bundle on refresh 2025-02-28 13:06:38 -05:00
Aarnav Tale 5b716ab5ce chore: update docs to reflect 0.5.1 2025-02-28 12:22:20 -05:00
10 changed files with 80 additions and 16 deletions
+12
View File
@@ -1,9 +1,21 @@
### 0.5.3 (March 1, 2025)
- Fixed an issue where Headplane expected the incorrect config value for OIDC scope (fixes [#111](https://github.com/tale/headplane/issues/111))
- Added an ARIA indicator for when an input is required and fixed the confirm buttons (fixed [#116](https://github.com/tale/headplane/issues/116))
- Fixed a typo in the docs that defaulted to `/var/run/docker.dock` for the Docker socket (via [#112](https://github.com/tale/headplane/pull/112))
### 0.5.2 (February 28, 2025)
- Hotfixed an issue where the server bundle got reloaded on each request
### 0.5.1 (February 28, 2025)
- Fixed an issue that caused the entire server to crash on start
- Fixed the published semver tags from Docker
- Fixed the Kubernetes integration not reading the config
### 0.5 (February 27, 2025)
> This release is a major overhaul and contains a significant breaking change.
> We now use a config file for all settings instead of environment variables.
> Please see [config.example.yaml](/config.example.yaml) for the new format.
- Completely redesigned the UI from the ground up for accessibility and performance.
- Switched to a config-file setup (this introduces breaking changes, see [config.example.yaml](/config.example.yaml) for the new format).
- If the config is read-only, the options are still visible, just disabled (fixes [#48](https://github.com/tale/headplane/issues/48))
+1 -3
View File
@@ -122,9 +122,7 @@ function Panel(props: DialogPanelProps) {
<Button
type="submit"
variant={variant === 'destructive' ? 'danger' : 'heavy'}
isDisabled={
isDisabled || !(ref.current?.checkValidity() ?? true)
}
isDisabled={isDisabled}
>
Confirm
</Button>
+5
View File
@@ -1,3 +1,4 @@
import { Asterisk } from 'lucide-react';
import { useRef } from 'react';
import { type AriaTextFieldProps, useId, useTextField } from 'react-aria';
import cn from '~/utils/cn';
@@ -9,6 +10,7 @@ export interface InputProps extends AriaTextFieldProps<HTMLInputElement> {
className?: string;
}
// TODO: Custom isInvalid logic for custom error messages
export default function Input(props: InputProps) {
const { label, labelHidden, className } = props;
const ref = useRef<HTMLInputElement | null>(null);
@@ -42,6 +44,9 @@ export default function Input(props: InputProps) {
)}
>
{label}
{props.isRequired && (
<Asterisk className="inline w-3.5 text-red-500 pb-1 ml-0.5" />
)}
</label>
<input
{...inputProps}
+3 -3
View File
@@ -105,8 +105,8 @@ const headscaleConfig = type({
magic_dns: goBool.default(true),
base_domain: 'string = "headscale.net"',
nameservers: type({
global: 'string[]',
split: 'Record<string, string[]>',
'global?': 'string[]',
'split?': 'Record<string, string[]>',
}).default(() => ({ global: [], split: {} })),
search_domains: type('string[]').default(() => []),
extra_records: type({
@@ -129,7 +129,7 @@ const headscaleConfig = type({
client_secret_path: 'string?',
expiry: goDuration.default('180d'),
use_expiry_from_token: goBool.default(false),
scope: 'string = "profile email"',
scope: type('string[]').default(() => ['openid', 'email', 'profile']),
extra_params: 'Record<string, string>?',
allowed_domains: 'string[]?',
allowed_groups: 'string[]?',
+1 -1
View File
@@ -44,7 +44,7 @@ integration:
# The path to the Docker socket (do not change this if you are unsure)
# Docker socket paths must start with unix:// or tcp:// and at the moment
# https connections are not supported.
socket: "unix:///var/run/docker.dock"
socket: "unix:///var/run/docker.sock"
# Please refer to docs/integration/Kubernetes.md for more information
# on how to configure the Kubernetes integration. There are requirements in
# order to allow Headscale to be controlled by Headplane in a cluster.
+1 -1
View File
@@ -3,7 +3,7 @@
> Since 0.5, you will need to manually migrate your configuration to the new format.
Headplane uses a configuration file to manage its settings
([**config.example.yaml**](./config.example.yaml)). By default, Headplane looks
([**config.example.yaml**](../config.example.yaml)). By default, Headplane looks
for a the file at `/etc/headplane/config.yaml`. This can be changed using the
**`HEADPLANE_CONFIG_PATH`** environment variable to point to a different location.
+35
View File
@@ -0,0 +1,35 @@
# Headplane Agent
The Headplane agent is a lightweight service that runs alongside the Headscale server.
It's used to interface with devices on your network locally, unlocking the following:
- **Node Information/Status**: View Tailscale versions, OS versions, and connection details.
- **SSH via Web (Soon)**: Connect to devices via SSH directly from the Headplane UI.
It's built on top of [tsnet](https://tailscale.com/kb/1244/tsnet), the official
set of libraries published by Tailscale for creating local services that can
join the tailnet.
While it isn't required to run the agent, it's highly recommended to get the
closest experience to the SaaS version of Tailscale. This is paired with the
integrations provided by Headplane to manage DNS and Headscale settings.
### Installation
The agent can either be ran as a standalone binary or as a Docker container.
Agent binaries are available on the [releases](https://github.com/tale/headplane/releases) page.
The Docker image is available through the `ghcr.io/tale/headplane-agent` tag.
The agent requires the following environment variables to be set:
- **`HP_AGENT_HOSTNAME`**: A hostname you want to use for the agent.
- **`HP_AGENT_TS_SERVER`**: The URL to your Headscale instance.
- **`HP_AGENT_TS_AUTHKEY`**: An authorization key to authenticate with Headscale (see below).
- **`HP_AGENT_HP_SERVER`**: The URL to your Headplane instance.
- **`HP_AGENT_HP_AUTHKEY`**: The generated auth key to authenticate with Headplane.
If you already have Headplane setup, you can generate all of these values within
the Headplane UI. Navigate to the `Settings` page and click `Agent` to get started.
HP_AGENT_HOSTNAME=headplane-agent
HP_AGENT_TS_SERVER=http://localhost:8080
#HP_AGENT_AUTH_KEY=3e0cd749021e5984267cde4b0a5a2ac32c1859e56f7911aa
HP_AGENT_TS_AUTHKEY=a4dab065c735cb4eae4f12804cf7e111206f9c7c9247c629
HP_AGENT_HP_SERVER=http://localhost:3000/admin
+2 -2
View File
@@ -34,7 +34,7 @@ Here is what a sample Docker Compose deployment would look like:
services:
headplane:
# I recommend you pin the version to a specific release
image: ghcr.io/tale/headplane:0.5.0
image: ghcr.io/tale/headplane:0.5.1
container_name: headplane
restart: unless-stopped
ports:
@@ -148,7 +148,7 @@ spec:
serviceAccountName: default
containers:
- name: headplane
image: ghcr.io/tale/headplane:0.5.0
image: ghcr.io/tale/headplane:0.5.1
env:
# Set these if the pod name for Headscale is not static
# We will use the downward API to get the pod name instead
+1 -1
View File
@@ -19,7 +19,7 @@ Here is what a sample Docker Compose deployment would look like:
services:
headplane:
# I recommend you pin the version to a specific release
image: ghcr.io/tale/headplane:0.5.0
image: ghcr.io/tale/headplane:0.5.1
container_name: headplane
restart: unless-stopped
ports:
+19 -5
View File
@@ -6,6 +6,8 @@ import {
writeReadableStreamToWritable,
} from '@react-router/node';
import mime from 'mime/lite';
import type { RequestHandler } from 'react-router';
import type { ViteDevServer } from 'vite';
import appContext from '~server/context/app';
import { loadDevtools, stacksafeTry } from '~server/dev/hot-server';
import prodBuild from '~server/prod-handler';
@@ -16,18 +18,30 @@ declare global {
const __hp_prefix: string;
}
export const listener: RequestListener = async (req, res) => {
// biome-ignore lint/suspicious/noExplicitAny: Who cares man
let devtools: { server: ViteDevServer; handler: any } | undefined = undefined;
let prodHandler: RequestHandler | undefined = undefined;
let loaded = false;
async function loadGlobals() {
await hp_loadConfig();
const devtools = import.meta.env.DEV ? await loadDevtools() : undefined;
const prodHandler = import.meta.env.PROD ? await prodBuild() : undefined;
const baseDir = resolve(join('./build', 'client'));
devtools = import.meta.env.DEV ? await loadDevtools() : undefined;
prodHandler = import.meta.env.PROD ? await prodBuild() : undefined;
loaded = true;
}
const baseDir = resolve(join('./build', 'client'));
export const listener: RequestListener = async (req, res) => {
if (!loaded) {
await loadGlobals();
}
const url = new URL(`http://${req.headers.host}${req.url}`);
// build:strip
if (devtools) {
await new Promise((resolve) => {
devtools.server.middlewares(req, res, resolve);
devtools?.server.middlewares(req, res, resolve);
});
}