docs: redo docs
72
README.md
@ -1,5 +1,5 @@
|
|||||||
# Headplane
|
# Headplane
|
||||||
> An advanced UI for [juanfont/headscale](https://github.com/juanfont/headscale)
|
> A feature-complete web UI for [Headscale](https://headscale.net)
|
||||||
|
|
||||||
<picture>
|
<picture>
|
||||||
<source
|
<source
|
||||||
@ -16,30 +16,62 @@
|
|||||||
>
|
>
|
||||||
</picture>
|
</picture>
|
||||||
|
|
||||||
Headscale is a self-hosted version of the Tailscale control server, however, it currently lacks a first-party web UI.
|
Headscale is the de-facto self-hosted version of Tailscale, a popular Wireguard
|
||||||
Headplane aims to solve this issue by providing a GUI that can deeply integrate with the Headscale server.
|
based VPN service. By default, it does not ship with a web UI, which is where
|
||||||
It's able to replicate nearly all of the functions of the official Tailscale SaaS UI, including:
|
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
|
Headplane aims to replicate the functionality offered by the official Tailscale
|
||||||
- Access Control List (ACL) and tagging configuration
|
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
|
- 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
|
## 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.
|
- ### [Integrated Mode (Recommended)](/docs/Integrated-Mode.md)
|
||||||
This includes automatic management of ACLs, DNS settings, and Headscale configuration.
|
Integrated mode unlocks all the features of Headplane and is the most
|
||||||
*This is the closest experience to the Tailscale UI that can be achieved with Headscale and Headplane.*
|
feature-complete deployment method. It communicates with Headscale directly.
|
||||||
*If you aren't sure which one to pick, we recommend this.*
|
|
||||||
|
|
||||||
If your environment is not able to support the advanced deployment, you can still use the basic deployment.
|
- ### [Simple Mode](/docs/Simple-Mode.md)
|
||||||
For basic deployments, see the [Basic Deployment](/docs/Basic-Integration.md) guide.
|
Simple mode does not include the automatic management of DNS and Headplane
|
||||||
It does not include automatic management of ACLs, DNS settings, or the Headscale configuration,
|
settings, requiring manual editing and reloading when making changes.
|
||||||
instead requiring manual editing and reloading when making changes.
|
|
||||||
|
|
||||||
## Contributing
|
<picture>
|
||||||
If you would like to contribute, please install a relatively modern version of Node.js and PNPM.
|
<source
|
||||||
Clone this repository, run `pnpm install`, and then run `pnpm dev` to start the development server.
|
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
|
# are set correctly. Turn this off if you are having issues with
|
||||||
# shareProcessNamespace not being validated correctly.
|
# shareProcessNamespace not being validated correctly.
|
||||||
validate_manifest: true
|
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
|
# Proc is the "Native" integration that only works when Headscale and
|
||||||
# Headplane are running outside of a container. There is no configuration,
|
# 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
|
# 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).
|
Here are a few more examples:
|
||||||
- **`HEADSCALE_URL`**: The public URL of your Headscale server (not required if using the configuration file).
|
|
||||||
|
|
||||||
#### 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`).
|
**This functionality is NOT enabled by default!**
|
||||||
- **`DEBUG`**: Enable debug logging (default: `false`).
|
To enable it, set the environment variable **`HEADPLANE_LOAD_ENV_OVERRIDES=true`**.
|
||||||
- **`HOST`**: The host to bind the server to (default: `0.0.0.0`).
|
Setting this also tells Headplane to load the relative `.env` file into the environment.
|
||||||
- **`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`).
|
|
||||||
|
|
||||||
#### Docker Integration
|
## Debugging
|
||||||
The Docker integration allows Headplane to manage the Headscale docker container.
|
To enable debug logging, set the **`HEADPLANE_DEBUG_LOG=true`** environment variable.
|
||||||
You'll need to provide these variables if you want to use this feature.
|
This will enable all debug logs for Headplane, which could fill up log space very quickly.
|
||||||
Keep in mind that `DOCKER_SOCK` must start with a protocol (e.g., `unix://`).
|
This is not recommended in production environments.
|
||||||
Secure API is currently not supported.
|
|
||||||
|
|
||||||
- **`DOCKER_SOCK`**: The protocol and path to the Docker socket (default: `unix:///var/run/docker.sock`).
|
## Reverse Proxying
|
||||||
- **`HEADSCALE_CONTAINER`**: The name of the Headscale container (required for Docker integration).
|
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
|
> The important part here is the CORS middleware. This is required for the
|
||||||
If you want to use OpenID Connect for SSO, you'll need to provide these variables.
|
> frontend to communicate with the backend. If you are using a different reverse
|
||||||
Headplane will utilize the expiry of your tokens to determine the expiry of the session.
|
> proxy, make sure to add the necessary headers to allow the frontend to communicate
|
||||||
If you use the Headscale configuration integration, these are not required.
|
> with the backend.
|
||||||
|
|
||||||
- **`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.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- client_id: 'headscale'
|
http:
|
||||||
client_name: 'Headscale and Headplane'
|
routers:
|
||||||
public: false
|
headscale:
|
||||||
authorization_policy: 'two_factor'
|
rule: 'Host(`headscale.tale.me`)'
|
||||||
redirect_uris:
|
service: 'headscale'
|
||||||
- 'https://headscale.example.com/oidc/callback'
|
middlewares:
|
||||||
- 'https://headscale.example.com/admin/oidc/callback'
|
- 'cors'
|
||||||
scopes:
|
|
||||||
- 'openid'
|
rewrite:
|
||||||
- 'profile'
|
rule: 'Host(`headscale.tale.me`) && Path(`/`)'
|
||||||
- 'email'
|
service: 'headscale'
|
||||||
userinfo_signed_response_alg: 'none'
|
middlewares:
|
||||||
client_secret: 'my_super_secret_client_secret'
|
- '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 { constants, access, readFile } from 'node:fs/promises';
|
||||||
import { type } from 'arktype';
|
import { type } from 'arktype';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
import { parseDocument } from 'yaml';
|
import { parseDocument } from 'yaml';
|
||||||
import { testOidc } from '~/utils/oidc';
|
import { testOidc } from '~/utils/oidc';
|
||||||
import log, { hpServer_loadLogger } from '~server/utils/log';
|
import log, { hpServer_loadLogger } from '~server/utils/log';
|
||||||
@ -27,7 +28,6 @@ const envBool = type('string | undefined').pipe((v) => {
|
|||||||
|
|
||||||
const rootEnvs = type({
|
const rootEnvs = type({
|
||||||
HEADPLANE_DEBUG_LOG: envBool,
|
HEADPLANE_DEBUG_LOG: envBool,
|
||||||
HEADPLANE_LOAD_ENV_FILE: envBool,
|
|
||||||
HEADPLANE_LOAD_ENV_OVERRIDES: envBool,
|
HEADPLANE_LOAD_ENV_OVERRIDES: envBool,
|
||||||
HEADPLANE_CONFIG_PATH: 'string | undefined',
|
HEADPLANE_CONFIG_PATH: 'string | undefined',
|
||||||
}).onDeepUndeclaredKey('reject');
|
}).onDeepUndeclaredKey('reject');
|
||||||
@ -65,7 +65,6 @@ export async function hp_loadConfig() {
|
|||||||
const envs = rootEnvs({
|
const envs = rootEnvs({
|
||||||
HEADPLANE_DEBUG_LOG: process.env.HEADPLANE_DEBUG_LOG,
|
HEADPLANE_DEBUG_LOG: process.env.HEADPLANE_DEBUG_LOG,
|
||||||
HEADPLANE_CONFIG_PATH: process.env.HEADPLANE_CONFIG_PATH,
|
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,
|
HEADPLANE_LOAD_ENV_OVERRIDES: process.env.HEADPLANE_LOAD_ENV_OVERRIDES,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,12 +96,10 @@ export async function hp_loadConfig() {
|
|||||||
debug: envs.HEADPLANE_DEBUG_LOG,
|
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) {
|
if (config && envs.HEADPLANE_LOAD_ENV_OVERRIDES) {
|
||||||
|
log.info('CFGX', 'Loading a .env file if one exists');
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
'CFGX',
|
'CFGX',
|
||||||
'Loading environment variables to override the configuration',
|
'Loading environment variables to override the configuration',
|
||||||
|
|||||||