1 { config, lib, pkgs, ... }:
4 cfg = config.services.seafile;
5 settingsFormat = pkgs.formats.ini { };
7 ccnetConf = settingsFormat.generate "ccnet.conf" cfg.ccnetSettings;
9 seafileConf = settingsFormat.generate "seafile.conf" cfg.seafileSettings;
11 seahubSettings = pkgs.writeText "seahub_settings.py" ''
12 FILE_SERVER_ROOT = '${cfg.ccnetSettings.General.SERVICE_URL}/seafhttp'
15 'ENGINE': 'django.db.backends.sqlite3',
16 'NAME': '${seahubDir}/seahub.db',
19 MEDIA_ROOT = '${seahubDir}/media/'
20 THUMBNAIL_ROOT = '${seahubDir}/thumbnail/'
22 SERVICE_URL = '${cfg.ccnetSettings.General.SERVICE_URL}'
24 with open('${seafRoot}/.seahubSecret') as f:
25 SECRET_KEY = f.readline().rstrip()
27 ${cfg.seahubExtraConf}
30 seafRoot = "/var/lib/seafile"; # hardcode it due to dynamicuser
31 ccnetDir = "${seafRoot}/ccnet";
32 dataDir = "${seafRoot}/data";
33 seahubDir = "${seafRoot}/seahub";
39 options.services.seafile = {
40 enable = mkEnableOption (lib.mdDoc "Seafile server");
42 ccnetSettings = mkOption {
43 type = types.submodule {
44 freeformType = settingsFormat.type;
48 SERVICE_URL = mkOption {
50 example = "https://www.example.com";
51 description = lib.mdDoc ''
59 description = lib.mdDoc ''
60 Configuration for ccnet, see
61 <https://manual.seafile.com/config/ccnet-conf/>
66 seafileSettings = mkOption {
67 type = types.submodule {
68 freeformType = settingsFormat.type;
75 description = lib.mdDoc ''
76 The tcp port used by seafile fileserver.
81 default = "127.0.0.1";
83 description = lib.mdDoc ''
84 The binding address used by seafile fileserver.
91 description = lib.mdDoc ''
92 Configuration for seafile-server, see
93 <https://manual.seafile.com/config/seafile-conf/>
102 description = lib.mdDoc ''
103 The number of gunicorn worker processes for handling requests.
107 adminEmail = mkOption {
108 example = "john@example.com";
110 description = lib.mdDoc ''
111 Seafile Seahub Admin Account Email.
115 initialAdminPassword = mkOption {
116 example = "someStrongPass";
118 description = lib.mdDoc ''
119 Seafile Seahub Admin Account initial password.
120 Should be change via Seahub web front-end.
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";
131 seahubExtraConf = mkOption {
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.
142 ###### Implementation
144 config = mkIf cfg.enable {
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;
150 systemd.targets.seafile = {
151 wantedBy = [ "multi-user.target" ];
152 description = "Seafile components";
155 systemd.services = let
159 PrivateDevices = 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" ];
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 // {
187 StateDirectory = "seafile";
188 RuntimeDirectory = "seafile";
189 LogsDirectory = "seafile";
190 ConfigurationDirectory = "seafile";
192 ${cfg.seafilePackage}/bin/seaf-server \
197 -l /var/log/seafile/server.log \
198 -P /run/seafile/server.pid \
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
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)
220 if [[ $installedMajor == $pkgMajor && $installedMinor == $pkgMinor ]]; then
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
227 echo "Unsupported upgrade" >&2
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 ];
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";
249 serviceConfig = securityOptions // {
253 RuntimeDirectory = "seahub";
254 StateDirectory = "seafile";
255 LogsDirectory = "seafile";
256 ConfigurationDirectory = "seafile";
258 ${pkgs.seahub.python.pkgs.gunicorn}/bin/gunicorn seahub.wsgi:application \
260 --workers ${toString cfg.workers} \
264 --limit-request-line=8190 \
265 --bind unix:/run/seahub/gunicorn.sock
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/
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
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
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"
288 if [ $(cat "${seafRoot}/seahub-setup" | cut -d"-" -f1) != "${pkgs.seahub.version}" ]; then
290 ${pkgs.seahub}/manage.py migrate
291 echo "${pkgs.seahub.version}-sqlite" > "${seafRoot}/seahub-setup"