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

filebrowser: 2.23.0 -> 2.28.0, add service module #289750

Open
wants to merge 5 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: 0 additions & 6 deletions maintainers/maintainer-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14394,12 +14394,6 @@
fingerprint = "E576 BFB2 CF6E B13D F571 33B9 E315 A758 4613 1564";
}];
};
nielsegberts = {
email = "[email protected]";
github = "nielsegberts";
githubId = 368712;
name = "Niels Egberts";
};
nigelgbanks = {
name = "Nigel Banks";
email = "[email protected]";
Expand Down
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m

- [systemd-lock-handler](https://git.sr.ht/~whynothugo/systemd-lock-handler/), a bridge between logind D-Bus events and systemd targets. Available as [services.systemd-lock-handler.enable](#opt-services.systemd-lock-handler.enable).

- [filebrowser](https://github.com/filebrowser/filebrowser), a web application for managing files and directories. Available as [services.filebrowser.enable](#opt-services.filebrowser.enable).

- [wastebin](https://github.com/matze/wastebin), a pastebin server written in rust. Available as [services.wastebin](#opt-services.wastebin.enable).

- [Mealie](https://nightly.mealie.io/), a self-hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in NuxtJS for a pleasant user experience for the whole family. Available as [services.mealie](#opt-services.mealie.enable)
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,7 @@
./services/web-apps/engelsystem.nix
./services/web-apps/ethercalc.nix
./services/web-apps/firefly-iii.nix
./services/web-apps/filebrowser.nix
./services/web-apps/fluidd.nix
./services/web-apps/freshrss.nix
./services/web-apps/galene.nix
Expand Down
181 changes: 181 additions & 0 deletions nixos/modules/services/web-apps/filebrowser.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{ config, lib, pkgs, ... }:

let
inherit (lib) generators types hasPrefix literalExpression mkEnableOption mkIf
mkOption mkPackageOption optional optionalAttrs;

cfg = config.services.filebrowser;
enableDynamicUser =
assert cfg.user != "filebrowser" -> cfg.group != "filebrowser";
cfg.user == "filebrowser";
Comment on lines +8 to +10
Copy link
Member

Choose a reason for hiding this comment

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

I find it kinda strange and unexpected that DynamicUser gets disabled when the user and group settings are changed.

enableTls =
assert cfg.tlsCertificate != null -> cfg.tlsCertificateKey != null;
Copy link
Member

Choose a reason for hiding this comment

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

This should be a module level assertion

cfg.tlsCertificate != null;

configFile = pkgs.writeText "filebrowser-config.json" (generators.toJSON {} ({
database = "/var/lib/filebrowser/filebrowser.db";
Copy link
Member

Choose a reason for hiding this comment

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

We use 2 spaces to intend nix code

Suggested change
database = "/var/lib/filebrowser/filebrowser.db";
database = "/var/lib/filebrowser/filebrowser.db";

root = cfg.rootDir;
inherit (cfg) address port;
} // (optionalAttrs enableTls {
cert = cfg.tlsCertificate;
key = cfg.tlsCertificateKey;
})
// (optionalAttrs (cfg.baseUrl != null) { baseurl = cfg.baseUrl; })
Copy link
Member

Choose a reason for hiding this comment

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

optionalAttrs doesn't require sourounding brackets

// (optionalAttrs (cfg.cacheDir != null) { cache-dir = cfg.cacheDir; })
// (optionalAttrs cfg.disableThumbnails { disable-thumbnails = true; })
// (optionalAttrs cfg.disablePreviewResize { disable-preview-resize = true; })
// (optionalAttrs cfg.disableCommandRunner { disable-exec = true; })
// (optionalAttrs cfg.disableTypeDetectionByHeader { disable-type-detection-by-header = true; })
Comment on lines +24 to +28
Copy link
Member

Choose a reason for hiding this comment

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

Why not use an rfc 42 freeform setting type for this?

));
in
{
options.services.filebrowser = {
enable = mkEnableOption
"File Browser is a web application for managing files and directories";

package = mkPackageOption pkgs "filebrowser" { };

# https://github.com/filebrowser/filebrowser/blob/bd3c1941ff8289a5dae877e08f7e25fa9b2a92c5/cmd/root.go#L56
address = mkOption {
type = types.str;
default = "127.0.0.1";
example = literalExpression "0.0.0.0";
description = ''
Address the service should listen on.
'';
};

port = mkOption {
type = types.port;
default = 8080;
description = "Port the service should listen on.";
};

tlsCertificate = mkOption {
type = types.nullOr types.path;
default = null;
description = "Optional TLS certificate";
};

tlsCertificateKey = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Key for TLS certificate. Must be set if `services.filebrowser.tlsCertficate`
is used.
'';
};

rootDir = mkOption {
type = types.path;
default = "/var/lib/filebrowser/files";
description = ''
Path to the directory that should be exposed by File Browser.
'';
};

baseUrl = mkOption {
type = types.nullOr types.str;
default = null;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this default to / ?

example = "/files";
description = ''
Subpath to serve the web interface at. Useful in combination with a
reverse proxy.
'';
};

cacheDir = mkOption {
type = types.nullOr types.path;
default = null;
example = literalExpression "/var/cache/filebrowser";
description = "File cache directory. Disabled by default.";
};

tokenExpirationTime = mkOption {
type = types.str;
default = "2h";
example = "12h";
description = "User session timeout";
};

imageProcessors = mkOption {
type = types.int;
default = 4;
example = 2;
description = "Number of image processes.";
};

disableThumbnails = mkEnableOption "Disable image thumbnails.";
disablePreviewResize = mkEnableOption "Disable resize of image previews.";
disableCommandRunner = mkEnableOption "Disable the Command Runner feature.";
disableTypeDetectionByHeader = mkEnableOption
"Disable file type detection by reading file headers.";
Comment on lines +108 to +112
Copy link
Member

Choose a reason for hiding this comment

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

The description of those will be prefixed with Wether to enable which I don't think the description considered.


user = mkOption {
type = types.str;
default = "filebrowser";
description = ''
If `services.filebrowser.group` is set, this must be set as well. Must
already exist.
'';
};

group = mkOption {
type = types.str;
default = "filebrowser";
description = ''
If `services.filebrowser.user` is set, this must be set as well. Must
Copy link
Member

Choose a reason for hiding this comment

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

Can't we create the default user?

already exist.
'';
};

openFirewall = mkEnableOption "Open the port in the firewall.";
};

config = mkIf cfg.enable {
systemd.services.filebrowser = {
description = "File Browser service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment.HOME = "/var/lib/filebrowser";

serviceConfig = {
Type = "simple";
Restart = "on-failure";
User = cfg.user;
Group = cfg.group;
StateDirectory = "filebrowser";
BindPaths = optional (!(hasPrefix "/var/lib/filebrowser" cfg.rootDir)) cfg.rootDir;

DynamicUser = enableDynamicUser;

# Basic hardening
NoNewPrivileges = "yes";
PrivateTmp = "yes";
PrivateDevices = "yes";
DevicePolicy = "closed";
ProtectSystem = "strict";
ProtectHome = "tmpfs";
ProtectControlGroups = "yes";
ProtectKernelModules = "yes";
ProtectKernelTunables = "yes";
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
RestrictNamespaces = "yes";
RestrictRealtime = "yes";
RestrictSUIDSGID = "yes";
MemoryDenyWriteExecute = "yes";
LockPersonality = "yes";
Comment on lines +153 to +167
Copy link
Member

Choose a reason for hiding this comment

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

In nixpkgs we mostly use true instead of "yes"


ExecStartPre = ''
${pkgs.coreutils}/bin/mkdir -p ${toString cfg.rootDir}
'';

ExecStart = "${cfg.package}/bin/filebrowser --config ${configFile}";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ExecStart = "${cfg.package}/bin/filebrowser --config ${configFile}";
ExecStart = "${lib.getExe cfg.package} --config ${configFile}";

};
};

networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
};

meta.maintainers = with lib.maintainers; [ christoph-heiss ];
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ in {
fenics = handleTest ./fenics.nix {};
ferm = handleTest ./ferm.nix {};
ferretdb = handleTest ./ferretdb.nix {};
filebrowser = handleTest ./filebrowser.nix { };
filesystems-overlayfs = runTest ./filesystems-overlayfs.nix;
firefly-iii = handleTest ./firefly-iii.nix {};
firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
Expand Down
46 changes: 46 additions & 0 deletions nixos/tests/filebrowser.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import ./make-test-python.nix ({ pkgs, lib, ... }:
let
tls-cert = pkgs.runCommand "selfSignedCerts" {
buildInputs = [ pkgs.openssl ];
} ''
mkdir -p $out
openssl req -x509 \
-subj '/CN=localhost/' -days 365 \
-addext 'subjectAltName = DNS:localhost' \
-keyout "$out/cert.key" -newkey ed25519 \
-out "$out/cert.pem" -noenc
'';
in {
name = "filebrowser";
meta.maintainers = with lib.maintainers; [ christoph-heiss ];

nodes = {
http = {
services.filebrowser = {
enable = true;
};
};
https = {
security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
services.filebrowser = {
enable = true;
tlsCertificate = "${tls-cert}/cert.pem";
tlsCertificateKey = "${tls-cert}/cert.key";
};
};
};

testScript = ''
start_all()

with subtest("check if http works"):
http.wait_for_unit("filebrowser.service")
http.wait_for_open_port(8080)
http.succeed("curl -sSf http://localhost:8080 | grep '<!doctype html>'")

with subtest("check if https works"):
https.wait_for_unit("filebrowser.service")
https.wait_for_open_port(8080)
https.succeed("curl -sSf https://localhost:8080 | grep '<!doctype html>'")
'';
})
32 changes: 13 additions & 19 deletions pkgs/applications/networking/filebrowser/default.nix
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
{ buildGoModule, buildNpmPackage, fetchFromGitHub, lib }:

let
version = "2.28.0";
src = fetchFromGitHub {
owner = "filebrowser";
repo = "filebrowser";
rev = "v${version}";
hash = "sha256-ubfNGsVClMIq7u0DQVrR4Hdr8NNf76QXqLxnRVJHaCM=";
};

frontend = buildNpmPackage rec {
pname = "filebrowser-frontend";
version = "2.23.0";

src = fetchFromGitHub {
owner = "filebrowser";
repo = "filebrowser";
rev = "v${version}";
hash = "sha256-xhBIJcEtxDdMXSgQtLAV0UWzPtrvKEil0WV76K5ycBc=";
};
inherit version src;

sourceRoot = "${src.name}/frontend";

npmDepsHash = "sha256-acNIMKHc4q7eiFLPBtKZBNweEsrt+//0VR6dqwXHTvA=";
npmDepsHash = "sha256-h2Sqco7NHLnaMNgh9Ykggarv6cS0NrA6M9/2nv2RU28=";

NODE_OPTIONS = "--openssl-legacy-provider";

Expand All @@ -30,16 +31,9 @@ let
in
buildGoModule rec {
pname = "filebrowser";
version = "2.23.0";

src = fetchFromGitHub {
owner = "filebrowser";
repo = "filebrowser";
rev = "v${version}";
hash = "sha256-xhBIJcEtxDdMXSgQtLAV0UWzPtrvKEil0WV76K5ycBc=";
};
inherit version src;

vendorHash = "sha256-MR0ju2Nomb3j78Z+1YcJY+jPd40MZpuOTuQJM94AM8A=";
vendorHash = "sha256-pDQyJ0F6gCkJtUnaoSe+lWpgNbk/2GDGQ67S3G+VudE=";

excludedPackages = [ "tools" ];

Expand All @@ -55,7 +49,7 @@ buildGoModule rec {
description = "Filebrowser is a web application for managing files and directories";
homepage = "https://filebrowser.org";
license = licenses.asl20;
maintainers = with maintainers; [ nielsegberts ];
maintainers = with maintainers; [ christoph-heiss ];
mainProgram = "filebrowser";
};
}