1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 concatStringsSep
11 isBool
12 mapAttrs
13 mkEnableOption
14 mkIf
15 mkOption
16 mkPackageOption
17 mkRenamedOptionModule
18 types
19 ;
20
21 cfg = config.services.redlib;
22
23 args = concatStringsSep " " ([
24 "--port ${toString cfg.port}"
25 "--address ${cfg.address}"
26 ]);
27
28 boolToString' = b: if b then "on" else "off";
29in
30{
31 imports = [
32 (mkRenamedOptionModule
33 [
34 "services"
35 "libreddit"
36 ]
37 [
38 "services"
39 "redlib"
40 ]
41 )
42 ];
43
44 options = {
45 services.redlib = {
46 enable = mkEnableOption "Private front-end for Reddit";
47
48 package = mkPackageOption pkgs "redlib" { };
49
50 address = mkOption {
51 default = "0.0.0.0";
52 example = "127.0.0.1";
53 type = types.str;
54 description = "The address to listen on";
55 };
56
57 port = mkOption {
58 default = 8080;
59 example = 8000;
60 type = types.port;
61 description = "The port to listen on";
62 };
63
64 openFirewall = mkOption {
65 type = types.bool;
66 default = false;
67 description = "Open ports in the firewall for the redlib web interface";
68 };
69
70 settings = lib.mkOption {
71 type = lib.types.submodule {
72 freeformType =
73 with types;
74 attrsOf (
75 nullOr (oneOf [
76 bool
77 int
78 str
79 ])
80 );
81 options = { };
82 };
83 default = { };
84 description = ''
85 See [GitHub](https://github.com/redlib-org/redlib/tree/main?tab=readme-ov-file#configuration) for available settings.
86 '';
87 };
88 };
89 };
90
91 config = mkIf cfg.enable {
92 systemd.packages = [ cfg.package ];
93 systemd.services.redlib = {
94 wantedBy = [ "default.target" ];
95 environment = mapAttrs (_: v: if isBool v then boolToString' v else toString v) cfg.settings;
96 serviceConfig =
97 {
98 ExecStart = [
99 ""
100 "${lib.getExe cfg.package} ${args}"
101 ];
102 }
103 // (
104 if (cfg.port < 1024) then
105 {
106 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
107 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
108 }
109 else
110 {
111 # A private user cannot have process capabilities on the host's user
112 # namespace and thus CAP_NET_BIND_SERVICE has no effect.
113 PrivateUsers = true;
114 }
115 );
116 };
117
118 networking.firewall = mkIf cfg.openFirewall {
119 allowedTCPPorts = [ cfg.port ];
120 };
121 };
122
123 meta = {
124 maintainers = with lib.maintainers; [ Guanran928 ];
125 };
126}