Skip to content

Commit dc8a253

Browse files
authored
nixos/send: init (#351255)
2 parents b29165b + c9086d8 commit dc8a253

File tree

4 files changed

+264
-0
lines changed

4 files changed

+264
-0
lines changed

nixos/modules/module-list.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,7 @@
15501550
./services/web-servers/phpfpm/default.nix
15511551
./services/web-servers/pomerium.nix
15521552
./services/web-servers/rustus.nix
1553+
./services/web-servers/send.nix
15531554
./services/web-servers/stargazer.nix
15541555
./services/web-servers/static-web-server.nix
15551556
./services/web-servers/tomcat.nix
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
{
2+
config,
3+
lib,
4+
pkgs,
5+
...
6+
}:
7+
let
8+
inherit (lib) mkOption types;
9+
cfg = config.services.send;
10+
in
11+
{
12+
options = {
13+
services.send = {
14+
enable = lib.mkEnableOption "Send, a file sharing web sevice for ffsend.";
15+
16+
package = lib.mkPackageOption pkgs "send" { };
17+
18+
environment = mkOption {
19+
type =
20+
with types;
21+
attrsOf (
22+
nullOr (oneOf [
23+
bool
24+
int
25+
str
26+
(listOf int)
27+
])
28+
);
29+
description = ''
30+
All the available config options and their defaults can be found here: https://github.com/timvisee/send/blob/master/server/config.js,
31+
some descriptions can found here: https://github.com/timvisee/send/blob/master/docs/docker.md#environment-variables
32+
33+
Values under {option}`services.send.environment` will override the predefined values in the Send service.
34+
- Time/duration should be in seconds
35+
- Filesize values should be in bytes
36+
'';
37+
example = {
38+
DEFAULT_DOWNLOADS = 1;
39+
DETECT_BASE_URL = true;
40+
EXPIRE_TIMES_SECONDS = [
41+
300
42+
3600
43+
86400
44+
604800
45+
];
46+
};
47+
};
48+
49+
dataDir = lib.mkOption {
50+
type = types.path;
51+
readOnly = true;
52+
default = "/var/lib/send";
53+
description = ''
54+
Directory for uploaded files.
55+
Due to limitations in {option}`systemd.services.send.serviceConfig.DynamicUser`, this item is read only.
56+
'';
57+
};
58+
59+
baseUrl = mkOption {
60+
type = types.nullOr types.str;
61+
default = null;
62+
description = ''
63+
Base URL for the Send service.
64+
Leave it blank to automatically detect the base url.
65+
'';
66+
};
67+
68+
host = lib.mkOption {
69+
type = types.str;
70+
default = "127.0.0.1";
71+
description = "The hostname or IP address for Send to bind to.";
72+
};
73+
74+
port = lib.mkOption {
75+
type = types.port;
76+
default = 1443;
77+
description = "Port the Send service listens on.";
78+
};
79+
80+
openFirewall = lib.mkOption {
81+
type = types.bool;
82+
default = false;
83+
description = "Whether to open firewall ports for send";
84+
};
85+
86+
redis = {
87+
createLocally = lib.mkOption {
88+
type = types.bool;
89+
default = true;
90+
description = "Whether to create a local redis automatically.";
91+
};
92+
93+
name = lib.mkOption {
94+
type = types.str;
95+
default = "send";
96+
description = ''
97+
Name of the redis server.
98+
Only used if {option}`services.send.redis.createLocally` is set to true.
99+
'';
100+
};
101+
102+
host = lib.mkOption {
103+
type = types.str;
104+
default = "localhost";
105+
description = "Redis server address.";
106+
};
107+
108+
port = lib.mkOption {
109+
type = types.port;
110+
default = 6379;
111+
description = "Port of the redis server.";
112+
};
113+
114+
passwordFile = mkOption {
115+
type = types.nullOr types.path;
116+
default = null;
117+
example = "/run/agenix/send-redis-password";
118+
description = ''
119+
The path to the file containing the Redis password.
120+
121+
If {option}`services.send.redis.createLocally` is set to true,
122+
the content of this file will be used as the password for the locally created Redis instance.
123+
124+
Leave it blank if no password is required.
125+
'';
126+
};
127+
};
128+
};
129+
};
130+
131+
config = lib.mkIf cfg.enable {
132+
133+
services.send.environment.DETECT_BASE_URL = cfg.baseUrl == null;
134+
135+
assertions = [
136+
{
137+
assertion = cfg.redis.createLocally -> cfg.redis.host == "localhost";
138+
message = "the redis host must be localhost if services.send.redis.createLocally is set to true";
139+
}
140+
];
141+
142+
networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.port;
143+
144+
services.redis = lib.optionalAttrs cfg.redis.createLocally {
145+
servers."${cfg.redis.name}" = {
146+
enable = true;
147+
bind = "localhost";
148+
port = cfg.redis.port;
149+
};
150+
};
151+
152+
systemd.services.send = {
153+
serviceConfig = {
154+
Type = "simple";
155+
Restart = "always";
156+
StateDirectory = "send";
157+
WorkingDirectory = cfg.dataDir;
158+
ReadWritePaths = cfg.dataDir;
159+
LoadCredential = lib.optionalString (
160+
cfg.redis.passwordFile != null
161+
) "redis-password:${cfg.redis.passwordFile}";
162+
163+
# Hardening
164+
RestrictAddressFamilies = [
165+
"AF_UNIX"
166+
"AF_INET"
167+
"AF_INET6"
168+
];
169+
AmbientCapabilities = lib.optionalString (cfg.port < 1024) "cap_net_bind_service";
170+
DynamicUser = true;
171+
CapabilityBoundingSet = "";
172+
NoNewPrivileges = true;
173+
RemoveIPC = true;
174+
PrivateTmp = true;
175+
ProcSubset = "pid";
176+
ProtectClock = true;
177+
ProtectControlGroups = true;
178+
ProtectHome = true;
179+
ProtectHostname = true;
180+
ProtectKernelLogs = true;
181+
ProtectKernelModules = true;
182+
ProtectKernelTunables = true;
183+
ProtectProc = "invisible";
184+
ProtectSystem = "full";
185+
RestrictNamespaces = true;
186+
RestrictRealtime = true;
187+
RestrictSUIDSGID = true;
188+
SystemCallArchitectures = "native";
189+
UMask = "0077";
190+
};
191+
environment =
192+
{
193+
IP_ADDRESS = cfg.host;
194+
PORT = toString cfg.port;
195+
BASE_URL = if (cfg.baseUrl == null) then "http://${cfg.host}:${toString cfg.port}" else cfg.baseUrl;
196+
FILE_DIR = cfg.dataDir + "/uploads";
197+
REDIS_HOST = cfg.redis.host;
198+
REDIS_PORT = toString cfg.redis.port;
199+
}
200+
// (lib.mapAttrs (
201+
name: value:
202+
if lib.isList value then
203+
"[" + lib.concatStringsSep ", " (map (x: toString x) value) + "]"
204+
else if lib.isBool value then
205+
lib.boolToString value
206+
else
207+
toString value
208+
) cfg.environment);
209+
after =
210+
[
211+
"network.target"
212+
]
213+
++ lib.optionals cfg.redis.createLocally [
214+
"redis-${cfg.redis.name}.service"
215+
];
216+
description = "Send web service";
217+
wantedBy = [ "multi-user.target" ];
218+
script = ''
219+
${lib.optionalString (cfg.redis.passwordFile != null) ''
220+
export REDIS_PASSWORD="$(cat $CREDENTIALS_DIRECTORY/redis-password)"
221+
''}
222+
${lib.getExe cfg.package}
223+
'';
224+
};
225+
};
226+
227+
meta.maintainers = with lib.maintainers; [ moraxyc ];
228+
}

nixos/tests/all-tests.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,7 @@ in {
904904
seafile = handleTest ./seafile.nix {};
905905
searx = runTest ./searx.nix;
906906
seatd = handleTest ./seatd.nix {};
907+
send = runTest ./send.nix;
907908
service-runner = handleTest ./service-runner.nix {};
908909
sftpgo = runTest ./sftpgo.nix;
909910
sfxr-qt = handleTest ./sfxr-qt.nix {};

nixos/tests/send.nix

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{ lib, pkgs, ... }:
2+
{
3+
name = "send";
4+
5+
meta = {
6+
maintainers = with lib.maintainers; [ moraxyc ];
7+
};
8+
9+
nodes.machine =
10+
{ pkgs, ... }:
11+
{
12+
environment.systemPackages = with pkgs; [
13+
curl
14+
ffsend
15+
];
16+
17+
services.send = {
18+
enable = true;
19+
};
20+
};
21+
22+
testScript = ''
23+
machine.wait_for_unit("send.service")
24+
25+
machine.wait_for_open_port(1443)
26+
27+
machine.succeed("curl --fail --max-time 10 http://127.0.0.1:1443")
28+
29+
machine.succeed("echo HelloWorld > /tmp/test")
30+
url = machine.succeed("ffsend upload -q -h http://127.0.0.1:1443/ /tmp/test")
31+
machine.succeed(f'ffsend download --output /tmp/download {url}')
32+
machine.succeed("cat /tmp/download | grep HelloWorld")
33+
'';
34+
}

0 commit comments

Comments
 (0)