diff --git a/README.md b/README.md index 76e9c39..d0125c1 100644 --- a/README.md +++ b/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) -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. + + + + ACLs + -> Copyright (c) 2024 Aarnav Tale + + + + Machine Management + + +> Copyright (c) 2025 Aarnav Tale diff --git a/assets/acls-dark.png b/assets/acls-dark.png index ce5c742..c778814 100644 Binary files a/assets/acls-dark.png and b/assets/acls-dark.png differ diff --git a/assets/acls-light.png b/assets/acls-light.png index 5275231..b8f1a8e 100644 Binary files a/assets/acls-light.png and b/assets/acls-light.png differ diff --git a/assets/dns-dark.png b/assets/dns-dark.png new file mode 100644 index 0000000..5ac108b Binary files /dev/null and b/assets/dns-dark.png differ diff --git a/assets/dns-light.png b/assets/dns-light.png new file mode 100644 index 0000000..35ec39b Binary files /dev/null and b/assets/dns-light.png differ diff --git a/assets/integration-dark.png b/assets/integration-dark.png deleted file mode 100644 index c5c388f..0000000 Binary files a/assets/integration-dark.png and /dev/null differ diff --git a/assets/integration-light.png b/assets/integration-light.png deleted file mode 100644 index 22a34c3..0000000 Binary files a/assets/integration-light.png and /dev/null differ diff --git a/assets/machine-dark.png b/assets/machine-dark.png new file mode 100644 index 0000000..1ea748b Binary files /dev/null and b/assets/machine-dark.png differ diff --git a/assets/machine-light.png b/assets/machine-light.png new file mode 100644 index 0000000..0aae488 Binary files /dev/null and b/assets/machine-light.png differ diff --git a/assets/preview-dark.png b/assets/preview-dark.png index 0037a89..10b66fa 100644 Binary files a/assets/preview-dark.png and b/assets/preview-dark.png differ diff --git a/assets/preview-light.png b/assets/preview-light.png index eff1411..ffdf41c 100644 Binary files a/assets/preview-light.png and b/assets/preview-light.png differ diff --git a/config.example.yaml b/config.example.yaml index cb3df4f..309db63 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -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, diff --git a/docs/Advanced-Integration.md b/docs/Advanced-Integration.md deleted file mode 100644 index b4bcefe..0000000 --- a/docs/Advanced-Integration.md +++ /dev/null @@ -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 - - - - - Integration Preview - - -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) - - - - - ACL Preview - - -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. diff --git a/docs/Bare-Metal.md b/docs/Bare-Metal.md new file mode 100644 index 0000000..3424a4c --- /dev/null +++ b/docs/Bare-Metal.md @@ -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. diff --git a/docs/Basic-Integration.md b/docs/Basic-Integration.md deleted file mode 100644 index 9f1c4c2..0000000 --- a/docs/Basic-Integration.md +++ /dev/null @@ -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. diff --git a/docs/Configuration.md b/docs/Configuration.md index a4401e8..abdbf57 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -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_
__`**. +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 diff --git a/docs/Integrated-Mode.md b/docs/Integrated-Mode.md new file mode 100644 index 0000000..c2d0341 --- /dev/null +++ b/docs/Integrated-Mode.md @@ -0,0 +1,181 @@ +# Integrated Mode + + + + + Integration Preview + + +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 +``` diff --git a/docs/Simple-Mode.md b/docs/Simple-Mode.md new file mode 100644 index 0000000..b99c0ec --- /dev/null +++ b/docs/Simple-Mode.md @@ -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. diff --git a/docs/integration/Docker.md b/docs/integration/Docker.md deleted file mode 100644 index 083dc55..0000000 --- a/docs/integration/Docker.md +++ /dev/null @@ -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://: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. diff --git a/docs/integration/Kubernetes.md b/docs/integration/Kubernetes.md deleted file mode 100644 index 0059503..0000000 --- a/docs/integration/Kubernetes.md +++ /dev/null @@ -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. diff --git a/docs/integration/Native.md b/docs/integration/Native.md deleted file mode 100644 index 84e020d..0000000 --- a/docs/integration/Native.md +++ /dev/null @@ -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. diff --git a/server/context/loader.ts b/server/context/loader.ts index bd1b681..f453c7e 100644 --- a/server/context/loader.ts +++ b/server/context/loader.ts @@ -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',