parent
3d051f49ba
commit
13f7b75553
@ -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; |
||||
}; |
||||
} |
@ -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 |
||||
& 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> |
@ -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() |
||||
''; |
||||
}) |
Loading…
Reference in new issue