From 14ec2ddba776266c4a38ce19db28dc1424491799 Mon Sep 17 00:00:00 2001 From: aviac Date: Sat, 3 May 2025 19:47:47 +0200 Subject: [PATCH] feat(nix): add continuwuity nixosModule to flake --- flake.nix | 5 +- nix/modules/config.nix | 81 ++++++++++++ nix/modules/default.nix | 266 ++++++++++++++++++++++++++++++++++++++++ nix/modules/test.nix | 27 ++++ 4 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 nix/modules/config.nix create mode 100644 nix/modules/default.nix create mode 100644 nix/modules/test.nix diff --git a/flake.nix b/flake.nix index 49e860ed..92ce83b9 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,7 @@ }; outputs = inputs: - inputs.flake-utils.lib.eachDefaultSystem (system: + (inputs.flake-utils.lib.eachDefaultSystem (system: let pkgsHost = import inputs.nixpkgs{ inherit system; @@ -574,5 +574,8 @@ main = prev.main.override { default_features = false; }; })); devShells.dynamic = mkDevShell scopeHost; + }) // { + nixosModules = import ./nix/modules { inherit inputs; }; + nixosConfigurations = import ./nix/modules/test.nix { inherit inputs; }; }); } diff --git a/nix/modules/config.nix b/nix/modules/config.nix new file mode 100644 index 00000000..1c9cffe0 --- /dev/null +++ b/nix/modules/config.nix @@ -0,0 +1,81 @@ +# These are the configuration options available in the continuwuity.toml file in a nixified way +{ config, lib }: +let + cfg = config.services.continuwuity; +in +{ + options = + let + mkDisableOption = + name: + lib.mkOption { + description = "Whether or not to enable ${name}"; + type = lib.types.bool; + default = true; + example = false; + }; + in + { + + database_backup_path = lib.mkOption { + description = '' + continuwuity supports online database backups using RocksDB's + Backup engine API. To use this, set a database backup path that + conduwuit can write to. + ''; + type = lib.types.str; + default = "${cfg.dataDir}/continuwuity-db-backups"; + example = "/var/lib/continuwuity-db-backups"; + }; + + database_backups_to_keep = lib.mkOption { + description = '' + The amount of online RocksDB database backups to keep/retain, + if using "database_backup_path", before deleting the oldest one. + ''; + type = lib.types.int; + default = 1; + example = 10; + }; + + new_user_displayname_suffix = lib.mkOption { + description = '' + Text which will be added to the end of the user's displayname upon + registration with a space before the text. In Conduit, this was the + lightning bolt emoji. + ''; + type = lib.types.str; + default = "🏳️‍⚧️"; + example = "🏳️‍⚧️"; + }; + + allow_announcements_check = mkDisableOption "regular GET request for announcements"; + + registration_token_file = lib.mkOption { + description = '' + The file the registration token is stored in. + ''; + type = lib.types.path; + readOnly = true; + }; + + allow_registration = lib.mkEnableOption "registration of new users"; + allow_encryption = mkDisableOption "creation of encrypted rooms"; + allow_federation = mkDisableOption "federation with other servers"; + + trusted_servers = lib.mkOption { + description = "Servers trusted with signing server keys."; + type = lib.types.listOf lib.types.str; + default = [ "matrix.org" ]; + }; + + extraConfig = lib.mkOption { + description = "Everything in the config scheme which isn't nixified yet. Note that you are on your own here."; + type = lib.types.attrs; + default = { }; + example = { + max_request_size = 2000000; + }; + }; + }; +} diff --git a/nix/modules/default.nix b/nix/modules/default.nix new file mode 100644 index 00000000..3ef1bb43 --- /dev/null +++ b/nix/modules/default.nix @@ -0,0 +1,266 @@ +{ inputs, ... }: +{ + continuwuity = + { + config, + pkgs, + lib, + ... + }: + let + cfg = config.services.continuwuity; + in + { + + options.services.continuwuity = { + enable = lib.mkEnableOption "continuwuity"; + nginx.enable = lib.mkEnableOption "nginx configuration for continuwuity"; + + package = lib.mkOption { + description = "The package containing the continuwuity binary"; + type = lib.types.package; + default = inputs.self.packages.${pkgs.system}.all-features; + }; + + port = lib.mkOption { + description = ''The local port of the continuwuity instance''; + type = lib.types.int; + example = 3000; + }; + address = lib.mkOption { + description = ''The local address of the continuwuity instance''; + type = lib.types.str; + example = "::1"; + }; + + domain = lib.mkOption { + description = ''The base domain of the continuwuity instance''; + type = lib.types.str; + example = "example.com"; + }; + subdomain = lib.mkOption { + description = ''An optional subdomain of the continuwuity instance''; + type = lib.types.nullOr lib.types.str; + example = "example.com"; + default = null; + }; + + user = lib.mkOption { + description = "The user that is running the continuwuity server"; + type = lib.types.str; + default = "continuwuity"; + example = "continuwuity"; + }; + + group = lib.mkOption { + description = "The group of the user that is running the continuwuity server"; + type = lib.types.str; + default = "continuwuity"; + example = "continuwuity"; + }; + + dataDir = lib.mkOption { + description = "The dataDir of the continuwuity server"; + type = lib.types.str; + default = "/var/lib/continuwuity"; + example = "/var/lib/continuwuity"; + }; + + settings = lib.mkOption { + description = '' + The continuwuity.toml config in nix format as an attrset. This + gets directly translated into a toml file. + ''; + type = lib.types.submodule (import ./config.nix { inherit config lib; }); + default = { }; + }; + }; + + config = + let + # optional prepend the subdomain + fullDomain = builtins.concatStringsSep "." ( + lib.flatten [ + # only use subdomain if not null + (lib.optional (cfg.subdomain != null) cfg.subdomain) + # always use the domain + [ cfg.domain ] + ] + ); + baseUrl = "https://${fullDomain}"; + in + lib.mkIf cfg.enable { + + users.users.${cfg.user} = { + inherit (cfg) group; + isSystemUser = true; + home = cfg.dataDir; + createHome = true; + shell = "${lib.getExe pkgs.bash}"; + }; + + users.groups.${cfg.group} = { }; + + systemd.services.continuwuity = + let + # these options shouldn't be set-able by endusers via the config but are derived from the other nix options + defaultConfigOptions = { + server_name = cfg.domain; + inherit (cfg) address port; + database_path = cfg.dataDir; + + well_known = { + client = baseUrl; + server = "${fullDomain}:443"; + }; + }; + mergedConfig = + builtins.foldl' lib.recursiveUpdate (builtins.removeAttrs cfg.settings [ "extraConfig" ]) + [ + defaultConfigOptions + cfg.settings.extraConfig + ]; + configFile = (pkgs.formats.toml { }).generate "continuwuity.toml" { global = mergedConfig; }; + in + { + description = "The continuwuity matrix server"; + + documentation = [ "https://forgejo.ellis.link/continuwuation/continuwuity" ]; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + aliases = [ "matrix-continuwuity.service" ]; + + environment = lib.mkMerge [ + # This has not yet been renamed + { CONDUWUIT_CONFIG = configFile; } + # include more stuff here ... if needed + ]; + # this is derived from + # + # https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/4158c1cf623a83b96d6a2d3cabb9f6aa1d618b4b/debian/conduwuit.service + # + # and + # + # https://github.com/NixOS/nixpkgs/blob/bf3287dac860542719fe7554e21e686108716879/nixos/modules/services/matrix/conduit.nix + # + # and + # + # https://github.com/NixOS/nixpkgs/blob/bf3287dac860542719fe7554e21e686108716879/nixos/modules/services/matrix/synapse.nix + serviceConfig = { + # maybe something more simple makes more sense here + Type = "notify"; + + DynamicUser = true; + User = cfg.user; + Group = cfg.group; + + WorkingDirectoy = cfg.dataDir; + RuntimeDirectory = "continuwuity"; + RuntimeDirectoryPreserve = true; + + ExecStart = "${lib.getExe cfg.package}"; + UMask = "0077"; + + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + PrivateIPC = true; + RemoveIPC = true; + + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + + ReadWritePaths = [ cfg.dataDir ]; + + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service @resources" + "~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc" + ]; + SystemCallErrorNumber = "EPERM"; + + Restart = "on-failure"; + RestartSec = 10; + + TimeoutStopSec = "2m"; + TimeoutStartSec = "2m"; + + StartLimitBurst = 5; + }; + }; + + services.nginx = + let + clientConfig = { + "m.homeserver".base_url = baseUrl; + }; + serverConfig."m.server" = "${fullDomain}:443"; + mkWellKnown = data: '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON data}'; + ''; + in + lib.mkIf cfg.nginx.enable { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + virtualHosts = { + "${cfg.domain}" = { + enableACME = true; + forceSSL = true; + locations = { + "= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; + "= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig; + }; + }; + "${fullDomain}" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = + let + # really naive predicate to differentiate between v4 and v6 + isIp6 = builtins.match cfg.address ".*:.*"; + # make sure to "guard" the ip6 addrs by surrounding them with braces + addr = "${lib.optionalString isIp6 "["}${cfg.address}${lib.optionalString isIp6 "]"}"; + in + { + proxyPass = "http://${addr}:${builtins.toString cfg.port}"; + proxyWebsockets = true; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/nix/modules/test.nix b/nix/modules/test.nix new file mode 100644 index 00000000..676e88d5 --- /dev/null +++ b/nix/modules/test.nix @@ -0,0 +1,27 @@ +{ inputs, ... }: +{ + testSystem = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + { + fileSystems."/".device = "/dev/sda1"; + boot.loader.grub.enable = true; # Enable GRUB bootloader + boot.loader.grub.device = "/dev/sda"; # Change to your boot device + } + inputs.self.nixosModules.continuwuity + { + config = { + services.continuwuity = { + enable = true; + port = 10000; + address = "::1"; + domain = "example.com"; + settings = { + registration_token_file = ./test.nix; + }; + }; + }; + } + ]; + }; +}