summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorMaximilian Bosch <maximilian@mbosch.me>2020-02-09 23:11:46 +0100
committerGitHub <noreply@github.com>2020-02-09 23:11:46 +0100
commitc2f2366f5c0d6574f1f5ba7e58149def2157c5d9 (patch)
tree9a578aa882d46584ee7121f63ef06c32b0cc4979 /nixos
parentc8718e29b3740b9094aee842e7b157872d98942e (diff)
parent13f7b7555322f5727d4b5279102222fdbee62587 (diff)
downloadnixpkgs-c2f2366f5c0d6574f1f5ba7e58149def2157c5d9.tar
nixpkgs-c2f2366f5c0d6574f1f5ba7e58149def2157c5d9.tar.gz
nixpkgs-c2f2366f5c0d6574f1f5ba7e58149def2157c5d9.tar.bz2
nixpkgs-c2f2366f5c0d6574f1f5ba7e58149def2157c5d9.tar.lz
nixpkgs-c2f2366f5c0d6574f1f5ba7e58149def2157c5d9.tar.xz
nixpkgs-c2f2366f5c0d6574f1f5ba7e58149def2157c5d9.tar.zst
nixpkgs-c2f2366f5c0d6574f1f5ba7e58149def2157c5d9.zip
Merge pull request #79485 from Ma27/grocy
grocy: init at 2.6.0
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/web-apps/grocy.nix172
-rw-r--r--nixos/modules/services/web-apps/grocy.xml77
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/grocy.nix47
5 files changed, 298 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 878b77969af..402f222f4e9 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -814,6 +814,7 @@
   ./services/web-apps/dokuwiki.nix
   ./services/web-apps/frab.nix
   ./services/web-apps/gotify-server.nix
+  ./services/web-apps/grocy.nix
   ./services/web-apps/icingaweb2/icingaweb2.nix
   ./services/web-apps/icingaweb2/module-monitoring.nix
   ./services/web-apps/ihatemoney
diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix
new file mode 100644
index 00000000000..568bdfd0c42
--- /dev/null
+++ b/nixos/modules/services/web-apps/grocy.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.grocy;
+in {
+  options.services.grocy = {
+    enable = mkEnableOption "grocy";
+
+    hostName = mkOption {
+      type = types.str;
+      description = ''
+        FQDN for the grocy instance.
+      '';
+    };
+
+    nginx.enableSSL = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether or not to enable SSL (with ACME and let's encrypt)
+        for the grocy vhost.
+      '';
+    };
+
+    phpfpm.settings = mkOption {
+      type = with types; attrsOf (oneOf [ int str bool ]);
+      default = {
+        "pm" = "dynamic";
+        "php_admin_value[error_log]" = "stderr";
+        "php_admin_flag[log_errors]" = true;
+        "listen.owner" = "nginx";
+        "catch_workers_output" = true;
+        "pm.max_children" = "32";
+        "pm.start_servers" = "2";
+        "pm.min_spare_servers" = "2";
+        "pm.max_spare_servers" = "4";
+        "pm.max_requests" = "500";
+      };
+
+      description = ''
+        Options for grocy's PHPFPM pool.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/grocy";
+      description = ''
+        Home directory of the <literal>grocy</literal> user which contains
+        the application's state.
+      '';
+    };
+
+    settings = {
+      currency = mkOption {
+        type = types.str;
+        default = "USD";
+        example = "EUR";
+        description = ''
+          ISO 4217 code for the currency to display.
+        '';
+      };
+
+      culture = mkOption {
+        type = types.enum [ "de" "en" "da" "en_GB" "es" "fr" "hu" "it" "nl" "no" "pl" "pt_BR" "ru" "sk_SK" "sv_SE" "tr" ];
+        default = "en";
+        description = ''
+          Display language of the frontend.
+        '';
+      };
+
+      calendar = {
+        showWeekNumber = mkOption {
+          default = true;
+          type = types.bool;
+          description = ''
+            Show the number of the weeks in the calendar views.
+          '';
+        };
+        firstDayOfWeek = mkOption {
+          default = null;
+          type = types.nullOr (types.enum (range 0 6));
+          description = ''
+            Which day of the week (0=Sunday, 1=Monday etc.) should be the
+            first day.
+          '';
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."grocy/config.php".text = ''
+      <?php
+      Setting('CULTURE', '${cfg.settings.culture}');
+      Setting('CURRENCY', '${cfg.settings.currency}');
+      Setting('CALENDAR_FIRST_DAY_OF_WEEK', '${toString cfg.settings.calendar.firstDayOfWeek}');
+      Setting('CALENDAR_SHOW_WEEK_OF_YEAR', ${boolToString cfg.settings.calendar.showWeekNumber});
+    '';
+
+    users.users.grocy = {
+      isSystemUser = true;
+      createHome = true;
+      home = cfg.dataDir;
+      group = "nginx";
+    };
+
+    systemd.tmpfiles.rules = map (
+      dirName: "d '${cfg.dataDir}/${dirName}' - grocy nginx - -"
+    ) [ "viewcache" "plugins" "settingoverrides" "storage" ];
+
+    services.phpfpm.pools.grocy = {
+      user = "grocy";
+      group = "nginx";
+
+      # PHP 7.3 is the only version which is supported/tested by upstream:
+      # https://github.com/grocy/grocy/blob/v2.6.0/README.md#how-to-install
+      phpPackage = pkgs.php73;
+
+      inherit (cfg.phpfpm) settings;
+
+      phpEnv = {
+        GROCY_CONFIG_FILE = "/etc/grocy/config.php";
+        GROCY_DB_FILE = "${cfg.dataDir}/grocy.db";
+        GROCY_STORAGE_DIR = "${cfg.dataDir}/storage";
+        GROCY_PLUGIN_DIR = "${cfg.dataDir}/plugins";
+        GROCY_CACHE_DIR = "${cfg.dataDir}/viewcache";
+      };
+    };
+
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.hostName}" = mkMerge [
+        { root = "${pkgs.grocy}/public";
+          locations."/".extraConfig = ''
+            rewrite ^ /index.php;
+          '';
+          locations."~ \\.php$".extraConfig = ''
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+            fastcgi_pass unix:${config.services.phpfpm.pools.grocy.socket};
+            include ${config.services.nginx.package}/conf/fastcgi.conf;
+            include ${config.services.nginx.package}/conf/fastcgi_params;
+          '';
+          locations."~ \\.(js|css|ttf|woff2?|png|jpe?g|svg)$".extraConfig = ''
+            add_header Cache-Control "public, max-age=15778463";
+            add_header X-Content-Type-Options nosniff;
+            add_header X-XSS-Protection "1; mode=block";
+            add_header X-Robots-Tag none;
+            add_header X-Download-Options noopen;
+            add_header X-Permitted-Cross-Domain-Policies none;
+            add_header Referrer-Policy no-referrer;
+            access_log off;
+          '';
+          extraConfig = ''
+            try_files $uri /index.php;
+          '';
+        }
+        (mkIf cfg.nginx.enableSSL {
+          enableACME = true;
+          forceSSL = true;
+        })
+      ];
+    };
+  };
+
+  meta = {
+    maintainers = with maintainers; [ ma27 ];
+    doc = ./grocy.xml;
+  };
+}
diff --git a/nixos/modules/services/web-apps/grocy.xml b/nixos/modules/services/web-apps/grocy.xml
new file mode 100644
index 00000000000..fdf6d00f4b1
--- /dev/null
+++ b/nixos/modules/services/web-apps/grocy.xml
@@ -0,0 +1,77 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="module-services-grocy">
+
+  <title>Grocy</title>
+  <para>
+    <link xlink:href="https://grocy.info/">Grocy</link> is a web-based self-hosted groceries
+    &amp; household management solution for your home.
+  </para>
+
+  <section xml:id="module-services-grocy-basic-usage">
+   <title>Basic usage</title>
+   <para>
+    A very basic configuration may look like this:
+<programlisting>{ pkgs, ... }:
+{
+  services.grocy = {
+    <link linkend="opt-services.grocy.enable">enable</link> = true;
+    <link linkend="opt-services.grocy.hostName">hostName</link> = "grocy.tld";
+  };
+}</programlisting>
+    This configures a simple vhost using <link linkend="opt-services.nginx.enable">nginx</link>
+    which listens to <literal>grocy.tld</literal> with fully configured ACME/LE (this can be
+    disabled by setting <link linkend="opt-services.grocy.nginx.enableSSL">services.grocy.nginx.enableSSL</link>
+    to <literal>false</literal>). After the initial setup the credentials <literal>admin:admin</literal>
+    can be used to login.
+   </para>
+   <para>
+    The application's state is persisted at <literal>/var/lib/grocy/grocy.db</literal> in a
+    <package>sqlite3</package> database. The migration is applied when requesting the <literal>/</literal>-route
+    of the application.
+   </para>
+  </section>
+
+  <section xml:id="module-services-grocy-settings">
+   <title>Settings</title>
+   <para>
+    The configuration for <literal>grocy</literal> is located at <literal>/etc/grocy/config.php</literal>.
+    By default, the following settings can be defined in the NixOS-configuration:
+<programlisting>{ pkgs, ... }:
+{
+  services.grocy.settings = {
+    # The default currency in the system for invoices etc.
+    # Please note that exchange rates aren't taken into account, this
+    # is just the setting for what's shown in the frontend.
+    <link linkend="opt-services.grocy.settings.currency">currency</link> = "EUR";
+
+    # The display language (and locale configuration) for grocy.
+    <link linkend="opt-services.grocy.settings.currency">culture</link> = "de";
+
+    calendar = {
+      # Whether or not to show the week-numbers
+      # in the calendar.
+      <link linkend="opt-services.grocy.settings.calendar.showWeekNumber">showWeekNumber</link> = true;
+
+      # Index of the first day to be shown in the calendar (0=Sunday, 1=Monday,
+      # 2=Tuesday and so on).
+      <link linkend="opt-services.grocy.settings.calendar.firstDayOfWeek">firstDayOfWeek</link> = 2;
+    };
+  };
+}</programlisting>
+   </para>
+   <para>
+    If you want to alter the configuration file on your own, you can do this manually with
+    an expression like this:
+<programlisting>{ lib, ... }:
+{
+  environment.etc."grocy/config.php".text = lib.mkAfter ''
+    // Arbitrary PHP code in grocy's configuration file
+  '';
+}</programlisting>
+   </para>
+  </section>
+
+</chapter>
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index bdac56169fd..5f2d2858163 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -96,6 +96,7 @@ in
   freeswitch = handleTest ./freeswitch.nix {};
   fsck = handleTest ./fsck.nix {};
   gotify-server = handleTest ./gotify-server.nix {};
+  grocy = handleTest ./grocy.nix {};
   gitea = handleTest ./gitea.nix {};
   gitlab = handleTest ./gitlab.nix {};
   gitolite = handleTest ./gitolite.nix {};
diff --git a/nixos/tests/grocy.nix b/nixos/tests/grocy.nix
new file mode 100644
index 00000000000..7fa479ed2c4
--- /dev/null
+++ b/nixos/tests/grocy.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "grocy";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  machine = { pkgs, ... }: {
+    services.grocy = {
+      enable = true;
+      hostName = "localhost";
+      nginx.enableSSL = false;
+    };
+    environment.systemPackages = [ pkgs.jq ];
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_open_port(80)
+    machine.wait_for_unit("multi-user.target")
+
+    machine.succeed("curl -sSf http://localhost")
+
+    machine.succeed(
+        "curl -c cookies -sSf -X POST http://localhost/login -d 'username=admin&password=admin'"
+    )
+
+    cookie = machine.succeed(
+        "grep -v '^#' cookies | awk '{ print $7 }' | sed -e '/^$/d' | perl -pe 'chomp'"
+    )
+
+    machine.succeed(
+        f"curl -sSf -X POST http://localhost/api/objects/tasks -b 'grocy_session={cookie}' "
+        + '-d \'{"assigned_to_user_id":1,"name":"Test Task","due_date":"1970-01-01"}\'''
+        + " --header 'Content-Type: application/json'"
+    )
+
+    task_name = machine.succeed(
+        f"curl -sSf http://localhost/api/tasks -b 'grocy_session={cookie}' --header 'Accept: application/json' | jq '.[].name' | xargs echo | perl -pe 'chomp'"
+    )
+
+    assert task_name == "Test Task"
+
+    machine.succeed("curl -sSfI http://localhost/api/tasks 2>&1 | grep '401 Unauthorized'")
+
+    machine.shutdown()
+  '';
+})