diff --git a/.sops.yaml b/.sops.yaml index 02668f8..a04fcc1 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -43,4 +43,23 @@ creation_rules: # LXCs that run a webhook receiver. age: >- # placeholder — fill with age_pubkey of: apps, caddy, claudio-bot, claudio-monitor host + + - path_regex: ^secrets/turn-shared-secret\.yaml$ + # coturn TURN long-term-credential password. Consumed by hubris (which + # renders /etc/turnserver.conf + /opt/management.json on the VPS via + # `homelab render-vps-configs`). Other recipients are convenience for + # operator debugging — only hubris's pubkey is strictly required. + age: >- + age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6, + age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0, + age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6 + + - path_regex: ^secrets/netbird-authentik-oidc\.yaml$ + # Authentik OIDC client secret for the netbird-dashboard provider. + # Consumed by hubris to render /opt/management.json on the VPS + # (PKCEAuthorizationFlow.ProviderConfig.ClientSecret). + age: >- + age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6, + age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0, + age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6 # webhook noop 2026-05-20T18:16:57+02:00 diff --git a/bin/homelab b/bin/homelab index 82eb8a3..aba2f93 100755 --- a/bin/homelab +++ b/bin/homelab @@ -16,6 +16,7 @@ from __future__ import annotations import argparse import json import os +import shlex import shutil import socket import subprocess @@ -189,8 +190,13 @@ def push_inventory(message: str, extra_paths: list[str] | None = None) -> None: # Secrets every enrolled client should be a recipient on. Each entry is # (secret-file-path-relative-to-CONTEXT, path_regex used in .sops.yaml). SHARED_SECRETS = [ - ("secrets/hello.yaml", "^secrets/hello\\.yaml$"), - ("secrets/gitea-pat.yaml", "^secrets/gitea-pat\\.yaml$"), + ("secrets/hello.yaml", "^secrets/hello\\.yaml$"), + ("secrets/gitea-pat.yaml", "^secrets/gitea-pat\\.yaml$"), + # VPS-render secrets — only strictly required on hubris (the rendering + # proxy), but kept on the SHARED list so any operator can `homelab secret + # turn-shared-secret` / `... netbird-authentik-oidc` for debugging. + ("secrets/turn-shared-secret.yaml", "^secrets/turn-shared-secret\\.yaml$"), + ("secrets/netbird-authentik-oidc.yaml", "^secrets/netbird-authentik-oidc\\.yaml$"), ] @@ -588,6 +594,31 @@ def cmd_status(args: argparse.Namespace) -> int: return 0 +def _decrypt_secret(name: str) -> dict: + """Decrypt secrets/.yaml and parse as YAML. Re-execs via sudo when + invoked as a non-root user (the age key at /etc/age/key.txt is 0600 root).""" + path = CONTEXT / "secrets" / f"{name}.yaml" + if not path.exists(): + die(f"no secret '{name}' (looked for {path})") + if os.geteuid() != 0: + proc = subprocess.run( + ["sudo", "-E", "env", f"SOPS_AGE_KEY_FILE={AGE_KEY}", + "sops", "-d", str(path)], + capture_output=True, text=True, + ) + else: + if not AGE_KEY.exists(): + die(f"no age key at {AGE_KEY} — has bootstrap run?") + env = {**os.environ, "SOPS_AGE_KEY_FILE": str(AGE_KEY)} + proc = subprocess.run( + ["sops", "-d", str(path)], + capture_output=True, text=True, env=env, + ) + if proc.returncode != 0: + die(f"sops decrypt of '{name}' failed: {proc.stderr.strip() or '(no stderr)'}") + return yaml.safe_load(proc.stdout) + + def cmd_secret(args: argparse.Namespace) -> int: name = args.name path = CONTEXT / "secrets" / f"{name}.yaml" @@ -608,6 +639,134 @@ def cmd_secret(args: argparse.Namespace) -> int: return subprocess.call(["sops", "-d", str(path)], env=env) +# ---------- render-vps-configs ---------- +# +# The IONOS netbird VPS hosts plaintext credentials that we now keep encrypted +# in secrets/. This command re-renders the affected config files on the VPS by +# decrypting the secrets locally and pushing the rendered output. Idempotent — +# safe to re-run; it shows a diff and prompts before applying. + +# Map of (template path in repo, target path on VPS, mode, restart cmd after). +_VPS_RENDER_TARGETS = [ + { + "tmpl": "vps/turnserver.conf.tmpl", + "remote": "/etc/turnserver.conf", + "mode": "0644", + "restart": "systemctl restart coturn", + }, + { + "tmpl": "vps/management.json.tmpl", + "remote": "/opt/management.json", + "mode": "0600", + "restart": "docker restart netbird-mgmt", + }, +] + + +def _vps_remote_cmd(remote_cmd: str) -> list[str]: + """Build the argv that, when run, executes `remote_cmd` on the IONOS VPS. + + The VPS only accepts hubris's pubkey (per infrastructure/vps-hardening.md), + so when we're not on hubris we hop through it: laptop → ssh hubris → ssh + netbird-vps. On hubris we go direct. + """ + direct = ssh_base("netbird-vps") # ["ssh", "root@netbird-ionos..."] + try: + on_hubris = socket.gethostname() == "hubris" + except Exception: + on_hubris = False + if on_hubris: + return direct + [remote_cmd] + # laptop → hubris → vps. shlex-quote the remote_cmd so it survives + # hubris's shell parsing as a single argument to the inner ssh. + inner = " ".join(direct) + " " + shlex.quote(remote_cmd) + return ssh_base("hubris") + [inner] + + +def _vps_send(remote_path: str, content: str, mode: str) -> None: + """Atomically write `content` to `remote_path` on the VPS with given mode. + Writes to .new, chmods, then mv-replaces.""" + new_path = f"{remote_path}.new" + write_cmd = f"umask 077 && cat > {new_path} && chmod {mode} {new_path}" + proc = subprocess.run( + _vps_remote_cmd(write_cmd), + input=content, text=True, capture_output=True, + ) + if proc.returncode != 0: + die(f"failed writing {new_path}: {proc.stderr.strip()}") + mv = subprocess.run(_vps_remote_cmd(f"mv {new_path} {remote_path}"), + capture_output=True, text=True) + if mv.returncode != 0: + die(f"failed renaming {new_path} → {remote_path}: {mv.stderr.strip()}") + + +def cmd_render_vps_configs(args: argparse.Namespace) -> int: + """Re-render /etc/turnserver.conf + /opt/management.json on the IONOS netbird + VPS from templates in vps/, substituting secrets decrypted from sops.""" + # Decrypt the two sops files we need. + turn = _decrypt_secret("turn-shared-secret") + auth = _decrypt_secret("netbird-authentik-oidc") + subs = { + "{{TURN_PASSWORD}}": turn["password"], + "{{AUTHENTIK_CLIENT_SECRET}}": auth["client_secret"], + } + + # Render each template and compare against current VPS content. + plans = [] + for t in _VPS_RENDER_TARGETS: + tmpl_path = CONTEXT / t["tmpl"] + if not tmpl_path.exists(): + die(f"missing template {tmpl_path}") + rendered = tmpl_path.read_text() + for needle, value in subs.items(): + rendered = rendered.replace(needle, value) + + cat = subprocess.run(_vps_remote_cmd(f"cat {t['remote']} 2>/dev/null"), + capture_output=True, text=True) + current = cat.stdout + plans.append({ + **t, + "rendered": rendered, + "changed": current != rendered, + "current_present": cat.returncode == 0 and bool(current), + }) + + # Summarize. + print("Render plan for IONOS netbird VPS:") + any_changes = False + for p in plans: + status = "(changed)" if p["changed"] else "(unchanged)" + print(f" {p['remote']:<28} {status}") + if p["changed"]: + any_changes = True + if not any_changes: + print("Nothing to do — every target matches the rendered template.") + return 0 + + if args.dry_run: + print("\n--- dry-run: not applying ---") + return 0 + + if not args.yes: + if not confirm("apply changes + restart services on netbird-vps?"): + return 1 + + # Apply changed targets, then restart their services. + for p in plans: + if not p["changed"]: + continue + print(f"writing {p['remote']} ({p['mode']}) ...") + _vps_send(p["remote"], p["rendered"], p["mode"]) + print(f" → {p['restart']}") + rc = subprocess.run(_vps_remote_cmd(p["restart"]), + capture_output=True, text=True) + if rc.returncode != 0: + print(f" ! restart failed: {rc.stderr.strip()}", file=sys.stderr) + return 1 + print("done.") + return 0 + + def cmd_doctor(args: argparse.Namespace) -> int: """Run health checks for the homelab-context client setup.""" results: list[tuple[str, str, str]] = [] # (status, label, detail) @@ -1286,6 +1445,14 @@ def main() -> int: sp.add_argument("name") sp.set_defaults(func=cmd_secret) + sp = sub.add_parser("render-vps-configs", + help="re-render /etc/turnserver.conf + /opt/management.json on the IONOS netbird VPS from templates + sops secrets") + sp.add_argument("--dry-run", action="store_true", + help="show what would change; don't apply or restart") + sp.add_argument("-y", "--yes", action="store_true", + help="skip the confirmation prompt") + sp.set_defaults(func=cmd_render_vps_configs) + sp = sub.add_parser("sync", help="manually trigger homelab-context-sync") sp.set_defaults(func=cmd_sync) diff --git a/hosts/netbird-vps.yaml b/hosts/netbird-vps.yaml new file mode 100644 index 0000000..8cc3fee --- /dev/null +++ b/hosts/netbird-vps.yaml @@ -0,0 +1,29 @@ +# Generated by mcp/build_host_files.py from inventory.yaml. +# Do NOT edit by hand — your changes will be overwritten. +# Source of truth: ../inventory.yaml +name: netbird-vps +kind: external +os: linux +role: netbird-mgmt +mesh: + netbird: + ip: 100.122.165.149 + fqdn: netbird-ionos.netbird.selfhosted +mesh_globals: + primary: netbird + accepted: + - netbird + - tailscale +ssh: + user: root +notes: +- "Public IONOS VPS \u2014 hosts the vanilla netbird mgmt+signal+relay+dashboard stack + host coturn (see\ + \ infrastructure/vps-hardening.md + infrastructure/mesh.md changelog 2026-05-21)." +- NOT a homelab client. No /etc/age/key.txt, no /opt/homelab-context clone. Managed via ssh from hubris; + sshd is locked to hubris's pubkey. +- Public IPv4 82.165.190.79. Auto-patching via unattended-upgrades. +- Configs rendered by `homelab render-vps-configs` from vps/turnserver.conf.tmpl + vps/management.json.tmpl, + with secrets decrypted from secrets/turn-shared-secret.yaml + secrets/netbird-authentik-oidc.yaml on + hubris. +mcp_endpoint: https://mcp.hubris.network/sse +secrets_issuance_endpoint: https://secrets.hubris.network/issue diff --git a/infrastructure/vps-hardening.md b/infrastructure/vps-hardening.md index afc6fa8..7c968ea 100644 --- a/infrastructure/vps-hardening.md +++ b/infrastructure/vps-hardening.md @@ -9,7 +9,8 @@ IONOS VPS that runs the Netbird control plane and the [public ingress traefik](i - **Public:** `82.165.190.79` (`ens6`). - **Public DNS:** IONOS wildcard `*.hubris.network → 82.165.190.79`. - **Docker stack** at `/opt/docker-compose.yml`: `traefik` (TLS/ACME) + `dashboard` + `mgmt` + `signal` + `relay` + `proxy` — netbird-mgmt 0.71.3 vanilla deploy since 2026-05-21 (see [mesh.md changelog](mesh.md#changelog)). -- **Host services (outside docker):** `coturn` (TURN-TCP on :3478, long-term creds at `/root/turn-pass.txt`, used by mgmt's `TURNConfig`). +- **Host services (outside docker):** `coturn` (TURN-TCP on :3478, long-term creds rendered into `/etc/turnserver.conf` by `homelab render-vps-configs` from sops-encrypted `secrets/turn-shared-secret.yaml`). +- **Config rendering:** `/etc/turnserver.conf` + `/opt/management.json` are generated from templates in `vps/*.tmpl` on this repo by `homelab render-vps-configs`. Secret placeholders (`{{TURN_PASSWORD}}`, `{{AUTHENTIK_CLIENT_SECRET}}`) are substituted from sops-encrypted secrets decrypted on hubris and pushed over ssh. **Do not hand-edit those two files on the VPS** — the next render will overwrite them. ## SSH diff --git a/inventory.yaml b/inventory.yaml index e64998d..c823ec9 100644 --- a/inventory.yaml +++ b/inventory.yaml @@ -7,7 +7,10 @@ # - hostname keys MUST match the actual `hostname` of the machine (on # macOS: `scutil --get LocalHostName` if set). # - `os:` one of: linux, macos -# - `kind:` one of: proxmox-host, lxc, vm, workstation +# - `kind:` one of: proxmox-host, lxc, vm, workstation, external +# ("external" is reserved for hosts the homelab CLI manages via ssh but +# that aren't homelab clients themselves — e.g. the IONOS netbird VPS +# with no /etc/age/key.txt and no /opt/homelab-context clone.) # - `mesh:` lists addresses the host is reachable at. Both `netbird` and # `tailscale` are accepted during the migration (see infrastructure/mesh.md). # Prefer netbird FQDNs over raw IPs. @@ -360,3 +363,24 @@ hosts: # ssh.user defaults to root; uncomment + set to the actual login user # before relying on `homelab ssh ludo-mini` or netbird-ssh INTO this host. # ssh: { user: ludo } + netbird-vps: + kind: external + os: linux + role: netbird-mgmt + mesh: + netbird: + ip: 100.122.165.149 + fqdn: netbird-ionos.netbird.selfhosted + ssh: + user: root + notes: + - Public IONOS VPS — hosts the vanilla netbird mgmt+signal+relay+dashboard + stack + host coturn (see infrastructure/vps-hardening.md + + infrastructure/mesh.md changelog 2026-05-21). + - NOT a homelab client. No /etc/age/key.txt, no /opt/homelab-context + clone. Managed via ssh from hubris; sshd is locked to hubris's pubkey. + - Public IPv4 82.165.190.79. Auto-patching via unattended-upgrades. + - Configs rendered by `homelab render-vps-configs` from + vps/turnserver.conf.tmpl + vps/management.json.tmpl, with secrets + decrypted from secrets/turn-shared-secret.yaml + + secrets/netbird-authentik-oidc.yaml on hubris. diff --git a/secrets/netbird-authentik-oidc.yaml b/secrets/netbird-authentik-oidc.yaml new file mode 100644 index 0000000..cb04de5 --- /dev/null +++ b/secrets/netbird-authentik-oidc.yaml @@ -0,0 +1,39 @@ +client_secret: ENC[AES256_GCM,data:p8csBIMAuDb1BYLLL9aYIifShpw9uIWm1Z0c59SlzBDP3SJD4I8Z6ipYBETisgxcECr+78zs2wtuebtcHZ3DBqF5PZaG87TJEGotTkjOVSjDLKgelMq0TDfnpWyR1yedsi6/SiRhm4ggTz0Vwwo+TLDNkw3Gv7EP7fZQphYdMyA=,iv:MZcvhiBwDc8FWcNqQvCiTL+9OVEfHpZrzt1kmcKcxdA=,tag:FmEIrw7u2wYiF3ksymC5lA==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzVVZIQzlPak4xTVVPMm9w + M2tFbTFoQmpRUGFETjF1ZTQ3ajlEdGxWQmtZCkdkZzdhblFybGV3OEUrWERBWFUv + bTdXSnZFSU8vY1JJVTJjL3dLdWpiNGsKLS0tIHVlVmcrcTNZUGs4MmJtWkR0aTh3 + Z0pTOWhsZWYyVW5TeXk5ZHhaQnZvZ3MKrnBt5T7WjSxGYvRc1olfhuMN6nOEJbbX + xoliPcKkGsBExXVgkpood+OdlH8dNAaT0z1+INzNiBAZ8SazZA4p9g== + -----END AGE ENCRYPTED FILE----- + - recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjYk1SOVZxUTBWYUw4aUhy + WG93N2xVWDlEWnBqQ0JTRWcxc2tXUnppSFF3Cmp6L1g4OUliVlNFYUo2bEtNOHhm + aytHK0RFWmNpSWFMaGc1cnNXNlEwbTQKLS0tIHMxU3VXYTZHNWk3cWZFNXBNRElD + bE1CUHc1TFlsbGU2aTJjSmhwZENuUDgKvGHg2Df/eBw5akRPYFLvXhzh7P6jTOgj + E56n29EJ+p4kTkC8yVBci01qpMioL+Wx2Rt+X+0LWrGBzu5fic2U5g== + -----END AGE ENCRYPTED FILE----- + - recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwSGdBYzlsYmc4dVJ5ZE8r + Q1FMR1NQL2lzZ1lmaTg1bGROa0hOcVk2ZTJvCllFMUtJdG5JVFhseWc5eXFsYk9p + Q2tIVm5yelAreVRrVkQrTnJ1N3FLclEKLS0tIGQ5dTdqZ3lKWU1KcHdHK1UwajIw + Znh5aXkwNmdyVi9EZlBzMUpTTW1VdjgKVVNGjiiKw3nLxq4YsCWoTS4R8wUD8wqD + awXSIOvZj5xiz6NvFk1X5T3H4XeEm6tKKzOpBvoVn5yl3zgn/bh5pA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-21T20:13:20Z" + mac: ENC[AES256_GCM,data:bi4o17skIOtZoxJGzLFJ7IAv+X265qIhgnQz6wr4bch2xofwPZZfzECck0I0xDhcwxcDtx3VZ5E2VBdvHWk+iSan+clRq7735k9+DIpjdWaGxIHuylmelZl6aE5kE36UO7fQdnwmiqCPHxiVq/sKuqknFXkl1+FvO1ryrtSHQsU=,iv:xxm+5rK7o2c43iGS1j2Q274QCRsbDn+k+wskpp5tzeU=,tag:sZHk9eth/TwqM8tIWSrv3w==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.9.4 diff --git a/secrets/turn-shared-secret.yaml b/secrets/turn-shared-secret.yaml new file mode 100644 index 0000000..3278bb4 --- /dev/null +++ b/secrets/turn-shared-secret.yaml @@ -0,0 +1,39 @@ +password: ENC[AES256_GCM,data:C9uA7LHAc/SmWJsICGMNLu2OOYyowIhLq/sTfuNo/w==,iv:211oFm7MdphnXwYszRxWrHLeKw7qeh0BNzP0yomTUTk=,tag:zfhfkzLm0AivKwD7TZTEqw==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiaFhuUlZJMkxERlR1T2xL + dnVKMXNMNVBIdDFuYzJtTjJWZE9JZnlaQ1dJCnNNbzJKc3VXOUZ1QUFzRWhSUkdF + WVJlNUtJSytSS2h5NGQyMHA4d2U0c28KLS0tIFVhSGJQOWlEQlp6SlRmS0ZMZzJ4 + Y2ZoVVVkeVNiYUNwWFFLd2VGaTR1VUkK9Dpk0kjuKoUh3zfVQV7qs/YTTg2BaOkg + kNYY01k9MftEwdtvpKk1ogzdHyhGFJ1yEepwK7se6W5KHDJXyVfA8w== + -----END AGE ENCRYPTED FILE----- + - recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4N2xmZEM3R2tMQjA4a1Fr + cWZ5a2lwUWlJZDdVSExnaHA3cEpubTVMWG1BCjNjTEJzL01PWm9xRWlEWUlWend5 + TUdMMjUzNE1RN0h5bzJuQnl5QkJXVzAKLS0tIDZ6aVh6dXlxTTVPZDRKQUkyZmlk + a2FLZ2s1NTA1ZUljeFBhSDZEcGZLNlkKcX5b3dXcJZejeSP4TLr1cOXQj7YEjj0G + znXBdDt6c42qE5XS+LGciunf3MmYxt0xXDb11cnSSEVgn0VyqE2x6w== + -----END AGE ENCRYPTED FILE----- + - recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBybWtTZEw3NTdwMWZhOUMv + L1RNWGduU1BSM0FodWMwUEtHNkxSY3MvdGhzCm1qTGlnM2ZRM2RoekhZbjFzZ2tE + Qk1DNjlnMk01R1ZzUDBubTMvR2hPNmcKLS0tIGsxZ2lQMi9vY0Zpb3pwRHBSSm5t + OWtkeCtNNnpTVXV6S1p2Y2E0V2toL1EK3RI0RgM0SudRguOpOimke7niuX3cIIVi + X+zK75QbvLN7pe1pIIC2UOEmRB9BhgGMVssHxnLzTJv0LUKjDMGdbw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-21T20:13:20Z" + mac: ENC[AES256_GCM,data:PXXEpDbJIoIwWuNwMetpALtxrcB7yJhDYqp6LSKV8WFQI2TJSDgDHiNQ2cngfB9PXKmUNNhlvhmB52Hi6WQSWQdXjiy79T1fOjMhbUAKmykTaZDrwvlrDGkPgiZ1cFSb8+hx/5aZw7YhdQO9+7LAmymeIldZIFEpEO2pvuPZ6MM=,iv:mJb3K8TnsnY183OX7o9pF7oVMDcwVU/rOgX74KTRpr4=,tag:f+M+FT0Vjozm7a8LFDI8lA==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.9.4 diff --git a/vps/management.json.tmpl b/vps/management.json.tmpl new file mode 100644 index 0000000..dab3fdd --- /dev/null +++ b/vps/management.json.tmpl @@ -0,0 +1,75 @@ +{ + "_comment": "Rendered from this template by `homelab render-vps-configs`. DO NOT edit /opt/management.json on the VPS directly; it is recreated from this template on each render. Secret placeholders are {{TURN_PASSWORD}} and {{AUTHENTIK_CLIENT_SECRET}}.", + "Stuns": [ + {"Proto": "udp", "URI": "stun:stun.l.google.com:19302", "Username": "", "Password": null}, + {"Proto": "udp", "URI": "stun:stun1.l.google.com:19302", "Username": "", "Password": null}, + {"Proto": "udp", "URI": "stun:stun.cloudflare.com:3478", "Username": "", "Password": null} + ], + "TURNConfig": { + "Turns": [ + {"Proto": "tcp", "URI": "turn:netbird.hubris.network:3478?transport=tcp", "Username": "netbird", "Password": "{{TURN_PASSWORD}}"} + ], + "CredentialsTTL": "12h", + "Secret": "not-used-when-time-based-false", + "TimeBasedCredentials": false + }, + "Relay": { + "Addresses": ["rels://netbird.hubris.network:443"], + "CredentialsTTL": "24h", + "Secret": "f6vaBSTqv53Jl9Fr+zUkzJ6iIsKv0RoYt++hKRqq58Q" + }, + "Signal": { + "Proto": "https", + "URI": "netbird.hubris.network:443", + "Username": "", + "Password": null + }, + "ReverseProxy": { + "TrustedHTTPProxies": ["172.30.0.0/24"], + "TrustedHTTPProxiesCount": 0, + "TrustedPeers": ["0.0.0.0/0"] + }, + "Datadir": "", + "DataStoreEncryptionKey": "U60qK19PEpe6LSocYs1OR+qeoE2rUq6tN+W20NAC+gs=", + "StoreConfig": {"Engine": "sqlite"}, + "DisableDefaultPolicy": false, + "HttpConfig": { + "Address": "0.0.0.0:80", + "AuthIssuer": "https://auth.hubris.network/application/o/netbird/", + "AuthAudience": "netbird-dashboard", + "AuthUserIDClaim": "sub", + "AuthKeysLocation": "https://auth.hubris.network/application/o/netbird/jwks/", + "OIDCConfigEndpoint": "https://auth.hubris.network/application/o/netbird/.well-known/openid-configuration", + "IdpSignKeyRefreshEnabled": true, + "CertFile": "", + "CertKey": "" + }, + "IdpManagerConfig": { + "ManagerType": "none" + }, + "DeviceAuthorizationFlow": { + "Provider": "hosted", + "ProviderConfig": { + "ClientID": "netbird-dashboard", + "Audience": "netbird-dashboard", + "Domain": "auth.hubris.network", + "TokenEndpoint": "https://auth.hubris.network/application/o/token/", + "DeviceAuthEndpoint": "https://auth.hubris.network/application/o/device/", + "Scope": "openid profile email offline_access", + "UseIDToken": false + } + }, + "PKCEAuthorizationFlow": { + "ProviderConfig": { + "ClientID": "netbird-dashboard", + "ClientSecret": "{{AUTHENTIK_CLIENT_SECRET}}", + "Audience": "netbird-dashboard", + "Domain": "auth.hubris.network", + "TokenEndpoint": "https://auth.hubris.network/application/o/token/", + "AuthorizationEndpoint": "https://auth.hubris.network/application/o/authorize/", + "Scope": "openid profile email offline_access", + "UseIDToken": false, + "RedirectURLs": ["http://localhost:53000/"] + } + } +} diff --git a/vps/turnserver.conf.tmpl b/vps/turnserver.conf.tmpl new file mode 100644 index 0000000..c1c21c9 --- /dev/null +++ b/vps/turnserver.conf.tmpl @@ -0,0 +1,23 @@ +# coturn for netbird symmetric-NAT peers — rendered from this template by +# `homelab render-vps-configs`. DO NOT edit /etc/turnserver.conf on the VPS +# directly; that file is recreated from this template on each render. +listening-port=3478 +listening-ip=0.0.0.0 +relay-ip=82.165.190.79 +external-ip=82.165.190.79 + +min-port=49152 +max-port=49999 + +fingerprint +lt-cred-mech +realm=netbird.hubris.network +user=netbird:{{TURN_PASSWORD}} + +no-stun +no-multicast-peers +no-cli +no-loopback-peers +no-tlsv1 +no-tlsv1_1 +no-udp