diff --git a/mod-seafile-server.nix b/mod-seafile-server.nix index f5ae079..68697ae 100644 --- a/mod-seafile-server.nix +++ b/mod-seafile-server.nix @@ -2,40 +2,6 @@ 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 { @@ -46,20 +12,6 @@ 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; @@ -76,40 +28,38 @@ in default = "seafile"; description = "Database user name. Not required for sqlite."; }; - dbnameSeafile = mkOption { + dbname = mkOption { type = types.nullOr types.str; default = "seafile"; - description = "Database name for Seafile server. Not required for sqlite."; + description = "Database name. 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 { + password = mkOption { type = types.nullOr types.str; default = null; description = '' - The full path to a file that contains the database password. - Not required for sqlite. - ''; + Database password. Use passwordFile to avoid this + being world-readable in the /nix/store. + + 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."; + }; }; - 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; @@ -123,11 +73,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; @@ -222,68 +172,16 @@ 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 0700 ${cfg.user} ${cfg.group} -" - "d ${cfg.storagePath}/pids 0710 ${cfg.user} ${cfg.group} -" + "d ${cfg.storagePath}/conf 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.storagePath}/home 0710 ${cfg.user} ${cfg.group} -" ]; services.seafile-server = { - path = with pkgs; [ seafile-server.seafile-server-core ]; + path = with pkgs; [ seafile-server.ccnet-server seafile-server.seafile-server-core ]; script = '' ./seafile-server/seafile-server-latest/bin/seafile-admin start ''; @@ -295,46 +193,66 @@ in ''}") ("${pkgs.writeShellScript "seafile-server-preStart-unprivileged" '' # stuff run as seafile user - set -ex + + # 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} # 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 + echo '[library_trash] + expire_days ${toString cfg.trashExpirationTime} - # 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 + [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 - # 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 + if [ ${toString (! isNull cfg.defaultQuota)} ]; then + echo '[quota]' >> ./conf/seafile.conf + echo 'default = ${toString cfg.defaultQuota}' >> ./conf/seafile.conf fi - ln -nsf ${pkgs.seafile-server} seafile-server - - # for determining update version mismatches - ${pkgs.coreutils}/bin/install ${pkgs.seafile-server}/installed_version . + 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 ''}") ]; User = cfg.user; diff --git a/seafile-nixos-tests.nix b/seafile-nixos-tests.nix index ed1122c..d1e404a 100644 --- a/seafile-nixos-tests.nix +++ b/seafile-nixos-tests.nix @@ -15,7 +15,7 @@ import () { (import ./default.nix) ]; - console.keyMap = "de"; + i18n.consoleKeyMap = "de"; users.mutableUsers = false; users.users.test = { isNormalUser = true; diff --git a/seafile-server/default.nix b/seafile-server/default.nix index 2f4d32b..02868fb 100644 --- a/seafile-server/default.nix +++ b/seafile-server/default.nix @@ -28,7 +28,7 @@ , python3Packages }: let - version = "8.0.3"; + version = "8.0.0"; python = python3; pythonPackages = python3Packages; django = pythonPackages.django; @@ -49,8 +49,6 @@ 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; @@ -58,7 +56,7 @@ let owner = "haiwen"; repo = "seafile-server"; rev = "v${version}-server"; - sha256 = "1wmbx4smf342b5pars1zm9af2i0yaq7kjj7ry0gr337gdpa4qn3b"; + sha256 = "0pd1zjsw6lkpxd54ln0dz5r9zx9585nib10kvpl1vgzp61g4d223"; }; # patch to work with latest, non-vulnerable libevhtp patches = [ @@ -68,14 +66,7 @@ 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 = 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" ]; + propagatedBuildInputs = [ libsearpc ] ++ seahubPythonDependencies; postFixup = '' buildPythonPath $propagatedBuildInputs wrapPythonProgramsIn "$out/bin" "$out $pythonPath" @@ -94,7 +85,7 @@ let owner = "haiwen"; repo = "seahub"; rev = "v${version}-server"; - sha256 = "0vfkiavsmpjm6wjr5rcnmnpnb3rxr3svwk8fsh5c76zg87ckdz4d"; + sha256 = "0j7g43j7w1zb00pg4aaacdv5ycva3qf561hj9pbwh4709mbiykip"; }; phases = [ "unpackPhase" "installPhase" "fixupPhase" "distPhase" ]; buildInputs = [ python pythonPackages.wrapPython ]; @@ -129,40 +120,19 @@ stdenv.mkDerivation { name = "seafile-server"; inherit version; - nativeBuildInputs = [ python3Packages.wrapPython ]; buildInputs = [ seahub seafile-server-core libsearpc ] ++ lib.optional withMysql libmysqlclient; phases = [ "installPhase" "fixupPhase" "distPhase" ]; - # 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. + # todo: create data directory in /srv in activation script installPhase = '' mkdir "$out" cd "$out" ln -s ${seahub} seahub - 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 + ln -s ${seafile-server-core} seafile-server-latest ''; 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; - }; } diff --git a/seafile-test.nix b/seafile-test.nix index 62aeded..cf624c2 100644 --- a/seafile-test.nix +++ b/seafile-test.nix @@ -10,13 +10,13 @@ (import ./default.nix) ]; - console.keyMap = "de"; + i18n.consoleKeyMap = "de"; users.mutableUsers = false; users.users.test = { isNormalUser = true; extraGroups = [ "wheel" ]; #hashedPassword = "$6$SZCzE/xB$Hr9sfsJ7xAcBCoptG39cxxQk8RZfldDjjGpSngOvn9Ufex5dHBEbdncXRZnfrGATsGcYPvLi7m4wIu.f8tY9B."; - password = "test"; + password = ""; home = "/home/test"; createHome = true; }; @@ -25,33 +25,7 @@ services.seafile-server = { enable = true; #autorun = false; - domainName = "seaf.local"; - db = { - type = "mysql"; - passwordFile = toString (pkgs.writeText "testPW" "test"); - }; + domainName = "localhost"; }; - # 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"); - ''; - }; - }