at 23.11-pre 11 kB view raw
1{ config, lib, pkgs, ... }: 2with lib; 3let 4 cfg = config.services.seafile; 5 settingsFormat = pkgs.formats.ini { }; 6 7 ccnetConf = settingsFormat.generate "ccnet.conf" cfg.ccnetSettings; 8 9 seafileConf = settingsFormat.generate "seafile.conf" cfg.seafileSettings; 10 11 seahubSettings = pkgs.writeText "seahub_settings.py" '' 12 FILE_SERVER_ROOT = '${cfg.ccnetSettings.General.SERVICE_URL}/seafhttp' 13 DATABASES = { 14 'default': { 15 'ENGINE': 'django.db.backends.sqlite3', 16 'NAME': '${seahubDir}/seahub.db', 17 } 18 } 19 MEDIA_ROOT = '${seahubDir}/media/' 20 THUMBNAIL_ROOT = '${seahubDir}/thumbnail/' 21 22 SERVICE_URL = '${cfg.ccnetSettings.General.SERVICE_URL}' 23 24 with open('${seafRoot}/.seahubSecret') as f: 25 SECRET_KEY = f.readline().rstrip() 26 27 ${cfg.seahubExtraConf} 28 ''; 29 30 seafRoot = "/var/lib/seafile"; # hardcode it due to dynamicuser 31 ccnetDir = "${seafRoot}/ccnet"; 32 dataDir = "${seafRoot}/data"; 33 seahubDir = "${seafRoot}/seahub"; 34 35in { 36 37 ###### Interface 38 39 options.services.seafile = { 40 enable = mkEnableOption (lib.mdDoc "Seafile server"); 41 42 ccnetSettings = mkOption { 43 type = types.submodule { 44 freeformType = settingsFormat.type; 45 46 options = { 47 General = { 48 SERVICE_URL = mkOption { 49 type = types.str; 50 example = "https://www.example.com"; 51 description = lib.mdDoc '' 52 Seahub public URL. 53 ''; 54 }; 55 }; 56 }; 57 }; 58 default = { }; 59 description = lib.mdDoc '' 60 Configuration for ccnet, see 61 <https://manual.seafile.com/config/ccnet-conf/> 62 for supported values. 63 ''; 64 }; 65 66 seafileSettings = mkOption { 67 type = types.submodule { 68 freeformType = settingsFormat.type; 69 70 options = { 71 fileserver = { 72 port = mkOption { 73 type = types.port; 74 default = 8082; 75 description = lib.mdDoc '' 76 The tcp port used by seafile fileserver. 77 ''; 78 }; 79 host = mkOption { 80 type = types.str; 81 default = "127.0.0.1"; 82 example = "0.0.0.0"; 83 description = lib.mdDoc '' 84 The binding address used by seafile fileserver. 85 ''; 86 }; 87 }; 88 }; 89 }; 90 default = { }; 91 description = lib.mdDoc '' 92 Configuration for seafile-server, see 93 <https://manual.seafile.com/config/seafile-conf/> 94 for supported values. 95 ''; 96 }; 97 98 workers = mkOption { 99 type = types.int; 100 default = 4; 101 example = 10; 102 description = lib.mdDoc '' 103 The number of gunicorn worker processes for handling requests. 104 ''; 105 }; 106 107 adminEmail = mkOption { 108 example = "john@example.com"; 109 type = types.str; 110 description = lib.mdDoc '' 111 Seafile Seahub Admin Account Email. 112 ''; 113 }; 114 115 initialAdminPassword = mkOption { 116 example = "someStrongPass"; 117 type = types.str; 118 description = lib.mdDoc '' 119 Seafile Seahub Admin Account initial password. 120 Should be change via Seahub web front-end. 121 ''; 122 }; 123 124 seafilePackage = mkOption { 125 type = types.package; 126 description = lib.mdDoc "Which package to use for the seafile server."; 127 default = pkgs.seafile-server; 128 defaultText = literalExpression "pkgs.seafile-server"; 129 }; 130 131 seahubExtraConf = mkOption { 132 default = ""; 133 type = types.lines; 134 description = lib.mdDoc '' 135 Extra config to append to `seahub_settings.py` file. 136 Refer to <https://manual.seafile.com/config/seahub_settings_py/> 137 for all available options. 138 ''; 139 }; 140 }; 141 142 ###### Implementation 143 144 config = mkIf cfg.enable { 145 146 environment.etc."seafile/ccnet.conf".source = ccnetConf; 147 environment.etc."seafile/seafile.conf".source = seafileConf; 148 environment.etc."seafile/seahub_settings.py".source = seahubSettings; 149 150 systemd.targets.seafile = { 151 wantedBy = [ "multi-user.target" ]; 152 description = "Seafile components"; 153 }; 154 155 systemd.services = let 156 securityOptions = { 157 ProtectHome = true; 158 PrivateUsers = true; 159 PrivateDevices = true; 160 ProtectClock = true; 161 ProtectHostname = true; 162 ProtectProc = "invisible"; 163 ProtectKernelModules = true; 164 ProtectKernelTunables = true; 165 ProtectKernelLogs = true; 166 ProtectControlGroups = true; 167 RestrictNamespaces = true; 168 LockPersonality = true; 169 RestrictRealtime = true; 170 RestrictSUIDSGID = true; 171 MemoryDenyWriteExecute = true; 172 SystemCallArchitectures = "native"; 173 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" ]; 174 }; 175 in { 176 seaf-server = { 177 description = "Seafile server"; 178 partOf = [ "seafile.target" ]; 179 after = [ "network.target" ]; 180 wantedBy = [ "seafile.target" ]; 181 restartTriggers = [ ccnetConf seafileConf ]; 182 path = [ pkgs.sqlite ]; 183 serviceConfig = securityOptions // { 184 User = "seafile"; 185 Group = "seafile"; 186 DynamicUser = true; 187 StateDirectory = "seafile"; 188 RuntimeDirectory = "seafile"; 189 LogsDirectory = "seafile"; 190 ConfigurationDirectory = "seafile"; 191 ExecStart = '' 192 ${cfg.seafilePackage}/bin/seaf-server \ 193 --foreground \ 194 -F /etc/seafile \ 195 -c ${ccnetDir} \ 196 -d ${dataDir} \ 197 -l /var/log/seafile/server.log \ 198 -P /run/seafile/server.pid \ 199 -p /run/seafile 200 ''; 201 }; 202 preStart = '' 203 if [ ! -f "${seafRoot}/server-setup" ]; then 204 mkdir -p ${dataDir}/library-template 205 mkdir -p ${ccnetDir}/{GroupMgr,misc,OrgMgr,PeerMgr} 206 sqlite3 ${ccnetDir}/GroupMgr/groupmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/groupmgr.sql" 207 sqlite3 ${ccnetDir}/misc/config.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/config.sql" 208 sqlite3 ${ccnetDir}/OrgMgr/orgmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/org.sql" 209 sqlite3 ${ccnetDir}/PeerMgr/usermgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/user.sql" 210 sqlite3 ${dataDir}/seafile.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/seafile.sql" 211 echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup 212 fi 213 # checking for upgrades and handling them 214 # WARNING: needs to be extended to actually handle major version migrations 215 installedMajor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f1) 216 installedMinor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f2) 217 pkgMajor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f1) 218 pkgMinor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f2) 219 220 if [[ $installedMajor == $pkgMajor && $installedMinor == $pkgMinor ]]; then 221 : 222 elif [[ $installedMajor == 8 && $installedMinor == 0 && $pkgMajor == 9 && $pkgMinor == 0 ]]; then 223 # Upgrade from 8.0 to 9.0 224 sqlite3 ${dataDir}/seafile.db ".read ${pkgs.seahub}/scripts/upgrade/sql/9.0.0/sqlite3/seafile.sql" 225 echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup 226 else 227 echo "Unsupported upgrade" >&2 228 exit 1 229 fi 230 ''; 231 }; 232 233 seahub = { 234 description = "Seafile Server Web Frontend"; 235 wantedBy = [ "seafile.target" ]; 236 partOf = [ "seafile.target" ]; 237 after = [ "network.target" "seaf-server.service" ]; 238 requires = [ "seaf-server.service" ]; 239 restartTriggers = [ seahubSettings ]; 240 environment = { 241 PYTHONPATH = "${pkgs.seahub.pythonPath}:${pkgs.seahub}/thirdpart:${pkgs.seahub}"; 242 DJANGO_SETTINGS_MODULE = "seahub.settings"; 243 CCNET_CONF_DIR = ccnetDir; 244 SEAFILE_CONF_DIR = dataDir; 245 SEAFILE_CENTRAL_CONF_DIR = "/etc/seafile"; 246 SEAFILE_RPC_PIPE_PATH = "/run/seafile"; 247 SEAHUB_LOG_DIR = "/var/log/seafile"; 248 }; 249 serviceConfig = securityOptions // { 250 User = "seafile"; 251 Group = "seafile"; 252 DynamicUser = true; 253 RuntimeDirectory = "seahub"; 254 StateDirectory = "seafile"; 255 LogsDirectory = "seafile"; 256 ConfigurationDirectory = "seafile"; 257 ExecStart = '' 258 ${pkgs.seahub.python.pkgs.gunicorn}/bin/gunicorn seahub.wsgi:application \ 259 --name seahub \ 260 --workers ${toString cfg.workers} \ 261 --log-level=info \ 262 --preload \ 263 --timeout=1200 \ 264 --limit-request-line=8190 \ 265 --bind unix:/run/seahub/gunicorn.sock 266 ''; 267 }; 268 preStart = '' 269 mkdir -p ${seahubDir}/media 270 # Link all media except avatars 271 for m in `find ${pkgs.seahub}/media/ -maxdepth 1 -not -name "avatars"`; do 272 ln -sf $m ${seahubDir}/media/ 273 done 274 if [ ! -e "${seafRoot}/.seahubSecret" ]; then 275 ${pkgs.seahub.python}/bin/python ${pkgs.seahub}/tools/secret_key_generator.py > ${seafRoot}/.seahubSecret 276 chmod 400 ${seafRoot}/.seahubSecret 277 fi 278 if [ ! -f "${seafRoot}/seahub-setup" ]; then 279 # avatars directory should be writable 280 install -D -t ${seahubDir}/media/avatars/ ${pkgs.seahub}/media/avatars/default.png 281 install -D -t ${seahubDir}/media/avatars/groups ${pkgs.seahub}/media/avatars/groups/default.png 282 # init database 283 ${pkgs.seahub}/manage.py migrate 284 # create admin account 285 ${pkgs.expect}/bin/expect -c 'spawn ${pkgs.seahub}/manage.py createsuperuser --email=${cfg.adminEmail}; expect "Password: "; send "${cfg.initialAdminPassword}\r"; expect "Password (again): "; send "${cfg.initialAdminPassword}\r"; expect "Superuser created successfully."' 286 echo "${pkgs.seahub.version}-sqlite" > "${seafRoot}/seahub-setup" 287 fi 288 if [ $(cat "${seafRoot}/seahub-setup" | cut -d"-" -f1) != "${pkgs.seahub.version}" ]; then 289 # update database 290 ${pkgs.seahub}/manage.py migrate 291 echo "${pkgs.seahub.version}-sqlite" > "${seafRoot}/seahub-setup" 292 fi 293 ''; 294 }; 295 }; 296 }; 297}