Unverified Commit af0bfe2d authored by Martin Weinelt's avatar Martin Weinelt Committed by GitHub
Browse files

nixos/limesurvey: nginx support (#448680)

parents 35f59034 4d28f658
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -325,6 +325,8 @@

- `services.dnscrypt-proxy` gains a `package` option to specify dnscrypt-proxy package to use.

- `services.limesurvey` now supports nginx as reverse-proxy. Available through [services.limesurvey.webserver](#opt-services.limesurvey.webserver).

- `services.nextcloud.configureRedis` now defaults to `true` in accordance with upstream recommendations to have caching for file locking. See the [upstream doc](https://docs.nextcloud.com/server/31/admin_manual/configuration_files/files_locking_transactional.html) for further details.

- mate-wayland-session 1.28.4 is now using the default wayfire decorator instead of firedecor, thus `services.xserver.desktopManager.mate.enableWaylandSession` is no longer shipping firedecor. If you are experiencing broken window decorations after upgrade, backup and remove `~/.config/mate/wayfire.ini` and re-login.
+265 −192
Original line number Diff line number Diff line
@@ -8,6 +8,8 @@
let

  inherit (lib)
    literalExpression
    mapAttrs
    mkDefault
    mkEnableOption
    mkForce
@@ -15,20 +17,21 @@ let
    mkMerge
    mkOption
    mkPackageOption
    ;
  inherit (lib)
    literalExpression
    mapAttrs
    mkRenamedOptionModule
    optional
    optionalString
    recursiveUpdate
    types
    ;

  cfg = config.services.limesurvey;
  fpm = config.services.phpfpm.pools.limesurvey;

  # https://github.com/LimeSurvey/LimeSurvey/blob/master/.github/workflows/main.yml
  php = pkgs.php83;

  user = "limesurvey";
  group = config.services.httpd.group;
  group = config.services.${cfg.webserver}.group;
  stateDir = "/var/lib/limesurvey";

  configType =
@@ -62,6 +65,13 @@ let

in
{
  imports = [
    (mkRenamedOptionModule
      [ "services" "limesurvey" "virtualHost" ]
      [ "services" "limesurvey" "httpd" "virtualHost" ]
    )
  ];

  # interface

  options.services.limesurvey = {
@@ -190,7 +200,19 @@ in
      };
    };

    virtualHost = mkOption {
    webserver = mkOption {
      type = types.enum [
        "httpd"
        "nginx"
      ];
      default = "httpd";
      example = "nginx";
      description = ''
        Webserver to configure for reverse-proxying limesurvey.
      '';
    };

    httpd.virtualHost = mkOption {
      type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
      example = literalExpression ''
        {
@@ -206,6 +228,23 @@ in
      '';
    };

    nginx.virtualHost = mkOption {
      type = types.submodule (
        recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) { }
      );
      example = literalExpression ''
        {
          serverName = "survey.example.org";
          forceSSL = true;
          enableACME = true;
        }
      '';
      description = ''
        Nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
        See [](#opt-services.nginx.virtualHosts) for further information.
      '';
    };

    poolConfig = mkOption {
      type =
        with types;
@@ -241,8 +280,8 @@ in

  # implementation

  config = mkIf cfg.enable {

  config = mkIf (cfg.enable) (mkMerge [
    {
      assertions = [
        {
          assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
@@ -280,6 +319,43 @@ in
        }
      ];

      users.users.${user} = {
        group = group;
        isSystemUser = true;
      };

      systemd.services.limesurvey-init = {
        wantedBy = [ "multi-user.target" ];
        before = [ "phpfpm-limesurvey.service" ];
        after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.target";
        environment.DBENGINE = "${cfg.database.dbEngine}";
        environment.LIMESURVEY_CONFIG = limesurveyConfig;
        script = ''
          # update or install the database as required
          ${lib.getExe php} ${cfg.package}/share/limesurvey/application/commands/console.php updatedb || \
          ${lib.getExe php} ${cfg.package}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
        '';
        serviceConfig = {
          User = user;
          Group = group;
          Type = "oneshot";
          LoadCredential = [
            "encryption_key:${
              if cfg.encryptionKeyFile != null then
                cfg.encryptionKeyFile
              else
                pkgs.writeText "key" cfg.encryptionKey
            }"
            "encryption_nonce:${
              if cfg.encryptionNonceFile != null then
                cfg.encryptionNonceFile
              else
                pkgs.writeText "nonce" cfg.encryptionKey
            }"
          ];
        };
      };

      services.limesurvey.config = mapAttrs (name: mkDefault) {
        runtimePath = "${stateDir}/tmp/runtime";
        components = {
@@ -306,40 +382,14 @@ in
          uploaddir = "${stateDir}/upload";
          userquestionthemerootdir = "${stateDir}/upload/themes/question";
          force_ssl = mkIf (
          cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL
            cfg.${cfg.webserver}.virtualHost.addSSL
            || cfg.${cfg.webserver}.virtualHost.forceSSL
            || cfg.${cfg.webserver}.virtualHost.onlySSL
          ) "on";
          config.defaultlang = "en";
        };
      };

    services.mysql = mkIf mysqlLocal {
      enable = true;
      package = mkDefault pkgs.mariadb;
      ensureDatabases = [ cfg.database.name ];
      ensureUsers = [
        {
          name = cfg.database.user;
          ensurePermissions = {
            "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX";
          };
        }
      ];
    };

    services.phpfpm.pools.limesurvey = {
      inherit user group;
      phpPackage = pkgs.php83;
      phpEnv.DBENGINE = "${cfg.database.dbEngine}";
      phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
      # App code cannot access credentials directly since the service starts
      # with the root user so we copy the credentials to a place accessible to Limesurvey
      phpEnv.CREDENTIALS_DIRECTORY = "${stateDir}/credentials";
      settings = {
        "listen.owner" = config.services.httpd.user;
        "listen.group" = config.services.httpd.group;
      }
      // cfg.poolConfig;
    };
      systemd.services.phpfpm-limesurvey.serviceConfig = {
        ExecStartPre = pkgs.writeShellScript "limesurvey-phpfpm-exec-pre" ''
          cp -f "''${CREDENTIALS_DIRECTORY}"/encryption_key "${stateDir}/credentials/encryption_key"
@@ -363,12 +413,55 @@ in
        ];
      };

      services.phpfpm.pools.limesurvey = {
        inherit user group;
        phpPackage = php;
        phpEnv.DBENGINE = "${cfg.database.dbEngine}";
        phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
        # App code cannot access credentials directly since the service starts
        # with the root user so we copy the credentials to a place accessible to Limesurvey
        phpEnv.CREDENTIALS_DIRECTORY = "${stateDir}/credentials";
        settings = {
          "listen.owner" = config.services.${cfg.webserver}.user;
          "listen.group" = config.services.${cfg.webserver}.group;
        }
        // cfg.poolConfig;
      };

      systemd.tmpfiles.rules = [
        "d ${stateDir} 0750 ${user} ${group} - -"
        "d ${stateDir}/tmp 0750 ${user} ${group} - -"
        "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -"
        "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -"
        "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -"
        "d ${stateDir}/credentials 0700 ${user} ${group} - -"
        "C ${stateDir}/upload 0750 ${user} ${group} - ${cfg.package}/share/limesurvey/upload"
      ];
    }

    (mkIf mysqlLocal {
      services.mysql = {
        enable = true;
        package = mkDefault pkgs.mariadb;
        ensureDatabases = [ cfg.database.name ];
        ensureUsers = [
          {
            name = cfg.database.user;
            ensurePermissions = {
              "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX";
            };
          }
        ];
      };
    })

    (mkIf (cfg.webserver == "httpd") {
      services.httpd = {
        enable = true;
      adminAddr = mkDefault cfg.virtualHost.adminAddr;
        adminAddr = mkDefault cfg.httpd.virtualHost.adminAddr;
        extraModules = [ "proxy_fcgi" ];
      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [
        cfg.virtualHost
        virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [
          cfg.httpd.virtualHost
          {
            documentRoot = mkForce "${cfg.package}/share/limesurvey";
            extraConfig = ''
@@ -401,56 +494,36 @@ in
          }
        ];
      };

    systemd.tmpfiles.rules = [
      "d ${stateDir} 0750 ${user} ${group} - -"
      "d ${stateDir}/tmp 0750 ${user} ${group} - -"
      "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -"
      "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -"
      "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -"
      "d ${stateDir}/credentials 0700 ${user} ${group} - -"
      "C ${stateDir}/upload 0750 ${user} ${group} - ${cfg.package}/share/limesurvey/upload"
    ];

    systemd.services.limesurvey-init = {
      wantedBy = [ "multi-user.target" ];
      before = [ "phpfpm-limesurvey.service" ];
      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.target";
      environment.DBENGINE = "${cfg.database.dbEngine}";
      environment.LIMESURVEY_CONFIG = limesurveyConfig;
      script = ''
        # update or install the database as required
        ${pkgs.php83}/bin/php ${cfg.package}/share/limesurvey/application/commands/console.php updatedb || \
        ${pkgs.php83}/bin/php ${cfg.package}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
      '';
      serviceConfig = {
        User = user;
        Group = group;
        Type = "oneshot";
        LoadCredential = [
          "encryption_key:${
            if cfg.encryptionKeyFile != null then
              cfg.encryptionKeyFile
            else
              pkgs.writeText "key" cfg.encryptionKey
          }"
          "encryption_nonce:${
            if cfg.encryptionNonceFile != null then
              cfg.encryptionNonceFile
            else
              pkgs.writeText "nonce" cfg.encryptionKey
          }"
        ];
      };
    };

      systemd.services.httpd.after =
        optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.target";
    })

    users.users.${user} = {
      group = group;
      isSystemUser = true;
    (mkIf (cfg.webserver == "nginx") {
      services.nginx = {
        enable = true;
        virtualHosts.${cfg.nginx.virtualHost.serverName} = lib.mkMerge [
          cfg.nginx.virtualHost
          {
            root = lib.mkForce "${cfg.package}/share/limesurvey";
            locations = {
              "/" = {
                index = "index.php";
                tryFiles = "$uri /index.php?$args";
              };

              "~ \.php$".extraConfig = ''
                fastcgi_pass unix:${config.services.phpfpm.pools."limesurvey".socket};
              '';
              "/tmp".root = "/var/lib/limesurvey";
              "/upload/".root = "/var/lib/limesurvey";

            };
          }
        ];
      };

      systemd.services.nginx.after =
        optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
    })
  ]);
}
+39 −20
Original line number Diff line number Diff line
{ lib, pkgs, ... }:

{
  name = "limesurvey";
  meta.maintainers = [ lib.maintainers.aanderse ];

  nodes.machine =
    { ... }:
    {
  nodes.machine = {
    services.limesurvey = {
      enable = true;
        virtualHost = {
      httpd.virtualHost = {
        hostName = "example.local";
        adminAddr = "root@example.local";
      };
@@ -18,14 +17,34 @@

    # limesurvey won't work without a dot in the hostname
    networking.hosts."127.0.0.1" = [ "example.local" ];

    specialisation.nginx = {
      inheritParentConfig = true;
      configuration.services.limesurvey = {
        webserver = "nginx";
        nginx.virtualHost.serverName = "example.local";
      };
    };
  };

  testScript = ''
  testScript =
    { nodes, ... }:
    ''
      def test():
        machine.wait_until_succeeds("curl --fail --silent http://example.local/ | grep -q 'The following surveys are available'")

      start_all()

      machine.wait_for_unit("phpfpm-limesurvey.service")
    assert "The following surveys are available" in machine.succeed(
        "curl -f http://example.local/"
    )
      machine.wait_for_unit("httpd.service")
      machine.wait_for_open_port(80)
      test()

      machine.execute("${nodes.machine.system.build.toplevel}/specialisation/nginx/bin/switch-to-configuration test")

      machine.wait_for_unit("nginx.service")
      test()


    '';
}
+4 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
  fetchFromGitHub,
  writeText,
  nixosTests,
  nix-update-script,
}:

stdenv.mkDerivation rec {
@@ -33,8 +34,9 @@ stdenv.mkDerivation rec {
    runHook postInstall
  '';

  passthru.tests = {
    smoke-test = nixosTests.limesurvey;
  passthru = {
    tests = { inherit (nixosTests) limesurvey; };
    updateScript = nix-update-script { };
  };

  meta = with lib; {