headscale rejects autogroups as tagOwners (only user/group:/tag:), which made the shipped policy fatal. Replace with a valid allow-all default plus correct commented examples for tightening. Document gating /admin to a pocket-id superuser group via the headplane client's Allowed User Groups. Rebuild archive.
95 lines
3.4 KiB
Markdown
95 lines
3.4 KiB
Markdown
# headscale
|
|
|
|
[Headscale](https://headscale.net) — a self-hosted Tailscale control server —
|
|
behind Caddy, with OIDC login delegated to [pocket-id](../pocket-id/).
|
|
|
|
## Prerequisite
|
|
|
|
Register an OIDC client in pocket-id's admin UI with redirect URI:
|
|
|
|
```
|
|
https://${HEADSCALE_DOMAIN}/oidc/callback
|
|
```
|
|
|
|
Then paste its `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` below.
|
|
|
|
## Required `.env` values
|
|
|
|
| Variable | Notes |
|
|
|----------|-------|
|
|
| `HEADSCALE_DOMAIN` | Public hostname (e.g. `hs.example.com`). Clients connect here over HTTPS. |
|
|
| `ACME_EMAIL` | Let's Encrypt registration email. |
|
|
| `TAILNET_DOMAIN` | MagicDNS suffix (e.g. `tail.example.com`). Must differ from `HEADSCALE_DOMAIN`. |
|
|
| `POCKETID_DOMAIN` | OIDC issuer hostname (your pocket-id). |
|
|
| `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | From the pocket-id client above. |
|
|
|
|
The deploy substitutes these into `config.yaml` from the embedded template. See
|
|
[`.env.example`](.env.example).
|
|
|
|
## Deploy
|
|
|
|
```bash
|
|
./automations.sh # Deploy on this host → deploy: headscale
|
|
```
|
|
|
|
Or build + run the self-contained artifact:
|
|
|
|
```bash
|
|
./build.sh
|
|
scp deploy.sh root@host:
|
|
ssh root@host 'bash deploy.sh'
|
|
# non-interactive: pass HEADSCALE_DOMAIN, ACME_EMAIL, TAILNET_DOMAIN,
|
|
# POCKETID_DOMAIN, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, SKIP_PROMPTS=1
|
|
```
|
|
|
|
Unattended provisioning: [`cloud-init.yml`](cloud-init.yml).
|
|
|
|
DNS for `HEADSCALE_DOMAIN` must resolve to the host and 80/443 be reachable
|
|
before deploy.
|
|
|
|
## Headplane (web UI)
|
|
|
|
[Headplane](https://github.com/tale/headplane) ships in the stack, served by
|
|
Caddy at **`https://HEADSCALE_DOMAIN/admin`**. It runs **API-only** — it talks
|
|
to the headscale API with a key and does *not* get the Docker socket, so it
|
|
can't control the host. `deploy.sh` mints the headscale API key automatically
|
|
on first deploy (stored in `.env`) and generates `headplane.yaml`.
|
|
|
|
**Login:** to use OIDC via pocket-id, register a **second** OIDC client in
|
|
pocket-id with redirect URI:
|
|
|
|
```
|
|
https://HEADSCALE_DOMAIN/admin/oidc/callback
|
|
```
|
|
|
|
and pass `HEADPLANE_OIDC_CLIENT_ID` / `HEADPLANE_OIDC_CLIENT_SECRET` (prompted
|
|
by the launcher, or set in `.env`). Leave them blank to use headplane's
|
|
API-key login instead. Pin the image with `HEADPLANE_TAG` (defaults to
|
|
`latest`).
|
|
|
|
**Gate `/admin` to a superuser group:** restrict it at pocket-id — edit the
|
|
headplane OIDC client and set its **Allowed User Groups** to your superuser
|
|
group. Only members of that group can complete the OIDC login, so only they can
|
|
reach `/admin`. (Enforced at the IdP, so it covers the UI regardless of
|
|
headplane's own checks.)
|
|
|
|
> headplane integration is version-sensitive; if the UI doesn't come up,
|
|
> check `docker compose logs headplane` and verify `headplane.yaml` /
|
|
> the minted `HEADPLANE_HS_API_KEY` in `.env`.
|
|
|
|
## ACL policy
|
|
|
|
A starter ACL ships in [`policy.hujson`](policy.hujson) (installed to
|
|
`$STACK_DIR/policy.hujson` on first deploy; your edits survive re-deploys).
|
|
**headscale uses Tailscale's legacy `acls` format, not the newer `grants`
|
|
syntax** from the Tailscale admin console — the bundled policy is that default
|
|
translated over (per-user self-access, a `tag:shared` everyone can reach, and
|
|
SSH to your own devices). Some pieces (`autogroup:self`, the `ssh` block) are
|
|
newer/experimental in headscale; a permissive fallback is included in the file.
|
|
|
|
```bash
|
|
$EDITOR /srv/headscale/policy.hujson
|
|
headscale policy check # validate (host CLI wrapper)
|
|
cd /srv/headscale && docker compose restart headscale # apply
|
|
```
|