nix

configuration files that power my machines
Log | Files | Refs | README | LICENSE

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 }