sops-encrypt TURN password + Authentik client secret; homelab render-vps-configs
The IONOS netbird VPS held two credentials in plaintext that were the last
holdouts from the homelab's sops+age secrets pattern:
- /root/turn-pass.txt (coturn long-term-credential password)
- PKCEAuthorizationFlow.ProviderConfig.ClientSecret inline in
/opt/management.json (Authentik OIDC client secret)
This commit moves both into sops-encrypted YAML in the repo and adds a render
command that recreates the VPS config files from templates + decrypted secrets:
* secrets/turn-shared-secret.yaml — encrypted `password: <coturn pwd>`
* secrets/netbird-authentik-oidc.yaml — encrypted `client_secret: <...>`
Both recipients = hubris + apps + republic-laptop (same 3 as hello.yaml).
* vps/turnserver.conf.tmpl + vps/management.json.tmpl — templates with
{{TURN_PASSWORD}} + {{AUTHENTIK_CLIENT_SECRET}} placeholders.
* bin/homelab new subcommand `render-vps-configs`:
- Decrypts both secrets locally (works on any recipient).
- Substitutes placeholders into templates.
- Diffs against current VPS state via ssh, prompts, applies atomically
(write `.new` then mv), restarts coturn + netbird-mgmt.
- --dry-run + -y flags. Hops through hubris when not running on hubris
itself, since VPS sshd is locked to hubris's pubkey.
* inventory.yaml adds the VPS as `kind: external` (new kind; reserved for
ssh-managed hosts that aren't homelab clients themselves — no age key,
no /opt/homelab-context). hosts/netbird-vps.yaml regenerated.
* SHARED_SECRETS list includes both new secrets so re-keys on enrollment
changes pick them up automatically.
After this lands + the 5-min sync propagates to hubris, run from hubris PVE
shell (or any client; hubris just skips the extra ssh hop):
homelab render-vps-configs --dry-run # see plan, no changes
homelab render-vps-configs -y # apply + restart services
Once verified working, the plaintext `/root/turn-pass.txt` should be deleted
on the VPS (the rendered /etc/turnserver.conf no longer needs it as a
reference).
This commit is contained in:
19
.sops.yaml
19
.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
|
||||
|
||||
171
bin/homelab
171
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/<name>.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 <path>.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)
|
||||
|
||||
|
||||
29
hosts/netbird-vps.yaml
Normal file
29
hosts/netbird-vps.yaml
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
39
secrets/netbird-authentik-oidc.yaml
Normal file
39
secrets/netbird-authentik-oidc.yaml
Normal file
@@ -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
|
||||
39
secrets/turn-shared-secret.yaml
Normal file
39
secrets/turn-shared-secret.yaml
Normal file
@@ -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
|
||||
75
vps/management.json.tmpl
Normal file
75
vps/management.json.tmpl
Normal file
@@ -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/"]
|
||||
}
|
||||
}
|
||||
}
|
||||
23
vps/turnserver.conf.tmpl
Normal file
23
vps/turnserver.conf.tmpl
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user