mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-27 16:15:05 +00:00
397 lines
11 KiB
Nix
397 lines
11 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.services.weblate;
|
|
|
|
dataDir = "/var/lib/weblate";
|
|
settingsDir = "${dataDir}/settings";
|
|
|
|
finalPackage = cfg.package.overridePythonAttrs (old: {
|
|
# We only support the PostgreSQL backend in this module
|
|
dependencies = old.dependencies ++ cfg.package.optional-dependencies.postgres;
|
|
# Use a settings module in dataDir, to avoid having to rebuild the package
|
|
# when user changes settings.
|
|
makeWrapperArgs = (old.makeWrapperArgs or [ ]) ++ [
|
|
"--set PYTHONPATH \"${settingsDir}\""
|
|
"--set DJANGO_SETTINGS_MODULE \"settings\""
|
|
];
|
|
});
|
|
inherit (finalPackage) python;
|
|
|
|
pythonEnv = python.buildEnv.override {
|
|
extraLibs = with python.pkgs; [
|
|
(toPythonModule finalPackage)
|
|
celery
|
|
];
|
|
};
|
|
|
|
# This extends and overrides the weblate/settings_example.py code found in upstream.
|
|
weblateConfig =
|
|
''
|
|
# This was autogenerated by the NixOS module.
|
|
|
|
SITE_TITLE = "Weblate"
|
|
SITE_DOMAIN = "${cfg.localDomain}"
|
|
# TLS terminates at the reverse proxy, but this setting controls how links to weblate are generated.
|
|
ENABLE_HTTPS = True
|
|
SESSION_COOKIE_SECURE = ENABLE_HTTPS
|
|
DATA_DIR = "${dataDir}"
|
|
CACHE_DIR = f"{DATA_DIR}/cache"
|
|
STATIC_ROOT = "${finalPackage.static}"
|
|
MEDIA_ROOT = "/var/lib/weblate/media"
|
|
COMPRESS_ROOT = "${finalPackage.static}"
|
|
COMPRESS_OFFLINE = True
|
|
DEBUG = False
|
|
|
|
DATABASES = {
|
|
"default": {
|
|
"ENGINE": "django.db.backends.postgresql",
|
|
"HOST": "/run/postgresql",
|
|
"NAME": "weblate",
|
|
"USER": "weblate",
|
|
}
|
|
}
|
|
|
|
with open("${cfg.djangoSecretKeyFile}") as f:
|
|
SECRET_KEY = f.read().rstrip("\n")
|
|
|
|
CACHES = {
|
|
"default": {
|
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
"LOCATION": "unix://${config.services.redis.servers.weblate.unixSocket}",
|
|
"OPTIONS": {
|
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
"PASSWORD": None,
|
|
"CONNECTION_POOL_KWARGS": {},
|
|
},
|
|
"KEY_PREFIX": "weblate",
|
|
"TIMEOUT": 3600,
|
|
},
|
|
"avatar": {
|
|
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
|
|
"LOCATION": "/var/lib/weblate/avatar-cache",
|
|
"TIMEOUT": 86400,
|
|
"OPTIONS": {"MAX_ENTRIES": 1000},
|
|
}
|
|
}
|
|
|
|
|
|
CELERY_TASK_ALWAYS_EAGER = False
|
|
CELERY_BROKER_URL = "redis+socket://${config.services.redis.servers.weblate.unixSocket}"
|
|
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
|
|
|
|
VCS_BACKENDS = ("weblate.vcs.git.GitRepository",)
|
|
|
|
SITE_URL = "https://{}".format(SITE_DOMAIN)
|
|
|
|
# WebAuthn
|
|
OTP_WEBAUTHN_RP_NAME = SITE_TITLE
|
|
OTP_WEBAUTHN_RP_ID = SITE_DOMAIN.split(":")[0]
|
|
OTP_WEBAUTHN_ALLOWED_ORIGINS = [SITE_URL]
|
|
|
|
''
|
|
+ lib.optionalString cfg.smtp.enable ''
|
|
ADMINS = (("Weblate Admin", "${cfg.smtp.user}"),)
|
|
|
|
EMAIL_HOST = "${cfg.smtp.host}"
|
|
EMAIL_USE_TLS = True
|
|
EMAIL_HOST_USER = "${cfg.smtp.user}"
|
|
SERVER_EMAIL = "${cfg.smtp.user}"
|
|
DEFAULT_FROM_EMAIL = "${cfg.smtp.user}"
|
|
EMAIL_PORT = 587
|
|
with open("${cfg.smtp.passwordFile}") as f:
|
|
EMAIL_HOST_PASSWORD = f.read().rstrip("\n")
|
|
|
|
''
|
|
+ cfg.extraConfig;
|
|
settings_py =
|
|
pkgs.runCommand "weblate_settings.py"
|
|
{
|
|
inherit weblateConfig;
|
|
passAsFile = [ "weblateConfig" ];
|
|
}
|
|
''
|
|
mkdir -p $out
|
|
cat \
|
|
${finalPackage}/${python.sitePackages}/weblate/settings_example.py \
|
|
$weblateConfigPath \
|
|
> $out/settings.py
|
|
'';
|
|
|
|
environment = {
|
|
PYTHONPATH = "${settingsDir}:${pythonEnv}/${python.sitePackages}/";
|
|
DJANGO_SETTINGS_MODULE = "settings";
|
|
# We run Weblate through gunicorn, so we can't utilise the env var set in the wrapper.
|
|
inherit (finalPackage) GI_TYPELIB_PATH;
|
|
};
|
|
|
|
weblatePath = with pkgs; [
|
|
gitSVN
|
|
borgbackup
|
|
|
|
#optional
|
|
git-review
|
|
tesseract
|
|
licensee
|
|
mercurial
|
|
];
|
|
in
|
|
{
|
|
|
|
options = {
|
|
services.weblate = {
|
|
enable = lib.mkEnableOption "Weblate service";
|
|
|
|
package = lib.mkPackageOption pkgs "weblate" { };
|
|
|
|
localDomain = lib.mkOption {
|
|
description = "The domain name serving your Weblate instance.";
|
|
example = "weblate.example.org";
|
|
type = lib.types.str;
|
|
};
|
|
|
|
djangoSecretKeyFile = lib.mkOption {
|
|
description = ''
|
|
Location of the Django secret key.
|
|
|
|
This should be a path pointing to a file with secure permissions (not /nix/store).
|
|
|
|
Can be generated with `weblate-generate-secret-key` which is available as the `weblate` user.
|
|
'';
|
|
type = lib.types.path;
|
|
};
|
|
|
|
extraConfig = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
description = ''
|
|
Text to append to `settings.py` Weblate configuration file.
|
|
'';
|
|
};
|
|
|
|
smtp = {
|
|
enable = lib.mkEnableOption "Weblate SMTP support";
|
|
user = lib.mkOption {
|
|
description = "SMTP login name.";
|
|
example = "weblate@example.org";
|
|
type = lib.types.str;
|
|
};
|
|
|
|
host = lib.mkOption {
|
|
description = "SMTP host used when sending emails to users.";
|
|
type = lib.types.str;
|
|
example = "127.0.0.1";
|
|
};
|
|
|
|
passwordFile = lib.mkOption {
|
|
description = ''
|
|
Location of a file containing the SMTP password.
|
|
|
|
This should be a path pointing to a file with secure permissions (not /nix/store).
|
|
'';
|
|
type = lib.types.path;
|
|
};
|
|
};
|
|
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
|
|
systemd.tmpfiles.rules = [ "L+ ${settingsDir} - - - - ${settings_py}" ];
|
|
|
|
services.nginx = {
|
|
enable = true;
|
|
virtualHosts."${cfg.localDomain}" = {
|
|
|
|
forceSSL = true;
|
|
enableACME = true;
|
|
|
|
locations = {
|
|
"= /favicon.ico".alias = "${finalPackage}/${python.sitePackages}/weblate/static/favicon.ico";
|
|
"/static/".alias = "${finalPackage.static}/";
|
|
"/media/".alias = "/var/lib/weblate/media/";
|
|
"/".proxyPass = "http://unix:///run/weblate.socket";
|
|
};
|
|
|
|
};
|
|
};
|
|
|
|
systemd.services.weblate-postgresql-setup = {
|
|
description = "Weblate PostgreSQL setup";
|
|
after = [ "postgresql.service" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = "postgres";
|
|
Group = "postgres";
|
|
ExecStart = ''
|
|
${config.services.postgresql.package}/bin/psql weblate -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
|
|
'';
|
|
};
|
|
};
|
|
|
|
systemd.services.weblate-migrate = {
|
|
description = "Weblate migration";
|
|
after = [ "weblate-postgresql-setup.service" ];
|
|
requires = [ "weblate-postgresql-setup.service" ];
|
|
# We want this to be active on boot, not just on socket activation
|
|
wantedBy = [ "multi-user.target" ];
|
|
inherit environment;
|
|
path = weblatePath;
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
StateDirectory = "weblate";
|
|
User = "weblate";
|
|
Group = "weblate";
|
|
ExecStart = "${finalPackage}/bin/weblate migrate --noinput";
|
|
};
|
|
};
|
|
|
|
systemd.services.weblate-celery = {
|
|
description = "Weblate Celery";
|
|
after = [
|
|
"network.target"
|
|
"redis.service"
|
|
"postgresql.service"
|
|
];
|
|
# We want this to be active on boot, not just on socket activation
|
|
wantedBy = [ "multi-user.target" ];
|
|
environment = environment // {
|
|
CELERY_WORKER_RUNNING = "1";
|
|
};
|
|
path = weblatePath;
|
|
# Recommendations from:
|
|
# https://github.com/WeblateOrg/weblate/blob/main/weblate/examples/celery-weblate.service
|
|
serviceConfig =
|
|
let
|
|
# We have to push %n through systemd's replacement, therefore %%n.
|
|
pidFile = "/run/celery/weblate-%%n.pid";
|
|
nodes = "celery notify memory backup translate";
|
|
cmd = verb: ''
|
|
${pythonEnv}/bin/celery multi ${verb} \
|
|
${nodes} \
|
|
-A "weblate.utils" \
|
|
--pidfile=${pidFile} \
|
|
--logfile=/var/log/celery/weblate-%%n%%I.log \
|
|
--loglevel=DEBUG \
|
|
--beat:celery \
|
|
--queues:celery=celery \
|
|
--prefetch-multiplier:celery=4 \
|
|
--queues:notify=notify \
|
|
--prefetch-multiplier:notify=10 \
|
|
--queues:memory=memory \
|
|
--prefetch-multiplier:memory=10 \
|
|
--queues:translate=translate \
|
|
--prefetch-multiplier:translate=4 \
|
|
--concurrency:backup=1 \
|
|
--queues:backup=backup \
|
|
--prefetch-multiplier:backup=2
|
|
'';
|
|
in
|
|
{
|
|
Type = "forking";
|
|
User = "weblate";
|
|
Group = "weblate";
|
|
WorkingDirectory = "${finalPackage}/${python.sitePackages}/weblate/";
|
|
RuntimeDirectory = "celery";
|
|
RuntimeDirectoryPreserve = "restart";
|
|
LogsDirectory = "celery";
|
|
ExecStart = cmd "start";
|
|
ExecReload = cmd "restart";
|
|
ExecStop = ''
|
|
${pythonEnv}/bin/celery multi stopwait \
|
|
${nodes} \
|
|
--pidfile=${pidFile}
|
|
'';
|
|
Restart = "always";
|
|
};
|
|
};
|
|
|
|
systemd.services.weblate = {
|
|
description = "Weblate Gunicorn app";
|
|
after = [
|
|
"network.target"
|
|
"weblate-migrate.service"
|
|
"weblate-celery.service"
|
|
];
|
|
requires = [
|
|
"weblate-migrate.service"
|
|
"weblate-celery.service"
|
|
"weblate.socket"
|
|
];
|
|
inherit environment;
|
|
path = weblatePath;
|
|
serviceConfig = {
|
|
Type = "notify";
|
|
NotifyAccess = "all";
|
|
ExecStart =
|
|
let
|
|
gunicorn = python.pkgs.gunicorn.overridePythonAttrs (old: {
|
|
# Allows Gunicorn to set a meaningful process name
|
|
dependencies = (old.dependencies or [ ]) ++ old.optional-dependencies.setproctitle;
|
|
});
|
|
in
|
|
''
|
|
${gunicorn}/bin/gunicorn \
|
|
--name=weblate \
|
|
--bind='unix:///run/weblate.socket' \
|
|
weblate.wsgi
|
|
'';
|
|
ExecReload = "kill -s HUP $MAINPID";
|
|
KillMode = "mixed";
|
|
PrivateTmp = true;
|
|
WorkingDirectory = dataDir;
|
|
StateDirectory = "weblate";
|
|
RuntimeDirectory = "weblate";
|
|
User = "weblate";
|
|
Group = "weblate";
|
|
};
|
|
};
|
|
|
|
systemd.sockets.weblate = {
|
|
before = [ "nginx.service" ];
|
|
wantedBy = [ "sockets.target" ];
|
|
socketConfig = {
|
|
ListenStream = "/run/weblate.socket";
|
|
SocketUser = "weblate";
|
|
SocketGroup = "weblate";
|
|
SocketMode = "770";
|
|
};
|
|
};
|
|
|
|
services.redis.servers.weblate = {
|
|
enable = true;
|
|
user = "weblate";
|
|
unixSocket = "/run/redis-weblate/redis.sock";
|
|
unixSocketPerm = 770;
|
|
};
|
|
|
|
services.postgresql = {
|
|
enable = true;
|
|
ensureUsers = [
|
|
{
|
|
name = "weblate";
|
|
ensureDBOwnership = true;
|
|
}
|
|
];
|
|
ensureDatabases = [ "weblate" ];
|
|
};
|
|
|
|
users.users.weblate = {
|
|
isSystemUser = true;
|
|
group = "weblate";
|
|
packages = [ finalPackage ] ++ weblatePath;
|
|
};
|
|
|
|
users.groups.weblate.members = [ config.services.nginx.user ];
|
|
};
|
|
|
|
meta.maintainers = with lib.maintainers; [ erictapen ];
|
|
|
|
}
|