parent
0107ee8c32
commit
32d2266d0d
@ -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; |
||||
}; |
||||
}; |
||||
}; |
||||
} |
||||
|
||||
|
@ -0,0 +1,49 @@ |
||||
{ 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 owership 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 persitent |
||||
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" ] |
@ -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 ]; |
||||
}; |
||||
} |
||||
|
||||
|
Loading…
Reference in new issue