1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8let
9 inherit (lib)
10 getExe
11 mkEnableOption
12 mkIf
13 mkOption
14 mkPackageOption
15 optionals
16 types
17 ;
18
19 cfg = config.services.ustreamer;
20in
21{
22 options.services.ustreamer = {
23 enable = mkEnableOption "µStreamer, a lightweight MJPEG-HTTP streamer";
24
25 package = mkPackageOption pkgs "ustreamer" { };
26
27 autoStart = mkOption {
28 description = ''
29 Wether to start µStreamer on boot. Disabling this will use socket
30 activation. The service will stop gracefully after some inactivity.
31 Disabling this will set `--exit-on-no-clients=300`
32 '';
33 type = types.bool;
34 default = true;
35 example = false;
36 };
37
38 listenAddress = mkOption {
39 description = ''
40 Address to expose the HTTP server. This accepts values for
41 ListenStream= defined in {manpage}`systemd.socket(5)`
42 '';
43 type = types.str;
44 default = "0.0.0.0:8080";
45 example = "/run/ustreamer.sock";
46 };
47
48 device = mkOption {
49 description = ''
50 The v4l2 device to stream.
51 '';
52 type = types.path;
53 default = "/dev/video0";
54 example = "/dev/v4l/by-id/usb-0000_Dummy_abcdef-video-index0";
55 };
56
57 extraArgs = mkOption {
58 description = ''
59 Extra arguments to pass to `ustreamer`. See {manpage}`ustreamer(1)`
60 '';
61 type = with types; listOf str;
62 default = [ ];
63 example = [ "--resolution=1920x1080" ];
64 };
65 };
66
67 config = mkIf cfg.enable {
68 services.ustreamer.extraArgs = [
69 "--device=${cfg.device}"
70 ]
71 ++ optionals (!cfg.autoStart) [
72 "--exit-on-no-clients=300"
73 ];
74
75 systemd.services."ustreamer" = {
76 description = "µStreamer, a lightweight MJPEG-HTTP streamer";
77 after = [ "network.target" ];
78 requires = [ "ustreamer.socket" ];
79 wantedBy = mkIf cfg.autoStart [ "multi-user.target" ];
80 serviceConfig = {
81 ExecStart = utils.escapeSystemdExecArgs (
82 [
83 (getExe cfg.package)
84 "--systemd"
85 ]
86 ++ cfg.extraArgs
87 );
88 Restart = if cfg.autoStart then "always" else "on-failure";
89
90 DynamicUser = true;
91 SupplementaryGroups = [ "video" ];
92
93 NoNewPrivileges = true;
94 ProcSubset = "pid";
95 ProtectProc = "noaccess";
96 ProtectClock = "yes";
97 DeviceAllow = [ cfg.device ];
98 };
99 };
100
101 systemd.sockets."ustreamer" = {
102 wantedBy = [ "sockets.target" ];
103 partOf = [ "ustreamer.service" ];
104 socketConfig = {
105 ListenStream = cfg.listenAddress;
106 };
107 };
108 };
109}