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