2e751c0772
the conversion procedure is simple: - find all things that look like options, ie calls to either `mkOption` or `lib.mkOption` that take an attrset. remember the attrset as the option - for all options, find a `description` attribute who's value is not a call to `mdDoc` or `lib.mdDoc` - textually convert the entire value of the attribute to MD with a few simple regexes (the set from mdize-module.sh) - if the change produced a change in the manual output, discard - if the change kept the manual unchanged, add some text to the description to make sure we've actually found an option. if the manual changes this time, keep the converted description this procedure converts 80% of nixos options to markdown. around 2000 options remain to be inspected, but most of those fail the "does not change the manual output check": currently the MD conversion process does not faithfully convert docbook tags like <code> and <package>, so any option using such tags will not be converted at all.
288 lines
10 KiB
Nix
288 lines
10 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
with lib;
|
|
let
|
|
cfg = config.services.seafile;
|
|
settingsFormat = pkgs.formats.ini { };
|
|
|
|
ccnetConf = settingsFormat.generate "ccnet.conf" cfg.ccnetSettings;
|
|
|
|
seafileConf = settingsFormat.generate "seafile.conf" cfg.seafileSettings;
|
|
|
|
seahubSettings = pkgs.writeText "seahub_settings.py" ''
|
|
FILE_SERVER_ROOT = '${cfg.ccnetSettings.General.SERVICE_URL}/seafhttp'
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.db.backends.sqlite3',
|
|
'NAME': '${seahubDir}/seahub.db',
|
|
}
|
|
}
|
|
MEDIA_ROOT = '${seahubDir}/media/'
|
|
THUMBNAIL_ROOT = '${seahubDir}/thumbnail/'
|
|
|
|
with open('${seafRoot}/.seahubSecret') as f:
|
|
SECRET_KEY = f.readline().rstrip()
|
|
|
|
${cfg.seahubExtraConf}
|
|
'';
|
|
|
|
seafRoot = "/var/lib/seafile"; # hardcode it due to dynamicuser
|
|
ccnetDir = "${seafRoot}/ccnet";
|
|
dataDir = "${seafRoot}/data";
|
|
seahubDir = "${seafRoot}/seahub";
|
|
|
|
in {
|
|
|
|
###### Interface
|
|
|
|
options.services.seafile = {
|
|
enable = mkEnableOption "Seafile server";
|
|
|
|
ccnetSettings = mkOption {
|
|
type = types.submodule {
|
|
freeformType = settingsFormat.type;
|
|
|
|
options = {
|
|
General = {
|
|
SERVICE_URL = mkOption {
|
|
type = types.str;
|
|
example = "https://www.example.com";
|
|
description = lib.mdDoc ''
|
|
Seahub public URL.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = lib.mdDoc ''
|
|
Configuration for ccnet, see
|
|
<https://manual.seafile.com/config/ccnet-conf/>
|
|
for supported values.
|
|
'';
|
|
};
|
|
|
|
seafileSettings = mkOption {
|
|
type = types.submodule {
|
|
freeformType = settingsFormat.type;
|
|
|
|
options = {
|
|
fileserver = {
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 8082;
|
|
description = lib.mdDoc ''
|
|
The tcp port used by seafile fileserver.
|
|
'';
|
|
};
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
example = "0.0.0.0";
|
|
description = lib.mdDoc ''
|
|
The binding address used by seafile fileserver.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = lib.mdDoc ''
|
|
Configuration for seafile-server, see
|
|
<https://manual.seafile.com/config/seafile-conf/>
|
|
for supported values.
|
|
'';
|
|
};
|
|
|
|
workers = mkOption {
|
|
type = types.int;
|
|
default = 4;
|
|
example = 10;
|
|
description = lib.mdDoc ''
|
|
The number of gunicorn worker processes for handling requests.
|
|
'';
|
|
};
|
|
|
|
adminEmail = mkOption {
|
|
example = "john@example.com";
|
|
type = types.str;
|
|
description = lib.mdDoc ''
|
|
Seafile Seahub Admin Account Email.
|
|
'';
|
|
};
|
|
|
|
initialAdminPassword = mkOption {
|
|
example = "someStrongPass";
|
|
type = types.str;
|
|
description = lib.mdDoc ''
|
|
Seafile Seahub Admin Account initial password.
|
|
Should be change via Seahub web front-end.
|
|
'';
|
|
};
|
|
|
|
seafilePackage = mkOption {
|
|
type = types.package;
|
|
description = lib.mdDoc "Which package to use for the seafile server.";
|
|
default = pkgs.seafile-server;
|
|
defaultText = literalExpression "pkgs.seafile-server";
|
|
};
|
|
|
|
seahubExtraConf = mkOption {
|
|
default = "";
|
|
type = types.lines;
|
|
description = ''
|
|
Extra config to append to `seahub_settings.py` file.
|
|
Refer to <link xlink:href="https://manual.seafile.com/config/seahub_settings_py/" />
|
|
for all available options.
|
|
'';
|
|
};
|
|
};
|
|
|
|
###### Implementation
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
environment.etc."seafile/ccnet.conf".source = ccnetConf;
|
|
environment.etc."seafile/seafile.conf".source = seafileConf;
|
|
environment.etc."seafile/seahub_settings.py".source = seahubSettings;
|
|
|
|
systemd.targets.seafile = {
|
|
wantedBy = [ "multi-user.target" ];
|
|
description = "Seafile components";
|
|
};
|
|
|
|
systemd.services = let
|
|
securityOptions = {
|
|
ProtectHome = true;
|
|
PrivateUsers = true;
|
|
PrivateDevices = true;
|
|
ProtectClock = true;
|
|
ProtectHostname = true;
|
|
ProtectProc = "invisible";
|
|
ProtectKernelModules = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelLogs = true;
|
|
ProtectControlGroups = true;
|
|
RestrictNamespaces = true;
|
|
LockPersonality = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
MemoryDenyWriteExecute = true;
|
|
SystemCallArchitectures = "native";
|
|
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" ];
|
|
};
|
|
in {
|
|
seaf-server = {
|
|
description = "Seafile server";
|
|
partOf = [ "seafile.target" ];
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "seafile.target" ];
|
|
restartTriggers = [ ccnetConf seafileConf ];
|
|
serviceConfig = securityOptions // {
|
|
User = "seafile";
|
|
Group = "seafile";
|
|
DynamicUser = true;
|
|
StateDirectory = "seafile";
|
|
RuntimeDirectory = "seafile";
|
|
LogsDirectory = "seafile";
|
|
ConfigurationDirectory = "seafile";
|
|
ExecStart = ''
|
|
${cfg.seafilePackage}/bin/seaf-server \
|
|
--foreground \
|
|
-F /etc/seafile \
|
|
-c ${ccnetDir} \
|
|
-d ${dataDir} \
|
|
-l /var/log/seafile/server.log \
|
|
-P /run/seafile/server.pid \
|
|
-p /run/seafile
|
|
'';
|
|
};
|
|
preStart = ''
|
|
if [ ! -f "${seafRoot}/server-setup" ]; then
|
|
mkdir -p ${dataDir}/library-template
|
|
mkdir -p ${ccnetDir}/{GroupMgr,misc,OrgMgr,PeerMgr}
|
|
${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/GroupMgr/groupmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/groupmgr.sql"
|
|
${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/misc/config.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/config.sql"
|
|
${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/OrgMgr/orgmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/org.sql"
|
|
${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/PeerMgr/usermgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/user.sql"
|
|
${pkgs.sqlite}/bin/sqlite3 ${dataDir}/seafile.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/seafile.sql"
|
|
echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup
|
|
fi
|
|
# checking for upgrades and handling them
|
|
# WARNING: needs to be extended to actually handle major version migrations
|
|
installedMajor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f1)
|
|
installedMinor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f2)
|
|
pkgMajor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f1)
|
|
pkgMinor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f2)
|
|
if [ $installedMajor != $pkgMajor ] || [ $installedMinor != $pkgMinor ]; then
|
|
echo "Unsupported upgrade" >&2
|
|
exit 1
|
|
fi
|
|
'';
|
|
};
|
|
|
|
seahub = {
|
|
description = "Seafile Server Web Frontend";
|
|
wantedBy = [ "seafile.target" ];
|
|
partOf = [ "seafile.target" ];
|
|
after = [ "network.target" "seaf-server.service" ];
|
|
requires = [ "seaf-server.service" ];
|
|
restartTriggers = [ seahubSettings ];
|
|
environment = {
|
|
PYTHONPATH = "${pkgs.seahub.pythonPath}:${pkgs.seahub}/thirdpart:${pkgs.seahub}";
|
|
DJANGO_SETTINGS_MODULE = "seahub.settings";
|
|
CCNET_CONF_DIR = ccnetDir;
|
|
SEAFILE_CONF_DIR = dataDir;
|
|
SEAFILE_CENTRAL_CONF_DIR = "/etc/seafile";
|
|
SEAFILE_RPC_PIPE_PATH = "/run/seafile";
|
|
SEAHUB_LOG_DIR = "/var/log/seafile";
|
|
};
|
|
serviceConfig = securityOptions // {
|
|
User = "seafile";
|
|
Group = "seafile";
|
|
DynamicUser = true;
|
|
RuntimeDirectory = "seahub";
|
|
StateDirectory = "seafile";
|
|
LogsDirectory = "seafile";
|
|
ConfigurationDirectory = "seafile";
|
|
ExecStart = ''
|
|
${pkgs.seahub.python.pkgs.gunicorn}/bin/gunicorn seahub.wsgi:application \
|
|
--name seahub \
|
|
--workers ${toString cfg.workers} \
|
|
--log-level=info \
|
|
--preload \
|
|
--timeout=1200 \
|
|
--limit-request-line=8190 \
|
|
--bind unix:/run/seahub/gunicorn.sock
|
|
'';
|
|
};
|
|
preStart = ''
|
|
mkdir -p ${seahubDir}/media
|
|
# Link all media except avatars
|
|
for m in `find ${pkgs.seahub}/media/ -maxdepth 1 -not -name "avatars"`; do
|
|
ln -sf $m ${seahubDir}/media/
|
|
done
|
|
if [ ! -e "${seafRoot}/.seahubSecret" ]; then
|
|
${pkgs.seahub.python}/bin/python ${pkgs.seahub}/tools/secret_key_generator.py > ${seafRoot}/.seahubSecret
|
|
chmod 400 ${seafRoot}/.seahubSecret
|
|
fi
|
|
if [ ! -f "${seafRoot}/seahub-setup" ]; then
|
|
# avatars directory should be writable
|
|
install -D -t ${seahubDir}/media/avatars/ ${pkgs.seahub}/media/avatars/default.png
|
|
install -D -t ${seahubDir}/media/avatars/groups ${pkgs.seahub}/media/avatars/groups/default.png
|
|
# init database
|
|
${pkgs.seahub}/manage.py migrate
|
|
# create admin account
|
|
${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."'
|
|
echo "${pkgs.seahub.version}-sqlite" > "${seafRoot}/seahub-setup"
|
|
fi
|
|
if [ $(cat "${seafRoot}/seahub-setup" | cut -d"-" -f1) != "${pkgs.seahub.version}" ]; then
|
|
# update database
|
|
${pkgs.seahub}/manage.py migrate
|
|
echo "${pkgs.seahub.version}-sqlite" > "${seafRoot}/seahub-setup"
|
|
fi
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
}
|