From f908c909e57341c0718661a8ddb273d3cd599092 Mon Sep 17 00:00:00 2001 From: Christoph Hollizeck Date: Thu, 26 Mar 2026 15:12:07 +0100 Subject: [PATCH 1/7] loptland: init mautrix-bridge --- modules/hosts/loptland/default.nix | 1 + modules/server/acme.nix | 2 +- modules/server/matrix-synapse.nix | 18 ++--- modules/server/mautrix-discord.nix | 102 +++++++++++++++++++++++++++++ secrets/secrets-loptland.yaml | 8 ++- 5 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 modules/server/mautrix-discord.nix diff --git a/modules/hosts/loptland/default.nix b/modules/hosts/loptland/default.nix index 6335d8d..54c01b7 100644 --- a/modules/hosts/loptland/default.nix +++ b/modules/hosts/loptland/default.nix @@ -32,6 +32,7 @@ topLevel: { # services matrix-synapse + mautrix-discord # game server minecraft-server diff --git a/modules/server/acme.nix b/modules/server/acme.nix index 60d8b72..631f967 100644 --- a/modules/server/acme.nix +++ b/modules/server/acme.nix @@ -44,7 +44,7 @@ topLevel: { inherit (topLevel.config.flake.meta.users.cholli) email; group = lib.mkIf config.services.nginx.enable "nginx"; - reloadServices = lib.mkIf config.services.nginx.enable "nginx.service"; + reloadServices = lib.mkIf config.services.nginx.enable [ "nginx.service" ]; dnsProvider = "netcup"; environmentFile = config.sops.templates."netcup.env".path; diff --git a/modules/server/matrix-synapse.nix b/modules/server/matrix-synapse.nix index 1d1b59e..9dc150f 100644 --- a/modules/server/matrix-synapse.nix +++ b/modules/server/matrix-synapse.nix @@ -1,6 +1,6 @@ { flake.modules.nixos.matrix-synapse = - { config, ... }: + { config, pkgs, lib, ... }: let domainName = "alwayssleepy.online"; matrixPort = 8008; @@ -40,13 +40,15 @@ User = "postgres"; RemainAfterExit = true; }; - script = '' - COLLATION=$(psql -tAc "SELECT datcollate FROM pg_database WHERE datname = 'matrix-synapse'") - if [ "$COLLATION" != "C" ]; then - psql -c "DROP DATABASE \"matrix-synapse\"" - psql -c "CREATE DATABASE \"matrix-synapse\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"matrix-synapse\"" - fi - ''; + script = + let psql = lib.getExe' pkgs.postgresql "psql"; in + '' + COLLATION=$(${psql} -tAc "SELECT datcollate FROM pg_database WHERE datname = 'matrix-synapse'") + if [ "$COLLATION" != "C" ]; then + ${psql} -c "DROP DATABASE \"matrix-synapse\"" + ${psql} -c "CREATE DATABASE \"matrix-synapse\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"matrix-synapse\"" + fi + ''; }; services.matrix-synapse = { diff --git a/modules/server/mautrix-discord.nix b/modules/server/mautrix-discord.nix new file mode 100644 index 0000000..3459b2e --- /dev/null +++ b/modules/server/mautrix-discord.nix @@ -0,0 +1,102 @@ +{ + flake.modules.nixos.mautrix-discord = + { + config, + pkgs, + lib, + ... + }: + let + matrixDomain = "alwayssleepy.online"; + bridgePort = 29334; + sopsFile = ../../secrets/secrets-loptland.yaml; + in + { + sops.secrets."matrix/mautrix-discord/botToken" = { + inherit sopsFile; + owner = "mautrix-discord"; + }; + + sops.templates."mautrix-discord.env" = { + owner = "mautrix-discord"; + content = '' + MAUTRIX_DISCORD_DISCORD_BOT_TOKEN=${config.sops.placeholder."matrix/mautrix-discord/botToken"} + ''; + }; + + services.postgresql = { + ensureDatabases = [ "mautrix-discord" ]; + ensureUsers = [ + { + name = "mautrix-discord"; + ensureDBOwnership = true; + } + ]; + }; + + # mautrix-discord (like matrix-synapse) requires C collation + systemd.services."mautrix-discord-db-setup" = { + description = "Set up mautrix-discord PostgreSQL database with C collation"; + wantedBy = [ "mautrix-discord.service" ]; + before = [ "mautrix-discord.service" ]; + after = [ + "postgresql.service" + "postgresql-setup.service" + ]; + requires = [ "postgresql.service" ]; + serviceConfig = { + Type = "oneshot"; + User = "postgres"; + RemainAfterExit = true; + }; + script = + let + psql = lib.getExe' pkgs.postgresql "psql"; + in + '' + COLLATION=$(${psql} -tAc "SELECT datcollate FROM pg_database WHERE datname = 'mautrix-discord'") + if [ "$COLLATION" != "C" ]; then + ${psql} -c "DROP DATABASE \"mautrix-discord\"" + ${psql} -c "CREATE DATABASE \"mautrix-discord\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"mautrix-discord\"" + fi + ''; + }; + + # mautrix-discord depends on libolm which is deprecated/insecure upstream. + nixpkgs.config.permittedInsecurePackages = [ "olm-3.2.16" ]; + + services.mautrix-discord = { + enable = true; + environmentFile = config.sops.templates."mautrix-discord.env".path; + + settings = { + homeserver = { + address = "http://localhost:${toString 8008}"; + domain = matrixDomain; + }; + + appservice = { + address = "http://localhost:${toString bridgePort}"; + hostname = "127.0.0.1"; + port = bridgePort; + database = { + type = "postgres"; + uri = "postgres:///mautrix-discord?host=/var/run/postgresql"; + }; + }; + + bridge = { + relay = { + enabled = true; + admin_only = false; + }; + + permissions = { + "@cholli:${matrixDomain}" = "admin"; + "${matrixDomain}" = "user"; + }; + }; + }; + }; + }; +} diff --git a/secrets/secrets-loptland.yaml b/secrets/secrets-loptland.yaml index ad032e9..effcc4c 100644 --- a/secrets/secrets-loptland.yaml +++ b/secrets/secrets-loptland.yaml @@ -22,6 +22,8 @@ hydra: private-key: ENC[AES256_GCM,data:FqdXFj4/leKNtNJ1H1sBnb/Gnso9soaLtdUToMsx3O6LAn2smdkFrguY9EESm+o1nIBWwc1S2cE/sfH8FR89NWbyfCDTsQLHRIIEYMS2kKLv7hqqdsmyQojW38TnUYtSo5W1V9pdmeYuotUrM7bPmW/Io/7/G/vW6LxtI7Mx1qT7OXnyEJVYsvY6TtJitWO0/jGUAGOyvu/+YhV4yRmArM2kjT+iYb8/dN0HpqCwo6aLvY7ctAA6ggESciuovEtUMv19y+RpMUaHxloziM3SFz/GjXekrqtPGDkCUusSChXuhzfmZDoz4dzNnkKn8HsmxzByzaTyNH9kCxzNV7vULTKi6/O4ny64FOk6pjymz2Yv6pK+pm3tP2wrPwynn3C1giwFCGn+2Nazixj4g4wd5iSFwNeAsDbLU0b3YN/NgQv0TeKGXR01Xgqvt06vtAnkpu8byPBUX5cz15kJckeztVHYCQyz6Uthk6NN+ScLok1z3I7Vn37KsF0Ka7k22aPMwXLLKkfEneavT41x1VNBq7Nedf9EFjjUG8S7,iv:mTlEphmcoFMv7dxIeSpsi77e3CJULcXxcOF1Nq66mUM=,tag:K2aGpaw2xeEj8537kB/cGA==,type:str] matrix: registrationSharedSecret: ENC[AES256_GCM,data:6IBlAfQhWlywWo/l8u5gAfW7bTgXwrAyk8WBBWkJQK+FL9LvUU5hDscozHrPIiRRzZdyeoAZ7phirDk3kN9E6Q==,iv:arZaxnIEUU3psaV8PqKAb46nlq73r2SAVlmCY+y+HB0=,tag:X/zsAtryEfl2PHKQ6GQfbg==,type:str] + mautrix-discord: + botToken: ENC[AES256_GCM,data:IrYMnUNorLK8853LXubpaXX2LwKbtlsdQzDHoeUq1VLyeH6Kz2CdnOV7UfuR4I0oEXBvw16PS+aBqjQCLcWGgXdTInEmq7lJ,iv:FmPlP1ZTdTTVcJeO0sKwiyaJ9KrZ8jbbyEiCK+O2XuI=,tag:Z+gVRNC34XV2OAUJcburIQ==,type:str] sops: age: - recipient: age1pc92kl38mfr0j68dxww7tpzvqp3lpw6lwfylj6hn2k3rf4rddgtsjxdx47 @@ -42,7 +44,7 @@ sops: czdSTjNGSEpURlZEUTlIaUtGQUk5cW8KvylMTgtmHNvGnN7DonAsYQZB31mVli75 3OTN+mOetq2YNxh/Se7vqzwbZnshfTDk9nJi9bKZQhBt2nYR8eLRkg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-26T10:22:50Z" - mac: ENC[AES256_GCM,data:RFNQKhb5AGMNZuZ8efT8s2/DrwOaN+Lge4M2a36NOzuNJ4xTI3Xcp0vEqpARplF9PSBZ64asWYqu4e21+KfS76Is6EaSyvfUc53QoX38zjn6S7EobiVwkCXcEfOAXOd14qqQNOBHcC4ELI5Ah0N7x/iYjX2+BYILQQCcgAGnbDE=,iv:/vJ7JsiP8adC5IUBYod/iHA3qQtDfrV9fmaglZyzQCA=,tag:vy9FfzKXSCCBM3cVMcAcfQ==,type:str] + lastmodified: "2026-03-26T12:52:45Z" + mac: ENC[AES256_GCM,data:ObHBFxdJlDrJJY9y+yRAJ+7lnBbIpAzV53Jc6BR5lvuwywu1LgPTigqs2YgK8Nnl7GSsW84s4ewN+aYj5UANx47iylSCyIQmfLz56d8r6REjNtH/hnRyoR7s2tFHE8FYlsW9P2PNSNBkjkPovWrPBejZ4ZmZdhaXbCx/13tJXU8=,iv:X6FyE7S5uo0fwluFtpUraiLJQ4FMbAMBiMaaggPaWdY=,tag:VEHWZ8QMGulYs0h+Q1CAvA==,type:str] unencrypted_suffix: _unencrypted - version: 3.11.0 + version: 3.12.2 From 8576262aca07d9d8e6bfa1b8023889859536da90 Mon Sep 17 00:00:00 2001 From: Christoph Hollizeck Date: Thu, 26 Mar 2026 17:43:07 +0100 Subject: [PATCH 2/7] loptland: init element-call --- modules/hosts/loptland/default.nix | 1 + modules/hosts/loptland/nginx.nix | 58 +++++++++++++++++++++++++++--- modules/server/element-call.nix | 48 +++++++++++++++++++++++++ secrets/secrets-loptland.yaml | 6 ++-- 4 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 modules/server/element-call.nix diff --git a/modules/hosts/loptland/default.nix b/modules/hosts/loptland/default.nix index 54c01b7..aaf6f9e 100644 --- a/modules/hosts/loptland/default.nix +++ b/modules/hosts/loptland/default.nix @@ -33,6 +33,7 @@ topLevel: { # services matrix-synapse mautrix-discord + element-call # game server minecraft-server diff --git a/modules/hosts/loptland/nginx.nix b/modules/hosts/loptland/nginx.nix index d6b759d..a28c106 100644 --- a/modules/hosts/loptland/nginx.nix +++ b/modules/hosts/loptland/nginx.nix @@ -8,6 +8,9 @@ }: let domainName = "christophhollizeck.dev"; + matrixDomain = "alwayssleepy.online"; + livekitPort = 7880; + lkJwtPort = 8089; in { services.nginx = { @@ -50,9 +53,9 @@ }; }; - "matrix.alwayssleepy.online" = lib.mkIf config.services.matrix-synapse.enable { + "matrix.${matrixDomain}" = lib.mkIf config.services.matrix-synapse.enable { forceSSL = true; - useACMEHost = "alwayssleepy.online"; + useACMEHost = matrixDomain; locations."/" = { proxyPass = "http://localhost:${toString 8008}"; @@ -62,15 +65,60 @@ }; }; + "call.${matrixDomain}" = lib.mkIf config.services.lk-jwt-service.enable { + forceSSL = true; + useACMEHost = matrixDomain; + + locations."= /config.json" = { + extraConfig = '' + default_type application/json; + return 200 '${builtins.toJSON { + default_server_config = { + "m.homeserver" = { + base_url = "https://matrix.${matrixDomain}"; + server_name = matrixDomain; + }; + }; + livekit = { + livekit_service_url = "https://call.${matrixDomain}/livekit/jwt"; + }; + }}'; + ''; + }; + + locations."/" = { + root = "${pkgs.element-call}"; + tryFiles = "$uri /index.html"; + extraConfig = '' + add_header Cache-Control "no-cache" always; + ''; + }; + + # Proxy lk-jwt-service for token generation + locations."/livekit/jwt" = { + proxyPass = "http://localhost:${toString lkJwtPort}"; + }; + + # Proxy LiveKit SFU websocket + locations."/livekit/sfu" = { + proxyPass = "http://localhost:${toString livekitPort}"; + extraConfig = '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + ''; + }; + }; + # .well-known Matrix delegation so Matrix IDs are @user:alwayssleepy.online "alwayssleepy.online" = { forceSSL = true; - useACMEHost = "alwayssleepy.online"; + useACMEHost = matrixDomain; locations."/.well-known/matrix/server" = { extraConfig = '' default_type application/json; - return 200 '{"m.server":"matrix.alwayssleepy.online:443"}'; + return 200 '{"m.server":"matrix.${matrixDomain}:443"}'; ''; }; @@ -78,7 +126,7 @@ extraConfig = '' default_type application/json; add_header 'Access-Control-Allow-Origin' '*'; - return 200 '{"m.homeserver":{"base_url":"https://matrix.alwayssleepy.online"}}'; + return 200 '{"m.homeserver":{"base_url":"https://matrix.${matrixDomain}"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://call.${matrixDomain}/livekit/jwt"}]}'; ''; }; }; diff --git a/modules/server/element-call.nix b/modules/server/element-call.nix new file mode 100644 index 0000000..2e10ba8 --- /dev/null +++ b/modules/server/element-call.nix @@ -0,0 +1,48 @@ +topLevel: { + flake.modules.nixos.element-call = + { config, lib, pkgs, ... }: + let + matrixDomain = "alwayssleepy.online"; + livekitPort = 7880; + livekitRtcPortStart = 50000; + livekitRtcPortEnd = 50200; + lkJwtPort = 8089; + sopsFile = ../../secrets/secrets-loptland.yaml; + in + { + sops.secrets."matrix/livekit/keyFile" = { + inherit sopsFile; + # livekit and lk-jwt-service both read this file + mode = "0440"; + group = "livekit-secrets"; + }; + + users.groups.livekit-secrets = { }; + + # LiveKit SFU media server + services.livekit = { + enable = true; + openFirewall = true; + keyFile = config.sops.secrets."matrix/livekit/keyFile".path; + + settings = { + port = livekitPort; + rtc = { + port_range_start = livekitRtcPortStart; + port_range_end = livekitRtcPortEnd; + }; + }; + }; + + # lk-jwt-service: bridges Matrix OpenID tokens to LiveKit JWTs + services.lk-jwt-service = { + enable = true; + livekitUrl = "wss://call.${matrixDomain}/livekit/sfu"; + keyFile = config.sops.secrets."matrix/livekit/keyFile".path; + port = lkJwtPort; + }; + + # Allow lk-jwt-service (DynamicUser) to read the secrets file + systemd.services.lk-jwt-service.serviceConfig.SupplementaryGroups = [ "livekit-secrets" ]; + }; +} diff --git a/secrets/secrets-loptland.yaml b/secrets/secrets-loptland.yaml index effcc4c..b9e4d11 100644 --- a/secrets/secrets-loptland.yaml +++ b/secrets/secrets-loptland.yaml @@ -24,6 +24,8 @@ matrix: registrationSharedSecret: ENC[AES256_GCM,data:6IBlAfQhWlywWo/l8u5gAfW7bTgXwrAyk8WBBWkJQK+FL9LvUU5hDscozHrPIiRRzZdyeoAZ7phirDk3kN9E6Q==,iv:arZaxnIEUU3psaV8PqKAb46nlq73r2SAVlmCY+y+HB0=,tag:X/zsAtryEfl2PHKQ6GQfbg==,type:str] mautrix-discord: botToken: ENC[AES256_GCM,data:IrYMnUNorLK8853LXubpaXX2LwKbtlsdQzDHoeUq1VLyeH6Kz2CdnOV7UfuR4I0oEXBvw16PS+aBqjQCLcWGgXdTInEmq7lJ,iv:FmPlP1ZTdTTVcJeO0sKwiyaJ9KrZ8jbbyEiCK+O2XuI=,tag:Z+gVRNC34XV2OAUJcburIQ==,type:str] + livekit: + keyFile: ENC[AES256_GCM,data:h7pIrLswWJhS5vkcvVquMCFC/prCVavCJWUck7W6x7emH+qalXxmMxPnkCskFr163re+Y04PuOsrtFe4,iv:8BDrFPDhC5UHAzGUZ77hzNQh2RuMzdWphLXt9WI54gk=,tag:66MGl67bpOF/3n/vzYUOuw==,type:str] sops: age: - recipient: age1pc92kl38mfr0j68dxww7tpzvqp3lpw6lwfylj6hn2k3rf4rddgtsjxdx47 @@ -44,7 +46,7 @@ sops: czdSTjNGSEpURlZEUTlIaUtGQUk5cW8KvylMTgtmHNvGnN7DonAsYQZB31mVli75 3OTN+mOetq2YNxh/Se7vqzwbZnshfTDk9nJi9bKZQhBt2nYR8eLRkg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-26T12:52:45Z" - mac: ENC[AES256_GCM,data:ObHBFxdJlDrJJY9y+yRAJ+7lnBbIpAzV53Jc6BR5lvuwywu1LgPTigqs2YgK8Nnl7GSsW84s4ewN+aYj5UANx47iylSCyIQmfLz56d8r6REjNtH/hnRyoR7s2tFHE8FYlsW9P2PNSNBkjkPovWrPBejZ4ZmZdhaXbCx/13tJXU8=,iv:X6FyE7S5uo0fwluFtpUraiLJQ4FMbAMBiMaaggPaWdY=,tag:VEHWZ8QMGulYs0h+Q1CAvA==,type:str] + lastmodified: "2026-03-26T15:57:42Z" + mac: ENC[AES256_GCM,data:gyiA6KTHS6I/geGuAldEHibD9TXKSW25k5hF+Ay1vFHdvjBqwvZ2ExOh/mgTz9qvE3FC24R2le8BTQbRvymWaE6wulzNEuzh3KoQHdsJpVWUIfizESj3Nt83WmJPr4jW7suTslhXdFHU3a1RTOHkiqARZtg9HdWg/Wo8gsLkXLU=,iv:L0aEQkQ5pyPKzVxbWrOYtIszV/AapdsdSI0yH7+xqrI=,tag:xvKEcWMfy1GnU4p1OfH1lA==,type:str] unencrypted_suffix: _unencrypted version: 3.12.2 From f89d1b8275a9d854235ff78e3494151a5bc1a0c5 Mon Sep 17 00:00:00 2001 From: Christoph Hollizeck Date: Tue, 31 Mar 2026 23:23:41 +0200 Subject: [PATCH 3/7] loptland: init matrix-signal --- modules/hosts/loptland/default.nix | 1 + modules/server/mautrix-signal.nix | 82 ++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 modules/server/mautrix-signal.nix diff --git a/modules/hosts/loptland/default.nix b/modules/hosts/loptland/default.nix index aaf6f9e..a4d2871 100644 --- a/modules/hosts/loptland/default.nix +++ b/modules/hosts/loptland/default.nix @@ -33,6 +33,7 @@ topLevel: { # services matrix-synapse mautrix-discord + mautrix-signal element-call # game server diff --git a/modules/server/mautrix-signal.nix b/modules/server/mautrix-signal.nix new file mode 100644 index 0000000..d39a2d9 --- /dev/null +++ b/modules/server/mautrix-signal.nix @@ -0,0 +1,82 @@ +{ + flake.modules.nixos.mautrix-signal = + { + config, + pkgs, + lib, + ... + }: + let + matrixDomain = "alwayssleepy.online"; + bridgePort = 29335; + sopsFile = ../../secrets/secrets-loptland.yaml; + in + { + services.postgresql = { + ensureDatabases = [ "mautrix-signal" ]; + ensureUsers = [ + { + name = "mautrix-signal"; + ensureDBOwnership = true; + } + ]; + }; + + # mautrix-signal (like matrix-synapse) requires C collation + systemd.services."mautrix-signal-db-setup" = { + description = "Set up mautrix-signal PostgreSQL database with C collation"; + wantedBy = [ "mautrix-signal.service" ]; + before = [ "mautrix-signal.service" ]; + after = [ + "postgresql.service" + "postgresql-setup.service" + ]; + requires = [ "postgresql.service" ]; + serviceConfig = { + Type = "oneshot"; + User = "postgres"; + RemainAfterExit = true; + }; + script = + let + psql = lib.getExe' pkgs.postgresql "psql"; + in + '' + COLLATION=$(${psql} -tAc "SELECT datcollate FROM pg_database WHERE datname = 'mautrix-signal'") + if [ "$COLLATION" != "C" ]; then + ${psql} -c "DROP DATABASE \"mautrix-signal\"" + ${psql} -c "CREATE DATABASE \"mautrix-signal\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"mautrix-signal\"" + fi + ''; + }; + + services.mautrix-signal = { + enable = true; + + settings = { + homeserver = { + address = "http://localhost:${toString 8008}"; + domain = matrixDomain; + }; + + appservice = { + address = "http://localhost:${toString bridgePort}"; + hostname = "127.0.0.1"; + port = bridgePort; + }; + + database = { + type = "postgres"; + uri = "postgres:///mautrix-signal?host=/var/run/postgresql"; + }; + + bridge = { + permissions = { + "@cholli:${matrixDomain}" = "admin"; + "${matrixDomain}" = "user"; + }; + }; + }; + }; + }; +} From 8fab0c421c78b2fafe01090721548c4617c42e92 Mon Sep 17 00:00:00 2001 From: Christoph Hollizeck Date: Thu, 26 Mar 2026 15:12:07 +0100 Subject: [PATCH 4/7] loptland: init mautrix-bridge --- modules/hosts/loptland/default.nix | 1 + modules/server/acme.nix | 2 +- modules/server/matrix-synapse.nix | 18 ++--- modules/server/mautrix-discord.nix | 105 +++++++++++++++++++++++++++++ secrets/secrets-loptland.yaml | 8 ++- 5 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 modules/server/mautrix-discord.nix diff --git a/modules/hosts/loptland/default.nix b/modules/hosts/loptland/default.nix index 6335d8d..54c01b7 100644 --- a/modules/hosts/loptland/default.nix +++ b/modules/hosts/loptland/default.nix @@ -32,6 +32,7 @@ topLevel: { # services matrix-synapse + mautrix-discord # game server minecraft-server diff --git a/modules/server/acme.nix b/modules/server/acme.nix index 60d8b72..631f967 100644 --- a/modules/server/acme.nix +++ b/modules/server/acme.nix @@ -44,7 +44,7 @@ topLevel: { inherit (topLevel.config.flake.meta.users.cholli) email; group = lib.mkIf config.services.nginx.enable "nginx"; - reloadServices = lib.mkIf config.services.nginx.enable "nginx.service"; + reloadServices = lib.mkIf config.services.nginx.enable [ "nginx.service" ]; dnsProvider = "netcup"; environmentFile = config.sops.templates."netcup.env".path; diff --git a/modules/server/matrix-synapse.nix b/modules/server/matrix-synapse.nix index 1d1b59e..9dc150f 100644 --- a/modules/server/matrix-synapse.nix +++ b/modules/server/matrix-synapse.nix @@ -1,6 +1,6 @@ { flake.modules.nixos.matrix-synapse = - { config, ... }: + { config, pkgs, lib, ... }: let domainName = "alwayssleepy.online"; matrixPort = 8008; @@ -40,13 +40,15 @@ User = "postgres"; RemainAfterExit = true; }; - script = '' - COLLATION=$(psql -tAc "SELECT datcollate FROM pg_database WHERE datname = 'matrix-synapse'") - if [ "$COLLATION" != "C" ]; then - psql -c "DROP DATABASE \"matrix-synapse\"" - psql -c "CREATE DATABASE \"matrix-synapse\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"matrix-synapse\"" - fi - ''; + script = + let psql = lib.getExe' pkgs.postgresql "psql"; in + '' + COLLATION=$(${psql} -tAc "SELECT datcollate FROM pg_database WHERE datname = 'matrix-synapse'") + if [ "$COLLATION" != "C" ]; then + ${psql} -c "DROP DATABASE \"matrix-synapse\"" + ${psql} -c "CREATE DATABASE \"matrix-synapse\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"matrix-synapse\"" + fi + ''; }; services.matrix-synapse = { diff --git a/modules/server/mautrix-discord.nix b/modules/server/mautrix-discord.nix new file mode 100644 index 0000000..6ac6c60 --- /dev/null +++ b/modules/server/mautrix-discord.nix @@ -0,0 +1,105 @@ +{ + flake.modules.nixos.mautrix-discord = + { + config, + pkgs, + lib, + ... + }: + let + matrixDomain = "alwayssleepy.online"; + bridgePort = 29334; + sopsFile = ../../secrets/secrets-loptland.yaml; + in + { + sops.secrets."matrix/mautrix-discord/botToken" = { + inherit sopsFile; + owner = "mautrix-discord"; + }; + + sops.templates."mautrix-discord.env" = { + owner = "mautrix-discord"; + content = '' + MAUTRIX_DISCORD_DISCORD_BOT_TOKEN=${config.sops.placeholder."matrix/mautrix-discord/botToken"} + ''; + }; + + services.postgresql = { + ensureDatabases = [ "mautrix-discord" ]; + ensureUsers = [ + { + name = "mautrix-discord"; + ensureDBOwnership = true; + } + ]; + }; + + # mautrix-discord (like matrix-synapse) requires C collation + systemd.services."mautrix-discord-db-setup" = { + description = "Set up mautrix-discord PostgreSQL database with C collation"; + wantedBy = [ "mautrix-discord.service" ]; + before = [ "mautrix-discord.service" ]; + after = [ + "postgresql.service" + "postgresql-setup.service" + ]; + requires = [ "postgresql.service" ]; + serviceConfig = { + Type = "oneshot"; + User = "postgres"; + RemainAfterExit = true; + }; + script = + let + psql = lib.getExe' pkgs.postgresql "psql"; + in + '' + COLLATION=$(${psql} -tAc "SELECT datcollate FROM pg_database WHERE datname = 'mautrix-discord'") + if [ "$COLLATION" != "C" ]; then + ${psql} -c "DROP DATABASE \"mautrix-discord\"" + ${psql} -c "CREATE DATABASE \"mautrix-discord\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"mautrix-discord\"" + fi + ''; + }; + + # mautrix-discord depends on libolm which is deprecated/insecure upstream. + nixpkgs.config.permittedInsecurePackages = [ "olm-3.2.16" ]; + + services.mautrix-discord = { + enable = true; + environmentFile = config.sops.templates."mautrix-discord.env".path; + + settings = { + homeserver = { + address = "http://localhost:${toString 8008}"; + domain = matrixDomain; + }; + + appservice = { + address = "http://localhost:${toString bridgePort}"; + hostname = "127.0.0.1"; + port = bridgePort; + database = { + type = "postgres"; + uri = "postgres:///mautrix-discord?host=/var/run/postgresql"; + }; + }; + + bridge = { + relay = { + enabled = true; + admin_only = false; + }; + + permissions = { + "@cholli:${matrixDomain}" = "admin"; + "${matrixDomain}" = "user"; + }; + }; + }; + }; + + # Give matrix-synapse access to the registration file via group membership + users.users.matrix-synapse.extraGroups = [ "mautrix-discord" ]; + }; +} diff --git a/secrets/secrets-loptland.yaml b/secrets/secrets-loptland.yaml index ad032e9..effcc4c 100644 --- a/secrets/secrets-loptland.yaml +++ b/secrets/secrets-loptland.yaml @@ -22,6 +22,8 @@ hydra: private-key: ENC[AES256_GCM,data:FqdXFj4/leKNtNJ1H1sBnb/Gnso9soaLtdUToMsx3O6LAn2smdkFrguY9EESm+o1nIBWwc1S2cE/sfH8FR89NWbyfCDTsQLHRIIEYMS2kKLv7hqqdsmyQojW38TnUYtSo5W1V9pdmeYuotUrM7bPmW/Io/7/G/vW6LxtI7Mx1qT7OXnyEJVYsvY6TtJitWO0/jGUAGOyvu/+YhV4yRmArM2kjT+iYb8/dN0HpqCwo6aLvY7ctAA6ggESciuovEtUMv19y+RpMUaHxloziM3SFz/GjXekrqtPGDkCUusSChXuhzfmZDoz4dzNnkKn8HsmxzByzaTyNH9kCxzNV7vULTKi6/O4ny64FOk6pjymz2Yv6pK+pm3tP2wrPwynn3C1giwFCGn+2Nazixj4g4wd5iSFwNeAsDbLU0b3YN/NgQv0TeKGXR01Xgqvt06vtAnkpu8byPBUX5cz15kJckeztVHYCQyz6Uthk6NN+ScLok1z3I7Vn37KsF0Ka7k22aPMwXLLKkfEneavT41x1VNBq7Nedf9EFjjUG8S7,iv:mTlEphmcoFMv7dxIeSpsi77e3CJULcXxcOF1Nq66mUM=,tag:K2aGpaw2xeEj8537kB/cGA==,type:str] matrix: registrationSharedSecret: ENC[AES256_GCM,data:6IBlAfQhWlywWo/l8u5gAfW7bTgXwrAyk8WBBWkJQK+FL9LvUU5hDscozHrPIiRRzZdyeoAZ7phirDk3kN9E6Q==,iv:arZaxnIEUU3psaV8PqKAb46nlq73r2SAVlmCY+y+HB0=,tag:X/zsAtryEfl2PHKQ6GQfbg==,type:str] + mautrix-discord: + botToken: ENC[AES256_GCM,data:IrYMnUNorLK8853LXubpaXX2LwKbtlsdQzDHoeUq1VLyeH6Kz2CdnOV7UfuR4I0oEXBvw16PS+aBqjQCLcWGgXdTInEmq7lJ,iv:FmPlP1ZTdTTVcJeO0sKwiyaJ9KrZ8jbbyEiCK+O2XuI=,tag:Z+gVRNC34XV2OAUJcburIQ==,type:str] sops: age: - recipient: age1pc92kl38mfr0j68dxww7tpzvqp3lpw6lwfylj6hn2k3rf4rddgtsjxdx47 @@ -42,7 +44,7 @@ sops: czdSTjNGSEpURlZEUTlIaUtGQUk5cW8KvylMTgtmHNvGnN7DonAsYQZB31mVli75 3OTN+mOetq2YNxh/Se7vqzwbZnshfTDk9nJi9bKZQhBt2nYR8eLRkg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-26T10:22:50Z" - mac: ENC[AES256_GCM,data:RFNQKhb5AGMNZuZ8efT8s2/DrwOaN+Lge4M2a36NOzuNJ4xTI3Xcp0vEqpARplF9PSBZ64asWYqu4e21+KfS76Is6EaSyvfUc53QoX38zjn6S7EobiVwkCXcEfOAXOd14qqQNOBHcC4ELI5Ah0N7x/iYjX2+BYILQQCcgAGnbDE=,iv:/vJ7JsiP8adC5IUBYod/iHA3qQtDfrV9fmaglZyzQCA=,tag:vy9FfzKXSCCBM3cVMcAcfQ==,type:str] + lastmodified: "2026-03-26T12:52:45Z" + mac: ENC[AES256_GCM,data:ObHBFxdJlDrJJY9y+yRAJ+7lnBbIpAzV53Jc6BR5lvuwywu1LgPTigqs2YgK8Nnl7GSsW84s4ewN+aYj5UANx47iylSCyIQmfLz56d8r6REjNtH/hnRyoR7s2tFHE8FYlsW9P2PNSNBkjkPovWrPBejZ4ZmZdhaXbCx/13tJXU8=,iv:X6FyE7S5uo0fwluFtpUraiLJQ4FMbAMBiMaaggPaWdY=,tag:VEHWZ8QMGulYs0h+Q1CAvA==,type:str] unencrypted_suffix: _unencrypted - version: 3.11.0 + version: 3.12.2 From a2ebdf9884b21f11cbc80d10a3e601ebf63de721 Mon Sep 17 00:00:00 2001 From: Christoph Hollizeck Date: Thu, 26 Mar 2026 17:43:07 +0100 Subject: [PATCH 5/7] loptland: init element-call --- modules/hosts/loptland/default.nix | 1 + modules/hosts/loptland/nginx.nix | 58 +++++++++++++++++++++++++++--- modules/server/element-call.nix | 48 +++++++++++++++++++++++++ secrets/secrets-loptland.yaml | 6 ++-- 4 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 modules/server/element-call.nix diff --git a/modules/hosts/loptland/default.nix b/modules/hosts/loptland/default.nix index 54c01b7..aaf6f9e 100644 --- a/modules/hosts/loptland/default.nix +++ b/modules/hosts/loptland/default.nix @@ -33,6 +33,7 @@ topLevel: { # services matrix-synapse mautrix-discord + element-call # game server minecraft-server diff --git a/modules/hosts/loptland/nginx.nix b/modules/hosts/loptland/nginx.nix index d6b759d..a28c106 100644 --- a/modules/hosts/loptland/nginx.nix +++ b/modules/hosts/loptland/nginx.nix @@ -8,6 +8,9 @@ }: let domainName = "christophhollizeck.dev"; + matrixDomain = "alwayssleepy.online"; + livekitPort = 7880; + lkJwtPort = 8089; in { services.nginx = { @@ -50,9 +53,9 @@ }; }; - "matrix.alwayssleepy.online" = lib.mkIf config.services.matrix-synapse.enable { + "matrix.${matrixDomain}" = lib.mkIf config.services.matrix-synapse.enable { forceSSL = true; - useACMEHost = "alwayssleepy.online"; + useACMEHost = matrixDomain; locations."/" = { proxyPass = "http://localhost:${toString 8008}"; @@ -62,15 +65,60 @@ }; }; + "call.${matrixDomain}" = lib.mkIf config.services.lk-jwt-service.enable { + forceSSL = true; + useACMEHost = matrixDomain; + + locations."= /config.json" = { + extraConfig = '' + default_type application/json; + return 200 '${builtins.toJSON { + default_server_config = { + "m.homeserver" = { + base_url = "https://matrix.${matrixDomain}"; + server_name = matrixDomain; + }; + }; + livekit = { + livekit_service_url = "https://call.${matrixDomain}/livekit/jwt"; + }; + }}'; + ''; + }; + + locations."/" = { + root = "${pkgs.element-call}"; + tryFiles = "$uri /index.html"; + extraConfig = '' + add_header Cache-Control "no-cache" always; + ''; + }; + + # Proxy lk-jwt-service for token generation + locations."/livekit/jwt" = { + proxyPass = "http://localhost:${toString lkJwtPort}"; + }; + + # Proxy LiveKit SFU websocket + locations."/livekit/sfu" = { + proxyPass = "http://localhost:${toString livekitPort}"; + extraConfig = '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + ''; + }; + }; + # .well-known Matrix delegation so Matrix IDs are @user:alwayssleepy.online "alwayssleepy.online" = { forceSSL = true; - useACMEHost = "alwayssleepy.online"; + useACMEHost = matrixDomain; locations."/.well-known/matrix/server" = { extraConfig = '' default_type application/json; - return 200 '{"m.server":"matrix.alwayssleepy.online:443"}'; + return 200 '{"m.server":"matrix.${matrixDomain}:443"}'; ''; }; @@ -78,7 +126,7 @@ extraConfig = '' default_type application/json; add_header 'Access-Control-Allow-Origin' '*'; - return 200 '{"m.homeserver":{"base_url":"https://matrix.alwayssleepy.online"}}'; + return 200 '{"m.homeserver":{"base_url":"https://matrix.${matrixDomain}"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://call.${matrixDomain}/livekit/jwt"}]}'; ''; }; }; diff --git a/modules/server/element-call.nix b/modules/server/element-call.nix new file mode 100644 index 0000000..2e10ba8 --- /dev/null +++ b/modules/server/element-call.nix @@ -0,0 +1,48 @@ +topLevel: { + flake.modules.nixos.element-call = + { config, lib, pkgs, ... }: + let + matrixDomain = "alwayssleepy.online"; + livekitPort = 7880; + livekitRtcPortStart = 50000; + livekitRtcPortEnd = 50200; + lkJwtPort = 8089; + sopsFile = ../../secrets/secrets-loptland.yaml; + in + { + sops.secrets."matrix/livekit/keyFile" = { + inherit sopsFile; + # livekit and lk-jwt-service both read this file + mode = "0440"; + group = "livekit-secrets"; + }; + + users.groups.livekit-secrets = { }; + + # LiveKit SFU media server + services.livekit = { + enable = true; + openFirewall = true; + keyFile = config.sops.secrets."matrix/livekit/keyFile".path; + + settings = { + port = livekitPort; + rtc = { + port_range_start = livekitRtcPortStart; + port_range_end = livekitRtcPortEnd; + }; + }; + }; + + # lk-jwt-service: bridges Matrix OpenID tokens to LiveKit JWTs + services.lk-jwt-service = { + enable = true; + livekitUrl = "wss://call.${matrixDomain}/livekit/sfu"; + keyFile = config.sops.secrets."matrix/livekit/keyFile".path; + port = lkJwtPort; + }; + + # Allow lk-jwt-service (DynamicUser) to read the secrets file + systemd.services.lk-jwt-service.serviceConfig.SupplementaryGroups = [ "livekit-secrets" ]; + }; +} diff --git a/secrets/secrets-loptland.yaml b/secrets/secrets-loptland.yaml index effcc4c..b9e4d11 100644 --- a/secrets/secrets-loptland.yaml +++ b/secrets/secrets-loptland.yaml @@ -24,6 +24,8 @@ matrix: registrationSharedSecret: ENC[AES256_GCM,data:6IBlAfQhWlywWo/l8u5gAfW7bTgXwrAyk8WBBWkJQK+FL9LvUU5hDscozHrPIiRRzZdyeoAZ7phirDk3kN9E6Q==,iv:arZaxnIEUU3psaV8PqKAb46nlq73r2SAVlmCY+y+HB0=,tag:X/zsAtryEfl2PHKQ6GQfbg==,type:str] mautrix-discord: botToken: ENC[AES256_GCM,data:IrYMnUNorLK8853LXubpaXX2LwKbtlsdQzDHoeUq1VLyeH6Kz2CdnOV7UfuR4I0oEXBvw16PS+aBqjQCLcWGgXdTInEmq7lJ,iv:FmPlP1ZTdTTVcJeO0sKwiyaJ9KrZ8jbbyEiCK+O2XuI=,tag:Z+gVRNC34XV2OAUJcburIQ==,type:str] + livekit: + keyFile: ENC[AES256_GCM,data:h7pIrLswWJhS5vkcvVquMCFC/prCVavCJWUck7W6x7emH+qalXxmMxPnkCskFr163re+Y04PuOsrtFe4,iv:8BDrFPDhC5UHAzGUZ77hzNQh2RuMzdWphLXt9WI54gk=,tag:66MGl67bpOF/3n/vzYUOuw==,type:str] sops: age: - recipient: age1pc92kl38mfr0j68dxww7tpzvqp3lpw6lwfylj6hn2k3rf4rddgtsjxdx47 @@ -44,7 +46,7 @@ sops: czdSTjNGSEpURlZEUTlIaUtGQUk5cW8KvylMTgtmHNvGnN7DonAsYQZB31mVli75 3OTN+mOetq2YNxh/Se7vqzwbZnshfTDk9nJi9bKZQhBt2nYR8eLRkg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-26T12:52:45Z" - mac: ENC[AES256_GCM,data:ObHBFxdJlDrJJY9y+yRAJ+7lnBbIpAzV53Jc6BR5lvuwywu1LgPTigqs2YgK8Nnl7GSsW84s4ewN+aYj5UANx47iylSCyIQmfLz56d8r6REjNtH/hnRyoR7s2tFHE8FYlsW9P2PNSNBkjkPovWrPBejZ4ZmZdhaXbCx/13tJXU8=,iv:X6FyE7S5uo0fwluFtpUraiLJQ4FMbAMBiMaaggPaWdY=,tag:VEHWZ8QMGulYs0h+Q1CAvA==,type:str] + lastmodified: "2026-03-26T15:57:42Z" + mac: ENC[AES256_GCM,data:gyiA6KTHS6I/geGuAldEHibD9TXKSW25k5hF+Ay1vFHdvjBqwvZ2ExOh/mgTz9qvE3FC24R2le8BTQbRvymWaE6wulzNEuzh3KoQHdsJpVWUIfizESj3Nt83WmJPr4jW7suTslhXdFHU3a1RTOHkiqARZtg9HdWg/Wo8gsLkXLU=,iv:L0aEQkQ5pyPKzVxbWrOYtIszV/AapdsdSI0yH7+xqrI=,tag:xvKEcWMfy1GnU4p1OfH1lA==,type:str] unencrypted_suffix: _unencrypted version: 3.12.2 From 8406518178f0247f9c81003a5c6471e641c26bbf Mon Sep 17 00:00:00 2001 From: Christoph Hollizeck Date: Tue, 31 Mar 2026 23:23:41 +0200 Subject: [PATCH 6/7] loptland: init matrix-signal --- modules/hosts/loptland/default.nix | 1 + modules/server/mautrix-signal.nix | 82 ++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 modules/server/mautrix-signal.nix diff --git a/modules/hosts/loptland/default.nix b/modules/hosts/loptland/default.nix index aaf6f9e..a4d2871 100644 --- a/modules/hosts/loptland/default.nix +++ b/modules/hosts/loptland/default.nix @@ -33,6 +33,7 @@ topLevel: { # services matrix-synapse mautrix-discord + mautrix-signal element-call # game server diff --git a/modules/server/mautrix-signal.nix b/modules/server/mautrix-signal.nix new file mode 100644 index 0000000..d39a2d9 --- /dev/null +++ b/modules/server/mautrix-signal.nix @@ -0,0 +1,82 @@ +{ + flake.modules.nixos.mautrix-signal = + { + config, + pkgs, + lib, + ... + }: + let + matrixDomain = "alwayssleepy.online"; + bridgePort = 29335; + sopsFile = ../../secrets/secrets-loptland.yaml; + in + { + services.postgresql = { + ensureDatabases = [ "mautrix-signal" ]; + ensureUsers = [ + { + name = "mautrix-signal"; + ensureDBOwnership = true; + } + ]; + }; + + # mautrix-signal (like matrix-synapse) requires C collation + systemd.services."mautrix-signal-db-setup" = { + description = "Set up mautrix-signal PostgreSQL database with C collation"; + wantedBy = [ "mautrix-signal.service" ]; + before = [ "mautrix-signal.service" ]; + after = [ + "postgresql.service" + "postgresql-setup.service" + ]; + requires = [ "postgresql.service" ]; + serviceConfig = { + Type = "oneshot"; + User = "postgres"; + RemainAfterExit = true; + }; + script = + let + psql = lib.getExe' pkgs.postgresql "psql"; + in + '' + COLLATION=$(${psql} -tAc "SELECT datcollate FROM pg_database WHERE datname = 'mautrix-signal'") + if [ "$COLLATION" != "C" ]; then + ${psql} -c "DROP DATABASE \"mautrix-signal\"" + ${psql} -c "CREATE DATABASE \"mautrix-signal\" ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER \"mautrix-signal\"" + fi + ''; + }; + + services.mautrix-signal = { + enable = true; + + settings = { + homeserver = { + address = "http://localhost:${toString 8008}"; + domain = matrixDomain; + }; + + appservice = { + address = "http://localhost:${toString bridgePort}"; + hostname = "127.0.0.1"; + port = bridgePort; + }; + + database = { + type = "postgres"; + uri = "postgres:///mautrix-signal?host=/var/run/postgresql"; + }; + + bridge = { + permissions = { + "@cholli:${matrixDomain}" = "admin"; + "${matrixDomain}" = "user"; + }; + }; + }; + }; + }; +} From 85eebcd2edfedef78960d5700993790318db5071 Mon Sep 17 00:00:00 2001 From: Christoph Hollizeck Date: Tue, 14 Apr 2026 01:01:03 +0200 Subject: [PATCH 7/7] loptland: init element-web --- modules/hosts/loptland/default.nix | 1 + modules/hosts/loptland/nginx.nix | 15 ++++++++++ modules/server/element-web.nix | 47 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 modules/server/element-web.nix diff --git a/modules/hosts/loptland/default.nix b/modules/hosts/loptland/default.nix index a4d2871..428f0fc 100644 --- a/modules/hosts/loptland/default.nix +++ b/modules/hosts/loptland/default.nix @@ -35,6 +35,7 @@ topLevel: { mautrix-discord mautrix-signal element-call + element-web # game server minecraft-server diff --git a/modules/hosts/loptland/nginx.nix b/modules/hosts/loptland/nginx.nix index a28c106..5de5314 100644 --- a/modules/hosts/loptland/nginx.nix +++ b/modules/hosts/loptland/nginx.nix @@ -57,6 +57,20 @@ forceSSL = true; useACMEHost = matrixDomain; + # MSC4143: advertise LiveKit as the RTC transport since Synapse doesn't implement this yet + locations."= /_matrix/client/unstable/org.matrix.msc4143/rtc/transports" = { + extraConfig = '' + default_type application/json; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; + if ($request_method = OPTIONS) { + return 204; + } + return 200 '{"rtc_transports":[{"type":"livekit","livekit_service_url":"https://call.${matrixDomain}/livekit/jwt"}]}'; + ''; + }; + locations."/" = { proxyPass = "http://localhost:${toString 8008}"; extraConfig = '' @@ -91,6 +105,7 @@ tryFiles = "$uri /index.html"; extraConfig = '' add_header Cache-Control "no-cache" always; + add_header Content-Security-Policy "frame-ancestors 'self' https://chat.${matrixDomain}" always; ''; }; diff --git a/modules/server/element-web.nix b/modules/server/element-web.nix new file mode 100644 index 0000000..b395d4b --- /dev/null +++ b/modules/server/element-web.nix @@ -0,0 +1,47 @@ +{ + flake.modules.nixos.element-web = + { pkgs, ... }: + let + matrixDomain = "alwayssleepy.online"; + in + { + services.nginx.virtualHosts."chat.${matrixDomain}" = { + forceSSL = true; + useACMEHost = matrixDomain; + + locations."= /config.json" = { + extraConfig = '' + default_type application/json; + return 200 '${builtins.toJSON { + default_server_config = { + "m.homeserver" = { + base_url = "https://matrix.${matrixDomain}"; + server_name = matrixDomain; + }; + }; + disable_custom_urls = true; + disable_guests = true; + features = { + feature_group_calls = true; + }; + element_call = { + url = "https://call.${matrixDomain}"; + use_exclusively = true; + brand = "Element Call"; + }; + brand = "Element"; + default_theme = "dark"; + }}'; + ''; + }; + + locations."/" = { + root = "${pkgs.element-web}"; + tryFiles = "$uri /index.html"; + extraConfig = '' + add_header Cache-Control "no-cache" always; + ''; + }; + }; + }; +}