diff options
Diffstat (limited to 'modules/services')
-rw-r--r-- | modules/services/git-daemon/_git-daemon-module.nix | 137 | ||||
-rw-r--r-- | modules/services/git-daemon/default.nix (renamed from modules/services/git-daemon.nix) | 2 | ||||
-rw-r--r-- | modules/services/matrix-bridge/_mautrix-discord-module.nix | 205 | ||||
-rw-r--r-- | modules/services/matrix-bridge/_mautrix-signal-module.nix | 204 | ||||
-rw-r--r-- | modules/services/matrix-bridge/default.nix (renamed from modules/services/matrix-bridge.nix) | 4 | ||||
-rw-r--r-- | modules/services/soju/_soju-module.nix | 132 | ||||
-rw-r--r-- | modules/services/soju/default.nix (renamed from modules/services/soju.nix) | 2 |
7 files changed, 682 insertions, 4 deletions
diff --git a/modules/services/git-daemon/_git-daemon-module.nix b/modules/services/git-daemon/_git-daemon-module.nix new file mode 100644 index 0000000..76b395e --- /dev/null +++ b/modules/services/git-daemon/_git-daemon-module.nix @@ -0,0 +1,137 @@ +{ config, lib, pkgs, ... }: +with lib; +let + + cfg = config.services.gitDaemon; + +in +{ + + ###### interface + + options = { + services.gitDaemon = { + + enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Enable Git daemon, which allows public hosting of git repositories + without any access controls. This is mostly intended for read-only access. + + You can allow write access by setting daemon.receivepack configuration + item of the repository to true. This is solely meant for a closed LAN setting + where everybody is friendly. + + If you need any access controls, use something else. + ''; + }; + + basePath = mkOption { + type = types.str; + default = ""; + example = "/srv/git/"; + description = lib.mdDoc '' + Remap all the path requests as relative to the given path. For example, + if you set base-path to /srv/git, then if you later try to pull + git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git. + ''; + }; + + exportAll = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Publish all directories that look like Git repositories (have the objects + and refs subdirectories), even if they do not have the git-daemon-export-ok file. + + If disabled, you need to touch .git/git-daemon-export-ok in each repository + you want the daemon to publish. + + Warning: enabling this without a repository whitelist or basePath + publishes every git repository you have. + ''; + }; + + repositories = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "/srv/git" "/home/user/git/repo2" ]; + description = lib.mdDoc '' + A whitelist of paths of git repositories, or directories containing repositories + all of which would be published. Paths must not end in "/". + + Warning: leaving this empty and enabling exportAll publishes all + repositories in your filesystem or basePath if specified. + ''; + }; + + listenAddress = mkOption { + type = types.str; + default = ""; + example = "example.com"; + description = lib.mdDoc "Listen on a specific IP address or hostname."; + }; + + port = mkOption { + type = types.port; + default = 9418; + description = lib.mdDoc "Port to listen on."; + }; + + options = mkOption { + type = types.str; + default = ""; + description = lib.mdDoc "Extra configuration options to be passed to Git daemon."; + }; + + user = mkOption { + type = types.str; + default = "git"; + description = lib.mdDoc "User under which Git daemon would be running."; + }; + + group = mkOption { + type = types.str; + default = "git"; + description = lib.mdDoc "Group under which Git daemon would be running."; + }; + + createUserAndGroup = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Create the specified group and user. + Disable this option if you want to use the existing user + ''; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + users.users.${cfg.user} = optionalAttrs (cfg.createUserAndGroup == true) { + uid = config.ids.uids.git; + group = cfg.group; + description = "Git daemon user"; + }; + + users.groups.${cfg.group} = optionalAttrs (cfg.createUserAndGroup == true) { + gid = config.ids.gids.git; + }; + + systemd.services.git-daemon = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + script = "${pkgs.git}/bin/git daemon --reuseaddr " + + (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ") + + (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ") + + "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} " + + "--verbose " + (optionalString cfg.exportAll "--export-all ") + concatStringsSep " " cfg.repositories; + }; + + }; + +} diff --git a/modules/services/git-daemon.nix b/modules/services/git-daemon/default.nix index 5d027de..bc06342 100644 --- a/modules/services/git-daemon.nix +++ b/modules/services/git-daemon/default.nix @@ -10,7 +10,7 @@ in ]; imports = [ - ../../overlays/git-daemon-module.nix + ./_git-daemon-module.nix ]; options.modules.services.gitDaemon = { diff --git a/modules/services/matrix-bridge/_mautrix-discord-module.nix b/modules/services/matrix-bridge/_mautrix-discord-module.nix new file mode 100644 index 0000000..36cafe6 --- /dev/null +++ b/modules/services/matrix-bridge/_mautrix-discord-module.nix @@ -0,0 +1,205 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + dataDir = "/var/lib/mautrix-discord"; + registrationFile = "${dataDir}/discord-registration.yaml"; + cfg = config.services.mautrix-discord; + settingsFormat = pkgs.formats.json { }; + settingsFile = "${dataDir}/config.json"; + settingsFileUnsubstituted = settingsFormat.generate "mautrix-discord-config.json" cfg.settings; + +in +{ + options = { + services.mautrix-discord = { + enable = mkEnableOption (lib.mdDoc "Mautrix-discord, a Matrix-discord puppeting bridge."); + + package = mkOption { type = types.package; default = pkgs.mautrix-discord; }; + + settings = mkOption rec { + apply = recursiveUpdate default; + inherit (settingsFormat) type; + default = { + homeserver = { + software = "standard"; + }; + + appservice = rec { + database = "sqlite:///${dataDir}/mautrix-discord.db"; + database_opts = { }; + hostname = "localhost"; + port = 8080; + address = "http://localhost:${toString port}"; + }; + + bridge = { + permissions."*" = "relay"; + relay.whitelist = [ ]; + double_puppet_server_map = { }; + login_shared_secret_map = { }; + }; + + logging = { + min_level = "debug"; + writers = [ + { + type = "stdout"; + format = "pretty-colored"; + } + ]; + }; + }; + example = literalExpression '' + { + homeserver = { + address = "http://localhost:8008"; + domain = "public-domain.tld"; + }; + + appservice.public = { + prefix = "/public"; + external = "https://public-appservice-address/public"; + }; + + bridge.permissions = { + "example.com" = "full"; + "@admin:example.com" = "admin"; + }; + } + ''; + description = lib.mdDoc '' + {file}`config.yaml` configuration as a Nix attribute set. + Configuration options should match those described in + [example-config.yaml](https://github.com/mautrix/discord/blob/master/discord/example-config.yaml). + + Secret tokens should be specified using {option}`environmentFile` + instead of this world-readable attribute set. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + File containing environment variables to be passed to the mautrix-discord service, + in which secret tokens can be specified securely by defining values for e.g. + `MAUTRIX_DISCORD_APPSERVICE_AS_TOKEN`, + `MAUTRIX_DISCORD_APPSERVICE_HS_TOKEN`, + + These environment variables can also be used to set other options by + replacing hierarchy levels by `.`, converting the name to uppercase + and prepending `MAUTRIX_DISCORD_`. + For example, the first value above maps to + {option}`settings.appservice.as_token`. + + The environment variable values can be prefixed with `json::` to have + them be parsed as JSON. For example, `login_shared_secret_map` can be + set as follows: + `MAUTRIX_DISCORD_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`. + ''; + }; + + serviceDependencies = mkOption { + type = with types; listOf str; + default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; + defaultText = literalExpression '' + optional config.services.matrix-synapse.enable "matrix-synapse.service" + ''; + description = lib.mdDoc '' + List of Systemd services to require and wait for when starting the application service. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + users.users.mautrix-discord = { + isSystemUser = true; + group = "mautrix-discord"; + home = dataDir; + description = "Mautrix-Discord bridge user"; + }; + + users.groups.mautrix-discord = {}; + + systemd.services.mautrix-discord = { + description = "Mautrix-discord, a Matrix-discord puppeting bridge."; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ] ++ cfg.serviceDependencies; + after = [ "network-online.target" ] ++ cfg.serviceDependencies; + + preStart = '' + # substitute the settings file by environment variables + # in this case read from EnvironmentFile + test -f '${settingsFile}' && rm -f '${settingsFile}' + old_umask=$(umask) + umask 0177 + ${pkgs.envsubst}/bin/envsubst \ + -o '${settingsFile}' \ + -i '${settingsFileUnsubstituted}' \ + umask $old_umask + + # generate the appservice's registration file if absent + if [ ! -f '${registrationFile}' ]; then + ${pkgs.mautrix-discord}/bin/mautrix-discord \ + --generate-registration \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + fi + chmod 640 ${registrationFile} + + umask 0177 + ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token + | .[0].appservice.hs_token = .[1].hs_token + | .[0]' '${settingsFile}' '${registrationFile}' \ + > '${settingsFile}.tmp' + mv '${settingsFile}.tmp' '${settingsFile}' + umask $old_umask + ''; + + serviceConfig = { + Type = "simple"; + Restart = "on-failure"; + + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + ProtectClock = true; + ProtectHostname = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = ["@system-service"]; + + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + + User = "mautrix-discord"; + Group = "mautrix-discord"; + WorkingDirectory = dataDir; + StateDirectory = baseNameOf dataDir; + UMask = "0027"; + EnvironmentFile = cfg.environmentFile; + + ExecStart = '' + ${cfg.package}/bin/mautrix-discord \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + ''; + }; + restartTriggers = [settingsFileUnsubstituted]; + }; + }; +} diff --git a/modules/services/matrix-bridge/_mautrix-signal-module.nix b/modules/services/matrix-bridge/_mautrix-signal-module.nix new file mode 100644 index 0000000..983d635 --- /dev/null +++ b/modules/services/matrix-bridge/_mautrix-signal-module.nix @@ -0,0 +1,204 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + dataDir = "/var/lib/mautrix-signal"; + registrationFile = "${dataDir}/signal-registration.yaml"; + cfg = config.services.mautrix-signal; + settingsFormat = pkgs.formats.json { }; + settingsFile = "${dataDir}/config.json"; + settingsFileUnsubstituted = + settingsFormat.generate "mautrix-signal-config.json" cfg.settings; + +in +{ + # NOTE(2024-01-11): Upstream has been moved to a Go version. + # Environment-based credential setting might not work. + options = { + services.mautrix-signal = { + enable = mkEnableOption (lib.mdDoc "Mautrix-Signal, a Matrix-Signal puppeting bridge."); + + package = mkOption { type = types.package; default = pkgs.mautrix-signal; }; + + settings = mkOption rec { + apply = recursiveUpdate default; + inherit (settingsFormat) type; + default = { + homeserver = { + software = "standard"; + }; + + appservice = rec { + database = "sqlite:///${dataDir}/mautrix-signal.db"; + database_opts = { }; + hostname = "localhost"; + port = 8080; + address = "http://localhost:${toString port}"; + }; + + signal.socket_path = config.services.signald.socketPath; + + bridge = { + permissions."*" = "relay"; + relay.whitelist = [ ]; + double_puppet_server_map = { }; + login_shared_secret_map = { }; + }; + + logging = { + min_level = "debug"; + writers = [ + { + type = "stdout"; + format = "pretty-colored"; + } + ]; + }; + }; + example = literalExpression '' + { + homeserver = { + address = "http://localhost:8008"; + domain = "public-domain.tld"; + }; + + appservice.public = { + prefix = "/public"; + external = "https://public-appservice-address/public"; + }; + + bridge.permissions = { + "example.com" = "full"; + "@admin:example.com" = "admin"; + }; + } + ''; + description = lib.mdDoc '' + {file}`config.yaml` configuration as a Nix attribute set. + Configuration options should match those described in + [example-config.yaml](https://github.com/mautrix/signal/blob/master/signal/example-config.yaml). + + Secret tokens should be specified using {option}`environmentFile` + instead of this world-readable attribute set. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + File containing environment variables to be passed to the mautrix-signal service, + in which secret tokens can be specified securely by defining values for e.g. + `MAUTRIX_SIGNAL_APPSERVICE_AS_TOKEN`, + `MAUTRIX_SIGNAL_APPSERVICE_HS_TOKEN`, + + These environment variables can also be used to set other options by + replacing hierarchy levels by `.`, converting the name to uppercase + and prepending `MAUTRIX_SIGNAL_`. + For example, the first value above maps to + {option}`settings.appservice.as_token`. + + The environment variable values can be prefixed with `json::` to have + them be parsed as JSON. For example, `login_shared_secret_map` can be + set as follows: + `MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`. + ''; + }; + + serviceDependencies = mkOption { + type = with types; listOf str; + default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; + defaultText = literalExpression '' + optional config.services.matrix-synapse.enable "matrix-synapse.service" + ''; + description = lib.mdDoc '' + List of Systemd services to require and wait for when starting the application service. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + services.signald.enable = true; + + systemd.services.mautrix-signal = { + description = "Mautrix-Signal, a Matrix-Signal puppeting bridge."; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ] ++ cfg.serviceDependencies; + after = [ "network-online.target" ] ++ cfg.serviceDependencies; + path = [ pkgs.lottieconverter pkgs.ffmpeg-full ]; + + # TODO(2023-01-11): Still relevant in Go version? + # mautrix-signal tries to generate a dotfile in the home directory of + # the running user if using a postgresql database: + # + # File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre> + # return (pathlib.Path.home() / '.postgresql' / filename).resolve() + # File "python3.10/pathlib.py", line 1000, in home + # return cls("~").expanduser() + # File "python3.10/pathlib.py", line 1440, in expanduser + # raise RuntimeError("Could not determine home directory.") + # RuntimeError: Could not determine home directory. + environment.HOME = dataDir; + + preStart = '' + # substitute the settings file by environment variables + # in this case read from EnvironmentFile + test -f '${settingsFile}' && rm -f '${settingsFile}' + old_umask=$(umask) + umask 0177 + ${pkgs.envsubst}/bin/envsubst \ + -o '${settingsFile}' \ + -i '${settingsFileUnsubstituted}' \ + umask $old_umask + + # generate the appservice's registration file if absent + if [ ! -f '${registrationFile}' ]; then + ${cfg.package}/bin/mautrix-signal \ + --generate-registration \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + fi + chmod 640 ${registrationFile} + + umask 0177 + ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token + | .[0].appservice.hs_token = .[1].hs_token + | .[0]' '${settingsFile}' '${registrationFile}' \ + > '${settingsFile}.tmp' + mv '${settingsFile}.tmp' '${settingsFile}' + umask $old_umask + ''; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + DynamicUser = true; + SupplementaryGroups = [ "signald" ]; + PrivateTmp = true; + WorkingDirectory = cfg.package; # necessary for the database migration scripts to be found + StateDirectory = baseNameOf dataDir; + UMask = "0027"; + EnvironmentFile = cfg.environmentFile; + + ExecStart = '' + ${cfg.package}/bin/mautrix-signal \ + --config='${settingsFile}' + ''; + + restartTriggers = [settingsFileUnsubstituted]; + }; + }; + }; + + # meta.maintainers = with maintainers; [ boppyt ]; +} diff --git a/modules/services/matrix-bridge.nix b/modules/services/matrix-bridge/default.nix index 3ea46d8..4d53223 100644 --- a/modules/services/matrix-bridge.nix +++ b/modules/services/matrix-bridge/default.nix @@ -6,8 +6,8 @@ let in { imports = [ - ../../overlays/mautrix-signal-module.nix - ../../overlays/mautrix-discord-module.nix + ./_mautrix-signal-module.nix + ./_mautrix-discord-module.nix ]; options.modules.services.matrix-bridge = { diff --git a/modules/services/soju/_soju-module.nix b/modules/services/soju/_soju-module.nix new file mode 100644 index 0000000..d14082c --- /dev/null +++ b/modules/services/soju/_soju-module.nix @@ -0,0 +1,132 @@ +# Not an overlay, module replacement +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.soju; + stateDir = "/var/lib/soju"; + listenCfg = concatMapStringsSep "\n" (l: "listen ${l}") cfg.listen; + tlsCfg = optionalString (cfg.tlsCertificate != null) + "tls ${cfg.tlsCertificate} ${cfg.tlsCertificateKey}"; + logCfg = optionalString cfg.enableMessageLogging + "log fs ${stateDir}/logs"; + + configFile = pkgs.writeText "soju.conf" '' + ${listenCfg} + hostname ${cfg.hostName} + ${tlsCfg} + db sqlite3 ${stateDir}/soju.db + ${logCfg} + http-origin ${concatStringsSep " " cfg.httpOrigins} + accept-proxy-ip ${concatStringsSep " " cfg.acceptProxyIP} + + ${cfg.extraConfig} + ''; +in +{ + ###### interface + + options.services.soju = { + enable = mkEnableOption (lib.mdDoc "soju"); + + listen = mkOption { + type = types.listOf types.str; + default = [ ":6697" ]; + description = lib.mdDoc '' + Where soju should listen for incoming connections. See the + `listen` directive in + {manpage}`soju(1)`. + ''; + }; + + hostName = mkOption { + type = types.str; + default = config.networking.hostName; + defaultText = literalExpression "config.networking.hostName"; + description = lib.mdDoc "Server hostname."; + }; + + tlsCertificate = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/host.cert"; + description = lib.mdDoc "Path to server TLS certificate."; + }; + + tlsCertificateKey = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/host.key"; + description = lib.mdDoc "Path to server TLS certificate key."; + }; + + enableMessageLogging = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to enable message logging."; + }; + + httpOrigins = mkOption { + type = types.listOf types.str; + default = [ ]; + description = lib.mdDoc '' + List of allowed HTTP origins for WebSocket listeners. The parameters are + interpreted as shell patterns, see + {manpage}`glob(7)`. + ''; + }; + + acceptProxyIP = mkOption { + type = types.listOf types.str; + default = [ ]; + description = lib.mdDoc '' + Allow the specified IPs to act as a proxy. Proxys have the ability to + overwrite the remote and local connection addresses (via the X-Forwarded-\* + HTTP header fields). The special name "localhost" accepts the loopback + addresses 127.0.0.0/8 and ::1/128. By default, all IPs are rejected. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = lib.mdDoc "Lines added verbatim to the configuration file."; + }; + + extraGroups = mkOption { + type = types.listOf types.str; + default = [ ]; + description = lib.mdDoc "Extra groups for the dynamic user."; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + assertions = [ + { + assertion = (cfg.tlsCertificate != null) == (cfg.tlsCertificateKey != null); + message = '' + services.soju.tlsCertificate and services.soju.tlsCertificateKey + must both be specified to enable TLS. + ''; + } + ]; + + systemd.services.soju = { + description = "soju IRC bouncer"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + serviceConfig = { + DynamicUser = true; + SupplementaryGroups = cfg.extraGroups; + Restart = "always"; + ExecStart = "${pkgs.soju}/bin/soju -config ${configFile}"; + StateDirectory = "soju"; + }; + }; + }; + + meta.maintainers = with maintainers; [ malvo ]; +} diff --git a/modules/services/soju.nix b/modules/services/soju/default.nix index b2f4faf..557222e 100644 --- a/modules/services/soju.nix +++ b/modules/services/soju/default.nix @@ -10,7 +10,7 @@ in ]; imports = [ - ../../overlays/soju-module.nix + ./_soju-module.nix ]; options.modules.services.soju = { |