Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seafile WebDAV extension (dependencies, package, and integration to seafile service) #171878

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions maintainers/maintainer-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4392,6 +4392,12 @@
githubId = 74379;
name = "Florian Pester";
};
flyx = {
email = "[email protected]";
github = "flyx";
githubId = 791710;
name = "Felix Krause";
};
fmthoma = {
email = "[email protected]";
github = "fmthoma";
Expand Down
85 changes: 85 additions & 0 deletions nixos/modules/services/networking/seafile.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ let
${cfg.seahubExtraConf}
'';

# Most official settings are irrelevant since seafdav is run via Gunicorn
# as WSGI application. Thus Gunicorn params are given inline.
# Only the share_name is actually consumed by seafdav itself. `enabled`
# is set simply to avoid confusion; seafdav doesn't act upon it.
seafdavConf = settingsFormat.generate "seafdav.conf" {
WEBDAV = {
enabled = cfg.webdav.enable;
share_name = cfg.webdav.shareName;
};
};

seafRoot = "/var/lib/seafile"; # hardcode it due to dynamicuser
ccnetDir = "${seafRoot}/ccnet";
dataDir = "${seafRoot}/data";
Expand Down Expand Up @@ -137,6 +148,42 @@ in {
for all available options.
'';
};

webdav = mkOption {
type = types.submodule {
options = {
enable = mkEnableOption "Seafile WebDAV Extension";
shareName = mkOption {
type = types.str;
default = "/";
example = "/webdav";
description = ''
Public path in URI under which the WebDAV server can be reached.
'';
};
workers = mkOption {
type = types.int;
default = 5;
example = 10;
description = ''
The number of gunicorn worker processes for handling requests.
'';
};
timeout = mkOption {
type = types.int;
default = 1200;
example = 920;
description = ''
Timeout for gunicorn in seconds. Affects large file uploads.
'';
};
};
};
default = { };
description = ''
Optional WebDAV extension.
'';
};
};

###### Implementation
Expand All @@ -146,6 +193,7 @@ in {
environment.etc."seafile/ccnet.conf".source = ccnetConf;
environment.etc."seafile/seafile.conf".source = seafileConf;
environment.etc."seafile/seahub_settings.py".source = seahubSettings;
environment.etc."seafile/seafdav.conf".source = seafdavConf;

systemd.targets.seafile = {
wantedBy = [ "multi-user.target" ];
Expand Down Expand Up @@ -292,6 +340,43 @@ in {
fi
'';
};

seafdav = mkIf cfg.webdav.enable {
description = "Seafile WebDAV Extension";
wantedBy = [ "seafile.target" ];
partOf = [ "seafile.target" ];
after = [ "network.target" "seaf-server.service" ];
requires = [ "seaf-server.service" ];
restartTriggers = [ seahubSettings ];
environment = {
# needs /etc/seafile to access seahub_settings.py
PYTHONPATH = "${pkgs.seafdav.pythonPath}:${pkgs.seafdav}:/etc/seafile";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need /etc/seafile in pythonpath?

Suggested change
PYTHONPATH = "${pkgs.seafdav.pythonPath}:${pkgs.seafdav}:/etc/seafile";
PYTHONPATH = "${pkgs.seafdav.pythonPath}:${pkgs.seafdav}";

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required for loading the seahub_settings.py which is processed by SeafDAV. From what I understand, this is handled via django settings for actual seahub, but that is not used in SeafDAV so I'm unsure whether there's a better way to do this.

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";
SEAFDAV_CONF = "/etc/seafile/seafdav.conf";
};
serviceConfig = securityOptions // {
User = "seafile";
Group = "seafile";
DynamicUser = true;
RuntimeDirectory = "seafdav";
StateDirectory = "seafile";
LogsDirectory = "seafile";
ConfigurationDirectory = "seafile";
ExecStart = ''
${pkgs.seafdav.python.pkgs.gunicorn}/bin/gunicorn seafdav:application \
--name seafdav \
--workers ${toString cfg.webdav.workers} \
--log-level=info \
--preload \
--timeout=${toString cfg.webdav.timeout} \
--bind unix:/run/seafdav/gunicorn.sock
'';
};
};
};
};
}
15 changes: 15 additions & 0 deletions nixos/tests/seafile.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import ./make-test-python.nix ({ pkgs, ... }:
ccnetSettings.General.SERVICE_URL = "http://server";
adminEmail = "[email protected]";
initialAdminPassword = "seafile_password";
webdav = {
enable = true;
shareName = "/webdav";
};
};
services.nginx = {
enable = true;
Expand All @@ -33,6 +37,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
proxy_http_version 1.1;
'';
};
locations."/webdav".proxyPass = "http://unix:/run/seafdav/gunicorn.sock:/webdav";
};
};
networking.firewall = { allowedTCPPorts = [ 80 ]; };
Expand Down Expand Up @@ -117,5 +122,15 @@ import ./make-test-python.nix ({ pkgs, ... }:
client2.succeed("ls -la test01 >&2")

client2.succeed('[ `cat test01/first_file` = "bla" ]')

with subtest("start webdav-server"):
server.wait_for_unit("seafdav.service")
server.wait_for_file("/run/seafdav/gunicorn.sock")

with subtest("webdav download file"):
client2.succeed(
"curl http://server/webdav/test01/first_file -u admin\@example.com -p seafile_password -o first_file.webdav"
)
client2.succeed('[ `cat first_file.webdav` = "bla" ]')
'';
})
54 changes: 54 additions & 0 deletions pkgs/applications/networking/seafdav/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{ lib
, fetchFromGitHub
, python3
, makeWrapper
}:
python3.pkgs.buildPythonApplication rec {
pname = "seafdav";
version = "9.0.7";

src = fetchFromGitHub {
owner = "haiwen";
repo = "seafdav";
rev = "v${version}-server";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream has a habit of pushing over existing tags, on other seafile component we end up using commit hashes.
Let's hope this is not the case here 🤞

sha256 = "0jdspxz9cakpx0vipqsqf02llz3w02fjid3kff0kclz9apmyvx55";
};

dontBuild = true; # $out will contain Python sources, no build necessary

doCheck = false; # disabled because it requires a ccnet environment

propagatedBuildInputs = with python3.pkgs; [
defusedxml
future
jinja2
json5
pysearpc
python-pam
pyyaml
six
lxml
seaserv
seafobj
sqlalchemy
gunicorn
];

installPhase = ''
cp -dr --no-preserve='ownership' . $out/
cp ${./seafdav.py} $out/seafdav.py
'';

passthru = {
python = python3;
pythonPath = python3.pkgs.makePythonPath propagatedBuildInputs;
};

meta = with lib; {
description = "Seafile WebDAV Server";
homepage = "https://github.com/haiwen/seafdav";
license = licenses.mit;
maintainers = with maintainers; [ flyx ];
platforms = platforms.linux;
};
}
24 changes: 24 additions & 0 deletions pkgs/applications/networking/seafdav/seafdav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This is a WSGI application that can be run via GUnicorn.
#
# It is unclear why Seafile does not provide this script with seafdav,
# when a similar script does exist for seahub. The existing server_cli
# script is not as flexible since it is a Python script that launches
# GUnicorn (or other WSGI servers) instead of being a consumable
# WSGI application. For example, server_cli isn't able to bind to a
# unix socket.

from wsgidav.default_conf import DEFAULT_CONFIG
from wsgidav.wsgidav_app import WsgiDAVApp
from wsgidav.server.server_cli import _loadSeafileSettings

import copy, logging

config = copy.deepcopy(DEFAULT_CONFIG)

logging.info("share_name = %s", config.get("share_name"))

# gets us data from seafdav.conf,
# and sets up the general Seafile configuration.
_loadSeafileSettings(config)

application = WsgiDAVApp(config)
46 changes: 46 additions & 0 deletions pkgs/development/python-modules/seafobj/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{ lib
, fetchFromGitHub
, buildPythonPackage
, boto3
, coverage
, future
, mock
, nose
, python
}:
buildPythonPackage rec {
pname = "seafobj";
version = "9.0.7";

src = fetchFromGitHub {
owner = "haiwen";
repo = "seafobj";
rev = "v${version}-server";
sha256 = "04vn75x4yx3lcvll69q4q0g4gbqlc4w7047x7hxan1zramjmdxfg";
};

dontBuild = true; # $out will contain Python sources, no build necessary

checkInputs = [ coverage mock nose boto3 ];

checkPhase = ''
runHook preCheck
PYTHONPATH=$PYTHONPATH:seafobj ${python.interpreter} run_test.py --storage fs
runHook postCheck
'';

installPhase = ''
runHook preInstall
dest="$out/${python.sitePackages}"
mkdir -p $dest
cp -dr --no-preserve='ownership' seafobj $dest/
runHook postInstall
'';

meta = with lib; {
homepage = "https://github.com/haiwen/seafobj";
description = "Python library for accessing seafile data model";
maintainers = with maintainers; [ flyx ];
license = licenses.asl20;
};
}
2 changes: 2 additions & 0 deletions pkgs/top-level/all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -30300,6 +30300,8 @@ with pkgs;
scribus_1_5 = libsForQt5.callPackage ../applications/office/scribus/default.nix { };
scribus = scribus_1_5;

seafdav = callPackage ../applications/networking/seafdav { };

seafile-client = libsForQt5.callPackage ../applications/networking/seafile-client { };

seahub = callPackage ../applications/networking/seahub { };
Expand Down
2 changes: 2 additions & 0 deletions pkgs/top-level/python-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9679,6 +9679,8 @@ in {

seabreeze = callPackage ../development/python-modules/seabreeze { };

seafobj = callPackage ../development/python-modules/seafobj { };

seaserv = toPythonModule (pkgs.seafile-server.override {
python3 = self.python;
});
Expand Down