Merge pull request #68973 from symphorien/ihatemoney
ihatemoney: init at 4.1 plus module and test
This commit is contained in:
commit
8fcf9926fa
@ -806,6 +806,7 @@
|
||||
./services/web-apps/gotify-server.nix
|
||||
./services/web-apps/icingaweb2/icingaweb2.nix
|
||||
./services/web-apps/icingaweb2/module-monitoring.nix
|
||||
./services/web-apps/ihatemoney
|
||||
./services/web-apps/limesurvey.nix
|
||||
./services/web-apps/mattermost.nix
|
||||
./services/web-apps/mediawiki.nix
|
||||
|
141
nixos/modules/services/web-apps/ihatemoney/default.nix
Normal file
141
nixos/modules/services/web-apps/ihatemoney/default.nix
Normal file
@ -0,0 +1,141 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.ihatemoney;
|
||||
user = "ihatemoney";
|
||||
group = "ihatemoney";
|
||||
db = "ihatemoney";
|
||||
python3 = config.services.uwsgi.package.python3;
|
||||
pkg = python3.pkgs.ihatemoney;
|
||||
toBool = x: if x then "True" else "False";
|
||||
configFile = pkgs.writeText "ihatemoney.cfg" ''
|
||||
from secrets import token_hex
|
||||
# load a persistent secret key
|
||||
SECRET_KEY_FILE = "/var/lib/ihatemoney/secret_key"
|
||||
SECRET_KEY = ""
|
||||
try:
|
||||
with open(SECRET_KEY_FILE) as f:
|
||||
SECRET_KEY = f.read()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if not SECRET_KEY:
|
||||
print("ihatemoney: generating a new secret key")
|
||||
SECRET_KEY = token_hex(50)
|
||||
with open(SECRET_KEY_FILE, "w") as f:
|
||||
f.write(SECRET_KEY)
|
||||
del token_hex
|
||||
del SECRET_KEY_FILE
|
||||
|
||||
# "normal" configuration
|
||||
DEBUG = False
|
||||
SQLALCHEMY_DATABASE_URI = '${
|
||||
if cfg.backend == "sqlite"
|
||||
then "sqlite:////var/lib/ihatemoney/ihatemoney.sqlite"
|
||||
else "postgresql:///${db}"}'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
MAIL_DEFAULT_SENDER = ("${cfg.defaultSender.name}", "${cfg.defaultSender.email}")
|
||||
ACTIVATE_DEMO_PROJECT = ${toBool cfg.enableDemoProject}
|
||||
ADMIN_PASSWORD = "${toString cfg.adminHashedPassword /*toString null == ""*/}"
|
||||
ALLOW_PUBLIC_PROJECT_CREATION = ${toBool cfg.enablePublicProjectCreation}
|
||||
ACTIVATE_ADMIN_DASHBOARD = ${toBool cfg.enableAdminDashboard}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.services.ihatemoney = {
|
||||
enable = mkEnableOption "ihatemoney webapp. Note that this will set uwsgi to emperor mode running as root";
|
||||
backend = mkOption {
|
||||
type = types.enum [ "sqlite" "postgresql" ];
|
||||
default = "sqlite";
|
||||
description = ''
|
||||
The database engine to use for ihatemoney.
|
||||
If <literal>postgresql</literal> is selected, then a database called
|
||||
<literal>${db}</literal> will be created. If you disable this option,
|
||||
it will however not be removed.
|
||||
'';
|
||||
};
|
||||
adminHashedPassword = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "The hashed password of the administrator. To obtain it, run <literal>ihatemoney generate_password_hash</literal>";
|
||||
};
|
||||
uwsgiConfig = mkOption {
|
||||
type = types.attrs;
|
||||
example = {
|
||||
http = ":8000";
|
||||
};
|
||||
description = "Additionnal configuration of the UWSGI vassal running ihatemoney. It should notably specify on which interfaces and ports the vassal should listen.";
|
||||
};
|
||||
defaultSender = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "Budget manager";
|
||||
description = "The display name of the sender of ihatemoney emails";
|
||||
};
|
||||
email = mkOption {
|
||||
type = types.str;
|
||||
default = "ihatemoney@${config.networking.hostName}";
|
||||
description = "The email of the sender of ihatemoney emails";
|
||||
};
|
||||
};
|
||||
enableDemoProject = mkEnableOption "access to the demo project in ihatemoney";
|
||||
enablePublicProjectCreation = mkEnableOption "permission to create projects in ihatemoney by anyone";
|
||||
enableAdminDashboard = mkEnableOption "ihatemoney admin dashboard";
|
||||
extraConfig = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Extra configuration appended to ihatemoney's configuration file. It is a python file, so pay attention to indentation.";
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
services.postgresql = mkIf (cfg.backend == "postgresql") {
|
||||
enable = true;
|
||||
ensureDatabases = [ db ];
|
||||
ensureUsers = [ {
|
||||
name = user;
|
||||
ensurePermissions = {
|
||||
"DATABASE ${db}" = "ALL PRIVILEGES";
|
||||
};
|
||||
} ];
|
||||
};
|
||||
systemd.services.postgresql = mkIf (cfg.backend == "postgresql") {
|
||||
wantedBy = [ "uwsgi.service" ];
|
||||
before = [ "uwsgi.service" ];
|
||||
};
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/ihatemoney 770 ${user} ${group}"
|
||||
];
|
||||
users = {
|
||||
users.${user} = {
|
||||
isSystemUser = true;
|
||||
inherit group;
|
||||
};
|
||||
groups.${group} = {};
|
||||
};
|
||||
services.uwsgi = {
|
||||
enable = true;
|
||||
plugins = [ "python3" ];
|
||||
# the vassal needs to be able to setuid
|
||||
user = "root";
|
||||
group = "root";
|
||||
instance = {
|
||||
type = "emperor";
|
||||
vassals.ihatemoney = {
|
||||
type = "normal";
|
||||
strict = true;
|
||||
uid = user;
|
||||
gid = group;
|
||||
# apparently flask uses threads: https://github.com/spiral-project/ihatemoney/commit/c7815e48781b6d3a457eaff1808d179402558f8c
|
||||
enable-threads = true;
|
||||
module = "wsgi:application";
|
||||
chdir = "${pkg}/${pkg.pythonModule.sitePackages}/ihatemoney";
|
||||
env = [ "IHATEMONEY_SETTINGS_FILE_PATH=${configFile}" ];
|
||||
pythonPackages = self: [ self.ihatemoney ];
|
||||
} // cfg.uwsgiConfig;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,6 @@ with lib;
|
||||
let
|
||||
cfg = config.services.uwsgi;
|
||||
|
||||
uwsgi = pkgs.uwsgi.override {
|
||||
plugins = cfg.plugins;
|
||||
};
|
||||
|
||||
buildCfg = name: c:
|
||||
let
|
||||
plugins =
|
||||
@ -23,8 +19,8 @@ let
|
||||
python =
|
||||
if hasPython2 && hasPython3 then
|
||||
throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
|
||||
else if hasPython2 then uwsgi.python2
|
||||
else if hasPython3 then uwsgi.python3
|
||||
else if hasPython2 then cfg.package.python2
|
||||
else if hasPython3 then cfg.package.python3
|
||||
else null;
|
||||
|
||||
pythonEnv = python.withPackages (c.pythonPackages or (self: []));
|
||||
@ -77,6 +73,11 @@ in {
|
||||
description = "Where uWSGI communication sockets can live";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
instance = mkOption {
|
||||
type = types.attrs;
|
||||
default = {
|
||||
@ -138,7 +139,7 @@ in {
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
|
||||
ExecStart = "${cfg.package}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
|
||||
NotifyAccess = "main";
|
||||
@ -156,5 +157,9 @@ in {
|
||||
users.groups = optionalAttrs (cfg.group == "uwsgi") {
|
||||
uwsgi.gid = config.ids.gids.uwsgi;
|
||||
};
|
||||
|
||||
services.uwsgi.package = pkgs.uwsgi.override {
|
||||
inherit (cfg) plugins;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ in
|
||||
i3wm = handleTest ./i3wm.nix {};
|
||||
icingaweb2 = handleTest ./icingaweb2.nix {};
|
||||
iftop = handleTest ./iftop.nix {};
|
||||
ihatemoney = handleTest ./ihatemoney.nix {};
|
||||
incron = handleTest ./incron.nix {};
|
||||
influxdb = handleTest ./influxdb.nix {};
|
||||
initrd-network-ssh = handleTest ./initrd-network-ssh {};
|
||||
|
52
nixos/tests/ihatemoney.nix
Normal file
52
nixos/tests/ihatemoney.nix
Normal file
@ -0,0 +1,52 @@
|
||||
{ system ? builtins.currentSystem
|
||||
, config ? {}
|
||||
, pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (import ../lib/testing.nix { inherit system pkgs; }) makeTest;
|
||||
in
|
||||
map (
|
||||
backend: makeTest {
|
||||
name = "ihatemoney-${backend}";
|
||||
machine = { lib, ... }: {
|
||||
services.ihatemoney = {
|
||||
enable = true;
|
||||
enablePublicProjectCreation = true;
|
||||
inherit backend;
|
||||
uwsgiConfig = {
|
||||
http = ":8000";
|
||||
};
|
||||
};
|
||||
boot.cleanTmpDir = true;
|
||||
# ihatemoney needs a local smtp server otherwise project creation just crashes
|
||||
services.opensmtpd = {
|
||||
enable = true;
|
||||
serverConfiguration = ''
|
||||
listen on lo
|
||||
action foo relay
|
||||
match from any for any action foo
|
||||
'';
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
$machine->waitForOpenPort(8000);
|
||||
$machine->waitForUnit("uwsgi.service");
|
||||
my $return = $machine->succeed("curl -X POST http://localhost:8000/api/projects -d 'name=yay&id=yay&password=yay&contact_email=yay\@example.com'");
|
||||
die "wrong project id $return" unless "\"yay\"\n" eq $return;
|
||||
my $timestamp = $machine->succeed("stat --printf %Y /var/lib/ihatemoney/secret_key");
|
||||
my $owner = $machine->succeed("stat --printf %U:%G /var/lib/ihatemoney/secret_key");
|
||||
die "wrong ownership for the secret key: $owner, is uwsgi running as the right user ?" unless $owner eq "ihatemoney:ihatemoney";
|
||||
$machine->shutdown();
|
||||
$machine->start();
|
||||
$machine->waitForOpenPort(8000);
|
||||
$machine->waitForUnit("uwsgi.service");
|
||||
# check that the database is really persistent
|
||||
print $machine->succeed("curl --basic -u yay:yay http://localhost:8000/api/projects/yay");
|
||||
# check that the secret key is really persistent
|
||||
my $timestamp2 = $machine->succeed("stat --printf %Y /var/lib/ihatemoney/secret_key");
|
||||
die unless $timestamp eq $timestamp2;
|
||||
$machine->succeed("curl http://localhost:8000 | grep ihatemoney");
|
||||
'';
|
||||
}
|
||||
) [ "sqlite" "postgresql" ]
|
91
pkgs/development/python-modules/ihatemoney/default.nix
Normal file
91
pkgs/development/python-modules/ihatemoney/default.nix
Normal file
@ -0,0 +1,91 @@
|
||||
{ buildPythonPackage, lib, fetchFromGitHub, nixosTests
|
||||
, alembic
|
||||
, aniso8601
|
||||
, Babel
|
||||
, blinker
|
||||
, click
|
||||
, dnspython
|
||||
, email_validator
|
||||
, flask
|
||||
, flask-babel
|
||||
, flask-cors
|
||||
, flask_mail
|
||||
, flask_migrate
|
||||
, flask-restful
|
||||
, flask_script
|
||||
, flask_sqlalchemy
|
||||
, flask_wtf
|
||||
, idna
|
||||
, itsdangerous
|
||||
, jinja2
|
||||
, Mako
|
||||
, markupsafe
|
||||
, python-dateutil
|
||||
, pytz
|
||||
, six
|
||||
, sqlalchemy
|
||||
, werkzeug
|
||||
, wtforms
|
||||
, psycopg2 # optional, for postgresql support
|
||||
, flask_testing
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "ihatemoney";
|
||||
version = "4.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "spiral-project";
|
||||
repo = pname;
|
||||
rev = version;
|
||||
sha256 = "1ai7v2i2rvswzv21nwyq51fvp8lr2x2cl3n34p11br06kc1pcmin";
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [
|
||||
alembic
|
||||
aniso8601
|
||||
Babel
|
||||
blinker
|
||||
click
|
||||
dnspython
|
||||
email_validator
|
||||
flask
|
||||
flask-babel
|
||||
flask-cors
|
||||
flask_mail
|
||||
flask_migrate
|
||||
flask-restful
|
||||
flask_script
|
||||
flask_sqlalchemy
|
||||
flask_wtf
|
||||
idna
|
||||
itsdangerous
|
||||
jinja2
|
||||
Mako
|
||||
markupsafe
|
||||
python-dateutil
|
||||
pytz
|
||||
six
|
||||
sqlalchemy
|
||||
werkzeug
|
||||
wtforms
|
||||
psycopg2
|
||||
];
|
||||
|
||||
checkInputs = [
|
||||
flask_testing
|
||||
];
|
||||
|
||||
passthru.tests = {
|
||||
inherit (nixosTests) ihatemoney;
|
||||
};
|
||||
meta = with lib; {
|
||||
homepage = "https://ihatemoney.org";
|
||||
description = "A simple shared budget manager web application";
|
||||
platforms = platforms.linux;
|
||||
license = licenses.beerware;
|
||||
maintainers = [ maintainers.symphorien ];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -761,6 +761,8 @@ in {
|
||||
|
||||
i3ipc = callPackage ../development/python-modules/i3ipc { };
|
||||
|
||||
ihatemoney = callPackage ../development/python-modules/ihatemoney { };
|
||||
|
||||
imutils = callPackage ../development/python-modules/imutils { };
|
||||
|
||||
inotify-simple = callPackage ../development/python-modules/inotify-simple { };
|
||||
|
Loading…
Reference in New Issue
Block a user