Unverified Commit 3f06b801 authored by nixpkgs-ci[bot]'s avatar nixpkgs-ci[bot] Committed by GitHub
Browse files

Merge master into staging-nixos

parents dfaef8c4 7c8cdf6d
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -22274,6 +22274,13 @@
    githubId = 44014925;
    name = "Rexx Larsson";
  };
  reylak = {
    name = "Joaquin Lopez";
    email = "elreylak@proton.me";
    github = "Reylak-dev";
    githubId = 178049808;
    matrix = "@reylak:unredacted.org";
  };
  rgnns = {
    email = "jglievano@gmail.com";
    github = "rgnns";
+2 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@

- [reaction](https://reaction.ppom.me/), a daemon that scans program outputs for repeated patterns, and takes action. A common usage is to scan ssh and webserver logs, and to ban hosts that cause multiple authentication errors. A modern alternative to fail2ban. Available as [services.reaction](#opt-services.reaction.enable).

- [qui](https://github.com/autobrr/qui), a modern alternative webUI for qBittorrent, with multi-instance support. Written in Go/React. Available as [services.qui](#opt-services.qui.enable).

- [LibreChat](https://www.librechat.ai/), open-source self-hostable ChatGPT clone with Agents and RAG APIs. Available as [services.librechat](#opt-services.librechat.enable).

- [nohang](https://github.com/hakavlad/nohang), a daemon for Linux that prevents out of memory (OOM) situations from affecting system responsiveness. Available as [services.nohang](#opt-services.nohang.enable)
+1 −0
Original line number Diff line number Diff line
@@ -1551,6 +1551,7 @@
  ./services/torrent/opentracker.nix
  ./services/torrent/peerflix.nix
  ./services/torrent/qbittorrent.nix
  ./services/torrent/qui.nix
  ./services/torrent/rtorrent.nix
  ./services/torrent/torrentstream.nix
  ./services/torrent/transmission.nix
+9 −9
Original line number Diff line number Diff line
@@ -51,6 +51,12 @@ let
    ''
    + script;

  # We need to collect all the ACME webroots to grant them write
  # access in the systemd service.
  webroots = lib.remove null (
    lib.unique (map (certAttrs: certAttrs.webroot) (lib.attrValues config.security.acme.certs))
  );

  # There are many services required to make cert renewals work.
  # They all follow a common structure:
  #   - They inherit this commonServiceConfig
@@ -69,7 +75,9 @@ let
    ReadWritePaths = [
      "/var/lib/acme"
      lockdir
    ];
    ]
    # Prevent runtime breakage by only adding non-overlapping paths.
    ++ (lib.filter (x: !(lib.strings.hasPrefix "/var/lib/acme/" x)) webroots);
    PrivateTmp = true;

    WorkingDirectory = "/tmp";
@@ -299,12 +307,6 @@ let
        ++ data.extraLegoRenewFlags
      );

      # We need to collect all the ACME webroots to grant them write
      # access in the systemd service.
      webroots = lib.remove null (
        lib.unique (map (certAttrs: certAttrs.webroot) (lib.attrValues config.security.acme.certs))
      );

      certificateKey = if data.csrKey != null then "${data.csrKey}" else "certificates/${keyName}.key";
    in
    {
@@ -469,8 +471,6 @@ let
              "acme/.lego/accounts/${accountHash}"
            ];

            ReadWritePaths = commonServiceConfig.ReadWritePaths ++ webroots;

            # Needs to be space separated, but can't use a multiline string because that'll include newlines
            BindPaths = [
              "${accountDir}:/tmp/accounts"
+191 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:

let
  inherit (lib)
    getExe
    maintainers
    mkEnableOption
    mkIf
    mkOption
    mkPackageOption
    ;
  inherit (lib.types)
    bool
    path
    port
    str
    submodule
    ;
  cfg = config.services.qui;

  stateDir = "/var/lib/qui";
  configFormat = pkgs.formats.toml { };
  configFile = configFormat.generate "qui.toml" cfg.settings;
in
{
  options = {
    services.qui = {
      enable = mkEnableOption "qui";

      package = mkPackageOption pkgs "qui" { };

      user = mkOption {
        type = str;
        default = "qui";
        description = "User to run qui as.";
      };

      group = mkOption {
        type = str;
        default = "qui";
        example = "torrents";
        description = "Group to run qui as.";
      };

      openFirewall = mkOption {
        type = bool;
        default = false;
        description = "Whether or not to open ports in the firewall for qui.";
      };

      secretFile = mkOption {
        type = path;
        example = "/run/secrets/qui-session.txt";
        description = ''
          Path to a file that contains the session secret. The session secret
          can be generated with `openssl rand -hex 32`.
        '';
      };

      settings = mkOption {
        default = { };
        example = {
          port = 7777;
          logLevel = "DEBUG";
          metricsEnabled = true;
        };
        type = submodule {
          freeformType = configFormat.type;
          options = {
            host = mkOption {
              type = str;
              default = "127.0.0.1";
              description = "The host address qui listens on.";
            };

            port = mkOption {
              type = port;
              default = 7476;
              description = "The port qui listens on.";
            };
          };
        };
        description = ''
          qui configuration options.

          Refer to the [template config](https://github.com/autobrr/qui/blob/main/internal/config/config.go)
          in the source code for the available options.
          The documentation contains the available [environment variables](https://getqui.com/docs/configuration/environment/),
          this can be used to get an overview.
        '';
      };

    };
  };

  config = mkIf cfg.enable {
    assertions = [
      {
        assertion = !(cfg.settings ? sessionSecret);
        message = ''
          Session secrets should not be passed via settings, as
          these are stored in the world-readable nix store.

          Use the secretFile option instead.'';
      }
    ];

    systemd.services.qui = {
      description = "qui: alternative qBittorrent webUI";
      after = [ "network-online.target" ];
      wants = [ "network-online.target" ];
      wantedBy = [ "multi-user.target" ];

      serviceConfig = {
        Type = "simple";
        User = cfg.user;
        Group = cfg.group;

        LoadCredential = "sessionSecret:${cfg.secretFile}";
        Environment = [ "QUI__SESSION_SECRET_FILE=%d/sessionSecret" ];
        StateDirectory = "qui";

        ExecStartPre = ''
          ${pkgs.coreutils}/bin/install -m 600 '${configFile}' '%S/qui/config.toml'
        '';
        ExecStart = "${getExe cfg.package} serve --config-dir %S/qui";
        Restart = "on-failure";

        # Based on qbittorrent and nemorosa hardening settings
        # Similar to what systemd hardening helper suggests
        CapabilityBoundingSet = "";
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateNetwork = false;
        PrivateTmp = true;
        PrivateUsers = true;
        ProcSubset = "pid";
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = "yes";
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        # This should allow for hardlinking to torrent client files
        ProtectSystem = "full";
        RemoveIPC = true;
        RestrictAddressFamilies = [
          "AF_INET"
          "AF_INET6"
          "AF_NETLINK"
          "AF_UNIX"
        ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        SystemCallFilter = [ "@system-service" ];
      };
    };

    networking.firewall = mkIf cfg.openFirewall {
      allowedTCPPorts = [ cfg.settings.port ];
    };

    users = {
      users = mkIf (cfg.user == "qui") {
        qui = {
          group = cfg.group;
          description = "qui user";
          isSystemUser = true;
          home = stateDir;
        };
      };

      groups = mkIf (cfg.group == "qui") {
        qui = { };
      };
    };
  };

  meta.maintainers = with maintainers; [ undefined-landmark ];
}
Loading