about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md4
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/security/acme/default.nix4
-rw-r--r--nixos/modules/services/mail/roundcube.nix16
-rw-r--r--nixos/modules/services/networking/consul.nix8
-rw-r--r--nixos/modules/services/web-servers/stargazer.nix198
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix22
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/gitea.nix1
-rw-r--r--nixos/tests/web-servers/stargazer.nix30
10 files changed, 275 insertions, 10 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index 601109ccee5e4..c411ecb6dcb67 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -95,6 +95,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable).
 
+- [stargazer](https://sr.ht/~zethra/stargazer/), a fast and easy to use Gemini server. Available as [services.stargazer](#opt-services.stargazer.enable).
+
 - [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable).
 
 - [peroxide](https://github.com/ljanyst/peroxide), a fork of the official [ProtonMail bridge](https://github.com/ProtonMail/proton-bridge) that aims to be similar to [Hydroxide](https://github.com/emersion/hydroxide). Available as [services.peroxide](#opt-services.peroxide.enable).
@@ -229,6 +231,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - To enable the HTTP3 (QUIC) protocol for a nginx virtual host, set the `quic` attribute on it to true, e.g. `services.nginx.virtualHosts.<name>.quic = true;`.
 
+- The default Asterisk package was changed to v20 from v19. Asterisk versions 16 and 19 have been dropped due to being EOL. You may need to update /var/lib/asterisk to match the template files in `${asterisk-20}/var/lib/asterisk`.
+
 - conntrack helper autodetection has been removed from kernels 6.0 and up upstream, and an assertion was added to ensure things don't silently stop working. Migrate your configuration to assign helpers explicitly or use an older LTS kernel branch as a temporary workaround.
 
 - The `services.pipewire.config` options have been removed, as they have basically never worked correctly. All behavior defined by the default configuration can be overridden with drop-in files as necessary - see [below](#sec-release-23.05-migration-pipewire) for details.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index f1c459f755708..bbbe8682fd072 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1243,6 +1243,7 @@
   ./services/web-servers/nginx/gitweb.nix
   ./services/web-servers/phpfpm/default.nix
   ./services/web-servers/pomerium.nix
+  ./services/web-servers/stargazer.nix
   ./services/web-servers/tomcat.nix
   ./services/web-servers/traefik.nix
   ./services/web-servers/trafficserver/default.nix
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index 66d1d2c5d9c5b..ea94b54312cfb 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/nixos/modules/security/acme/default.nix
@@ -487,7 +487,7 @@ let
       };
 
       email = mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
         inherit (defaultAndText "email" null) default defaultText;
         description = lib.mdDoc ''
           Email address for account creation and correspondence from the CA.
@@ -555,7 +555,7 @@ let
       };
 
       credentialsFile = mkOption {
-        type = types.path;
+        type = types.nullOr types.path;
         inherit (defaultAndText "credentialsFile" null) default defaultText;
         description = lib.mdDoc ''
           Path to an EnvironmentFile for the cert's service containing any required and
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 3aaec145930db..b9cf526b0bbe2 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -70,7 +70,12 @@ in
       };
       passwordFile = mkOption {
         type = types.str;
-        description = lib.mdDoc "Password file for the postgresql connection. Must be readable by user `nginx`. Ignored if `database.host` is set to `localhost`, as peer authentication will be used.";
+        description = lib.mdDoc ''
+          Password file for the postgresql connection.
+          Must be formated according to PostgreSQL .pgpass standard (see https://www.postgresql.org/docs/current/libpq-pgpass.html)
+          but only one line, no comments and readable by user `nginx`.
+          Ignored if `database.host` is set to `localhost`, as peer authentication will be used.
+        '';
       };
       dbname = mkOption {
         type = types.str;
@@ -123,7 +128,13 @@ in
     environment.etc."roundcube/config.inc.php".text = ''
       <?php
 
-      ${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"}
+      ${lib.optionalString (!localDB) ''
+        $password = file('${cfg.database.passwordFile}')[0];
+        $password = preg_split('~\\\\.(*SKIP)(*FAIL)|\:~s', $password);
+        $password = end($password);
+        $password = str_replace("\\:", ":", $password);
+        $password = str_replace("\\\\", "\\", $password);
+      ''}
 
       $config = array();
       $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
@@ -223,6 +234,7 @@ in
         path = [ config.services.postgresql.package ];
       })
       {
+        after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         script = let
           psql = "${lib.optionalString (!localDB) "PGPASSFILE=${cfg.database.passwordFile}"} ${pkgs.postgresql}/bin/psql ${lib.optionalString (!localDB) "-h ${cfg.database.host} -U ${cfg.database.username} "} ${cfg.database.dbname}";
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index f1c36138be3e4..955463b9031eb 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -199,7 +199,7 @@ in
             (filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc);
 
         serviceConfig = {
-          ExecStart = "@${cfg.package}/bin/consul consul agent -config-dir /etc/consul.d"
+          ExecStart = "@${lib.getExe cfg.package} consul agent -config-dir /etc/consul.d"
             + concatMapStrings (n: " -config-file ${n}") configFiles;
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           PermissionsStartOnly = true;
@@ -207,10 +207,10 @@ in
           Restart = "on-failure";
           TimeoutStartSec = "infinity";
         } // (optionalAttrs (cfg.leaveOnStop) {
-          ExecStop = "${cfg.package}/bin/consul leave";
+          ExecStop = "${lib.getExe cfg.package} leave";
         });
 
-        path = with pkgs; [ iproute2 gnugrep gawk consul ];
+        path = with pkgs; [ iproute2 gawk cfg.package ];
         preStart = let
           family = if cfg.forceAddrFamily == "ipv6" then
             "-6"
@@ -269,7 +269,7 @@ in
 
         serviceConfig = {
           ExecStart = ''
-            ${cfg.alerts.package}/bin/consul-alerts start \
+            ${lib.getExe cfg.alerts.package} start \
               --alert-addr=${cfg.alerts.listenAddr} \
               --consul-addr=${cfg.alerts.consulAddr} \
               ${optionalString cfg.alerts.watchChecks "--watch-checks"} \
diff --git a/nixos/modules/services/web-servers/stargazer.nix b/nixos/modules/services/web-servers/stargazer.nix
new file mode 100644
index 0000000000000..85783a500d6fe
--- /dev/null
+++ b/nixos/modules/services/web-servers/stargazer.nix
@@ -0,0 +1,198 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.stargazer;
+  routesFormat = pkgs.formats.ini { };
+  globalFile = pkgs.writeText "global.ini" ''
+    listen = ${concatStringsSep " " cfg.listen}
+    connection-logging = ${boolToString cfg.connectionLogging}
+    log-ip = ${boolToString cfg.ipLog}
+    log-ip-partial = ${boolToString cfg.ipLogPartial}
+    request-timeout = ${toString cfg.requestTimeout}
+    response-timeout = ${toString cfg.responseTimeout}
+
+    [:tls]
+    store = ${toString cfg.store}
+    organization = ${cfg.certOrg}
+    gen-certs = ${boolToString cfg.genCerts}
+    regen-certs = ${boolToString cfg.regenCerts}
+    ${optionalString (cfg.certLifetime != "") "cert-lifetime = ${cfg.certLifetime}"}
+
+  '';
+  routesFile = routesFormat.generate "router.ini" cfg.routes;
+  configFile = pkgs.runCommand "config.ini" { } ''
+    cat ${globalFile} ${routesFile} > $out
+  '';
+in
+{
+  options.services.stargazer = {
+    enable = mkEnableOption (lib.mdDoc "Stargazer Gemini server");
+
+    listen = lib.mkOption {
+      type = types.listOf types.str;
+      default = [ "0.0.0.0" ] ++ optional config.networking.enableIPv6 "[::0]";
+      defaultText = literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"'';
+      example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
+      description = lib.mdDoc ''
+        Address and port to listen on.
+      '';
+    };
+
+    connectionLogging = lib.mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc "Whether or not to log connections to stdout.";
+    };
+
+    ipLog = lib.mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "Log client IP addresses in the connection log.";
+    };
+
+    ipLogPartial = lib.mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "Log partial client IP addresses in the connection log.";
+    };
+
+    requestTimeout = lib.mkOption {
+      type = types.int;
+      default = 5;
+      description = lib.mdDoc ''
+        Number of seconds to wait for the client to send a complete
+        request. Set to 0 to disable.
+      '';
+    };
+
+    responseTimeout = lib.mkOption {
+      type = types.int;
+      default = 0;
+      description = lib.mdDoc ''
+        Number of seconds to wait for the client to send a complete
+        request and for stargazer to finish sending the response.
+        Set to 0 to disable.
+      '';
+    };
+
+    store = lib.mkOption {
+      type = types.path;
+      default = /var/lib/gemini/certs;
+      description = lib.mdDoc ''
+        Path to the certificate store on disk. This should be a
+        persistent directory writable by Stargazer.
+      '';
+    };
+
+    certOrg = lib.mkOption {
+      type = types.str;
+      default = "stargazer";
+      description = lib.mdDoc ''
+        The name of the organization responsible for the X.509
+        certificate's /O name.
+      '';
+    };
+
+    genCerts = lib.mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Set to false to disable automatic certificate generation.
+        Use if you want to provide your own certs.
+      '';
+    };
+
+    regenCerts = lib.mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Set to false to turn off automatic regeneration of expired certificates.
+        Use if you want to provide your own certs.
+      '';
+    };
+
+    certLifetime = lib.mkOption {
+      type = types.str;
+      default = "";
+      description = lib.mdDoc ''
+        How long certs generated by Stargazer should live for.
+        Certs live forever by default.
+      '';
+      example = literalExpression "\"1y\"";
+    };
+
+    routes = lib.mkOption {
+      type = routesFormat.type;
+      default = { };
+      description = lib.mdDoc ''
+        Routes that Stargazer should server.
+
+        [Refer to upstream docs](https://git.sr.ht/~zethra/stargazer/tree/main/item/doc/stargazer.ini.5.txt)
+      '';
+      example = literalExpression ''
+        {
+          "example.com" = {
+            root = "/srv/gemini/example.com";
+          };
+          "example.com:/man" = {
+            root = "/cgi-bin";
+            cgi = true;
+          };
+          "other.org~(.*)" = {
+            redirect = "gemini://example.com";
+            rewrite = "\1";
+          };
+        }
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "stargazer";
+      description = lib.mdDoc "User account under which stargazer runs.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "stargazer";
+      description = lib.mdDoc "Group account under which stargazer runs.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.stargazer = {
+      description = "Stargazer gemini server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.stargazer}/bin/stargazer ${configFile}";
+        Restart = "always";
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    # Create default cert store
+    system.activationScripts.makeStargazerCertDir =
+      optionalAttrs (cfg.store == /var/lib/gemini/certs) ''
+        mkdir -p /var/lib/gemini/certs
+        chown -R ${cfg.user}:${cfg.group} /var/lib/gemini/certs
+      '';
+
+    users.users = optionalAttrs (cfg.user == "stargazer") {
+      stargazer = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "stargazer") {
+      stargazer = { };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ gaykitty ];
+}
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index cbc58344791d0..5b515a29ae628 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -574,12 +574,15 @@ in
     virtualisation.writableStore =
       mkOption {
         type = types.bool;
-        default = true; # FIXME
+        default = cfg.mountHostNixStore;
+        defaultText = literalExpression "cfg.mountHostNixStore";
         description =
           lib.mdDoc ''
             If enabled, the Nix store in the VM is made writable by
             layering an overlay filesystem on top of the host's Nix
             store.
+
+            By default, this is enabled if you mount a host Nix store.
           '';
       };
 
@@ -713,6 +716,21 @@ in
           For applications which do a lot of reads from the store,
           this can drastically improve performance, but at the cost of
           disk space and image build time.
+
+          As an alternative, you can use a bootloader which will provide you
+          with a full NixOS system image containing a Nix store and
+          avoid mounting the host nix store through
+          {option}`virtualisation.mountHostNixStore`.
+        '';
+      };
+
+    virtualisation.mountHostNixStore =
+      mkOption {
+        type = types.bool;
+        default = !cfg.useNixStoreImage && !cfg.useBootLoader;
+        defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader";
+        description = lib.mdDoc ''
+          Mount the host Nix store as a 9p mount.
         '';
       };
 
@@ -933,7 +951,7 @@ in
     virtualisation.additionalPaths = [ config.system.build.toplevel ];
 
     virtualisation.sharedDirectories = {
-      nix-store = mkIf (!cfg.useNixStoreImage) {
+      nix-store = mkIf cfg.mountHostNixStore {
         source = builtins.storeDir;
         target = "/nix/store";
       };
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 4926286a4ece5..506cba25ba50a 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -645,6 +645,7 @@ in {
   sslh = handleTest ./sslh.nix {};
   sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {};
   sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {};
+  stargazer = runTest ./web-servers/stargazer.nix;
   starship = handleTest ./starship.nix {};
   step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
   stratis = handleTest ./stratis {};
diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix
index 86d4fce379291..c38aad1f44ece 100644
--- a/nixos/tests/gitea.nix
+++ b/nixos/tests/gitea.nix
@@ -72,6 +72,7 @@ let
 
       server.wait_for_unit("gitea.service")
       server.wait_for_open_port(3000)
+      server.wait_for_open_port(22)
       server.succeed("curl --fail http://localhost:3000/")
 
       server.succeed(
diff --git a/nixos/tests/web-servers/stargazer.nix b/nixos/tests/web-servers/stargazer.nix
new file mode 100644
index 0000000000000..6e720b120d1ab
--- /dev/null
+++ b/nixos/tests/web-servers/stargazer.nix
@@ -0,0 +1,30 @@
+{ pkgs, lib, ... }:
+{
+  name = "stargazer";
+  meta = with lib.maintainers; { maintainers = [ gaykitty ]; };
+
+  nodes = {
+    geminiserver = { pkgs, ... }: {
+      services.stargazer = {
+        enable = true;
+        routes = {
+          "localhost" = {
+            root = toString (pkgs.writeTextDir "index.gmi" ''
+              # Hello NixOS!
+            '');
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    geminiserver.wait_for_unit("stargazer")
+    geminiserver.wait_for_open_port(1965)
+
+    with subtest("check is serving over gemini"):
+      response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965")
+      print(response)
+      assert "Hello NixOS!" in response
+  '';
+}