Docker Compose

Il file docker-compose.yaml avvia due container: Headscale (il server di coordinamento vero e proprio) e Headplane (un’interfaccia web per gestirlo comodamente). Headplane comunica con Headscale tramite la rete interna e può riavviarlo grazie all’accesso al Docker socket.

yaml
services:
  headscale:
    image: headscale/headscale:latest
    container_name: headscale
    restart: unless-stopped
    command: serve
    port:
      - 3478:3478
    labels:
      # Necessaria: Headplane usa questa label per trovare il container
      # tramite il Docker socket e poterlo riavviare quando cambi DNS/config
      me.tale.headplane.target: headscale
    volumes:
      - ./headscale-config/headscale.yaml:/etc/headscale/config.yaml:ro
      - ./headscale-config/acl.hjson:/etc/headscale/acl.hjson:ro
      - ./headscale-data:/var/lib/headscale
    networks:
      - internal
      - caddy_network

  headplane:
    image: ghcr.io/tale/headplane:0.6.2-beta.5
    container_name: headplane
    restart: unless-stopped
    volumes:
      # Config propria di Headplane
      - ./headplane-config/headplane.yaml:/etc/headplane/config.yaml:ro
      # Storage persistente (database interno, cache)
      - ./headplane-data:/var/lib/headplane
      # Config di Headscale (read-only) — serve a Headplane per mostrare
      # le impostazioni nella UI. Il path interno deve corrispondere
      # a headscale.config_path nel headplane.yaml
      - ./headscale-config/headscale.yaml:/etc/headscale/config.yaml:ro
      # Docker socket per l'integrazione (restart Headscale da UI)
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - headscale
    networks:
      - internal
      - caddy_network

networks:
  internal:
    driver: bridge
    internal: true
  caddy_network:
    external: true

Configurazione di Headscale

Il file headscale-config/headscale.yaml contiene la configurazione principale del server. Qui si definiscono l’URL pubblico, il database, i prefissi di rete e il server DERP integrato per il relay del traffico quando la connessione diretta tra i nodi non è possibile.

yaml
server_url: https://hs.gennaro.cc
listen_addr: 0.0.0.0:8080
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false
metrics_listen_addr: 0.0.0.0:9090

database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite

prefixes:
  v4: 100.64.0.0/10
  v6: fd7a:115c:a1e0::/48
  allocation: sequential

derp:
  server:
    enabled: true
    region_id: 999
    region_code: "homelab"
    region_name: "Homelab DERP"
    stun_listen_addr: 0.0.0.0:3478
    private_key_path: /var/lib/headscale/derp_server_private.key
  urls:
    - https://controlplane.tailscale.com/derpmap/default
  auto_update_enabled: true
  update_frequency: 24h

dns:
  magic_dns: true
  base_domain: tail.gennaro.cc
  nameservers:
    global:
      - 1.1.1.1
      - 8.8.8.8

log:
  level: info

private_key_path: /var/lib/headscale/private.key
noise:
  private_key_path: /var/lib/headscale/noise_private.key

policy:
  mode: file
  path: /etc/headscale/acl.hjson

ACL di Headscale

Le Access Control List si trovano nel file headscale-config/acl.hjson. In questo esempio la policy è completamente aperta: tutti i nodi possono raggiungere qualsiasi altro nodo su qualsiasi porta. I tag owner permettono di assegnare ruoli ai dispositivi direttamente dalla UI di Headplane.

json
{
  // Definisci un gruppo con il tuo utente
  "groups": {
    "group:admins": ["edoardo@"]
  },

  // Usa il gruppo come tag owner
  "tagOwners": {
    "tag:server":  ["group:admins"],
    "tag:test":    ["group:admins"],
    "tag:media":   ["group:admins"],
    "tag:admin":   ["group:admins"]
  },

  "acls": [
    {
      "action": "accept",
      "src": ["*"],
      "dst": ["*:*"]
    }
  ]
}

Configurazione di Headplane

Il file headplane-config/headplane.yaml configura l’interfaccia web. Headplane si collega a Headscale sulla rete interna Docker e gestisce l’autenticazione tramite OIDC. L’integrazione con il Docker socket permette di riavviare Headscale direttamente dalla UI quando si modificano DNS o altre impostazioni.

yaml
headscale:
  url: http://headscale:8080
  config_path: /etc/headscale/config.yaml
  config_strict: false

server:
  host: 0.0.0.0
  port: 3000
  cookie_secure: true
  cookie_secret: "aaaaaaaaaaaaaaaa"
  data_path: /var/lib/headplane

integration:
  docker:
    enabled: true
    socket: "unix:///var/run/docker.sock"
    container_name: headscale

oidc:
  disable_api_key_login: false
  token_endpoint_auth_method: "client_secret_post"
  issuer: "https://login.infomaniak.com"
  client_id: "a3512bae-a959-....-a5c6-f48d6baa97c0"
  client_secret: "ayb7np8sEP...6RwNuZ9TdV69BlPWzrVueYiMih"
  redirect_uri: "https://hs.gennaro.cc/admin/oidc/callback"
  headscale_api_key: "hskA...Iilnb"
  allowed_emails:
    - "email_account@domain.xx"