diff --git a/flakes/common/nix_modules/tailnet/h001_dns.nix b/flakes/common/nix_modules/tailnet/h001_dns.nix index a689054f..69fd205d 100644 --- a/flakes/common/nix_modules/tailnet/h001_dns.nix +++ b/flakes/common/nix_modules/tailnet/h001_dns.nix @@ -20,8 +20,6 @@ "etebase" "photos" "location" - "matrix" - "element" ]; # Base domain diff --git a/hosts/h001/containers/default.nix b/hosts/h001/containers/default.nix index ec0e352f..d93d7073 100644 --- a/hosts/h001/containers/default.nix +++ b/hosts/h001/containers/default.nix @@ -6,7 +6,6 @@ ./dawarich.nix ./forgejo.nix ./immich.nix - ./matrix.nix ./opengist.nix ./zitadel.nix ]; diff --git a/hosts/h001/containers/matrix.nix b/hosts/h001/containers/matrix.nix deleted file mode 100644 index fe5f5f08..00000000 --- a/hosts/h001/containers/matrix.nix +++ /dev/null @@ -1,501 +0,0 @@ -{ - config, - pkgs, - lib, - inputs, - ... -}: -let - name = "matrix"; - hostDataDir = "/var/lib/${name}"; - hostAddress = "10.0.0.1"; - containerAddress = "10.0.0.6"; - - # Use unstable nixpkgs for the container (mautrix-gmessages module not in stable) - matrixNixpkgs = inputs.matrix-nixpkgs; - - # Matrix server configuration - serverName = "matrix.joshuabell.xyz"; - elementDomain = "element.joshuabell.xyz"; - - # Bind mount definitions following forgejo.nix pattern - binds = [ - { - host = "${hostDataDir}/postgres"; - container = "/var/lib/postgresql/17"; - user = "postgres"; - uid = 71; - gid = 71; - } - { - host = "${hostDataDir}/backups"; - container = "/var/backup/postgresql"; - user = "postgres"; - uid = 71; - gid = 71; - } - { - host = "${hostDataDir}/dendrite"; - container = "/var/lib/dendrite"; - user = "dendrite"; - uid = 993; - gid = 993; - } - { - host = "${hostDataDir}/gmessages"; - container = "/var/lib/mautrix_gmessages"; - user = "mautrix_gmessages"; - uid = 992; - gid = 992; - } - ]; - - uniqueUsers = lib.unique (map (b: { inherit (b) user uid gid; }) binds); - - # Element Web configuration - points to our server, registration disabled - elementConfig = { - default_server_config = { - "m.homeserver" = { - base_url = "https://${serverName}"; - server_name = serverName; - }; - }; - disable_guests = true; - disable_login_language_selector = false; - disable_3pid_login = true; - brand = "Element"; - integrations_ui_url = ""; - integrations_rest_url = ""; - integrations_widgets_urls = [ ]; - show_labs_settings = false; - room_directory = { - servers = [ serverName ]; - }; - # Security: disable features that could leak data - enable_presence_by_hs_url = { }; - permalink_prefix = "https://${elementDomain}"; - }; - - elementConfigFile = pkgs.writeText "element-config.json" (builtins.toJSON elementConfig); - - # Custom Element Web with our config - elementWebCustom = pkgs.runCommand "element-web-custom" { } '' - cp -r ${pkgs.element-web} $out - chmod -R u+w $out - rm $out/config.json - cp ${elementConfigFile} $out/config.json - ''; - -in -{ - # Create host directories and users - system.activationScripts."${name}-dirs" = lib.stringAfter [ "users" "groups" ] '' - ${lib.concatMapStringsSep "\n" (b: '' - mkdir -p ${b.host} - chown ${toString b.uid}:${toString b.gid} ${b.host} - '') binds} - ''; - - # Create users/groups on host for bind mount permissions - users.users = lib.listToAttrs ( - map (u: { - name = u.user; - value = { - isSystemUser = true; - uid = u.uid; - group = u.user; - }; - }) (lib.filter (u: u.user != "postgres") uniqueUsers) - ); - - users.groups = lib.listToAttrs ( - map (u: { - name = u.user; - value = { - gid = u.gid; - }; - }) (lib.filter (u: u.user != "postgres") uniqueUsers) - ); - - # nginx reverse proxy on host - services.nginx.virtualHosts = { - # Matrix server - handles client and federation API - "${serverName}" = { - forceSSL = true; - useACMEHost = "joshuabell.xyz"; - - # .well-known for Matrix federation discovery - locations."= /.well-known/matrix/server" = { - return = ''200 '{"m.server": "${serverName}:443"}' ''; - extraConfig = '' - default_type application/json; - add_header Access-Control-Allow-Origin *; - ''; - }; - - locations."= /.well-known/matrix/client" = { - return = ''200 '{"m.homeserver": {"base_url": "https://${serverName}"}}' ''; - extraConfig = '' - default_type application/json; - add_header Access-Control-Allow-Origin *; - ''; - }; - - # Matrix client and federation API - locations."/_matrix" = { - proxyPass = "http://${containerAddress}:8008"; - proxyWebsockets = true; - extraConfig = '' - proxy_read_timeout 600s; - client_max_body_size 50M; - ''; - }; - - # Dendrite admin API (only accessible internally) - locations."/_dendrite" = { - proxyPass = "http://${containerAddress}:8008"; - proxyWebsockets = true; - }; - - # Default location - redirect to Element - locations."/" = { - return = "301 https://${elementDomain}"; - }; - }; - - # Element Web client - "${elementDomain}" = { - forceSSL = true; - useACMEHost = "joshuabell.xyz"; - - locations."/" = { - proxyPass = "http://${containerAddress}:80"; - proxyWebsockets = true; - }; - }; - }; - - # The container - containers.${name} = { - ephemeral = true; - autoStart = true; - privateNetwork = true; - hostAddress = hostAddress; - localAddress = containerAddress; - nixpkgs = matrixNixpkgs; - - bindMounts = lib.listToAttrs ( - map (b: { - name = b.container; - value = { - hostPath = b.host; - isReadOnly = false; - }; - }) binds - ); - - config = - { config, pkgs, ... }: - { - system.stateVersion = "24.11"; - - # Allow olm - required by mautrix-gmessages. The security issues are - # side-channel attacks on E2EE crypto, but SMS/RCS isn't E2EE through - # the bridge anyway (RCS encryption is handled by Google Messages). - nixpkgs.config.permittedInsecurePackages = [ - "olm-3.2.16" - ]; - - networking = { - firewall = { - enable = true; - allowedTCPPorts = [ - 8008 # Dendrite Matrix API - 80 # Element Web (nginx) - ]; - }; - useHostResolvConf = lib.mkForce false; - }; - - services.resolved.enable = true; - - # PostgreSQL for Dendrite and mautrix-gmessages - services.postgresql = { - enable = true; - package = pkgs.postgresql_17; - ensureDatabases = [ - "dendrite" - "mautrix_gmessages" - ]; - ensureUsers = [ - { - name = "dendrite"; - ensureDBOwnership = true; - } - { - name = "mautrix_gmessages"; - ensureDBOwnership = true; - } - ]; - # Only allow local connections - no network access - enableTCPIP = false; - authentication = '' - local all all peer - ''; - }; - - # PostgreSQL backup - services.postgresqlBackup = { - enable = true; - databases = [ - "dendrite" - "mautrix_gmessages" - ]; - }; - - # Dendrite Matrix homeserver - services.dendrite = { - enable = true; - httpPort = 8008; - - # Load signing key from file (generated on first boot) - loadCredential = [ "signing_key:/var/lib/dendrite/matrix_key.pem" ]; - - settings = { - global = { - server_name = serverName; - private_key = "$CREDENTIALS_DIRECTORY/signing_key"; - - # Security: Disable federation to keep messages private - # Set to true if you want to chat with users on other Matrix servers - disable_federation = true; - - database = { - connection_string = "postgresql:///dendrite?host=/run/postgresql"; - max_open_conns = 50; - max_idle_conns = 5; - conn_max_lifetime = -1; - }; - - # Security: strict DNS caching - dns_cache = { - enabled = true; - cache_size = 256; - cache_lifetime = "5m"; - }; - }; - - # Client API configuration - client_api = { - # Security: Disable registration - only admin can create accounts - registration_disabled = true; - - # Security: Disable guest access - guests_disabled = true; - - # Rate limiting - rate_limiting = { - enabled = true; - threshold = 20; - cooloff_ms = 500; - exempt_user_ids = [ ]; - }; - }; - - # Federation API - disabled for privacy - federation_api = { - # Security: No federation means messages stay on your server only - disable_tls_validation = false; - disable_http_keepalives = false; - send_max_retries = 16; - key_perspectives = [ ]; - }; - - # Media API - media_api = { - base_path = "/var/lib/dendrite/media"; - max_file_size_bytes = 52428800; # 50MB - dynamic_thumbnails = true; - }; - - # Sync API - sync_api = { - real_ip_header = "X-Forwarded-For"; - search = { - enabled = true; - index_path = "/var/lib/dendrite/searchindex"; - }; - }; - - # User API - user_api = { - bcrypt_cost = 12; # Security: higher bcrypt cost - }; - - # MSCs (Matrix Spec Changes) - enable useful ones - mscs = { - mscs = [ - "msc2836" # Threading - "msc2946" # Spaces - ]; - }; - - # Logging - logging = [ - { - type = "std"; - level = "warn"; - } - ]; - - # App services (bridges) - will be configured below - app_service_api = { - database = { - connection_string = "postgresql:///dendrite?host=/run/postgresql"; - max_open_conns = 10; - max_idle_conns = 2; - conn_max_lifetime = -1; - }; - config_files = [ - "/var/lib/mautrix_gmessages/registration.yaml" - ]; - }; - }; - }; - - # Generate Dendrite signing key if it doesn't exist - systemd.services.dendrite-keygen = { - description = "Generate Dendrite signing key"; - wantedBy = [ "dendrite.service" ]; - before = [ "dendrite.service" ]; - serviceConfig = { - Type = "oneshot"; - User = "dendrite"; - Group = "dendrite"; - RemainAfterExit = true; - }; - script = '' - if [ ! -f /var/lib/dendrite/matrix_key.pem ]; then - ${pkgs.dendrite}/bin/generate-keys --private-key /var/lib/dendrite/matrix_key.pem - chmod 600 /var/lib/dendrite/matrix_key.pem - fi - ''; - }; - - # mautrix-gmessages bridge (manual service - no NixOS module exists) - systemd.services.mautrix-gmessages = { - description = "mautrix-gmessages Matrix-Google Messages bridge"; - after = [ - "network.target" - "dendrite.service" - "postgresql.service" - "mautrix-gmessages-init.service" - ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - serviceConfig = { - Type = "simple"; - User = "mautrix_gmessages"; - Group = "mautrix_gmessages"; - ExecStart = "${pkgs.mautrix-gmessages}/bin/mautrix-gmessages -c /var/lib/mautrix_gmessages/config.yaml"; - Restart = "on-failure"; - RestartSec = "10s"; - - # Security hardening - NoNewPrivileges = true; - PrivateTmp = true; - ProtectSystem = "strict"; - ProtectHome = true; - ReadWritePaths = [ "/var/lib/mautrix_gmessages" ]; - StateDirectory = "mautrix_gmessages"; - }; - }; - - # Generate mautrix-gmessages config if it doesn't exist - systemd.services.mautrix-gmessages-init = { - description = "Initialize mautrix-gmessages configuration"; - wantedBy = [ "mautrix-gmessages.service" ]; - before = [ "mautrix-gmessages.service" ]; - after = [ "postgresql.service" ]; - requires = [ "postgresql.service" ]; - - serviceConfig = { - Type = "oneshot"; - User = "mautrix_gmessages"; - Group = "mautrix_gmessages"; - RemainAfterExit = true; - }; - - script = '' - CONFIG_DIR="/var/lib/mautrix_gmessages" - CONFIG_FILE="$CONFIG_DIR/config.yaml" - REG_FILE="$CONFIG_DIR/registration.yaml" - - # Generate example config if none exists - if [ ! -f "$CONFIG_FILE" ]; then - ${pkgs.mautrix-gmessages}/bin/mautrix-gmessages -c "$CONFIG_FILE" -g - - # Patch the generated config with our settings - ${pkgs.yq-go}/bin/yq -i ' - .homeserver.address = "http://localhost:8008" | - .homeserver.domain = "${serverName}" | - .appservice.database.type = "postgres" | - .appservice.database.uri = "postgresql:///mautrix_gmessages?host=/run/postgresql" | - .appservice.hostname = "127.0.0.1" | - .appservice.port = 29336 | - .appservice.id = "gmessages" | - .appservice.bot.username = "gmessagesbot" | - .appservice.bot.displayname = "Google Messages Bridge" | - .bridge.permissions."${serverName}" = "user" | - .bridge.permissions."@josh:${serverName}" = "admin" | - .bridge.delivery_receipts = true | - .bridge.sync_direct_chat_list = true | - .logging.min_level = "warn" - ' "$CONFIG_FILE" - fi - - # Generate registration file if none exists - if [ ! -f "$REG_FILE" ]; then - ${pkgs.mautrix-gmessages}/bin/mautrix-gmessages -c "$CONFIG_FILE" -r "$REG_FILE" - chmod 640 "$REG_FILE" - fi - ''; - }; - - # Create user/group for mautrix_gmessages - users.users.mautrix_gmessages = { - isSystemUser = true; - group = "mautrix_gmessages"; - home = "/var/lib/mautrix_gmessages"; - }; - - users.groups.mautrix_gmessages = { }; - - # nginx inside container for Element Web - services.nginx = { - enable = true; - virtualHosts."element" = { - listen = [ - { - addr = "0.0.0.0"; - port = 80; - } - ]; - root = elementWebCustom; - locations."/" = { - tryFiles = "$uri $uri/ /index.html"; - }; - }; - }; - - # Ensure directories exist with proper permissions - systemd.tmpfiles.rules = [ - "d /var/lib/dendrite 0750 dendrite dendrite -" - "d /var/lib/dendrite/media 0750 dendrite dendrite -" - "d /var/lib/dendrite/searchindex 0750 dendrite dendrite -" - "d /var/lib/mautrix_gmessages 0750 mautrix_gmessages mautrix_gmessages -" - ]; - }; - }; -} diff --git a/hosts/h001/flake.lock b/hosts/h001/flake.lock index 3b9afc02..93ffebd4 100644 --- a/hosts/h001/flake.lock +++ b/hosts/h001/flake.lock @@ -257,22 +257,6 @@ "type": "github" } }, - "matrix-nixpkgs": { - "locked": { - "lastModified": 1770562336, - "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "n8n-nixpkgs": { "locked": { "lastModified": 1769170682, @@ -1351,7 +1335,6 @@ "home-manager": "home-manager", "immich-nixpkgs": "immich-nixpkgs", "litellm-nixpkgs": "litellm-nixpkgs", - "matrix-nixpkgs": "matrix-nixpkgs", "n8n-nixpkgs": "n8n-nixpkgs", "nixarr": "nixarr", "nixpkgs": "nixpkgs_3", diff --git a/hosts/h001/flake.nix b/hosts/h001/flake.nix index b97b1482..24dc6b6a 100644 --- a/hosts/h001/flake.nix +++ b/hosts/h001/flake.nix @@ -15,7 +15,6 @@ n8n-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; dawarich-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; immich-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - matrix-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; # Use relative to get current version for testing # common.url = "path:../../flakes/common";