Compare commits
1
Commits
main
..
4a228b5bdb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a228b5bdb |
@@ -15,6 +15,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: merge_flake_lock_update
|
- name: merge_flake_lock_update
|
||||||
run: >-
|
run: >-
|
||||||
|
nix develop .#devShells.x86_64-linux.default -c
|
||||||
python -m python.gitea_flake_lock merge
|
python -m python.gitea_flake_lock merge
|
||||||
--repo "${{ github.repository }}"
|
--repo "${{ github.repository }}"
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
@@ -21,5 +21,6 @@ jobs:
|
|||||||
JEEVES_BOT_TOKEN: ${{ secrets.JEEVES_BOT_TOKEN }}
|
JEEVES_BOT_TOKEN: ${{ secrets.JEEVES_BOT_TOKEN }}
|
||||||
GITEA_URL: https://gitea.tmmworkshop.com
|
GITEA_URL: https://gitea.tmmworkshop.com
|
||||||
run: >-
|
run: >-
|
||||||
|
nix develop .#devShells.x86_64-linux.default -c
|
||||||
python -m python.gitea_flake_lock update
|
python -m python.gitea_flake_lock update
|
||||||
--repo "${{ github.repository }}"
|
--repo "${{ github.repository }}"
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ in
|
|||||||
"flakes"
|
"flakes"
|
||||||
"ca-derivations"
|
"ca-derivations"
|
||||||
];
|
];
|
||||||
system-features = lib.mkAfter [
|
|
||||||
"gccarch-x86-64-v2"
|
|
||||||
"gccarch-x86-64-v3"
|
|
||||||
];
|
|
||||||
warn-dirty = false;
|
warn-dirty = false;
|
||||||
flake-registry = ""; # disable global flake registries
|
flake-registry = ""; # disable global flake registries
|
||||||
connect-timeout = 10;
|
connect-timeout = 10;
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
{
|
{
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
git
|
git
|
||||||
|
my_python
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
logDriver = "local";
|
logDriver = "local";
|
||||||
storageDriver = "overlay2";
|
storageDriver = "overlay2";
|
||||||
daemon.settings = {
|
daemon.settings = {
|
||||||
live-restore = false;
|
|
||||||
experimental = true;
|
experimental = true;
|
||||||
exec-opts = [ "native.cgroupdriver=systemd" ];
|
exec-opts = [ "native.cgroupdriver=systemd" ];
|
||||||
log-opts = {
|
log-opts = {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
nixpkgs.hostPlatform = {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
gcc = {
|
|
||||||
arch = "x86-64-v3";
|
|
||||||
tune = "generic";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
inputs,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
@@ -7,11 +6,6 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.services.snapshot_manager;
|
cfg = config.services.snapshot_manager;
|
||||||
snapshotManagerPackages =
|
|
||||||
ps: with ps; [
|
|
||||||
httpx
|
|
||||||
typer
|
|
||||||
];
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
@@ -40,41 +34,6 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
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 = {
|
systemd = {
|
||||||
services.snapshot_manager = {
|
services.snapshot_manager = {
|
||||||
description = "ZFS Snapshot Manager";
|
description = "ZFS Snapshot Manager";
|
||||||
@@ -86,7 +45,7 @@ in
|
|||||||
};
|
};
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
ExecStart = "${pkgs.snapshot_manager_python}/bin/python -m python.tools.snapshot_manager ${lib.escapeShellArg cfg.path}";
|
ExecStart = "${pkgs.my_python}/bin/python -m python.tools.snapshot_manager ${lib.escapeShellArg cfg.path}";
|
||||||
}
|
}
|
||||||
// lib.optionalAttrs (cfg.EnvironmentFile != null) {
|
// lib.optionalAttrs (cfg.EnvironmentFile != null) {
|
||||||
EnvironmentFile = cfg.EnvironmentFile;
|
EnvironmentFile = cfg.EnvironmentFile;
|
||||||
|
|||||||
@@ -22,12 +22,6 @@ hourly = 0
|
|||||||
daily = 0
|
daily = 0
|
||||||
monthly = 0
|
monthly = 0
|
||||||
|
|
||||||
["root_pool/nix_build"]
|
|
||||||
15_min = 1
|
|
||||||
hourly = 0
|
|
||||||
daily = 0
|
|
||||||
monthly = 0
|
|
||||||
|
|
||||||
["root_pool/var"]
|
["root_pool/var"]
|
||||||
15_min = 8
|
15_min = 8
|
||||||
hourly = 24
|
hourly = 24
|
||||||
|
|||||||
Generated
+38
-15
@@ -20,6 +20,28 @@
|
|||||||
"type": "github"
|
"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": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -27,11 +49,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1788651960,
|
"lastModified": 1786999651,
|
||||||
"narHash": "sha256-v9wJd32eZ2bvhBzVOd7TIjLQd011P7nwOhjKtWlci5I=",
|
"narHash": "sha256-MTGMFlLDTklsXhCp4r5GXB4VAVadPdalXLvUjd/K7h0=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "2c0350c759688177331b8f5242311fae8877bdb3",
|
"rev": "353742587cbaf079b3caee743115d037bc51fea6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -45,11 +67,11 @@
|
|||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1788860136,
|
"lastModified": 1786867632,
|
||||||
"narHash": "sha256-MhPMOFV4pVkygWEbQ8t1De/uQ9cWF1u++tRe2L5tG48=",
|
"narHash": "sha256-ez+ubZlA1RtdjCB18a6zJ9M4u8qoPDy08EcnsW5M3Xw=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "62173785b9a18c78b4a15aca2623d02bceb9d077",
|
"rev": "ff17823245ab9ff7bcae6acf950bd89cba82c38c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -74,11 +96,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-master": {
|
"nixpkgs-master": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1788892992,
|
"lastModified": 1787081018,
|
||||||
"narHash": "sha256-cIMFh9gyU4/aLeB3JCcsWM3tTAvD9pAq9Smr1Wa8aIU=",
|
"narHash": "sha256-K0uwZBtZsbBigHAMQW7YWti3gPe6a5ct5bcOw5F+Q9Y=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "dff6994123e257ec9901c271bc2b52e64d7c8f05",
|
"rev": "cacac5ac351a010599d9f9d106acfed25a8e4c77",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -106,11 +128,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1788752844,
|
"lastModified": 1787001381,
|
||||||
"narHash": "sha256-VaWGJ6+cIYN2erfSecbRV+4ljI185Ty2wUrXyvQbgOw=",
|
"narHash": "sha256-Ue1Yo8gfHdD4TMtNewhA4tkSYeFqXThju0nCyJc3ALo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "dc5d91f840324650bac8c379428c7037a416959a",
|
"rev": "ec2d622de0773551768cf98f3fc50cbcc003b9c5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -123,6 +145,7 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
|
"firefox-addons": "firefox-addons",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"nixos-hardware": "nixos-hardware",
|
"nixos-hardware": "nixos-hardware",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
@@ -139,11 +162,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1788337237,
|
"lastModified": 1786629091,
|
||||||
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
|
"narHash": "sha256-gkig4nPi1CWc4Z50GBsjE4ygSE7hMpl/TwID2an2Cck=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
|
"rev": "a8627b21b9107c5711c96b84f32a9a4b3d45295f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -26,6 +26,11 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
firefox-addons = {
|
||||||
|
url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
sops-nix = {
|
sops-nix = {
|
||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|||||||
+30
-2
@@ -15,6 +15,34 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
test-exclusions = import ./test-exclusions.nix;
|
python-env = final: _prev: {
|
||||||
x86-64-v3-workarounds = import ./x86-64-v3-workarounds.nix;
|
my_python = final.python314.withPackages (
|
||||||
|
ps: with ps; [
|
||||||
|
alembic
|
||||||
|
apprise
|
||||||
|
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
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
# 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)
|
|
||||||
):'
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,49 @@ authors = [{ name = "Richie Cahill", email = "richie@tmmworkshop.com" }]
|
|||||||
requires-python = "~=3.14.0"
|
requires-python = "~=3.14.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
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",
|
||||||
|
"beautifulsoup4",
|
||||||
|
"bm25s",
|
||||||
|
"ebooklib",
|
||||||
|
"fastapi",
|
||||||
|
"fastapi-cli",
|
||||||
|
"httpx",
|
||||||
|
"jinja2",
|
||||||
|
"pgvector",
|
||||||
|
"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]
|
[tool.ruff]
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ DEFAULT_BASE_BRANCH = "main"
|
|||||||
DEFAULT_BRANCH = "automation/update-flake-lock"
|
DEFAULT_BRANCH = "automation/update-flake-lock"
|
||||||
DEFAULT_GITEA_URL = "https://gitea.tmmworkshop.com"
|
DEFAULT_GITEA_URL = "https://gitea.tmmworkshop.com"
|
||||||
PR_LABELS = ["dependencies", "automated", "flake_lock_update"]
|
PR_LABELS = ["dependencies", "automated", "flake_lock_update"]
|
||||||
PR_CHECK_WORKFLOWS = ["build_systems.yml", "treefmt.yml"]
|
PR_CHECK_WORKFLOWS = ["build_systems.yml", "treefmt.yml", "pytest.yml"]
|
||||||
PR_TITLE = "Update flake.lock"
|
PR_TITLE = "Update flake.lock"
|
||||||
PR_BODY = "Automated flake.lock update."
|
PR_BODY = "Automated flake.lock update."
|
||||||
|
|
||||||
|
|||||||
@@ -176,21 +176,6 @@ def create_zfs_datasets() -> None:
|
|||||||
run_output(("zfs", "create", "root_pool/home"))
|
run_output(("zfs", "create", "root_pool/home"))
|
||||||
run_output(("zfs", "create", "-o", "reservation=1G", "root_pool/var"))
|
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", "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"))
|
datasets = run_output(("zfs", "list", "-o", "name"))
|
||||||
|
|
||||||
expected_datasets = {
|
expected_datasets = {
|
||||||
@@ -198,7 +183,6 @@ def create_zfs_datasets() -> None:
|
|||||||
"root_pool/home",
|
"root_pool/home",
|
||||||
"root_pool/var",
|
"root_pool/var",
|
||||||
"root_pool/nix",
|
"root_pool/nix",
|
||||||
"root_pool/nix_build",
|
|
||||||
}
|
}
|
||||||
missing_datasets = expected_datasets.difference(datasets.splitlines())
|
missing_datasets = expected_datasets.difference(datasets.splitlines())
|
||||||
if missing_datasets:
|
if missing_datasets:
|
||||||
@@ -264,7 +248,6 @@ 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'
|
' "/home" = {\n device = "root_pool/home";\n fsType = "zfs";\n };\n\n'
|
||||||
' "/var" = {\n device = "root_pool/var";\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" = {\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'
|
' "/boot" = {\n'
|
||||||
f' device = "/dev/disk/by-uuid/{get_boot_drive_id(disks[0])}";\n'
|
f' device = "/dev/disk/by-uuid/{get_boot_drive_id(disks[0])}";\n'
|
||||||
' fsType = "vfat";\n options = [\n "fmask=0077"\n'
|
' fsType = "vfat";\n options = [\n "fmask=0077"\n'
|
||||||
@@ -287,7 +270,6 @@ 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/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/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", 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:
|
for disk in disks:
|
||||||
run_output(("mkfs.vfat", "-n", "EFI", f"{disk}-part1"))
|
run_output(("mkfs.vfat", "-n", "EFI", f"{disk}-part1"))
|
||||||
|
|||||||
+5
-25
@@ -5,13 +5,10 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from os import getenv
|
from os import getenv
|
||||||
|
|
||||||
import httpx
|
from apprise import Apprise
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
def signal_alert(body: str, title: str = "") -> None:
|
||||||
"""Send a signal alert.
|
"""Send a signal alert.
|
||||||
@@ -20,31 +17,14 @@ def signal_alert(body: str, title: str = "") -> None:
|
|||||||
body (str): The body of the alert.
|
body (str): The body of the alert.
|
||||||
title (str, optional): The title of the alert. Defaults to "".
|
title (str, optional): The title of the alert. Defaults to "".
|
||||||
"""
|
"""
|
||||||
|
apprise_client = Apprise()
|
||||||
|
|
||||||
from_phone = getenv("SIGNAL_ALERT_FROM_PHONE")
|
from_phone = getenv("SIGNAL_ALERT_FROM_PHONE")
|
||||||
to_phone = getenv("SIGNAL_ALERT_TO_PHONE")
|
to_phone = getenv("SIGNAL_ALERT_TO_PHONE")
|
||||||
if not from_phone or not to_phone:
|
if not from_phone or not to_phone:
|
||||||
logger.info("SIGNAL_ALERT_FROM_PHONE or SIGNAL_ALERT_TO_PHONE not set")
|
logger.info("SIGNAL_ALERT_FROM_PHONE or SIGNAL_ALERT_TO_PHONE not set")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Apprise's Signal integration did not support titles, so preserve that behavior.
|
apprise_client.add(f"signal://localhost:8989/{from_phone}/{to_phone}")
|
||||||
if title:
|
|
||||||
logger.debug("Signal does not support notification titles; ignoring title")
|
|
||||||
|
|
||||||
try:
|
apprise_client.notify(title=title, body=body)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""system_tests."""
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"""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)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
"""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()
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# loader path, which NixOS does not provide globally.
|
# loader path, which NixOS does not provide globally.
|
||||||
ebook-search = pkgs.mkShell {
|
ebook-search = pkgs.mkShell {
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
python314
|
my_python
|
||||||
uv
|
uv
|
||||||
];
|
];
|
||||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
nix
|
nix
|
||||||
home-manager
|
home-manager
|
||||||
git
|
git
|
||||||
|
my_python
|
||||||
|
|
||||||
ssh-to-age
|
ssh-to-age
|
||||||
gnupg
|
gnupg
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
imports = [
|
imports = [
|
||||||
"${inputs.self}/users/math"
|
"${inputs.self}/users/math"
|
||||||
"${inputs.self}/users/richie"
|
"${inputs.self}/users/richie"
|
||||||
|
"${inputs.self}/users/steve"
|
||||||
"${inputs.self}/common/global"
|
"${inputs.self}/common/global"
|
||||||
"${inputs.self}/common/optional/docker.nix"
|
"${inputs.self}/common/optional/docker.nix"
|
||||||
"${inputs.self}/common/optional/monitoring-agent.nix"
|
"${inputs.self}/common/optional/monitoring-agent.nix"
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
"${inputs.self}/common/optional/x86-64-v3.nix"
|
"${inputs.self}/common/optional/yubikey.nix"
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./syncthing.nix
|
./syncthing.nix
|
||||||
|
|||||||
@@ -50,11 +50,6 @@
|
|||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
"/nix/var/nix/builds" = {
|
|
||||||
device = "root_pool/nix_build";
|
|
||||||
fsType = "zfs";
|
|
||||||
};
|
|
||||||
|
|
||||||
"/var" = {
|
"/var" = {
|
||||||
device = "root_pool/var";
|
device = "root_pool/var";
|
||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
|
|||||||
@@ -22,12 +22,6 @@ hourly = 0
|
|||||||
daily = 0
|
daily = 0
|
||||||
monthly = 0
|
monthly = 0
|
||||||
|
|
||||||
["root_pool/nix_build"]
|
|
||||||
15_min = 1
|
|
||||||
hourly = 0
|
|
||||||
daily = 0
|
|
||||||
monthly = 0
|
|
||||||
|
|
||||||
["root_pool/models"]
|
["root_pool/models"]
|
||||||
15_min = 4
|
15_min = 4
|
||||||
hourly = 24
|
hourly = 24
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
"${inputs.self}/common/optional/x86-64-v3.nix"
|
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./docker
|
./docker
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
|
|||||||
@@ -53,11 +53,6 @@
|
|||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
"/nix/var/nix/builds" = {
|
|
||||||
device = "root_pool/nix_build";
|
|
||||||
fsType = "zfs";
|
|
||||||
};
|
|
||||||
|
|
||||||
"/boot" = {
|
"/boot" = {
|
||||||
device = "/dev/disk/by-uuid/12CE-A600";
|
device = "/dev/disk/by-uuid/12CE-A600";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
|
|||||||
@@ -4,20 +4,6 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
nixpkgs.overlays = [
|
|
||||||
(final: _prev: {
|
|
||||||
heater_python = final.python314.withPackages (
|
|
||||||
ps: with ps; [
|
|
||||||
fastapi
|
|
||||||
pydantic
|
|
||||||
tinytuya
|
|
||||||
typer
|
|
||||||
uvicorn
|
|
||||||
]
|
|
||||||
);
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 8124 ];
|
networking.firewall.allowedTCPPorts = [ 8124 ];
|
||||||
|
|
||||||
systemd.services.heater-api = {
|
systemd.services.heater-api = {
|
||||||
@@ -31,7 +17,7 @@
|
|||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
ExecStart = "${pkgs.heater_python}/bin/python -m python.heater.main --host 0.0.0.0 --port 8124";
|
ExecStart = "${pkgs.my_python}/bin/python -m python.heater.main --host 0.0.0.0 --port 8124";
|
||||||
EnvironmentFile = "/etc/heater.env";
|
EnvironmentFile = "/etc/heater.env";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = "5s";
|
RestartSec = "5s";
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
paho-mqtt # for mqtt
|
paho-mqtt # for mqtt
|
||||||
psycopg2 # for postgresql
|
psycopg2 # for postgresql
|
||||||
py-improv-ble-client # for esphome
|
py-improv-ble-client # for esphome
|
||||||
|
pymodbus # for modbus
|
||||||
pyopenweathermap # for weather
|
pyopenweathermap # for weather
|
||||||
pymetno # for met.no weather
|
pymetno # for met.no weather
|
||||||
uiprotect # for ubiquiti integration
|
uiprotect # for ubiquiti integration
|
||||||
@@ -75,10 +76,7 @@
|
|||||||
jsonpath # for rest sensors
|
jsonpath # for rest sensors
|
||||||
monarchmoneycommunity # for monarch
|
monarchmoneycommunity # for monarch
|
||||||
];
|
];
|
||||||
extraComponents = [
|
extraComponents = [ "isal" ];
|
||||||
"isal"
|
|
||||||
"modbus" # for victron modbus integration
|
|
||||||
];
|
|
||||||
customComponents = with pkgs.home-assistant-custom-components; [
|
customComponents = with pkgs.home-assistant-custom-components; [
|
||||||
garmin_connect
|
garmin_connect
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
{ 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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,10 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
"${inputs.self}/users/dov"
|
||||||
"${inputs.self}/users/math"
|
"${inputs.self}/users/math"
|
||||||
"${inputs.self}/users/richie"
|
"${inputs.self}/users/richie"
|
||||||
|
"${inputs.self}/users/steve"
|
||||||
"${inputs.self}/common/global"
|
"${inputs.self}/common/global"
|
||||||
"${inputs.self}/common/optional/docker.nix"
|
"${inputs.self}/common/optional/docker.nix"
|
||||||
"${inputs.self}/common/optional/monitoring-agent.nix"
|
"${inputs.self}/common/optional/monitoring-agent.nix"
|
||||||
@@ -13,12 +15,10 @@ in
|
|||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
"${inputs.self}/common/optional/x86-64-v3.nix"
|
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./monitoring
|
./monitoring
|
||||||
./docker
|
./docker
|
||||||
./services
|
./services
|
||||||
./containers.nix
|
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./networking
|
./networking
|
||||||
./programs.nix
|
./programs.nix
|
||||||
|
|||||||
@@ -96,11 +96,6 @@ in
|
|||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
"/nix/var/nix/builds" = {
|
|
||||||
device = "root_pool/nix_build";
|
|
||||||
fsType = "zfs";
|
|
||||||
};
|
|
||||||
|
|
||||||
"/var" = {
|
"/var" = {
|
||||||
device = "root_pool/var";
|
device = "root_pool/var";
|
||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
|
|||||||
@@ -23,13 +23,6 @@ let
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
giteaAutomationPython = pkgs.python314.withPackages (
|
|
||||||
ps: with ps; [
|
|
||||||
httpx
|
|
||||||
pydantic
|
|
||||||
typer
|
|
||||||
]
|
|
||||||
);
|
|
||||||
runnerConfigFile = (pkgs.formats.yaml { }).generate "gitea-runner.yaml" { };
|
runnerConfigFile = (pkgs.formats.yaml { }).generate "gitea-runner.yaml" { };
|
||||||
registerRunner = pkgs.writeShellApplication {
|
registerRunner = pkgs.writeShellApplication {
|
||||||
name = "register-gitea-runner";
|
name = "register-gitea-runner";
|
||||||
@@ -50,8 +43,6 @@ let
|
|||||||
useHostResolvConf = false;
|
useHostResolvConf = false;
|
||||||
};
|
};
|
||||||
nix.settings = {
|
nix.settings = {
|
||||||
cores = 8;
|
|
||||||
max-jobs = 2;
|
|
||||||
system-features = lib.mkAfter [
|
system-features = lib.mkAfter [
|
||||||
"gccarch-x86-64-v2"
|
"gccarch-x86-64-v2"
|
||||||
"gccarch-x86-64-v3"
|
"gccarch-x86-64-v3"
|
||||||
@@ -96,20 +87,18 @@ let
|
|||||||
url = "http://192.168.99.14:6443/";
|
url = "http://192.168.99.14:6443/";
|
||||||
labels = runnerLabels;
|
labels = runnerLabels;
|
||||||
tokenFile = "/run/secrets/gitea-runners/registration-token";
|
tokenFile = "/run/secrets/gitea-runners/registration-token";
|
||||||
settings.runner.timeout = "12h";
|
|
||||||
hostPackages = with pkgs; [
|
hostPackages = with pkgs; [
|
||||||
bash
|
bash
|
||||||
coreutils
|
coreutils
|
||||||
curl
|
curl
|
||||||
gawk
|
gawk
|
||||||
gitMinimal
|
gitMinimal
|
||||||
giteaAutomationPython
|
|
||||||
gnused
|
gnused
|
||||||
|
my_python
|
||||||
nix
|
nix
|
||||||
nixfmt
|
nixfmt
|
||||||
nixos-rebuild
|
nixos-rebuild
|
||||||
nodejs
|
nodejs
|
||||||
ruff
|
|
||||||
treefmt
|
treefmt
|
||||||
wget
|
wget
|
||||||
];
|
];
|
||||||
@@ -195,6 +184,93 @@ in
|
|||||||
isReadOnly = false;
|
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;
|
) cfg.containers;
|
||||||
|
|
||||||
@@ -205,7 +281,6 @@ in
|
|||||||
value = {
|
value = {
|
||||||
requires = [ "gitea.service" ];
|
requires = [ "gitea.service" ];
|
||||||
after = [ "gitea.service" ];
|
after = [ "gitea.service" ];
|
||||||
serviceConfig.CPUQuota = "800%";
|
|
||||||
};
|
};
|
||||||
}) (builtins.attrNames (filterAttrs (_: c: c.enable) cfg.containers))
|
}) (builtins.attrNames (filterAttrs (_: c: c.enable) cfg.containers))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ sudo zpool create scratch -o ashift=12 -O acltype=posixacl -O atime=off -O dnode
|
|||||||
# media datasets
|
# media datasets
|
||||||
sudo zfs create media/temp -o sync=disabled -o redundant_metadata=none
|
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 -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/docker -o compression=zstd-9
|
||||||
sudo zfs create media/secure/github-runners -o compression=zstd-9 -o sync=disabled
|
sudo zfs create media/secure/github-runners -o compression=zstd-9 -o sync=disabled
|
||||||
sudo zfs create media/secure/notes -o copies=2
|
sudo zfs create media/secure/notes -o copies=2
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,47 +6,8 @@
|
|||||||
let
|
let
|
||||||
vars = import ../vars.nix;
|
vars = import ../vars.nix;
|
||||||
stateDir = "${vars.services}/gems";
|
stateDir = "${vars.services}/gems";
|
||||||
gemsPackages =
|
|
||||||
ps: with ps; [
|
|
||||||
fastapi
|
|
||||||
jinja2
|
|
||||||
pydantic
|
|
||||||
pydantic-settings
|
|
||||||
python-multipart
|
|
||||||
typer
|
|
||||||
uvicorn
|
|
||||||
];
|
|
||||||
in
|
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.groups.gems = { };
|
||||||
users.users.gems = {
|
users.users.gems = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
@@ -75,7 +36,7 @@ in
|
|||||||
Type = "simple";
|
Type = "simple";
|
||||||
User = "gems";
|
User = "gems";
|
||||||
Group = "gems";
|
Group = "gems";
|
||||||
ExecStart = "${pkgs.gems_python}/bin/python -m python.gems.main --host 0.0.0.0 --port 8002";
|
ExecStart = "${pkgs.my_python}/bin/python -m python.gems.main --host 0.0.0.0 --port 8002";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = "5s";
|
RestartSec = "5s";
|
||||||
StandardOutput = "journal";
|
StandardOutput = "journal";
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ in
|
|||||||
actions = {
|
actions = {
|
||||||
ENABLED = true;
|
ENABLED = true;
|
||||||
DEFAULT_ACTIONS_URL = "github";
|
DEFAULT_ACTIONS_URL = "github";
|
||||||
ENDLESS_TASK_TIMEOUT = "12h";
|
|
||||||
};
|
};
|
||||||
service.DISABLE_REGISTRATION = true;
|
service.DISABLE_REGISTRATION = true;
|
||||||
server = {
|
server = {
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
vars = import ../vars.nix;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
systemd = {
|
systemd = {
|
||||||
services = {
|
services = {
|
||||||
@@ -26,6 +30,21 @@
|
|||||||
ExecStart = "${pkgs.bash}/bin/bash -c 'echo 1 > /sys/bus/pci/devices/0000:61:00.0/remove'";
|
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 = {
|
timers = {
|
||||||
plex_permission = {
|
plex_permission = {
|
||||||
@@ -36,6 +55,13 @@
|
|||||||
Unit = "plex_permission.service";
|
Unit = "plex_permission.service";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
startup_validation = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnBootSec = "10min";
|
||||||
|
Unit = "startup_validation.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
zpool = ["root_pool", "storage", "media"]
|
||||||
|
services = [
|
||||||
|
"audiobookshelf",
|
||||||
|
"docker",
|
||||||
|
"jellyfin",
|
||||||
|
]
|
||||||
@@ -23,12 +23,6 @@ hourly = 0
|
|||||||
daily = 0
|
daily = 0
|
||||||
monthly = 0
|
monthly = 0
|
||||||
|
|
||||||
["root_pool/nix_build"]
|
|
||||||
15_min = 1
|
|
||||||
hourly = 0
|
|
||||||
daily = 0
|
|
||||||
monthly = 0
|
|
||||||
|
|
||||||
["root_pool/var"]
|
["root_pool/var"]
|
||||||
15_min = 8
|
15_min = 8
|
||||||
hourly = 24
|
hourly = 24
|
||||||
@@ -66,12 +60,6 @@ daily = 30
|
|||||||
monthly = 12
|
monthly = 12
|
||||||
|
|
||||||
# media
|
# media
|
||||||
["media/secure/containers"]
|
|
||||||
15_min = 2
|
|
||||||
hourly = 0
|
|
||||||
daily = 0
|
|
||||||
monthly = 0
|
|
||||||
|
|
||||||
["media/temp"]
|
["media/temp"]
|
||||||
15_min = 2
|
15_min = 2
|
||||||
hourly = 0
|
hourly = 0
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit zfs_media zfs_storage zfs_scratch;
|
inherit zfs_media zfs_storage zfs_scratch;
|
||||||
containers = "${zfs_media}/containers";
|
|
||||||
database = "${zfs_media}/database";
|
database = "${zfs_media}/database";
|
||||||
docker = "${zfs_media}/docker";
|
docker = "${zfs_media}/docker";
|
||||||
docker_configs = "${zfs_media}/docker/configs";
|
docker_configs = "${zfs_media}/docker/configs";
|
||||||
|
|||||||
@@ -10,12 +10,13 @@
|
|||||||
"${inputs.self}/users/richie"
|
"${inputs.self}/users/richie"
|
||||||
"${inputs.self}/common/global"
|
"${inputs.self}/common/global"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/x86-64-v3.nix"
|
|
||||||
./disk-config.nix
|
./disk-config.nix
|
||||||
./haproxy
|
./haproxy
|
||||||
./monitoring.nix
|
./monitoring.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
|
|
||||||
boot = {
|
boot = {
|
||||||
# Avoid consuming the VM's limited memory for /tmp.
|
# Avoid consuming the VM's limited memory for /tmp.
|
||||||
tmp.useTmpfs = false;
|
tmp.useTmpfs = false;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
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}" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,10 +9,10 @@
|
|||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.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/yubikey.nix"
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
|
./open_webui.nix
|
||||||
./programs.nix
|
./programs.nix
|
||||||
./qmk.nix
|
./qmk.nix
|
||||||
./syncthing.nix
|
./syncthing.nix
|
||||||
|
|||||||
@@ -34,22 +34,14 @@
|
|||||||
device = "root_pool/root";
|
device = "root_pool/root";
|
||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
"/home" = {
|
"/home" = {
|
||||||
device = "root_pool/home";
|
device = "root_pool/home";
|
||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
"/nix/var/nix/builds" = {
|
|
||||||
device = "root_pool/nix_build";
|
|
||||||
fsType = "zfs";
|
|
||||||
};
|
|
||||||
|
|
||||||
"/var" = {
|
"/var" = {
|
||||||
device = "root_pool/var";
|
device = "root_pool/var";
|
||||||
fsType = "zfs";
|
fsType = "zfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
"/boot" = {
|
"/boot" = {
|
||||||
device = "/dev/disk/by-uuid/12CE-A600";
|
device = "/dev/disk/by-uuid/12CE-A600";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
"""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"]
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""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
|
||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from os import environ
|
from os import environ
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import httpx
|
from apprise import Apprise
|
||||||
|
|
||||||
from python.signal_alert import signal_alert
|
from python.signal_alert import signal_alert
|
||||||
|
|
||||||
@@ -19,23 +19,14 @@ def test_signal_alert(mocker: MockerFixture) -> None:
|
|||||||
environ["SIGNAL_ALERT_TO_PHONE"] = "0987654321"
|
environ["SIGNAL_ALERT_TO_PHONE"] = "0987654321"
|
||||||
|
|
||||||
mock_logger = mocker.patch("python.signal_alert.logger")
|
mock_logger = mocker.patch("python.signal_alert.logger")
|
||||||
mock_response = mocker.MagicMock(spec=httpx.Response, status_code=httpx.codes.CREATED)
|
mock_apprise_client = mocker.MagicMock(spec=Apprise)
|
||||||
mock_post = mocker.patch("python.signal_alert.httpx.post", return_value=mock_response)
|
mocker.patch("python.signal_alert.Apprise", return_value=mock_apprise_client)
|
||||||
|
|
||||||
signal_alert("test")
|
signal_alert("test")
|
||||||
|
|
||||||
mock_logger.info.assert_not_called()
|
mock_logger.info.assert_not_called()
|
||||||
mock_post.assert_called_once_with(
|
mock_apprise_client.add.assert_called_once_with("signal://localhost:8989/1234567890/0987654321")
|
||||||
"http://localhost:8989/v2/send",
|
mock_apprise_client.notify.assert_called_once_with(title="", body="test")
|
||||||
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:
|
def test_signal_alert_no_phones(mocker: MockerFixture) -> None:
|
||||||
@@ -48,34 +39,3 @@ def test_signal_alert_no_phones(mocker: MockerFixture) -> None:
|
|||||||
signal_alert("test")
|
signal_alert("test")
|
||||||
|
|
||||||
mock_logger.info.assert_called_once_with("SIGNAL_ALERT_FROM_PHONE or SIGNAL_ALERT_TO_PHONE not set")
|
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)
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./direnv.nix
|
||||||
|
./git.nix
|
||||||
|
./zsh.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.starship.enable = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
programs.direnv = {
|
||||||
|
enable = true;
|
||||||
|
enableZshIntegration = true;
|
||||||
|
nix-direnv.enable = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{ 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{ 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
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
programs.ssh = {
|
||||||
|
enable = true;
|
||||||
|
enableDefaultConfig = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../home/global.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -3,17 +3,54 @@
|
|||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
# cli
|
# cli
|
||||||
bat
|
bat
|
||||||
|
btop
|
||||||
|
eza
|
||||||
|
fd
|
||||||
|
ffmpegthumbnailer
|
||||||
fzf
|
fzf
|
||||||
git
|
git
|
||||||
|
gnupg
|
||||||
|
imagemagick
|
||||||
jq
|
jq
|
||||||
ncdu
|
ncdu
|
||||||
fastfetch
|
ouch
|
||||||
p7zip
|
p7zip
|
||||||
|
poppler
|
||||||
rar
|
rar
|
||||||
|
ripgrep
|
||||||
|
starship
|
||||||
|
tmux
|
||||||
unzip
|
unzip
|
||||||
|
yazi
|
||||||
zoxide
|
zoxide
|
||||||
|
# system info
|
||||||
|
hwloc
|
||||||
|
lynis
|
||||||
|
pciutils
|
||||||
|
smartmontools
|
||||||
|
usbutils
|
||||||
|
# networking
|
||||||
|
iperf3
|
||||||
|
nmap
|
||||||
|
wget
|
||||||
# python
|
# python
|
||||||
ruff
|
ruff
|
||||||
uv
|
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
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{ pkgs, ... }:
|
{ inputs, pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./comms.nix
|
"${inputs.self}/users/shared/comms.nix"
|
||||||
./games.nix
|
"${inputs.self}/users/shared/games.nix"
|
||||||
|
"${inputs.self}/users/shared/sweet.nix"
|
||||||
|
./firefox
|
||||||
./kitty.nix
|
./kitty.nix
|
||||||
./llm_tools.nix
|
./llm_tools.nix
|
||||||
./sweet.nix
|
|
||||||
./t3_code
|
./t3_code
|
||||||
./vscode
|
./vscode
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
{ 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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 957 B |
Binary file not shown.
|
After Width: | Height: | Size: 924 B |
@@ -0,0 +1,99 @@
|
|||||||
|
{ 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" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,14 +3,21 @@
|
|||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
# cli
|
# cli
|
||||||
bat
|
bat
|
||||||
|
fd
|
||||||
|
ffmpegthumbnailer
|
||||||
fzf
|
fzf
|
||||||
git
|
git
|
||||||
|
gnupg
|
||||||
|
imagemagick
|
||||||
jq
|
jq
|
||||||
ncdu
|
ncdu
|
||||||
fastfetch
|
fastfetch
|
||||||
|
ouch
|
||||||
p7zip
|
p7zip
|
||||||
|
poppler
|
||||||
rar
|
rar
|
||||||
unzip
|
unzip
|
||||||
|
yazi
|
||||||
zoxide
|
zoxide
|
||||||
# Home Assistant
|
# Home Assistant
|
||||||
esphome
|
esphome
|
||||||
@@ -28,6 +35,8 @@
|
|||||||
# python
|
# python
|
||||||
ruff
|
ruff
|
||||||
uv
|
uv
|
||||||
|
# nodejs
|
||||||
|
nodejs
|
||||||
# Rust packages
|
# Rust packages
|
||||||
bacon
|
bacon
|
||||||
cargo
|
cargo
|
||||||
@@ -42,6 +51,9 @@
|
|||||||
rustfmt
|
rustfmt
|
||||||
trunk
|
trunk
|
||||||
wasm-pack
|
wasm-pack
|
||||||
|
# cpp
|
||||||
|
clang-tools
|
||||||
|
clang_20
|
||||||
# nix
|
# nix
|
||||||
nix-init
|
nix-init
|
||||||
nix-output-monitor
|
nix-output-monitor
|
||||||
@@ -49,5 +61,6 @@
|
|||||||
nix-tree
|
nix-tree
|
||||||
nixfmt
|
nixfmt
|
||||||
treefmt
|
treefmt
|
||||||
|
codebase-memory-mcp
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./direnv.nix
|
||||||
|
./git.nix
|
||||||
|
./zsh.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.starship.enable = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
programs.direnv = {
|
||||||
|
enable = true;
|
||||||
|
enableZshIntegration = true;
|
||||||
|
nix-direnv.enable = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
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";
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{ 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{ 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
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
programs.ssh = {
|
||||||
|
enable = true;
|
||||||
|
enableDefaultConfig = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../home/global.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../home/global.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user