flake-update-20260505
1{
2 pkgs,
3 lib,
4 ...
5}:
6let
7 officemode = pkgs.writeShellScriptBin "officemode" ''
8 echo "80" > /sys/class/power_supply/BAT0/charge_control_end_threshold
9 echo "70" > /sys/class/power_supply/BAT0/charge_control_start_threshold
10 echo "Office mode: charging between 70%–80%"
11 '';
12 roadmode = pkgs.writeShellScriptBin "roadmode" ''
13 echo "100" > /sys/class/power_supply/BAT0/charge_control_end_threshold
14 echo "0" > /sys/class/power_supply/BAT0/charge_control_start_threshold
15 echo "Road mode: charging to 100%"
16 '';
17in
18{
19
20 imports = [
21 ../common/hardware/laptop.nix
22 ../common/programs/direnv.nix
23 ../common/programs/git.nix
24 ../common/programs/tmux.nix
25 ../common/services/networkmanager.nix
26 ../common/services/containers.nix
27 ../common/services/docker.nix
28 ../common/services/libvirt.nix
29 ../common/services/binfmt.nix
30 ../common/services/oomd.nix
31 ../../modules/laptop-keyboard-inhibit
32
33 ../redhat
34 ];
35
36 # It takes.. multiple GB, and I don't really use it...
37 programs.obs-studio = {
38 enable = false;
39 plugins = with pkgs.obs-studio-plugins; [
40 wlrobs
41 obs-backgroundremoval
42 obs-pipewire-audio-capture
43 input-overlay
44 ];
45 };
46
47 # Auto-disable internal keyboard when Moonlander (USB) is connected
48 services.laptop-keyboard-inhibit = {
49 enable = true;
50 keyboards = [ "Eyelash Corne" ];
51 usbKeyboards = [
52 {
53 vendor = "3297";
54 product = "1969";
55 }
56 ];
57 };
58
59 services.keybase.enable = true;
60 services.kbfs = {
61 enable = true;
62 mountPoint = "%t/keybase";
63 };
64
65 services = {
66 getty = {
67 autologinOnce = true;
68 autologinUser = "vincent";
69 };
70 # TODO probably migrate elsewhere
71 kanata = {
72 enable = true;
73 package = pkgs.kanata-with-cmd;
74 keyboards.x1 = {
75 devices = [ "/dev/input/event0" ]; # internal keyboard
76 config = builtins.readFile (./. + "/main.kbd");
77 extraDefCfg = ''
78 danger-enable-cmd yes
79 process-unmapped-keys yes
80 override-release-on-activation yes
81 concurrent-tap-hold yes
82 '';
83 };
84 };
85 locate = {
86 enable = true;
87 pruneBindMounts = true;
88 };
89
90 hardware.bolt.enable = true;
91 printing = {
92 enable = true;
93 drivers = with pkgs; [
94 # cnijfilter2 # Disabled: broken in nixpkgs-unstable (bool typedef error)
95 gutenprint
96 gutenprintBin
97 ];
98 };
99 };
100
101 # Canon MX530 printer via Gutenprint driver (driverless/IPP auto-config
102 # defaults to Photographic media type, causing documents to print tiny)
103 hardware.printers = {
104 ensurePrinters = [
105 {
106 name = "Canon_MX530_Gutenprint";
107 description = "Canon MX530 series (Gutenprint)";
108 location = "Home";
109 deviceUri = "ipp://192.168.1.16:631/ipp/print";
110 model = "gutenprint.${lib.versions.majorMinor (lib.getVersion pkgs.gutenprint)}://bjc-MX530-series/expert";
111 ppdOptions = {
112 PageSize = "A4";
113 MediaType = "Plain";
114 InputSlot = "Front";
115 ColorModel = "RGB";
116 Duplex = "None";
117 StpQuality = "Standard";
118 };
119 }
120 ];
121 ensureDefaultPrinter = "Canon_MX530_Gutenprint";
122 };
123
124 hardware.keyboard.qmk.enable = true;
125
126 services.udev.packages = [ pkgs.sane-airscan ];
127 services.udev.extraRules = ''
128 # Disable autosuspend for Logitech C920 to prevent xHCI controller resets
129 # that cascade to the r8152 USB ethernet adapter, causing packet drops.
130 ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="046d", ATTR{idProduct}=="082d", ATTR{power/control}="on"
131 '';
132 hardware.sane = {
133 enable = true;
134 extraBackends = [ pkgs.sane-airscan ];
135 openFirewall = true;
136 netConf = "192.168.12.70";
137 };
138
139 # Battery charge thresholds: default to office mode on boot
140 systemd.services.battery-charge-threshold = {
141 description = "Set battery charge threshold to office mode (80%)";
142 wantedBy = [ "multi-user.target" ];
143 after = [ "multi-user.target" ];
144 serviceConfig = {
145 Type = "oneshot";
146 ExecStart = "${officemode}/bin/officemode";
147 };
148 };
149
150 security.sudo.extraRules = [
151 {
152 groups = [ "wheel" ];
153 commands = [
154 {
155 command = "${officemode}/bin/officemode";
156 options = [ "NOPASSWD" ];
157 }
158 {
159 command = "${roadmode}/bin/roadmode";
160 options = [ "NOPASSWD" ];
161 }
162 ];
163 }
164 ];
165
166 environment.systemPackages = with pkgs; [
167 kanata
168 nixos-rebuild-ng
169 battery-monitor
170 # backup
171 virt-manager
172 ];
173
174 # Make sure we don't start docker until required
175 systemd.services.docker.wantedBy = lib.mkForce [ ];
176
177 # Slack Archive - daily backup of public Slack channels
178 systemd.tmpfiles.rules = [
179 "d /var/lib/slack-archive 0750 vincent users -"
180 ];
181
182 systemd.services.slack-archive = {
183 description = "Slack Public Channel Archiver";
184 after = [ "network-online.target" ];
185 wants = [ "network-online.target" ];
186
187 serviceConfig = {
188 Type = "oneshot";
189 User = "vincent";
190 Group = "users";
191 ExecStart = "${pkgs.slack-archive}/bin/slack-archive archive";
192 Environment = [
193 "SLACK_ARCHIVE_DIR=/var/lib/slack-archive"
194 "SLACK_ARCHIVE_HTML_DIR=/home/vincent/src/experiments/tektoncd-slack-archive"
195 "HOME=/home/vincent"
196 "XDG_CACHE_HOME=/home/vincent/.local/cache"
197 ];
198
199 # Security hardening
200 PrivateTmp = true;
201 ProtectSystem = "strict";
202 ProtectHome = "read-only";
203 ReadWritePaths = [
204 "/var/lib/slack-archive"
205 "/home/vincent/.local/cache/slackdump"
206 "/home/vincent/.local/cache/uv"
207 "/home/vincent/.local/share/uv"
208 "/home/vincent/src/experiments/tektoncd-slack-archive"
209 ];
210 NoNewPrivileges = true;
211
212 # Logging
213 StandardOutput = "journal";
214 StandardError = "journal";
215 SyslogIdentifier = "slack-archive";
216 };
217 };
218
219 systemd.timers.slack-archive = {
220 description = "Daily Slack Archive Timer";
221 wantedBy = [ "timers.target" ];
222
223 timerConfig = {
224 OnCalendar = "09:00"; # Run at 9 AM daily (system should be fully up by then)
225 RandomizedDelaySec = 1800; # 0-30 min random delay
226 Persistent = true;
227 };
228 };
229}