me like nix
1{ ... }: {
2 flake.modules.nixos.pi-camera =
3 { pkgs, lib, config, ... }:
4 let
5 cfg = config.pi;
6 in
7 {
8 options.pi = {
9 streamName = lib.mkOption {
10 type = lib.types.str;
11 description = "Name of the camera stream";
12 };
13 resolution = {
14 width = lib.mkOption {
15 type = lib.types.int;
16 default = 1920;
17 description = "Camera resolution width";
18 };
19 height = lib.mkOption {
20 type = lib.types.int;
21 default = 1080;
22 description = "Camera resolution height";
23 };
24 };
25 framerate = lib.mkOption {
26 type = lib.types.int;
27 default = 30;
28 description = "Camera framerate";
29 };
30 gpuMem = lib.mkOption {
31 type = lib.types.int;
32 default = 256;
33 description = "GPU memory allocation in MB";
34 };
35 cmaMB = lib.mkOption {
36 type = lib.types.enum [ 64 128 256 512 ];
37 default = 256;
38 description = "CMA reservation in MB. Pi Zero 2W (512 MB total) should use 64 or 128.";
39 };
40 flipCamera = lib.mkOption {
41 type = lib.types.bool;
42 default = false;
43 description = "Flip camera image 180 degrees";
44 };
45 };
46
47 config = {
48 nix.settings.trusted-users = [ "sean" ];
49
50 networking.useDHCP = true;
51
52 services.openssh = {
53 enable = true;
54 settings = {
55 PasswordAuthentication = false;
56 PermitRootLogin = "no";
57 };
58 };
59
60 users.users.sean = {
61 isNormalUser = true;
62 extraGroups = [
63 "wheel"
64 "video"
65 ];
66 openssh.authorizedKeys.keys = [
67 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc="
68 "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"
69 ];
70 };
71
72 security.sudo.wheelNeedsPassword = false;
73
74 # Firmware config.txt: CMA and GPU memory
75 hardware.raspberry-pi.config.all = {
76 options.gpu_mem = {
77 enable = true;
78 value = cfg.gpuMem;
79 };
80 dt-overlays.vc4-kms-v3d.params."cma-${toString cfg.cmaMB}".enable = true;
81 };
82
83 services.go2rtc = {
84 enable = true;
85 settings = {
86 ffmpeg.bin = "${pkgs.ffmpeg}/bin/ffmpeg";
87 streams = {
88 "${cfg.streamName}" = "exec:${pkgs.rpi.rpicam-apps}/bin/rpicam-vid -t 0 --width ${toString cfg.resolution.width} --height ${toString cfg.resolution.height} --framerate ${toString cfg.framerate} --codec h264 --inline --autofocus-mode auto${lib.optionalString cfg.flipCamera " --vflip --hflip"} -o -";
89 };
90 };
91 };
92
93 services.udev.extraRules = ''
94 SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660"
95 '';
96
97 systemd.services.go2rtc.serviceConfig = {
98 User = lib.mkForce "root";
99 SupplementaryGroups = [ "video" ];
100 };
101 systemd.services.go2rtc.environment = {
102 LIBCAMERA_IPA_MODULE_PATH = "${pkgs.rpi.libcamera}/lib/libcamera/ipa";
103 };
104
105 environment.systemPackages = [
106 pkgs.ffmpeg
107 pkgs.rpi.libcamera
108 pkgs.rpi.rpicam-apps
109 pkgs.v4l-utils
110 ];
111
112 networking.firewall.allowedTCPPorts = [
113 22
114 1984
115 8554
116 ];
117 };
118 };
119}