[009] Add Headscale stack behind Caddy at remote.waldson.com.br
Headscale (the self-hosted Tailscale control plane) runs as a new stack on proxy_net, following the house pattern of syncthing/forgejo/ntfy: own directory, own compose file, committed config, git-ignored bind-mounted state, CLI-only admin via docker exec. - headscale/config.yaml: server_url https://remote.waldson.com.br, MagicDNS with base domain ts.waldson.com.br, embedded DERP relay with the upstream Tailscale DERP map dropped so relayed traffic stays on the droplet, HA subnet-router route failover, SQLite state under /var/lib/headscale. The CLI unix socket is relocated into the data dir so the UID 1000 container can use it. - headscale/compose.yaml: image pinned to the v0.29 minor, runs as 1000:1000, publishes only STUN on 3478/udp; gRPC not exposed. - Caddyfile: remote.waldson.com.br reverse-proxies to headscale-headscale-1:8080. - README: Headscale stack section (deploy, upgrade, user/node CLI runbook), firewall table row for 3478/udp, DNS row for remote.waldson.com.br.
This commit is contained in:
parent
04df6b9c09
commit
6da5b3a8ab
4 changed files with 320 additions and 5 deletions
146
headscale/config.yaml
Normal file
146
headscale/config.yaml
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Headscale coordination server for the owner's self-hosted tailnet, reached at
|
||||
# https://remote.waldson.com.br through the central Caddy proxy. This file is
|
||||
# the committed posture: MagicDNS, the embedded DERP relay, and the SQLite data
|
||||
# layout are all reproduced on every recreate instead of depending on runtime
|
||||
# state. Only the SQLite database and the auto-generated private keys (under
|
||||
# /var/lib/headscale, the git-ignored bind mount) are mutable at runtime.
|
||||
#
|
||||
# Schema tracks the pinned image (headscale/headscale:v0.29). When bumping the
|
||||
# minor tag, diff this against that release's config-example.yaml: Headscale's
|
||||
# config schema changes between minors.
|
||||
|
||||
# Public login URL every client connects to. Caddy terminates TLS for this
|
||||
# hostname and reverse-proxies to listen_addr below. DERP requires https here.
|
||||
server_url: https://remote.waldson.com.br
|
||||
|
||||
# Bind on all interfaces inside the container so Caddy can reach it by name on
|
||||
# proxy_net (headscale-headscale-1:8080). The port is never published to the
|
||||
# host; only Caddy on 80/443 fronts it, the same shape as the other stacks.
|
||||
listen_addr: 0.0.0.0:8080
|
||||
|
||||
# Metrics and gRPC stay bound to loopback inside the container and are never
|
||||
# published. Administration is done with `docker exec ... headscale` over the
|
||||
# unix socket below, so the gRPC remote-CLI surface is not exposed.
|
||||
metrics_listen_addr: 127.0.0.1:9090
|
||||
grpc_listen_addr: 127.0.0.1:50443
|
||||
grpc_allow_insecure: false
|
||||
|
||||
# Caddy is the only thing in front of Headscale; trust its forwarding headers so
|
||||
# logs and rate limiting see the real client IP. Caddy reaches Headscale from
|
||||
# its own container on proxy_net, so trust that private range.
|
||||
trusted_proxies:
|
||||
- 172.16.0.0/12
|
||||
|
||||
noise:
|
||||
private_key_path: /var/lib/headscale/noise_private.key
|
||||
|
||||
# Tailnet address pools. Left at the standard Tailscale CGNAT/ULA ranges.
|
||||
prefixes:
|
||||
v4: 100.64.0.0/10
|
||||
v6: fd7a:115c:a1e0::/48
|
||||
allocation: sequential
|
||||
|
||||
# Embedded DERP relay: run our own relay and STUN so even traffic that cannot go
|
||||
# direct is relayed through this droplet, never through Tailscale Inc. The
|
||||
# upstream DERP map is dropped (urls: [] and no auto-update) so the tailnet uses
|
||||
# only this region.
|
||||
derp:
|
||||
server:
|
||||
enabled: true
|
||||
region_id: 999
|
||||
region_code: "waldson"
|
||||
region_name: "Waldson Embedded DERP"
|
||||
verify_clients: true
|
||||
# STUN for NAT traversal. Published on the host as 3478/udp (the one new
|
||||
# firewall row this stack adds); mandatory when the embedded DERP is on.
|
||||
stun_listen_addr: "0.0.0.0:3478"
|
||||
private_key_path: /var/lib/headscale/derp_server_private.key
|
||||
automatically_add_embedded_derp_region: true
|
||||
# Droplet public IPv4 so clients can reach this DERP region directly for
|
||||
# better connection stability. No IPv6 line: the droplet has no stable
|
||||
# public IPv6, and advertising a bogus one would send clients to a dead
|
||||
# address.
|
||||
ipv4: 174.138.36.232
|
||||
# No external DERP maps: relayed traffic must stay on our own infrastructure.
|
||||
urls: []
|
||||
paths: []
|
||||
auto_update_enabled: false
|
||||
update_frequency: 3h
|
||||
|
||||
disable_check_updates: false
|
||||
|
||||
node:
|
||||
expiry: 0
|
||||
ephemeral:
|
||||
inactivity_timeout: 30m
|
||||
# HA subnet-router health probing. Kept on so that when the Home Assistant
|
||||
# box (primary) and mercurio (failover) both advertise 192.168.0.0/24, the
|
||||
# primary role fails over to whichever is healthy. This is the route-failover
|
||||
# support the tailnet topology depends on.
|
||||
routes:
|
||||
ha:
|
||||
probe_interval: 10s
|
||||
probe_timeout: 5s
|
||||
|
||||
database:
|
||||
type: sqlite
|
||||
debug: false
|
||||
gorm:
|
||||
prepare_stmt: true
|
||||
parameterized_queries: true
|
||||
skip_err_record_not_found: true
|
||||
slow_threshold: 1000
|
||||
sqlite:
|
||||
path: /var/lib/headscale/db.sqlite
|
||||
write_ahead_log: true
|
||||
wal_autocheckpoint: 1000
|
||||
|
||||
# TLS is terminated by Caddy, so Headscale serves plain HTTP on listen_addr and
|
||||
# manages no certificates of its own.
|
||||
tls_letsencrypt_hostname: ""
|
||||
tls_letsencrypt_challenge_type: HTTP-01
|
||||
tls_cert_path: ""
|
||||
tls_key_path: ""
|
||||
|
||||
log:
|
||||
level: info
|
||||
format: text
|
||||
|
||||
policy:
|
||||
# Allow-all within the tailnet: single-user fleet, the tailnet boundary is the
|
||||
# security boundary. An empty path keeps the built-in allow-all default.
|
||||
mode: file
|
||||
path: ""
|
||||
|
||||
# MagicDNS so devices reach each other by name (ssh mercurio). The base domain
|
||||
# is deliberately different from server_url's host, which Headscale requires,
|
||||
# and exists only inside the tailnet (no public records for it).
|
||||
dns:
|
||||
magic_dns: true
|
||||
base_domain: ts.waldson.com.br
|
||||
override_local_dns: true
|
||||
nameservers:
|
||||
global:
|
||||
- 1.1.1.1
|
||||
- 1.0.0.1
|
||||
- 2606:4700:4700::1111
|
||||
- 2606:4700:4700::1001
|
||||
split: {}
|
||||
search_domains: []
|
||||
extra_records: []
|
||||
|
||||
# CLI connects over this unix socket with no auth. Relocated from the default
|
||||
# /var/run/headscale (root-owned, not writable by our non-root container) into
|
||||
# the bind-mounted data dir, so the UID 1000 process can create it and
|
||||
# `docker exec ... headscale` works.
|
||||
unix_socket: /var/lib/headscale/headscale.sock
|
||||
unix_socket_permission: "0770"
|
||||
|
||||
logtail:
|
||||
enabled: false
|
||||
|
||||
taildrop:
|
||||
enabled: true
|
||||
|
||||
auto_update:
|
||||
enabled: false
|
||||
Loading…
Add table
Add a link
Reference in a new issue