Compare commits

...
Author SHA1 Message Date
Richie 48ee057b5b refactor(signal): replace Apprise with httpx
build_systems / build-portal-1 (pull_request) Successful in 27s
build_systems / build-brain (pull_request) Successful in 1m1s
build_systems / build-bob (pull_request) Successful in 1m6s
build_systems / build-jeeves (pull_request) Successful in 1m9s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m15s
test ebook search / test-ebook-search (pull_request) Successful in 1m15s
treefmt / nix fmt (pull_request) Successful in 4s
2026-09-19 22:28:55 -04:00
Richie e18cb7b963 chore: drop Python dependency declarations
treefmt / nix fmt (pull_request) Successful in 4s
build_systems / build-portal-1 (pull_request) Successful in 34s
build_systems / build-bob (pull_request) Successful in 53s
build_systems / build-brain (pull_request) Successful in 54s
test ebook search / test-ebook-search (pull_request) Successful in 1m11s
build_systems / build-jeeves (pull_request) Successful in 1m27s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m37s
treefmt / nix fmt (push) Successful in 2s
build_systems / build-portal-1 (push) Successful in 18s
build_systems / build-brain (push) Successful in 29s
build_systems / build-bob (push) Successful in 29s
build_systems / build-jeeves (push) Successful in 33s
build_systems / build-rhapsody-in-green (push) Successful in 39s
test ebook search / test-ebook-search (push) Successful in 1m6s
2026-09-19 21:08:15 -04:00
Richie ac4746277d test(services): run tests during system builds 2026-09-19 21:05:41 -04:00
Richie e2c240ba4b refactor(python): use dedicated runtime environments 2026-09-19 21:01:23 -04:00
Richie 545115725c refactor(jeeves): remove startup validation 2026-09-19 20:51:49 -04:00
Richie 9dbb8f69a8 deleted dead file 2026-09-19 20:47:14 -04:00
Richie 3614428e3d (feat)removing firefox
treefmt / nix fmt (pull_request) Successful in 4s
build_systems / build-portal-1 (pull_request) Successful in 31s
build_systems / build-brain (pull_request) Successful in 53s
build_systems / build-bob (pull_request) Successful in 54s
build_systems / build-jeeves (pull_request) Successful in 58s
pytest / pytest (pull_request) Successful in 1m6s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m12s
test ebook search / test-ebook-search (pull_request) Successful in 1m18s
treefmt / nix fmt (push) Successful in 4s
build_systems / build-jeeves (push) Successful in 7s
build_systems / build-portal-1 (push) Successful in 19s
build_systems / build-brain (push) Successful in 28s
build_systems / build-bob (push) Successful in 31s
build_systems / build-rhapsody-in-green (push) Successful in 40s
pytest / pytest (push) Successful in 53s
test ebook search / test-ebook-search (push) Successful in 1m15s
im not using firefox any more and with the move to x86 v3 1 less browser build is nice
2026-09-18 09:38:53 -04:00
Richie 7cc87ff278 more x86-64-v3 workarounds and text exclushions
treefmt / nix fmt (pull_request) Successful in 4s
test ebook search / test-ebook-search (pull_request) Successful in 1m5s
pytest / pytest (pull_request) Successful in 48s
build_systems / build-portal-1 (pull_request) Successful in 18s
build_systems / build-brain (pull_request) Successful in 26s
build_systems / build-bob (pull_request) Successful in 27s
build_systems / build-jeeves (pull_request) Successful in 31s
build_systems / build-rhapsody-in-green (pull_request) Successful in 38s
treefmt / nix fmt (push) Successful in 3s
build_systems / build-portal-1 (push) Successful in 21s
build_systems / build-brain (push) Successful in 31s
build_systems / build-bob (push) Successful in 33s
build_systems / build-jeeves (push) Successful in 38s
build_systems / build-rhapsody-in-green (push) Successful in 47s
pytest / pytest (push) Successful in 56s
test ebook search / test-ebook-search (push) Successful in 1m14s
2026-09-17 08:44:18 -04:00
Richie 224c75768b moved modbus to extraComponents
modbus intgration requres more then just pymodbus bus now
2026-09-17 08:35:35 -04:00
Richie 562c92ae77 more test issues 2026-09-17 08:35:35 -04:00
Richie dd4a0e1f9c more test-exclusions 2026-09-17 08:35:35 -04:00
Richie c56aa0ab3d fix(overlays): stabilize timing-sensitive tests 2026-09-17 08:35:35 -04:00
Richie 21066116b0 testing numprocesses to fix pytest-xdist tests 2026-09-17 08:35:35 -04:00
Richie 0b333552e6 fix(overlays): exclude failing x86-64-v3 tests
Skip the flaky GnuTLS UDP readiness test and the SciPy FFT tests whose
precision differences are acceptable for our workloads.
2026-09-17 08:35:35 -04:00
Richie b1c21438d9 removed hostPlatform from portal-1 2026-09-17 08:35:35 -04:00
Richie 0f794411fd testing x86_v3 2026-09-17 08:35:35 -04:00
Richie f9049353e2 removing open_webui
treefmt / nix fmt (pull_request) Successful in 4s
build_systems / build-portal-1 (pull_request) Successful in 27s
build_systems / build-brain (pull_request) Successful in 48s
build_systems / build-bob (pull_request) Successful in 52s
build_systems / build-jeeves (pull_request) Successful in 53s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m4s
pytest / pytest (pull_request) Successful in 1m17s
test ebook search / test-ebook-search (pull_request) Successful in 1m21s
build_systems / build-brain (push) Successful in 34s
build_systems / build-jeeves (push) Successful in 44s
build_systems / build-rhapsody-in-green (push) Successful in 50s
pytest / pytest (push) Successful in 1m1s
treefmt / nix fmt (push) Successful in 2s
build_systems / build-portal-1 (push) Successful in 22s
build_systems / build-bob (push) Successful in 38s
test ebook search / test-ebook-search (push) Successful in 1m15s
2026-09-16 20:26:47 -04:00
Richie f905b6e270 removing camofox-browser.nix
treefmt / nix fmt (pull_request) Successful in 4s
build_systems / build-portal-1 (pull_request) Successful in 36s
build_systems / build-brain (pull_request) Successful in 1m6s
pytest / pytest (pull_request) Successful in 1m8s
build_systems / build-bob (pull_request) Successful in 1m12s
test ebook search / test-ebook-search (pull_request) Successful in 1m23s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m30s
build_systems / build-jeeves (pull_request) Successful in 1m32s
treefmt / nix fmt (push) Successful in 3s
build_systems / build-portal-1 (push) Successful in 33s
build_systems / build-brain (push) Successful in 52s
build_systems / build-bob (push) Successful in 57s
build_systems / build-jeeves (push) Successful in 1m5s
pytest / pytest (push) Successful in 1m16s
build_systems / build-rhapsody-in-green (push) Successful in 1m20s
test ebook search / test-ebook-search (push) Successful in 1m23s
2026-09-13 12:32:35 -04:00
Richie f1f027abfe removed steve
pytest / pytest (push) Successful in 1m17s
build_systems / build-brain (pull_request) Successful in 1m7s
pytest / pytest (pull_request) Successful in 1m6s
build_systems / build-bob (pull_request) Successful in 1m37s
test ebook search / test-ebook-search (pull_request) Successful in 1m22s
build_systems / build-portal-1 (pull_request) Successful in 37s
treefmt / nix fmt (pull_request) Successful in 4s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m46s
build_systems / build-jeeves (pull_request) Successful in 1m52s
treefmt / nix fmt (push) Successful in 5s
build_systems / build-portal-1 (push) Successful in 40s
build_systems / build-brain (push) Successful in 1m0s
build_systems / build-bob (push) Successful in 1m9s
test ebook search / test-ebook-search (push) Successful in 1m17s
build_systems / build-jeeves (push) Successful in 1m43s
build_systems / build-rhapsody-in-green (push) Successful in 1m38s
2026-09-12 11:20:15 -04:00
Richie 0243da86f2 refactor(home): trim user packages and move GUI modules into Richie's config 2026-09-12 11:20:15 -04:00
Richie 1803da420e feat(jeeves): limit builder CPU usage and tune Nix parallelism
treefmt / nix fmt (pull_request) Successful in 5s
pytest / pytest (pull_request) Successful in 57s
build_systems / build-portal-1 (pull_request) Successful in 1m4s
test ebook search / test-ebook-search (pull_request) Successful in 1m11s
build_systems / build-brain (pull_request) Successful in 1m49s
build_systems / build-bob (pull_request) Successful in 1m52s
build_systems / build-rhapsody-in-green (pull_request) Successful in 2m14s
build_systems / build-jeeves (pull_request) Successful in 2m32s
treefmt / nix fmt (push) Successful in 4s
build_systems / build-jeeves (push) Successful in 8s
build_systems / build-portal-1 (push) Successful in 18s
build_systems / build-brain (push) Successful in 30s
build_systems / build-bob (push) Successful in 34s
build_systems / build-rhapsody-in-green (push) Successful in 45s
pytest / pytest (push) Successful in 54s
test ebook search / test-ebook-search (push) Successful in 1m10s
2026-09-12 09:47:35 -04:00
Richie 2fb3a0d3ba feat(gitea): allow 12-hour runner builds
treefmt / nix fmt (pull_request) Successful in 4s
pytest / pytest (pull_request) Successful in 1m12s
treefmt / nix fmt (push) Successful in 5s
test ebook search / test-ebook-search (pull_request) Successful in 1m34s
build_systems / build-portal-1 (pull_request) Successful in 1m38s
build_systems / build-portal-1 (push) Successful in 38s
build_systems / build-brain (pull_request) Successful in 2m7s
pytest / pytest (push) Successful in 46s
build_systems / build-bob (pull_request) Successful in 2m9s
build_systems / build-brain (push) Successful in 51s
build_systems / build-bob (push) Successful in 56s
test ebook search / test-ebook-search (push) Successful in 54s
build_systems / build-rhapsody-in-green (pull_request) Successful in 2m18s
build_systems / build-rhapsody-in-green (push) Successful in 1m7s
build_systems / build-jeeves (pull_request) Successful in 2m40s
build_systems / build-jeeves (push) Successful in 1m23s
2026-09-12 09:09:30 -04:00
Richie 4154640637 feat(jeeves): move container state to dedicated ZFS dataset
treefmt / nix fmt (pull_request) Successful in 4s
pytest / pytest (pull_request) Successful in 1m30s
test ebook search / test-ebook-search (pull_request) Successful in 1m28s
build_systems / build-portal-1 (pull_request) Successful in 2m51s
build_systems / build-bob (pull_request) Successful in 3m43s
build_systems / build-jeeves (pull_request) Successful in 3m43s
build_systems / build-brain (pull_request) Successful in 3m29s
build_systems / build-rhapsody-in-green (pull_request) Successful in 3m54s
treefmt / nix fmt (push) Successful in 4s
build_systems / build-portal-1 (push) Successful in 24s
pytest / pytest (push) Successful in 33s
build_systems / build-brain (push) Successful in 41s
test ebook search / test-ebook-search (push) Successful in 38s
build_systems / build-bob (push) Successful in 46s
build_systems / build-jeeves (push) Successful in 1m2s
build_systems / build-rhapsody-in-green (push) Successful in 1m5s
- add a performance-tuned media/containers dataset
- store Docker and NixOS container state on the dataset
- prepare storage directories after ZFS mounts
- bind NixOS container state to its required path
- configure snapshot retention for container storage
2026-09-11 14:58:41 -04:00
Richie 78641f78dd updating snapshot config
treefmt / nix fmt (pull_request) Successful in 6s
pytest / pytest (pull_request) Successful in 1m2s
test ebook search / test-ebook-search (pull_request) Successful in 1m10s
build_systems / build-portal-1 (pull_request) Successful in 1m15s
build_systems / build-brain (pull_request) Successful in 2m5s
build_systems / build-bob (pull_request) Successful in 2m10s
build_systems / build-jeeves (pull_request) Successful in 2m32s
build_systems / build-rhapsody-in-green (pull_request) Successful in 2m34s
treefmt / nix fmt (push) Successful in 6s
build_systems / build-jeeves (push) Successful in 17s
build_systems / build-rhapsody-in-green (push) Successful in 23s
build_systems / build-portal-1 (push) Successful in 39s
pytest / pytest (push) Successful in 51s
build_systems / build-brain (push) Successful in 57s
test ebook search / test-ebook-search (push) Successful in 59s
build_systems / build-bob (push) Successful in 1m1s
2026-09-11 12:37:47 -04:00
Richie 0e09b1db34 feat(zfs): mount dedicated Nix build dataset
treefmt / nix fmt (pull_request) Successful in 4s
build_systems / build-portal-1 (pull_request) Successful in 33s
pytest / pytest (pull_request) Successful in 34s
test ebook search / test-ebook-search (pull_request) Successful in 39s
build_systems / build-brain (pull_request) Successful in 59s
build_systems / build-jeeves (pull_request) Successful in 1m16s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m16s
build_systems / build-bob (pull_request) Successful in 1m4s
treefmt / nix fmt (push) Successful in 5s
build_systems / build-rhapsody-in-green (push) Successful in 18s
build_systems / build-portal-1 (push) Successful in 49s
pytest / pytest (push) Successful in 1m1s
test ebook search / test-ebook-search (push) Successful in 1m18s
build_systems / build-bob (push) Successful in 1m33s
build_systems / build-brain (push) Successful in 1m46s
build_systems / build-jeeves (push) Successful in 2m30s
Mount root_pool/nix_build at /nix/var/nix/builds on existing hosts and
generate the same layout for new installations.
2026-09-11 12:07:24 -04:00
Richie 82a3bdf562 temp v3 suport 2026-09-11 12:00:43 -04:00
Richie 46cb80f809 feat(installer) adding root_pool/nix_build 2026-09-11 10:47:52 -04:00
Richie bce4ccb754 config(docker): disable live restore
treefmt / nix fmt (pull_request) Successful in 7s
pytest / pytest (pull_request) Successful in 1m40s
test ebook search / test-ebook-search (pull_request) Successful in 1m40s
build_systems / build-portal-1 (pull_request) Successful in 2m40s
build_systems / build-brain (pull_request) Successful in 3m43s
build_systems / build-bob (pull_request) Successful in 4m0s
build_systems / build-rhapsody-in-green (pull_request) Successful in 4m19s
build_systems / build-jeeves (pull_request) Successful in 4m37s
treefmt / nix fmt (push) Successful in 3s
build_systems / build-portal-1 (push) Successful in 22s
pytest / pytest (push) Successful in 31s
test ebook search / test-ebook-search (push) Successful in 32s
build_systems / build-brain (push) Successful in 35s
build_systems / build-bob (push) Successful in 39s
build_systems / build-rhapsody-in-green (push) Successful in 48s
build_systems / build-jeeves (push) Successful in 52s
2026-09-10 07:26:34 -04:00
Richie 7c7740fe90 remove dov
treefmt / nix fmt (pull_request) Successful in 6s
pytest / pytest (pull_request) Successful in 57s
test ebook search / test-ebook-search (pull_request) Successful in 1m1s
build_systems / build-portal-1 (pull_request) Successful in 1m7s
build_systems / build-brain (pull_request) Successful in 1m25s
build_systems / build-bob (pull_request) Successful in 1m26s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m33s
build_systems / build-jeeves (pull_request) Successful in 1m45s
treefmt / nix fmt (push) Successful in 4s
build_systems / build-jeeves (push) Successful in 7s
build_systems / build-portal-1 (push) Successful in 24s
test ebook search / test-ebook-search (push) Successful in 31s
pytest / pytest (push) Successful in 32s
build_systems / build-brain (push) Successful in 35s
build_systems / build-bob (push) Successful in 38s
build_systems / build-rhapsody-in-green (push) Successful in 50s
2026-09-09 11:06:44 -04:00
Richie a59ccd293d feat(jeeves): enable x86-64 v2 and v3 system features
treefmt / nix fmt (pull_request) Successful in 6s
build_systems / build-portal-1 (pull_request) Successful in 31s
pytest / pytest (pull_request) Successful in 33s
test ebook search / test-ebook-search (pull_request) Successful in 36s
build_systems / build-brain (pull_request) Successful in 53s
build_systems / build-bob (pull_request) Successful in 53s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m5s
build_systems / build-jeeves (pull_request) Successful in 1m8s
treefmt / nix fmt (push) Successful in 5s
build_systems / build-jeeves (push) Successful in 10s
build_systems / build-portal-1 (push) Successful in 22s
pytest / pytest (push) Successful in 32s
test ebook search / test-ebook-search (push) Successful in 34s
build_systems / build-bob (push) Successful in 41s
build_systems / build-brain (push) Successful in 36s
build_systems / build-rhapsody-in-green (push) Successful in 51s
2026-09-08 19:59:14 -04:00
Richie 5514047eda feat(jeeves): share one system closure across builders
treefmt / nix fmt (pull_request) Successful in 5s
build_systems / build-portal-1 (pull_request) Successful in 32s
pytest / pytest (pull_request) Successful in 32s
test ebook search / test-ebook-search (pull_request) Successful in 36s
build_systems / build-brain (pull_request) Successful in 54s
build_systems / build-bob (pull_request) Successful in 55s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m6s
build_systems / build-jeeves (pull_request) Successful in 1m58s
treefmt / nix fmt (push) Successful in 5s
build_systems / build-portal-1 (push) Successful in 24s
pytest / pytest (push) Successful in 32s
build_systems / build-brain (push) Successful in 37s
test ebook search / test-ebook-search (push) Successful in 35s
build_systems / build-bob (push) Successful in 41s
build_systems / build-rhapsody-in-green (push) Successful in 50s
build_systems / build-jeeves (push) Successful in 53s
Reuse a single NixOS container build for all runners while preserving
unique runtime hostnames and existing Gitea registrations.
2026-09-08 19:37:18 -04:00
Richie 810f9a66f6 flake update 09-08-26
treefmt / nix fmt (pull_request) Successful in 5s
build_systems / build-portal-1 (pull_request) Successful in 30s
pytest / pytest (pull_request) Successful in 32s
test ebook search / test-ebook-search (pull_request) Successful in 37s
build_systems / build-brain (pull_request) Successful in 49s
build_systems / build-bob (pull_request) Successful in 55s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m6s
build_systems / build-jeeves (pull_request) Successful in 2m33s
treefmt / nix fmt (push) Successful in 5s
build_systems / build-portal-1 (push) Successful in 24s
pytest / pytest (push) Successful in 32s
build_systems / build-brain (push) Successful in 38s
test ebook search / test-ebook-search (push) Successful in 36s
build_systems / build-bob (push) Successful in 41s
build_systems / build-rhapsody-in-green (push) Successful in 52s
build_systems / build-jeeves (push) Successful in 2m9s
2026-09-08 19:26:26 -04:00
Richie 3aa66c48af removing zerotierone from jeeves
treefmt / nix fmt (pull_request) Successful in 6s
pytest / pytest (pull_request) Successful in 39s
build_systems / build-portal-1 (pull_request) Successful in 45s
test ebook search / test-ebook-search (pull_request) Successful in 46s
build_systems / build-brain (pull_request) Successful in 1m1s
build_systems / build-bob (pull_request) Successful in 1m1s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m10s
build_systems / build-jeeves (pull_request) Successful in 2m29s
treefmt / nix fmt (push) Successful in 5s
build_systems / build-portal-1 (push) Successful in 24s
pytest / pytest (push) Successful in 32s
build_systems / build-brain (push) Successful in 37s
build_systems / build-bob (push) Successful in 37s
test ebook search / test-ebook-search (push) Successful in 37s
build_systems / build-rhapsody-in-green (push) Successful in 50s
build_systems / build-jeeves (push) Successful in 2m2s
2026-09-08 18:12:20 -04:00
Richie 97d63eecda add portal-1 to build_system
treefmt / nix fmt (pull_request) Successful in 6s
pytest / pytest (pull_request) Successful in 35s
test ebook search / test-ebook-search (pull_request) Successful in 41s
build_systems / build-portal-1 (pull_request) Successful in 57s
build_systems / build-brain (pull_request) Successful in 57s
build_systems / build-bob (pull_request) Successful in 59s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m12s
build_systems / build-jeeves (pull_request) Successful in 2m29s
treefmt / nix fmt (push) Successful in 6s
build_systems / build-portal-1 (push) Successful in 26s
pytest / pytest (push) Successful in 34s
test ebook search / test-ebook-search (push) Successful in 35s
build_systems / build-brain (push) Successful in 39s
build_systems / build-bob (push) Successful in 39s
build_systems / build-rhapsody-in-green (push) Successful in 58s
build_systems / build-jeeves (push) Successful in 2m21s
2026-09-08 16:40:45 -04:00
Richie 1d0ced8fd4 removed pirate-weather from home_assistant
test ebook search / test-ebook-search (push) Successful in 45s
treefmt / nix fmt (pull_request) Successful in 6s
treefmt / nix fmt (push) Successful in 6s
pytest / pytest (pull_request) Successful in 35s
test ebook search / test-ebook-search (pull_request) Successful in 47s
pytest / pytest (push) Successful in 42s
build_systems / build-brain (pull_request) Successful in 59s
build_systems / build-bob (push) Successful in 58s
build_systems / build-bob (pull_request) Successful in 1m1s
build_systems / build-brain (push) Successful in 55s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m15s
build_systems / build-rhapsody-in-green (push) Successful in 1m10s
build_systems / build-jeeves (pull_request) Successful in 2m52s
build_systems / build-jeeves (push) Successful in 2m50s
2026-09-08 16:38:21 -04:00
Richie 3ebead7663 feat(van_weather): remove python van_weather and apscheduler
build_systems / build-rhapsody-in-green (push) Successful in 48s
build_systems / build-jeeves (push) Successful in 2m0s
pytest / pytest (pull_request) Successful in 44s
test ebook search / test-ebook-search (pull_request) Successful in 47s
build_systems / build-brain (pull_request) Successful in 1m0s
build_systems / build-jeeves (pull_request) Successful in 2m59s
treefmt / nix fmt (pull_request) Successful in 6s
build_systems / build-bob (pull_request) Successful in 1m1s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m35s
pytest / pytest (push) Successful in 32s
build_systems / build-bob (push) Successful in 35s
treefmt / nix fmt (push) Successful in 6s
build_systems / build-brain (push) Successful in 8s
test ebook search / test-ebook-search (push) Successful in 32s
2026-09-08 14:44:39 -04:00
85 changed files with 713 additions and 1926 deletions

No files matched your search

+1
View File
@@ -18,6 +18,7 @@ jobs:
- "brain"
- "jeeves"
- "rhapsody-in-green"
- "portal-1"
continue-on-error: true
steps:
- uses: actions/checkout@v4
@@ -15,7 +15,6 @@ jobs:
steps:
- name: merge_flake_lock_update
run: >-
nix develop .#devShells.x86_64-linux.default -c
python -m python.gitea_flake_lock merge
--repo "${{ github.repository }}"
env:
-19
View File
@@ -1,19 +0,0 @@
name: pytest
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
pytest:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run tests
run: nix develop .#devShells.x86_64-linux.default -c pytest tests
-1
View File
@@ -21,6 +21,5 @@ jobs:
JEEVES_BOT_TOKEN: ${{ secrets.JEEVES_BOT_TOKEN }}
GITEA_URL: https://gitea.tmmworkshop.com
run: >-
nix develop .#devShells.x86_64-linux.default -c
python -m python.gitea_flake_lock update
--repo "${{ github.repository }}"
-3
View File
@@ -10,7 +10,6 @@
"aiounifi",
"alsa",
"apiclient",
"apscheduler",
"archlinux",
"ashift",
"asrouter",
@@ -337,8 +336,6 @@
"yubioath",
"yzhang",
"zeroconf",
"zerotier",
"zerotierone",
"zoxide",
"zram",
"zstd"
+4
View File
@@ -31,6 +31,10 @@ in
"flakes"
"ca-derivations"
];
system-features = lib.mkAfter [
"gccarch-x86-64-v2"
"gccarch-x86-64-v3"
];
warn-dirty = false;
flake-registry = ""; # disable global flake registries
connect-timeout = 10;
-1
View File
@@ -2,6 +2,5 @@
{
environment.systemPackages = with pkgs; [
git
my_python
];
}
+1
View File
@@ -17,6 +17,7 @@
logDriver = "local";
storageDriver = "overlay2";
daemon.settings = {
live-restore = false;
experimental = true;
exec-opts = [ "native.cgroupdriver=systemd" ];
log-opts = {
+9
View File
@@ -0,0 +1,9 @@
{
nixpkgs.hostPlatform = {
system = "x86_64-linux";
gcc = {
arch = "x86-64-v3";
tune = "generic";
};
};
}
+42 -1
View File
@@ -1,4 +1,5 @@
{
inputs,
pkgs,
lib,
config,
@@ -6,6 +7,11 @@
}:
let
cfg = config.services.snapshot_manager;
snapshotManagerPackages =
ps: with ps; [
httpx
typer
];
in
{
options = {
@@ -34,6 +40,41 @@ in
};
config = lib.mkIf cfg.enable {
nixpkgs.overlays = [
(final: _prev: {
snapshot_manager_python = final.python314.withPackages snapshotManagerPackages;
snapshot_manager_test_python = final.python314.withPackages (
ps:
snapshotManagerPackages ps
++ (with ps; [
pyfakefs
pytest
pytest-asyncio
pytest-mock
pytest-xdist
])
);
snapshot_manager_tests =
final.runCommand "snapshot-manager-tests"
{
nativeBuildInputs = [ final.snapshot_manager_test_python ];
}
''
export HOME="$TMPDIR"
cd ${inputs.self}
pytest \
-o cache_dir="$TMPDIR/pytest-cache" \
tests/test_common.py \
tests/test_signal_alert.py \
tests/test_snapshot_manager.py \
tests/test_zfs.py
touch "$out"
'';
})
];
system.checks = [ pkgs.snapshot_manager_tests ];
systemd = {
services.snapshot_manager = {
description = "ZFS Snapshot Manager";
@@ -45,7 +86,7 @@ in
};
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.my_python}/bin/python -m python.tools.snapshot_manager ${lib.escapeShellArg cfg.path}";
ExecStart = "${pkgs.snapshot_manager_python}/bin/python -m python.tools.snapshot_manager ${lib.escapeShellArg cfg.path}";
}
// lib.optionalAttrs (cfg.EnvironmentFile != null) {
EnvironmentFile = cfg.EnvironmentFile;
+6
View File
@@ -22,6 +22,12 @@ hourly = 0
daily = 0
monthly = 0
["root_pool/nix_build"]
15_min = 1
hourly = 0
daily = 0
monthly = 0
["root_pool/var"]
15_min = 8
hourly = 24
Generated
+15 -38
View File
@@ -20,28 +20,6 @@
"type": "github"
}
},
"firefox-addons": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"dir": "pkgs/firefox-addons",
"lastModified": 1787025780,
"narHash": "sha256-NhyLP9G4DFOn/7aYr7K/D7hWrzEGr5EgUBV+lpdmJ24=",
"owner": "rycee",
"repo": "nur-expressions",
"rev": "5ad360b6d3cb0aa1b61f9cb27fef113ca9117c37",
"type": "gitlab"
},
"original": {
"dir": "pkgs/firefox-addons",
"owner": "rycee",
"repo": "nur-expressions",
"type": "gitlab"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -49,11 +27,11 @@
]
},
"locked": {
"lastModified": 1786999651,
"narHash": "sha256-MTGMFlLDTklsXhCp4r5GXB4VAVadPdalXLvUjd/K7h0=",
"lastModified": 1788651960,
"narHash": "sha256-v9wJd32eZ2bvhBzVOd7TIjLQd011P7nwOhjKtWlci5I=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "353742587cbaf079b3caee743115d037bc51fea6",
"rev": "2c0350c759688177331b8f5242311fae8877bdb3",
"type": "github"
},
"original": {
@@ -67,11 +45,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1786867632,
"narHash": "sha256-ez+ubZlA1RtdjCB18a6zJ9M4u8qoPDy08EcnsW5M3Xw=",
"lastModified": 1788860136,
"narHash": "sha256-MhPMOFV4pVkygWEbQ8t1De/uQ9cWF1u++tRe2L5tG48=",
"owner": "nixos",
"repo": "nixos-hardware",
"rev": "ff17823245ab9ff7bcae6acf950bd89cba82c38c",
"rev": "62173785b9a18c78b4a15aca2623d02bceb9d077",
"type": "github"
},
"original": {
@@ -96,11 +74,11 @@
},
"nixpkgs-master": {
"locked": {
"lastModified": 1787081018,
"narHash": "sha256-K0uwZBtZsbBigHAMQW7YWti3gPe6a5ct5bcOw5F+Q9Y=",
"lastModified": 1788892992,
"narHash": "sha256-cIMFh9gyU4/aLeB3JCcsWM3tTAvD9pAq9Smr1Wa8aIU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "cacac5ac351a010599d9f9d106acfed25a8e4c77",
"rev": "dff6994123e257ec9901c271bc2b52e64d7c8f05",
"type": "github"
},
"original": {
@@ -128,11 +106,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1787001381,
"narHash": "sha256-Ue1Yo8gfHdD4TMtNewhA4tkSYeFqXThju0nCyJc3ALo=",
"lastModified": 1788752844,
"narHash": "sha256-VaWGJ6+cIYN2erfSecbRV+4ljI185Ty2wUrXyvQbgOw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ec2d622de0773551768cf98f3fc50cbcc003b9c5",
"rev": "dc5d91f840324650bac8c379428c7037a416959a",
"type": "github"
},
"original": {
@@ -145,7 +123,6 @@
"root": {
"inputs": {
"disko": "disko",
"firefox-addons": "firefox-addons",
"home-manager": "home-manager",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2",
@@ -162,11 +139,11 @@
]
},
"locked": {
"lastModified": 1786629091,
"narHash": "sha256-gkig4nPi1CWc4Z50GBsjE4ygSE7hMpl/TwID2an2Cck=",
"lastModified": 1788337237,
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "a8627b21b9107c5711c96b84f32a9a4b3d45295f",
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
"type": "github"
},
"original": {
-5
View File
@@ -26,11 +26,6 @@
inputs.nixpkgs.follows = "nixpkgs";
};
firefox-addons = {
url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
+2 -31
View File
@@ -15,35 +15,6 @@
};
};
python-env = final: _prev: {
my_python = final.python314.withPackages (
ps: with ps; [
alembic
apprise
apscheduler
fastapi
fastapi-cli
httpx
jinja2
mypy
pgvector
psycopg
pydantic
pyfakefs
pytest
pytest-cov
pytest-mock
pytest-xdist
python-multipart
pydantic-settings
ruff
sqlalchemy
tenacity
tinytuya
typer
uvicorn
websockets
]
);
};
test-exclusions = import ./test-exclusions.nix;
x86-64-v3-workarounds = import ./x86-64-v3-workarounds.nix;
}
+133
View File
@@ -0,0 +1,133 @@
# Test exclusions for the locally rebuilt x86-64-v3 package set.
#
# Selecting x86-64-v3 changes every affected derivation, so the normal
# nixpkgs binary cache cannot be used and upstream test suites run locally.
# The jeeves builder uses /tmp/nix-builds so filesystem tests run on tmpfs
# instead of ZFS with normalization=formD and utf8only=on; those tests remain
# enabled. The remaining workarounds cover UDP readiness, resource-sensitive
# parser and nested-worker races, and mismatched timeout clocks, plus
# architecture-dependent floating-point differences whose risk we accept for
# our workloads. Keep these exceptions visible until their causes are fixed.
_final: prev: {
gnutls = prev.gnutls.overrideAttrs (old: {
# This test uses a fixed four-second sleep instead of checking UDP
# readiness; the client saw no listener in the x86-64-v3 build.
postPatch = (old.postPatch or "") + ''
sed '2iexit 77' -i tests/serv-udp.sh
'';
});
prometheus = prev.prometheus.overrideAttrs (
old:
let
assets = old.passthru.assets.overrideAttrs (assetsOld: {
# CodeMirror's bounded synchronous parser can return an incomplete tree
# when these cases run on a heavily loaded builder.
postPatch = (assetsOld.postPatch or "") + ''
substituteInPlace module/codemirror-promql/src/complete/hybrid.test.ts \
--replace-fail "it(value.title, () => {" \
"(value.title === 'autocomplete topk params 2' ? it.skip : it)(value.title, () => {"
substituteInPlace module/codemirror-promql/src/parser/vector.test.ts \
--replace-fail "it(value.binaryExpr, () => {" \
"(value.binaryExpr === 'foo * on(test,blub) bar' ? it.skip : it)(value.binaryExpr, () => {"
'';
});
in
{
postPatch = builtins.replaceStrings [ "${old.passthru.assets}" ] [ "${assets}" ] (
builtins.unsafeDiscardStringContext old.postPatch
);
passthru = old.passthru // {
inherit assets;
};
}
);
pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [
(_pythonFinal: pythonPrev: {
backrefs = pythonPrev.backrefs.overridePythonAttrs (old: {
# regex measures its timeout in process CPU time, while this test used
# wall time and could miss the timeout when a busy builder descheduled it.
postPatch = (old.postPatch or "") + ''
substituteInPlace tests/test_bregex.py \
--replace-fail "time.time()" "time.process_time()"
'';
});
pytest-xdist = pythonPrev.pytest-xdist.overridePythonAttrs (old: {
# The suite exercises its own worker pools. Run the outer suite with one
# worker and allow inner workers more time on heavily loaded builders.
postPatch = (old.postPatch or "") + ''
substituteInPlace testing/test_remote.py \
--replace-fail "WAIT_TIMEOUT = 10.0" "WAIT_TIMEOUT = 60.0"
'';
preCheck = builtins.replaceStrings [ "--numprocesses=$NIX_BUILD_CORES" ] [ "--numprocesses=1" ] (
old.preCheck or ""
);
# This test deliberately crashes workers past the restart limit and
# races while checking which replacement message was emitted.
disabledTests = (old.disabledTests or [ ]) ++ [
"test_max_worker_restart_tests_queued"
];
});
jupyter-server = pythonPrev.jupyter-server.overridePythonAttrs (old: {
# The kernel reply arrived after the one-second outer deadline when the
# builder was heavily loaded. Keep the regression test but allow it the
# same margin as the other resource-sensitive tests.
postPatch = (old.postPatch or "") + ''
substituteInPlace tests/services/kernels/test_connection.py \
--replace-fail \
"await asyncio.wait_for(asyncio.wrap_future(conn2.request_kernel_info()), timeout=1.0)" \
"await asyncio.wait_for(asyncio.wrap_future(conn2.request_kernel_info()), timeout=10.0)"
'';
});
scipy = pythonPrev.scipy.overridePythonAttrs (old: {
# x86-64-v3 FFT implementations produce rounding differences outside
# these tests' strict tolerances. We accept the numerical-precision
# risk for our workloads.
disabledTests = (old.disabledTests or [ ]) ++ [
"test_roundtrip_float32"
"test_roundtrip_scaling"
];
});
sentry-sdk = pythonPrev.sentry-sdk.overridePythonAttrs (old: {
# This test globally mocks threading.current_thread while another
# thread is running. On Python 3.14, Thread.join can race with that
# mock and exhaust its single side effect before the worker removes it.
disabledTests = (old.disabledTests or [ ]) ++ [
"test_get_current_thread_meta_main_thread"
];
});
torchaudio = pythonPrev.torchaudio.overridePythonAttrs (old: {
# x86-64-v3 pitch shifting produces batch-versus-single-item numerical
# differences up to 2.9e-6. We accept that audio-precision risk for our
# workloads.
disabledTests = (old.disabledTests or [ ]) ++ [
"test_batch_pitch_shift"
];
});
torchcodec = pythonPrev.torchcodec.overridePythonAttrs (old: {
# For these 8 kHz MP3 cases, the x86-64-v3 API and CLI codec paths
# differ in 0.8% of decoded samples. Retain the original tolerance for
# 99% of samples and accept the localized audio-precision risk.
postPatch = (old.postPatch or "") + ''
substituteInPlace test/test_encoders.py \
--replace-fail \
'if sys.platform == "darwin":' \
'if sys.platform == "darwin" or (
format == "mp3"
and sample_rate == 8_000
and asset is SINE_MONO_S32
and bit_rate in (None, 0)
and num_channels in (None, 1)
):'
'';
});
})
];
}
+60
View File
@@ -0,0 +1,60 @@
# Compatibility fixes for packages rebuilt with x86-64-v3.
#
# The v3 baseline enables instructions that expose source assumptions hidden
# by the generic x86-64 build. Keep compile fixes here, separate from test
# exclusions, until upstream or nixpkgs incorporates them.
_final: prev:
let
patchAbseilBmi2Include =
package:
package.overrideAttrs (old: {
# GCC and Clang prohibit including their internal BMI2 header directly.
# The public umbrella provides the same intrinsics with the required
# compiler setup.
postPatch = (old.postPatch or "") + ''
substituteInPlace third_party/abseil-cpp/absl/container/internal/raw_hash_set.h \
--replace-fail "#include <bmi2intrin.h>" "#include <immintrin.h>"
'';
});
removeSiblingOutputChecks =
package:
package.overrideAttrs (old: {
# Nix 2.34 can validate a partial multi-output rebuild against only the
# outputs still being realised. PostgreSQL's checks then reject valid
# sibling names such as "out" and "lib". Keep the test suite and
# disallowed-requisite checks; accept the loss of cross-output checks.
outputChecks = builtins.mapAttrs (
_output: checks: builtins.removeAttrs checks [ "disallowedReferences" ]
) (old.outputChecks or { });
});
electron43Unwrapped = patchAbseilBmi2Include prev.electron_43.unwrapped;
electron43 = prev.electron_43.override {
electron-unwrapped = electron43Unwrapped;
};
signalCallPackage =
path: args:
let
package = prev.callPackage path args;
in
if builtins.baseNameOf path == "webrtc.nix" then patchAbseilBmi2Include package else package;
in
prev.lib.optionalAttrs ((prev.stdenv.hostPlatform.gcc.arch or null) == "x86-64-v3") {
deno =
let
librusty_v8 = patchAbseilBmi2Include prev.deno.passthru.librusty_v8;
in
prev.deno.override { inherit librusty_v8; };
electron_43 = electron43;
postgresql = removeSiblingOutputChecks prev.postgresql;
postgresql_18 = removeSiblingOutputChecks prev.postgresql_18;
signal-desktop = prev.signal-desktop.override {
electron_43 = electron43;
callPackage = signalCallPackage;
};
}
-45
View File
@@ -6,51 +6,6 @@ authors = [{ name = "Richie Cahill", email = "richie@tmmworkshop.com" }]
requires-python = "~=3.14.0"
readme = "README.md"
license = "MIT"
# these dependencies are a best effort and aren't guaranteed to work
# for up-to-date dependencies, see overlays/default.nix
dependencies = [
"alembic",
"apprise",
"apscheduler",
"beautifulsoup4",
"bm25s",
"ebooklib",
"fastapi",
"fastapi-cli",
"httpx",
"jinja2",
"pgvector",
"polars",
"psycopg[binary]",
"pydantic",
"pydantic-settings",
"python-multipart",
"sqlalchemy[asyncio]",
"tenacity",
"tiktoken",
"tinytuya",
"typer",
"uvicorn",
"websockets",
"yake",
]
[project.scripts]
database = "python.database_cli:app"
whisper-transcribe = "python.tools.whisper.transcribe:main"
[dependency-groups]
dev = [
"aiosqlite",
"mypy",
"pyfakefs",
"pytest-asyncio",
"pytest-cov",
"pytest-mock",
"pytest-xdist",
"pytest",
"ruff",
]
[tool.ruff]
+1 -1
View File
@@ -14,7 +14,7 @@ DEFAULT_BASE_BRANCH = "main"
DEFAULT_BRANCH = "automation/update-flake-lock"
DEFAULT_GITEA_URL = "https://gitea.tmmworkshop.com"
PR_LABELS = ["dependencies", "automated", "flake_lock_update"]
PR_CHECK_WORKFLOWS = ["build_systems.yml", "treefmt.yml", "pytest.yml"]
PR_CHECK_WORKFLOWS = ["build_systems.yml", "treefmt.yml"]
PR_TITLE = "Update flake.lock"
PR_BODY = "Automated flake.lock update."
+18
View File
@@ -176,6 +176,21 @@ def create_zfs_datasets() -> None:
run_output(("zfs", "create", "root_pool/home"))
run_output(("zfs", "create", "-o", "reservation=1G", "root_pool/var"))
run_output(("zfs", "create", "-o", "compression=zstd-9", "-o", "reservation=10G", "root_pool/nix"))
run_output(
(
"zfs",
"create",
"-o",
"sync=disabled",
"-o",
"redundant_metadata=some",
"-o",
"normalization=none",
"-o",
"utf8only=off",
"root_pool/nix_build",
)
)
datasets = run_output(("zfs", "list", "-o", "name"))
expected_datasets = {
@@ -183,6 +198,7 @@ def create_zfs_datasets() -> None:
"root_pool/home",
"root_pool/var",
"root_pool/nix",
"root_pool/nix_build",
}
missing_datasets = expected_datasets.difference(datasets.splitlines())
if missing_datasets:
@@ -248,6 +264,7 @@ def create_nix_hardware_file(mnt_dir: str, disks: Sequence[str], encrypt: str |
' "/home" = {\n device = "root_pool/home";\n fsType = "zfs";\n };\n\n'
' "/var" = {\n device = "root_pool/var";\n fsType = "zfs";\n };\n\n'
' "/nix" = {\n device = "root_pool/nix";\n fsType = "zfs";\n };\n\n'
' "/nix/var/nix/builds" = {\n device = "root_pool/nix_build";\n fsType = "zfs";\n };\n\n'
' "/boot" = {\n'
f' device = "/dev/disk/by-uuid/{get_boot_drive_id(disks[0])}";\n'
' fsType = "vfat";\n options = [\n "fmask=0077"\n'
@@ -270,6 +287,7 @@ def install_nixos(mnt_dir: str, disks: Sequence[str], encrypt: str | None) -> No
run_output(("mount", "-o", "X-mount.mkdir", "-t", "zfs", "root_pool/home", f"{mnt_dir}/home"))
run_output(("mount", "-o", "X-mount.mkdir", "-t", "zfs", "root_pool/var", f"{mnt_dir}/var"))
run_output(("mount", "-o", "X-mount.mkdir", "-t", "zfs", "root_pool/nix", f"{mnt_dir}/nix"))
run_output(("mount", "-o", "X-mount.mkdir", "-t", "zfs", "root_pool/nix_build", f"{mnt_dir}/nix/var/nix/builds"))
for disk in disks:
run_output(("mkfs.vfat", "-n", "EFI", f"{disk}-part1"))
+25 -5
View File
@@ -5,10 +5,13 @@ from __future__ import annotations
import logging
from os import getenv
from apprise import Apprise
import httpx
logger = logging.getLogger(__name__)
SIGNAL_API_URL = "http://localhost:8989/v2/send"
SIGNAL_API_TIMEOUT = 4.0
def signal_alert(body: str, title: str = "") -> None:
"""Send a signal alert.
@@ -17,14 +20,31 @@ def signal_alert(body: str, title: str = "") -> None:
body (str): The body of the alert.
title (str, optional): The title of the alert. Defaults to "".
"""
apprise_client = Apprise()
from_phone = getenv("SIGNAL_ALERT_FROM_PHONE")
to_phone = getenv("SIGNAL_ALERT_TO_PHONE")
if not from_phone or not to_phone:
logger.info("SIGNAL_ALERT_FROM_PHONE or SIGNAL_ALERT_TO_PHONE not set")
return
apprise_client.add(f"signal://localhost:8989/{from_phone}/{to_phone}")
# Apprise's Signal integration did not support titles, so preserve that behavior.
if title:
logger.debug("Signal does not support notification titles; ignoring title")
apprise_client.notify(title=title, body=body)
try:
response = httpx.post(
SIGNAL_API_URL,
json={
"message": body,
"number": from_phone,
"recipients": [to_phone],
"text_mode": "normal",
},
timeout=SIGNAL_API_TIMEOUT,
follow_redirects=True,
)
except httpx.HTTPError:
logger.exception("Unable to contact the Signal API")
return
if response.status_code not in {httpx.codes.OK, httpx.codes.CREATED}:
logger.error("Signal API returned HTTP status %d", response.status_code)
-1
View File
@@ -1 +0,0 @@
"""system_tests."""
-99
View File
@@ -1,99 +0,0 @@
"""Validate Jeeves."""
from __future__ import annotations
import logging
from copy import copy
from re import search
from time import sleep
from typing import TYPE_CHECKING
from python.common import bash_wrapper
from python.zfs import Zpool
if TYPE_CHECKING:
from collections.abc import Sequence
logger = logging.getLogger(__name__)
def zpool_tests(pool_names: Sequence[str], zpool_capacity_threshold: int = 90) -> list[str] | None:
"""Check the zpool health and capacity.
Args:
pool_names (Sequence[str]): A list of pool names to test.
zpool_capacity_threshold (int, optional): The threshold for the zpool capacity. Defaults to 90.
Returns:
list[str] | None: A list of errors if any.
"""
logger.info("Testing zpool")
errors: list[str] = []
for pool_name in pool_names:
pool = Zpool(pool_name)
if pool.health != "ONLINE":
errors.append(f"{pool.name} is {pool.health}")
if pool.capacity >= zpool_capacity_threshold:
errors.append(f"{pool.name} is low on space")
upgrade_status, _ = bash_wrapper("zpool upgrade")
if not search(r"Every feature flags pool has all supported and requested features enabled.", upgrade_status):
errors.append("ZPool out of date run `sudo zpool upgrade -a`")
return errors
def systemd_tests(
service_names: Sequence[str],
max_retries: int = 30,
retry_delay_secs: int = 1,
retryable_statuses: Sequence[str] | None = None,
valid_statuses: Sequence[str] | None = None,
) -> list[str] | None:
"""Tests a systemd services.
Args:
service_names (Sequence[str]): A list of service names to test.
max_retries (int, optional): The maximum number of retries. Defaults to 30.
minimum value is 1.
retry_delay_secs (int, optional): The delay between retries in seconds. Defaults to 1.
minimum value is 1.
retryable_statuses (Sequence[str] | None, optional): A list of retryable statuses. Defaults to None.
valid_statuses (Sequence[str] | None, optional): A list of valid statuses. Defaults to None.
Returns:
list[str] | None: A list of errors if any.
"""
logger.info("Testing systemd service")
max_retries = max(max_retries, 1)
retry_delay_secs = max(retry_delay_secs, 1)
last_try = max_retries - 1
if retryable_statuses is None:
retryable_statuses = ("inactive\n", "activating\n")
if valid_statuses is None:
valid_statuses = ("active\n",)
service_names_set = set(service_names)
errors: set[str] = set()
for retry in range(max_retries):
if not service_names_set:
break
logger.info(f"Testing systemd service in {retry + 1} of {max_retries}")
service_names_to_test = copy(service_names_set)
for service_name in service_names_to_test:
service_status, _ = bash_wrapper(f"systemctl is-active {service_name}")
if service_status in valid_statuses:
service_names_set.remove(service_name)
continue
if service_status in retryable_statuses and retry < last_try:
continue
errors.add(f"{service_name} is {service_status.strip()}")
sleep(retry_delay_secs)
return list(errors)
-67
View File
@@ -1,67 +0,0 @@
"""Validate {server_name}."""
import logging
import sys
import tomllib
from os import environ
from pathlib import Path # noqa: TC003 This is required for the typer CLI
from socket import gethostname
import typer
from python.common import configure_logger
from python.signal_alert import signal_alert
from python.system_tests.components import systemd_tests, zpool_tests
logger = logging.getLogger(__name__)
def load_config_data(config_file: Path) -> dict[str, list[str]]:
"""Load a TOML configuration file.
Args:
config_file (Path): The path to the configuration file.
Returns:
dict: The configuration data.
"""
return tomllib.loads(config_file.read_text())
def main(config_file: Path) -> None:
"""Main."""
configure_logger(level=environ.get("LOG_LEVEL", "INFO"))
server_name = gethostname()
logger.info(f"Starting {server_name} validation")
config_data = load_config_data(config_file)
errors: list[str] = []
try:
if config_data.get("zpools") and (zpool_errors := zpool_tests(config_data["zpools"])):
errors.extend(zpool_errors)
if config_data.get("services") and (systemd_errors := systemd_tests(config_data["services"])):
errors.extend(systemd_errors)
except Exception as error:
logger.exception(f"{server_name} validation failed")
errors.append(f"{server_name} validation failed: {error}")
if errors:
logger.error(f"{server_name} validation failed: \n{'\n'.join(errors)}")
signal_alert(f"{server_name} validation failed {errors}")
sys.exit(1)
logger.info(f"{server_name} validation passed")
def cli() -> None:
"""CLI."""
typer.run(main)
if __name__ == "__main__":
cli()
-1
View File
@@ -1 +0,0 @@
"""Van weather service - fetches weather with masked GPS location."""
-293
View File
@@ -1,293 +0,0 @@
"""Van weather service - fetches weather with masked GPS for privacy."""
import logging
from datetime import UTC, datetime
from typing import Annotated, Any
import httpx
import typer
from apscheduler.schedulers.blocking import BlockingScheduler
from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed
from python.common import configure_logger
from python.van_weather.models import Config, DailyForecast, HourlyForecast, Weather
# Map Pirate Weather icons to Home Assistant conditions
CONDITION_MAP = {
"clear-day": "sunny",
"clear-night": "clear-night",
"rain": "rainy",
"snow": "snowy",
"sleet": "snowy-rainy",
"wind": "windy",
"fog": "fog",
"cloudy": "cloudy",
"partly-cloudy-day": "partlycloudy",
"partly-cloudy-night": "partlycloudy",
}
logger = logging.getLogger(__name__)
@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(5),
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
def get_ha_state(url: str, token: str, entity_id: str) -> float:
"""Get numeric state from Home Asasistant entity."""
response = httpx.get(
f"{url}/api/states/{entity_id}",
headers={"Authorization": f"Bearer {token}"},
timeout=30,
)
response.raise_for_status()
state = response.json()["state"]
if state in ("unavailable", "unknown"):
error = f"{entity_id} is {state}"
raise ValueError(error)
return float(state)
def parse_daily_forecast(data: dict[str, dict[str, Any]]) -> list[DailyForecast]:
"""Parse daily forecast from Pirate Weather API."""
daily = data.get("daily", {}).get("data", [])
daily_forecasts = []
for day in daily[:8]: # Up to 8 days
time_stamp = day.get("time")
if time_stamp:
date_time = datetime.fromtimestamp(time_stamp, tz=UTC).isoformat()
daily_forecasts.append(
DailyForecast(
date_time=date_time,
condition=CONDITION_MAP.get(day.get("icon", ""), "cloudy"),
temperature=day.get("temperatureHigh"),
templow=day.get("temperatureLow"),
precipitation_probability=day.get("precipProbability"),
moon_phase=day.get("moonPhase"),
wind_gust=day.get("windGust"),
cloud_cover=day.get("cloudCover"),
)
)
return daily_forecasts
def parse_hourly_forecast(data: dict[str, dict[str, Any]]) -> list[HourlyForecast]:
"""Parse hourly forecast from Pirate Weather API."""
hourly = data.get("hourly", {}).get("data", [])
hourly_forecasts = []
for hour in hourly[:48]: # Up to 48 hours
time_stamp = hour.get("time")
if time_stamp:
date_time = datetime.fromtimestamp(time_stamp, tz=UTC).isoformat()
hourly_forecasts.append(
HourlyForecast(
date_time=date_time,
condition=CONDITION_MAP.get(hour.get("icon", ""), "cloudy"),
temperature=hour.get("temperature"),
precipitation_probability=hour.get("precipProbability"),
)
)
return hourly_forecasts
@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(5),
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
def fetch_weather(api_key: str, lat: float, lon: float) -> Weather:
"""Fetch weather from Pirate Weather API."""
url = f"https://api.pirateweather.net/forecast/{api_key}/{lat},{lon}"
response = httpx.get(url, params={"units": "us"}, timeout=30)
response.raise_for_status()
data = response.json()
daily_forecasts = parse_daily_forecast(data)
hourly_forecasts = parse_hourly_forecast(data)
current = data.get("currently", {})
icon = current.get("icon", "")
return Weather(
temperature=current.get("temperature"),
feels_like=current.get("apparentTemperature"),
humidity=current.get("humidity"),
wind_speed=current.get("windSpeed"),
wind_bearing=current.get("windBearing"),
condition=CONDITION_MAP.get(icon, "cloudy"),
summary=current.get("summary"),
pressure=current.get("pressure"),
visibility=current.get("visibility"),
uv_index=current.get("uvIndex"),
ozone=current.get("ozone"),
nearest_storm_distance=current.get("nearestStormDistance"),
nearest_storm_bearing=current.get("nearestStormBearing"),
precip_probability=current.get("precipProbability"),
cloud_cover=current.get("cloudCover"),
daily_forecasts=daily_forecasts,
hourly_forecasts=hourly_forecasts,
)
@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(5),
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
def post_to_ha(url: str, token: str, weather: Weather) -> None:
"""Post weather data to Home Assistant as sensor entities."""
headers = {"Authorization": f"Bearer {token}"}
# Post current weather as individual sensors
sensors = {
"sensor.van_weather_condition": {
"state": weather.condition or "unknown",
"attributes": {"friendly_name": "Van Weather Condition"},
},
"sensor.van_weather_temperature": {
"state": weather.temperature,
"attributes": {"unit_of_measurement": "°F", "device_class": "temperature"},
},
"sensor.van_weather_apparent_temperature": {
"state": weather.feels_like,
"attributes": {"unit_of_measurement": "°F", "device_class": "temperature"},
},
"sensor.van_weather_humidity": {
"state": int((weather.humidity or 0) * 100),
"attributes": {"unit_of_measurement": "%", "device_class": "humidity"},
},
"sensor.van_weather_pressure": {
"state": weather.pressure,
"attributes": {"unit_of_measurement": "mbar", "device_class": "pressure"},
},
"sensor.van_weather_wind_speed": {
"state": weather.wind_speed,
"attributes": {"unit_of_measurement": "mph", "device_class": "wind_speed"},
},
"sensor.van_weather_wind_bearing": {
"state": weather.wind_bearing,
"attributes": {"unit_of_measurement": "°"},
},
"sensor.van_weather_visibility": {
"state": weather.visibility,
"attributes": {"unit_of_measurement": "mi"},
},
"sensor.van_weather_uv_index": {
"state": weather.uv_index,
"attributes": {"friendly_name": "Van Weather UV Index", "icon": "mdi:sun-wireless"},
},
"sensor.van_weather_ozone": {
"state": weather.ozone,
"attributes": {"unit_of_measurement": "DU", "icon": "mdi:earth"},
},
"sensor.van_weather_nearest_storm_distance": {
"state": weather.nearest_storm_distance,
"attributes": {"unit_of_measurement": "mi", "icon": "mdi:weather-lightning"},
},
"sensor.van_weather_nearest_storm_bearing": {
"state": weather.nearest_storm_bearing,
"attributes": {"unit_of_measurement": "°", "icon": "mdi:weather-lightning"},
},
"sensor.van_weather_precip_probability": {
"state": int((weather.precip_probability or 0) * 100),
"attributes": {"unit_of_measurement": "%", "icon": "mdi:weather-rainy"},
},
"sensor.van_weather_cloud_cover": {
"state": int((weather.cloud_cover or 0) * 100),
"attributes": {"unit_of_measurement": "%", "icon": "mdi:weather-cloudy"},
},
}
for entity_id, data in sensors.items():
if data["state"] is not None:
response = httpx.post(f"{url}/api/states/{entity_id}", headers=headers, json=data, timeout=30)
response.raise_for_status()
# Post daily forecast as JSON attribute sensor
daily_forecast = [
{
"datetime": daily_forecast.date_time.isoformat(),
"condition": daily_forecast.condition,
"temperature": daily_forecast.temperature,
"templow": daily_forecast.templow,
"precipitation_probability": int((daily_forecast.precipitation_probability or 0) * 100),
}
for daily_forecast in weather.daily_forecasts
]
response = httpx.post(
f"{url}/api/states/sensor.van_weather_forecast_daily",
headers=headers,
json={"state": len(daily_forecast), "attributes": {"forecast": daily_forecast}},
timeout=30,
)
response.raise_for_status()
# Post hourly forecast as JSON attribute sensor
hourly_forecast = [
{
"datetime": hourly_forecast.date_time.isoformat(),
"condition": hourly_forecast.condition,
"temperature": hourly_forecast.temperature,
"precipitation_probability": int((hourly_forecast.precipitation_probability or 0) * 100),
}
for hourly_forecast in weather.hourly_forecasts
]
response = httpx.post(
f"{url}/api/states/sensor.van_weather_forecast_hourly",
headers=headers,
json={"state": len(hourly_forecast), "attributes": {"forecast": hourly_forecast}},
timeout=30,
)
response.raise_for_status()
def update_weather(config: Config) -> None:
"""Fetch weather using last-known location, post to HA."""
lat = get_ha_state(config.ha_url, config.ha_token, config.lat_entity)
lon = get_ha_state(config.ha_url, config.ha_token, config.lon_entity)
masked_lat = round(lat, config.mask_decimals)
masked_lon = round(lon, config.mask_decimals)
logger.info(f"Masked location: {masked_lat}, {masked_lon}")
weather = fetch_weather(config.pirate_weather_api_key, masked_lat, masked_lon)
logger.info(f"Weather: {weather.temperature}°F, {weather.condition}")
post_to_ha(config.ha_url, config.ha_token, weather)
logger.info("Posted weather to HA")
def main(
ha_url: Annotated[str, typer.Option(envvar="HA_URL")],
ha_token: Annotated[str, typer.Option(envvar="HA_TOKEN")],
api_key: Annotated[str, typer.Option(envvar="PIRATE_WEATHER_API_KEY")],
interval: Annotated[int, typer.Option(help="Poll interval in seconds")] = 900,
log_level: Annotated[str, typer.Option()] = "INFO",
) -> None:
"""Fetch weather for van using masked GPS location."""
configure_logger(log_level)
config = Config(ha_url=ha_url, ha_token=ha_token, pirate_weather_api_key=api_key)
logger.info(f"Starting van weather service, polling every {interval}s")
scheduler = BlockingScheduler()
scheduler.add_job(
update_weather,
"interval",
seconds=interval,
args=[config],
next_run_time=datetime.now(UTC),
)
scheduler.start()
if __name__ == "__main__":
typer.run(main)
-72
View File
@@ -1,72 +0,0 @@
"""Models for van weather service."""
from __future__ import annotations
from datetime import datetime # noqa: TC003 This is required for pydantic
from pydantic import BaseModel, field_serializer
class Config(BaseModel):
"""Service configuration."""
ha_url: str
ha_token: str
pirate_weather_api_key: str
lat_entity: str = "sensor.van_last_known_latitude"
lon_entity: str = "sensor.van_last_known_longitude"
mask_decimals: int = 1 # ~11km accuracy
class DailyForecast(BaseModel):
"""Daily forecast entry."""
date_time: datetime
condition: str | None = None
temperature: float | None = None # High
templow: float | None = None # Low
precipitation_probability: float | None = None
moon_phase: float | None = None
wind_gust: float | None = None
cloud_cover: float | None = None
@field_serializer("date_time")
def serialize_date_time(self, date_time: datetime) -> str:
"""Serialize datetime to ISO format."""
return date_time.isoformat()
class HourlyForecast(BaseModel):
"""Hourly forecast entry."""
date_time: datetime
condition: str | None = None
temperature: float | None = None
precipitation_probability: float | None = None
@field_serializer("date_time")
def serialize_date_time(self, date_time: datetime) -> str:
"""Serialize datetime to ISO format."""
return date_time.isoformat()
class Weather(BaseModel):
"""Weather data from Pirate Weather."""
temperature: float | None = None
feels_like: float | None = None
humidity: float | None = None
wind_speed: float | None = None
wind_bearing: float | None = None
condition: str | None = None
summary: str | None = None
pressure: float | None = None
visibility: float | None = None
uv_index: float | None = None
ozone: float | None = None
nearest_storm_distance: float | None = None
nearest_storm_bearing: float | None = None
precip_probability: float | None = None
cloud_cover: float | None = None
daily_forecasts: list[DailyForecast] = []
hourly_forecasts: list[HourlyForecast] = []
+1 -2
View File
@@ -8,7 +8,7 @@
# loader path, which NixOS does not provide globally.
ebook-search = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
my_python
python314
uv
];
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
@@ -23,7 +23,6 @@
nix
home-manager
git
my_python
ssh-to-age
gnupg
+1 -2
View File
@@ -3,7 +3,6 @@
imports = [
"${inputs.self}/users/math"
"${inputs.self}/users/richie"
"${inputs.self}/users/steve"
"${inputs.self}/common/global"
"${inputs.self}/common/optional/docker.nix"
"${inputs.self}/common/optional/monitoring-agent.nix"
@@ -14,7 +13,7 @@
"${inputs.self}/common/optional/systemd-boot.nix"
"${inputs.self}/common/optional/tailscale.nix"
"${inputs.self}/common/optional/update.nix"
"${inputs.self}/common/optional/yubikey.nix"
"${inputs.self}/common/optional/x86-64-v3.nix"
"${inputs.self}/common/optional/zfs"
./hardware.nix
./syncthing.nix
+5
View File
@@ -50,6 +50,11 @@
fsType = "zfs";
};
"/nix/var/nix/builds" = {
device = "root_pool/nix_build";
fsType = "zfs";
};
"/var" = {
device = "root_pool/var";
fsType = "zfs";
+6
View File
@@ -22,6 +22,12 @@ hourly = 0
daily = 0
monthly = 0
["root_pool/nix_build"]
15_min = 1
hourly = 0
daily = 0
monthly = 0
["root_pool/models"]
15_min = 4
hourly = 24
+1
View File
@@ -9,6 +9,7 @@
"${inputs.self}/common/optional/systemd-boot.nix"
"${inputs.self}/common/optional/tailscale.nix"
"${inputs.self}/common/optional/update.nix"
"${inputs.self}/common/optional/x86-64-v3.nix"
"${inputs.self}/common/optional/zfs"
./docker
./hardware.nix
+5
View File
@@ -53,6 +53,11 @@
fsType = "zfs";
};
"/nix/var/nix/builds" = {
device = "root_pool/nix_build";
fsType = "zfs";
};
"/boot" = {
device = "/dev/disk/by-uuid/12CE-A600";
fsType = "vfat";
+15 -1
View File
@@ -4,6 +4,20 @@
...
}:
{
nixpkgs.overlays = [
(final: _prev: {
heater_python = final.python314.withPackages (
ps: with ps; [
fastapi
pydantic
tinytuya
typer
uvicorn
]
);
})
];
networking.firewall.allowedTCPPorts = [ 8124 ];
systemd.services.heater-api = {
@@ -17,7 +31,7 @@
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.my_python}/bin/python -m python.heater.main --host 0.0.0.0 --port 8124";
ExecStart = "${pkgs.heater_python}/bin/python -m python.heater.main --host 0.0.0.0 --port 8124";
EnvironmentFile = "/etc/heater.env";
Restart = "on-failure";
RestartSec = "5s";
+4 -3
View File
@@ -68,7 +68,6 @@
paho-mqtt # for mqtt
psycopg2 # for postgresql
py-improv-ble-client # for esphome
pymodbus # for modbus
pyopenweathermap # for weather
pymetno # for met.no weather
uiprotect # for ubiquiti integration
@@ -76,10 +75,12 @@
jsonpath # for rest sensors
monarchmoneycommunity # for monarch
];
extraComponents = [ "isal" ];
extraComponents = [
"isal"
"modbus" # for victron modbus integration
];
customComponents = with pkgs.home-assistant-custom-components; [
garmin_connect
pirate-weather
];
};
+71
View File
@@ -0,0 +1,71 @@
{ pkgs, ... }:
let
vars = import ./vars.nix;
in
{
virtualisation.docker.daemon.settings."data-root" = "${vars.containers}/docker";
# nixos-container hardcodes its state directory to /var/lib/nixos-containers,
# so route it to the shared container dataset with a bind mount.
fileSystems."/var/lib/nixos-containers" = {
device = "${vars.containers}/nixos-containers";
fsType = "none";
options = [ "bind" ];
};
systemd.services = {
prepare-docker-storage = {
description = "Create Docker storage directory";
requiredBy = [ "docker.service" ];
before = [ "docker.service" ];
requires = [ "zfs-mount.service" ];
after = [ "zfs-mount.service" ];
path = [
pkgs.coreutils
pkgs.util-linux
];
unitConfig = {
DefaultDependencies = false;
RequiresMountsFor = [ "/nix" ];
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
mountpoint -q ${vars.containers}
install -d -m 0710 ${vars.containers}/docker
'';
};
prepare-nixos-container-storage = {
description = "Create NixOS container storage directory";
requiredBy = [ "var-lib-nixos\\x2dcontainers.mount" ];
before = [ "var-lib-nixos\\x2dcontainers.mount" ];
requires = [ "zfs-mount.service" ];
after = [ "zfs-mount.service" ];
path = [
pkgs.coreutils
pkgs.util-linux
];
unitConfig = {
DefaultDependencies = false;
RequiresMountsFor = [ "/nix" ];
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
mountpoint -q ${vars.containers}
install -d -m 0755 ${vars.containers}/nixos-containers
'';
};
};
}
+2 -10
View File
@@ -4,10 +4,8 @@ let
in
{
imports = [
"${inputs.self}/users/dov"
"${inputs.self}/users/math"
"${inputs.self}/users/richie"
"${inputs.self}/users/steve"
"${inputs.self}/common/global"
"${inputs.self}/common/optional/docker.nix"
"${inputs.self}/common/optional/monitoring-agent.nix"
@@ -15,10 +13,12 @@ in
"${inputs.self}/common/optional/syncthing_base.nix"
"${inputs.self}/common/optional/tailscale.nix"
"${inputs.self}/common/optional/update.nix"
"${inputs.self}/common/optional/x86-64-v3.nix"
"${inputs.self}/common/optional/zfs"
./monitoring
./docker
./services
./containers.nix
./hardware.nix
./networking
./programs.nix
@@ -35,14 +35,6 @@ in
path = ./snapshot_config.toml;
EnvironmentFile = "${vars.secrets}/services/snapshot_manager";
};
zerotierone = {
enable = true;
joinNetworks = [
"e4da7455b2ae64ca"
"a09acf02330d37b9"
];
};
};
users.groups = {
+5
View File
@@ -96,6 +96,11 @@ in
fsType = "zfs";
};
"/nix/var/nix/builds" = {
device = "root_pool/nix_build";
fsType = "zfs";
};
"/var" = {
device = "root_pool/var";
fsType = "zfs";
+129 -89
View File
@@ -1,8 +1,7 @@
{
config,
lib,
outputs,
utils,
pkgs,
...
}:
@@ -12,6 +11,131 @@ let
cfg = config.services.nix_builder;
runnerUsername = "gitea-runner";
runnerUserid = 601;
runnerLabels = [
"self-hosted:host"
"nixos:host"
];
containerConfig =
{
config,
pkgs,
lib,
...
}:
let
giteaAutomationPython = pkgs.python314.withPackages (
ps: with ps; [
httpx
pydantic
typer
]
);
runnerConfigFile = (pkgs.formats.yaml { }).generate "gitea-runner.yaml" { };
registerRunner = pkgs.writeShellApplication {
name = "register-gitea-runner";
runtimeInputs = [
pkgs.coreutils
pkgs.hostname
config.services.gitea-actions-runner.package
];
text = builtins.readFile ./register_gitea_runner.sh;
};
in
{
networking = {
hostName = "nix-builder";
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;
};
nix.settings = {
cores = 8;
max-jobs = 2;
system-features = lib.mkAfter [
"gccarch-x86-64-v2"
"gccarch-x86-64-v3"
];
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 = [ runnerUsername ];
trusted-users = [
"root"
runnerUsername
];
};
users = {
users.${runnerUsername} = {
isSystemUser = true;
group = runnerUsername;
uid = runnerUserid;
};
groups.${runnerUsername}.gid = runnerUserid;
};
services.gitea-actions-runner.instances.builder = {
enable = true;
name = "jeeves-nix-builder";
url = "http://192.168.99.14:6443/";
labels = runnerLabels;
tokenFile = "/run/secrets/gitea-runners/registration-token";
settings.runner.timeout = "12h";
hostPackages = with pkgs; [
bash
coreutils
curl
gawk
gitMinimal
giteaAutomationPython
gnused
nix
nixfmt
nixos-rebuild
nodejs
treefmt
wget
];
};
systemd.services."gitea-runner-builder" = {
serviceConfig = {
DynamicUser = mkForce false;
User = mkForce runnerUsername;
Group = mkForce runnerUsername;
ExecStartPre = mkForce [
"${getExe registerRunner} builder http://192.168.99.14:6443/ ${runnerConfigFile} ${escapeShellArgs runnerLabels}"
];
};
};
system.stateVersion = "24.05";
};
sharedContainerPath =
(import "${pkgs.path}/nixos/lib/eval-config.nix" {
modules = [
{
boot.isNspawnContainer = true;
nixpkgs.pkgs = pkgs;
}
containerConfig
];
system = null;
}).config.system.build.toplevel;
in
{
options.services.nix_builder = {
@@ -48,9 +172,11 @@ in
containers = mapAttrs (
name: containerCfg:
mkIf containerCfg.enable {
path = sharedContainerPath;
autoStart = true;
privateNetwork = true;
hostBridge = cfg.bridgeName;
extraFlags = [ "--hostname=${name}" ];
bindMounts = {
host-nix = {
mountPoint = "/host-nix/var/nix/daemon-socket";
@@ -68,93 +194,6 @@ in
isReadOnly = false;
};
};
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;
};
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 = [ "gitea-runner" ];
trusted-users = [
"root"
"gitea-runner"
];
};
nixpkgs = {
overlays = builtins.attrValues outputs.overlays;
config.allowUnfree = true;
};
users = {
users.${runnerUsername} = {
isSystemUser = true;
group = runnerUsername;
uid = runnerUserid;
};
groups.${runnerUsername}.gid = runnerUserid;
};
services.gitea-actions-runner.instances.${name} = {
enable = true;
name = "jeeves-${name}";
url = "http://192.168.99.14:6443/";
labels = [
"self-hosted:host"
"nixos:host"
];
tokenFile = "/run/secrets/gitea-runners/registration-token";
hostPackages = with pkgs; [
bash
coreutils
curl
gawk
gitMinimal
gnused
my_python
nix
nixfmt
nixos-rebuild
nodejs
treefmt
wget
];
};
systemd.services."gitea-runner-${utils.escapeSystemdPath name}" = {
serviceConfig = {
DynamicUser = mkForce false;
User = mkForce runnerUsername;
Group = mkForce runnerUsername;
};
};
system.stateVersion = "24.05";
};
}
) cfg.containers;
@@ -165,6 +204,7 @@ in
value = {
requires = [ "gitea.service" ];
after = [ "gitea.service" ];
serviceConfig.CPUQuota = "800%";
};
}) (builtins.attrNames (filterAttrs (_: c: c.enable) cfg.containers))
);
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
legacy_instance_dir="$STATE_DIRECTORY/$(hostname)"
instance_dir="$STATE_DIRECTORY/$1"
runner_url="$2"
runner_config_file="$3"
shift 3
runner_labels="$(IFS=,; printf '%s' "$*")"
if [ ! -e "$instance_dir" ] && [ -d "$legacy_instance_dir" ]; then
mv "$legacy_instance_dir" "$instance_dir"
fi
mkdir -vp "$instance_dir"
cd "$instance_dir" || exit 1
token_hash_file="$instance_dir/.token-hash"
token_hash_current="$(printf '%s' "$TOKEN" | sha256sum | cut -d' ' -f1)"
token_hash_stored="$(cat "$token_hash_file" 2>/dev/null || echo "")"
labels_file="$instance_dir/.labels"
labels_wanted="$(printf '%s\n' "$@" | sort)"
labels_current="$(cat "$labels_file" 2>/dev/null || echo 0)"
if [ ! -e "$instance_dir/.runner" ] || [ "$labels_wanted" != "$labels_current" ] || [ "$token_hash_current" != "$token_hash_stored" ]; then
rm -v "$instance_dir/.runner" || true
gitea-runner register --no-interactive \
--instance "$runner_url" \
--token "$TOKEN" \
--name "jeeves-$(hostname)" \
--labels "$runner_labels" \
--config "$runner_config_file"
printf '%s' "$token_hash_current" > "$token_hash_file"
printf '%s\n' "$labels_wanted" > "$labels_file"
fi
+1
View File
@@ -17,6 +17,7 @@ sudo zpool create scratch -o ashift=12 -O acltype=posixacl -O atime=off -O dnode
# media datasets
sudo zfs create media/temp -o sync=disabled -o redundant_metadata=none
sudo zfs create media/secure -o encryption=aes-256-gcm -o keyformat=hex -o keylocation=file:///root/zfs.key
sudo zfs create media/secure/containers -o mountpoint=/zfs/media/containers -o compression=lz4 -o sync=disabled -o redundant_metadata=some -o normalization=none -o utf8only=off
sudo zfs create media/secure/docker -o compression=zstd-9
sudo zfs create media/secure/github-runners -o compression=zstd-9 -o sync=disabled
sudo zfs create media/secure/notes -o copies=2
@@ -1,80 +0,0 @@
{
...
}:
let
vars = import ../vars.nix;
in
{
systemd.tmpfiles.rules = [
"d ${vars.docker_configs}/camofox-browser 0750 root root - -"
];
containers.camofox-browser = {
autoStart = true;
privateNetwork = false;
bindMounts = {
camofox-browser = {
hostPath = "${vars.docker_configs}/camofox-browser";
mountPoint = "/var/lib/camofox-browser";
isReadOnly = false;
};
};
config =
{
pkgs,
lib,
...
}:
{
networking.hostName = "camofox-browser";
environment.systemPackages = with pkgs; [
ffmpeg
git
nodejs
python3Packages.yt-dlp
];
systemd.services.camofox-browser = {
description = "Camofox browser server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = {
CAMOFOX_HOST = "127.0.0.1";
CAMOFOX_PORT = "9377";
HOME = "/var/lib/camofox-browser";
};
path = with pkgs; [
bash
coreutils
git
nodejs
];
serviceConfig = {
Restart = "always";
RestartSec = "5s";
WorkingDirectory = "/var/lib/camofox-browser";
};
script = ''
set -eu
app_dir=/var/lib/camofox-browser/app
if [ ! -d "$app_dir/.git" ]; then
git clone --depth 1 https://github.com/jo-inc/camofox-browser "$app_dir"
fi
cd "$app_dir"
if [ ! -d node_modules ]; then
npm install
fi
exec npm start
'';
};
system.stateVersion = lib.mkDefault "24.05";
};
};
}
+40 -1
View File
@@ -6,8 +6,47 @@
let
vars = import ../vars.nix;
stateDir = "${vars.services}/gems";
gemsPackages =
ps: with ps; [
fastapi
jinja2
pydantic
pydantic-settings
python-multipart
typer
uvicorn
];
in
{
nixpkgs.overlays = [
(final: _prev: {
gems_python = final.python314.withPackages gemsPackages;
gems_test_python = final.python314.withPackages (
ps:
gemsPackages ps
++ (with ps; [
httpx
pytest
pytest-asyncio
pytest-xdist
])
);
gems_tests =
final.runCommand "gems-tests"
{
nativeBuildInputs = [ final.gems_test_python ];
}
''
export HOME="$TMPDIR"
cd ${inputs.self}
pytest -o cache_dir="$TMPDIR/pytest-cache" tests/gems
touch "$out"
'';
})
];
system.checks = [ pkgs.gems_tests ];
users.groups.gems = { };
users.users.gems = {
isSystemUser = true;
@@ -36,7 +75,7 @@ in
Type = "simple";
User = "gems";
Group = "gems";
ExecStart = "${pkgs.my_python}/bin/python -m python.gems.main --host 0.0.0.0 --port 8002";
ExecStart = "${pkgs.gems_python}/bin/python -m python.gems.main --host 0.0.0.0 --port 8002";
Restart = "on-failure";
RestartSec = "5s";
StandardOutput = "journal";
+1
View File
@@ -19,6 +19,7 @@ in
actions = {
ENABLED = true;
DEFAULT_ACTIONS_URL = "github";
ENDLESS_TASK_TIMEOUT = "12h";
};
service.DISABLE_REGISTRATION = true;
server = {
-12
View File
@@ -1,12 +0,0 @@
{
services.open-webui = {
enable = true;
host = "0.0.0.0";
environment = {
ANONYMIZED_TELEMETRY = "False";
DO_NOT_TRACK = "True";
SCARF_NO_ANALYTICS = "True";
OLLAMA_API_BASE_URL = "http://127.0.0.1:11434";
};
};
}
-26
View File
@@ -1,11 +1,7 @@
{
pkgs,
inputs,
...
}:
let
vars = import ../vars.nix;
in
{
systemd = {
services = {
@@ -30,21 +26,6 @@ in
ExecStart = "${pkgs.bash}/bin/bash -c 'echo 1 > /sys/bus/pci/devices/0000:61:00.0/remove'";
};
};
startup_validation = {
requires = [ "network-online.target" ];
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
description = "validates startup";
path = [ pkgs.zfs ];
environment = {
PYTHONPATH = "${inputs.self}/";
};
serviceConfig = {
EnvironmentFile = "${vars.secrets}/services/server-validation";
Type = "oneshot";
ExecStart = "${pkgs.my_python}/bin/python -m python.system_tests.validate_system '${./validate_system.toml}'";
};
};
};
timers = {
plex_permission = {
@@ -55,13 +36,6 @@ in
Unit = "plex_permission.service";
};
};
startup_validation = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "10min";
Unit = "startup_validation.service";
};
};
};
};
}
@@ -1,6 +0,0 @@
zpool = ["root_pool", "storage", "media"]
services = [
"audiobookshelf",
"docker",
"jellyfin",
]
+12
View File
@@ -23,6 +23,12 @@ hourly = 0
daily = 0
monthly = 0
["root_pool/nix_build"]
15_min = 1
hourly = 0
daily = 0
monthly = 0
["root_pool/var"]
15_min = 8
hourly = 24
@@ -60,6 +66,12 @@ daily = 30
monthly = 12
# media
["media/secure/containers"]
15_min = 2
hourly = 0
daily = 0
monthly = 0
["media/temp"]
15_min = 2
hourly = 0
+1
View File
@@ -5,6 +5,7 @@ let
in
{
inherit zfs_media zfs_storage zfs_scratch;
containers = "${zfs_media}/containers";
database = "${zfs_media}/database";
docker = "${zfs_media}/docker";
docker_configs = "${zfs_media}/docker/configs";
+1 -2
View File
@@ -10,13 +10,12 @@
"${inputs.self}/users/richie"
"${inputs.self}/common/global"
"${inputs.self}/common/optional/tailscale.nix"
"${inputs.self}/common/optional/x86-64-v3.nix"
./disk-config.nix
./haproxy
./monitoring.nix
];
nixpkgs.hostPlatform = "x86_64-linux";
boot = {
# Avoid consuming the VM's limited memory for /tmp.
tmp.useTmpfs = false;
@@ -1,35 +0,0 @@
{
pkgs,
inputs,
...
}:
{
systemd.services.agent-logger = {
description = "Unified agent logger";
after = [ "local-fs.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
AGENT_LOG_DB = "/var/lib/agent-logger/agent_log.sqlite";
HOME = "/home/richie";
PYTHONPATH = "${inputs.self}";
};
serviceConfig = {
Type = "simple";
User = "richie";
WorkingDirectory = "/home/richie";
ExecStart = "${pkgs.my_python}/bin/python -m python.agent_logger.main";
StateDirectory = "agent-logger";
Restart = "on-failure";
RestartSec = "5s";
StandardOutput = "journal";
StandardError = "journal";
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
PrivateTmp = true;
ReadOnlyPaths = [ "${inputs.self}" ];
};
};
}
+1 -1
View File
@@ -9,10 +9,10 @@
"${inputs.self}/common/optional/syncthing_base.nix"
"${inputs.self}/common/optional/systemd-boot.nix"
"${inputs.self}/common/optional/tailscale.nix"
"${inputs.self}/common/optional/x86-64-v3.nix"
"${inputs.self}/common/optional/yubikey.nix"
"${inputs.self}/common/optional/zfs"
./hardware.nix
./open_webui.nix
./programs.nix
./qmk.nix
./syncthing.nix
+8
View File
@@ -34,14 +34,22 @@
device = "root_pool/root";
fsType = "zfs";
};
"/home" = {
device = "root_pool/home";
fsType = "zfs";
};
"/nix/var/nix/builds" = {
device = "root_pool/nix_build";
fsType = "zfs";
};
"/var" = {
device = "root_pool/var";
fsType = "zfs";
};
"/boot" = {
device = "/dev/disk/by-uuid/12CE-A600";
fsType = "vfat";
-13
View File
@@ -1,13 +0,0 @@
{
services.open-webui = {
enable = true;
host = "0.0.0.0";
environment = {
ANONYMIZED_TELEMETRY = "False";
DO_NOT_TRACK = "True";
SCARF_NO_ANALYTICS = "True";
OLLAMA_API_BASE_URL = "https://ollama.com";
WEBUI_AUTH = "False";
};
};
}
-104
View File
@@ -1,104 +0,0 @@
"""test_components."""
from pytest_mock import MockerFixture
from python.system_tests.components import systemd_tests, zpool_tests
from python.zfs import Zpool
temp = "Every feature flags pool has all supported and requested features enabled.\n"
SYSTEM_TESTS_COMPONENTS = "python.system_tests.components"
def test_zpool_tests(mocker: MockerFixture) -> None:
"""test_zpool_tests."""
mock_zpool = mocker.MagicMock(spec=Zpool)
mock_zpool.health = "ONLINE"
mock_zpool.capacity = 70
mock_zpool.name = "Main"
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.Zpool", return_value=mock_zpool)
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper", return_value=(temp, ""))
errors = zpool_tests(("Main",))
assert errors == []
def test_zpool_tests_out_of_date(mocker: MockerFixture) -> None:
"""test_zpool_tests_out_of_date."""
mock_zpool = mocker.MagicMock(spec=Zpool)
mock_zpool.health = "ONLINE"
mock_zpool.capacity = 70
mock_zpool.name = "Main"
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.Zpool", return_value=mock_zpool)
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper", return_value=("", ""))
errors = zpool_tests(("Main",))
assert errors == ["ZPool out of date run `sudo zpool upgrade -a`"]
def test_zpool_tests_out_of_space(mocker: MockerFixture) -> None:
"""test_zpool_tests_out_of_space."""
mock_zpool = mocker.MagicMock(spec=Zpool)
mock_zpool.health = "ONLINE"
mock_zpool.capacity = 100
mock_zpool.name = "Main"
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.Zpool", return_value=mock_zpool)
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper", return_value=(temp, ""))
errors = zpool_tests(("Main",))
assert errors == ["Main is low on space"]
def test_zpool_tests_offline(mocker: MockerFixture) -> None:
"""test_zpool_tests_offline."""
mock_zpool = mocker.MagicMock(spec=Zpool)
mock_zpool.health = "OFFLINE"
mock_zpool.capacity = 70
mock_zpool.name = "Main"
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.Zpool", return_value=mock_zpool)
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper", return_value=(temp, ""))
errors = zpool_tests(("Main",))
assert errors == ["Main is OFFLINE"]
def test_systemd_tests(mocker: MockerFixture) -> None:
"""test_systemd_tests."""
mocker.patch(
f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper",
side_effect=[
("inactive\n", ""),
("active\n", ""),
],
)
errors = systemd_tests(("docker",))
assert errors == []
"""test_systemd_tests."""
def test_systemd_tests_multiple_negative_retries(mocker: MockerFixture) -> None:
"""test_systemd_tests_fail."""
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper", return_value=("active\n", ""))
errors = systemd_tests(("docker",), max_retries=-1, retry_delay_secs=-1)
assert errors == []
def test_systemd_tests_multiple_pass(mocker: MockerFixture) -> None:
"""test_systemd_tests_fail."""
mocker.patch(
f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper",
side_effect=[
("inactive\n", ""),
("activating\n", ""),
("active\n", ""),
],
)
errors = systemd_tests(
("docker",),
retryable_statuses=("inactive\n", "activating\n"),
valid_statuses=("active\n",),
)
assert errors == []
def test_systemd_tests_fail(mocker: MockerFixture) -> None:
"""test_systemd_tests_fail."""
mocker.patch(f"{SYSTEM_TESTS_COMPONENTS}.bash_wrapper", return_value=("inactive\n", ""))
errors = systemd_tests(("docker",), max_retries=5)
assert errors == ["docker is inactive"]
-63
View File
@@ -1,63 +0,0 @@
"""test_server_validate_scripts."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from pytest_mock import MockerFixture
from python.system_tests.validate_system import main
if TYPE_CHECKING:
from pyfakefs.fake_filesystem import FakeFilesystem
from pytest_mock import MockerFixture
VALIDATE_SYSTEM = "python.system_tests.validate_system"
def test_validate_system(mocker: MockerFixture, fs: FakeFilesystem) -> None:
"""test_validate_system."""
fs.create_file(
"/mock_snapshot_config.toml",
contents='zpools = ["root_pool", "storage", "media"]\nservices = ["docker"]\n',
)
mocker.patch(f"{VALIDATE_SYSTEM}.systemd_tests", return_value=None)
mocker.patch(f"{VALIDATE_SYSTEM}.zpool_tests", return_value=None)
main(Path("/mock_snapshot_config.toml"))
def test_validate_system_errors(mocker: MockerFixture, fs: FakeFilesystem) -> None:
"""test_validate_system_errors."""
fs.create_file(
"/mock_snapshot_config.toml",
contents='zpools = ["root_pool", "storage", "media"]\nservices = ["docker"]\n',
)
mocker.patch(f"{VALIDATE_SYSTEM}.signal_alert")
mocker.patch(f"{VALIDATE_SYSTEM}.systemd_tests", return_value=["systemd_tests error"])
mocker.patch(f"{VALIDATE_SYSTEM}.zpool_tests", return_value=["zpool_tests error"])
with pytest.raises(SystemExit) as exception_info:
main(Path("/mock_snapshot_config.toml"))
assert exception_info.value.code == 1
def test_validate_system_execution(mocker: MockerFixture, fs: FakeFilesystem) -> None:
"""test_validate_system_execution."""
fs.create_file(
"/mock_snapshot_config.toml",
contents='zpools = ["root_pool", "storage", "media"]\nservices = ["docker"]\n',
)
mocker.patch(f"{VALIDATE_SYSTEM}.signal_alert")
mocker.patch(f"{VALIDATE_SYSTEM}.systemd_tests", return_value=None)
mocker.patch(f"{VALIDATE_SYSTEM}.zpool_tests", side_effect=RuntimeError("zpool_tests error"))
with pytest.raises(SystemExit) as exception_info:
main(Path("/mock_snapshot_config.toml"))
assert exception_info.value.code == 1
+45 -5
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from os import environ
from typing import TYPE_CHECKING
from apprise import Apprise
import httpx
from python.signal_alert import signal_alert
@@ -19,14 +19,23 @@ def test_signal_alert(mocker: MockerFixture) -> None:
environ["SIGNAL_ALERT_TO_PHONE"] = "0987654321"
mock_logger = mocker.patch("python.signal_alert.logger")
mock_apprise_client = mocker.MagicMock(spec=Apprise)
mocker.patch("python.signal_alert.Apprise", return_value=mock_apprise_client)
mock_response = mocker.MagicMock(spec=httpx.Response, status_code=httpx.codes.CREATED)
mock_post = mocker.patch("python.signal_alert.httpx.post", return_value=mock_response)
signal_alert("test")
mock_logger.info.assert_not_called()
mock_apprise_client.add.assert_called_once_with("signal://localhost:8989/1234567890/0987654321")
mock_apprise_client.notify.assert_called_once_with(title="", body="test")
mock_post.assert_called_once_with(
"http://localhost:8989/v2/send",
json={
"message": "test",
"number": "1234567890",
"recipients": ["0987654321"],
"text_mode": "normal",
},
timeout=4.0,
follow_redirects=True,
)
def test_signal_alert_no_phones(mocker: MockerFixture) -> None:
@@ -39,3 +48,34 @@ def test_signal_alert_no_phones(mocker: MockerFixture) -> None:
signal_alert("test")
mock_logger.info.assert_called_once_with("SIGNAL_ALERT_FROM_PHONE or SIGNAL_ALERT_TO_PHONE not set")
def test_signal_alert_http_error(mocker: MockerFixture) -> None:
"""HTTP errors are logged rather than propagated."""
environ["SIGNAL_ALERT_FROM_PHONE"] = "1234567890"
environ["SIGNAL_ALERT_TO_PHONE"] = "0987654321"
mock_logger = mocker.patch("python.signal_alert.logger")
request = httpx.Request("POST", "http://localhost:8989/v2/send")
mocker.patch(
"python.signal_alert.httpx.post",
side_effect=httpx.ConnectError("connection failed", request=request),
)
signal_alert("test")
mock_logger.exception.assert_called_once_with("Unable to contact the Signal API")
def test_signal_alert_unsuccessful_response(mocker: MockerFixture) -> None:
"""Unexpected response statuses are logged."""
environ["SIGNAL_ALERT_FROM_PHONE"] = "1234567890"
environ["SIGNAL_ALERT_TO_PHONE"] = "0987654321"
mock_logger = mocker.patch("python.signal_alert.logger")
mock_response = mocker.MagicMock(spec=httpx.Response, status_code=httpx.codes.BAD_GATEWAY)
mocker.patch("python.signal_alert.httpx.post", return_value=mock_response)
signal_alert("test")
mock_logger.error.assert_called_once_with("Signal API returned HTTP status %d", httpx.codes.BAD_GATEWAY)
-26
View File
@@ -1,26 +0,0 @@
{
pkgs,
...
}:
{
users = {
users.dov = {
isNormalUser = true;
shell = pkgs.zsh;
group = "dov";
openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCoSBmMfvp6aThkrfkLQ4TDwZJD0HCC0lsTIRNREIAWeduNkVFdkO3T1SMLmCKJ+zlL9xziNBEqB8NFl4TabAhptIGlKgTOc8C9eDaPQMQV8PB/4DxZhCt7O2qb4Vfcz82IHPtdwwaIsGpssgh81mQ4tPtP8BI0TluRBy+2v095s20j+PYRGrAXJtOWpVY9zaXxEJ8dXDhHDF2xzxvUcwu9NsoY8t+4/ZJ1mrTTG+eGp5gLAKnSVhAGgsmqCY577Nkso1jSzKer1XcCiaGIedpLuTzmUbOFFEVhhTSy+Ki1NLEcjGW2e6Vyg5Pm8VGN75MqyDZsi5igv9Grxq62EHQ4mFea9rns48B74O/bNQ1OoLVO9u/HwdLCgBTZzptrCmNwML6kBWrmCogoH3ueVbHwDCW5kTTMBCoVV+HaZ+qDWu7xZVx49MuCK29QGZj/IrN0N7h78KL0NYajdei87R0mcmWGP0YaJTdCQ4iKi9c77BUYQp+Qpqt+mnIX0cgjQOU= dkruger@kzin" # cspell:disable-line
];
extraGroups = [
"audio"
"video"
"users"
];
uid = 1004;
};
groups.dov.gid = 1004;
};
}
-9
View File
@@ -1,9 +0,0 @@
{
imports = [
./direnv.nix
./git.nix
./zsh.nix
];
programs.starship.enable = true;
}
-8
View File
@@ -1,8 +0,0 @@
{
programs.direnv = {
enable = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};
}
-15
View File
@@ -1,15 +0,0 @@
{
programs.git = {
enable = true;
signing.format = null;
settings = {
user = {
email = "dov.kruger@gmail.com";
name = "Dov Kruger";
};
pull.rebase = true;
color.ui = true;
};
lfs.enable = true;
};
}
-27
View File
@@ -1,27 +0,0 @@
{
programs.zsh = {
enable = true;
syntaxHighlighting.enable = true;
history.size = 10000;
oh-my-zsh = {
enable = true;
plugins = [
"git"
"docker"
"docker-compose"
"colored-man-pages"
"rust"
"systemd"
"tmux"
"ufw"
"z"
];
};
shellAliases = {
"lrt" = "eza --icons -lsnew";
"ls" = "eza";
"ll" = "eza --long --group";
"la" = "eza --all";
};
};
}
-22
View File
@@ -1,22 +0,0 @@
{ config, ... }:
{
imports = [
./cli
./programs.nix
./ssh_config.nix
];
programs = {
home-manager.enable = true;
git.enable = true;
};
home = {
username = "dov";
homeDirectory = "/home/${config.home.username}";
stateVersion = "24.05";
sessionVariables = {
FLAKE = "$HOME/dotfiles";
};
};
}
-56
View File
@@ -1,56 +0,0 @@
{ pkgs, ... }:
{
home.packages = with pkgs; [
# cli
bat
btop
eza
fd
ffmpegthumbnailer
fzf
git
gnupg
imagemagick
jq
ncdu
ouch
p7zip
poppler
rar
ripgrep
starship
tmux
unzip
yazi
zoxide
# system info
hwloc
lynis
pciutils
smartmontools
usbutils
# networking
iperf3
nmap
wget
# python
ruff
uv
# nodejs
nodejs
# Rust packages
trunk
wasm-pack
cargo-watch
cargo-generate
cargo-audit
cargo-update
# nix
nix-init
nix-output-monitor
nix-prefetch
nix-tree
nixfmt
treefmt
];
}
-6
View File
@@ -1,6 +0,0 @@
{
programs.ssh = {
enable = true;
enableDefaultConfig = false;
};
}
-5
View File
@@ -1,5 +0,0 @@
{
imports = [
../home/global.nix
];
}
+1 -38
View File
@@ -3,54 +3,17 @@
home.packages = with pkgs; [
# cli
bat
btop
eza
fd
ffmpegthumbnailer
fzf
git
gnupg
imagemagick
jq
ncdu
ouch
fastfetch
p7zip
poppler
rar
ripgrep
starship
tmux
unzip
yazi
zoxide
# system info
hwloc
lynis
pciutils
smartmontools
usbutils
# networking
iperf3
nmap
wget
# python
ruff
uv
# nodejs
nodejs
# Rust packages
trunk
wasm-pack
cargo-watch
cargo-generate
cargo-audit
cargo-update
# nix
nix-init
nix-output-monitor
nix-prefetch
nix-tree
nixfmt
treefmt
];
}
File renamed without changes.
+4 -5
View File
@@ -1,12 +1,11 @@
{ inputs, pkgs, ... }:
{ pkgs, ... }:
{
imports = [
"${inputs.self}/users/shared/comms.nix"
"${inputs.self}/users/shared/games.nix"
"${inputs.self}/users/shared/sweet.nix"
./firefox
./comms.nix
./games.nix
./kitty.nix
./llm_tools.nix
./sweet.nix
./t3_code
./vscode
];
-257
View File
@@ -1,257 +0,0 @@
{ config, inputs, ... }:
{
imports = [ ./search_engines.nix ];
programs.firefox = {
configPath = "${config.xdg.configHome}/mozilla/firefox";
enable = true;
profiles.richie = {
extensions.packages = with inputs.firefox-addons.packages.x86_64-linux; [
bitwarden
darkreader
dearrow
fastforwardteam
return-youtube-dislikes
sponsorblock
ublock-origin
];
search = {
force = true;
default = "kagi";
order = [
"kagi"
"ddg"
"google"
];
};
settings = {
# SECTION: FASTFOX
# GENERAL
"content.notify.interval" = 100000;
# GFX
"gfx.canvas.accelerated.cache-items" = 4096;
"gfx.canvas.accelerated.cache-size" = 512;
"gfx.content.skia-font-cache-size" = 20;
# DISK CACHE
"browser.cache.jsbc_compression_level" = 3;
# MEDIA CACHE
"media.memory_cache_max_size" = 65536;
"media.cache_readahead_limit" = 7200;
"media.cache_resume_threshold" = 3600;
# IMAGE CACHE
"image.mem.decode_bytes_at_a_time" = 32768;
# NETWORK
"network.buffer.cache.size" = 262144;
"network.buffer.cache.count" = 128;
"network.http.max-connections" = 1800;
"network.http.max-persistent-connections-per-server" = 10;
"network.http.max-urgent-start-excessive-connections-per-host" = 5;
"network.http.accept-encoding" = "gzip, deflate, br, zstd";
"network.http.pacing.requests.enabled" = false;
"network.dnsCacheExpiration" = 3600;
"network.dns.max_high_priority_threads" = 8;
"network.ssl_tokens_cache_capacity" = 10240;
# SPECULATIVE LOADING
"network.dns.disablePrefetch" = true;
"network.prefetch-next" = false;
"network.predictor.enabled" = false;
# EXPERIMENTAL
"layout.css.grid-template-masonry-value.enabled" = true;
"dom.enable_web_task_scheduling" = true;
"layout.css.has-selector.enabled" = true;
"dom.security.sanitizer.enabled" = true;
# SECTION: SECUREFOX
# TRACKING PROTECTION
"browser.contentblocking.category" = "strict";
"urlclassifier.trackingSkipURLs" = "*.reddit.com, *.twitter.com, *.twimg.com, *.tiktok.com";
"urlclassifier.features.socialtracking.skipURLs" = "*.instagram.com, *.twitter.com, *.twimg.com";
"network.cookie.sameSite.noneRequiresSecure" = true;
"browser.download.start_downloads_in_tmp_dir" = true;
"browser.helperApps.deleteTempFileOnExit" = true;
"browser.uitour.enabled" = false;
"privacy.globalprivacycontrol.enabled" = true;
# OCSP & CERTS / HPKP
"security.OCSP.enabled" = 0;
"security.remote_settings.crlite_filters.enabled" = true;
"security.pki.crlite_mode" = 2;
# SSL / TLS
"security.ssl.treat_unsafe_negotiation_as_broken" = true;
"browser.xul.error_pages.expert_bad_cert" = true;
"security.tls.enable_0rtt_data" = false;
# DISK AVOIDANCE
"browser.privatebrowsing.forceMediaMemoryCache" = true;
"browser.sessionstore.interval" = 60000;
# SHUTDOWN & SANITIZING
"privacy.history.custom" = true;
# SEARCH / URL BAR
"browser.search.separatePrivateDefault.ui.enabled" = true;
"browser.urlbar.update2.engineAliasRefresh" = true;
# PREF: restore search engine suggestions
"browser.search.suggest.enabled" = true;
"browser.urlbar.suggest.quicksuggest.sponsored" = false;
"browser.urlbar.suggest.quicksuggest.nonsponsored" = false;
"browser.formfill.enable" = false;
"security.insecure_connection_text.enabled" = true;
"security.insecure_connection_text.pbmode.enabled" = true;
"network.IDN_show_punycode" = true;
# HTTPS-FIRST POLICY
"dom.security.https_first" = true;
"dom.security.https_first_schemeless" = true;
# PASSWORDS
"signon.formlessCapture.enabled" = false;
"signon.rememberSignons" = false;
"signon.privateBrowsingCapture.enabled" = false;
"network.auth.subresource-http-auth-allow" = 1;
"editor.truncate_user_pastes" = false;
# MIXED CONTENT + CROSS-SITE
"security.mixed_content.block_display_content" = true;
"security.mixed_content.upgrade_display_content" = true;
"security.mixed_content.upgrade_display_content.image" = true;
"pdfjs.enableScripting" = false;
"extensions.postDownloadThirdPartyPrompt" = false;
# HEADERS / REFERERS
"network.http.referer.XOriginTrimmingPolicy" = 2;
# CONTAINERS
"privacy.userContext.ui.enabled" = true;
# WEBRTC
"media.peerconnection.ice.proxy_only_if_behind_proxy" = true;
"media.peerconnection.ice.default_address_only" = true;
# SAFE BROWSING
"browser.safebrowsing.downloads.remote.enabled" = false;
# MOZILLA
# PREF: allow websites to ask you to receive site notifications
"permissions.default.desktop-notification" = 0; # allow websites to ask
# PREF: allow websites to ask you for your location
"permissions.default.geo" = 0;
"geo.provider.network.url" =
"https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%";
"permissions.manager.defaultsUrl" = "";
"webchannel.allowObject.urlWhitelist" = "";
# TELEMETRY
"datareporting.policy.dataSubmissionEnabled" = false;
"datareporting.healthreport.uploadEnabled" = false;
"toolkit.telemetry.unified" = false;
"toolkit.telemetry.enabled" = false;
"toolkit.telemetry.server" = "data:,";
"toolkit.telemetry.archive.enabled" = false;
"toolkit.telemetry.newProfilePing.enabled" = false;
"toolkit.telemetry.shutdownPingSender.enabled" = false;
"toolkit.telemetry.updatePing.enabled" = false;
"toolkit.telemetry.bhrPing.enabled" = false;
"toolkit.telemetry.firstShutdownPing.enabled" = false;
"toolkit.telemetry.coverage.opt-out" = true;
"toolkit.coverage.opt-out" = true;
"toolkit.coverage.endpoint.base" = "";
"browser.ping-centre.telemetry" = false;
"browser.newtabpage.activity-stream.feeds.telemetry" = false;
"browser.newtabpage.activity-stream.telemetry" = false;
# EXPERIMENTS
"app.shield.optoutstudies.enabled" = false;
"app.normandy.enabled" = false;
"app.normandy.api_url" = "";
# CRASH REPORTS
"breakpad.reportURL" = "";
"browser.tabs.crashReporting.sendReport" = false;
"browser.crashReports.unsubmittedCheck.autoSubmit2" = false;
# DETECTION
"captivedetect.canonicalURL" = "";
"network.captive-portal-service.enabled" = false;
"network.connectivity-service.enabled" = false;
# SECTION: PESKYFOX
# MOZILLA UI
"browser.privatebrowsing.vpnpromourl" = "";
"extensions.getAddons.showPane" = false;
"extensions.htmlaboutaddons.recommendations.enabled" = false;
"browser.discovery.enabled" = false;
"browser.shell.checkDefaultBrowser" = false;
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons" = false;
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features" = false;
"browser.preferences.moreFromMozilla" = false;
"browser.tabs.tabmanager.enabled" = false;
"browser.aboutConfig.showWarning" = false;
"browser.aboutwelcome.enabled" = false;
# THEME ADJUSTMENTS
"toolkit.legacyUserProfileCustomizations.stylesheets" = true;
"browser.compactmode.show" = true;
"browser.display.focus_ring_on_anything" = true;
"browser.display.focus_ring_style" = 0;
"browser.display.focus_ring_width" = 0;
"layout.css.prefers-color-scheme.content-override" = 2;
# COOKIE BANNER HANDLING
"cookiebanners.service.mode" = 1;
"cookiebanners.service.mode.privateBrowsing" = 1;
# FULLSCREEN NOTICE
"full-screen-api.transition-duration.enter" = "0 0";
"full-screen-api.transition-duration.leave" = "0 0";
"full-screen-api.warning.delay" = -1;
"full-screen-api.warning.timeout" = 0;
# URL BAR
"browser.urlbar.suggest.calculator" = true;
"browser.urlbar.unitConversion.enabled" = true;
"browser.urlbar.trending.featureGate" = false;
# NEW TAB PAGE
"browser.newtabpage.activity-stream.feeds.topsites" = false;
"browser.newtabpage.activity-stream.feeds.section.topstories" = false;
# POCKET
"extensions.pocket.enabled" = false;
# DOWNLOADS
"browser.download.always_ask_before_handling_new_types" = true;
"browser.download.manager.addToRecentDocs" = false;
# PDF
"browser.download.open_pdf_attachments_inline" = true;
# TAB BEHAVIOR
"browser.bookmarks.openInTabClosesMenu" = false;
"browser.menu.showViewImageInfo" = true;
"findbar.highlightAll" = true;
"layout.word_select.eat_space_to_next_word" = false;
# SECTION: MY OVERRIDES
"browser.startup.homepage" = "https://google.com";
"identity.fxaccounts.enabled" = false;
# SECTION SMOOTHFOX
# OPTION: SHARPEN SCROLLING *
"apz.overscroll.enabled" = true; # DEFAULT NON-LINUX
"mousewheel.min_line_scroll_amount" = 10; # 10-40; adjust this number to your liking; default=5
"general.smoothScroll.mouseWheel.durationMinMS" = 80; # default=50
"general.smoothScroll.currentVelocityWeighting" = "0.15"; # default=.25
"general.smoothScroll.stopDecelerationWeighting" = "0.6"; # default=.4
};
};
};
}
-3
View File
@@ -1,3 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 0C7.16 0 0 7.16 0 16C0 23.08 4.58 29.06 10.94 31.18C11.74 31.32 12.04 30.84 12.04 30.42C12.04 30.04 12.02 28.78 12.02 27.44C8 28.18 6.96 26.46 6.64 25.56C6.46 25.1 5.68 23.68 5 23.3C4.44 23 3.64 22.26 4.98 22.24C6.24 22.22 7.14 23.4 7.44 23.88C8.88 26.3 11.18 25.62 12.1 25.2C12.24 24.16 12.66 23.46 13.12 23.06C9.56 22.66 5.84 21.28 5.84 15.16C5.84 13.42 6.46 11.98 7.48 10.86C7.32 10.46 6.76 8.82 7.64 6.62C7.64 6.62 8.98 6.2 12.04 8.26C13.32 7.9 14.68 7.72 16.04 7.72C17.4 7.72 18.76 7.9 20.04 8.26C23.1 6.18 24.44 6.62 24.44 6.62C25.32 8.82 24.76 10.46 24.6 10.86C25.62 11.98 26.24 13.4 26.24 15.16C26.24 21.3 22.5 22.66 18.94 23.06C19.52 23.56 20.02 24.52 20.02 26.02C20.02 28.16 20 29.88 20 30.42C20 30.84 20.3 31.34 21.1 31.18C27.42 29.06 32 23.06 32 16C32 7.16 24.84 0 16 0V0Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 B

@@ -1,99 +0,0 @@
{ pkgs, ... }:
{
programs.firefox.profiles.richie.search.engines = {
"Nix Options" = {
urls = [
{
template = "https://search.nixos.org/options";
params = [
{
name = "type";
value = "packages";
}
{
name = "channel";
value = "unstable";
}
{
name = "query";
value = "{searchTerms}";
}
];
}
];
icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
definedAliases = [ "@o" ];
};
"Nix Packages" = {
urls = [
{
template = "https://search.nixos.org/packages";
params = [
{
name = "type";
value = "packages";
}
{
name = "channel";
value = "unstable";
}
{
name = "query";
value = "{searchTerms}";
}
];
}
];
icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
definedAliases = [ "@n" ];
};
"Nix Packages pr-tracker" = {
urls = [
{
template = "https://nixpk.gs/pr-tracker.html?";
params = [
{
name = "pr";
value = "{searchTerms}";
}
];
}
];
icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
definedAliases = [ "@nprt" ];
};
"kagi" = {
urls = [
{
template = "https://kagi.com/search?";
params = [
{
name = "q";
value = "{searchTerms}";
}
];
}
];
icon = ./kagi.png;
};
github = {
urls = [
{
template = "https://github.com/search?";
params = [
{
name = "q";
value = "{searchTerms}";
}
{
name = "type";
value = "code";
}
];
}
];
icon = ./github.svg;
definedAliases = [ "@g" ];
};
};
}
File renamed without changes.
File renamed without changes.
-13
View File
@@ -3,21 +3,14 @@
home.packages = with pkgs; [
# cli
bat
fd
ffmpegthumbnailer
fzf
git
gnupg
imagemagick
jq
ncdu
fastfetch
ouch
p7zip
poppler
rar
unzip
yazi
zoxide
# Home Assistant
esphome
@@ -35,8 +28,6 @@
# python
ruff
uv
# nodejs
nodejs
# Rust packages
bacon
cargo
@@ -51,9 +42,6 @@
rustfmt
trunk
wasm-pack
# cpp
clang-tools
clang_20
# nix
nix-init
nix-output-monitor
@@ -61,6 +49,5 @@
nix-tree
nixfmt
treefmt
codebase-memory-mcp
];
}
-44
View File
@@ -1,44 +0,0 @@
{
pkgs,
config,
...
}:
let
ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups;
in
{
users = {
users.steve = {
isNormalUser = true;
shell = pkgs.zsh;
group = "steve";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJH03VzDbUhzfhvwD+OsYh6GobODYaI9jdNdzWQoqFsp matth@Jove" # cspell:disable-line
];
extraGroups = [
"audio"
"video"
"wheel"
"users"
]
++ ifTheyExist [
"dialout"
"docker"
"hass"
"libvirtd"
"networkmanager"
"plugdev"
"scanner"
"transmission"
"uaccess"
"wireshark"
];
uid = 1005;
};
groups.steve.gid = 1005;
};
home-manager.users.steve = import ./systems/${config.networking.hostName}.nix;
}
-9
View File
@@ -1,9 +0,0 @@
{
imports = [
./direnv.nix
./git.nix
./zsh.nix
];
programs.starship.enable = true;
}
-8
View File
@@ -1,8 +0,0 @@
{
programs.direnv = {
enable = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};
}
-15
View File
@@ -1,15 +0,0 @@
{
programs.git = {
enable = true;
signing.format = null;
settings = {
user = {
email = "matthew.michal11@gmail.com";
name = "Matthew Michal";
};
pull.rebase = true;
color.ui = true;
};
lfs.enable = true;
};
}
-28
View File
@@ -1,28 +0,0 @@
{
programs.zsh = {
enable = true;
syntaxHighlighting.enable = true;
history.size = 10000;
oh-my-zsh = {
enable = true;
plugins = [
"git"
"docker"
"docker-compose"
"colored-man-pages"
"rust"
"systemd"
"tmux"
"ufw"
"z"
];
};
shellAliases = {
"lrt" = "eza --icons -lsnew";
"ls" = "eza";
"ll" = "eza --long --group";
"la" = "eza --all";
};
};
}
-22
View File
@@ -1,22 +0,0 @@
{ config, ... }:
{
imports = [
./cli
./programs.nix
./ssh_config.nix
];
programs = {
home-manager.enable = true;
git.enable = true;
};
home = {
username = "steve";
homeDirectory = "/home/${config.home.username}";
stateVersion = "24.05";
sessionVariables = {
FLAKE = "$HOME/dotfiles";
};
};
}
-56
View File
@@ -1,56 +0,0 @@
{ pkgs, ... }:
{
home.packages = with pkgs; [
# cli
bat
btop
eza
fd
ffmpegthumbnailer
fzf
git
gnupg
imagemagick
jq
ncdu
ouch
p7zip
poppler
rar
ripgrep
starship
tmux
unzip
yazi
zoxide
# system info
hwloc
lynis
pciutils
smartmontools
usbutils
# networking
iperf3
nmap
wget
# python
ruff
uv
# nodejs
nodejs
# Rust packages
trunk
wasm-pack
cargo-watch
cargo-generate
cargo-audit
cargo-update
# nix
nix-init
nix-output-monitor
nix-prefetch
nix-tree
nixfmt
treefmt
];
}
-6
View File
@@ -1,6 +0,0 @@
{
programs.ssh = {
enable = true;
enableDefaultConfig = false;
};
}
-5
View File
@@ -1,5 +0,0 @@
{
imports = [
../home/global.nix
];
}
-5
View File
@@ -1,5 +0,0 @@
{
imports = [
../home/global.nix
];
}