diff --git a/machines/gerd.nix b/machines/gerd.nix index e12911a..59c791c 100644 --- a/machines/gerd.nix +++ b/machines/gerd.nix @@ -4,6 +4,8 @@ ./../shared/applications/server/acme.nix ./../shared/applications/server/nginx.nix + ./../shared/applications/server/postgresql.nix + ./../shared/applications/state/postgresql.nix ./../shared/applications/state/ssh.nix ./gerd/services/fricloud-website.nix @@ -17,6 +19,8 @@ ./gerd/services/cyberchef.nix ./gerd/services/nextcloud.nix ./gerd/services/stalwart + + ./gerd/services/matrix-synapse.nix ]; networking.hostName = "gerd"; @@ -31,6 +35,9 @@ "safe/svcs/hedgedoc" = { mountpoint = "/srv/hedgedoc"; extra.options.quota = "5G"; }; "safe/svcs/nextcloud" = { mountpoint = "/srv/nextcloud"; extra.options.quota = "5G"; }; "safe/svcs/stalwart" = { mountpoint = "/srv/stalwart"; extra.options.quota = "5G"; }; + "safe/svcs/synapse" = { mountpoint = "/srv/synapse"; extra.options.quota = "5G"; }; + "safe/svcs/postgresql" = { mountpoint = "/srv/postgresql"; extra.options.quota = "5G"; }; + "backup/postgresql" = { mountpoint = "/media/backup/postgresqlbackup"; extra.options.quota = "5G"; }; }; }; diff --git a/machines/gerd/services/hedgedoc.nix b/machines/gerd/services/hedgedoc.nix index 596b6d7..85ec8e7 100644 --- a/machines/gerd/services/hedgedoc.nix +++ b/machines/gerd/services/hedgedoc.nix @@ -4,6 +4,8 @@ let svc_domain = "hedgedoc.${config.mine.shared.settings.domain}"; stateDir = config.mine.zfsMounts."rpool/safe/svcs/hedgedoc"; + + hedgedoc_user = config.users.users.hedgedoc.name; in { services.hedgedoc = { enable = true; @@ -14,8 +16,11 @@ in { protocolUseSSL = true; debug = true; uploadsPath = stateDir + "/uploads"; - db.dialect = "sqlite"; - db.storage = stateDir + "/db.sqlite"; + + db = { + dialect = "postgresql"; + host = "/run/postgresql"; + }; # disable annonymous notes, but allow annonymous edits allowAnonymous = false; @@ -44,6 +49,15 @@ in { systemd.services.hedgedoc.serviceConfig.ReadWritePaths = [ stateDir ]; systemd.services.hedgedoc.serviceConfig.EnvironmentFile = config.age.secrets.lldap-bind-user-pass-hedgedoc-env.path; + # setup postgresql + services.postgresql = { + ensureDatabases = [ hedgedoc_user ]; + ensureUsers = [{ + name = hedgedoc_user; + ensureDBOwnership = true; + }]; + }; + services.nginx.virtualHosts."${svc_domain}" = { forceSSL = true; enableACME = true; diff --git a/machines/gerd/services/matrix-synapse.nix b/machines/gerd/services/matrix-synapse.nix new file mode 100644 index 0000000..2cbe497 --- /dev/null +++ b/machines/gerd/services/matrix-synapse.nix @@ -0,0 +1,198 @@ +{ config, lib, pkgs, ... }: + +let + svc_domain = "matrix.${config.mine.shared.settings.domain}"; + + max_upload_size = "50M"; + + name = "matrix-synapse"; + + matrix_synapse_user = name; + matrix_synapse_group = name; + + oidcConfigPath = "/run/${name}/oidc.yaml"; + + stateDir = config.mine.zfsMounts."rpool/safe/svcs/synapse"; + matrix_port = 8448; +in { + services.matrix-synapse = { + enable = true; + dataDir = stateDir; + + extras = [ "oidc" ]; + + extraConfigFiles = [ + oidcConfigPath + ]; + + settings = { + server_name = config.mine.shared.settings.domain; + public_baseurl = "https://${svc_domain}"; + + enable_registration = false; + password_config.enabled = false; + + listeners = [ + { + port = matrix_port; + bind_addresses = [ "localhost" ]; + x_forwarded = true; + tls = false; + resources = [{ + names = [ "federation" "client" ]; + compress = false; + }]; + } + ]; + + # database + # shut up shut up shut up shut up!!! + database.allow_unsafe_locale = true; + database_type = "psycopg2"; + database_args.database = "matrix-synapse"; + + # increase max_upload_size, otherwise might not + # be able to watch cat videos + max_upload_size = max_upload_size; + + # only authenticated media + # TODO: Should default to true at some point + enable_authenticated_media = true; + + # retentien policies + media_retention = { + local_media_lifetime = "90d"; + remote_media_lifetime = "14d"; + }; + + # keep messages MIN 1 day, MAX 1 year + retention = { + enabled = true; + default_policy = { + min_lifetime = "1d"; + max_lifetime = "1y"; + }; + allowed_lifetime_min = "1d"; + allowed_lifetime_max = "1y"; + }; + }; + }; + + # setup OIDC/OpenID/OAUTH2/whatever for synapse + # do this way, instead of having EVERYTHING in a secret file. + systemd.services.matrix-synapse = { + # https://linux-audit.com/systemd/systemd-syscall-filtering/ + # https://github.com/restic/rest-server/pull/249 + # https://github.com/golang/go/issues/46279 + serviceConfig.SystemCallFilter = [ "setrlimit" ]; + serviceConfig.EnvironmentFile = config.age.secrets.matrix-synapse-config-authelia-secret.path; + preStart = let + oidc_config = (pkgs.formats.yaml {}).generate "matrix-synapse-oidc-config" { + oidc_providers = [{ + idp_id = "authelia"; + idp_name = "Authelia"; + idp_icon = "mxc://authelia.com/cKlrTPsGvlpKxAYeHWJsdVHI"; + client_id = "synapse"; + client_secret = "$CLIENT_SECRET"; + issuer = "https://${config.mine.shared.settings.authelia.domain}"; + discover = true; + + scopes = [ + "openid" + "profile" + "email" + ]; + + user_mapping_provider.config = { + subject_claim = "sub"; + localpart_template = "{{ user.preferred_username }}"; + display_name_template = "{{ user.name }}"; + email_template = "{{ user.email }}"; + }; + }]; + }; + in '' + ${pkgs.envsubst}/bin/envsubst \ + -o ${oidcConfigPath} \ + -i ${oidc_config} + ''; + }; + + # setup for oidc + services.authelia.instances.main.settings.identity_providers.oidc.clients = [{ + client_id = "synapse"; + client_name = "Synapse"; + client_secret = "$pbkdf2-sha512$310000$SmE9y.LA9lnzxNWL6CeWQA$zcrum.Rst9xQy/MKBI5i.UiUdSjx/F0ak65Z3vYk0w7/GMWIqXaW3GnE7bJQw6nHi5eZ2uhKHtW/DKp2TDVhbQ"; + redirect_uris = [ "https://${svc_domain}/_synapse/client/oidc/callback" ]; + scopes = [ + "openid" + "profile" + "email" + ]; + }]; + + # ensure databases + user is setup + services.postgresql = { + ensureDatabases = [ matrix_synapse_user ]; + ensureUsers = [{ + name = matrix_synapse_user; + ensureDBOwnership = true; + }]; + }; + + services.nginx = { + virtualHosts."${svc_domain}" = { + enableACME = true; + forceSSL = true; + + # only proxy `_matrix` and `_synapse/client`, it is not ideal to proxy `_synapse/admin` + locations."~ ^(/_matrix|/_synapse/client)" = { + proxyPass = "http://localhost:${builtins.toString matrix_port}"; + + extraConfig = "client_max_body_size ${max_upload_size};"; + }; + }; + + # serve `.well-known/matrix/{server,client}` because matrix synapse runs on subdomain + virtualHosts."${config.mine.shared.settings.domain}" = let + client = { + "m.homeserver" = { "base_url" = "https://${svc_domain}"; }; + "m.identity_server" = { "base_url" = "https://matrix.org"; }; + }; + server = { "m.server" = "${svc_domain}:443"; }; + in { + locations."/.well-known/matrix/server".extraConfig = '' + add_header Content-Type application/json; + return 200 '${builtins.toJSON server}'; + ''; + + locations."/.well-known/matrix/client".extraConfig = '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON client}'; + ''; + }; + }; + + systemd.tmpfiles.rules = [ + "Z ${stateDir} 0770 ${matrix_synapse_user} ${matrix_synapse_group} -" + ]; + + age.secrets = { + matrix-synapse-config-authelia-secret.owner = matrix_synapse_user; + }; + + mine.shared.meta.matrix-synapse = { + name = "Matrix Synapse"; + description = "We host our own Matrix homeserver using Synapse! Login using your favourite which supports OpenID."; + url = "https://${svc_domain}"; + + package = let + pkg = pkgs.matrix-synapse-unwrapped; + in { + name = pkg.pname; + version = pkg.version; + meta = pkg.meta; + }; + }; +} diff --git a/machines/gerd/services/member-website/app.py b/machines/gerd/services/member-website/app.py index a8cdb4d..70f02e7 100755 --- a/machines/gerd/services/member-website/app.py +++ b/machines/gerd/services/member-website/app.py @@ -38,6 +38,19 @@ tmpl_index = """