diff --git a/README.md b/README.md index 15e1240..941d077 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Each stack is independently deployable with its own `compose.yml` and `.env`. Al | `coolify` | Deployment platform | coolify, realtime, redis, db | | `passbolt` | Password manager (GPG-encrypted, team sharing) | passbolt, db | | `code` | Browser-based VS Code IDE with Anthropic API access | code | +| `headscale` | Self-hosted Tailscale control server (WireGuard mesh VPN) | headscale | ## Config files diff --git a/headscale/.env.example b/headscale/.env.example new file mode 100644 index 0000000..71767e4 --- /dev/null +++ b/headscale/.env.example @@ -0,0 +1,2 @@ +TRAEFIK_HOST=headscale.example.com +NETWORK_NAME=falcon_network diff --git a/headscale/compose.yml b/headscale/compose.yml new file mode 100644 index 0000000..6e945d9 --- /dev/null +++ b/headscale/compose.yml @@ -0,0 +1,37 @@ +services: + headscale: + image: headscale/headscale:latest + container_name: headscale + command: ["serve"] + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + HEADSCALE_SERVER_URL: https://${TRAEFIK_HOST} + volumes: + - ./config.yaml:/etc/headscale/config.yaml:ro + - ../.data/headscale/data:/var/lib/headscale + - ../.data/headscale/run:/var/run/headscale + restart: always + healthcheck: + test: ["CMD", "/ko-app/headscale", "health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + labels: + - "traefik.enable=true" + - "traefik.http.middlewares.headscale-redirect-web-secure.redirectscheme.scheme=https" + - "traefik.http.routers.headscale-web.middlewares=headscale-redirect-web-secure" + - "traefik.http.routers.headscale-web.rule=Host(`${TRAEFIK_HOST}`)" + - "traefik.http.routers.headscale-web.entrypoints=web" + - "traefik.http.routers.headscale-web-secure.rule=Host(`${TRAEFIK_HOST}`)" + - "traefik.http.routers.headscale-web-secure.tls.certresolver=resolver" + - "traefik.http.routers.headscale-web-secure.entrypoints=web-secure" + - "traefik.http.routers.headscale-web-secure.middlewares=security-headers@file,no-index@file" + - "traefik.http.services.headscale-web-secure.loadbalancer.server.port=8080" + - "traefik.docker.network=${NETWORK_NAME}" + networks: + - compose_network +networks: + compose_network: + name: ${NETWORK_NAME} + external: true diff --git a/headscale/config.yaml b/headscale/config.yaml new file mode 100644 index 0000000..dd98cd6 --- /dev/null +++ b/headscale/config.yaml @@ -0,0 +1,64 @@ +# Base Headscale config. Host-specific values (server_url, listen addrs) are +# overridden via HEADSCALE_* environment variables in compose.yml, following +# headscale's viper env-binding (HEADSCALE_SERVER_URL overrides server_url, etc). +# Full reference: https://github.com/juanfont/headscale/blob/main/config-example.yaml + +server_url: https://headscale.example.com +listen_addr: 0.0.0.0:8080 +metrics_listen_addr: 127.0.0.1:9090 +grpc_listen_addr: 127.0.0.1:50443 +grpc_allow_insecure: false + +noise: + private_key_path: /var/lib/headscale/noise_private.key + +prefixes: + v4: 100.64.0.0/10 + v6: fd7a:115c:a1e0::/48 + allocation: sequential + +derp: + server: + enabled: false + urls: + - https://controlplane.tailscale.com/derpmap/default + paths: [] + auto_update_enabled: true + update_frequency: 24h + +disable_check_updates: false + +node: + ephemeral: + inactivity_timeout: 30m + +database: + type: sqlite + sqlite: + path: /var/lib/headscale/db.sqlite + write_ahead_log: true + +unix_socket: /var/run/headscale/headscale.sock +unix_socket_permission: "0770" + +log: + format: text + level: info + +policy: + mode: file + path: "" + +dns: + magic_dns: true + base_domain: hs.internal + nameservers: + global: + - 1.1.1.1 + - 1.0.0.1 + search_domains: [] + +# TLS is terminated by Traefik; headscale itself serves plain HTTP internally. +tls_letsencrypt_hostname: "" +tls_cert_path: "" +tls_key_path: ""