soju.nix (6521B)
1 { 2 config, 3 lib, 4 pkgs, 5 haravara, 6 ... 7 }: let 8 cfg = config.services.haravara.soju; 9 10 # Define the paths for the TLS cert and key. 11 # We prefer systemd credentials which are populated from Caddy's certs. 12 certPath = 13 if cfg.acme.enable 14 then "/run/credentials/soju.service/cert.pem" 15 else cfg.tlsCertificate; 16 keyPath = 17 if cfg.acme.enable 18 then "/run/credentials/soju.service/key.pem" 19 else cfg.tlsCertificateKey; 20 21 configFile = pkgs.writeText "soju.conf" '' 22 listen ${lib.concatStringsSep "\nlisten " cfg.listen} 23 ${lib.optionalString cfg.adminSocket.enable "listen unix+admin://${cfg.runtimeDir}/admin"} 24 hostname ${cfg.hostName} 25 ${lib.optionalString (certPath != null) "tls ${certPath} ${keyPath}"} 26 ${lib.optionalString cfg.enableMessageLogging "message-store fs ${cfg.stateDir}/logs"} 27 http-origin ${lib.concatStringsSep " " cfg.httpOrigins} 28 accept-proxy-ip ${lib.concatStringsSep " " cfg.acceptProxyIP} 29 30 ${cfg.extraConfig} 31 ''; 32 33 sojuctl = pkgs.writeShellScriptBin "sojuctl" '' 34 exec ${lib.getExe' cfg.package "sojuctl"} --config ${configFile} "$@" 35 ''; 36 37 # Path to Caddy's certificates 38 caddyCertDir = "${cfg.acme.caddyCertDir}/${cfg.domain}"; 39 in { 40 options.services.haravara.soju = { 41 enable = lib.mkEnableOption "soju IRC bouncer"; 42 43 package = lib.mkPackageOption pkgs "soju" {}; 44 45 domain = lib.mkOption { 46 type = lib.types.str; 47 description = "Domain for the soju WebSocket endpoint (proxied by Caddy)"; 48 }; 49 50 port = lib.mkOption { 51 type = lib.types.port; 52 default = 6669; 53 description = "Internal port for the soju WebSocket listener"; 54 }; 55 56 authLabel = haravara.authLabelOption; 57 58 listen = lib.mkOption { 59 type = lib.types.listOf lib.types.str; 60 default = [":6697"]; 61 description = "Where soju should listen for incoming connections."; 62 }; 63 64 hostName = lib.mkOption { 65 type = lib.types.str; 66 default = cfg.domain; 67 defaultText = lib.literalExpression "config.services.haravara.soju.domain"; 68 description = "Server hostname."; 69 }; 70 71 tlsCertificate = lib.mkOption { 72 type = lib.types.nullOr lib.types.path; 73 default = null; 74 description = "Path to server TLS certificate."; 75 }; 76 77 tlsCertificateKey = lib.mkOption { 78 type = lib.types.nullOr lib.types.path; 79 default = null; 80 description = "Path to server TLS certificate key."; 81 }; 82 83 acme = { 84 enable = lib.mkEnableOption "Use Caddy-managed ACME certificates for soju"; 85 certificateDomain = lib.mkOption { 86 type = lib.types.str; 87 default = cfg.domain; 88 description = "The domain name of the certificates in Caddy's storage."; 89 }; 90 caddyCertDir = lib.mkOption { 91 type = lib.types.str; 92 default = "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory"; 93 description = "Path to Caddy's ACME certificate directory"; 94 }; 95 }; 96 97 enableMessageLogging = lib.mkOption { 98 type = lib.types.bool; 99 default = true; 100 description = "Whether to enable message logging."; 101 }; 102 103 adminSocket.enable = lib.mkOption { 104 type = lib.types.bool; 105 default = true; 106 description = "Listen for admin connections from sojuctl at /run/soju/admin."; 107 }; 108 109 httpOrigins = lib.mkOption { 110 type = lib.types.listOf lib.types.str; 111 default = []; 112 description = "List of allowed HTTP origins for WebSocket listeners."; 113 }; 114 115 acceptProxyIP = lib.mkOption { 116 type = lib.types.listOf lib.types.str; 117 default = ["localhost"]; 118 description = "Allow the specified IPs to act as a proxy."; 119 }; 120 121 extraConfig = lib.mkOption { 122 type = lib.types.lines; 123 default = ""; 124 description = "Lines added verbatim to the generated configuration file."; 125 }; 126 127 stateDir = lib.mkOption { 128 type = lib.types.str; 129 default = "/var/lib/soju"; 130 description = "Directory for soju state data"; 131 }; 132 133 runtimeDir = lib.mkOption { 134 type = lib.types.str; 135 default = "/run/soju"; 136 description = "Runtime directory for soju"; 137 }; 138 }; 139 140 config = lib.mkIf cfg.enable { 141 assertions = [ 142 { 143 assertion = (cfg.tlsCertificate != null) == (cfg.tlsCertificateKey != null); 144 message = "services.haravara.soju.tlsCertificate and services.haravara.soju.tlsCertificateKey must both be specified to enable TLS."; 145 } 146 ]; 147 148 environment.systemPackages = [sojuctl]; 149 150 systemd.services.soju = { 151 description = "soju IRC bouncer"; 152 wantedBy = ["multi-user.target"]; 153 wants = ["network-online.target" "caddy.service"]; 154 after = ["network-online.target" "caddy.service"]; 155 documentation = ["man:soju(1)"]; 156 serviceConfig = { 157 DynamicUser = true; 158 Restart = "always"; 159 ExecStart = "${cfg.package}/bin/soju -config ${configFile}"; 160 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 161 StateDirectory = "soju"; 162 RuntimeDirectory = "soju"; 163 WorkingDirectory = cfg.stateDir; 164 165 # Load certs from Caddy's storage 166 LoadCredential = lib.optionals cfg.acme.enable [ 167 "cert.pem:${caddyCertDir}/${cfg.domain}.crt" 168 "key.pem:${caddyCertDir}/${cfg.domain}.key" 169 ]; 170 171 SupplementaryGroups = lib.optional cfg.acme.enable "caddy"; 172 173 # Hardening 174 CapabilityBoundingSet = ""; 175 DeviceAllow = ""; 176 LockPersonality = true; 177 MemoryDenyWriteExecute = true; 178 NoNewPrivileges = true; 179 PrivateDevices = true; 180 PrivateMounts = true; 181 PrivateTmp = true; 182 ProtectClock = true; 183 ProtectControlGroups = true; 184 ProtectHome = true; 185 ProtectHostname = true; 186 ProtectKernelLogs = true; 187 ProtectKernelModules = true; 188 ProtectKernelTunables = true; 189 ProtectProc = "invisible"; 190 ProtectSystem = "strict"; 191 RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"]; 192 RestrictNamespaces = true; 193 RestrictRealtime = true; 194 RestrictSUIDSGID = true; 195 SystemCallArchitectures = "native"; 196 SystemCallFilter = ["@system-service" "~@privileged"]; 197 }; 198 }; 199 200 services.caddy.virtualHosts."${cfg.domain}".extraConfig = lib.mkOrder 500 '' 201 ${haravara.mkAuthImport cfg.authLabel} 202 reverse_proxy 127.0.0.1:${toString cfg.port} 203 redir / https://${haravara.stripSubdomain cfg.domain} 204 ''; 205 }; 206 }