7 Commits
Author SHA1 Message Date
naturallaw777 93492e88fb chore(release): prepare v1.1.4 2026-09-02 15:13:56 -05:00
naturallaw777 7da37cdb34 updated nixpkgs 2026-09-02 14:52:15 -05:00
Sovran_SystemsOS f34d1533c3 element-calling: fix Nix string interpolation of LAN_CIDR echo
The debug echo used bash ${LAN_CIDR:-<none>} syntax, but inside a Nix
indented string ${...} is Nix interpolation, not bash. Nix parsed
'LAN_CIDR:-<none>' as a lambda and failed the build with 'cannot coerce a
function to a string'. Rewrite the echo without brace expansion.
2026-09-01 12:29:47 -05:00
Sovran_SystemsOS 220d6dff2c docs/hub: update Element Calling port guidance to the new port set
User-facing polish to match the element-calling port changes:

- helpers.js: port-forward guide example now shows the 40000-40099 range
  instead of the removed 30000-40000.
- versions.json: livekit dev fallback 1.5.2 -> 1.13.6 (the Nix-generated
  version in sovran-hub.nix already derives from pkgs.livekit.version).
- server.py: docstring/comment examples reference 40000-40099.
- CHANGELOG.md: record the calling changes under [Unreleased].

The tile/modal port tables themselves are data-driven from server.py's
port_requirements / _PORTS_ELEMENT_CALLING (updated in the previous commit),
so no further UI changes are needed.
2026-09-01 12:15:16 -05:00
Sovran_SystemsOS 81ab3b2280 element-calling: fix Wi-Fi calls and tighten media/TURN ports
Root cause of 'calls fail on Wi-Fi but work on mobile data': LiveKit only
advertised the public/WAN IP (rtc.node_ip), so LAN clients had to hairpin
through the router for media. Fixes and cleanup:

- rtc.advertise_internal_ip: true — also advertise the primary interface's
  LAN host candidate, so Wi-Fi callers connect directly (no hairpin).
- Drop rtc.port_range_start/end (30000-40000) and keep the single UDP mux
  (udp_port: 7882). In LiveKit 1.13.x the range takes precedence over
  udp_port, so media was actually spread over 10000 ports.
- Drop turn.tls_port: 5349 — LiveKit advertises turns:<domain>:443 to clients
  regardless of tls_port, so a 5349 TURN/TLS listener was unreachable dead
  config (and needless attack surface).
- Pin TURN relay allocation to 40000-40099 (disjoint from the media mux) and
  open/forward that range; the old default overlapped RTC media.
- turn.allow_restricted_peer_cidrs with the LAN subnet derived from the
  primary interface: without it the relay refuses to deliver to the private
  LAN host candidate and its final hop would fall back to WAN hairpin.
- Update Hub port guidance (server.py) to the new list.
2026-09-01 12:15:16 -05:00
Sovran_SystemsOS d5d40a1697 flake: pin LiveKit to 1.13.6 for rtc.advertise_internal_ip support
rtc.advertise_internal_ip (which lets LAN callers reach the SFU via a LAN
host candidate, avoiding NAT-hairpin on the router) is only honoured when
node_ip is set manually from LiveKit v1.13.6 (mediatransportutil f234b53).
nixpkgs-unstable currently ships 1.13.5, so pin 1.13.6 with the hashes from
nixpkgs master. Remove this override once nixpkgs-unstable reaches >= 1.13.6.
2026-09-01 12:15:16 -05:00
naturallaw777 3756a0058d updated sovran-bitcon 2026-08-31 20:37:46 -05:00
9 changed files with 145 additions and 61 deletions
+37
View File
@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
--- ---
## [1.1.4] - 2026-09-02
### Added
- Element-calling: fix Nix string interpolation of LAN_CIDR echo
- Docs/hub: update Element Calling port guidance to the new port set
- Element-calling: fix Wi-Fi calls and tighten media/TURN ports
- Flake: pin LiveKit to 1.13.6 for rtc.advertise_internal_ip support
### Changed
- Updated nixpkgs
- Updated sovran-bitcon
- Dedupe NWC tooling — use Sovran_Bitcoin's sovran-nwc
- Update sovran-bitcoin flake input
- Extract bitcoin stack into Sovran_Bitcoin flake input
### Fixed
- Set lnurl domainFile for Hub-managed Lightning Address domain
- Correct disablewallet casing
- Fixed typo
[1.1.4]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/v1.1.4
## [Unreleased]
### Changed
- Element Calling: pin LiveKit to 1.13.6 and advertise the LAN interface
(rtc.advertise_internal_ip) so Wi-Fi callers get a local ICE candidate and
calls no longer depend on router NAT hairpin
- Element Calling: use a single UDP media mux (7882); move the TURN relay to
40000-40099 and remove the unreachable 5349 TURN/TLS listener
- Hub: update Element Calling router-port guidance to the new port set
### Fixed
- Element Calling: TURN relay could not deliver media to the SFU on routers
without NAT loopback (turn.allow_restricted_peer_cidrs now permits the LAN
subnet derived from the primary interface)
## [1.1.3] - 2026-08-27 ## [1.1.3] - 2026-08-27
### Added ### Added
+12 -12
View File
@@ -21,9 +21,9 @@ Lightning infrastructure, private cloud, and communications platform when you
are ready. are ready.
[Visit the Website](https://sovransystems.com) · [Visit the Website](https://sovransystems.com) ·
[Download the ISO](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.3.iso) · [Download the ISO](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.4.iso) ·
[Try it safely in a VM](#try-it-first-in-a-virtual-machine) · [Try it safely in a VM](#try-it-first-in-a-virtual-machine) ·
[Verify the Download](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.3.iso.sha256) · [Verify the Download](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.4.iso.sha256) ·
[Build from Source](#build-from-source) [Build from Source](#build-from-source)
<img src="assets/desktop-screenshot.webp" alt="Sovran_SystemsOS private Bitcoin desktop" width="800" /> <img src="assets/desktop-screenshot.webp" alt="Sovran_SystemsOS private Bitcoin desktop" width="800" />
@@ -345,8 +345,8 @@ with an imaging application such as [Balena Etcher](https://etcher.balena.io).
### 1. Download the ISO and checksum ### 1. Download the ISO and checksum
- [Download Sovran_SystemsOS-1.1.3.iso](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.3.iso) - [Download Sovran_SystemsOS-1.1.4.iso](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.4.iso)
- [Download Sovran_SystemsOS-1.1.3.iso.sha256](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.3.iso.sha256) - [Download Sovran_SystemsOS-1.1.4.iso.sha256](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.4.iso.sha256)
The download may take some time. Do not rename or modify the ISO before The download may take some time. Do not rename or modify the ISO before
verifying it, and keep both files in the same folder. verifying it, and keep both files in the same folder.
@@ -364,16 +364,16 @@ checksum exactly.
Open a terminal in the download folder and run: Open a terminal in the download folder and run:
```bash ```bash
sha256sum --check Sovran_SystemsOS-1.1.3.iso.sha256 sha256sum --check Sovran_SystemsOS-1.1.4.iso.sha256
``` ```
A successful comparison reports: A successful comparison reports:
```text ```text
Sovran_SystemsOS-1.1.3.iso: OK Sovran_SystemsOS-1.1.4.iso: OK
``` ```
You can also run `sha256sum Sovran_SystemsOS-1.1.3.iso` and compare the output You can also run `sha256sum Sovran_SystemsOS-1.1.4.iso` and compare the output
against the checksum file manually. against the checksum file manually.
</details> </details>
@@ -384,11 +384,11 @@ against the checksum file manually.
Open Terminal in the download folder and run: Open Terminal in the download folder and run:
```bash ```bash
shasum -a 256 Sovran_SystemsOS-1.1.3.iso shasum -a 256 Sovran_SystemsOS-1.1.4.iso
``` ```
Compare the value shown in Terminal with the value inside Compare the value shown in Terminal with the value inside
`Sovran_SystemsOS-1.1.3.iso.sha256`. `Sovran_SystemsOS-1.1.4.iso.sha256`.
</details> </details>
@@ -398,7 +398,7 @@ Compare the value shown in Terminal with the value inside
Open PowerShell in the download folder and run: Open PowerShell in the download folder and run:
```powershell ```powershell
Get-FileHash .\Sovran_SystemsOS-1.1.3.iso -Algorithm SHA256 Get-FileHash .\Sovran_SystemsOS-1.1.4.iso -Algorithm SHA256
``` ```
Compare the value under `Hash` with the published checksum. Compare the value under `Hash` with the published checksum.
@@ -413,7 +413,7 @@ match exactly.
1. Download and install [Balena Etcher](https://etcher.balena.io), then 1. Download and install [Balena Etcher](https://etcher.balena.io), then
connect the USB drive. connect the USB drive.
2. Choose **Flash from file** and select `Sovran_SystemsOS-1.1.3.iso`. 2. Choose **Flash from file** and select `Sovran_SystemsOS-1.1.4.iso`.
3. Choose **Select target**, select the USB drive, and review your selection 3. Choose **Select target**, select the USB drive, and review your selection
carefully. carefully.
4. Choose **Flash** and wait for the writing and verification process to 4. Choose **Flash** and wait for the writing and verification process to
@@ -856,7 +856,7 @@ primary location for collaboration. Please read our
## Privacy. Sovereignty. Bitcoin. ## Privacy. Sovereignty. Bitcoin.
[Visit Sovran Systems](https://sovransystems.com) · [Visit Sovran Systems](https://sovransystems.com) ·
[Download Sovran_SystemsOS](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.3.iso) · [Download Sovran_SystemsOS](https://downloads.sovransystems.com/Sovran_SystemsOS-1.1.4.iso) ·
[View the License](LICENSE) [View the License](LICENSE)
</div> </div>
+1 -1
View File
@@ -1 +1 @@
1.1.3 1.1.4
+10 -12
View File
@@ -291,11 +291,10 @@ FEATURE_REGISTRY = [
"port_requirements": [ "port_requirements": [
{"port": "80", "protocol": "TCP", "description": "HTTP (redirect to HTTPS)"}, {"port": "80", "protocol": "TCP", "description": "HTTP (redirect to HTTPS)"},
{"port": "443", "protocol": "TCP", "description": "HTTPS (domain)"}, {"port": "443", "protocol": "TCP", "description": "HTTPS (domain)"},
{"port": "7881", "protocol": "TCP", "description": "LiveKit WebRTC signalling"}, {"port": "7881", "protocol": "TCP", "description": "WebRTC media (TCP fallback)"},
{"port": "7882", "protocol": "UDP", "description": "LiveKit media (UDP mux)"}, {"port": "7882", "protocol": "UDP", "description": "WebRTC media (UDP)"},
{"port": "5349", "protocol": "TCP", "description": "TURN over TLS"}, {"port": "3478", "protocol": "UDP", "description": "TURN relay + STUN"},
{"port": "3478", "protocol": "UDP", "description": "TURN (STUN/relay)"}, {"port": "40000-40099", "protocol": "UDP", "description": "TURN relay (WebRTC media)"},
{"port": "30000-40000", "protocol": "TCP/UDP", "description": "TURN relay (WebRTC)"},
], ],
}, },
{ {
@@ -395,11 +394,10 @@ FEATURE_SERVICE_MAP = {
# Port requirements for service tiles (keyed by unit name or icon) # Port requirements for service tiles (keyed by unit name or icon)
_PORTS_ELEMENT_CALLING = [ _PORTS_ELEMENT_CALLING = [
{"port": "7881", "protocol": "TCP", "description": "LiveKit WebRTC signalling"}, {"port": "7881", "protocol": "TCP", "description": "WebRTC media (TCP fallback)"},
{"port": "7882", "protocol": "UDP", "description": "LiveKit media (UDP mux)"}, {"port": "7882", "protocol": "UDP", "description": "WebRTC media (UDP)"},
{"port": "5349", "protocol": "TCP", "description": "TURN over TLS"}, {"port": "3478", "protocol": "UDP", "description": "TURN relay + STUN"},
{"port": "3478", "protocol": "UDP", "description": "TURN (STUN/relay)"}, {"port": "40000-40099", "protocol": "UDP", "description": "TURN relay (WebRTC media)"},
{"port": "30000-40000", "protocol": "TCP/UDP", "description": "TURN relay (WebRTC)"},
] ]
# Units whose port requirements exist purely so the user can forward them in # Units whose port requirements exist purely so the user can forward them in
@@ -1114,7 +1112,7 @@ def _get_firewall_allowed_ports() -> dict[str, set[int]]:
) )
if proc.returncode == 0: if proc.returncode == 0:
for line in proc.stdout.splitlines(): for line in proc.stdout.splitlines():
# e.g. ACCEPT tcp -- ... dpt:443 or dpts:7882:7894 # e.g. ACCEPT tcp -- ... dpt:443 or dpts:40000:40099
m = re.search(r'(tcp|udp).*dpts?:(\d+)(?::(\d+))?', line) m = re.search(r'(tcp|udp).*dpts?:(\d+)(?::(\d+))?', line)
if m: if m:
proto_match = m.group(1) proto_match = m.group(1)
@@ -1172,7 +1170,7 @@ def _resolve_all_addresses_cached(domain: str) -> list[str]:
def _port_range_to_ints(port_str: str) -> list[int]: def _port_range_to_ints(port_str: str) -> list[int]:
"""Convert a port string like ``"443"``, ``"30000-40000"`` to a list of ints.""" """Convert a port string like ``"443"``, ``"40000-40099"`` to a list of ints."""
port_str = port_str.strip() port_str = port_str.strip()
if re.match(r'^\d+$', port_str): if re.match(r'^\d+$', port_str):
return [int(port_str)] return [int(port_str)]
@@ -88,7 +88,7 @@ function renderPortForwardGuideHtml(ports, opts) {
'<ul class="port-req-steps">' + '<ul class="port-req-steps">' +
'<li>Set the <strong>internal (private) port</strong> and the <strong>external (public) port</strong> to the <strong>same number</strong>.</li>' + '<li>Set the <strong>internal (private) port</strong> and the <strong>external (public) port</strong> to the <strong>same number</strong>.</li>' +
'<li>Match the <strong>protocol</strong> exactly — a rule set to TCP will not pass UDP traffic. Where the table says <strong>TCP + UDP</strong>, create both rules (or pick &ldquo;Both&rdquo;/&ldquo;TCP/UDP&rdquo; if your router offers it).</li>' + '<li>Match the <strong>protocol</strong> exactly — a rule set to TCP will not pass UDP traffic. Where the table says <strong>TCP + UDP</strong>, create both rules (or pick &ldquo;Both&rdquo;/&ldquo;TCP/UDP&rdquo; if your router offers it).</li>' +
'<li>For a range such as <strong>30000-40000</strong>, use your router&rsquo;s port-range fields — start 30000, end 40000 — rather than one rule per port.</li>' + '<li>For a range such as <strong>40000-40099</strong>, use your router&rsquo;s port-range fields — start 40000, end 40099 — rather than one rule per port.</li>' +
'</ul>' + '</ul>' +
'<table class="' + tableClass + '">' + '<table class="' + tableClass + '">' +
'<thead><tr><th>Port(s)</th><th>Protocol</th><th>Used for</th></tr></thead>' + '<thead><tr><th>Port(s)</th><th>Protocol</th><th>Used for</th></tr></thead>' +
+1 -1
View File
@@ -10,7 +10,7 @@
"albyhub.service": "1.24.0", "albyhub.service": "1.24.0",
"mempool.service": "3.2.1", "mempool.service": "3.2.1",
"matrix-synapse.service": "1.115.0", "matrix-synapse.service": "1.115.0",
"livekit.service": "1.5.2", "livekit.service": "1.13.6",
"vaultwarden.service": "1.32.0", "vaultwarden.service": "1.32.0",
"haven-relay.service": "0.1.0" "haven-relay.service": "0.1.0"
} }
Generated
+27 -27
View File
@@ -5,11 +5,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1787839684, "lastModified": 1787862680,
"narHash": "sha256-xTafqsVs2/zyGsaCel41QT5Lpbs/rG56Kp+YsmLXL8I=", "narHash": "sha256-mv+W62cI1Bjtkz9k8N8M7+7VSauv0/WmBrDYjcy5sE0=",
"owner": "emmanuelrosa", "owner": "emmanuelrosa",
"repo": "btc-clients-nix", "repo": "btc-clients-nix",
"rev": "ef81159876930802ef9861aa5c039cec6886a30f", "rev": "f00585b12e751ac738392e2cccfbe305d077e2d4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -26,11 +26,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1785627969, "lastModified": 1787559586,
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=", "narHash": "sha256-onL0VLf9vPllmT0H/OlURIU5r5t5WIEl7t4tVNKT0Nw=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a", "rev": "9d0d87172c374f89da73c1cfe6d81ae62feac1f1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -56,11 +56,11 @@
}, },
"nixpkgs-stable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1787753485, "lastModified": 1788297115,
"narHash": "sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg=", "narHash": "sha256-Z+vUNbfd2FIKkWOTkcT7RYlh3oFCnig/d2eXD1SWf2E=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "062346a6d85bc4b49dfaa61c986e9c5be21217d1", "rev": "a3116115851d68b8952a2a4221cc25a84e56b532",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -72,11 +72,11 @@
}, },
"nixpkgs-stable_2": { "nixpkgs-stable_2": {
"locked": { "locked": {
"lastModified": 1787753485, "lastModified": 1788297115,
"narHash": "sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg=", "narHash": "sha256-Z+vUNbfd2FIKkWOTkcT7RYlh3oFCnig/d2eXD1SWf2E=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "062346a6d85bc4b49dfaa61c986e9c5be21217d1", "rev": "a3116115851d68b8952a2a4221cc25a84e56b532",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -88,11 +88,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1787498568, "lastModified": 1788179007,
"narHash": "sha256-9i/VTdusq/+NM/tz+J1Re+ojkMB8MBf0QshnYfzHz30=", "narHash": "sha256-hn1oU2rue2SYK8dAr8+WNZWtbsz1S2W5mnHlSEuh3bo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "56c02bc00adcf003215cc4bd996d6efaf4cff188", "rev": "34ab99075ac4f7e40cf037eef32cb1c360bb85e9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -104,11 +104,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1787364730, "lastModified": 1787631388,
"narHash": "sha256-NcYt9QJfpJiF1lAyN8BDPB4EeScbPU+EwQqPiBElrpU=", "narHash": "sha256-vMiXptXarfSdJb1Gkc+FYVOAibuBRj7qxGa8z68q1Uw=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a831408e6378bc02ebf8cc09b52c96ca86f6bab4", "rev": "ac6b2166e7a9375683b8e98f860f273222337b16",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -120,11 +120,11 @@
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1787736819, "lastModified": 1788179007,
"narHash": "sha256-cV5xEJJK3BvhU8rEd4mC9UsmDi5qscv/kzGPhBRC5WA=", "narHash": "sha256-hn1oU2rue2SYK8dAr8+WNZWtbsz1S2W5mnHlSEuh3bo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9fbb54b33e91ee4ca368e35a78e0613c720600b3", "rev": "34ab99075ac4f7e40cf037eef32cb1c360bb85e9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -141,11 +141,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1787691219, "lastModified": 1788190018,
"narHash": "sha256-CjeJTOjrluiDuL0W/YBFkSBOQjBYmdKwj7hWAJ4PIh0=", "narHash": "sha256-59BAfH0txPAZrPBF4QJqwvUWppD+ICrcjA1LZAmPnrQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixvim", "repo": "nixvim",
"rev": "ebec1ae277a50b16e7d6682edd009ef585d68261", "rev": "41844750e55f17b1385d5b09ca7ade5f11f49506",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -169,11 +169,11 @@
"nixpkgs-stable": "nixpkgs-stable_2" "nixpkgs-stable": "nixpkgs-stable_2"
}, },
"locked": { "locked": {
"lastModified": 1788225336, "lastModified": 1788378694,
"narHash": "sha256-ixw/ChkLWWrLOzoiSa9n1g2g5KDIVkedC6eYFCba+gA=", "narHash": "sha256-s2yvGC1IHrQq2jP4KSbV2TA9Gh1T/YkcS+9neS/WcVI=",
"owner": "naturallaw777", "owner": "naturallaw777",
"repo": "Sovran_Bitcoin", "repo": "Sovran_Bitcoin",
"rev": "37caecaa3f3666231c8ec82a9a9a0df53fa40910", "rev": "b4678fcc712da9dd01c167f62275192183490085",
"type": "github" "type": "github"
}, },
"original": { "original": {
+18
View File
@@ -19,6 +19,24 @@
system = prev.stdenv.hostPlatform.system; system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true; config.allowUnfree = true;
}; };
# Pin LiveKit to 1.13.6: element-calling.nix sets
# rtc.advertise_internal_ip, which gives LAN callers a host candidate
# so calls work on Wi-Fi without the router needing NAT-hairpin. That
# flag is only honoured when node_ip is set manually from LiveKit
# v1.13.6 (mediatransportutil f234b53); nixpkgs-unstable currently
# ships 1.13.5. Remove this override once nixpkgs-unstable reaches
# >= 1.13.6.
livekit = prev.livekit.overrideAttrs (old: {
version = "1.13.6";
src = prev.fetchFromGitHub {
owner = "livekit";
repo = "livekit";
rev = "v1.13.6";
hash = "sha256-sUAx6ooeEUUqot5xuZv7xiQa3DdRFVULteTwYgFUzCI=";
};
vendorHash = "sha256-nOGSmoNuQQm/sIVI1HojsiS4GkbhA68uYMQ6X7d4a5Q=";
});
}; };
in in
{ {
+38 -7
View File
@@ -185,6 +185,14 @@ EOF
fi fi
echo "Detected primary network interface: $IFACE" echo "Detected primary network interface: $IFACE"
# Derive the LAN subnet this box sits on so the embedded TURN relay
# is allowed to hand media to LiveKit's LAN host candidate (see the
# allow_restricted_peer_cidrs block below). Computed from the primary
# interface's own address, so it always matches the subnet the LAN
# clients (phones on Wi-Fi) actually live on.
LAN_CIDR=$(ip -4 -o addr show dev "$IFACE" | awk '{print $4}' | grep -vE '^(127\.|169\.254\.)' | head -n1 | python3 -c 'import sys, ipaddress; s = sys.stdin.read().strip(); print(str(ipaddress.ip_network(s, strict=False)) if s else "")' 2>/dev/null)
if [ -n "$LAN_CIDR" ]; then echo "Derived LAN CIDR for TURN relay: $LAN_CIDR"; else echo "Derived LAN CIDR for TURN relay: <none>"; fi
# Generate the full LiveKit config the daemon will load. turn.domain and # Generate the full LiveKit config the daemon will load. turn.domain and
# rtc.interfaces.includes are only known at runtime, so they are # rtc.interfaces.includes are only known at runtime, so they are
# substituted here. The cert/key paths point at the LoadCredential-staged # substituted here. The cert/key paths point at the LoadCredential-staged
@@ -229,10 +237,9 @@ port: 7880
rtc: rtc:
use_external_ip: false use_external_ip: false
node_ip: $PUBLIC_IP node_ip: $PUBLIC_IP
advertise_internal_ip: true
tcp_port: 7881 tcp_port: 7881
udp_port: 7882 udp_port: 7882
port_range_start: 30000
port_range_end: 40000
interfaces: interfaces:
includes: includes:
- $IFACE - $IFACE
@@ -244,10 +251,9 @@ port: 7880
rtc: rtc:
use_external_ip: true use_external_ip: true
skip_external_ip_validation: true skip_external_ip_validation: true
advertise_internal_ip: true
tcp_port: 7881 tcp_port: 7881
udp_port: 7882 udp_port: 7882
port_range_start: 30000
port_range_end: 40000
interfaces: interfaces:
includes: includes:
- $IFACE - $IFACE
@@ -264,16 +270,37 @@ EOF
# vhost (/livekit/jwt/sfu_webhook → 8073). # vhost (/livekit/jwt/sfu_webhook → 8073).
LK_KEY=$(cut -d: -f1 < ${livekitKeyFile} | tr -d '[:space:]') LK_KEY=$(cut -d: -f1 < ${livekitKeyFile} | tr -d '[:space:]')
# TURN/TLS is intentionally not configured (no tls_port): LiveKit
# advertises turns:<domain>:443 to clients regardless of tls_port, so
# a 5349 TURN/TLS listener would be unreachable and only adds attack
# surface. The staged cert/key stay for a future TURN/TLS-on-443
# (Caddy layer4 SNI) setup.
cat >> /run/livekit/livekit.yaml <<EOF cat >> /run/livekit/livekit.yaml <<EOF
room: room:
auto_create: false auto_create: false
turn: turn:
enabled: true enabled: true
domain: $MATRIX domain: $MATRIX
tls_port: 5349
udp_port: 3478 udp_port: 3478
relay_range_start: 40000
relay_range_end: 40099
cert_file: /run/credentials/livekit.service/turn-cert cert_file: /run/credentials/livekit.service/turn-cert
key_file: /run/credentials/livekit.service/turn-key key_file: /run/credentials/livekit.service/turn-key
EOF
# By default the embedded TURN relay refuses to send media to
# private/loopback peers. That would force its final hop to the
# public/WAN IP (hairpin NAT) — exactly what breaks calls on routers
# without NAT loopback. Allow the LAN subnet so the relay can deliver
# directly to LiveKit's LAN host candidate instead.
if [ -n "$LAN_CIDR" ]; then
cat >> /run/livekit/livekit.yaml <<EOF
allow_restricted_peer_cidrs:
- $LAN_CIDR
EOF
fi
cat >> /run/livekit/livekit.yaml <<EOF
webhook: webhook:
api_key: $LK_KEY api_key: $LK_KEY
urls: urls:
@@ -315,10 +342,14 @@ EOF
"turn-key:/var/lib/livekit/turn.key" "turn-key:/var/lib/livekit/turn.key"
]; ];
networking.firewall.allowedTCPPorts = [ 5349 7881 ]; # 5349/TCP (TURN/TLS) is deliberately absent — see livekit-turn-setup. RTC
# media uses the single UDP mux (7882); the 30000-40000 range is gone so
# media is no longer spread across 10000 ports. The TURN relay allocation
# range (40000-40099) is kept separate from the media mux.
networking.firewall.allowedTCPPorts = [ 7881 ];
networking.firewall.allowedUDPPorts = [ 3478 7882 ]; networking.firewall.allowedUDPPorts = [ 3478 7882 ];
networking.firewall.allowedUDPPortRanges = [ networking.firewall.allowedUDPPortRanges = [
{ from = 30000; to = 40000; } # LiveKit internal TURN relay range { from = 40000; to = 40099; } # LiveKit embedded TURN relay allocation range
]; ];
####### JWT SERVICE RUNTIME CONFIG ####### ####### JWT SERVICE RUNTIME CONFIG #######