163 lines
4.5 KiB
Markdown
163 lines
4.5 KiB
Markdown
# Headscale pilot on PD
|
|
|
|
Self-hosted Headscale + Headplane pilot stack for replacing the Tailscale free-tier 3-user limit without doing a blind cutover.
|
|
|
|
## Why this exists
|
|
|
|
- remove the 3-human-user ceiling from the current Tailscale free-tier setup
|
|
- keep the control plane on PD, the long-term primary Docker host
|
|
- prove a small household-safe access model before expanding scope
|
|
- keep the repo copy as the source of truth for stack config and policy
|
|
|
|
## Pilot shape
|
|
|
|
- Host: **PlausibleDeniability**
|
|
- Live path: `/mnt/docker-ssd/docker/compose/headscale`
|
|
- Services:
|
|
- `headscale`
|
|
- `headplane`
|
|
- Data paths:
|
|
- `/mnt/docker-ssd/docker/appdata/headscale`
|
|
- `/mnt/docker-ssd/docker/appdata/headplane`
|
|
- No OIDC on day one
|
|
- No subnet-router or exit-node rollout on day one
|
|
- No public Pangolin exposure on day one
|
|
|
|
## URLs for the pilot
|
|
|
|
- Headscale control plane: `http://headscale.home.paccoco.com:8084`
|
|
- Headplane UI: `http://headplane.home.paccoco.com:3005/admin`
|
|
|
|
These are intentionally LAN/private-DNS endpoints for the pilot. If you later want Traefik/Pangolin exposure, use `examples/traefik-routes.yml` as a starting point instead of changing the pilot shape first.
|
|
|
|
## Initial users
|
|
|
|
- `fizzlepoof`
|
|
- `manndra`
|
|
|
|
Policy references them as:
|
|
- `fizzlepoof@`
|
|
- `manndra@`
|
|
|
|
## Initial tags
|
|
|
|
- `tag:infra`
|
|
- `tag:apps`
|
|
- `tag:admin`
|
|
|
|
## Access model
|
|
|
|
- `fizzlepoof@` = full pilot admin access to `tag:infra`, `tag:apps`, `tag:admin`, and his own devices
|
|
- `manndra@` = app access only via `tag:apps` plus her own devices
|
|
- no limited-user tier yet
|
|
- no routed-LAN access yet
|
|
|
|
## Files
|
|
|
|
- `docker-compose.yaml`
|
|
- `.env.example`
|
|
- `config/headscale/config.yaml`
|
|
- `config/headscale/policy.hujson`
|
|
- `config/headplane/config.yaml`
|
|
- `examples/traefik-routes.yml`
|
|
|
|
## Pre-deploy edits
|
|
|
|
Before first deploy, replace the placeholder values in:
|
|
|
|
- `.env` copied from `.env.example`
|
|
- `config/headplane/config.yaml`
|
|
|
|
Required changes:
|
|
- replace `CHANGE_ME_TO_EXACTLY_32_CHARS` with a real 32-character cookie secret
|
|
- confirm the hostnames/IP-backed DNS records exist in Technitium
|
|
- if you want different ports or hostnames, update both the config files and `.env`
|
|
|
|
## Deploy on PD
|
|
|
|
1. Sync this directory into the live compose tree:
|
|
- `/mnt/docker-ssd/docker/compose/headscale`
|
|
2. Copy `.env.example` to `.env`
|
|
3. Create persistent appdata paths:
|
|
- `/mnt/docker-ssd/docker/appdata/headscale`
|
|
- `/mnt/docker-ssd/docker/appdata/headscale/run`
|
|
- `/mnt/docker-ssd/docker/appdata/headplane`
|
|
4. Validate:
|
|
|
|
```bash
|
|
cd /mnt/docker-ssd/docker/compose/headscale
|
|
docker compose --env-file .env config
|
|
```
|
|
|
|
5. Bring up Headscale first:
|
|
|
|
```bash
|
|
docker compose --env-file .env up -d headscale
|
|
docker logs headscale --tail=100
|
|
```
|
|
|
|
6. Create pilot users:
|
|
|
|
```bash
|
|
docker exec -it headscale headscale users create fizzlepoof
|
|
docker exec -it headscale headscale users create manndra
|
|
docker exec -it headscale headscale users list
|
|
```
|
|
|
|
7. Generate a Headscale API key for Headplane login:
|
|
|
|
```bash
|
|
docker exec -it headscale headscale apikeys create --expiration 90d
|
|
```
|
|
|
|
Save the returned key somewhere secure. Headplane uses it for the initial admin login.
|
|
|
|
8. Enroll John's admin device.
|
|
9. Bring up Headplane:
|
|
|
|
```bash
|
|
docker compose --env-file .env up -d headplane
|
|
docker logs headplane --tail=100
|
|
```
|
|
|
|
10. Log in to Headplane with the API key from step 7.
|
|
11. Register one tagged service node.
|
|
12. Enroll one Manndra device.
|
|
|
|
## Policy reload
|
|
|
|
After editing `config/headscale/policy.hujson`:
|
|
|
|
```bash
|
|
docker exec -it headscale kill -HUP 1
|
|
```
|
|
|
|
Then inspect the container logs for policy parse results.
|
|
|
|
## What to validate before expanding
|
|
|
|
- Headscale stays healthy
|
|
- Headplane is usable enough to justify keeping it
|
|
- John can reach `tag:infra`, `tag:admin`, and `tag:apps`
|
|
- Manndra can reach `tag:apps` but not `tag:infra` or `tag:admin`
|
|
- one tagged service node registers cleanly and behaves as expected
|
|
- existing Tailscale remains intact as rollback during the pilot
|
|
|
|
## Notes on Headplane integration
|
|
|
|
This stack enables Headplane's Docker integration so it can identify the Headscale container via the label:
|
|
|
|
- `me.tale.headplane.target=headscale`
|
|
|
|
It also mounts the tracked Headscale config into Headplane so the UI can inspect and, if you later choose, manage more than just node registration.
|
|
|
|
## Not done here on purpose
|
|
|
|
- no OIDC wiring yet
|
|
- no DERP customization yet
|
|
- no public auth/proxy routing yet
|
|
- no explicit subnet-router policy yet
|
|
- no migration of the existing infrastructure stack's `tailscale` container yet
|
|
|
|
Pilot first. Cutover later.
|