From 4aa75315370776b0fa4c2f108962503bf5309d2d Mon Sep 17 00:00:00 2001 From: waveringana Date: Sun, 1 Jun 2025 23:57:27 -0400 Subject: [PATCH] add headscale --- .gitignore | 5 +- headscale/docker-compose.yml | 58 ++++ headscale/headplane/config/config.yaml | 112 +++++++ headscale/headplane/users.json | 1 + headscale/headscale/config/config.yaml | 410 +++++++++++++++++++++++++ headscale/litestream.yml | 7 + 6 files changed, 592 insertions(+), 1 deletion(-) create mode 100644 headscale/docker-compose.yml create mode 100644 headscale/headplane/config/config.yaml create mode 100644 headscale/headplane/users.json create mode 100644 headscale/headscale/config/config.yaml create mode 100644 headscale/litestream.yml diff --git a/.gitignore b/.gitignore index 8272d53..c962add 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env +s3.env *.env.local *.env.[a-zA-Z0-9]* # Ignore specific environment files like .env.production, .env.staging, etc. @@ -6,4 +7,6 @@ vaultwarden/vw-data pocketid/data beszel/beszel_data beszel/beszel_socket -authentik \ No newline at end of file +authentik + +*.key \ No newline at end of file diff --git a/headscale/docker-compose.yml b/headscale/docker-compose.yml new file mode 100644 index 0000000..f854a58 --- /dev/null +++ b/headscale/docker-compose.yml @@ -0,0 +1,58 @@ +networks: + default: + name: "proxy_network" +services: + headscale: + image: headscale/headscale:v0.25.1 + restart: unless-stopped + ports: + - "8080:8080" + volumes: + - ./headscale/config:/etc/headscale + - ./headscale:/var/lib/headscale + command: serve + labels: + caddy: headscale.nekomimi.pet + caddy.reverse_proxy: "* {{upstreams 8080}}" + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv6.conf.all.forwarding=1 + dns: + - "1.1.1.1" + - "8.8.8.8" + + headplane: + image: ghcr.io/tale/headplane:0.5.10 + container_name: headplane + restart: unless-stopped + volumes: + - "./headplane/config/config.yaml:/etc/headplane/config.yaml" + - "./headscale/config/config.yaml:/etc/headscale/config.yaml" + - "./headplane:/var/lib/headplane" + - "/var/run/docker.sock:/var/run/docker.sock:ro" + labels: + caddy: hui.nekomimi.pet + caddy.reverse_proxy: "* {{upstreams 3000}}" + + litestream: + container_name: litestream + image: litestream/litestream:0.3.13 + restart: unless-stopped + volumes: + - "./headscale:/var/lib/headscale" + - "./litestream.yml:/etc/litestream.yml" + env_file: + - ./s3.env + command: ["replicate"] + + caddy: + image: "lucaslorentz/caddy-docker-proxy:ci-alpine" + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /srv/caddy/:/data + restart: unless-stopped + environment: + - CADDY_INGRESS_NETWORKS=proxy_network diff --git a/headscale/headplane/config/config.yaml b/headscale/headplane/config/config.yaml new file mode 100644 index 0000000..d32d3c8 --- /dev/null +++ b/headscale/headplane/config/config.yaml @@ -0,0 +1,112 @@ +# Configuration for the Headplane server and web application +server: + host: "0.0.0.0" + port: 3000 + + # The secret used to encode and decode web sessions + # Ensure that this is exactly 32 characters long + cookie_secret: "r!5{4UZzX9vRpGdj.^wB-b3h?CL(6#Vq" + + # Should the cookies only work over HTTPS? + # Set to false if running via HTTP without a proxy + # (I recommend this is true in production) + cookie_secure: true + +# Headscale specific settings to allow Headplane to talk +# to Headscale and access deep integration features +headscale: + # The URL to your Headscale instance + # (All API requests are routed through this URL) + # (THIS IS NOT the gRPC endpoint, but the HTTP endpoint) + # + # IMPORTANT: If you are using TLS this MUST be set to `https://` + url: "http://headscale:8080" + + # If you use the TLS configuration in Headscale, and you are not using + # Let's Encrypt for your certificate, pass in the path to the certificate. + # (This has no effect `url` does not start with `https://`) + # tls_cert_path: "/var/lib/headplane/tls.crt" + + # Optional, public URL if they differ + # This affects certain parts of the web UI + public_url: "https://headscale.nekomimi.pet" + + # Path to the Headscale configuration file + # This is optional, but HIGHLY recommended for the best experience + # If this is read only, Headplane will show your configuration settings + # in the Web UI, but they cannot be changed. + config_path: "/etc/headscale/config.yaml" + + # Headplane internally validates the Headscale configuration + # to ensure that it changes the configuration in a safe way. + # If you want to disable this validation, set this to false. + config_strict: true + +# Integration configurations for Headplane to interact with Headscale +# Only one of these should be enabled at a time or you will get errors +integration: + docker: + enabled: true + # The name (or ID) of the container running Headscale + container_name: "headscale" + # The path to the Docker socket (do not change this if you are unsure) + # Docker socket paths must start with unix:// or tcp:// and at the moment + # https connections are not supported. + socket: "unix:///var/run/docker.sock" + # Please refer to docs/integration/Kubernetes.md for more information + # on how to configure the Kubernetes integration. There are requirements in + # order to allow Headscale to be controlled by Headplane in a cluster. + kubernetes: + enabled: false + # Validates the manifest for the Pod to ensure all of the criteria + # are set correctly. Turn this off if you are having issues with + # shareProcessNamespace not being validated correctly. + validate_manifest: true + # This should be the name of the Pod running Headscale and Headplane. + # If this isn't static you should be using the Kubernetes Downward API + # to set this value (refer to docs/Integrated-Mode.md for more info). + pod_name: "headscale" + + # Proc is the "Native" integration that only works when Headscale and + # Headplane are running outside of a container. There is no configuration, + # but you need to ensure that the Headplane process can terminate the + # Headscale process. + # + # (If they are both running under systemd as sudo, this will work). + proc: + enabled: false + +# OIDC Configuration for simpler authentication +# (This is optional, but recommended for the best experience) +oidc: + issuer: "https://pocketid.nekomimi.pet" + client_id: "5d42faf9-636a-4ff9-90c2-6d9fa4a58a9f" + + # The client secret for the OIDC client + # Either this or `client_secret_path` must be set for OIDC to work + client_secret: "SNSR0EG0JU64LSUrpLDVxEjwf474ANSN" + + # You can alternatively set `client_secret_path` to read the secret from disk. + # The path specified can resolve environment variables, making integration + # with systemd's `LoadCredential` straightforward: + # client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret" + + disable_api_key_login: false + token_endpoint_auth_method: "client_secret_post" + + # If you are using OIDC, you need to generate an API key + # that can be used to authenticate other sessions when signing in. + # + # This can be done with `headscale apikeys create --expiration 999d` + headscale_api_key: "5rtHP12.W6SEUXXA0Fdf4rg9lPsXUBY-R96T-Oi_" + + # Optional, but highly recommended otherwise Headplane + # will attempt to automatically guess this from the issuer + # + # This should point to your publicly accessibly URL + # for your Headplane instance with /admin/oidc/callback + redirect_uri: "https://hui.nekomimi.pet/admin/oidc/callback" + + # Stores the users and their permissions for Headplane + # This is a path to a JSON file, default is specified below. + user_storage_file: "/var/lib/headplane/users.json" \ No newline at end of file diff --git a/headscale/headplane/users.json b/headscale/headplane/users.json new file mode 100644 index 0000000..fa1cc63 --- /dev/null +++ b/headscale/headplane/users.json @@ -0,0 +1 @@ +[{"u":"106894806700407619198","c":65535,"oo":true},{"u":"109904340892037651371","c":0,"oo":true},{"u":"2233cb996ee3c72044b262875d57b6a024efed025a3b256e0e7d7e568d9c42c9","c":32767},{"u":"5b5d5948-e6af-4f0b-b705-82e371fcd16f","c":32767},{"u":"577e2cf3-fe52-465b-9116-4df252c17283","c":1323},{"u":"012369e4-8251-485f-8376-861cba6cba9f","c":1323}] \ No newline at end of file diff --git a/headscale/headscale/config/config.yaml b/headscale/headscale/config/config.yaml new file mode 100644 index 0000000..742fe5b --- /dev/null +++ b/headscale/headscale/config/config.yaml @@ -0,0 +1,410 @@ +--- +# headscale will look for a configuration file named `config.yaml` (or `config.json`) in the following order: +# +# - `/etc/headscale` +# - `~/.headscale` +# - current working directory + +# The url clients will connect to. +# Typically this will be a domain like: +# +# https://myheadscale.example.com:443 +# +server_url: https://headscale.nekomimi.pet + +# Address to listen to / bind to on the server +# +# For production: +listen_addr: 0.0.0.0:8080 +#listen_addr: 127.0.0.1:8080 + +# Address to listen to /metrics and /debug, you may want +# to keep this endpoint private to your internal network +metrics_listen_addr: 127.0.0.1:9090 + +# Address to listen for gRPC. +# gRPC is used for controlling a headscale server +# remotely with the CLI +# Note: Remote access _only_ works if you have +# valid certificates. +# +# For production: +# grpc_listen_addr: 0.0.0.0:50443 +grpc_listen_addr: 127.0.0.1:50443 + +# Allow the gRPC admin interface to run in INSECURE +# mode. This is not recommended as the traffic will +# be unencrypted. Only enable if you know what you +# are doing. +grpc_allow_insecure: false + +# The Noise section includes specific configuration for the +# TS2021 Noise protocol +noise: + # The Noise private key is used to encrypt the traffic between headscale and + # Tailscale clients when using the new Noise-based protocol. A missing key + # will be automatically generated. + private_key_path: /var/lib/headscale/noise_private.key + +# List of IP prefixes to allocate tailaddresses from. +# Each prefix consists of either an IPv4 or IPv6 address, +# and the associated prefix length, delimited by a slash. +# It must be within IP ranges supported by the Tailscale +# client - i.e., subnets of 100.64.0.0/10 and fd7a:115c:a1e0::/48. +# See below: +# IPv6: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#LL81C52-L81C71 +# IPv4: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#L33 +# Any other range is NOT supported, and it will cause unexpected issues. +prefixes: + v4: 100.64.0.0/10 + v6: fd7a:115c:a1e0::/48 + + # Strategy used for allocation of IPs to nodes, available options: + # - sequential (default): assigns the next free IP from the previous given IP. + # - random: assigns the next free IP from a pseudo-random IP generator (crypto/rand). + allocation: sequential + +# DERP is a relay system that Tailscale uses when a direct +# connection cannot be established. +# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp +# +# headscale needs a list of DERP servers that can be presented +# to the clients. +derp: + server: + # If enabled, runs the embedded DERP server and merges it into the rest of the DERP config + # The Headscale server_url defined above MUST be using https, DERP requires TLS to be in place + enabled: false + + # Region ID to use for the embedded DERP server. + # The local DERP prevails if the region ID collides with other region ID coming from + # the regular DERP config. + region_id: 999 + + # Region code and name are displayed in the Tailscale UI to identify a DERP region + region_code: "headscale" + region_name: "Headscale Embedded DERP" + + # Listens over UDP at the configured address for STUN connections - to help with NAT traversal. + # When the embedded DERP server is enabled stun_listen_addr MUST be defined. + # + # For more details on how this works, check this great article: https://tailscale.com/blog/how-tailscale-works/ + stun_listen_addr: "0.0.0.0:3478" + + # Private key used to encrypt the traffic between headscale DERP and + # Tailscale clients. A missing key will be automatically generated. + private_key_path: /var/lib/headscale/derp_server_private.key + + # This flag can be used, so the DERP map entry for the embedded DERP server is not written automatically, + # it enables the creation of your very own DERP map entry using a locally available file with the parameter DERP.paths + # If you enable the DERP server and set this to false, it is required to add the DERP server to the DERP map using DERP.paths + automatically_add_embedded_derp_region: true + + # For better connection stability (especially when using an Exit-Node and DNS is not working), + # it is possible to optionally add the public IPv4 and IPv6 address to the Derp-Map using: + ipv4: 1.2.3.4 + ipv6: 2001:db8::1 + + # List of externally available DERP maps encoded in JSON + urls: + - https://controlplane.tailscale.com/derpmap/default + + # Locally available DERP map files encoded in YAML + # + # This option is mostly interesting for people hosting + # their own DERP servers: + # https://tailscale.com/kb/1118/custom-derp-servers/ + # + # paths: + # - /etc/headscale/derp-example.yaml + paths: [] + + # If enabled, a worker will be set up to periodically + # refresh the given sources and update the derpmap + # will be set up. + auto_update_enabled: true + + # How often should we check for DERP updates? + update_frequency: 24h + +# Disables the automatic check for headscale updates on startup +disable_check_updates: false + +# Time before an inactive ephemeral node is deleted? +ephemeral_node_inactivity_timeout: 30m + +database: + # Database type. Available options: sqlite, postgres + # Please note that using Postgres is highly discouraged as it is only supported for legacy reasons. + # All new development, testing and optimisations are done with SQLite in mind. + type: sqlite + + # Enable debug mode. This setting requires the log.level to be set to "debug" or "trace". + debug: false + + # GORM configuration settings. + gorm: + # Enable prepared statements. + prepare_stmt: true + + # Enable parameterized queries. + parameterized_queries: true + + # Skip logging "record not found" errors. + skip_err_record_not_found: true + + # Threshold for slow queries in milliseconds. + slow_threshold: 1000 + + # SQLite config + sqlite: + path: /var/lib/headscale/db.sqlite + + # Enable WAL mode for SQLite. This is recommended for production environments. + # https://www.sqlite.org/wal.html + write_ahead_log: true + + # Maximum number of WAL file frames before the WAL file is automatically checkpointed. + # https://www.sqlite.org/c3ref/wal_autocheckpoint.html + # Set to 0 to disable automatic checkpointing. + wal_autocheckpoint: 1000 + # # Postgres config + # Please note that using Postgres is highly discouraged as it is only supported for legacy reasons. + # See database.type for more information. + # postgres: + # # If using a Unix socket to connect to Postgres, set the socket path in the 'host' field and leave 'port' blank. + # host: localhost + # port: 5432 + # name: headscale + # user: foo + # pass: bar + # max_open_conns: 10 + # max_idle_conns: 10 + # conn_max_idle_time_secs: 3600 + + # # If other 'sslmode' is required instead of 'require(true)' and 'disabled(false)', set the 'sslmode' you need + # # in the 'ssl' field. Refers to https://www.postgresql.org/docs/current/libpq-ssl.html Table 34.1. + # ssl: false + + ### TLS configuration + # + ## Let's encrypt / ACME + # + # headscale supports automatically requesting and setting up + # TLS for a domain with Let's Encrypt. + # + # URL to ACME directory +acme_url: https://acme-v02.api.letsencrypt.org/directory + +# Email to register with ACME provider +acme_email: "" + +# Domain name to request a TLS certificate for: +tls_letsencrypt_hostname: "" + +# Path to store certificates and metadata needed by +# letsencrypt +# For production: +tls_letsencrypt_cache_dir: /var/lib/headscale/cache + +# Type of ACME challenge to use, currently supported types: +# HTTP-01 or TLS-ALPN-01 +# See: docs/ref/tls.md for more information +tls_letsencrypt_challenge_type: HTTP-01 +# When HTTP-01 challenge is chosen, letsencrypt must set up a +# verification endpoint, and it will be listening on: +# :http = port 80 +tls_letsencrypt_listen: ":http" + +## Use already defined certificates: +tls_cert_path: "" +tls_key_path: "" + +log: + # Output formatting for logs: text or json + format: text + level: info + +## Policy +# headscale supports Tailscale's ACL policies. +# Please have a look to their KB to better +# understand the concepts: https://tailscale.com/kb/1018/acls/ +policy: + # The mode can be "file" or "database" that defines + # where the ACL policies are stored and read from. + mode: file + # If the mode is set to "file", the path to a + # HuJSON file containing ACL policies. + path: "" + +## DNS +# +# headscale supports Tailscale's DNS configuration and MagicDNS. +# Please have a look to their KB to better understand the concepts: +# +# - https://tailscale.com/kb/1054/dns/ +# - https://tailscale.com/kb/1081/magicdns/ +# - https://tailscale.com/blog/2021-09-private-dns-with-magicdns/ +# +# Please note that for the DNS configuration to have any effect, +# clients must have the `--accept-dns=true` option enabled. This is the +# default for the Tailscale client. This option is enabled by default +# in the Tailscale client. +# +# Setting _any_ of the configuration and `--accept-dns=true` on the +# clients will integrate with the DNS manager on the client or +# overwrite /etc/resolv.conf. +# https://tailscale.com/kb/1235/resolv-conf +# +# If you want stop Headscale from managing the DNS configuration +# all the fields under `dns` should be set to empty values. +dns: + # Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). + magic_dns: true + + # Defines the base domain to create the hostnames for MagicDNS. + # This domain _must_ be different from the server_url domain. + # `base_domain` must be a FQDN, without the trailing dot. + # The FQDN of the hosts will be + # `hostname.base_domain` (e.g., _myhost.example.com_). + base_domain: dns.sharkgirl.pet + + # List of DNS servers to expose to clients. + nameservers: + global: + - 100.64.0.15 + - 1.1.1.1 + - 1.0.0.1 + - 2606:4700:4700::1111 + - 2606:4700:4700::1001 + split: + {} + # foo.bar.com: + # - 1.1.1.1 + # darp.headscale.net: + # - 1.1.1.1 + # - 8.8.8.8 + + # Set custom DNS search domains. With MagicDNS enabled, + # your tailnet base_domain is always the first search domain. + search_domains: [] + + # Extra DNS records + # so far only A and AAAA records are supported (on the tailscale side) + # See: docs/ref/dns.md + extra_records: [] + # - name: "grafana.myvpn.example.com" + # type: "A" + # value: "100.64.0.3" + # + # # you can also put it in one line + # - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" } + # + # Alternatively, extra DNS records can be loaded from a JSON file. + # Headscale processes this file on each change. + # extra_records_path: /var/lib/headscale/extra-records.json + + # Unix socket used for the CLI to connect without authentication + # Note: for production you will want to set this to something like: +unix_socket: /var/run/headscale/headscale.sock +unix_socket_permission: "0770" +# +# headscale supports experimental OpenID connect support, +# it is still being tested and might have some bugs, please +# help us test it. +# OpenID Connect +oidc: + only_start_if_oidc_is_available: true + issuer: "https://pocketid.nekomimi.pet" + client_id: "f345acad-3eac-45b7-9d91-57f388987a57" + client_secret: "vTk5VAutyeok44phpv2Z7BjnNGhqD1cv" + pkce: + enabled: true + method: S256 + + #issuer: "https://authelia.nekomimi.pet/application/o/headscale/" + #client_id: "6qgprTjDVR12U68jlEn8kInN6yvXwXZKJ2K9NAMv" + #client_secret: "14o8SR7O2ZuVaEtR9hZDUry1a0kaKJSN1tm7CCpFWAUUFXQEVn5aPxjtBqEtMa5\ + # HweasjXrBGrN7Lgbot73VzEVPk5lEv0PFHGXpJZSrmj5E5ROjT5IgMscElDshoZjQ" + #scope: [ "openid", "profile", "email", "custom" ] + #extra_params: + # domain_hint: nekomimi.pet + +# oidc: +# only_start_if_oidc_is_available: true +# issuer: "https://your-oidc.issuer.com/path" +# client_id: "your-oidc-client-id" +# client_secret: "your-oidc-client-secret" +# # Alternatively, set `client_secret_path` to read the secret from the file. +# # It resolves environment variables, making integration to systemd's +# # `LoadCredential` straightforward: +# client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret" +# # client_secret and client_secret_path are mutually exclusive. +# +# # The amount of time from a node is authenticated with OpenID until it +# # expires and needs to reauthenticate. +# # Setting the value to "0" will mean no expiry. +# expiry: 180d +# +# # Use the expiry from the token received from OpenID when the user logged +# # in, this will typically lead to frequent need to reauthenticate and should +# # only been enabled if you know what you are doing. +# # Note: enabling this will cause `oidc.expiry` to be ignored. +# use_expiry_from_token: false +# +# # Customize the scopes used in the OIDC flow, defaults to "openid", "profile" and "email" and add custom query +# # parameters to the Authorize Endpoint request. Scopes default to "openid", "profile" and "email". +# +# scope: ["openid", "profile", "email", "custom"] +# extra_params: +# domain_hint: example.com +# +# # List allowed principal domains and/or users. If an authenticated user's domain is not in this list, the +# # authentication request will be rejected. +# +# allowed_domains: +# - example.com +# # Note: Groups from keycloak have a leading '/' +# allowed_groups: +# - /headscale +# allowed_users: +# - alice@example.com +# +# # Optional: PKCE (Proof Key for Code Exchange) configuration +# # PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow +# # by preventing authorization code interception attacks +# # See https://datatracker.ietf.org/doc/html/rfc7636 +# pkce: +# # Enable or disable PKCE support (default: false) +# enabled: false +# # PKCE method to use: +# # - plain: Use plain code verifier +# # - S256: Use SHA256 hashed code verifier (default, recommended) +# method: S256 +# +# # Map legacy users from pre-0.24.0 versions of headscale to the new OIDC users +# # by taking the username from the legacy user and matching it with the username +# # provided by the OIDC. This is useful when migrating from legacy users to OIDC +# # to force them using the unique identifier from the OIDC and to give them a +# # proper display name and picture if available. +# # Note that this will only work if the username from the legacy user is the same +# # and there is a possibility for account takeover should a username have changed +# # with the provider. +# # When this feature is disabled, it will cause all new logins to be created as new users. +# # Note this option will be removed in the future and should be set to false +# # on all new installations, or when all users have logged in with OIDC once. +# map_legacy_users: false + +# Logtail configuration +# Logtail is Tailscales logging and auditing infrastructure, it allows the control panel +# to instruct tailscale nodes to log their activity to a remote server. +logtail: + # Enable logtail for this headscales clients. + # As there is currently no support for overriding the log server in headscale, this is + # disabled by default. Enabling this will make your clients send logs to Tailscale Inc. + enabled: false + +# Enabling this option makes devices prefer a random port for WireGuard traffic over the +# default static port 41641. This option is intended as a workaround for some buggy +# firewall devices. See https://tailscale.com/kb/1181/firewalls/ for more information. +randomize_client_port: false diff --git a/headscale/litestream.yml b/headscale/litestream.yml new file mode 100644 index 0000000..c4c82a3 --- /dev/null +++ b/headscale/litestream.yml @@ -0,0 +1,7 @@ +dbs: + - path: /var/lib/headscale/db.sqlite + replicas: + - url: s3://headscale/db.sqlite + endpoint: https://s3.nekomimi.pet + region: garage + force-path-style: true \ No newline at end of file