Compare commits

..

59 Commits

Author SHA1 Message Date
24b66745d1 updated test_test_bash_wrapper_error 2025-12-31 21:00:22 -05:00
3074e3c47e added busybox to nix_builder.nix 2025-12-31 17:06:38 -05:00
9aea31e841 added pytest_safe.yml 2025-12-31 16:55:05 -05:00
ec5975d663 added busybox 2025-12-31 16:34:21 -05:00
f5b22322d0 testing unshare 2025-12-31 16:14:20 -05:00
41d3a8fe1a removed filebrowser 2025-12-27 09:52:19 -05:00
github-actions[bot]
e6ac8f8021 flake.lock: Update
Flake lock file updates:

• Updated input 'firefox-addons':
    'gitlab:rycee/nur-expressions/981ca9c?dir=pkgs/firefox-addons' (2025-12-25)
  → 'gitlab:rycee/nur-expressions/03d7d31?dir=pkgs/firefox-addons' (2025-12-26)
• Updated input 'nixpkgs-master':
    'github:nixos/nixpkgs/ebe3ab4' (2025-12-25)
  → 'github:nixos/nixpkgs/088b069' (2025-12-27)
2025-12-26 21:35:18 -05:00
0f8f6f96d6 moved scratch pool to zfs encryption 2025-12-26 21:26:25 -05:00
4cb4bd6f3d removing qbit 2025-12-26 18:48:11 -05:00
c046710258 updating desktop kernelPackages 2025-12-26 11:53:26 -05:00
7f9fbe3602 updated to pkgs.zfs_2_4 2025-12-26 11:18:17 -05:00
github-actions[bot]
8ee3b4d6e5 flake.lock: Update
Flake lock file updates:

• Updated input 'firefox-addons':
    'gitlab:rycee/nur-expressions/0f01129?dir=pkgs/firefox-addons' (2025-12-19)
  → 'gitlab:rycee/nur-expressions/981ca9c?dir=pkgs/firefox-addons' (2025-12-25)
• Updated input 'home-manager':
    'github:nix-community/home-manager/bb35f07' (2025-12-19)
  → 'github:nix-community/home-manager/91cdb0e' (2025-12-25)
• Updated input 'nixos-hardware':
    'github:nixos/nixos-hardware/9154f45' (2025-11-29)
  → 'github:nixos/nixos-hardware/c5db956' (2025-12-24)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/c6245e8' (2025-12-18)
  → 'github:nixos/nixpkgs/3e2499d' (2025-12-25)
• Updated input 'nixpkgs-master':
    'github:nixos/nixpkgs/704c236' (2025-12-20)
  → 'github:nixos/nixpkgs/ebe3ab4' (2025-12-25)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/443a7f2' (2025-12-15)
  → 'github:Mic92/sops-nix/9836912' (2025-12-21)
2025-12-26 11:18:17 -05:00
18b7fb2d60 added runners 2025-12-25 21:11:43 -05:00
2f1fa5c750 converted to github pat 2025-12-25 21:11:43 -05:00
164d0dd59e fixed bugs 2025-12-24 20:17:24 -05:00
d4459643ab zero padded nix builder names 2025-12-24 20:17:24 -05:00
c09dba0c37 fixed container dns 2025-12-24 20:17:24 -05:00
409f376166 setup a isolated vlan for the runners 2025-12-24 20:17:24 -05:00
a9a6e1f932 harding nix_builder.nix 2025-12-24 20:17:24 -05:00
6472f07a88 removed acceleration = "cuda"; 2025-12-21 21:24:17 -05:00
github-actions[bot]
51c79f6b40 flake.lock: Update
Flake lock file updates:

• Updated input 'firefox-addons':
    'gitlab:rycee/nur-expressions/cefce78?dir=pkgs/firefox-addons' (2025-11-21)
  → 'gitlab:rycee/nur-expressions/0f01129?dir=pkgs/firefox-addons' (2025-12-19)
• Updated input 'home-manager':
    'github:nix-community/home-manager/d10a9b1' (2025-11-21)
  → 'github:nix-community/home-manager/bb35f07' (2025-12-19)
• Updated input 'nixos-hardware':
    'github:nixos/nixos-hardware/899dc44' (2025-11-11)
  → 'github:nixos/nixos-hardware/9154f45' (2025-11-29)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/89c2b23' (2025-11-17)
  → 'github:nixos/nixpkgs/c6245e8' (2025-12-18)
• Updated input 'nixpkgs-master':
    'github:nixos/nixpkgs/8a7cf7e' (2025-11-22)
  → 'github:nixos/nixpkgs/704c236' (2025-12-20)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/877bb49' (2025-11-20)
  → 'github:Mic92/sops-nix/443a7f2' (2025-12-15)
2025-12-21 21:24:17 -05:00
b0d5147296 removed ssh setting from nix_builder.nix 2025-12-21 20:54:58 -05:00
c56082b516 tighten security 2025-12-21 11:49:09 -05:00
34b728c88f added ./qmk.nix to rhapsody-in-green 2025-12-21 11:49:09 -05:00
5697458bad adding udev rules 2025-12-21 11:49:09 -05:00
276c2ac74b adding qmk.nix to rhapsody-in-green 2025-12-21 11:49:09 -05:00
69e5aa20d5 removed ssh-keys 2025-12-21 09:39:11 -05:00
3d1f773fa5 testing unix socket 2025-12-21 09:39:11 -05:00
14dd1fe52e removed nvidia gpu from jeeves 2025-12-21 08:35:01 -05:00
30fe41ea1b removed tests 2025-12-20 23:09:19 -05:00
3a17c5514d testing nix socket 2025-12-20 22:47:19 -05:00
c6586db91e testing sshless nix-cache copy 2025-12-20 22:26:30 -05:00
81b199373e adding unifi to brain HA 2025-12-20 22:00:45 -05:00
a957e23041 fixed energy out integrations 2025-12-20 22:00:45 -05:00
52389f729d adding more extraPackages to brain HA 2025-12-20 22:00:45 -05:00
cc2a609f52 added gitea to haproxy 2025-12-20 17:18:51 -05:00
ca4693a1ba removed log.console-warn 2025-12-20 17:18:51 -05:00
90e5e0855d added gitea to postgress.nix 2025-12-20 17:18:51 -05:00
e339667c2b updated gitea settings 2025-12-20 17:18:51 -05:00
85540ee920 setting up gitea 2025-12-20 17:18:51 -05:00
3be1b8aa8f fixed some coderabit issues 2025-12-20 12:20:21 -05:00
7c56954cda get splendor code ruff complient 2025-12-20 12:20:21 -05:00
290f972346 ran ruff check python --fix --unsafe-fixes 2025-12-20 12:20:21 -05:00
72c3ccfb6d ran ruff check python --fix 2025-12-20 12:20:21 -05:00
9630633ff5 temp 2025-12-20 12:20:21 -05:00
8c83f306b2 added more options for simulat.py 2025-12-20 12:20:21 -05:00
5b4609dc3b added can_bot_afford to improve profiling 2025-12-20 12:20:21 -05:00
d1be25c6e8 added load_cards and load_nobles 2025-12-20 12:20:21 -05:00
31910586d2 added __init__.py to splendor 2025-12-20 12:20:21 -05:00
b8dfd0852a moved max_token_take to GameConfig 2025-12-20 12:20:21 -05:00
6ce622e93e speed up check_nobles_for_player 2025-12-20 12:20:21 -05:00
55e652a51d added .gitignore to splendor 2025-12-20 12:20:21 -05:00
b5455a5483 cleaned up human.py 2025-12-20 12:20:21 -05:00
8baf388061 starting splendor 2025-12-20 12:20:21 -05:00
7ffb7b4a37 adding AGENTS.md 2025-12-06 17:54:03 -05:00
eb04f4a56d 'system' has been renamed to/replaced by 'stdenv.hostPlatform.system' 2025-12-06 12:45:55 -05:00
5b8e543226 added environment sensor 2025-12-06 12:33:10 -05:00
da48f62195 splint HA battery monitoring 2025-12-06 12:33:10 -05:00
60f2ab1039 testing google antigravity 2025-12-06 12:24:54 -05:00
40 changed files with 682 additions and 724 deletions

View File

@@ -25,4 +25,4 @@ jobs:
- name: Build default package
run: "nixos-rebuild build --flake ./#${{ matrix.system }}"
- name: copy to nix-cache
run: nix copy --to ssh://jeeves .#nixosConfigurations.${{ matrix.system }}.config.system.build.toplevel
run: nix copy --accept-flake-config --to unix:///host-nix/var/nix/daemon-socket/socket .#nixosConfigurations.${{ matrix.system }}.config.system.build.toplevel

19
.github/workflows/pytest_safe.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: pytest_safe
on:
push:
branches:
- main
pull_request:
branches:
- main
merge_group:
jobs:
pytest:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run tests
run: unshare --map-root-user --user --net -- pytest tests

13
.vscode/settings.json vendored
View File

@@ -81,7 +81,6 @@
"FASTFOX",
"ffmpegthumbnailer",
"filebot",
"filebrowser",
"fileroller",
"findbar",
"Fira",
@@ -98,6 +97,7 @@
"getch",
"getmaxyx",
"ghdeploy",
"gitea",
"globalprivacycontrol",
"gparted",
"gtts",
@@ -116,7 +116,9 @@
"httpchk",
"hurlenko",
"hwloc",
"ical",
"ignorelist",
"improv",
"INITDB",
"iocharset",
"ioit",
@@ -126,6 +128,8 @@
"jnoortheen",
"jsbc",
"kagi",
"keyformat",
"keylocation",
"kuma",
"lazer",
"levelname",
@@ -225,12 +229,9 @@
"pylint",
"pymetno",
"pymodbus",
"pyopenweathermap",
"pyownet",
"pytest",
"qbit",
"qbittorrent",
"qbittorrentvpn",
"qbitvpn",
"quicksuggest",
"radarr",
"readahead",
@@ -288,9 +289,11 @@
"twimg",
"typer",
"uaccess",
"ubiquiti",
"ublock",
"uiprotect",
"uitour",
"unifi",
"unrar",
"unsubmitted",
"uptimekuma",

5
AGENTS.md Normal file
View File

@@ -0,0 +1,5 @@
## Dev environment tips
- use treefmt to format all files
- make python code ruff compliant
- use pytest to test python code

View File

@@ -16,7 +16,6 @@
./nh.nix
./nix.nix
./programs.nix
./safe_reboot.nix
./ssh.nix
./snapshot_manager.nix
];
@@ -24,7 +23,7 @@
boot = {
tmp.useTmpfs = true;
kernelPackages = lib.mkDefault pkgs.linuxPackages_6_12;
zfs.package = lib.mkDefault pkgs.zfs_2_3;
zfs.package = lib.mkDefault pkgs.zfs_2_4;
};
hardware.enableRedistributableFirmware = true;
@@ -50,11 +49,6 @@
PYTHONPATH = "${inputs.self}/";
};
safe_reboot = {
enable = lib.mkDefault true;
datasetPrefix = "root_pool/";
};
zfs = {
trim.enable = lib.mkDefault true;
autoScrub.enable = lib.mkDefault true;

View File

@@ -1,56 +0,0 @@
{
config,
inputs,
lib,
pkgs,
...
}:
let
cfg = config.services.safe_reboot;
python_command =
lib.escapeShellArgs (
[
"${pkgs.my_python}/bin/python"
"-m"
"python.tools.safe_reboot"
]
++ lib.optionals (cfg.drivePath != null) [ cfg.drivePath ]
++ [
"--dataset-prefix"
cfg.datasetPrefix
"--check-only"
]
);
in
{
options.services.safe_reboot = {
enable = lib.mkEnableOption "Safe reboot dataset/drive validation";
datasetPrefix = lib.mkOption {
type = lib.types.str;
default = "root_pool/";
description = "Dataset prefix that must have exec enabled before rebooting.";
};
drivePath = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Drive path that must exist before rebooting. Set to null to skip.";
};
};
config = lib.mkIf cfg.enable {
systemd.services.safe-reboot-check = {
description = "Safe reboot validation";
before = [ "systemd-reboot.service" ];
wantedBy = [ "reboot.target" ];
partOf = [ "reboot.target" ];
path = [ pkgs.zfs ];
environment = {
PYTHONPATH = "${inputs.self}/";
};
serviceConfig = {
Type = "oneshot";
ExecStart = python_command;
};
};
};
}

View File

@@ -1,8 +1,8 @@
{ pkgs, ... }:
{
boot = {
kernelPackages = pkgs.linuxPackages_6_17;
zfs.package = pkgs.zfs_unstable;
kernelPackages = pkgs.linuxPackages_6_18;
zfs.package = pkgs.zfs_2_4;
};
hardware.bluetooth = {

View File

@@ -1,129 +0,0 @@
esphome:
name: batteries
friendly_name: batteries
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
encryption:
key: !secret api_key
external_components:
- source: github://syssi/esphome-jk-bms@main
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
captive_portal:
esp32_ble_tracker:
scan_parameters:
interval: 1100ms
window: 1100ms
active: true
ble_client:
- mac_address: "C8:47:80:29:0F:DB"
id: jk_ble0
- mac_address: "C8:47:80:37:9D:DD"
id: jk_ble1
jk_bms_ble:
- ble_client_id: jk_ble0
protocol_version: JK02_32S
throttle: 1s
id: jk_bms0
- ble_client_id: jk_ble1
protocol_version: JK02_32S
throttle: 1s
id: jk_bms1
sensor:
# BMS1 sensors
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms0
total_voltage:
name: "JK0 Total Voltage"
current:
name: "JK0 Current"
state_of_charge:
name: "JK0 SoC"
power:
name: "JK0 Power"
temperature_sensor_1:
name: "JK0 Temp 1"
temperature_sensor_2:
name: "JK0 Temp 2"
balancing:
name: "JK0 balancing"
charging_cycles:
name: "JK0 charging cycles"
total_runtime:
name: "JK0 total runtime"
balancing_current:
name: "JK0 balancing current"
# BMS2 sensors
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms1
total_voltage:
name: "JK1 Total Voltage"
current:
name: "JK1 Current"
state_of_charge:
name: "JK1 SoC"
power:
name: "Jk1 Power"
temperature_sensor_1:
name: "JK1 Temp 1"
temperature_sensor_2:
name: "Jk1 Temp 2"
balancing:
name: "JK1 balancing"
charging_cycles:
name: "JK1 charging cycles"
total_runtime:
name: "JK1 total runtime"
balancing_current:
name: "JK1 balancing current"
text_sensor:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms0
errors:
name: "JK0 Errors"
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms1
errors:
name: "JK1 Errors"
switch:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms0
charging:
name: "JK0 Charging"
discharging:
name: "JK0 Discharging"
balancer:
name: "JK0 Balancing"
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms1
charging:
name: "JK1 Charging"
discharging:
name: "JK1 Discharging"
balancer:
name: "JK1 Balancing"

132
esphome/battery0.yml Normal file
View File

@@ -0,0 +1,132 @@
esphome:
name: batteries
friendly_name: batteries
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
encryption:
key: !secret api_key
external_components:
- source: github://syssi/esphome-jk-bms@main
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: on
captive_portal:
esp32_ble_tracker:
scan_parameters:
interval: 1100ms
window: 1100ms
active: true
ble_client:
- mac_address: "C8:47:80:29:0F:DB"
id: jk_ble0
jk_bms_ble:
- ble_client_id: jk_ble0
protocol_version: JK02_32S
throttle: 1s
id: jk_bms0
button:
- platform: jk_bms_ble
retrieve_settings:
name: "JK0 retrieve settings"
retrieve_device_info:
name: "JK0 retrieve device info"
sensor:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms0
total_voltage:
name: "JK0 Total Voltage"
state_of_charge:
name: "JK0 SoC"
charging_power:
name: "JK0 charging power"
discharging_power:
name: "JK0 discharging power"
temperature_sensor_1:
name: "JK0 Temp 1"
temperature_sensor_2:
name: "JK0 Temp 2"
balancing:
name: "JK0 balancing"
total_runtime:
name: "JK0 total runtime"
balancing_current:
name: "JK0 balancing current"
delta_cell_voltage:
name: "JK0 cell delta voltage"
average_cell_voltage:
name: "JK0 cell average voltage"
cell_voltage_1:
name: "JK0 cell voltage 1"
cell_voltage_2:
name: "JK0 cell voltage 2"
cell_voltage_3:
name: "JK0 cell voltage 3"
cell_voltage_4:
name: "JK0 cell voltage 4"
cell_voltage_5:
name: "JK0 cell voltage 5"
cell_voltage_6:
name: "JK0 cell voltage 6"
cell_voltage_7:
name: "JK0 cell voltage 7"
cell_voltage_8:
name: "JK0 cell voltage 8"
cell_resistance_1:
name: "JK0 cell resistance 1"
cell_resistance_2:
name: "JK0 cell resistance 2"
cell_resistance_3:
name: "JK0 cell resistance 3"
cell_resistance_4:
name: "JK0 cell resistance 4"
cell_resistance_5:
name: "JK0 cell resistance 5"
cell_resistance_6:
name: "JK0 cell resistance 6"
cell_resistance_7:
name: "JK0 cell resistance 7"
cell_resistance_8:
name: "JK0 cell resistance 8"
total_charging_cycle_capacity:
name: "JK0 total charging cycle capacity"
text_sensor:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms0
errors:
name: "JK0 Errors"
switch:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms0
charging:
name: "JK0 Charging"
discharging:
name: "JK0 Discharging"
balancer:
name: "JK0 Balancing"
- platform: ble_client
ble_client_id: jk_ble0
name: "JK0 enable bluetooth connection"
id: ble_client_switch0

132
esphome/battery1.yml Normal file
View File

@@ -0,0 +1,132 @@
esphome:
name: battery1
friendly_name: battery1
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
encryption:
key: !secret api_key
external_components:
- source: github://syssi/esphome-jk-bms@main
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: on
captive_portal:
esp32_ble_tracker:
scan_parameters:
interval: 1100ms
window: 1100ms
active: true
ble_client:
- mac_address: "C8:47:80:37:9D:DD"
id: jk_ble1
jk_bms_ble:
- ble_client_id: jk_ble1
protocol_version: JK02_32S
throttle: 1s
id: jk_bms1
button:
- platform: jk_bms_ble
retrieve_settings:
name: "JK1 retrieve settings"
retrieve_device_info:
name: "JK1 retrieve device info"
sensor:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms1
total_voltage:
name: "JK1 Total Voltage"
state_of_charge:
name: "JK1 SoC"
charging_power:
name: "JK1 charging power"
discharging_power:
name: "JK1 discharging power"
temperature_sensor_1:
name: "JK1 Temp 1"
temperature_sensor_2:
name: "JK1 Temp 2"
balancing:
name: "JK1 balancing"
total_runtime:
name: "JK1 total runtime"
balancing_current:
name: "JK1 balancing current"
delta_cell_voltage:
name: "JK1 cell delta voltage"
average_cell_voltage:
name: "JK1 cell average voltage"
cell_voltage_1:
name: "JK1 cell voltage 1"
cell_voltage_2:
name: "JK1 cell voltage 2"
cell_voltage_3:
name: "JK1 cell voltage 3"
cell_voltage_4:
name: "JK1 cell voltage 4"
cell_voltage_5:
name: "JK1 cell voltage 5"
cell_voltage_6:
name: "JK1 cell voltage 6"
cell_voltage_7:
name: "JK1 cell voltage 7"
cell_voltage_8:
name: "JK1 cell voltage 8"
cell_resistance_1:
name: "JK1 cell resistance 1"
cell_resistance_2:
name: "JK1 cell resistance 2"
cell_resistance_3:
name: "JK1 cell resistance 3"
cell_resistance_4:
name: "JK1 cell resistance 4"
cell_resistance_5:
name: "JK1 cell resistance 5"
cell_resistance_6:
name: "JK1 cell resistance 6"
cell_resistance_7:
name: "JK1 cell resistance 7"
cell_resistance_8:
name: "JK1 cell resistance 8"
total_charging_cycle_capacity:
name: "JK1 total charging cycle capacity"
text_sensor:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms1
errors:
name: "JK1 Errors"
switch:
- platform: jk_bms_ble
jk_bms_ble_id: jk_bms1
charging:
name: "JK1 Charging"
discharging:
name: "JK1 Discharging"
balancer:
name: "JK1 Balancing"
- platform: ble_client
ble_client_id: jk_ble1
name: "JK1 enable bluetooth connection"
id: ble_client_switch0

48
esphome/environment.yml Normal file
View File

@@ -0,0 +1,48 @@
esphome:
name: "environment"
friendly_name: "environment"
esp32:
board: esp32dev
framework:
type: arduino
i2c:
sda: GPIO21
scl: GPIO22
scan: True
id: bus_a
sensor:
- platform: aht10
i2c_id: bus_a
address: 0x38
variant: AHT20
temperature:
name: "environment Temperature"
id: aht10_temperature
humidity:
name: "environment Humidity"
id: aht10_humidity
update_interval: 5s
web_server:
port: 80
logger:
level: DEBUG
api:
encryption:
key: !secret api_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: on
captive_portal:

36
flake.lock generated
View File

@@ -8,11 +8,11 @@
},
"locked": {
"dir": "pkgs/firefox-addons",
"lastModified": 1763697825,
"narHash": "sha256-AgCCcVPOi1tuzuW5/StlwqBjRWSX62oL97qWuxrq5UA=",
"lastModified": 1766762570,
"narHash": "sha256-Nevsj5NYurwp3I6nSMeh3uirwoinVSbCldqOXu4smms=",
"owner": "rycee",
"repo": "nur-expressions",
"rev": "cefce78793603231be226fa77e7ad58e0e4899b8",
"rev": "03d7d310ea91d6e4b47ed70aa86c781fcc5b38e1",
"type": "gitlab"
},
"original": {
@@ -29,11 +29,11 @@
]
},
"locked": {
"lastModified": 1763748372,
"narHash": "sha256-AUc78Qv3sWir0hvbmfXoZ7Jzq9VVL97l+sP9Jgms+JU=",
"lastModified": 1766682973,
"narHash": "sha256-GKO35onS711ThCxwWcfuvbIBKXwriahGqs+WZuJ3v9E=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "d10a9b16b2a3ee28433f3d1c603f4e9f1fecb8e1",
"rev": "91cdb0e2d574c64fae80d221f4bf09d5592e9ec2",
"type": "github"
},
"original": {
@@ -44,11 +44,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1762847253,
"narHash": "sha256-BWWnUUT01lPwCWUvS0p6Px5UOBFeXJ8jR+ZdLX8IbrU=",
"lastModified": 1766568855,
"narHash": "sha256-UXVtN77D7pzKmzOotFTStgZBqpOcf8cO95FcupWp4Zo=",
"owner": "nixos",
"repo": "nixos-hardware",
"rev": "899dc449bc6428b9ee6b3b8f771ca2b0ef945ab9",
"rev": "c5db9569ac9cc70929c268ac461f4003e3e5ca80",
"type": "github"
},
"original": {
@@ -60,11 +60,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1763421233,
"narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=",
"lastModified": 1766651565,
"narHash": "sha256-QEhk0eXgyIqTpJ/ehZKg9IKS7EtlWxF3N7DXy42zPfU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648",
"rev": "3e2499d5539c16d0d173ba53552a4ff8547f4539",
"type": "github"
},
"original": {
@@ -76,11 +76,11 @@
},
"nixpkgs-master": {
"locked": {
"lastModified": 1763774007,
"narHash": "sha256-PPeHfKA11P09kBkBD5pS3tIAFjnG5muHQnODQGTY87g=",
"lastModified": 1766794443,
"narHash": "sha256-Q8IyTQ3Lu8vX/iqO3U+E4pjLbP1NsqFih6uElf8OYrQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "8a7cf7e9e18384533d9ecd0bfbcf475ac1dc497e",
"rev": "088b069b8270ee36d83533c86b9f91d924d185d9",
"type": "github"
},
"original": {
@@ -125,11 +125,11 @@
]
},
"locked": {
"lastModified": 1763607916,
"narHash": "sha256-VefBA1JWRXM929mBAFohFUtQJLUnEwZ2vmYUNkFnSjE=",
"lastModified": 1766289575,
"narHash": "sha256-BOKCwOQQIP4p9z8DasT5r+qjri3x7sPCOq+FTjY8Z+o=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "877bb495a6f8faf0d89fc10bd142c4b7ed2bcc0b",
"rev": "9836912e37aef546029e48c8749834735a6b9dad",
"type": "github"
},
"original": {

View File

@@ -3,14 +3,14 @@
# When applied, the stable nixpkgs set (declared in the flake inputs) will be accessible through 'pkgs.stable'
stable = final: _prev: {
stable = import inputs.nixpkgs-stable {
system = final.system;
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
# When applied, the master nixpkgs set (declared in the flake inputs) will be accessible through 'pkgs.master'
master = final: _prev: {
master = import inputs.nixpkgs-master {
system = final.system;
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};

View File

@@ -1,111 +0,0 @@
"""Safe reboot helper."""
from __future__ import annotations
import logging
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Annotated
import typer
from python.common import bash_wrapper, configure_logger
from python.zfs import Dataset, get_datasets
if TYPE_CHECKING:
from collections.abc import Sequence
logger = logging.getLogger(__name__)
def get_root_pool_datasets(dataset_prefix: str) -> list[Dataset]:
"""Return datasets that start with the provided prefix."""
return [dataset for dataset in get_datasets() if dataset.name.startswith(dataset_prefix)]
def get_non_executable_datasets(datasets: Sequence[Dataset]) -> list[str]:
"""Return dataset names that have exec disabled."""
return [dataset.name for dataset in datasets if dataset.exec.lower() != "on"]
def drive_present(drive: str) -> bool:
"""Check whether the provided drive exists."""
drive_path = drive.strip()
if not drive_path:
error = "Drive path cannot be empty"
raise ValueError(error)
return Path(drive_path).exists()
def reboot_system() -> None:
"""Call systemctl reboot."""
output, return_code = bash_wrapper("systemctl reboot")
if return_code != 0:
raise RuntimeError(output.strip() or "Failed to issue reboot command")
def validate_state(drive: str | None, dataset_prefix: str) -> list[str]:
"""Validate dataset and drive state."""
datasets = get_root_pool_datasets(dataset_prefix)
errors: list[str] = []
if not datasets:
errors.append(f"No datasets found with prefix {dataset_prefix}")
else:
non_exec_datasets = get_non_executable_datasets(datasets)
if non_exec_datasets:
errors.append(f"Datasets missing exec=on: {', '.join(non_exec_datasets)}")
if drive:
try:
if not drive_present(drive):
errors.append(f"Drive {drive} is not present")
except ValueError as err:
errors.append(str(err))
return errors
def reboot(
drive: Annotated[str | None, typer.Argument(help="Drive that must exist before rebooting.")] = None,
dataset_prefix: Annotated[
str,
typer.Option(
"--dataset-prefix",
"-p",
help="Datasets with this prefix are validated.",
),
] = "root_pool/",
dry_run: Annotated[
bool,
typer.Option(
"--check-only",
help="Only validate state without issuing the reboot command.",
),
] = False,
) -> None:
"""Validate datasets and drive before rebooting."""
configure_logger()
logger.info("Starting safe reboot checks")
if errors := validate_state(drive, dataset_prefix):
for error in errors:
logger.error(error)
sys.exit(1)
if dry_run:
logger.info("All checks passed")
return
logger.info("All checks passed, issuing reboot")
reboot_system()
def cli() -> None:
"""CLI entry point."""
typer.run(reboot)
if __name__ == "__main__":
cli()

View File

@@ -6,14 +6,14 @@
default = pkgs.mkShell {
NIX_CONFIG = "extra-experimental-features = nix-command flakes ca-derivations";
nativeBuildInputs = with pkgs; [
nix
home-manager
git
my_python
ssh-to-age
gnupg
age
busybox
git
gnupg
home-manager
my_python
nix
ssh-to-age
];
};
}

View File

@@ -16,7 +16,6 @@
"Qihoo360-Light-R1-32B"
];
models = "/zfs/models";
acceleration = "cuda";
openFirewall = true;
};
# open-webui = {

View File

@@ -60,16 +60,20 @@
extraPackages =
python3Packages: with python3Packages; [
aioesphomeapi # for esphome
aiounifi # for ubiquiti integration
bleak-esphome # for esphome
esphome-dashboard-api # for esphome
forecast-solar # for solar forecast
gtts # not sure what wants this
ical # for todo
jellyfin-apiclient-python # for jellyfin
paho-mqtt # for mqtt
psycopg2 # for postgresql
py-improv-ble-client # for esphome
pymodbus # for modbus
pyopenweathermap # for weather
uiprotect # for ubiquiti integration
unifi-discovery # for ubiquiti integration
];
extraComponents = [ "isal" ];
};

View File

@@ -1,45 +1,7 @@
template:
- sensor:
# Battery 0
- name: "JK0 charge power W"
unique_id: jk0_charge_power_w
unit_of_measurement: W
device_class: power
state_class: measurement
state: >
{% set p = states('sensor.batteries_jk0_power')|float(0) %}
{{ max(0, p) }}
- name: "JK0 discharge power W"
unique_id: jk0_discharge_power_w
unit_of_measurement: W
device_class: power
state_class: measurement
state: >
{% set p = states('sensor.batteries_jk0_power')|float(0) %}
{{ max(0, -p) }}
# Battery 1
- name: "JK1 charge power W"
unique_id: jk1_charge_power_w
unit_of_measurement: W
device_class: power
state_class: measurement
state: >
{% set p = states('sensor.batteries_jk1_power')|float(0) %}
{{ max(0, p) }}
- name: "JK1 discharge power W"
unique_id: jk1_discharge_power_w
unit_of_measurement: W
device_class: power
state_class: measurement
state: >
{% set p = states('sensor.batteries_jk1_power')|float(0) %}
{{ max(0, -p) }}
sensor:
# Battery 0
- platform: integration
source: sensor.jk0_charge_power_w
source: sensor.batteries_jk0_charging_power
name: "JK0 energy in"
unique_id: jk0_energy_in_kwh
unit_prefix: k
@@ -48,7 +10,7 @@ sensor:
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.jk0_discharge_power_w
source: sensor.batteries_jk0_discharging_power
name: "JK0 energy out"
unique_id: jk0_energy_out_kwh
unit_prefix: k
@@ -59,7 +21,7 @@ sensor:
# Battery 1
- platform: integration
source: sensor.jk1_charge_power_w
source: sensor.battery1_jk1_charging_power
name: "JK1 energy in"
unique_id: jk1_energy_in_kwh
unit_prefix: k
@@ -68,7 +30,7 @@ sensor:
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.jk1_discharge_power_w
source: sensor.battery1_jk1_discharging_power
name: "JK1 energy out"
unique_id: jk1_energy_out_kwh
unit_prefix: k

View File

@@ -17,7 +17,6 @@ in
./services
./hardware.nix
./networking.nix
./nvidia.nix
./programs.nix
./runners
./syncthing.nix

View File

@@ -1,47 +0,0 @@
let
vars = import ../vars.nix;
in
{
networking.firewall = {
allowedTCPPorts = [
6882
8081
8118
];
allowedUDPPorts = [ 6882 ];
};
virtualisation.oci-containers.containers.qbitvpn = {
image = "binhex/arch-qbittorrentvpn:5.0.3-1-01";
devices = [ "/dev/net/tun:/dev/net/tun" ];
extraOptions = [ "--cap-add=NET_ADMIN" ];
ports = [
"6882:6881"
"6882:6881/udp"
"8081:8081"
"8118:8118"
];
volumes = [
"${vars.docker_configs}/qbitvpn:/config"
"${vars.qbitvpn}:/data"
"${vars.qbitvpn_scratch}:/data/incomplete"
"/etc/localtime:/etc/localtime:ro"
];
environment = {
WEBUI_PORT = "8081";
PUID = "600";
PGID = "100";
VPN_ENABLED = "yes";
VPN_CLIENT = "openvpn";
STRICT_PORT_FORWARD = "yes";
ENABLE_PRIVOXY = "yes";
LAN_NETWORK = "192.168.90.0/24";
NAME_SERVERS = "1.1.1.1,1.0.0.1";
UMASK = "000";
DEBUG = "false";
DELUGE_DAEMON_LOG_LEVEL = "debug";
DELUGE_WEB_LOG_LEVEL = "debug";
};
environmentFiles = [ "${vars.secrets}/docker/qbitvpn" ];
autoStart = true;
};
}

View File

@@ -71,12 +71,6 @@ in
"luks-media_pool-nvme-INTEL_SSDPE2ME012T4_CVMD5130000U1P2HGN-part1" =
makeLuksSSD "/dev/disk/by-id/nvme-INTEL_SSDPE2ME012T4_CVMD5130000U1P2HGN-part1";
# Scratch pool
"luks-scratch-pool-ata-CT480BX500SSD1_2314E6C3C01C-part1" =
makeLuksSSD "/dev/disk/by-id/ata-CT480BX500SSD1_2314E6C3C01C-part1";
"luks-scratch-pool-ata-CT480BX500SSD1_2314E6C3C01E-part1" =
makeLuksSSD "/dev/disk/by-id/ata-CT480BX500SSD1_2314E6C3C01E-part1";
# Storage pool
"luks-storage_pool-nvme-Samsung_SSD_970_EVO_Plus_2TB_S6S2NS0T834822N-part1" =
makeLuksSSD "/dev/disk/by-id/nvme-Samsung_SSD_970_EVO_Plus_2TB_S6S2NS0T834822N-part1";

View File

@@ -2,31 +2,71 @@
networking = {
hostName = "jeeves";
hostId = "0e15ce35";
firewall.enable = true;
firewall = {
enable = true;
interfaces.br-nix-builder = {
allowedTCPPorts = [ ];
allowedUDPPorts = [ ];
};
};
useNetworkd = true;
};
systemd.network = {
enable = true;
wait-online = {
enable = false;
anyInterface = true;
};
netdevs = {
"20-br-nix-builder" = {
netdevConfig = {
Kind = "bridge";
Name = "br-nix-builder";
};
};
"30-internet-vlan" = {
netdevConfig = {
Kind = "vlan";
Name = "internet-vlan";
};
vlanConfig.Id = 100;
};
};
networks = {
"10-1GB_Primary" = {
matchConfig.Name = "enp98s0f0";
matchConfig.Name = "enp97s0f1";
address = [ "192.168.99.14/24" ];
routes = [ { Gateway = "192.168.99.1"; } ];
vlan = [ "internet-vlan" ];
linkConfig.RequiredForOnline = "routable";
};
"10-1GB_Secondary" = {
matchConfig.Name = "enp98s0f1";
DHCP = "yes";
"50-internet-vlan" = {
matchConfig.Name = "internet-vlan";
bridge = [ "br-nix-builder" ];
linkConfig.RequiredForOnline = "no";
};
"10-10GB_Primary" = {
matchConfig.Name = "enp97s0f0np0";
DHCP = "yes";
linkConfig.RequiredForOnline = "routable";
};
"10-10GB_Secondary" = {
matchConfig.Name = "enp97s0f1np1";
DHCP = "yes";
"60-br-nix-builder" = {
matchConfig.Name = "br-nix-builder";
bridgeConfig = { };
address = [ "192.168.3.10/24" ];
routingPolicyRules = [
{
From = "192.168.3.0/24";
Table = 100;
Priority = 100;
}
];
routes = [
{
Gateway = "192.168.3.1";
Table = 100;
GatewayOnLink = false;
Metric = 2048;
PreferredSource = "192.168.3.10";
}
];
linkConfig.RequiredForOnline = "no";
};
};
};

View File

@@ -1,16 +0,0 @@
{ config, ... }:
{
nixpkgs.config.cudaSupport = true;
services.xserver.videoDrivers = [ "nvidia" ];
hardware = {
nvidia = {
modesetting.enable = true;
powerManagement.enable = true;
package = config.boot.kernelPackages.nvidiaPackages.beta;
nvidiaSettings = true;
open = false;
};
nvidia-container-toolkit.enable = true;
};
}

View File

@@ -16,11 +16,20 @@
};
services.nix_builder.containers = {
nix-builder-0.enable = true;
nix-builder-1.enable = true;
nix-builder-2.enable = true;
nix-builder-3.enable = true;
nix-builder-4.enable = true;
nix-builder-5.enable = true;
nix-builder-00.enable = true;
nix-builder-01.enable = true;
nix-builder-02.enable = true;
nix-builder-03.enable = true;
nix-builder-04.enable = true;
nix-builder-05.enable = true;
nix-builder-06.enable = true;
nix-builder-07.enable = true;
nix-builder-08.enable = true;
nix-builder-09.enable = true;
nix-builder-10.enable = true;
nix-builder-11.enable = true;
nix-builder-12.enable = true;
nix-builder-13.enable = true;
nix-builder-14.enable = true;
};
}

View File

@@ -6,106 +6,131 @@
}:
with lib;
let
vars = import ../vars.nix;
cfg = config.services.nix_builder;
in
{
options.services.nix_builder.containers = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
options.enable = mkEnableOption "GitHub runner container";
}
)
);
default = { };
description = "GitHub runner container configurations";
options.services.nix_builder = {
bridgeName = mkOption {
type = types.str;
default = "br-nix-builder";
description = "Bridge name for the builder containers.";
};
containers = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
options.enable = mkEnableOption "GitHub runner container";
}
)
);
default = { };
description = "GitHub runner container configurations";
};
};
config.containers = mapAttrs (
name: cfg:
mkIf cfg.enable {
autoStart = true;
bindMounts = {
"/storage" = {
mountPoint = "/zfs/media/github-runners/${name}";
isReadOnly = false;
config = {
containers = mapAttrs (
name: containerCfg:
mkIf containerCfg.enable {
autoStart = true;
privateNetwork = true;
hostBridge = cfg.bridgeName;
ephemeral = true;
bindMounts = {
storage = {
hostPath = "/zfs/media/github-runners/${name}";
mountPoint = "/zfs/media/github-runners/${name}";
isReadOnly = false;
};
host-nix = {
mountPoint = "/host-nix/var/nix/daemon-socket";
hostPath = "/nix/var/nix/daemon-socket";
isReadOnly = false;
};
pat = {
hostPath = "${vars.secrets}/services/github-runners/runner_pat";
mountPoint = "${vars.secrets}/services/github-runners/runner_pat";
isReadOnly = true;
};
};
"/secrets".mountPoint = "${vars.secrets}/services/github-runners/${name}";
"ssh-keys".mountPoint = "${vars.secrets}/services/github-runners/id_ed25519_github-runners";
};
config =
{
config,
pkgs,
lib,
...
}:
{
nix.settings = {
trusted-substituters = [
"https://cache.nixos.org"
"https://cache.tmmworkshop.com"
"https://nix-community.cachix.org"
];
substituters = [
"https://cache.nixos.org/?priority=2&want-mass-query=true"
"https://cache.tmmworkshop.com/?priority=2&want-mass-query=true"
"https://nix-community.cachix.org/?priority=10&want-mass-query=true"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"cache.tmmworkshop.com:jHffkpgbmEdstQPoihJPYW9TQe6jnQbWR2LqkNGV3iA="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
experimental-features = [
"flakes"
"nix-command"
];
};
programs.ssh.extraConfig = ''
Host jeeves
Port 629
User github-runners
HostName jeeves
IdentityFile ${vars.secrets}/services/github-runners/id_ed25519_github-runners
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
'';
nixpkgs = {
overlays = builtins.attrValues outputs.overlays;
config.allowUnfree = true;
};
services.github-runners.${name} = {
enable = true;
replace = true;
workDir = "/zfs/media/github-runners/${name}";
url = "https://github.com/RichieCahill/dotfiles";
extraLabels = [ "nixos" ];
tokenFile = "${vars.secrets}/services/github-runners/${name}";
user = "github-runners";
group = "github-runners";
extraPackages = with pkgs; [
nixfmt-rfc-style
nixos-rebuild
openssh
treefmt
my_python
];
};
users = {
users.github-runners = {
shell = pkgs.bash;
isSystemUser = true;
group = "github-runners";
uid = 601;
config =
{
config,
pkgs,
lib,
...
}:
{
networking = {
useDHCP = lib.mkDefault true;
interfaces.eth0.useDHCP = true;
# Ensure containers don't inherit the host's stub resolver (127.0.0.53) which was causing issues
useHostResolvConf = false;
};
groups.github-runners.gid = 601;
nix.settings = {
trusted-substituters = [
"https://cache.nixos.org"
"https://cache.tmmworkshop.com"
"https://nix-community.cachix.org"
];
substituters = [
"https://cache.nixos.org/?priority=2&want-mass-query=true"
"https://cache.tmmworkshop.com/?priority=2&want-mass-query=true"
"https://nix-community.cachix.org/?priority=10&want-mass-query=true"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"cache.tmmworkshop.com:jHffkpgbmEdstQPoihJPYW9TQe6jnQbWR2LqkNGV3iA="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
experimental-features = [
"flakes"
"nix-command"
];
sandbox = true;
allowed-users = [ "github-runners" ];
trusted-users = [
"root"
"github-runners"
];
};
nixpkgs = {
overlays = builtins.attrValues outputs.overlays;
config.allowUnfree = true;
};
services.github-runners.${name} = {
enable = true;
replace = true;
workDir = "/zfs/media/github-runners/${name}";
url = "https://github.com/RichieCahill/dotfiles";
extraLabels = [ "nixos" ];
tokenFile = "${vars.secrets}/services/github-runners/runner_pat";
user = "github-runners";
group = "github-runners";
extraPackages = with pkgs; [
busybox
nixfmt-rfc-style
nixos-rebuild
treefmt
my_python
];
};
users = {
users.github-runners = {
shell = pkgs.bash;
isSystemUser = true;
group = "github-runners";
uid = 601;
};
groups.github-runners.gid = 601;
};
system.stateVersion = "24.05";
};
system.stateVersion = "24.11";
};
}
) config.services.nix_builder.containers;
}
) cfg.containers;
};
}

View File

@@ -12,7 +12,7 @@ sudo zpool add storage -o ashift=12 special mirror
sudo zpool add storage -o ashift=12 logs mirror
# scratch
sudo zpool create -o ashift=12 -O acltype=posixacl -O atime=off -O dnodesize=auto -O xattr=sa -O compression=zstd -m /zfs/scratch scratch
sudo zpool create scratch -o ashift=12 -O acltype=posixacl -O atime=off -O dnodesize=auto -O xattr=sa -O compression=zstd -O encryption=aes-256-gcm -O keyformat=hex -O keylocation=file:///key -m /zfs/scratch
# media datasets
sudo zfs create -o compression=zstd-9 media/docker
@@ -25,9 +25,8 @@ sudo zfs create -o exec=off media/share
sudo zfs create -o recordsize=16k -o primarycache=metadata -o mountpoint=/zfs/media/database/postgres media/postgres
# scratch datasets
sudo zfs create -o recordsize=16k -o sync=disabled scratch/qbitvpn
sudo zfs create -o recordsize=16k -o sync=disabled scratch/transmission
sudo zfs create -o recordsize=1M scratch/kafka
sudo zfs create scratch/kafka -o mountpoint=/zfs/scratch/kafka -o recordsize=1M
sudo zfs create scratch/transmission -o mountpoint=/zfs/scratch/transmission -o recordsize=16k -o sync=disabled
# storage datasets
sudo zfs create -o recordsize=1M -o compression=zstd-19 storage/archive

View File

@@ -1,21 +0,0 @@
{
pkgs,
...
}:
let
vars = import ../vars.nix;
in
{
systemd.services.filebrowser = {
description = "filebrowser";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = "richie";
Group = "users";
ExecStart = "${pkgs.filebrowser}/bin/filebrowser --root=/zfs --address=0.0.0.0 --database=${vars.docker_configs}/filebrowser/filebrowser.db";
Restart = "on-failure";
};
};
}

View File

@@ -0,0 +1,46 @@
let
vars = import ../vars.nix;
in
{
networking.firewall.allowedTCPPorts = [ 6443 ];
services.gitea = {
enable = true;
appName = "TMM Workshop";
stateDir = "${vars.services}/gitea/";
lfs.enable = true;
database = {
type = "postgres";
name = "gitea";
user = "gitea";
socket = "/run/postgresql";
port = 5432;
createDatabase = false;
};
settings = {
service.DISABLE_REGISTRATION = true;
server = {
DOMAIN = "tmmworkshop.com";
ROOT_URL = "https://gitea.tmmworkshop.com/";
HTTP_PORT = 6443;
SSH_PORT = 2223;
SSH_LISTEN_PORT = 2224;
START_SSH_SERVER = true;
PUBLIC_URL_DETECTION = "auto";
};
repository = {
ENABLE_PUSH_CREATE_USER = true;
DEFAULT_MERGE_STYLE = "rebase-merge";
};
log = {
LEVEL = "Trace";
ENABLE_SSH_LOG = true;
};
};
};
systemd.services.gitea = {
requires = [ "docker.service" ];
after = [ "docker.service" ];
};
}

View File

@@ -27,21 +27,21 @@ frontend ContentSwitching
# tmmworkshop.com
acl host_audiobookshelf hdr(host) -i audiobookshelf.tmmworkshop.com
acl host_cache hdr(host) -i cache.tmmworkshop.com
acl host_filebrowser hdr(host) -i filebrowser.tmmworkshop.com
acl host_homeassistant hdr(host) -i homeassistant.tmmworkshop.com
acl host_jellyfin hdr(host) -i jellyfin.tmmworkshop.com
acl host_share hdr(host) -i share.tmmworkshop.com
acl host_gcw hdr(host) -i gcw.tmmworkshop.com
acl host_n8n hdr(host) -i n8n.tmmworkshop.com
acl host_gitea hdr(host) -i gitea.tmmworkshop.com
use_backend audiobookshelf_nodes if host_audiobookshelf
use_backend cache_nodes if host_cache
use_backend filebrowser_nodes if host_filebrowser
use_backend homeassistant_nodes if host_homeassistant
use_backend jellyfin if host_jellyfin
use_backend share_nodes if host_share
use_backend gcw_nodes if host_gcw
use_backend n8n if host_n8n
use_backend gitea if host_gitea
backend audiobookshelf_nodes
mode http
@@ -51,10 +51,6 @@ backend cache_nodes
mode http
server server 127.0.0.1:5000
backend filebrowser_nodes
mode http
server server 127.0.0.1:8080
backend homeassistant_nodes
mode http
server server 192.168.90.35:8123
@@ -77,3 +73,7 @@ backend gcw_nodes
backend n8n
mode http
server server 127.0.0.1:5678
backend gitea
mode http
server server 127.0.0.1:6443

View File

@@ -28,13 +28,7 @@ in
#type database DBuser origin-address auth-method
local hass hass trust
# ipv4
host hass hass 192.168.90.1/24 trust
host hass hass 127.0.0.1/32 trust
# ipv6
host hass hass ::1/128 trust
local gitea gitea trust
# megan
host megan megan 192.168.90.1/24 trust
@@ -96,6 +90,16 @@ in
replication = true;
};
}
{
name = "gitea";
ensureDBOwnership = true;
ensureClauses = {
login = true;
createrole = true;
createdb = true;
replication = true;
};
}
{
name = "megan";
ensureDBOwnership = true;
@@ -130,6 +134,7 @@ in
ensureDatabases = [
"gcw"
"hass"
"gitea"
"math"
"megan"
"mxr_dev"

View File

@@ -3,9 +3,7 @@ services = [
"audiobookshelf",
"cloud_flare_tunnel",
"haproxy",
"docker-qbitvpn",
"docker",
"filebrowser",
"home-assistant",
"jellyfin",
]

View File

@@ -64,24 +64,12 @@ hourly = 12
daily = 14
monthly = 2
["scratch/qbitvpn"]
15_min = 0
hourly = 0
daily = 0
monthly = 0
["scratch/transmission"]
15_min = 0
hourly = 0
daily = 0
monthly = 0
["storage/qbitvpn"]
15_min = 0
hourly = 0
daily = 0
monthly = 0
["storage/transmission"]
15_min = 0
hourly = 0

View File

@@ -10,8 +10,6 @@ in
docker_configs = "${zfs_media}/docker/configs";
home_assistant = "${zfs_media}/home_assistant";
notes = "${zfs_media}/notes";
qbitvpn = "${zfs_storage}/qbitvpn";
qbitvpn_scratch = "${zfs_scratch}/qbitvpn";
secrets = "${zfs_storage}/secrets";
services = "${zfs_media}/services";
share = "${zfs_media}/share";

View File

@@ -13,6 +13,7 @@
./hardware.nix
./llms.nix
./syncthing.nix
./qmk.nix
inputs.nixos-hardware.nixosModules.framework-13-7040-amd
];

View File

@@ -0,0 +1,34 @@
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
qmk
vial
];
services = {
udev = {
packages = [ pkgs.qmk-udev-rules ];
extraRules = ''
# Keychron keyboards
KERNEL=="hidraw*", ATTRS{idVendor}=="3434", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="3434", MODE="0660", GROUP="plugdev"
# Some boards use 32f0 as vendor id
KERNEL=="hidraw*", ATTRS{idVendor}=="32f0", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="32f0", MODE="0660", GROUP="plugdev"
# Keychron HID device permissions
SUBSYSTEM=="usb", ATTR{idVendor}=="3434", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="3434", MODE="0660", GROUP="plugdev"
KERNEL=="hidraw*", ATTRS{idVendor}=="32f0", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="32f0", MODE="0660", GROUP="plugdev"
# Keychron / QMK common bootloaders
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="df11", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="03eb", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", MODE="0660", GROUP="plugdev"
'';
};
};
}

View File

@@ -57,5 +57,5 @@ def test_test_bash_wrapper_error() -> None:
"""test_test_bash_wrapper_error."""
expected_error = 2
stdout, returncode = bash_wrapper("ls /this/path/does/not/exist")
assert stdout == "ls: cannot access '/this/path/does/not/exist': No such file or directory\n"
assert stdout == "ls: /this/path/does/not/exist: No such file or directory\n"
assert returncode == expected_error

View File

@@ -1,96 +0,0 @@
"""Tests for safe_reboot."""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from python.tools.safe_reboot import reboot
from python.zfs.dataset import Dataset
if TYPE_CHECKING:
from pytest_mock import MockerFixture
SAFE_REBOOT = "python.tools.safe_reboot"
def create_dataset(mocker: MockerFixture, name: str, exec_state: str) -> Dataset:
"""Create a mock dataset."""
dataset = mocker.MagicMock(spec=Dataset)
dataset.name = name
dataset.exec = exec_state
return dataset
def test_reboot_reboots_when_checks_pass(mocker: MockerFixture) -> None:
"""The command should reboot when all checks pass."""
dataset = create_dataset(mocker, "root_pool/root", "on")
mocker.patch(f"{SAFE_REBOOT}.get_datasets", return_value=(dataset,))
mocker.patch(f"{SAFE_REBOOT}.drive_present", return_value=True)
mock_bash = mocker.patch(f"{SAFE_REBOOT}.bash_wrapper", return_value=("", 0))
reboot("/dev/disk/root-drive")
mock_bash.assert_called_once_with("systemctl reboot")
def test_reboot_reboots_without_drive_requirement(mocker: MockerFixture) -> None:
"""The command should reboot even when no drive is provided."""
dataset = create_dataset(mocker, "root_pool/root", "on")
mocker.patch(f"{SAFE_REBOOT}.get_datasets", return_value=(dataset,))
mock_bash = mocker.patch(f"{SAFE_REBOOT}.bash_wrapper", return_value=("", 0))
reboot(None)
mock_bash.assert_called_once_with("systemctl reboot")
def test_reboot_errors_on_non_exec_dataset(mocker: MockerFixture) -> None:
"""The command should exit when a dataset lacks exec."""
dataset = create_dataset(mocker, "root_pool/root", "off")
mocker.patch(f"{SAFE_REBOOT}.get_datasets", return_value=(dataset,))
mocker.patch(f"{SAFE_REBOOT}.drive_present", return_value=True)
mocker.patch(f"{SAFE_REBOOT}.bash_wrapper", return_value=("", 0))
with pytest.raises(SystemExit) as excinfo:
reboot("/dev/disk/root-drive")
assert excinfo.value.code == 1
def test_reboot_errors_when_driver_missing(mocker: MockerFixture) -> None:
"""The command should exit when the requested driver is absent."""
dataset = create_dataset(mocker, "root_pool/root", "on")
mocker.patch(f"{SAFE_REBOOT}.get_datasets", return_value=(dataset,))
mocker.patch(f"{SAFE_REBOOT}.drive_present", return_value=False)
mocker.patch(f"{SAFE_REBOOT}.bash_wrapper", return_value=("", 0))
with pytest.raises(SystemExit) as excinfo:
reboot("/dev/disk/root-drive")
assert excinfo.value.code == 1
def test_reboot_errors_when_no_datasets_found(mocker: MockerFixture) -> None:
"""The command should exit when no datasets match the prefix."""
mocker.patch(f"{SAFE_REBOOT}.get_datasets", return_value=())
mocker.patch(f"{SAFE_REBOOT}.drive_present", return_value=True)
mocker.patch(f"{SAFE_REBOOT}.bash_wrapper", return_value=("", 0))
with pytest.raises(SystemExit) as excinfo:
reboot("/dev/disk/root-drive")
assert excinfo.value.code == 1
def test_reboot_check_only_skips_reboot(mocker: MockerFixture) -> None:
"""The command should only validate when --check-only is provided."""
dataset = create_dataset(mocker, "root_pool/root", "on")
mocker.patch(f"{SAFE_REBOOT}.get_datasets", return_value=(dataset,))
mocker.patch(f"{SAFE_REBOOT}.drive_present", return_value=True)
mock_bash = mocker.patch(f"{SAFE_REBOOT}.bash_wrapper", return_value=("", 0))
reboot("/dev/disk/root-drive", check_only=True)
mock_bash.assert_not_called()

View File

@@ -41,6 +41,7 @@ in
"scanner"
"transmission"
"uaccess"
"uucp"
"wireshark"
];
uid = 1000;

View File

@@ -9,23 +9,24 @@
home.packages = with pkgs; [
candy-icons
chromium
discord-canary
gimp
gparted
jetbrains.datagrip
mediainfo
nemo
nemo-fileroller
obs-studio
obsidian
prismlauncher
proxychains
prusa-slicer
signal-desktop
sweet-nova
util-linux
vlc
# comms
discord-canary
signal-desktop
zoom-us
# dev tools
jetbrains.datagrip
proxychains
master.antigravity-fhs
gparted
# games
dwarf-fortress
tower-pixel-dungeon

View File

@@ -4,6 +4,7 @@
# cli
bat
btop
busybox
eza
fd
ffmpegthumbnailer
@@ -21,7 +22,6 @@
ripgrep
starship
tmux
unzip
yazi
zoxide
# Home Assistant
@@ -31,13 +31,11 @@
# system info
hwloc
lynis
pciutils
smartmontools
usbutils
# networking
iperf3
nmap
wget
# python
poetry
ruff