1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
{
config,
lib,
pkgs,
...
}: let
cfg = config.services.tsnet-serve;
pkg = pkgs.callPackage ../pkg {inherit pkgs;};
instanceOptions = {
name,
config,
...
}: {
options = {
enable = lib.mkEnableOption "tsnet-serve-${name}";
user = lib.mkOption {
type = lib.types.str;
default = "tsnet-serve";
description = "User to run tsnet-serve";
};
backend = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Target URL to proxy";
example = "https://localhost:3000";
};
hostname = lib.mkOption {
type = lib.types.str;
default = name;
description = "App name";
example = "myapp";
};
port = lib.mkOption {
type = lib.types.port;
default = 443;
description = "Port to listen on";
};
mountPath = lib.mkOption {
type = lib.types.str;
default = "/";
description = "Path to mount proxy on";
};
stateDir = lib.mkOption {
type = lib.types.str;
default = "/var/lib/tsnet-serve/state/${name}";
description = "Directory to store state in";
};
authKey = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Tailscale auth key";
};
};
};
in {
options = {
services.tsnet-serve.instances = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule instanceOptions);
default = {};
description = "Attribute set of tsnet-serve instances";
};
};
config = lib.mkIf (cfg.instances != {}) {
systemd.services = lib.mapAttrs' (
name: instanceCfg:
lib.nameValuePair "tsnet-serve-${name}" {
description = "tsnet-serve-${name}";
wantedBy = ["multi-user.target"];
after = ["network.target"];
serviceConfig = {
ExecStart = let
args =
lib.optionals (instanceCfg.backend != null) [
"--backend=${instanceCfg.backend}"
]
++ [
"--hostname=${instanceCfg.hostname}"
"--listen-port=${builtins.toString instanceCfg.port}"
"--mount-path=${instanceCfg.mountPath}"
"--state-dir=${instanceCfg.stateDir}"
];
in "${pkg}/bin/tsnet-serve ${lib.concatStringsSep " " args}";
User = instanceCfg.user;
Restart = "on-failure";
Environment = ["TS_AUTHKEY=${instanceCfg.authKey}"];
};
}
) (lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg.instances);
users.users = lib.mapAttrs' (
name: instanceCfg:
lib.nameValuePair instanceCfg.user {
isSystemUser = true;
group = instanceCfg.user;
home = instanceCfg.stateDir;
createHome = true;
}
) (lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg.instances);
users.groups = lib.mapAttrs' (
name: instanceCfg:
lib.nameValuePair instanceCfg.user {}
) (lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg.instances);
};
}
|