nixos/tusd: init module (#505412)

This commit is contained in:
Ilan Joselevich 2026-04-18 18:46:27 +00:00 committed by GitHub
commit 6a73e02580
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 186 additions and 22 deletions

View file

@ -16251,6 +16251,12 @@
github = "M0ustach3";
githubId = 37956764;
};
m1-s = {
email = "michael@m1-s.com";
github = "m1-s";
githubId = 94642227;
name = "Michael Schneider";
};
m1cr0man = {
email = "lucas+nix@m1cr0man.com";
github = "m1cr0man";

View file

@ -1833,6 +1833,7 @@
./services/web-servers/traefik.nix
./services/web-servers/trafficserver/default.nix
./services/web-servers/ttyd.nix
./services/web-servers/tusd.nix
./services/web-servers/unit/default.nix
./services/web-servers/uwsgi.nix
./services/web-servers/varnish/default.nix

View file

@ -0,0 +1,162 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.tusd;
username = "tusd";
groupname = "tusd";
args = [
"-host=${cfg.host}"
"-port=${toString cfg.port}"
"-base-path=${cfg.basePath}"
"-upload-dir=${cfg.uploadDir}"
]
++ lib.optional cfg.behindProxy "-behind-proxy"
++ lib.optional (cfg.maxSize != null) "-max-size=${toString cfg.maxSize}"
++ lib.optional (cfg.networkTimeout != null) "-network-timeout=${cfg.networkTimeout}"
++ lib.optional (cfg.hooksHttp != null) "-hooks-http=${cfg.hooksHttp}"
++ lib.optional (
cfg.hooksEnabledEvents != [ ]
) "-hooks-enabled-events=${lib.concatStringsSep "," cfg.hooksEnabledEvents}"
++ cfg.extraArgs;
in
{
meta.maintainers = with lib.maintainers; [ m1-s ];
options.services.tusd = {
enable = lib.mkEnableOption "tus resumable upload protocol server";
host = lib.mkOption {
type = lib.types.str;
default = "0.0.0.0";
description = "The host to bind the HTTP server to.";
};
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "The port to bind the HTTP server to.";
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to open the firewall port for tusd.";
};
basePath = lib.mkOption {
type = lib.types.str;
default = "/files/";
description = "The basepath of the HTTP server.";
};
uploadDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/tusd/data";
description = "The directory to store uploads in.";
};
behindProxy = lib.mkEnableOption null // {
description = "Whether to respect X-Forwarded-* and similar headers which may be set by proxies.";
};
maxSize = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "The maximum size of a single upload in bytes.";
};
networkTimeout = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
The timeout for reading the request and writing the response.
If tusd does not receive data for this duration,
it will consider the connection dead.
'';
example = "30s";
};
hooksHttp = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "The HTTP endpoint to which hook events will be sent to.";
example = "http://localhost:8081/hooks";
};
hooksEnabledEvents = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "The list of enabled hook events.";
example = [
"pre-create"
"post-finish"
];
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Additional arguments given to tusd.";
example = [
"-verbose"
"-log-format=json"
];
};
};
config = lib.mkIf cfg.enable {
users.users.${username} = {
isSystemUser = true;
group = groupname;
};
users.groups.${groupname} = { };
# tusd knows how to create subdirectories in this folder but we have to
# create the root folder ourselves.
systemd.tmpfiles.settings."tusd".${cfg.uploadDir}.d = {
user = username;
group = groupname;
# default taken from https://github.com/tus/tusd/blob/55a096a10942b85360664a1e8aea7bd758272053/pkg/filestore/filestore.go#L37
mode = "0775";
};
systemd.services.tusd = {
description = "tusd - tus resumable upload protocol server";
documentation = [ "https://github.com/tus/tusd" ];
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
User = username;
Group = groupname;
ExecStart = lib.escapeShellArgs ([ (lib.getExe pkgs.tusd) ] ++ args);
Restart = "on-failure";
StateDirectory = "tusd";
# Hardening
LockPersonality = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostUserNamespaces = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
};
};
networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.port;
};
}

View file

@ -2,37 +2,28 @@
let
port = 1080;
client =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.curl ];
};
server =
{ pkgs, ... }:
{
# tusd does not have a NixOS service yet.
systemd.services.tusd = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = ''${pkgs.tusd}/bin/tusd -port "${toString port}" -upload-dir=/data'';
};
};
networking.firewall.allowedTCPPorts = [ port ];
};
uploadDir = "/var/lib/tusd/data";
in
{
name = "tusd";
meta.maintainers = with lib.maintainers; [
m1-s
nh2
kalbasit
];
nodes = {
inherit server;
inherit client;
client = {
environment.systemPackages = [ pkgs.curl ];
};
server = {
services.tusd = {
enable = true;
inherit port uploadDir;
openFirewall = true;
};
};
};
testScript = ''
@ -46,6 +37,9 @@ in
client.wait_for_unit("network.target")
client.succeed("${./tus-curl-upload.sh} file-100M.bin http://server:${toString port}/files/")
# Verify file was created in uploadDir
server.succeed("test -n \"$(ls -A ${uploadDir})\"")
print("Upload succeeded")
'';
}

View file

@ -34,6 +34,7 @@ buildGoModule (finalAttrs: {
description = "Reference server implementation in Go of tus: the open protocol for resumable file uploads";
license = lib.licenses.mit;
homepage = "https://tus.io/";
mainProgram = "tusd";
maintainers = with lib.maintainers; [
nh2
kalbasit