main
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 # Force Gigabit advertisement on ThinkPad dock ethernet (r8152/RTL8153).
133 # Without this, auto-negotiation only advertises 100Mbps.
134 ACTION=="add", SUBSYSTEM=="net", DRIVERS=="r8152", ATTR{address}=="00:50:b6:b4:2c:14", RUN+="${pkgs.ethtool}/bin/ethtool -s $env{INTERFACE} advertise 0x03F"
135 '';
136 hardware.sane = {
137 enable = true;
138 extraBackends = [ pkgs.sane-airscan ];
139 openFirewall = true;
140 netConf = "192.168.12.70";
141 };
142
143 # Battery charge thresholds: default to office mode on boot
144 systemd.services.battery-charge-threshold = {
145 description = "Set battery charge threshold to office mode (80%)";
146 wantedBy = [ "multi-user.target" ];
147 after = [ "multi-user.target" ];
148 serviceConfig = {
149 Type = "oneshot";
150 ExecStart = "${officemode}/bin/officemode";
151 };
152 };
153
154 security.sudo.extraRules = [
155 {
156 groups = [ "wheel" ];
157 commands = [
158 {
159 command = "${officemode}/bin/officemode";
160 options = [ "NOPASSWD" ];
161 }
162 {
163 command = "${roadmode}/bin/roadmode";
164 options = [ "NOPASSWD" ];
165 }
166 ];
167 }
168 ];
169
170 environment.systemPackages = with pkgs; [
171 kanata
172 nixos-rebuild-ng
173 battery-monitor
174 # backup
175 virt-manager
176 ];
177
178 # Make sure we don't start docker until required
179 systemd.services.docker.wantedBy = lib.mkForce [ ];
180
181 # Slack Archive - daily backup of public Slack channels
182 systemd.tmpfiles.rules = [
183 "d /var/lib/slack-archive 0750 vincent users -"
184 ];
185
186 systemd.services.slack-archive = {
187 description = "Slack Public Channel Archiver";
188 after = [ "network-online.target" ];
189 wants = [ "network-online.target" ];
190
191 serviceConfig = {
192 Type = "oneshot";
193 User = "vincent";
194 Group = "users";
195 ExecStart = "${pkgs.slack-archive}/bin/slack-archive archive";
196 Environment = [
197 "SLACK_ARCHIVE_DIR=/var/lib/slack-archive"
198 "SLACK_ARCHIVE_HTML_DIR=/home/vincent/src/experiments/tektoncd-slack-archive"
199 "HOME=/home/vincent"
200 "XDG_CACHE_HOME=/home/vincent/.local/cache"
201 ];
202
203 # Security hardening
204 PrivateTmp = true;
205 ProtectSystem = "strict";
206 ProtectHome = "read-only";
207 ReadWritePaths = [
208 "/var/lib/slack-archive"
209 "/home/vincent/.local/cache/slackdump"
210 "/home/vincent/.local/cache/uv"
211 "/home/vincent/.local/share/uv"
212 "/home/vincent/src/experiments/tektoncd-slack-archive"
213 ];
214 NoNewPrivileges = true;
215
216 # Logging
217 StandardOutput = "journal";
218 StandardError = "journal";
219 SyslogIdentifier = "slack-archive";
220 };
221 };
222
223 systemd.timers.slack-archive = {
224 description = "Daily Slack Archive Timer";
225 wantedBy = [ "timers.target" ];
226
227 timerConfig = {
228 OnCalendar = "09:00"; # Run at 9 AM daily (system should be fully up by then)
229 RandomizedDelaySec = 1800; # 0-30 min random delay
230 Persistent = true;
231 };
232 };
233}