Skip to content

Commit

Permalink
Merge pull request #68973 from symphorien/ihatemoney
Browse files Browse the repository at this point in the history
ihatemoney: init at 4.1 plus module and test
  • Loading branch information
Ekleog committed Jan 9, 2020
2 parents 0cffec3 + b31660e commit 8fcf992
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 7 deletions.
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Expand Up @@ -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
Expand Down
141 changes: 141 additions & 0 deletions nixos/modules/services/web-apps/ihatemoney/default.nix
@@ -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;
};
};
};
}


19 changes: 12 additions & 7 deletions nixos/modules/services/web-servers/uwsgi.nix
Expand Up @@ -5,10 +5,6 @@ with lib;
let
cfg = config.services.uwsgi;

uwsgi = pkgs.uwsgi.override {
plugins = cfg.plugins;
};

buildCfg = name: c:
let
plugins =
Expand All @@ -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: []));
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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";
Expand All @@ -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;
};
};
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Expand Up @@ -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 {};
Expand Down
52 changes: 52 additions & 0 deletions nixos/tests/ihatemoney.nix
@@ -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 changes: 91 additions & 0 deletions pkgs/development/python-modules/ihatemoney/default.nix
@@ -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 ];
};
}


2 changes: 2 additions & 0 deletions pkgs/top-level/python-packages.nix
Expand Up @@ -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 { };
Expand Down

0 comments on commit 8fcf992

Please sign in to comment.