diff --git a/README.md b/README.md
index 01e16c2..cb12ae0 100644
--- a/README.md
+++ b/README.md
@@ -32,17 +32,7 @@ These are some of the features that Headplane offers:
- Configurability for Headscale's settings
## Deployment
-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:
-
-- ### [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.
-
-- ### [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.
+Refer to the [website](https://headplane.net) for detailed installation instructions.
## Versioning
Headplane uses [semantic versioning](https://semver.org/) for its releases (since v0.6.0).
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 1765216..57664d7 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -3,7 +3,6 @@ import { defineConfig } from 'vitepress';
export default defineConfig({
title: 'Headplane',
description: 'The missing dashboard for Headscale',
- ignoreDeadLinks: ['/docs/Integrated-Mode', '/docs/Simple-Mode'],
cleanUrls: true,
head: [['link', { rel: 'icon', href: '/favicon.ico' }]],
themeConfig: {
@@ -11,13 +10,19 @@ export default defineConfig({
nav: [{ text: 'Home', link: '/' }],
sidebar: [
{
- text: 'Chapters',
+ text: 'Getting Started',
items: [
- { text: 'Getting Started', link: '/README' },
+ { text: 'What is Headplane?', link: '/introduction' },
+ {
+ text: 'Installation',
+ link: '/install',
+ items: [
+ { text: 'Limited Mode', link: '/install/limited-mode' },
+ { text: 'Native Mode', link: '/install/native-mode' },
+ { text: 'Docker', link: '/install/docker' },
+ ],
+ },
{ text: 'Configuration', link: '/Configuration' },
- { text: 'Bare-Metal Mode', link: '/Bare-Metal' },
- { text: 'Integrated Mode', link: '/Integrated-Mode' },
- { text: 'Simple Mode', link: '/Simple-Mode' },
{ text: 'Nix', link: '/Nix' },
{ text: 'NixOS', link: '/NixOS-options' },
{ text: 'Security', link: '/SECURITY' },
diff --git a/docs/Bare-Metal.md b/docs/Bare-Metal.md
deleted file mode 100644
index e8a1725..0000000
--- a/docs/Bare-Metal.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# 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.26 or newer (already deployed)
-- Node.js **22.16** LTS or newer
-- [PNPM](https://pnpm.io/installation) 10.x
-- A finished configuration file (config.yaml)
-
-Before installing Headplane, ensure that `/var/lib/headplane` exists and is
-writable by the user that will run the Headplane service. You can create this
-directory with the following command:
-
-```sh
-sudo mkdir -p /var/lib/headplane
-# Replace headplane:headplane with the appropriate user and group if not root.
-sudo chown -R headplane:headplane /var/lib/headplane
-```
-
-Clone the Headplane repository, install dependencies, and build the project:
-```sh
-git clone https://github.com/tale/headplane
-cd headplane
-git checkout v0.6.0 # Or whatever tag you want to use
-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**](./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**](./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
-# Decomment the following line if running on bare metal with integrated mode (/proc integration)
-# PartOf=headscale.service
-
-[Service]
-Type=simple
-User=headplane
-Group=headplane
-WorkingDirectory=/path/to/headplane
-ExecStart=/usr/bin/node /path/to/headplane/build/server/index.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.
diff --git a/docs/Integrated-Mode.md b/docs/Integrated-Mode.md
deleted file mode 100644
index 85e5709..0000000
--- a/docs/Integrated-Mode.md
+++ /dev/null
@@ -1,199 +0,0 @@
-# Integrated Mode
-
-
-
-
-
-
-
-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**](./Bare-Metal.md) deployment guide.
-> Refer to the `Integrated Mode` section at the bottom for caveats.
-
-Requirements:
-- Docker and Docker Compose
-- Headscale 0.26 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.6.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 using dns.extra_records in Headscale (recommended), this should
- # match the headscale.dns_records_path in your config.yaml
- - './headscale-config/dns_records.json:/etc/headscale/dns_records.json'
-
- # Headplane stores its data in this directory
- - './headplane-data:/var/lib/headplane'
-
- # 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.26.0
- container_name: headscale
- restart: unless-stopped
- command: serve
- labels:
- # This is needed for Headplane to find it and signal it
- me.tale.headplane.target: headscale
- 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**](./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 set up, as it only requires mounting the
-Docker socket into the container along with some basic configuration. Headplane
-uses Docker labels to discover the Headscale container. As long as Headplane has
-access to the Docker socket and can identify the Headscale container—either by
-label or name—it will automatically propagate configuration and DNS changes to
-Headscale without any additional setup. Alternatively, instead of using a label
-to dynamically determine the container name, it is possible to directly specify
-the container name.
-
-## 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.6.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_LOAD_ENV_OVERRIDES
- value: 'true'
- - name: 'HEADPLANE_INTEGRATION__KUBERNETES__POD_NAME'
- valueFrom:
- fieldRef:
- fieldPath: metadata.name
- volumeMounts:
- - name: headscale-config
- mountPath: /etc/headscale
- - name: headplane-data
- mountPath: /var/lib/headplane
-
- - name: headscale
- image: headscale/headscale:0.26.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: headplane-data
- persistentVolumeClaim:
- claimName: headplane-data
- - name: headscale-config
- persistentVolumeClaim:
- claimName: headscale-config
-```
diff --git a/docs/README.md b/docs/README.md
deleted file mode 120000
index 3dfb7d7..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1 +0,0 @@
-./../README.md
\ No newline at end of file
diff --git a/docs/Simple-Mode.md b/docs/Simple-Mode.md
deleted file mode 100644
index 2660a44..0000000
--- a/docs/Simple-Mode.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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**](./Integrated-Mode.md).
-
-## Deployment
-> If you are not looking to deploy with Docker, follow the [**Bare-Metal**](./Bare-Metal.md) deployment guide.
-
-Requirements:
-- Docker and Docker Compose
-- Headscale 0.26 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.6.0
- container_name: headplane
- restart: unless-stopped
- ports:
- - '3000:3000'
- volumes:
- - './config.yaml:/etc/headplane/config.yaml'
- - './headplane-data:/var/lib/headplane'
-```
-
-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**](./Configuration.md) guide for help with
-> setting up your `config.yaml` file to the appropriate values.
diff --git a/docs/features/agent.md b/docs/features/agent.md
new file mode 100644
index 0000000..fc06cf2
--- /dev/null
+++ b/docs/features/agent.md
@@ -0,0 +1,7 @@
+---
+title: Headplane Agent
+description: Configure the Headplane Agent for enhanced functionality.
+---
+
+# Headplane Agent
+TODO
diff --git a/docs/features/sso.md b/docs/features/sso.md
new file mode 100644
index 0000000..e9be232
--- /dev/null
+++ b/docs/features/sso.md
@@ -0,0 +1,7 @@
+---
+title: Single Sign-On (SSO)
+description: Configure Single Sign-On (SSO) authentication for Headplane.
+---
+
+# Single Sign-On (SSO)
+TODO
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/index.md b/docs/index.md
index b7272b5..cf5890d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -10,7 +10,10 @@ hero:
alt: "Headplane"
actions:
- text: "Getting Started"
- link: "/README"
+ link: "/introduction"
+ - text: "Installation"
+ link: "/install"
+ theme: "alt"
- text: "GitHub"
link: "https://github.com/tale/headplane"
theme: "alt"
diff --git a/docs/install/docker.md b/docs/install/docker.md
new file mode 100644
index 0000000..2443569
--- /dev/null
+++ b/docs/install/docker.md
@@ -0,0 +1,266 @@
+---
+title: Docker
+description: Install Headplane with Docker.
+outline: [2, 3]
+---
+
+# Docker Installation
+::: tip
+If you are not looking to deploy with Docker, follow the
+[**Native Mode**](./native-mode.md) deployment guide.
+:::
+
+The recommended way to deploy Headplane is through Docker. This method is quick,
+easy, and works in most environments. It requires that Headscale is also running
+with Docker.
+
+## Prerequisites
+- Docker and Docker Compose
+- Headscale version 0.26.0 or later installed and running
+- A [completed configuration file](/index.md#configuration) for Headplane.
+
+
+## Installation
+Running Headplane in with Docker is as simple as applying 1 compose file:
+```yaml
+services:
+ headplane:
+ image: ghcr.io/tale/headplane:latest
+ container_name: headplane
+ restart: unless-stopped
+ ports:
+ - '3000:3000'
+ volumes:
+ - './config.yaml:/etc/headplane/config.yaml'
+ - './headplane-data:/var/lib/headplane'
+```
+
+It's important to mount your configuration file and also provide a persistent
+storage location for Headplane to store its own data. You can also change the
+port mapping if you want to run it on a different port.
+
+## Accessing Headplane
+
+After starting the container, you can access the Headplane web interface by
+navigating to `http://localhost:3000/admin` in your web browser (replace
+`localhost` with your server's IP address or domain name if not running locally).
+
+In order to log in, you'll need to supply a Headscale API key. You can create
+one by running the following command within your Headscale environment:
+
+```bash
+# You may want to tweak the expiration duration as needed
+headscale apikeys create --expiration 90d
+```
+
+## Enabling advanced features
+
+You've technically completed the installation, but read on if you would like
+to enable advanced features like the ability to edit network settings from the
+UI or remote SSH from the browser.
+
+### Network Management
+
+Network management allows you to configure Tailnet settings such as DNS servers,
+custom A records, the tailnet domain name, and MagicDNS from the Headplane UI.
+
+#### Prerequisites
+Network management (and other configurable Headscale features) requires that
+Headplane and Headscale both run together in the same Docker machine. This is
+because Headplane needs the following permissions:
+
+- Access to read and write the Head**scale** configuration file through a shared
+volume used by both Headscale and Headplane.
+- Access to the Docker socket (usually `/var/run/docker.sock`, you may also use
+a proxy such as [Tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy)).
+
+#### Configuration
+
+First you'll need to run both Headscale and Headplane in the same Docker
+environment. Here is an example `compose.yaml` file that accomplishes this:
+
+```yaml
+services:
+ headplane:
+ image: ghcr.io/tale/headplane:latest
+ container_name: headplane
+ restart: unless-stopped
+ ports:
+ - '3000:3000'
+ volumes:
+ # Same as before
+ - '/path/to/your/config.yaml:/etc/headplane/config.yaml'
+ - '/path/to/data/storage:/var/lib/headplane'
+
+ # A shared path to the Headscale config file. It is important that the
+ # path you mount this on matches `headscale.config_path` in your
+ # Headplane config.yaml file.
+ - '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
+
+ # If you are using dns.extra_records in Headscale (recommended), you
+ # should also mount that file here so Headplane can read and write it.
+ # Ensure that the path matches `headscale.dns_records_path` in your
+ # Headplane config.yaml file.
+ - '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
+
+ # Read-only access to the Docker socket (or a proxy)
+ - '/var/run/docker.sock:/var/run/docker.sock:ro'
+ headscale:
+ image: headscale/headscale:0.26.0
+ container_name: headscale
+ restart: unless-stopped
+ command: serve
+ labels:
+ # This label is absolutely necessary to help Headplane find Headscale.
+ me.tale.headplane.target: headscale
+ ports:
+ - '8080:8080'
+ volumes:
+ # Notice how these are on the exact same path as the host for both
+ # Headscale and Headplane! This is very important.
+ - '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
+ - '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
+
+ - '/path/to/headscale/data/storage:/var/lib/headscale'
+```
+
+::: info
+With some effort, you can technically run Headscale and Headplane in separate
+Docker hosts and remotely connect to a Docker daemon. This is an advanced setup
+that is not covered in this documentation. Refer to the
+[example configuration](https://github.com/tale/headplane/blob/main/config.example.yaml)
+for more details on setting it up.
+:::
+
+You'll also need to enable a few fields in your Headplane configuration file:
+
+| Field | Description |
+|---------------------|--------------------------------------------------------|
+| **`integration.docker.enabled`** | Set to `true` to enable Docker integration. |
+| **`headscale.config_path`** | Path to your Head**scale** configuration file within the container (e.g., `/etc/headscale/config.yaml`). |
+| `headscale.dns_records_path` | *Optional*. Refer to the [example configuration](https://github.com/tale/headplane/blob/main/config.example.yaml) for details. |
+
+With these settings in place, restart Headplane. You should now see additional
+options in the UI navbar such as "DNS" and "Settings" where you can manage your
+Tailnet configuration.
+
+### Remote Web SSH
+
+Remote Web SSH allows you to open a terminal session to your Tailscale nodes
+directly from the Headplane web interface via
+[Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh). This feature
+requires that Tailscale SSH is running on your nodes (done via
+`tailscale up --ssh`).
+
+This feature uses the [Headplane Agent](../features/agent.md) to facilitate the
+SSH connections. Refer to the [Agent documentation](../features/agent.md) for
+setup instructions.
+
+### Single Sign-On (SSO)
+
+Single Sign-On (SSO) authentication allows users to log in to Headplane using
+external identity providers such as Google, GitHub, or any provider that
+supports OpenID Connect (OIDC).
+
+To get started with SSO, refer to the [SSO documentation](../features/sso.md)
+for detailed setup instructions.
+
+
+## Reverse Proxying
+
+You *should* run Headplane behind a reverse proxy such as Nginx or Caddy in
+production. Additionally, putting Headscale beind the reverse proxy allows
+you to access both services via the same domain and TLS certificate.
+
+#### Configuration
+
+Headscale supports integrating with
+[several reverse proxies](https://headscale.net/stable/ref/integration/reverse-proxy/)
+such as Nginx, Caddy, Apache, etc. Deploying Headplane is as simple as adding
+a handler to route any requests to `/admin` to the Headplane service. Refer
+to the Traefik example below for a reference configuration. A similar setup via
+Nginx without Docker is available in the
+[Native Mode](./native-mode.md#reverse-proxying) installation documentation.
+
+#### Example Traefik Configuration
+
+The following configuration will set up Traefik to proxy all Headscale requests
+on `headscale.example.com` and serve the Headplane UI under the `/admin` path.
+This is identical to how Tailscale's own admin console is served.
+
+Keep in mind this won't work on its own as you'll need to configure Traefik
+and TLS certificates as needed. This is just a snippet to show how to configure
+the routing for Headplane and Headscale.
+
+
+Traefik config snippet
+
+```yaml
+services:
+ # Same as before
+ headplane:
+ image: ghcr.io/tale/headplane:latest
+ container_name: headplane
+ restart: unless-stopped
+ ports:
+ - '3000:3000'
+ volumes:
+ - '/path/to/your/config.yaml:/etc/headplane/config.yaml'
+ - '/path/to/data/storage:/var/lib/headplane'
+ - '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
+ - '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
+ - '/var/run/docker.sock:/var/run/docker.sock:ro'
+ labels:
+ # Expose the admin UI at /admin
+ - 'traefik.enable=true'
+ - 'traefik.http.routers.headplane.rule=Host(`headscale.example.com`) && PathPrefix(`/admin`)'
+ - 'traefik.http.routers.headplane.entrypoints=websecure'
+ - 'traefik.http.routers.headplane.tls=true'
+ headscale:
+ image: headscale/headscale:0.26.0
+ container_name: headscale
+ restart: unless-stopped
+ command: serve
+ ports:
+ - '8080:8080'
+ volumes:
+ - '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
+ - '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
+ - '/path/to/headscale/data/storage:/var/lib/headscale'
+ labels:
+ - 'me.tale.headplane.target=headscale'
+
+ # Traefik labels to expose Headscale at headscale.example.com
+ - 'traefik.enable=true'
+ - 'traefik.http.routers.headscale.rule=Host(`headscale.example.com`)'
+ - 'traefik.http.routers.headscale.entrypoints=websecure'
+ - 'traefik.http.routers.headscale.tls=true'
+
+ # This middleware is essential to ensuring Headplane works correctly
+ - 'traefik.http.routers.headscale.middlewares=cors'
+ - 'traefik.http.middlewares.cors.headers.accesscontrolallowheaders=*'
+ - 'traefik.http.middlewares.cors.headers.accesscontrolallowmethods=GET,POST,PUT'
+ - 'traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=https://headscale.example.com'
+ - 'traefik.http.middlewares.cors.headers.accesscontrolmaxage=100'
+ - 'traefik.http.middlewares.cors.headers.addvaryheader=true'
+
+ # If you would optionally like to automatically redirect / to /admin
+ - 'traefik.http.routers.rewrite.rule=Host(`headscale.example.com`) && Path(`/`)'
+ - 'traefik.http.routers.rewrite.service=headscale'
+ - 'traefik.http.routers.rewrite.middlewares=rewrite'
+ - 'traefik.http.middlewares.rewrite.addprefix.prefix=/admin'
+
+ traefik:
+ image: traefik:v3.0
+ container_name: traefik
+ restart: unless-stopped
+ ports:
+ - '80:80'
+ - '443:443'
+ volumes:
+ # Example volumes/setup, please configure Traefik as needed
+ - '/var/run/docker.sock:/var/run/docker.sock:ro'
+ - '/path/to/certs/storage:/certs'
+```
+
+
diff --git a/docs/install/index.md b/docs/install/index.md
new file mode 100644
index 0000000..04200c4
--- /dev/null
+++ b/docs/install/index.md
@@ -0,0 +1,22 @@
+---
+title: Installation
+description: Get started with Headplane.
+---
+
+# Installation
+
+Headplane is designed to be deployed within several different environments to
+ensure that it can seamlessly integrate into your existing infrastructure. First
+set up your configuration file and then pick the installation method that best
+suits your needs.
+
+## Configuration
+Headplane requires a configuration file to operate. A
+[sample file](https://github.com/tale/headplane/blob/main/config.example.yaml)
+is available to use as a starting point. Some of the important fields include:
+
+| Field | Description |
+|---------------------|--------------------------------------------------------|
+| **`headscale.url`** | Point to your Headscale server (e.g., `http://headscale.example.com` or `http://headscale:8080` in Docker). |
+| **`server.cookie_secret`** | Used to encrypt cookies. You can generate a random string using a command like `openssl rand -base64 32`. |
+| **`server.data_path`** | Just a path to keep in mind, especially if you're using Docker. |
diff --git a/docs/install/limited-mode.md b/docs/install/limited-mode.md
new file mode 100644
index 0000000..bfcdf6d
--- /dev/null
+++ b/docs/install/limited-mode.md
@@ -0,0 +1,86 @@
+---
+title: Limited Mode
+description: Install Headplane in Limited Mode.
+---
+
+# Limited Mode
+
+::: warning
+**Limited Mode is not recommended for production use.**
+Please consider using one of the other installation methods for a production
+deployment. Limited mode lacks advanced features such as network management,
+remote web SSH, and more.
+:::
+
+Limited Mode is good for users who want to test out the *basic* functionality
+provided by Headplane. It only interacts with the Headplane API and lacks all
+advanced features, making it suitable for local testing and development.
+
+## Prerequisites
+- Docker (and optionally Docker Compose)
+- Headscale version 0.26.0 or later installed and running
+- A [completed configuration file](/index.md#configuration) for Headplane.
+
+## Installation
+::: tip
+If you want to test Limited Mode without Docker, you can follow the
+[Native Mode](./native-mode.md) installation guide and simply avoid setting
+up any of the advanced features.
+:::
+
+Running Headplane in Limited Mode is as simple as running 1 command:
+```bash
+docker run -d \
+ -p 3000:3000 \
+ -v /path/to/your/config.yaml:/etc/headplane/config.yaml \
+ -v /path/to/data/storage:/var/lib/headplane \
+ --name headplane
+ --restart unless-stopped
+ ghcr.io/tale/headplane:latest
+```
+
+It's important to mount your configuration file and also provide a persistent
+storage location for Headplane to store its own data. You can also change the
+port mapping if you want to run it on a different port.
+
+
+Example Docker Compose configuration
+
+```yaml
+services:
+ headplane:
+ image: ghcr.io/tale/headplane:latest
+ container_name: headplane
+ restart: unless-stopped
+ ports:
+ - '3000:3000'
+ volumes:
+ - '/path/to/your/config.yaml:/etc/headplane/config.yaml'
+ - '/path/to/data/storage:/var/lib/headplane'
+```
+
+
+## Accessing Headplane
+
+After starting the container, you can access the Headplane web interface by
+navigating to `http://localhost:3000/admin` in your web browser (replace
+`localhost` with your server's IP address or domain name if not running locally).
+
+In order to log in, you'll need to supply a Headscale API key. You can create
+one by running the following command within your Headscale environment:
+
+```bash
+# You may want to tweak the expiration duration as needed
+headscale apikeys create --expiration 90d
+```
+
+Limited Mode is intended for testing and development purposes, so please avoid
+using it in a production environment. For production deployments, consider using
+one of the other installation methods that will provide both the advanced
+features of Headplane and a more robust deployment.
+
+Limited Mode also technically supports
+[Single Sign-On (SSO) authentication](../features/sso.md), but some parts of it
+may not work as expected. For a full-featured experience with SSO, please use
+one of the other installation methods.
+
diff --git a/docs/install/native-mode.md b/docs/install/native-mode.md
new file mode 100644
index 0000000..62f86f5
--- /dev/null
+++ b/docs/install/native-mode.md
@@ -0,0 +1,251 @@
+---
+title: Native Mode
+description: Install Headplane without Docker.
+outline: [2, 3]
+---
+
+# Native Mode
+::: tip
+If you are looking to deploy with Docker, follow the
+[**Docker**](./docker.md) deployment guide.
+:::
+
+Headplane can be installed and run directly on your host system without the need
+for Docker. This method is suitable for users who already run Headscale natively
+or prefer to avoid containers.
+
+## Prerequisites
+- A Linux-based operating system (e.g, Ubuntu, Debian, CentOS, Fedora)
+- Go version 1.25.1 installed (only needed to build Headplane)
+- Node.js version 22.16.x and [pnpm](https://pnpm.io/) version 10.4.x installed
+- Headscale version 0.26.0 or later installed and running
+- A [completed configuration file](./index.md#configuration) for Headplane.
+
+Before building and running Headplane, ensure that the directory defined in
+`server.data_path` in your configuration exists and is writable by the user who
+will run Headplane.
+
+```bash
+# Adjust as needed and set a custom user if you desire
+sudo mkdir -p /var/lib/headplane
+sudo chown -R $(whoami):$(whoami) /var/lib/headplane
+```
+
+## Building Headplane
+Clone the Headplane repository, install dependencies, and build the project:
+
+```bash
+# You can optionally checkout a specific release tag.
+git clone https://github.com/tale/headplane.git
+cd headplane
+pnpm install
+pnpm build
+```
+
+## Running Headplane
+Running Headplane is as straightforward as running `pnpm start` (or also
+directly with `node build/server/index.js`). Headplane will look for a config
+file at `/etc/headplane/config.yaml` by default, but you can specify a different
+path by setting the `HEADPLANE_CONFIG_PATH` environment variable.
+
+> Ensure that the `build/` directory exists relative to where the start command
+> is run, otherwise Headplane will not be able to find the frontend assets.
+
+
+Example Systemd service file
+
+Slotting this file into `/etc/systemd/system/headplane.service` will allow
+you to manage Headplane via systemd. Adjust the paths and user as needed,
+run `sudo systemctl daemon-reload`, and then enable/start the service.
+
+```ini
+[Unit]
+Description=Headplane Service
+After=network.target # (or headscale.service if it runs via systemd)
+Requires=network.target # (or headscale.service if it runs via systemd)
+StartLimitIntervalSec=0
+
+[Service]
+Type=simple
+User=your-username
+WorkingDirectory=/path/to/your/cloned/headplane
+ExecStart=/usr/bin/node /path/to/your/cloned/headplane/build/server/index.js
+Restart=on-failure
+RestartSec=5s
+
+# Uncomment and set if using a custom config path
+# Environment=HEADPLANE_CONFIG_PATH=/path/to/your/config.yaml
+
+[Install]
+WantedBy=multi-user.target
+```
+
+
+To access Headplane, navigate to `http://localhost:3000/admin` in your web
+browser (replace `localhost` with your server's IP address or domain name if
+not running locally).
+
+In order to log in, you'll need to supply a Headscale API key. You can create
+one by running the following command within your Headscale environment:
+
+```bash
+# You may want to tweak the expiration duration as needed
+headscale apikeys create --expiration 90d
+```
+
+## Enabling advanced features
+
+You've technically completed the installation, but read on if you would like
+to enable advanced features like the ability to edit network settings from the
+UI or remote SSH from the browser.
+
+### Network Management
+
+Network management allows you to configure Tailnet settings such as DNS servers,
+custom A records, the tailnet domain name, and MagicDNS from the Headplane UI.
+
+#### Prerequisites
+Network management (and other configurable Headscale features) requires that
+Headplane and Headscale both run on the same machine because Headplane needs
+
+- Access to read and write the Head**scale** configuration file
+- Access to read the `/proc` filesystem on Linux to locate Headscale's process
+
+#### Configuration
+
+Enabling network management is as simple as setting a few additional fields in
+your Headplane configuration file:
+
+| Field | Description |
+|---------------------|--------------------------------------------------------|
+| **`integration.proc.enabled`** | Set to `true` to enable process inspection. |
+| **`headscale.config_path`** | Path to your Head**scale** configuration file (e.g., `/etc/headscale/config.yaml`). |
+| `headscale.dns_records_path` | *Optional*. Refer to the [example configuration](https://github.com/tale/headplane/blob/main/config.example.yaml) for details. |
+
+With these settings in place, restart Headplane. You should now see additional
+options in the UI navbar such as "DNS" and "Settings" where you can manage your
+Tailnet configuration.
+
+### Remote Web SSH
+
+Remote Web SSH allows you to open a terminal session to your Tailscale nodes
+directly from the Headplane web interface via
+[Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh). This feature
+requires that Tailscale SSH is running on your nodes (done via
+`tailscale up --ssh`).
+
+This feature uses the [Headplane Agent](../features/agent.md) to facilitate the
+SSH connections. Refer to the [Agent documentation](../features/agent.md) for
+setup instructions and specifically follow the
+[native mode configuration](../features/agent.md#native-mode-configuration)
+section to point Headplane to the correct agent location.
+
+
+### Single Sign-On (SSO)
+
+Single Sign-On (SSO) authentication allows users to log in to Headplane using
+external identity providers such as Google, GitHub, or any provider that
+supports OpenID Connect (OIDC).
+
+To get started with SSO, refer to the [SSO documentation](../features/sso.md)
+for detailed setup instructions.
+
+
+## Reverse Proxying
+
+You *should* run Headplane behind a reverse proxy such as Nginx or Caddy in
+production. Additionally, putting Headscale beind the reverse proxy allows
+you to access both services via the same domain and TLS certificate.
+
+#### Configuration
+::: tip
+If you are using a [custom path prefix](#custom-path-prefix) for Headplane,
+adjust the `/admin` paths in the examples below accordingly.
+:::
+
+Headscale supports integrating with
+[several reverse proxies](https://headscale.net/stable/ref/integration/reverse-proxy/)
+such as Nginx, Caddy, Apache, etc. Deploying Headplane is as simple as adding
+a handler to route any requests to `/admin` to the Headplane service. Refer
+to the Nginx example below for a reference configuration. A similar setup via
+Traefik in Docker is available in the [Docker](./docker.md#reverse-proxying)
+installation documentation.
+
+#### Example Nginx Configuration
+
+The following configuration will set up Nginx to proxy all Headscale requests
+on `headscale.example.com` and serve the Headplane UI under the `/admin` path.
+This is identical to how Tailscale's own admin console is served.
+
+
+Nginx config snippet
+
+```nginx
+server {
+ listen 80;
+ listen [::]:80;
+
+ listen 443 ssl http2;
+ listen [::]:443 ssl http2;
+ server_name headscale.example.com;
+
+ # Or use LetsEncrypt with Certbot (up to you)
+ ssl_certificate /path/to/your/fullchain.pem;
+ ssl_certificate_key /path/to/your/privkey.pem;
+ ssl_protocols TLSv1.2 TLSv1.3;
+
+ location / { # Headscale runs on the root path
+ proxy_pass http://localhost:8080/; # Adjust if Headscale runs on a different port
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ proxy_set_header Host $host;
+ proxy_redirect http:// https://;
+ proxy_buffering off;
+
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+ }
+
+ location /admin/ { # Headplane is served under /admin
+ proxy_pass http://localhost:3000/; # Adjust if Headplane runs on a different port
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ proxy_set_header Host $host;
+ proxy_redirect http:// https://;
+ proxy_buffering off;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+
+## Custom Path Prefix
+
+::: warning
+The only officially supported path prefix for Headplane is `/admin`. Using a
+custom path prefix may lead to unexpected issues and is not recommended.
+:::
+
+If for whatever reason you do not want to serve Headplane under `/admin`
+(e.g., you want to serve it under `/headplane`), you can set the prefix
+while building Headplane via the `__INTERNAL_PREFIX` environment variable.
+
+```bash
+# Example for /headplane prefix
+git clone
+cd headplane
+pnpm install
+# Set the prefix here
+__INTERNAL_PREFIX=/headplane pnpm build
+```
+
+When running Headplane, all requests will only be served under the specified
+path. Make sure to also adjust your reverse proxy configuration accordingly if
+you are using one. Additionally, if you want to change the path prefix again,
+you will need to rebuild Headplane with the new prefix.
diff --git a/docs/introduction.md b/docs/introduction.md
new file mode 100644
index 0000000..4fb3fa3
--- /dev/null
+++ b/docs/introduction.md
@@ -0,0 +1,38 @@
+---
+title: Introduction
+description: What is Headplane?
+---
+
+# What is Headplane (and Headscale)?
+
+Headplane is a web-based UI that bridges the transforms Headscale into a
+feature-rich VPN platform that rivals Tailscale's official offering. Headscale
+is a self-hosted implementation of the Tailscale control server, enabling users
+to create and manage their own private VPN networks via the Tailscale client.
+
+
+
+
+Headscale does not ship with any web UI by default, which is where Headplane
+comes in. Headplane provides a comprehensive web interface for managing your
+Headscale instance, allowing you to easily administer your nodes, networks, and
+ACLs.
+
+It goes beyond basic management features, offering advanced capabilities such as
+remote web-based SSH access to your nodes, single sign-on (SSO) via OpenID
+Connect (OIDC), and detailed insights into your Tailnet's configuration and
+status. Things like DNS management, ACL editing, and Headscale configuration can
+all be done directly from the Headplane UI when compared to other Headscale UIs.
+
+
+
+
+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
+- The ability to edit DNS settings and automatically provision Headscale
+- Configurability for Headscale's settings