These files need to be owned by the copying user instead of the Nix store permissions. install takes care of that.
363 lines
12 KiB
Nix
363 lines
12 KiB
Nix
{ config, pkgs, lib, ...}:
|
|
with lib;
|
|
let
|
|
cfg = config.services.seafile-server;
|
|
seafileConfigFile = pkgs.writeText "seafile.conf"
|
|
(generators.toINI {} cfg.seafileSettings);
|
|
ccnetConfigFile = pkgs.writeText "ccnet.conf"
|
|
(generators.toINI {} cfg.ccnetSettings);
|
|
gunicornConfigFile = pkgs.writeText "gunicorn.conf.py"
|
|
''
|
|
import os
|
|
daemon = True
|
|
workers = 5
|
|
# default localhost:8000
|
|
bind = "127.0.0.1:8000"
|
|
# Pid
|
|
pids_dir = '${cfg.storagePath}/pids'
|
|
pidfile = os.path.join(pids_dir, 'seahub.pid')
|
|
# for file upload, we need a longer timeout value (default is only 30s, too short)
|
|
timeout = 1200
|
|
limit_request_line = 8190
|
|
'';
|
|
seahubConfigFile = pkgs.writeText "seahub_settings.py"
|
|
''
|
|
SECRET_KEY = #seckey#
|
|
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.db.backends.${if cfg.db.type == "mysql" then
|
|
"mysql" else abort "invalid db type"}',
|
|
'NAME': '${cfg.db.dbnameSeahub}',
|
|
'USER': '${cfg.db.user}',
|
|
'PASSWORD': '#dbpass#',
|
|
'HOST': '${cfg.db.host}',
|
|
'PORT': '${toString cfg.db.port}'
|
|
}
|
|
}
|
|
'';
|
|
# fix permissions at start
|
|
in
|
|
{
|
|
options.services.seafile-server = {
|
|
enable = mkEnableOption "Seafile server";
|
|
storagePath = mkOption {
|
|
type = types.path;
|
|
default = "/srv/seafile";
|
|
description = "where to store uploaded file data";
|
|
};
|
|
ccnetSettings = mkOption {
|
|
type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
|
|
default = {};
|
|
description = ''
|
|
all possible ccnet.conf settings
|
|
'';
|
|
};
|
|
seafileSettings = mkOption {
|
|
type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
|
|
default = {};
|
|
description = ''
|
|
all possible seafile.conf settings
|
|
'';
|
|
};
|
|
autorun = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "enable the seafile-server service to get started automatically";
|
|
};
|
|
db = {
|
|
type = mkOption {
|
|
type = types.enum ["sqlite" "mysql"];
|
|
default = "sqlite";
|
|
description = "database backend type";
|
|
};
|
|
user = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = "seafile";
|
|
description = "Database user name. Not required for sqlite.";
|
|
};
|
|
dbnameSeafile = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = "seafile";
|
|
description = "Database name for Seafile server. Not required for sqlite.";
|
|
};
|
|
dbnameCcnet = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = "seafile";
|
|
description = "Database name for Ccnet server. Not required for sqlite.";
|
|
};
|
|
dbnameSeahub = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = "seafile";
|
|
description = "Database name for Seahub web interface. Not required for sqlite.";
|
|
};
|
|
passwordFile = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = ''
|
|
The full path to a file that contains the database password.
|
|
Not required for sqlite.
|
|
'';
|
|
};
|
|
host = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = "localhost";
|
|
description = "Database host.";
|
|
};
|
|
port = mkOption {
|
|
type = with types; nullOr (either int str);
|
|
default = 3306;
|
|
description = "Database port. Not required for sqlite.";
|
|
};
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "seafile";
|
|
description = "User account under which the Seafile server runs.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "seafile";
|
|
description = "Group account under which the Seafile server runs.";
|
|
};
|
|
|
|
name = mkOption {
|
|
type = types.str;
|
|
default = "Seafile";
|
|
description = "name of the Seafile instance, will show up in client and web interface";
|
|
};
|
|
|
|
domainName = mkOption {
|
|
type = types.str;
|
|
description = "full domain name of the seafile instance";
|
|
};
|
|
|
|
# ccnetPort = mkOption {
|
|
# type = types.int;
|
|
# default = 10001;
|
|
# description = "listening port for ccnet server";
|
|
# };
|
|
|
|
seafilePort = mkOption {
|
|
type = types.int;
|
|
default = 12001;
|
|
description = "listening port for Seafile server";
|
|
};
|
|
|
|
fileserverPort = mkOption {
|
|
type = types.int;
|
|
default = 8082;
|
|
description = "listening port for Seafile's fileserver component";
|
|
};
|
|
|
|
seahubPort = mkOption {
|
|
type = types.int;
|
|
default = 443;
|
|
description = "listening http port for Seahub web interface";
|
|
};
|
|
|
|
externalPort = mkOption {
|
|
type = types.int;
|
|
default = 443;
|
|
description = "external port under which Seahub is reachable from the outside. Possibly the external port of a reverse proxy like nginx.";
|
|
};
|
|
|
|
enableTLS = mkEnableOption {
|
|
default = true;
|
|
description = "whether to use TLS and HTTPS for communication";
|
|
};
|
|
|
|
openFirewall = mkEnableOption {
|
|
default = true;
|
|
description = "whether to open up the firewall ports for ccnet, seafile-server and seahub";
|
|
};
|
|
|
|
defaultQuota = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "default quota to be set per user, in GB";
|
|
};
|
|
|
|
trashExpirationTime = mkOption {
|
|
type = types.int;
|
|
default = 30;
|
|
description = "default time for automatic library trash cleanup";
|
|
};
|
|
|
|
fileRevisionHistoryDays = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "default history length of file revisions to keep in days. <literal>null</literal> means keeping all revisions.";
|
|
};
|
|
|
|
fileserverBindAddress = mkOption {
|
|
type = types.str;
|
|
default = "0.0.0.0";
|
|
description = "bind address for fileserver, binds to all addresses by default";
|
|
};
|
|
|
|
fileserverWorkers = mkOption {
|
|
type = types.int;
|
|
default = 10;
|
|
description = "number of worker threads to server http requests";
|
|
};
|
|
|
|
fileserverIndexers = mkOption {
|
|
type = types.int;
|
|
default = 1;
|
|
description = "number of threads used to sequentially divide uploaded files into blocks for storage";
|
|
};
|
|
|
|
fileserverBlockSize = mkOption {
|
|
type = types.int;
|
|
default = 1;
|
|
description = "size of blocks that uploaded files are divided into, in MB";
|
|
};
|
|
};
|
|
|
|
|
|
config = let
|
|
directoriesToManage = [ cfg.storagePath ];
|
|
in
|
|
mkIf cfg.enable {
|
|
services.seafile-server.ccnetSettings = {
|
|
# TODO: ID and NAME might be required
|
|
General.SERVICE_URL="http${if cfg.enableTLS then "s" else ""}://${cfg.domainName}:${toString cfg.externalPort}/";
|
|
Database = mkMerge [
|
|
{
|
|
ENGINE = cfg.db.type;
|
|
}
|
|
(mkIf (cfg.db.type == "mysql") {
|
|
HOST = cfg.db.host;
|
|
PORT = cfg.db.port;
|
|
USER = cfg.db.user;
|
|
CONNECTION_CHARSET = "utf8";
|
|
DB = cfg.db.dbnameCcnet;
|
|
password = "#dbpass#";
|
|
})
|
|
];
|
|
|
|
};
|
|
|
|
services.seafile-server.seafileSettings = {
|
|
library_trash.expire_days = cfg.trashExpirationTime;
|
|
fileserver = {
|
|
host = cfg.fileserverBindAddress;
|
|
port = cfg.fileserverPort;
|
|
worker_threads = cfg.fileserverWorkers;
|
|
max_indexing_threads = cfg.fileserverIndexers;
|
|
fixed_block_size = cfg.fileserverBlockSize;
|
|
};
|
|
quota = mkIf (! isNull cfg.defaultQuota) {
|
|
default = cfg.defaultQuota;
|
|
};
|
|
history = mkIf (! isNull cfg.fileRevisionHistoryDays) {
|
|
keep_days = cfg.fileRevisionHistoryDays;
|
|
};
|
|
database = mkMerge [
|
|
{
|
|
type = cfg.db.type;
|
|
}
|
|
# while just using the cfg.db set directly might be possible and
|
|
# save lines of code, I prefer hand-picking options
|
|
(mkIf (cfg.db.type == "mysql") {
|
|
host = cfg.db.host;
|
|
port = cfg.db.port;
|
|
user = cfg.db.user;
|
|
connection_charset = "utf8";
|
|
db_name = cfg.db.dbnameSeafile;
|
|
max_connections = 100;
|
|
password = "#dbpass#";
|
|
})
|
|
];
|
|
};
|
|
|
|
systemd = {
|
|
# state directory permissions managed by systemd
|
|
tmpfiles.rules = [
|
|
"d ${cfg.storagePath} 0750 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.storagePath}/conf 0700 ${cfg.user} ${cfg.group} -"
|
|
"d ${cfg.storagePath}/pids 0710 ${cfg.user} ${cfg.group} -"
|
|
];
|
|
services.seafile-server = {
|
|
|
|
path = with pkgs; [ seafile-server.seafile-server-core ];
|
|
script = ''
|
|
./seafile-server/seafile-server-latest/bin/seafile-admin start
|
|
'';
|
|
serviceConfig = {
|
|
ExecStartPre = [
|
|
("+${pkgs.writeScript "seafile-server-preStart-privileged" ''
|
|
#!${pkgs.runtimeShell}
|
|
# stuff run as root
|
|
''}")
|
|
("${pkgs.writeShellScript "seafile-server-preStart-unprivileged" ''
|
|
# stuff run as seafile user
|
|
set -ex
|
|
|
|
# seafile.conf generation
|
|
|
|
# move config templates from nix store
|
|
${pkgs.coreutils}/bin/install ${ccnetConfigFile} ./conf/ccnet.conf
|
|
${pkgs.coreutils}/bin/install ${seafileConfigFile} ./conf/seafile.conf
|
|
${pkgs.coreutils}/bin/install ${gunicornConfigFile} ./conf/gunicorn.conf.py
|
|
${pkgs.coreutils}/bin/install ${seahubConfigFile} ./conf/seahub_settings.py
|
|
|
|
# seahub secret key
|
|
if [ ! -e .seahubSecret ]; then
|
|
${pkgs.seafile-server.pythonEnv}/bin/python ${pkgs.seafile-server}/seahub/tools/secret_key_generator.py > .seahubSecret
|
|
chmod 400 .seahubSecret
|
|
fi
|
|
SEAHUB_SECRET="$(head -n1 .seahubSecret)"
|
|
# TODO: check for special characters needing to be escaped
|
|
sed -e "s,#seckey#,$SEAHUB_SECRET,g" -i ./conf/seahub_settings.py
|
|
|
|
# replace placeholder secrets with real secret read from file
|
|
#TODO: unset -x to prevent DBPASS from being leaked in journal
|
|
${if !(isNull cfg.db.passwordFile) then ''
|
|
DBPASS="$(head -n1 ${toString cfg.db.passwordFile})"
|
|
sed -e "s,#dbpass#,$DBPASS,g" -i ./conf/seafile.conf ./conf/ccnet.conf ./conf/seahub_settings.py
|
|
''
|
|
else ""
|
|
}
|
|
|
|
|
|
# initialise db and other things needed at first run
|
|
if [ -e .initialised ]; then
|
|
#TODO: db initialisation
|
|
|
|
touch .initialised
|
|
fi
|
|
|
|
ln -nsf ${pkgs.seafile-server} seafile-server
|
|
|
|
# for determining update version mismatches
|
|
${pkgs.coreutils}/bin/install ${pkgs.seafile-server}/installed_version .
|
|
''}")
|
|
];
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
Type = "oneshot";
|
|
WorkingDirectory = cfg.storagePath;
|
|
};
|
|
enable = cfg.autorun;
|
|
wantedBy = [ "multi-user.target" ];
|
|
};
|
|
};
|
|
|
|
users.users.${cfg.user} = {
|
|
home = "${cfg.storagePath}/home";
|
|
group = cfg.group;
|
|
# don't make NixOS create the home directory as otherwise the permissions for /srv might be 0700,
|
|
# making it impossible to cd into the storagePath
|
|
createHome = false;
|
|
isNormalUser = false;
|
|
};
|
|
users.groups.${cfg.group}.members = [ cfg.user ];
|
|
|
|
# ToDo: make sure ccnet is reachable
|
|
networking.firewall.allowedTCPPorts = with cfg; if openFirewall then [ seafilePort fileserverPort ] else [];
|
|
};
|
|
}
|