docs: redo docs

This commit is contained in:
Aarnav Tale 2025-02-27 15:29:36 -05:00
parent 25e6410c65
commit 870680b6c5
No known key found for this signature in database
22 changed files with 432 additions and 507 deletions

View File

@ -1,5 +1,5 @@
# Headplane
> An advanced UI for [juanfont/headscale](https://github.com/juanfont/headscale)
> A feature-complete web UI for [Headscale](https://headscale.net)
<picture>
<source
@ -16,30 +16,62 @@
>
</picture>
Headscale is a self-hosted version of the Tailscale control server, however, it currently lacks a first-party web UI.
Headplane aims to solve this issue by providing a GUI that can deeply integrate with the Headscale server.
It's able to replicate nearly all of the functions of the official Tailscale SaaS UI, including:
Headscale is the de-facto self-hosted version of Tailscale, a popular Wireguard
based VPN service. By default, it does not ship with a web UI, which is where
Headplane comes in. Headplane is a feature-complete web UI for Headscale, allowing
you to manage your nodes, networks, and ACLs with ease.
- Machine/Node expiry, network routing, name, and owner management
- Access Control List (ACL) and tagging configuration
Headplane aims to replicate the functionality offered by the official Tailscale
product and dashboard, being one of the most feature complete Headscale UIs available.
These are some of the features that Headplane offers:
- Machine management, including expiry, network routing, name, and owner management
- Access Control List (ACL) and tagging configuration for ACL enforcement
- Support for OpenID Connect (OIDC) as a login provider
- DNS and *safe* Headscale configuration management
- The ability to edit DNS settings and automatically provision Headscale
- Configurability for Headscale's settings
## Deployment
> For more configuration options, refer to the [Configuration](/docs/Configuration.md) guide.
Headplane runs as a server-based web-application, meaning you'll need a server to run it.
It's available as a Docker image (recommended) or through a manual installation.
There are 2 ways to deploy Headplane:
For fully-featured deployments, see the [Advanced Deployment](/docs/Advanced-Integration.md) guide.
This includes automatic management of ACLs, DNS settings, and Headscale configuration.
*This is the closest experience to the Tailscale UI that can be achieved with Headscale and Headplane.*
*If you aren't sure which one to pick, we recommend this.*
- ### [Integrated Mode (Recommended)](/docs/Integrated-Mode.md)
Integrated mode unlocks all the features of Headplane and is the most
feature-complete deployment method. It communicates with Headscale directly.
If your environment is not able to support the advanced deployment, you can still use the basic deployment.
For basic deployments, see the [Basic Deployment](/docs/Basic-Integration.md) guide.
It does not include automatic management of ACLs, DNS settings, or the Headscale configuration,
instead requiring manual editing and reloading when making changes.
- ### [Simple Mode](/docs/Simple-Mode.md)
Simple mode does not include the automatic management of DNS and Headplane
settings, requiring manual editing and reloading when making changes.
## Contributing
If you would like to contribute, please install a relatively modern version of Node.js and PNPM.
Clone this repository, run `pnpm install`, and then run `pnpm dev` to start the development server.
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="./assets/acls-dark.png"
>
<source
media="(prefers-color-scheme: light)"
srcset="./assets/acls-light.png"
>
<img
alt="ACLs"
src="./assets/acls-dark.png"
>
</picture>
> Copyright (c) 2024 Aarnav Tale
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="./assets/machine-dark.png"
>
<source
media="(prefers-color-scheme: light)"
srcset="./assets/machine-light.png"
>
<img
alt="Machine Management"
src="./assets/machine-dark.png"
>
</picture>
> Copyright (c) 2025 Aarnav Tale

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 118 KiB

BIN
assets/dns-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
assets/dns-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

BIN
assets/machine-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
assets/machine-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -54,6 +54,10 @@ integration:
# are set correctly. Turn this off if you are having issues with
# shareProcessNamespace not being validated correctly.
validate_manifest: true
# This should be the name of the Pod running Headscale and Headplane.
# If this isn't static you should be using the Kubernetes Downward API
# to set this value (refer to docs/Integrated-Mode.md for more info).
pod_name: "headscale"
# Proc is the "Native" integration that only works when Headscale and
# Headplane are running outside of a container. There is no configuration,

View File

@ -1,78 +0,0 @@
# Advanced Integration
The advanced integration methods unlock the full capabilities of Headplane.
This is the closest you can get to the SaaS experience if you were paying for
Tailscale.
### Configuration Management
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="../assets/integration-dark.png"
>
<source
media="(prefers-color-scheme: light)"
srcset="../assets/integration-light.png"
>
<img
alt="Integration Preview"
src="../assets/integration-dark.png"
>
</picture>
The advanced integration allows you to manage the Headscale configuration via
the Headplane UI. When the configuration is available for editing, the `DNS`
and `Settings` tabs will become available. When using the Docker or Kubernetes
integration, changes to the configuration file will be automatically applied
to Headscale.
> By default, the configuration file is read from `/etc/headscale/config.yaml`.
This can be overridden by setting the `CONFIG_FILE` environment variable. Any
variables including `HEADSCALE_URL`, `OIDC_CLIENT_ID`, `OIDC_ISSUER`, and
`OIDC_CLIENT_SECRET` will take priority over the configuration file.
### Access Control Lists (ACLs)
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="../assets/acls-dark.png"
>
<source
media="(prefers-color-scheme: light)"
srcset="../assets/acls-light.png"
>
<img
alt="ACL Preview"
src="../assets/acls-dark.png"
>
</picture>
The advanced integration allows you to manage the ACLs via the Headplane UI.
When the ACL file is available for editing, the `Access Controls` tab will
become available. All of the integrations support automatic reloading of the
ACLs when the file is changed.
> By default, the ACL file is read from `/etc/headscale/acl_policy.json`.
> If `policy.path` is set and `policy.mode` is set to `file`, the ACL file will
> be read from the path specified in the configuration file instead.
## Deployment
Requirements:
- Headscale 0.23 or newer
- Headscale and Headplane need a Reverse Proxy (NGINX, Traefik, Caddy, etc)
Currently there are 3 integration providers that can do this for you:
- [Docker Integration](/docs/integration/Docker.md)
- [Kubernetes Integration](/docs/integration/Kubernetes.md)
- [Native Linux Integration](/docs/integration/Native.md)
Once configured, the Headplane UI will be available at the `/admin` path
of the server you deployed it on. This is currently not configurable unless
you build the Docker image yourself or run the Node.js server directly.
Additionally, if you require access to health information for either Docker
or Kubernetes, the `/admin/healthz` path will be available. This is useful for
monitoring services like Prometheus or Grafana.

80
docs/Bare-Metal.md Normal file
View File

@ -0,0 +1,80 @@
# Bare-Metal Mode
Bare-Metal mode is the most flexible way to deploy Headplane. It allows you to
run Headplane on any system without the need for Docker or any other container
runtime. This is not recommended, but I understand that everyone has
different needs.
> It works with both the **Simple** and **Integrated** deployment modes. Refer
> to the section below for instructions on configuring Integrated mode.
## Deployment
Requirements:
- Headscale 0.25 or newer (already deployed)
- Node.js 22 LTS or newer
- [PNPM](https://pnpm.io/installation) 10.x
- A finished configuration file (config.yaml)
Clone the Headplane repository, install dependencies, and build the project:
```sh
git clone https://github.com/tale/headplane
cd headplane
pnpm install
pnpm build
```
## Running Headplane
You can start headplane through `pnpm start` or `node build/headplane/server.js`.
Headplane expects the `build` directory to be present when running the server.
The structure of this folder is very important and should not be tampered with.
### Integrated Mode
Since you are running Headplane in Bare-Metal, you most likely also are running
Headscale in Bare-Metal. Refer to the [**Integrated Mode**](/docs/Integrated-Mode.md)
guide for instructions on setting up the integrated mode in Native Linux (/proc).
### Changing the Admin Path
Since you are building Headplane yourself, you are able to configure the admin
path to be anything you want. When running `pnpm build`, you can pass the
`__INTERNAL_PREFIX` environment variable to change the admin path. For example:
```sh
__INTERNAL_PREFIX=/admin2 pnpm build
```
Just keep in mind that the admin path is not configurable at runtime, so you
will need to rebuild the project if you want to change it. Also, anything aside
from `/admin` is not officially supported and could break in future versions.
> Refer to the [**Configuration**](/docs/Configuration.md) guide for help with
> setting up your `config.yaml` file to the appropriate values.
### Systemd Unit
Here is an example of a systemd unit file that you can use to manage the
Headplane service:
```ini
[Unit]
Description=Headplane
After=network.target
[Service]
Type=simple
User=headplane
Group=headplane
WorkingDirectory=/path/to/headplane
ExecStart=/usr/bin/node /path/to/headplane/build/headplane/server.js
Restart=always
[Install]
WantedBy=multi-user.target
```
You will need to replace `/path/to/headplane` with the actual path to the
Headplane repository on your system. Save this file as `headplane.service` in
`/etc/systemd/system/` and run `systemctl enable headplane` to enable the service.
Other fields may also need some configuration, as this unit expects a user and a
group called `headplane` to exist on the system. You can change these values to
match your system's configuration.

View File

@ -1,60 +0,0 @@
# Basic Integration
The basic integration is the simplest way to get started with Headplane.
It's more of a preview and is heavily limited in the features it can offer
when compared to the [Advanced Integration](/docs/Advanced-Integration.md).
> Note that the Advanced integration is the recommend way to run
Headplane in a production environment.
## Limitations
- No automatic management of Access Control Lists (ACLs)
- No management of DNS settings for your tailnet
- No capability to edit the configuration
- Limited support for OIDC authentication
## Deployment
Requirements:
- Headscale 0.23 or newer
- Headscale and Headplane need a Reverse Proxy (NGINX, Traefik, Caddy, etc)
Docker heavily simplifies the deployment process, but this process can be
adopted to run natively. Follow the first section of the deployment guide
in the [Native Integration](/docs/integration/Native.md#deployment) for a
bare-metal or virtual machine deployment.
Here is a simple Docker Compose deployment:
```yaml
services:
headplane:
container_name: headplane
image: ghcr.io/tale/headplane:0.3.9
restart: unless-stopped
ports:
- '3000:3000'
environment:
HEADSCALE_URL: 'http://headscale:8080'
COOKIE_SECRET: 'abcdefghijklmnopqrstuvwxyz'
# These are all optional!
ROOT_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'
COOKIE_SECURE: 'false'
# These are the default values
HOST: '0.0.0.0'
PORT: '3000'
```
Once configured, the Headplane UI will be available at the `/admin` path
of the server you deployed it on. This is currently not configurable unless
you build the Docker image yourself or run the Node.js server directly.
> For a breakdown of each configuration variable, please refer to the
[Configuration](/docs/Configuration.md) guide.
> It explains what each variable does, how to configure them, and what the
default values are.

View File

@ -1,62 +1,86 @@
# Configuration
> Previous versions of Headplane used environment variables without a configuration file.
> Since 0.5, you will need to manually migrate your configuration to the new format.
You can configure Headplane using environment variables.
Headplane uses a configuration file to manage its settings
([**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.
#### Required Variables
## Environment Variables
It is also possible to override the configuration file using environment variables.
These changes get merged *after* the configuration file is loaded, so they will take precedence.
Environment variables follow this pattern: **`HEADPLANE_<SECTION>__<KEY_NAME>`**.
For example, to override `oidc.client_secret`, you would set `HEADPLANE_OIDC__CLIENT_SECRET`
to the value that you want.
- **`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).
Here are a few more examples:
#### Optional Variables
- `HEADPLANE_HEADSCALE__URL`: `headscale.url`
- `HEADPLANE_SERVER__PORT`: `server.port`
- **`HEADSCALE_PUBLIC_URL`**: The public URL of your Headscale server (if different from `HEADSCALE_URL`).
- **`DEBUG`**: Enable debug logging (default: `false`).
- **`HOST`**: The host to bind the server to (default: `0.0.0.0`).
- **`PORT`**: The port to bind the server to (default: `3000`).
- **`CONFIG_FILE`**: The path to the Headscale `config.yaml` (default: `/etc/headscale/config.yaml`).
- **`HEADSCALE_CONFIG_UNSTRICT`**: This will disable the strict configuration loader (default: `false`).
- **`COOKIE_SECURE`**: This option enables the `Secure` flag for cookies, ensuring they are sent only over HTTPS, which helps prevent interception and enhances data security. It should be disabled when using HTTP instead of HTTPS (default: `true`).
- **`LOAD_ENV_FILE`**: Tell Headplane to read the `.env` file and load it into the environment (default: `false`).
**This functionality is NOT enabled by default!**
To enable it, set the environment variable **`HEADPLANE_LOAD_ENV_OVERRIDES=true`**.
Setting this also tells Headplane to load the relative `.env` file into the environment.
#### Docker Integration
The Docker integration allows Headplane to manage the Headscale docker container.
You'll need to provide these variables if you want to use this feature.
Keep in mind that `DOCKER_SOCK` must start with a protocol (e.g., `unix://`).
Secure API is currently not supported.
## Debugging
To enable debug logging, set the **`HEADPLANE_DEBUG_LOG=true`** environment variable.
This will enable all debug logs for Headplane, which could fill up log space very quickly.
This is not recommended in production environments.
- **`DOCKER_SOCK`**: The protocol and path to the Docker socket (default: `unix:///var/run/docker.sock`).
- **`HEADSCALE_CONTAINER`**: The name of the Headscale container (required for Docker integration).
## Reverse Proxying
Reverse proxying is very common when deploying web applications. Headscale and
Headplane are very similar in this regard. You can use the same configuration
of any reverse proxy you are familiar with. Here is an example of how to do it
using Traefik:
### SSO/OpenID Connect
If you want to use OpenID Connect for SSO, you'll need to provide these variables.
Headplane will utilize the expiry of your tokens to determine the expiry of the session.
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.
- **`OIDC_CLIENT_SECRET_METHOD`**: The method used to send the client secret (default: `client_secret_basic`).
- **`OIDC_REDIRECT_URI`**: The redirect URI for the OIDC provider (recommended, otherwise guessed).
- **`OIDC_SKIP_CONFIG_VALIDATION`**: Skip the OIDC configuration validation (default: `false`).
- **`ROOT_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.
Keep in mind that the recommended deployment would be putting Headplane behind /admin on a reverse proxy.
If you use a different domain than the Headscale server, you'll need to make sure that Headscale responds with CORS headers.
> The important part here is the CORS middleware. This is required for the
> frontend to communicate with the backend. If you are using a different reverse
> proxy, make sure to add the necessary headers to allow the frontend to communicate
> with the backend.
```yaml
- client_id: 'headscale'
client_name: 'Headscale and Headplane'
public: false
authorization_policy: 'two_factor'
redirect_uris:
- 'https://headscale.example.com/oidc/callback'
- 'https://headscale.example.com/admin/oidc/callback'
scopes:
- 'openid'
- 'profile'
- 'email'
userinfo_signed_response_alg: 'none'
client_secret: 'my_super_secret_client_secret'
```
http:
routers:
headscale:
rule: 'Host(`headscale.tale.me`)'
service: 'headscale'
middlewares:
- 'cors'
rewrite:
rule: 'Host(`headscale.tale.me`) && Path(`/`)'
service: 'headscale'
middlewares:
- 'rewrite'
headplane:
rule: 'Host(`headscale.tale.me`) && PathPrefix(`/admin`)'
service: 'headplane'
services:
headscale:
loadBalancer:
servers:
- url: 'http://headscale:8080'
headplane:
loadBalancer:
servers:
- url: 'http://headplane:3000'
middlewares:
rewrite:
addPrefix:
prefix: '/admin'
cors:
headers:
accessControlAllowHeaders: '*'
accessControlAllowMethods:
- 'GET'
- 'POST'
- 'PUT'
accessControlAllowOriginList:
- 'https://headscale.tale.me'
accessControlMaxAge: 100
addVaryHeader: true

181
docs/Integrated-Mode.md Normal file
View File

@ -0,0 +1,181 @@
# Integrated Mode
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="../assets/dns-dark.png"
>
<source
media="(prefers-color-scheme: light)"
srcset="../assets/dns-light.png"
>
<img
alt="Integration Preview"
src="../assets/dns-dark.png"
>
</picture>
Integrated mode is a deployment method that allows you to deploy Headplane with
automatic management of DNS and Headplane settings. This is the recommended
deployment method for most users, as it provides a more feature-complete
experience.
## Deployment
> If you are not looking to deploy with Docker, follow the [**Bare-Metal**](/docs/Bare-Metal.md) deployment guide.
> Refer to the `Integrated Mode` section at the bottom for caveats.
Requirements:
- Docker and Docker Compose
- Headscale 0.25 or newer
- A finished configuration file (config.yaml)
Here is what a sample Docker Compose deployment would look like:
```yaml
services:
headplane:
# I recommend you pin the version to a specific release
image: ghcr.io/tale/headplane:0.5.0
container_name: headplane
restart: unless-stopped
ports:
- '3000:3000'
volumes:
- './config.yaml:/etc/headplane/config.yaml'
# This should match headscale.config_path in your config.yaml
- './headscale-config/config.yaml:/etc/headscale/config.yaml'
# If you are using the Docker integration, mount the Docker socket
- '/var/run/docker.sock:/var/run/docker.sock:ro'
headscale:
image: headscale/headscale:0.25.0
container_name: headscale
restart: unless-stopped
command: serve
ports:
- '8080:8080'
volumes:
- './headscale-data:/var/lib/headscale'
- './headscale-config:/etc/headscale'
```
This will result in the Headplane UI being available at the `/admin` path of the
server you deployed it on. The `/admin` path is currently not configurable unless
you build the container yourself or run Headplane in Bare-Metal mode.
> Refer to the [**Configuration**](/docs/Configuration.md) guide for help with
> setting up your `config.yaml` file to the appropriate values.
## Docker Integration
The Docker integration is the easiest to setup, as it only requires the Docker socket
to be mounted into the container along with some configuration. As long as Headplane
has access to the Docker socket and the name of the Headscale container, it will
automatically propagate config and DNS changes to Headscale without any additional
configuration.
## Native Linux (/proc) Integration
The `proc` integration is used when you are running Headscale and Headplane on
non-Docker environments. Headplane will attempt to locate the Headscale process
PID through the `/proc` filesystem and communicate with it directly. In order for
this to work, the Headplane process must have permission to do the following:
- Read the `/proc` filesystem
- Send signals to the Headscale process (`SIGTERM`)
The best way to ensure this is to run Headplane as the same user as Headscale
(or optionally just run them both as `root`). Due to the way the integration is
currently configured, Headplane will not re-check the Headscale process PID if
it changes. This means that if you restart Headscale manually, you will need to
restart Headplane as well.
## Kubernetes Integration
The Kubernetes integration is the most complex to setup, as it requires a
service account with the appropriate permissions to be created. The service
account must have the following permissions and looks like this:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: headplane-agent
namespace: default # Adjust namespace as needed
rules:
- apiGroups: ['']
resources: ['pods']
verbs: ['get', 'list']
- apiGroups: ['apps']
resources: ['deployments']
verbs: ['get', 'list']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headplane-agent
namespace: default # Adjust namespace as needed
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: headplane-agent
subjects:
- kind: ServiceAccount
name: default # If you use a different service account, change this
namespace: default # Adjust namespace as needed
```
To successfully deploy Headplane in Kubernetes, you will need to run both the
Headplane and Headscale containers in the same pod. This is because Headplane
needs access to Headscale's PID in order to communicate with it. Here is an
example, note the **`shareProcessNamespace: true`** field:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: headplane
namespace: default # Adjust namespace as needed
labels:
app: headplane
spec:
replicas: 1
selector:
matchLabels:
app: headplane
template:
metadata:
labels:
app: headplane
spec:
shareProcessNamespace: true
serviceAccountName: default
containers:
- name: headplane
image: ghcr.io/tale/headplane:0.5.0
env:
# Set these if the pod name for Headscale is not static
# We will use the downward API to get the pod name instead
- name: HEADPLANE_ENV_LOAD_OVERRIDES
value: 'true'
- name: 'HEADPLANE_INTEGRATION__KUBERNETES__POD_NAME'
valueFrom:
fieldRef:
fieldPath: metadata.name
volumeMounts:
- name: headscale-config
mountPath: /etc/headscale
- name: headscale
image: headscale/headscale:0.25.0
command: ['serve']
volumeMounts:
- name: headscale-data
mountPath: /var/lib/headscale
- name: headscale-config
mountPath: /etc/headscale
volumes:
- name: headscale-data
persistentVolumeClaim:
claimName: headscale-data
- name: headscale-config
persistentVolumeClaim:
claimName: headscale-config
```

36
docs/Simple-Mode.md Normal file
View File

@ -0,0 +1,36 @@
# Simple Mode
Simple mode enables you to quickly deploy Headplane and is recommended for any
testing or simple environments. It does not include the automatic management of
DNS and Headplane settings, requiring manual editing and reloading when making
changes. If you're looking for a more feature-complete deployment method, check
out [**Integrated Mode**](/docs/Integrated-Mode.md).
## Deployment
> If you are not looking to deploy with Docker, follow the [**Bare-Metal**](/docs/Bare-Metal.md) deployment guide.
Requirements:
- Docker and Docker Compose
- Headscale 0.25 or newer (already deployed)
- A finished configuration file (config.yaml)
Here is what a sample Docker Compose deployment would look like:
```yaml
services:
headplane:
# I recommend you pin the version to a specific release
image: ghcr.io/tale/headplane:0.5.0
container_name: headplane
restart: unless-stopped
ports:
- '3000:3000'
volumes:
- './config.yaml:/etc/headplane/config.yaml'
```
This will result in the Headplane UI being available at the `/admin` path of the
server you deployed it on. The `/admin` path is currently not configurable unless
you build the container yourself or run Headplane in Bare-Metal mode.
> Refer to the [**Configuration**](/docs/Configuration.md) guide for help with
> setting up your `config.yaml` file to the appropriate values.

View File

@ -1,89 +0,0 @@
## Docker Integration
The Docker integration allows you to run Headplane and Headscale separately
in a dockerized environment. It allows you to unlock full functionality such as
automatic reloading of ACLs, DNS management, and Headscale configuration
management.
### Deployment
> When running with the Docker integration, it's assumed that both Headscale and
Headplane will run as containers. If you are running Headscale natively, then
refer to the [Native Integration](/docs/integration/Native.md) guide.
To enable the Docker integration, set the `HEADSCALE_INTEGRATION` environment
variable to `docker`. You'll also need to supply `HEADSCALE_CONTAINER` with the
name or ID of the Headscale container.
By default Headplane uses `unix:///var/run/docker.sock` to connect to Docker.
This can be overridden by setting the `DOCKER_SOCK` environment variable. For
example, a remote socket would be `tcp://<my-remote-host>:2375`. When setting
the variable, you'll need to specify the protocol (`unix://` or `tcp://`).
> The `DOCKER_SOCK` variable does not support the HTTPS protocol.
To enable the Docker integration, set `HEADSCALE_INTEGRATION=docker` in the environment variables.
Additionally, you'll need to pass in the `HEADSCALE_CONTAINER` environment variable.
This should be either the name or ID of the Headscale container (you can retrieve this using `docker ps`).
If the other integrations aren't setup, then Headplane will automatically disable the Docker integration.
By default the integration will check for `/var/run/docker.sock`, however you can override this by
setting the `DOCKER_SOCK` environment variable if you use a different configuration than the default.
When setting `DOCKER_SOCK`, you'll need to include the protocol (e.g., `unix://` or `tcp://`).
Headplane currently does not support the HTTPS protocol for the Docker socket.
Here's an example deployment using Docker Compose (recommended). Keep in mind
that you'll NEED to setup a reverse proxy and this is incomplete:
```yaml
services:
headscale:
image: 'headscale/headscale:0.23.0'
container_name: 'headscale'
restart: 'unless-stopped'
command: 'serve'
volumes:
- './data:/var/lib/headscale'
- './configs:/etc/headscale'
ports:
- '8080:8080'
environment:
TZ: 'America/New_York'
headplane:
container_name: headplane
image: ghcr.io/tale/headplane:0.3.9
restart: unless-stopped
volumes:
- './data:/var/lib/headscale'
- './configs:/etc/headscale'
- '/var/run/docker.sock:/var/run/docker.sock:ro'
ports:
- '3000:3000'
environment:
# This is always required for Headplane to work
COOKIE_SECRET: 'abcdefghijklmnopqrstuvwxyz'
HEADSCALE_INTEGRATION: 'docker'
HEADSCALE_CONTAINER: 'headscale'
DISABLE_API_KEY_LOGIN: 'true'
HOST: '0.0.0.0'
PORT: '3000'
# Only set this to false if you aren't behind a reverse proxy
COOKIE_SECURE: 'false'
# Overrides the configuration file values if they are set in config.yaml
# If you want to share the same OIDC configuration you do not need this
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
ROOT_API_KEY: 'abcdefghijklmnopqrstuvwxyz'
```
> For a breakdown of each configuration variable, please refer to the
[Configuration](/docs/Configuration.md) guide.
> It explains what each variable does, how to configure them, and what the
default values are.

View File

@ -1,133 +0,0 @@
## Kubernetes Integration
The Kubernetes integration allows you to run Headplane and Headscale together
in a cluster. It allows you to unlock full functionality such as automatic
reloading of ACLs, DNS management, and Headscale configuration management.
Currently there are a few limitations to the Kubernetes integration:
- Headplane and Headscale need to run in the same Pod and share the same
process space for the integration to work correctly due to a limitation in
the Kubernetes API.
- The only supported methods of deploying the integration are through a
`Deployment` or `Pod` (more coming soon). You can still get around this with
the `HEADSCALE_INTEGRATION_UNSTRICT` variable, but it's not recommended.
- The integration will assume that the Headscale container will always restart
because the integration relies on a system call that will exit the container.
### Deployment
In order to ensure Headplane can read Kubernetes resources, you'll need to
grant additional RBAC permissions to the default `ServiceAccount` in the
namespace. This can be done with the following:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: headplane-agent
namespace: default # Adjust namespace as needed
rules:
- apiGroups: ['']
resources: ['pods']
verbs: ['get', 'list']
- apiGroups: ['apps']
resources: ['deployments']
verbs: ['get', 'list']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headplane-agent
namespace: default # Adjust namespace as needed
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: headplane-agent
subjects:
- kind: ServiceAccount
name: default # If you use a different service account, change this
namespace: default # Adjust namespace as needed
```
Keep in mind you'll need to make `PersistentVolumeClaim`s for the data and that
they need to be either `ReadWriteOnce` or `ReadWriteMany` depending on your
topology. Additionally, you can abstract environment variables and configuration
away into a `ConfigMap` or `Secret` for easier management.
The important parts of this deployment are the `HEADSCALE_INTEGRATION` and
`DEPLOYMENT_NAME` environment variables. The `HEADSCALE_INTEGRATION` variable
should be set to `kubernetes` and the `POST_NAME` variable should be set
to the name of the pod (done using the Downward API below).
> If you are having issues with validating `shareProcessNamespace`, you can
set `HEADSCALE_INTEGRATION_UNSTRICT` to `true` to disable the strict checks.
A basic deployment of the integration would look like this. Keep in mind that
you are responsible for setting up a reverse-proxy via an `Ingress` or `Service`
otherwise Headplane will not work:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: headplane
namespace: default # Adjust namespace as needed
labels:
app: headplane
spec:
replicas: 1
selector:
matchLabels:
app: headplane
template:
metadata:
labels:
app: headplane
spec:
shareProcessNamespace: true
serviceAccountName: default
containers:
- name: headplane
image: ghcr.io/tale/headplane:0.3.9
env:
- name: COOKIE_SECRET
value: 'abcdefghijklmnopqrstuvwxyz'
- name: HEADSCALE_INTEGRATION
value: 'kubernetes'
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# Only set this to false if you aren't behind a reverse proxy
- name: COOKIE_SECURE
value: 'false'
volumeMounts:
- name: headscale-config
mountPath: /etc/headscale
- name: headscale
image: headscale/headscale:0.23.0
command: ['serve']
env:
- name: TZ
value: 'America/New_York'
volumeMounts:
- name: headscale-data
mountPath: /var/lib/headscale
- name: headscale-config
mountPath: /etc/headscale
volumes:
- name: headscale-data
persistentVolumeClaim:
claimName: headscale-data
- name: headscale-config
persistentVolumeClaim:
claimName: headscale-config
```
> For a breakdown of each configuration variable, please refer to the
[Configuration](/docs/Configuration.md) guide.
> It explains what each variable does, how to configure them, and what the
default values are.

View File

@ -1,69 +0,0 @@
## Native Integration
The Native integration allows you to run both Headplane and Headscale on
bare-metal servers or virtual machines. This integration is best suited for
environments where Docker or Kubernetes are not available or not desired.
Currently the Native integration only supports automatic reloading of ACLs. It
cannot handle configuration changes as killing the `headscale` process can lead
to undefined behavior or the service not restarting.
### Deployment
1. Follow the instructions to install Headscale from the
[Linux Installation Guide](https://headscale.net/stable/setup/install/official/).
2. Install [Node.js](https://nodejs.org/en/download/package-manager)
version 20 or higher (your package manager most likely already has this).
3. Install [PNPM](https://pnpm.io/installation). This is required
as Headplane has issues running correctly when installed and built via NPM or Yarn.
4. Clone the Headplane repository, install dependencies, and build the project:
```sh
git clone https://github.com/tale/headplane # (or clone via SSH)
cd headplane
pnpm install
pnpm build
```
### Running Headplane
Start Headplane with `node build/headplane/server.js`.
Headplane does need various environment variables to run correctly. The required
variables can be found in the [Configuration](/docs/Configuration.md) guide.
If you choose to do this with a `.env` file, you can use the `LOAD_ENV_FILE`
variable to tell Headplane to load the file.
Finally, make sure to set `HEADSCALE_INTEGRATION=proc` to take advantage
of controlling Headscale natively on Linux.
```sh
LOAD_ENV_FILE=true node ./build/headplane/server.js
```
> If you'd like, you can turn this into a `systemd` unit to manage the service.
> I plan to provide packages and unit files to make this easier in the future.
### Cannot find ./build directory?
Headplane expects the `build` directory to be present when running the server.
The structure of this folder is very important and should not be tampered with.
If you would like to keep the build directory in a different location, you can
set the `BUILD_PATH` environment variable to the path of the build directory
at runtime.
```sh
LOAD_ENV_FILE=true BUILD_PATH=/path/to/build node ./build/headplane/server.js
```
### Changing Headplane's Path from `/admin`
Additionally, because you are building Headplane from source, you're able to
change the default path that Headplane is served from. This can be done by
specifying the `__INTERNAL_PREFIX` environment variable, when building.
```sh
__INTERNAL_PREFIX=/my-admin-path pnpm build
```
> Keep in mind that this is very much an experimental feature. Things can easily
> break and until it's more stable, it's not recommended to use in production.

View File

@ -1,5 +1,6 @@
import { constants, access, readFile } from 'node:fs/promises';
import { type } from 'arktype';
import dotenv from 'dotenv';
import { parseDocument } from 'yaml';
import { testOidc } from '~/utils/oidc';
import log, { hpServer_loadLogger } from '~server/utils/log';
@ -27,7 +28,6 @@ const envBool = type('string | undefined').pipe((v) => {
const rootEnvs = type({
HEADPLANE_DEBUG_LOG: envBool,
HEADPLANE_LOAD_ENV_FILE: envBool,
HEADPLANE_LOAD_ENV_OVERRIDES: envBool,
HEADPLANE_CONFIG_PATH: 'string | undefined',
}).onDeepUndeclaredKey('reject');
@ -65,7 +65,6 @@ export async function hp_loadConfig() {
const envs = rootEnvs({
HEADPLANE_DEBUG_LOG: process.env.HEADPLANE_DEBUG_LOG,
HEADPLANE_CONFIG_PATH: process.env.HEADPLANE_CONFIG_PATH,
HEADPLANE_LOAD_ENV_FILE: process.env.HEADPLANE_LOAD_ENV_FILE,
HEADPLANE_LOAD_ENV_OVERRIDES: process.env.HEADPLANE_LOAD_ENV_OVERRIDES,
});
@ -97,12 +96,10 @@ export async function hp_loadConfig() {
debug: envs.HEADPLANE_DEBUG_LOG,
});
if (envs.HEADPLANE_LOAD_ENV_FILE) {
log.info('CFGX', 'Loading a .env file if one exists');
await import('dotenv/config');
}
if (config && envs.HEADPLANE_LOAD_ENV_OVERRIDES) {
log.info('CFGX', 'Loading a .env file if one exists');
dotenv.config();
log.info(
'CFGX',
'Loading environment variables to override the configuration',