Compare commits

...

14 commits

Author SHA1 Message Date
Trolli Schmittlauch 8ecde56247 fix ownership and mode of copied-over (config) files
These files need to be owned by the copying user instead of the Nix store permissions.
install takes care of that.
2021-02-07 19:59:57 +01:00
Trolli Schmittlauch e6fec8642a add a mysql server to test vm 2021-01-31 23:46:56 +01:00
Trolli Schmittlauch cb44156519 generate seafile_settings.py 2021-01-31 01:18:47 +01:00
Trolli Schmittlauch d3f1c04e72 generate gunicorn.conf.py 2021-01-30 23:09:53 +01:00
Trolli Schmittlauch ce44d19168 make test vm use mysql db 2021-01-30 00:59:25 +01:00
Trolli Schmittlauch 3429d2ea63 generate ccnet.conf 2021-01-30 00:49:58 +01:00
Trolli Schmittlauch 1f4e3b5c7f seafile-server: 8.0.2 -> 8.0.3 2021-01-27 00:45:57 +01:00
Trolli Schmittlauch 8b73ec0585 expose python environment throug sub-attribute, for accessing python interpreter to run admin scripts 2021-01-24 23:13:23 +01:00
Trolli Schmittlauch 9dde0d319f seafile-server: 8.0.1 -> 8.0.2 2021-01-21 00:30:39 +01:00
Trolli Schmittlauch cb16f19b58 try initialising mysql db 2020-12-22 12:17:03 +01:00
Trolli Schmittlauch bc63307993 create required dirctory hierarchy and copy over required scripts 2020-12-21 12:24:43 +01:00
Trolli Schmittlauch 65f337bd15 seafile-server: 8.0.0 -> 8.0.1 2020-12-09 18:54:08 +01:00
Trolli Schmittlauch 86a300eaec change seafile.conf to be generated by from an attribute set 2020-11-30 02:21:10 +01:00
Trolli Schmittlauch 00458bf734 make test vms buildable by removing all mentions of ccnet-server 2020-11-27 22:26:09 +01:00
4 changed files with 234 additions and 96 deletions

View file

@ -2,6 +2,40 @@
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
{
@ -12,6 +46,20 @@ in
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;
@ -28,38 +76,40 @@ in
default = "seafile";
description = "Database user name. Not required for sqlite.";
};
dbname = mkOption {
dbnameSeafile = mkOption {
type = types.nullOr types.str;
default = "seafile";
description = "Database name. Not required for sqlite.";
description = "Database name for Seafile server. Not required for sqlite.";
};
password = mkOption {
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 = ''
Database password. Use <literal>passwordFile</literal> to avoid this
being world-readable in the <literal>/nix/store</literal>.
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.
'';
};
host = mkOption {
type = types.nullOr types.str;
default = "localhost";
description = "Database host.";
};
dbport = mkOption {
type = with types; nullOr (either int str);
default = 3306;
description = "Database port. Not required for sqlite.";
};
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;
@ -73,11 +123,11 @@ in
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";
# };
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;
@ -172,16 +222,68 @@ in
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 0750 ${cfg.user} ${cfg.group} -"
"d ${cfg.storagePath}/home 0710 ${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.ccnet-server seafile-server.seafile-server-core ];
path = with pkgs; [ seafile-server.seafile-server-core ];
script = ''
./seafile-server/seafile-server-latest/bin/seafile-admin start
'';
@ -193,66 +295,46 @@ in
''}")
("${pkgs.writeShellScript "seafile-server-preStart-unprivileged" ''
# stuff run as seafile user
# ccnet-init must only be run once per installation, as it also generates stateful key and ID
# solution: invoke it once, use result as template
if [ ! -e ./ccnet/mykey.peer ]; then
${pkgs.seafile-server.ccnet-server}/bin/ccnet-init -c ./ccnet -H 'TEMPLATEHOST'
mv ./ccnet/ccnet.conf{,.template}
fi
# generate actual ccnet config file
echo "[General]" > ./conf/ccnet.conf
grep "^ID =" ./ccnet/ccnet.conf.template >> ./conf/ccnet.conf
# outside URL
SERVICE_URL = http${if cfg.enableTLS then "s" else ""}://${cfg.domainName}:${toString cfg.externalPort}
set -ex
# seafile.conf generation
echo '[library_trash]
expire_days ${toString cfg.trashExpirationTime}
# 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
[fileserver]
host = ${cfg.fileserverBindAddress}
port = ${toString cfg.fileserverPort}
worker_threads = ${toString cfg.fileserverWorkers}
max_indexing_threads = ${toString cfg.fileserverIndexers}
fixed_block_size = ${toString cfg.fileserverIndexers}' > ./conf/seafile.conf
# 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
if [ ${toString (! isNull cfg.defaultQuota)} ]; then
echo '[quota]' >> ./conf/seafile.conf
echo 'default = ${toString cfg.defaultQuota}' >> ./conf/seafile.conf
# 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
if [ ${toString (! isNull cfg.fileRevisionHistoryDays)} ]; then
echo '[history]' >> ./conf/seafile.conf
echo 'keep_days = ${toString cfg.defaultQuota}' >> ./conf/seafile.conf
fi
# seafile database settings
if [ ${cfg.db.type} = "mysql" ]; then
echo '[database]
type = mysql
host = ${cfg.db.host}
port = ${toString cfg.db.dbport}
user = ${cfg.db.user}
connection_charset = utf8
db_name = ${cfg.db.dbname}
max_connections = 100' >> ./conf/seafile.conf
if [ ${toString (! isNull cfg.db.password)}; then
echo 'password = ${toString cfg.db.password}' >> ./conf/seafile.conf
else
echo "password = $(cat ${toString cfg.db.passwordFile})" >> ./conf/seafile.conf
fi
else
echo '[database]
type = sqlite' >> ./conf/seafile.conf
fi
ln -s ${pkgs.seafile-server} seafile-server
./seafile-server/seafile-server-latest/bin/seafile-admin setup
ln -nsf ${pkgs.seafile-server} seafile-server
# for determining update version mismatches
${pkgs.coreutils}/bin/install ${pkgs.seafile-server}/installed_version .
''}")
];
User = cfg.user;

View file

@ -15,7 +15,7 @@ import (<nixos-unstable/nixos/tests/make-test-python.nix>) {
(import ./default.nix)
];
i18n.consoleKeyMap = "de";
console.keyMap = "de";
users.mutableUsers = false;
users.users.test = {
isNormalUser = true;

View file

@ -28,7 +28,7 @@
, python3Packages
}:
let
version = "8.0.0";
version = "8.0.3";
python = python3;
pythonPackages = python3Packages;
django = pythonPackages.django;
@ -49,6 +49,8 @@ let
pycryptodome
]
++ map (p: p.override { inherit django; }) djangoModules; # build django modules with required version
# defining them here to be able to expose them in a python environment as well
pythonEnvDeps = seahubPythonDependencies ++ [ libsearpc ];
seafile-server-core = stdenv.mkDerivation rec {
name = "seafile-server-core";
inherit version;
@ -56,7 +58,7 @@ let
owner = "haiwen";
repo = "seafile-server";
rev = "v${version}-server";
sha256 = "0pd1zjsw6lkpxd54ln0dz5r9zx9585nib10kvpl1vgzp61g4d223";
sha256 = "1wmbx4smf342b5pars1zm9af2i0yaq7kjj7ry0gr337gdpa4qn3b";
};
# patch to work with latest, non-vulnerable libevhtp
patches = [
@ -66,7 +68,14 @@ let
# `which` is called directly from python during buildPhase, so we need the binary
nativeBuildInputs = [ autoconf automake libtool pkgconfig vala autoreconfHook which pythonPackages.wrapPython ];
buildInputs = [ sqlite glib python libuuid openssl oniguruma fuse libarchive libevent libevhtp ];
propagatedBuildInputs = [ libsearpc ] ++ seahubPythonDependencies;
propagatedBuildInputs = pythonEnvDeps;
# copy manual to required location
postInstall = ''
mkdir $out/doc
cp ${src}/doc/*.doc $out/doc/
'';
# prevent doc directory from being moved to share in fixupPhase
forceShare = [ "man" "info" ];
postFixup = ''
buildPythonPath $propagatedBuildInputs
wrapPythonProgramsIn "$out/bin" "$out $pythonPath"
@ -85,7 +94,7 @@ let
owner = "haiwen";
repo = "seahub";
rev = "v${version}-server";
sha256 = "0j7g43j7w1zb00pg4aaacdv5ycva3qf561hj9pbwh4709mbiykip";
sha256 = "0vfkiavsmpjm6wjr5rcnmnpnb3rxr3svwk8fsh5c76zg87ckdz4d";
};
phases = [ "unpackPhase" "installPhase" "fixupPhase" "distPhase" ];
buildInputs = [ python pythonPackages.wrapPython ];
@ -120,19 +129,40 @@ stdenv.mkDerivation {
name = "seafile-server";
inherit version;
nativeBuildInputs = [ python3Packages.wrapPython ];
buildInputs = [ seahub seafile-server-core libsearpc ]
++ lib.optional withMysql libmysqlclient;
phases = [ "installPhase" "fixupPhase" "distPhase" ];
# todo: create data directory in /srv in activation script
# create required directory structure
# Which files need to be copied is specified in the function `copy_scripts_and_libs`
# of ${seafile-server-core.src}/scripts/build/build-server.py
# The install script below has been hand crafted from that list of files and needs to be updated on new releases.
installPhase = ''
mkdir "$out"
cd "$out"
ln -s ${seahub} seahub
ln -s ${seafile-server-core} seafile-server-latest
ln -s ${seafile-server-core} seafile-server
# copy general scripts
cp ${seafile-server-core.src}/scripts/{setup-seafile.sh,setup-seafile-mysql.sh,setup-seafile-mysql.py,seafile.sh,seahub.sh,reset-admin.sh,seaf-fuse.sh,check_init_admin.py,seaf-gc.sh,seaf-fsck.sh} .
# copy update scripts (and their sql)
cp -r ${seafile-server-core.src}/scripts/upgrade .
cp -r ${seafile-server-core.src}/scripts/sql .
# copy_user_manual is already done in the postInstall hook of seafile-server-core
# python admin scripts need to be made executable and patched with python path
chmod ugo+x *.py
buildPythonPath $propagatedBuildInputs
wrapPythonProgramsIn "$out/*.py" "$out $pythonPath"
echo -n "${version}" > installed_version
'';
meta = with lib; {
maintainers = with maintainers; [ schmittlauch ];
license = licenses.free; # components with different free software licenses are combined
};
inherit seafile-server-core seahub;# for using the path in the NixOS module
pythonEnv = python3.buildEnv.override {
extraLibs = pythonEnvDeps;
ignoreCollisions = true;
};
}

View file

@ -10,13 +10,13 @@
(import ./default.nix)
];
i18n.consoleKeyMap = "de";
console.keyMap = "de";
users.mutableUsers = false;
users.users.test = {
isNormalUser = true;
extraGroups = [ "wheel" ];
#hashedPassword = "$6$SZCzE/xB$Hr9sfsJ7xAcBCoptG39cxxQk8RZfldDjjGpSngOvn9Ufex5dHBEbdncXRZnfrGATsGcYPvLi7m4wIu.f8tY9B.";
password = "";
password = "test";
home = "/home/test";
createHome = true;
};
@ -25,7 +25,33 @@
services.seafile-server = {
enable = true;
#autorun = false;
domainName = "localhost";
domainName = "seaf.local";
db = {
type = "mysql";
passwordFile = toString (pkgs.writeText "testPW" "test");
};
};
# db backend
services.mysql =
{
enable = true;
package = pkgs.mariadb;
ensureDatabases = [ "ccnet" "seafile" "seahub" ];
ensureUsers = [
rec {
name = config.services.seafile-server.db.user;
ensurePermissions = {
"ccnet.*" = "ALL PRIVILEGES";
"seafile.*" = "ALL PRIVILEGES";
"seahub.*" = "ALL PRIVILEGES";
};
}
];
# set a password for the seafile user
initialScript = pkgs.writeText "mariadb-init.sql" ''
CREATE USER ${config.services.seafile-server.db.user}@localhost IDENTIFIED VIA mysql_native_password USING PASSWORD("test");
'';
};
}