mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-17 07:13:23 +01:00
Merge pull request #68973 from symphorien/ihatemoney
ihatemoney: init at 4.1 plus module and test
This commit is contained in:
commit
8fcf9926fa
7 changed files with 300 additions and 7 deletions
|
@ -806,6 +806,7 @@
|
||||||
./services/web-apps/gotify-server.nix
|
./services/web-apps/gotify-server.nix
|
||||||
./services/web-apps/icingaweb2/icingaweb2.nix
|
./services/web-apps/icingaweb2/icingaweb2.nix
|
||||||
./services/web-apps/icingaweb2/module-monitoring.nix
|
./services/web-apps/icingaweb2/module-monitoring.nix
|
||||||
|
./services/web-apps/ihatemoney
|
||||||
./services/web-apps/limesurvey.nix
|
./services/web-apps/limesurvey.nix
|
||||||
./services/web-apps/mattermost.nix
|
./services/web-apps/mattermost.nix
|
||||||
./services/web-apps/mediawiki.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
|
let
|
||||||
cfg = config.services.uwsgi;
|
cfg = config.services.uwsgi;
|
||||||
|
|
||||||
uwsgi = pkgs.uwsgi.override {
|
|
||||||
plugins = cfg.plugins;
|
|
||||||
};
|
|
||||||
|
|
||||||
buildCfg = name: c:
|
buildCfg = name: c:
|
||||||
let
|
let
|
||||||
plugins =
|
plugins =
|
||||||
|
@ -23,8 +19,8 @@ let
|
||||||
python =
|
python =
|
||||||
if hasPython2 && hasPython3 then
|
if hasPython2 && hasPython3 then
|
||||||
throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
|
throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
|
||||||
else if hasPython2 then uwsgi.python2
|
else if hasPython2 then cfg.package.python2
|
||||||
else if hasPython3 then uwsgi.python3
|
else if hasPython3 then cfg.package.python3
|
||||||
else null;
|
else null;
|
||||||
|
|
||||||
pythonEnv = python.withPackages (c.pythonPackages or (self: []));
|
pythonEnv = python.withPackages (c.pythonPackages or (self: []));
|
||||||
|
@ -77,6 +73,11 @@ in {
|
||||||
description = "Where uWSGI communication sockets can live";
|
description = "Where uWSGI communication sockets can live";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
internal = true;
|
||||||
|
};
|
||||||
|
|
||||||
instance = mkOption {
|
instance = mkOption {
|
||||||
type = types.attrs;
|
type = types.attrs;
|
||||||
default = {
|
default = {
|
||||||
|
@ -138,7 +139,7 @@ in {
|
||||||
'';
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "notify";
|
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";
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||||
ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
|
ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
|
||||||
NotifyAccess = "main";
|
NotifyAccess = "main";
|
||||||
|
@ -156,5 +157,9 @@ in {
|
||||||
users.groups = optionalAttrs (cfg.group == "uwsgi") {
|
users.groups = optionalAttrs (cfg.group == "uwsgi") {
|
||||||
uwsgi.gid = config.ids.gids.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 {};
|
i3wm = handleTest ./i3wm.nix {};
|
||||||
icingaweb2 = handleTest ./icingaweb2.nix {};
|
icingaweb2 = handleTest ./icingaweb2.nix {};
|
||||||
iftop = handleTest ./iftop.nix {};
|
iftop = handleTest ./iftop.nix {};
|
||||||
|
ihatemoney = handleTest ./ihatemoney.nix {};
|
||||||
incron = handleTest ./incron.nix {};
|
incron = handleTest ./incron.nix {};
|
||||||
influxdb = handleTest ./influxdb.nix {};
|
influxdb = handleTest ./influxdb.nix {};
|
||||||
initrd-network-ssh = handleTest ./initrd-network-ssh {};
|
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 { };
|
i3ipc = callPackage ../development/python-modules/i3ipc { };
|
||||||
|
|
||||||
|
ihatemoney = callPackage ../development/python-modules/ihatemoney { };
|
||||||
|
|
||||||
imutils = callPackage ../development/python-modules/imutils { };
|
imutils = callPackage ../development/python-modules/imutils { };
|
||||||
|
|
||||||
inotify-simple = callPackage ../development/python-modules/inotify-simple { };
|
inotify-simple = callPackage ../development/python-modules/inotify-simple { };
|
||||||
|
|
Loading…
Reference in a new issue