diff options
-rw-r--r-- | modules/bookwyrm.nix | 140 | ||||
-rw-r--r-- | pkgs/bookwyrm.nix | 50 |
2 files changed, 186 insertions, 4 deletions
diff --git a/modules/bookwyrm.nix b/modules/bookwyrm.nix new file mode 100644 index 0000000..4fcc323 --- /dev/null +++ b/modules/bookwyrm.nix @@ -0,0 +1,140 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.bookwyrm; +in +{ + options.services.bookwyrm = with lib; { + enable = mkEnableOption "bookwyrm"; + + settings = mkOption { + default = {}; + type = types.attrsOf (types.oneOf [ types.bool types.str types.int ]); + description = mdDoc '' + Settings passed to bookwyrm via environment variables. See bookwyrm's + [.env.example](https://github.com/bookwyrm-social/bookwyrm/blob/v0.7.2/.env.example) + file for what is permissible here. + ''; + }; + + package = mkOption { + default = pkgs.bookwyrm.override { inherit (cfg) settings; }; + type = types.package; + description = mdDoc '' + The bookwyrm package to use. Note that this includes the settings .env file; + if you set this directly, settings set via {option}`services.bookwyrm.settings` + will be ignored. + ''; + }; + + stateDir = mkOption { + default = "/var/lib/bookwyrm"; + type = types.path; + description = mdDoc '' + Where bookwyrm keeps dynamic data (in practice, exclusively book covers?) when + not configured to use an S3-compatible storage. This should be persistent storage, + otherwise you will have missing book covers and no obvious way to re-download them + from a catalogue. + ''; + }; + + bindAddress = mkOption { + default = "0.0.0.0"; + type = types.str; + description = mdDoc '' + Address bookwyrm should bind to. + ''; + }; + + port = mkOption { + default = 8000; + type = types.port; + description = mdDoc '' + Port bookwyrm should listen on. + ''; + }; + + threads = mkOption { + default = 8; + type = types.int; + description = mdDoc '' + Number of threads that gunicorn should spawn. + ''; + }; + + setupNginx = mkOption { + default = false; + type = types.bool; + description = mdDoc '' + Whether to set up a simple nginx config to server bookwyrm's `static/` and `image` + directories. + ''; + }; + + nginxVirtualHost = mkOption { + default = "default"; + type = types.str; + description = mdDoc '' + The name of the nginx virtual host to set up. + ''; + }; + }; + + + config = with lib; mkIf cfg.enable { + systemd.packages = [ cfg.package ]; + + systemd.services = { + bookwyrm = { + enable = true; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + BindPaths = [ + cfg.package.passthru.gunicorn + cfg.package.passthru.celery + cfg.stateDir + ]; + } // mkIf (cfg.bindAddress != "0.0.0.0" || cfg.port != 8000 || cfg.threads != 8) { + ExecStart = "${lib.getExe cfg.package.passthru.gunicorn} bookwyrm.wsgi:application --threads=${toString cfg.threads} --bind ${cfg.bindAddress}:${toString cfg.port}"; + }; + + environment.PYTHONPATH = cfg.package.passthru.pythonPath; + }; + + bookwyrm-worker = { + enable = true; + wantedBy = [ "multi-user.target" ]; + environment.PYTHONPATH = cfg.package.passthru.pythonPath; + serviceConfig.BindPaths = [ + cfg.stateDir + ]; + }; + bookwyrm-scheduler = { + enable = true; + wantedBy = [ "multi-user.target" ]; + environment.PYTHONPATH = cfg.package.passthru.pythonPath; + }; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.stateDir}/images 0750 bookwyrm bookwyrm - -" + ]; + + services.nginx = mkIf cfg.setupNginx { + enable = true; + virtualHosts.${cfg.nginxVirtualHost} = { + locations."/static".root = config.services.bookwyrm.package; + locations."/images".root = cfg.stateDir; + locations."/".proxyPass = "http://${cfg.bindAddress}:${toString cfg.port}"; + }; + }; + + users.users.bookwyrm = { + isSystemUser = true; + group = "bookwyrm"; + }; + users.users.nginx.extraGroups = [ "bookwyrm" ]; + users.groups.bookwyrm = {}; + }; + +} diff --git a/pkgs/bookwyrm.nix b/pkgs/bookwyrm.nix index c3838f2..309e90b 100644 --- a/pkgs/bookwyrm.nix +++ b/pkgs/bookwyrm.nix @@ -1,9 +1,42 @@ -{ lib, fetchFromGitHub, python, writeShellScriptBin, writeText, settings ? { } +{ lib +, fetchFromGitHub +, python +, writeShellScriptBin +, writeText +, settings ? { } }: let + # set some dummy values to make the package build + settingsWithDefaults = { + DOMAIN = "localhost"; + DEBUG = false; + SECRET_KEY = "fnord"; + USE_HTTPS = false; + EMAIL = "your@email.here"; + PGPORT = 5432; + POSTGRES_USER = "bookwyrm"; + POSTGRES_DB = "bookwyrm"; + POSTGRES_HOST = "localhost"; + REDIS_ACTIVITY_HOST = "localhost"; + REDIS_ACTIVITY_PORT = 6379; + REDIS_BROKER_HOST = "localhost"; + REDIS_BROKER_PORT = 6379; + EMAIL_HOST = "smtp.example.com"; + EMAIL_PORT = 587; + EMAIL_HOST_USER = "mail@example.org"; + EMAIL_HOST_PASSWORD = "blub"; + MEDIA_ROOT = "/var/lib/bookwyrm/images"; + } // settings; + + # toShellVar produces "" for false, which bookwyrm rejects + toDjangoVar = name: value: lib.toShellVar name + (if value == false then "false" else + (if value == true then "true" else value)); + envfile = writeText "bookwyrm.env" - (lib.strings.concatLines (lib.mapAttrsToList lib.toShellVar settings)); + (lib.strings.concatLines + (lib.mapAttrsToList toDjangoVar settingsWithDefaults)); bookwyrm = python.pkgs.buildPythonApplication rec { pname = "bookwyrm"; @@ -62,13 +95,22 @@ let postBuild = '' ln -s ${envfile} .env + + substituteInPlace contrib/systemd/* \ + --replace /opt/bookwyrm/venv/bin/gunicorn ${lib.getExe python.pkgs.gunicorn} \ + --replace /opt/bookwyrm/venv/bin/celery ${lib.getExe' python.pkgs.celery "celery"} \ + --replace /opt/bookwyrm $out + + sed -i /BindPath/d contrib/systemd/* + python manage.py compile_themes - python manage.py collectstatic --no-input + python manage.py collectstatic --no-input --ignore=*.scss ''; postInstall = '' - mkdir -p $out + mkdir -p $out/lib/systemd/system cp -r * .env $out + cp -r contrib/systemd/* $out/lib/systemd/system ''; passthru = { |