docs: redo docs
72
README.md
@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 118 KiB |
BIN
assets/dns-dark.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
assets/dns-light.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 189 KiB |
BIN
assets/machine-dark.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
assets/machine-light.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 107 KiB |
@ -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,
|
||||
|
||||
@ -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
@ -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.
|
||||
@ -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.
|
||||
@ -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
@ -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
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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',
|
||||
|
||||