indico: wip: create module
Signed-off-by: Jeltz <jeltz@federez.net>
This commit is contained in:
parent
d75eba0b8e
commit
abbafb082d
2 changed files with 443 additions and 107 deletions
423
modules/indico.nix
Normal file
423
modules/indico.nix
Normal file
|
@ -0,0 +1,423 @@
|
|||
# Inspired by https://git.hubrecht.ovh/hubrecht/nix-infra
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.indico;
|
||||
pythonFmt = pkgs.formats.pythonVars { };
|
||||
yamlFmt = pkgs.formats.yaml {};
|
||||
indico = pkgs.callPackage ../pkgs/indico { };
|
||||
pythonEnv = pkgs.python3.withPackages (ps: [
|
||||
(ps.toPythonModule indico)
|
||||
ps.gunicorn
|
||||
]);
|
||||
redisSocket = config.services.redis.servers.${cfg.redis.name}.unixSocket;
|
||||
indicoSocket = "/run/indico/indico.sock";
|
||||
baseDir = "${pythonEnv}/${pythonEnv.sitePackages}/indico";
|
||||
loggingFile = yamlFmt.generate "logging.yaml" {
|
||||
version = 1;
|
||||
root = {
|
||||
level = "INFO";
|
||||
handlers = [ "stderr" ];
|
||||
};
|
||||
loggers = {
|
||||
indico.handlers = [ "stderr" ];
|
||||
celery.handlers = [ "stderr" ];
|
||||
};
|
||||
handlers.stderr = {
|
||||
class = "logging.StreamHandler";
|
||||
formatter = "default";
|
||||
};
|
||||
formatters.default = {
|
||||
format = "%(levelname)s %(request_id)s %(user_id)s %(name)s %(message)s";
|
||||
};
|
||||
};
|
||||
configFile = pythonFmt.generate "indico.conf" {
|
||||
SQLALCHEMY_DATABASE_URI = cfg.database;
|
||||
CACHE_DIR = "${cfg.stateDir}/cache";
|
||||
TEMP_DIR = "${cfg.stateDir}/tmp";
|
||||
LOG_DIR = "/dev/null";
|
||||
STATIC_FILE_METHOD = [
|
||||
"xaccelredirect"
|
||||
{ ${cfg.stateDir} = "/.xsf/indico"; }
|
||||
];
|
||||
STORAGE_BACKENDS = {
|
||||
default = "fs:${cfg.storageDir}";
|
||||
};
|
||||
DEFAULT_LOCALE = cfg.defaultLocale;
|
||||
DEFAULT_TIMEZONE = cfg.defaultTimezone;
|
||||
REDIS_CACHE_URL = cfg.cacheRedis;
|
||||
CELERY_BROKER = cfg.celeryBrokerRedis;
|
||||
USE_PROXY = true;
|
||||
BASE_URL = cfg.baseUrl;
|
||||
SECRET_KEY = cfg.secretKey;
|
||||
LOGGING_CONFIG_FILE = loggingFile;
|
||||
NO_REPLY_EMAIL = cfg.email.noReply;
|
||||
SUPPORT_EMAIL = cfg.email.support;
|
||||
SMTP_SENDER_FALLBACK = cfg.email.senderFallback;
|
||||
PUBLIC_SUPPORT_EMAIL = cfg.email.publicSupport;
|
||||
SMTP_SERVER = [ cfg.email.smtp.host cfg.email.smtp.port ];
|
||||
SMTP_LOGIN = cfg.email.smtp.login;
|
||||
SMTP_PASSWORD = cfg.email.smtp.password;
|
||||
SMTP_USE_TLS = cfg.email.smtp.useTLS;
|
||||
};
|
||||
in
|
||||
{
|
||||
# TODO cProfile; indico standalone command is *very* slow
|
||||
# (~30s just to print the help)
|
||||
|
||||
options.services.indico = {
|
||||
enable = lib.mkEnableOption "indico";
|
||||
|
||||
# TODO use
|
||||
package = lib.mkPackageOption pkgs "indico" { };
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "indico";
|
||||
description = "User under which indico should run.";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "indico";
|
||||
description = "Group under which indico should run.";
|
||||
};
|
||||
|
||||
email = {
|
||||
noReply = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "No-reply email address.";
|
||||
};
|
||||
|
||||
senderFallback = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = cfg.email.noReply;
|
||||
description = "Sender fallback email address.";
|
||||
};
|
||||
|
||||
publicSupport = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Public support email address.";
|
||||
};
|
||||
|
||||
support = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Support email address.";
|
||||
};
|
||||
|
||||
smtp = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "SMTP host.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 587;
|
||||
description = "SMTP port.";
|
||||
};
|
||||
|
||||
useTLS = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "SMTP use TLS.";
|
||||
};
|
||||
|
||||
login = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "SMTP login.";
|
||||
};
|
||||
|
||||
password = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "SMTP password.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
secretKey = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Secret key.";
|
||||
};
|
||||
|
||||
baseUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = lib.optionalString
|
||||
cfg.nginx.enable
|
||||
"https://${cfg.nginx.domain}";
|
||||
description = "Base URL.";
|
||||
};
|
||||
|
||||
defaultLocale = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "fr_FR";
|
||||
description = "Default locale.";
|
||||
};
|
||||
|
||||
defaultTimezone = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "Europe/Paris";
|
||||
description = "Default timezone.";
|
||||
};
|
||||
|
||||
nginx = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to setup an NGinx virtual host.";
|
||||
};
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "NGinx virtual host domain.";
|
||||
};
|
||||
};
|
||||
|
||||
redis = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to setup a Redis server.";
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "indico";
|
||||
description = "Redis server name.";
|
||||
};
|
||||
};
|
||||
|
||||
cacheRedis = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = lib.optionalString cfg.redis.enable
|
||||
"unix://${redisSocket}?db=0";
|
||||
description = "Redis cache URL.";
|
||||
};
|
||||
|
||||
celeryBrokerRedis = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = lib.optionalString cfg.redis.enable
|
||||
"redis+socket://${redisSocket}?virtual_host=1";
|
||||
description = "Redis Celery broker URL.";
|
||||
};
|
||||
|
||||
stateDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/indico";
|
||||
description = ''
|
||||
State directory (used for cache and temporary files).
|
||||
'';
|
||||
};
|
||||
|
||||
storageDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "${cfg.stateDir}/storage";
|
||||
description = "Storage directory.";
|
||||
};
|
||||
|
||||
postgresql = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to setup a PostgreSQL server.";
|
||||
};
|
||||
};
|
||||
|
||||
database = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = lib.optionalString
|
||||
cfg.postgresql.enable
|
||||
"postgresql://@/${cfg.user}?host=/run/postgresql";
|
||||
description = "Database URL.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users = {
|
||||
${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
} // (lib.optionalAttrs cfg.nginx.enable {
|
||||
${config.services.nginx.user}.extraGroups = [ cfg.group ];
|
||||
});
|
||||
|
||||
users.groups.${cfg.group} = { };
|
||||
|
||||
services.postgresql = lib.mkIf cfg.postgresql.enable {
|
||||
enable = true;
|
||||
package = pkgs.postgresql_16; # TODO add setting
|
||||
# TODO db name != cfg.user
|
||||
ensureDatabases = [ cfg.user ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = cfg.user;
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
environment.systemPackages =
|
||||
let
|
||||
indico = pkgs.writeScriptBin "indico" ''
|
||||
#!${pkgs.runtimeShell}
|
||||
sudo=exec
|
||||
if [[ "$USER" != "indico" ]]; then
|
||||
sudo='exec /run/wrappers/bin/sudo -u indico -E INDICO_CONFIG'
|
||||
fi
|
||||
export INDICO_CONFIG=${configFile}
|
||||
$sudo ${lib.getExe' pythonEnv "indico"} "$@"
|
||||
'';
|
||||
in
|
||||
[ indico ];
|
||||
|
||||
services.redis = lib.mkIf cfg.redis.enable {
|
||||
servers.${cfg.redis.name} = {
|
||||
enable = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/cache' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/tmp' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.storageDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
psqlExtensionsCommands = ''
|
||||
CREATE EXTENSION IF NOT EXISTS unaccent;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
'';
|
||||
# TODO StateDirectory, CacheDirectory?
|
||||
common = {
|
||||
environment.INDICO_CONFIG = configFile;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Group = cfg.group;
|
||||
User = cfg.user;
|
||||
# Restart = "on-failure";
|
||||
RuntimeDirectory = "indico";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
indico-db = lib.recursiveUpdate common {
|
||||
description = "Indico database preparation and upgrade";
|
||||
after = [
|
||||
"postgresql.service"
|
||||
];
|
||||
serviceConfig.Type = "oneshot";
|
||||
# Source: pretalx module ; passer par un service oneshot
|
||||
script = ''
|
||||
versionFile="${cfg.stateDir}/version"
|
||||
if [[ ! -f "$versionFile" ]]; then
|
||||
${lib.getExe' config.services.postgresql.package "psql"} \
|
||||
-d "${cfg.database}" \
|
||||
-c "${psqlExtensionsCommands}"
|
||||
${lib.getExe' pythonEnv "indico"} db prepare
|
||||
echo "${indico.version}" > "$versionFile"
|
||||
fi
|
||||
version="$(cat "$versionFile" 2>/dev/null || echo 0)"
|
||||
# FIXME: not tested; may not work
|
||||
if [[ "$version" != "${indico.version}" ]]; then
|
||||
${lib.getExe' pythonEnv "indico"} db upgrade
|
||||
echo "${indico.version}" > "$versionFile"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
indico-worker = lib.recursiveUpdate common {
|
||||
description = "Indico background worker";
|
||||
after = [
|
||||
"network.target"
|
||||
"redis-indico.service"
|
||||
"postgresql.service"
|
||||
"indico-db.service"
|
||||
];
|
||||
serviceConfig.ExecStart = ''
|
||||
${lib.getExe' pythonEnv "indico"} celery worker -B
|
||||
'';
|
||||
};
|
||||
|
||||
indico-web = lib.recursiveUpdate common {
|
||||
description = "Indico web service";
|
||||
after = [
|
||||
"network.target"
|
||||
"redis-indico.service"
|
||||
"postgresql.service"
|
||||
"indico-worker.service"
|
||||
"indico-db.service"
|
||||
];
|
||||
# TODO bind on a TCP socket when cfg.nginx.enable == false?
|
||||
serviceConfig.ExecStart = ''
|
||||
${lib.getExe' pythonEnv "gunicorn"} \
|
||||
--bind unix:${indicoSocket} \
|
||||
--name=indico \
|
||||
indico.web.wsgi
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
systemd.sockets = lib.mkIf cfg.nginx.enable {
|
||||
indico-web.socketConfig = {
|
||||
ListenStream = indicoSocket;
|
||||
SocketUser = config.services.nginx.user;
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = lib.mkIf cfg.nginx.enable {
|
||||
enable = true;
|
||||
recommendedProxySettings = lib.mkDefault true;
|
||||
recommendedOptimisation = lib.mkDefault true;
|
||||
recommendedTlsSettings = lib.mkDefault true;
|
||||
recommendedGzipSettings = lib.mkDefault true;
|
||||
upstreams.indico.servers."unix:${indicoSocket}" = { };
|
||||
virtualHosts.${cfg.nginx.domain} = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations = {
|
||||
"/.xsf/indico/" = {
|
||||
alias = "${cfg.stateDir}/";
|
||||
extraConfig = ''
|
||||
internal;
|
||||
'';
|
||||
};
|
||||
"~ ^/(images|fonts)(.*)/(.+?)(__v[0-9a-f]+)?\\.([^.]+)$" = {
|
||||
alias = "${baseDir}/web/static/$1$2/$3.$5";
|
||||
priority = 900;
|
||||
extraConfig = ''
|
||||
access_log off;
|
||||
'';
|
||||
};
|
||||
"~ ^/(css|dist|images|fonts)/(.*)$" = {
|
||||
alias = "${baseDir}/web/static/$1/$2";
|
||||
extraConfig = ''
|
||||
access_log off;
|
||||
'';
|
||||
};
|
||||
"= /robots.txt" = {
|
||||
alias = "${baseDir}/web/static/robots.txt";
|
||||
extraConfig = ''
|
||||
access_log off;
|
||||
'';
|
||||
};
|
||||
"/" = {
|
||||
proxyPass = "http://indico";
|
||||
# FIXME is it useful?
|
||||
extraConfig = ''
|
||||
client_max_body_size 1G;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue