{ config, pkgs, lib, ... }: # TODO: when DEBUG = False, serving static/media does not work, not sure why with lib; let cfg = config.services.wger; defaultUser = "wger"; wgerpkgs = pkgs.callPackage ./default.nix {}; # generate settings files settingsFormat = pkgs.formats.json {}; wger_settings_file = pkgs.writeText "settings.json" (builtins.toJSON cfg.wgerSettings); django_settings_file = pkgs.writeText "settings.json" (builtins.toJSON cfg.djangoSettings); settingsFile = pkgs.writeText "settings.py" '' from wger.settings_global import * import json import os with open("${django_settings_file}") as f: for k, v in json.load(f).items(): if isinstance(v, str) and v.startswith("$"): v = os.environ[v[1:]] globals()[k] = v with open("${wger_settings_file}") as f: for k, v in json.load(f).items(): if isinstance(v, str) and v.startswith("$"): v = os.environ[v[1:]] WGER_SETTINGS[k] = v ''; settingsFileDir = pkgs.writeTextDir "settings.py" (builtins.readFile settingsFile); in { meta.maintainers = with maintainers; [ eyjhb ]; options.services.wger = { enable = mkOption { type = lib.types.bool; default = false; description = '' Enable Wger. ''; }; dataDir = mkOption { type = types.str; default = "/var/lib/wger"; description = "Directory to store the Wger data."; }; mediaDir = mkOption { type = types.str; default = "${cfg.dataDir}/media"; defaultText = literalExpression ''"''${dataDir}/media"''; description = "Directory to store the Wger media."; }; environmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; example = "/var/lib/teeworlds/teeworlds.env"; description = '' Environment file as defined in {manpage}`systemd.exec(5)`. Secrets may be passed to the service without adding them to the world-readable Nix store, by specifying placeholder variables as the option value in Nix and setting these variables accordingly in the environment file. ``` # snippet of teeworlds-related config services.teeworlds.settings.SECRET_KEY = "$SECRETS_KEY"; ``` ``` # content of the environment file SECRETS_KEY=verysecretpassword ``` Note that this file needs to be available on the host on which `wger` is running. ''; }; address = mkOption { type = types.str; default = "localhost"; description = "Web interface address."; }; port = mkOption { type = types.port; default = 28391; description = "Web interface port."; }; djangoSettings = mkOption { type = lib.types.submodule { freeformType = settingsFormat.type; }; default = { }; }; wgerSettings = mkOption { type = lib.types.submodule { freeformType = settingsFormat.type; }; default = { }; }; user = mkOption { type = types.str; default = defaultUser; # TODO: fix this, it is because of the database thing readOnly = true; description = "User under which Wger runs."; }; configureRedis = lib.mkOption { type = lib.types.bool; default = true; }; configurePostgres = lib.mkOption { type = lib.types.bool; default = true; }; package = mkPackageOption { wger = wgerpkgs; } "wger" { }; }; config = mkIf cfg.enable { services.wger.wgerSettings = { EMAIL_FROM = mkDefault "wger Workout Manager "; ALLOW_REGISTRATION = mkDefault true; ALLOW_GUEST_USERS = mkDefault true; ALLOW_UPLOAD_VIDEOS = mkDefault false; MIN_ACCOUNT_AGE_TO_TRUST = mkDefault 1; EXERCISE_CACHE_TTL = mkDefault 3600; # 1 hour }; services.wger.djangoSettings = rec { DEBUG = mkDefault false; # configure database as postgresql or sqlite DATABASES.default = if cfg.configurePostgres then { ENGINE = "django.db.backends.postgresql"; NAME = "wger"; USER = "wger"; PASSWORD = ""; HOST = "/run/postgresql"; PORT = ""; } else { ENGINE = "django.db.backends.sqlite3"; NAME = "${cfg.dataDir}/database.db"; USER = ""; PASSWORD = ""; HOST = ""; PORT = ""; }; SECRET_KEY = "$SECRET_KEY"; MEDIA_ROOT = cfg.mediaDir; MEDIA_URL = "/media/"; # EMAIL EMAIL_BACKEND = mkDefault "django.core.mail.backends.console.EmailBackend"; # Cache - Redis CACHES.default = mkIf cfg.configureRedis { BACKEND = "django_redis.cache.RedisCache"; LOCATION = "unix://${config.services.redis.servers.wger.unixSocket}"; TIMEOUT = 15 * 24 * 60 * 60; # 15 days OPTIONS.CLIENT_CLASS = "django_redis.client.DefaultClient"; }; # setup allowed hosts # CSRF_TRUSTED_ORIGINS = [ "https://${svc_domain}" ]; # ALLOWED_HOSTS = [ svc_domain ]; # disable recaptcha RECAPTCHA_PUBLIC_KEY = ""; RECAPTCHA_PRIVATE_KEY = ""; USE_RECAPTCHA = false; # does not work STATIC_ROOT = "${cfg.package}/share/static"; COMPRESS_ROOT = STATIC_ROOT; COMPRESS_OFFLINE = true; }; # main service systemd.services.wger = { description = "wger fitness"; wantedBy = [ "multi-user.target" ]; after = [ "networking.target" ]; script = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ gunicorn # TODO: fix this, it should work with cfg.package (pkgs.python3Packages.callPackage ./default.nix {}) ]); in '' # initial setup ${cfg.package}/bin/wger migrate-db -s ${settingsFile} || true # TODO: fix at some point # ${cfg.package}/bin/wger load-fixtures -s ${settingsFile} || true # run server # ${cfg.package}/bin/wger start -s ${settingsFile} PYTHONPATH="${pythonEnv}/${pkgs.python3.sitePackages}:${settingsFileDir}" ${pythonEnv}/bin/gunicorn wger.wsgi:application --reload --bind ${cfg.address}:${builtins.toString cfg.port} ''; serviceConfig = { EnvironmentFile = config.age.secrets.wger-env.path; Restart = "on-failure"; RestartSec = "5s"; PrivateTmp = "yes"; User = cfg.user; # TODO: fix this, maybe Group = cfg.user; }; }; # periodic keep up-to-date systemd.timers."wger-housekeeping" = { wantedBy = [ "timers.target" ]; timerConfig.OnCalendar = "daily"; }; systemd.services."wger-housekeeping" = { after = [ "wger.service" ]; requires = [ "wger.service" ]; script = '' WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage sync-exercises || true WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage download-exercise-images || true WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage download-exercise-videos || true # WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage sync-ingredients || true ${cfg.package}/bin/wger load-online-fixtures -s ${settingsFile} || true WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage exercises-health-check || true ''; serviceConfig = { EnvironmentFile = config.age.secrets.wger-env.path; # Type = "oneshot"; User = cfg.user; # TODO: fix this, maybe Group = cfg.user; }; }; # postgresql services.postgresql = lib.mkIf cfg.configurePostgres { ensureDatabases = [ cfg.user ]; ensureUsers = [{ name = cfg.user; ensureDBOwnership = true; }]; }; # redis services.redis.servers.wger = lib.mkIf cfg.configureRedis { enable = true; user = cfg.user; }; # setup user users = optionalAttrs (cfg.user == defaultUser) { users.${defaultUser} = { group = defaultUser; # TODO: fix this # uid = config.ids.uids.paperless + 2; uid = 738; home = cfg.dataDir; }; groups.${defaultUser} = { # TODO: fix this # gid = config.ids.gids.paperless + 2; gid = 738; }; }; }; }