11 Commits
Author SHA1 Message Date
Joakim Repomaa b4caf86628 setup podman
Build Images / build (push) Failing after 11s
Check / check (push) Failing after 1m13s
2026-03-07 13:30:23 +02:00
Joakim Repomaa 80263ea4db fix gitea image registry 2026-03-07 13:28:44 +02:00
Joakim Repomaa 3a93db3839 remove home assistant 2026-03-07 13:28:04 +02:00
Joakim Repomaa 3de5679411 fix gitea runner 2026-03-07 13:28:04 +02:00
Joakim Repomaa 551fe64603 fix invidious 2026-03-07 13:28:03 +02:00
Joakim Repomaa fd29342f58 allow x86 emulation on freun-dev 2026-03-07 13:28:03 +02:00
Joakim Repomaa 43848028af fix synthing 2026-03-07 13:28:03 +02:00
Joakim Repomaa fb6ebd55c3 update flake inputs 2026-03-07 13:28:03 +02:00
Joakim Repomaa 1ddbce110f rekey secrets 2026-03-07 13:28:03 +02:00
Joakim Repomaa bc85dabce0 apu local dhcp lease dns and invidious 2026-03-07 13:28:03 +02:00
Joakim Repomaa 4745fa3d58 setup niri 2026-03-07 13:28:03 +02:00
79 changed files with 1436 additions and 2661 deletions
-1
View File
@@ -2,4 +2,3 @@
/.direnv
/.gcroots
*.qcow2
hosts/turny/ssh_host_ed25519_key
+1 -1
View File
@@ -8,7 +8,7 @@
- `nix build .#nixosConfigurations.<host>.config.system.build.toplevel` - Build specific host
## Code Style
- **Format**: Use `nixfmt` for all .nix files
- **Format**: Use `nixfmt-rfc-style` for all .nix files
- **Imports**: List imports first, group by: local modules, external inputs (inputs.*)
- **Function args**: Multi-line with trailing comma when >3 args: `{ config, lib, pkgs, ... }:`
- **Options**: Define in `options.<service>` block, implement in `config = lib.mkIf cfg.enable { ... }`
+1
View File
@@ -0,0 +1 @@
{ util, ... }: util.package { }
+14
View File
@@ -0,0 +1,14 @@
{ pkgs, ... }:
let
inherit (pkgs) crystal libffi;
in
crystal.overrideAttrs (oldAttrs: {
makeFlags = oldAttrs.makeFlags ++ [
"interpreter=1"
];
buildInputs = oldAttrs.buildInputs ++ [
libffi
];
FLAGS = [ "--single-module" ];
doCheck = false;
})
-6
View File
@@ -1,6 +0,0 @@
{
util,
lib,
...
}:
util.package { inherit lib; }
-80
View File
@@ -1,80 +0,0 @@
{
pkgs,
lib,
}:
let
version = "4.9.9";
src = pkgs.fetchFromGitHub {
owner = "kreuzberg-dev";
repo = "kreuzberg";
rev = "v${version}";
hash = "sha256-eu0qjyxoRiDLHsBLPPPAqb/DKVaPzqgP/4hHPjgOBoM=";
};
in
pkgs.rustPlatform.buildRustPackage {
pname = "kreuzberg-cli";
inherit version src;
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
inherit src;
hash = "sha256-j1tmjQ7KQQAMz3QxhQGTGracMqqdcaX/+74lpUfU/kE=";
};
buildFeatures = [ "all" ];
doCheck = false;
nativeBuildInputs = [
pkgs.cmake
pkgs.makeWrapper
pkgs.pkg-config
];
buildInputs = [
pkgs.pdfium-binaries
pkgs.openssl
pkgs.onnxruntime
pkgs.tesseract
pkgs.leptonica
];
dontUseCmakeConfigure = true;
env = {
KREUZBERG_PDFIUM_PREBUILT = pkgs.pdfium-binaries;
ORT_LIB_PATH = "${pkgs.onnxruntime}/lib";
ORT_PREFER_DYNAMIC_LINK = "1";
};
postPatch = ''
substituteInPlace crates/kreuzberg/Cargo.toml \
--replace 'kreuzberg-tesseract = { path = "../kreuzberg-tesseract", version = "4.9.9", optional = true }' \
'kreuzberg-tesseract = { path = "../kreuzberg-tesseract", version = "4.9.9", optional = true, default-features = false, features = ["dynamic-linking"] }'
for f in crates/kreuzberg-tesseract/src/*.rs; do
substituteInPlace "$f" \
--replace 'any(feature = "build-tesseract", feature = "build-tesseract-wasm")' \
'any(feature = "build-tesseract", feature = "build-tesseract-wasm", feature = "dynamic-linking")'
done
'';
postFixup = ''
wrapProgram $out/bin/kreuzberg \
--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath [
pkgs.tesseract
pkgs.onnxruntime
]
} \
--set TESSDATA_PREFIX ${pkgs.tesseract}/share
ln -s kreuzberg $out/bin/kreuzberg-cli
'';
meta = {
description = "High-performance document intelligence CLI with a Rust core";
homepage = "https://github.com/kreuzberg-dev/kreuzberg";
license = lib.licenses.unfree;
platforms = [ "x86_64-linux" ];
mainProgram = "kreuzberg";
};
}
-6
View File
@@ -1,6 +0,0 @@
{
util,
lib,
...
}:
util.package { inherit lib; }
-160
View File
@@ -1,160 +0,0 @@
{
lib,
pkgs,
}:
let
stylesheet = pkgs.writeText "md2pdf-style.css" ''
body {
font-family: "Noto Sans", sans-serif;
font-size: 11px;
max-width: 100%;
margin: 0;
padding: 15px;
line-height: 1.4;
}
h1 {
font-size: 22px;
border-bottom: 2px solid #333;
padding-bottom: 5px;
break-after: avoid;
}
h2 {
font-size: 18px;
margin-top: 25px;
border-bottom: 1px solid #ccc;
padding-bottom: 3px;
break-after: avoid;
}
h3 {
font-size: 14px;
margin-top: 15px;
break-after: avoid;
}
table {
border-collapse: collapse;
width: 100%;
font-size: 10px;
margin: 10px 0;
}
th {
background: #f0f0f0;
border: 1px solid #ccc;
padding: 4px 6px;
text-align: left;
break-after: avoid;
break-inside: avoid;
}
td {
border: 1px solid #ccc;
padding: 4px 6px;
vertical-align: top;
break-inside: avoid;
}
tr {
break-inside: avoid;
break-after: auto;
}
code {
background: #f5f5f5;
padding: 1px 3px;
border-radius: 2px;
font-family: "Noto Sans Mono", monospace;
font-size: 10px;
}
pre {
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 10px;
}
pre code {
background: none;
padding: 0;
}
blockquote {
border-left: 3px solid #ccc;
margin-left: 0;
padding-left: 10px;
color: #555;
}
img {
max-width: 100%;
height: auto;
border: 1px solid #ddd;
margin: 5px 0;
break-inside: avoid;
}
em {
color: #666;
}
a {
color: #0066cc;
}
'';
md-to-pdf = pkgs.buildNpmPackage {
pname = "md-to-pdf";
version = "5.2.5";
src = pkgs.fetchFromGitHub {
owner = "simonhaenisch";
repo = "md-to-pdf";
rev = "v5.2.5";
hash = "sha256-6Zr4jidQ6cX2t6jKFqEnCSqvRtw4gZkqDKdv9SIyDC4=";
};
npmDepsHash = "sha256-AGn8g/nbkvWi4o6ENCnt7VexjfBIoDz+tl9eYvy+bao=";
# canvas is an optional native dependency that fails to build in nix;
# md-to-pdf works fine without it (puppeteer is used for PDF generation)
npmFlags = [ "--ignore-scripts" ];
postInstall = ''
rm -rf $out/lib/node_modules/md-to-pdf/node_modules/canvas
'';
};
in
pkgs.stdenv.mkDerivation {
pname = "md2pdf";
version = "1.0.0";
src = ./.;
dontBuild = true;
nativeBuildInputs = [ ];
installPhase = ''
runHook preInstall
mkdir -p $out/bin $out/share/md2pdf
cp ${stylesheet} $out/share/md2pdf/style.css
cat > $out/bin/md2pdf << 'WRAPPER'
#!/bin/sh
export PUPPETEER_EXECUTABLE_PATH="@chromium@/bin/chromium"
export PATH="@chromium@/bin''${PATH:+:''${PATH}}"
exec "@md-to-pdf@/bin/md-to-pdf" \
--stylesheet "@stylesheet@" \
--pdf-options '{"format":"A4","margin":{"top":"10mm","bottom":"10mm","left":"12mm","right":"12mm"},"printBackground":true}' \
"$@"
WRAPPER
substituteInPlace $out/bin/md2pdf \
--replace-fail "@chromium@" "${pkgs.chromium}" \
--replace-fail "@md-to-pdf@" "${md-to-pdf}" \
--replace-fail "@stylesheet@" "$out/share/md2pdf/style.css"
chmod +x $out/bin/md2pdf
runHook postInstall
'';
meta = {
description = "Convert Markdown to PDF with Noto Sans styling and tight margins";
mainProgram = "md2pdf";
platforms = lib.platforms.linux;
};
}
Generated
+132 -368
View File
@@ -23,64 +23,6 @@
"type": "github"
}
},
"blueprint": {
"inputs": {
"nixpkgs": [
"llm-agents",
"nixpkgs"
],
"systems": [
"llm-agents",
"systems"
]
},
"locked": {
"lastModified": 1776249299,
"narHash": "sha256-Dt9t1TGRmJFc0xVYhttNBD6QsAgHOHCArqGa0AyjrJY=",
"owner": "numtide",
"repo": "blueprint",
"rev": "56131e8628f173d24a27f6d27c0215eff57e40dd",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "blueprint",
"type": "github"
}
},
"bun2nix": {
"inputs": {
"flake-parts": [
"llm-agents",
"flake-parts"
],
"nixpkgs": [
"llm-agents",
"nixpkgs"
],
"systems": [
"llm-agents",
"systems"
],
"treefmt-nix": [
"llm-agents",
"treefmt-nix"
]
},
"locked": {
"lastModified": 1778446047,
"narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=",
"owner": "nix-community",
"repo": "bun2nix",
"rev": "f2bc12af1a6369648aac41041ceeaa0b866599c6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "bun2nix",
"type": "github"
}
},
"colmena": {
"inputs": {
"flake-compat": "flake-compat",
@@ -92,11 +34,11 @@
"stable": "stable"
},
"locked": {
"lastModified": 1781446832,
"narHash": "sha256-/6+NHQstxRqkbNjtNcQgCojzu59SEbdwwpWqBw7Fm9s=",
"lastModified": 1762034856,
"narHash": "sha256-QVey3iP3UEoiFVXgypyjTvCrsIlA4ecx6Acaz5C8/PQ=",
"owner": "zhaofengli",
"repo": "colmena",
"rev": "938cb5cdd9b25d9774b6168e5f03d0afd04d321f",
"rev": "349b035a5027f23d88eeb3bc41085d7ee29f18ed",
"type": "github"
},
"original": {
@@ -107,11 +49,11 @@
},
"crane": {
"locked": {
"lastModified": 1781825982,
"narHash": "sha256-SlXKwIRIhrOSAcTjCB3ftPLzJWZStQIPS7J1FlZPnKk=",
"lastModified": 1771796463,
"narHash": "sha256-9bCDuUzpwJXcHMQYMS1yNuzYMmKO/CCwCexpjWOl62I=",
"owner": "ipetkov",
"repo": "crane",
"rev": "469fd08d0bcf6926321fa973c6777fbc87785dd7",
"rev": "3d3de3313e263e04894f284ac18177bd26169bad",
"type": "github"
},
"original": {
@@ -145,11 +87,11 @@
"dnote": {
"flake": false,
"locked": {
"lastModified": 1774496111,
"narHash": "sha256-F7sP/QtZhfoNJX7LAZRmL/MjRByzJFNPUQBNpifSimQ=",
"lastModified": 1772691105,
"narHash": "sha256-RC18Gi3/dagBitZIRIuPwIokk6pwwv+ZpawLTXSJ18c=",
"owner": "dnote",
"repo": "dnote",
"rev": "1610b41e18a814b7aa7f4f784ba283794150853f",
"rev": "f34a96abbe47e8b516ea7cac2bdec06c64c01493",
"type": "github"
},
"original": {
@@ -195,11 +137,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github"
},
"original": {
@@ -244,27 +186,6 @@
"type": "github"
}
},
"flake-parts_4": {
"inputs": {
"nixpkgs-lib": [
"llm-agents",
"nixpkgs"
]
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1659877975,
@@ -335,6 +256,24 @@
}
},
"flake-utils_5": {
"inputs": {
"systems": "systems_5"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"inputs": {
"systems": "systems_6"
},
@@ -352,6 +291,24 @@
"type": "github"
}
},
"flake-utils_7": {
"inputs": {
"systems": "systems_7"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"frontend": {
"inputs": {
"flake-utils": "flake-utils_2",
@@ -465,11 +422,11 @@
]
},
"locked": {
"lastModified": 1779506708,
"narHash": "sha256-QOD/CNm196nCJRheux/URi4/HE66fthdOMqCJoPP1Y0=",
"lastModified": 1772633058,
"narHash": "sha256-SO7JapRy2HPhgmqiLbfnW1kMx5rakPMKZ9z3wtRLQjI=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3ee51fbdac8c8bdfe1e7e1fcaba6520a563f394f",
"rev": "080657a04188aca25f8a6c70a0fb2ea7e37f1865",
"type": "github"
},
"original": {
@@ -519,18 +476,16 @@
"lanzaboote": {
"inputs": {
"crane": "crane",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs": "nixpkgs_3",
"pre-commit": "pre-commit",
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1782160136,
"narHash": "sha256-APwmLI4UuSTim0JO/kZ6FXDPiZVp3zIj0uGDF2UZ5kU=",
"lastModified": 1772216104,
"narHash": "sha256-1TnGN26vnCEQk5m4AavJZxGZTb/6aZyphemRPRwFUfs=",
"owner": "nix-community",
"repo": "lanzaboote",
"rev": "6650fb7c6b48177c8200e21ffac99a4adc72b08d",
"rev": "dbe5112de965bbbbff9f0729a9789c20a65ab047",
"type": "github"
},
"original": {
@@ -539,121 +494,6 @@
"type": "github"
}
},
"llm-agents": {
"inputs": {
"blueprint": "blueprint",
"bun2nix": "bun2nix",
"flake-parts": "flake-parts_4",
"nixpkgs": [
"nixpkgs-unstable"
],
"systems": "systems_5",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1782366663,
"narHash": "sha256-hcxEl/eWQQaYkUqygP4ca8S7WlX9c6vqgvQCqtV3vuw=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "5f9d86061f538b3f5f3568583c001fb9c0705b71",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "llm-agents.nix",
"type": "github"
}
},
"models-dev": {
"flake": false,
"locked": {
"lastModified": 1782405514,
"narHash": "sha256-E25yt4q3YruuxZSepv2hXdNX4FyGk60LnbxlcthJL18=",
"owner": "anomalyco",
"repo": "models.dev",
"rev": "7ceac334bbacc5ece7fbaea510ac5d7b850cfdf0",
"type": "github"
},
"original": {
"owner": "anomalyco",
"repo": "models.dev",
"type": "github"
}
},
"niri-flake": {
"inputs": {
"niri-stable": "niri-stable",
"niri-unstable": "niri-unstable",
"nixpkgs": [
"nixpkgs-unstable"
],
"nixpkgs-stable": "nixpkgs-stable",
"xwayland-satellite-stable": "xwayland-satellite-stable",
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
},
"locked": {
"lastModified": 1782333733,
"narHash": "sha256-QYrNYMNPKErRgNlxWwk0cjNKftnq6WzSs84pcZnUskM=",
"owner": "sodiboo",
"repo": "niri-flake",
"rev": "a6351044a3d69877d1c23be54971d18f8531c930",
"type": "github"
},
"original": {
"owner": "sodiboo",
"repo": "niri-flake",
"type": "github"
}
},
"niri-stable": {
"flake": false,
"locked": {
"lastModified": 1756556321,
"narHash": "sha256-RLD89dfjN0RVO86C/Mot0T7aduCygPGaYbog566F0Qo=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "01be0e65f4eb91a9cd624ac0b76aaeab765c7294",
"type": "github"
},
"original": {
"owner": "YaLTeR",
"ref": "v25.08",
"repo": "niri",
"type": "github"
}
},
"niri-unstable": {
"flake": false,
"locked": {
"lastModified": 1781781064,
"narHash": "sha256-Ii/koEm/sRyg65qbAQWqEgboSEIhdH0EL4KglAc14p0=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "49fc6117fd6c043adaa2ead316b82db5ed735d36",
"type": "github"
},
"original": {
"owner": "YaLTeR",
"repo": "niri",
"type": "github"
}
},
"nix-flatpak": {
"locked": {
"lastModified": 1767983141,
"narHash": "sha256-7ZCulYUD9RmJIDULTRkGLSW1faMpDlPKcbWJLYHoXcs=",
"owner": "gmodena",
"repo": "nix-flatpak",
"rev": "440818969ac2cbd77bfe025e884d0aa528991374",
"type": "github"
},
"original": {
"owner": "gmodena",
"ref": "latest",
"repo": "nix-flatpak",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
@@ -676,17 +516,12 @@
}
},
"nixos-hardware": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1782379505,
"narHash": "sha256-zPvPiU+a7pqtH47xrtZLNRABJKpOjfZQclDbcvNtH+I=",
"lastModified": 1771969195,
"narHash": "sha256-qwcDBtrRvJbrrnv1lf/pREQi8t2hWZxVAyeMo7/E9sw=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "603d3afd1b6145bd66e97ae38a34d91c95df70cf",
"rev": "41c6b421bdc301b2624486e11905c9af7b8ec68e",
"type": "github"
},
"original": {
@@ -712,29 +547,13 @@
"type": "github"
}
},
"nixpkgs-25_11": {
"locked": {
"lastModified": 1782847189,
"narHash": "sha256-twXPFqFsrrY5r28Zh7Homgcp2gUMBgQ6WDS98Q/3xFI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b6018f87da91d19d0ab4cf979885689b469cdd41",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"lastModified": 1772328832,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
"type": "github"
},
"original": {
@@ -767,29 +586,13 @@
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1782188297,
"narHash": "sha256-imdcA5fgbHK259bhHlyiiSr7bMY1AZ6onOWDdAcydc4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9a1a7dbb18f0eb31c49b031babb8def7eab0af54",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1781577229,
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
"lastModified": 1772624091,
"narHash": "sha256-QKyJ0QGWBn6r0invrMAK8dmJoBYWoOWy7lN+UHzW1jc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
"rev": "80bdc1e5ce51f56b19791b52b2901187931f5353",
"type": "github"
},
"original": {
@@ -817,48 +620,33 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1782233679,
"narHash": "sha256-QyuGP5+QOtmXpy4i2X4DhBVBaySBdDKQEhqKcphcp34=",
"lastModified": 1771848320,
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "667d5cf1c59585031d743c78b394b0a647537c35",
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-26.05",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1781577229,
"narHash": "sha256-rcUHdUtJEvMdNEl2Wq+YpHraHKfcer3KsBscpZEF2Yg=",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1017464.567a49d1913c/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
}
},
"noctalia": {
"inputs": {
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1782405004,
"narHash": "sha256-7uAqPz5SmLBoIETAvZPeHu1e22suVEEcNWIZKoen2/8=",
"owner": "noctalia-dev",
"repo": "noctalia",
"rev": "6779a947a14738bf81c0d66ee083acff105f66a2",
"lastModified": 1772598333,
"narHash": "sha256-YaHht/C35INEX3DeJQNWjNaTcPjYmBwwjFJ2jdtr+5U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fabb8c9deee281e50b1065002c9828f2cf7b2239",
"type": "github"
},
"original": {
"owner": "noctalia-dev",
"repo": "noctalia",
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
@@ -891,11 +679,11 @@
]
},
"locked": {
"lastModified": 1781733627,
"narHash": "sha256-U3yTuGBnmXvXoQI3qkpfEDsn9RovQPAjN7ndRco+3u0=",
"lastModified": 1771858127,
"narHash": "sha256-Gtre9YoYl3n25tJH2AoSdjuwcqij5CPxL3U3xysYD08=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "3bbec39bc90eadfa031e6f3b77272f3f60803e39",
"rev": "49bbbfc218bf3856dfa631cead3b052d78248b83",
"type": "github"
},
"original": {
@@ -916,23 +704,39 @@
"ketchup": "ketchup",
"ksoloti-pr": "ksoloti-pr",
"lanzaboote": "lanzaboote",
"llm-agents": "llm-agents",
"models-dev": "models-dev",
"niri-flake": "niri-flake",
"nix-flatpak": "nix-flatpak",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_3",
"nixpkgs-25_11": "nixpkgs-25_11",
"nixpkgs": "nixpkgs_4",
"nixpkgs-unstable": "nixpkgs-unstable",
"noctalia": "noctalia",
"router-dash": "router-dash",
"syntax-renderer": "syntax-renderer",
"tree_hugger": "tree_hugger",
"tonearm": "tonearm",
"turny": "turny",
"voidauth": "voidauth",
"voxtype": "voxtype",
"workout-sync": "workout-sync"
}
},
"router-dash": {
"inputs": {
"flake-utils": "flake-utils_5",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1771795019,
"narHash": "sha256-mcGQMNKVUj/CSGXeejzqHEeU+O0ThzKb4pc7pLjUWN0=",
"ref": "refs/heads/main",
"rev": "ea315d3a527db870a51d23901037a5d1b37f5780",
"revCount": 7,
"type": "git",
"url": "https://git.freun.dev/repomaa/router-dash"
},
"original": {
"type": "git",
"url": "https://git.freun.dev/repomaa/router-dash"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
@@ -941,11 +745,11 @@
]
},
"locked": {
"lastModified": 1782012058,
"narHash": "sha256-9mWUnReOUXfKjZuJAL/bAFH3LUyECTRtXgSNVjRw3UY=",
"lastModified": 1771988922,
"narHash": "sha256-Fc6FHXtfEkLtuVJzd0B6tFYMhmcPLuxr90rWfb/2jtQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "8534567325bd8a8d2928e6afd81e0a87d19efd3c",
"rev": "f4443dc3f0b6c5e6b77d923156943ce816d1fcb9",
"type": "github"
},
"original": {
@@ -1076,45 +880,38 @@
"type": "github"
}
},
"tree_hugger": {
"inputs": {
"nixpkgs": [
"nixpkgs-unstable"
]
},
"systems_7": {
"locked": {
"lastModified": 1774531531,
"narHash": "sha256-v4zPHfGQw79uyOUeqWAlOE0A7ZjQVYhpfyibnsUe1k4=",
"ref": "refs/heads/main",
"rev": "af16fcdcfcfc17f2be03879b7d8e858976936619",
"revCount": 67,
"type": "git",
"url": "ssh://git@github.com/everii-Group/tree_hugger"
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"type": "git",
"url": "ssh://git@github.com/everii-Group/tree_hugger"
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"tonearm": {
"inputs": {
"flake-utils": "flake-utils_6",
"nixpkgs": [
"llm-agents",
"nixpkgs"
]
},
"locked": {
"lastModified": 1780220602,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github"
"lastModified": 1769118914,
"narHash": "sha256-ASM7MVDWxT+BBtcAJ1z8XNjyNdL5E1Kc0miCWIepf+o=",
"rev": "68ea2190d1b37f9030b7586d2030f9dc48915a85",
"type": "tarball",
"url": "https://codeberg.org/api/v1/repos/dergs/Tonearm/archive/68ea2190d1b37f9030b7586d2030f9dc48915a85.tar.gz"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
"type": "tarball",
"url": "https://codeberg.org/dergs/Tonearm/archive/v1.0.0.tar.gz"
}
},
"turny": {
@@ -1127,11 +924,11 @@
]
},
"locked": {
"lastModified": 1783250457,
"narHash": "sha256-J7hZuGi7+SJRFeSpWJzzQy9IBH/hU8KokPV5A3rr65c=",
"lastModified": 1752874301,
"narHash": "sha256-A6IZz46Lfopm5UhMtFfBDimepEUt9lGwhWoEIEQHsgk=",
"owner": "~repomaa",
"repo": "turny",
"rev": "4b711b8814c91bc053729c5903d1ee924b8ccb8d",
"rev": "133c05151e77616c7973c1c1038506b2fdee8eab",
"type": "sourcehut"
},
"original": {
@@ -1166,17 +963,17 @@
},
"voxtype": {
"inputs": {
"flake-utils": "flake-utils_5",
"flake-utils": "flake-utils_7",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1782311621,
"narHash": "sha256-K8mog5N+Qv01VyM5n74sOeXCn+2OYgrSZnVDAKQ33zc=",
"lastModified": 1772443545,
"narHash": "sha256-oD3lameQXilKcgxQORR2l0+iDbnCO61+mjYD3MEVbuQ=",
"owner": "peteonrails",
"repo": "voxtype",
"rev": "31b7f38c4e22c4ce75d6350945729e5db001cb9a",
"rev": "d011f3ff074a6a14c14e75fefb375a408e9e8887",
"type": "github"
},
"original": {
@@ -1204,39 +1001,6 @@
"repo": "workout-sync",
"type": "sourcehut"
}
},
"xwayland-satellite-stable": {
"flake": false,
"locked": {
"lastModified": 1755491097,
"narHash": "sha256-m+9tUfsmBeF2Gn4HWa6vSITZ4Gz1eA1F5Kh62B0N4oE=",
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"rev": "388d291e82ffbc73be18169d39470f340707edaa",
"type": "github"
},
"original": {
"owner": "Supreeeme",
"ref": "v0.7",
"repo": "xwayland-satellite",
"type": "github"
}
},
"xwayland-satellite-unstable": {
"flake": false,
"locked": {
"lastModified": 1781226823,
"narHash": "sha256-28696iIw8uE0ZUyFTtzhEM8xMh85clCYypMxkvUi+sc=",
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"rev": "8575d0ef55d70f9b4c46b6bffb3accf912217e1e",
"type": "github"
},
"original": {
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"type": "github"
}
}
},
"root": "root",
+8 -77
View File
@@ -2,20 +2,13 @@
inputs = {
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
nixpkgs-25_11.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
gtrackmap = {
url = "github:gtrackmap/gtrackmap";
inputs.nixpkgs.follows = "nixpkgs";
};
lanzaboote = {
url = "github:nix-community/lanzaboote";
inputs.nixpkgs.follows = "nixpkgs";
};
nixos-hardware = {
url = "github:NixOS/nixos-hardware/master";
inputs.nixpkgs.follows = "nixpkgs";
};
lanzaboote.url = "github:nix-community/lanzaboote";
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
inputs.nixpkgs.follows = "nixpkgs";
@@ -54,6 +47,10 @@
inputs.flake-parts.follows = "flake-parts";
inputs.nixpkgs.follows = "nixpkgs";
};
tonearm = {
url = "https://codeberg.org/dergs/Tonearm/archive/v1.0.0.tar.gz";
inputs.nixpkgs.follows = "nixpkgs";
};
voidauth = {
url = "github:repomaa/voidauth/feat/nix-packaging";
inputs.nixpkgs.follows = "nixpkgs-unstable";
@@ -63,24 +60,6 @@
url = "github:peteonrails/voxtype";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
tree_hugger = {
url = "git+ssh://git@github.com/everii-Group/tree_hugger";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
models-dev = {
url = "github:anomalyco/models.dev";
flake = false;
};
noctalia.url = "github:noctalia-dev/noctalia";
niri-flake = {
url = "github:sodiboo/niri-flake";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
nix-flatpak.url = "github:gmodena/nix-flatpak/?ref=latest";
llm-agents = {
url = "github:numtide/llm-agents.nix";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
};
outputs =
{
@@ -115,6 +94,7 @@
packages = with pkgs; [
agenix.packages.${system}.default
colmena.packages.${system}.colmena
colmena.packages.${system}.manual
(writeShellScriptBin "build" ''
colmena build --keep-result "$@"
'')
@@ -124,49 +104,6 @@
(writeShellScriptBin "apply-local" ''
nh os switch .
'')
(writeShellScriptBin "build-sd" ''
set -euo pipefail
echo "Building SD image for turny..."
nix build .#images.turny --system aarch64-linux --print-build-logs
STORE_IMG=$(readlink -f result/sd-image/*.img)
if [ -z "$STORE_IMG" ]; then
echo "Error: No .img file found in result/sd-image/"
exit 1
fi
KEY_DIR="$(git rev-parse --show-toplevel)/hosts/turny"
if [ ! -f "$KEY_DIR/ssh_host_ed25519_key" ]; then
echo "Error: SSH host key not found at $KEY_DIR/ssh_host_ed25519_key"
echo "Generate it with: ssh-keygen -t ed25519 -f hosts/turny/ssh_host_ed25519_key -N \"\" -C turny"
exit 1
fi
IMG="/tmp/turny-sd-image.img"
echo "Copying image to writable location: $IMG"
cp "$STORE_IMG" "$IMG"
echo "Injecting SSH host key into image..."
LOOP=$(sudo losetup --show -P -f "$IMG")
MNT=$(sudo mktemp -d)
cleanup() {
sudo umount "$MNT" 2>/dev/null || true
sudo rmdir "$MNT" 2>/dev/null || true
sudo losetup -d "$LOOP" 2>/dev/null || true
}
trap cleanup EXIT
sudo mount "''${LOOP}p2" "$MNT"
sudo mkdir -p "$MNT/etc/ssh"
sudo cp "$KEY_DIR/ssh_host_ed25519_key" "$MNT/etc/ssh/ssh_host_ed25519_key"
sudo cp "$KEY_DIR/ssh_host_ed25519_key.pub" "$MNT/etc/ssh/ssh_host_ed25519_key.pub"
sudo chmod 600 "$MNT/etc/ssh/ssh_host_ed25519_key"
sudo chmod 644 "$MNT/etc/ssh/ssh_host_ed25519_key.pub"
echo "Done! Image ready at: $IMG"
echo "Flash with: sudo dd if=$IMG of=/dev/sdX bs=4M conv=fsync status=progress"
'')
];
};
};
@@ -277,10 +214,4 @@
};
}
);
nixConfig = {
extra-substituters = [ "https://noctalia.cachix.org" ];
extra-trusted-public-keys = [
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
];
};
}
+157 -550
View File
@@ -1,5 +1,6 @@
{
config,
osConfig,
lib,
pkgs,
pkgs-unstable,
@@ -7,17 +8,6 @@
self,
...
}:
let
noctalia-package = inputs.noctalia.packages.${pkgs.stdenv.hostPlatform.system}.default;
ns = lib.getExe noctalia-package;
ns-ipc =
args:
[
ns
"msg"
]
++ args;
in
{
imports = [
../gnome
@@ -29,9 +19,6 @@ in
inputs.hastebin.nixosModules.hm
inputs.agenix.homeManagerModules.default
inputs.voxtype.homeManagerModules.default
inputs.noctalia.homeModules.default
inputs.niri-flake.homeModules.niri
inputs.nix-flatpak.homeManagerModules.nix-flatpak
];
# This value determines the Home Manager release that your configuration is
@@ -43,11 +30,7 @@ in
# release notes.
home.stateVersion = "23.11"; # Please read the comment before changing.
home.packages =
with pkgs;
[
atool
unzip
home.packages = with pkgs; [
htop
gnupg
pkgs-unstable.yubioath-flutter
@@ -69,7 +52,7 @@ in
gnumeric
gh
neovim-remote
gradia
pkgs-unstable.gradia
crystal
shards
moreutils
@@ -95,7 +78,7 @@ in
protonmail-bridge
hydroxide
imapsync
nixfmt
nixfmt-rfc-style
tree
virt-manager
pkgs-unstable.ncspot
@@ -110,9 +93,12 @@ in
xargs ${lib.getExe rbw} get "$@" | ${lib.getExe' wl-clipboard "wl-copy"}
'')
(google-fonts.override { fonts = [ "Tajawal" ]; })
pkgs-unstable.opencode
(writeShellScriptBin "nr" ''
${lib.getExe nix} run "nixpkgs#''${1}" "$@"
'')
pkgs-unstable.tidal-hifi
inputs.tonearm.packages.${pkgs.stdenv.hostPlatform.system}.tonearm
blueman
pavucontrol
(writeShellScriptBin "voxtoggle" ''
@@ -127,100 +113,87 @@ in
kill -SIGUSR1 "$pid"
fi
'')
(writeShellScriptBin "scrot" ''
set -eo pipefail
];
++ lib.optional osConfig.programs.niri.enable (
pkgs.writeShellScriptBin "handle-lid-close" ''
alias niri=${lib.getExe osConfig.programs.niri.package}
${lib.getExe grim} -t ppm -g "$(${lib.getExe slurp} -o -d -F monospace)" - | ${lib.getExe gradia}
'')
inputs.tree_hugger.packages.${stdenv.hostPlatform.system}.default
(writeShellScriptBin "ns" "${lib.join " " (ns-ipc [ ''"$@"'' ])}")
libnotify
supersonic-wayland
]
++ (with inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}; [
pi
crush
opencode
]);
output_count=$(niri outputs | ${lib.getExe jq} -r 'length')
if [ "$output_count" -eq 1 ]; then
niri msg action spawn hyprlock
systemctl suspend
fi
''
);
programs = {
noctalia = {
ashell = {
enable = true;
package = pkgs-unstable.ashell;
systemd = {
enable = true;
target = "graphical-session.target";
};
settings = {
bar.main = {
position = "top";
auto_hide = true;
reserve_space = false;
margin_edge = 5;
capsule = false;
start = [ "workspaces" ];
center = [ "clock" ];
end = [
"battery"
"notifications"
"network"
"bluetooth"
"control-center"
modules = {
left = [ "Workspaces" ];
center = [ "WindowTitle" ];
right = [
"CustomNotifications"
"SystemInfo"
[
"Clock"
"Privacy"
"Settings"
]
];
};
widget = {
workspaces = {
labels_only_when_occupied = true;
display = "none";
};
clock = {
format = "{:%H:%M}";
vertical_format = "{:%H\n%M}";
tooltip_format = "{:%A, %d. %B %Y}";
};
battery.warning_threshold = 20;
control-center.glyph = "snowflake";
};
audio.visualizerType = "linear";
wallpaper = {
directory = "~/Pictures/Wallpapers";
automation.enabled = true;
};
backdrop.enabled = true;
location.auto_locate = true;
control_center.shortcuts = [
{ type = "power_profile"; }
{ type = "caffeine"; }
{ type = "nightlight"; }
{ type = "dark_mode"; }
settings = {
lock_cmd = "hyprlock &";
logout_cmd = "niri msg action quit";
audio_sinks_more_cmd = "pavucontrol -t 3";
audio_sources_more_cmd = "pavucontrol -t 4";
bluetooth_more_cmd = "blueman-manager";
CustomButton =
let
isDark = lib.getExe (
pkgs.writeShellScriptBin "is-dark" ''
gsettings get org.gnome.desktop.interface color-scheme | grep -q dark
''
);
toggleDark = lib.getExe (
pkgs.writeShellScriptBin "toggle-dark" ''
if ${isDark}; then
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
else
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
fi
''
);
in
[
{
name = "Dark Mode";
icon = " ";
command = toggleDark;
status_command = isDark;
}
];
calendar = {
enabled = true;
account = {
mailbox = {
type = "caldav";
name = "Yhteinen Kalenteri";
provider = "custom";
server_url = "https://dav.mailbox.org/caldav/Y2FsOi8vMC82NA";
};
CustomModule = [
{
name = "CustomNotifications";
type = "Button";
icon = " ";
command = "swaync-client -t -sw";
listen_cmd = "swaync-client -swb";
icons."dnd.*" = " ";
alert = ".*notification";
}
];
};
};
nightlight.enabled = true;
idle.enabled = true;
shell = {
avatar_path = "~/.face";
};
theme = {
mode = "auto";
source = "wallpaper";
wallpaper_scheme = "m3-tonal-spot";
};
hooks.theme_mode_changed = "gsettings set org.gnome.desktop.interface color-scheme prefer-\${NOCTALIA_THEME_MODE}";
};
package = noctalia-package;
};
nh = {
enable = true;
flake = self;
@@ -425,11 +398,6 @@ in
gh pr checkout {{.Form.pr | quote}}
'';
}
{
key = "D";
context = "localBranches";
command = "th prune {{.SelectedLocalBranch.Name}}";
}
];
};
};
@@ -555,6 +523,37 @@ in
enable = true;
defaultEditor = true;
};
hyprlock = {
enable = true;
package = pkgs-unstable.hyprlock;
settings = {
general = {
hide_cursor = true;
ignore_empty_input = true;
};
background = {
monitor = "";
path = "screenshot";
blur_passes = 3;
};
input-field = {
size = "20%, 5%";
monitor = "";
dots_center = true;
fade_on_empty = false;
rounding = 15;
shadow_passes = 2;
outline_thickness = 2;
placeholder_text = "Password...";
fail_text = "$PAMFAIL";
dots_spacing = "0.3";
position = "0, -20";
halign = "center";
valign = "center";
};
};
};
voxtype = {
enable = true;
package = inputs.voxtype.packages.${pkgs.stdenv.hostPlatform.system}.vulkan;
@@ -577,23 +576,23 @@ in
prompts = [
{
title = "Clean up";
instructions = "First identify what language this is, then respond in that language. Clean up: remove filler words, fix grammar and punctuation. Preserve all nuances of the original dictation. Output ONLY the cleaned text";
instructions = "Clean up this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the cleaned text";
}
{
title = "Make a title";
instructions = "First identify what language this is, then respond in that language. Create a concise and descriptive title";
instructions = "Make a concise and descriptive title for this dictation. Output ONLY the title";
}
{
title = "Summarize";
instructions = "First identify what language this is, then respond in that language. Summarize in a few sentences";
instructions = "Summarize this dictation in a few sentences. Output ONLY the summary";
}
{
title = "Commit message";
instructions = "Write a concise and descriptive git commit message for this dictation in English";
instructions = "Write a concise and descriptive git commit message for this dictation. Output ONLY the commit message";
}
{
title = "Translate to English";
instructions = "Translate this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the translation";
instructions = "Translate this dictation to English. Remove filler words, fix grammar and punctuation. Output ONLY the translation";
}
];
};
@@ -754,7 +753,6 @@ in
MANPAGER = "sh -c 'col -bx | bat -l man -p'";
MANROFFOPT = "-c";
DO_NOT_TRACK = 1;
DOCKER_HOST = "unix://$XDG_RUNTIME_DIR/podman/podman.sock";
};
systemd.user.sessionVariables = lib.mapAttrs (_: v: toString v) config.home.sessionVariables;
@@ -771,20 +769,53 @@ in
};
services = {
swaync = {
enable = true;
package = pkgs-unstable.swaynotificationcenter;
settings = {
scripts = {
focus-window =
let
jq = lib.getExe pkgs.jq;
niri = lib.getExe osConfig.programs.niri.package;
script = pkgs.writeShellScriptBin "swaync-focus-window" ''
set -e
APP_NAME="''${SWAYNC_APP_NAME:-}"
DESKTOP_ENTRY="''${SWAYNC_DESKTOP_ENTRY:-}"
APP_ID=""
if [[ -n "$DESKTOP_ENTRY" ]]; then
APP_ID="$DESKTOP_ENTRY"
elif [[ -n "$APP_NAME" ]]; then
APP_ID=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/ //g')
fi
[[ -z "$APP_ID" ]] && exit
# Find window ID for this app in niri and focus it
${jq} -r --arg app_id "$APP_ID" '.[] | select(.app_id | ascii_downcase | contains($app_id)) | .id' \
<(${niri} msg --json windows 2>/dev/null) | head -n1 | while read -r WINDOW_ID; do
if [[ -n "$WINDOW_ID" && "$WINDOW_ID" != "null" ]]; then
${niri} msg action focus-window --id "$WINDOW_ID"
fi
done
'';
in
{
exec = lib.getExe script;
run-on = "action";
};
};
};
};
gpg-agent = with pkgs; {
enable = true;
enableSshSupport = true;
pinentry.package = pinentry-gnome3;
};
};
services.flatpak = {
update.onActivation = true;
packages = [
"dev.diegovsky.Riff"
"app.zen_browser.zen"
];
};
services.walker = {
@@ -844,10 +875,10 @@ in
xdg.configFile."opencode/opencode.jsonc".text = builtins.toJSON {
"$schema" = "https://opencode.ai/config.json";
model = "fireworks-ai/accounts/fireworks/models/glm-5p2";
small_model = "fireworks-ai/accounts/fireworks/models/minimax-m3";
model = "opencode-go/glm-5";
small_model = "opencode-go/kimi-k2.5";
agent = {
explore.model = "fireworks-ai/accounts/fireworks/models/minimax-m3";
explore.model = "opencode-go/kimi-k2.5";
};
theme = "system";
lsp = {
@@ -924,431 +955,7 @@ in
};
};
programs.niri =
let
bind =
{
modifiers,
key,
action,
args ? [ ],
}:
{
${lib.join "+" (modifiers ++ [ key ])}.action.${action} = args;
};
in
{
enable = true;
package = pkgs-unstable.niri;
settings = {
input = {
keyboard = {
xkb = {
layout = "us";
variant = "altgr-intl";
};
numlock = true;
};
touchpad = {
tap = true;
dwt = true;
dwtp = true;
natural-scroll = true;
};
focus-follows-mouse = {
enable = true;
max-scroll-amount = "10%";
};
};
outputs = {
"eDP-1" = {
scale = 1.5;
};
"DP-5" = {
scale = 1.0;
};
"DP-3" = {
scale = 1.2;
};
};
layout = {
gaps = 5;
center-focused-column = "never";
preset-column-widths = [
{ proportion = 1.0 / 3.0; }
{ proportion = 1.0 / 2.0; }
{ proportion = 2.0 / 3.0; }
];
default-column-width = {
proportion = 1.0 / 2.0;
};
focus-ring = {
enable = true;
width = 4;
active.color = "#7fc8ff";
inactive.color = "#505050";
};
border = {
enable = false;
width = 4;
active.color = "#ffc87f";
inactive.color = "#505050";
urgent.color = "#9b0000";
};
shadow = {
enable = true;
softness = 30;
spread = 5;
offset = {
x = 0;
y = 5;
};
color = "#0007";
};
};
spawn-at-startup = [
{ argv = [ ns ]; }
];
hotkey-overlay.skip-at-startup = true;
screenshot-path = "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png";
debug.honor-xdg-activation-with-invalid-serial = true;
layer-rules = [
{
matches = [ { namespace = "^noctalia-backdrop*"; } ];
place-within-backdrop = true;
}
];
window-rules = [
{
matches = [
{
app-id = "firefox$";
title = "^Picture-in-Picture$";
}
];
open-floating = true;
}
{
geometry-corner-radius = {
top-left = 14.;
top-right = 14.;
bottom-left = 14.;
bottom-right = 14.;
};
clip-to-geometry = true;
}
#{
# background-effect = {
# blur = true;
# xray = false;
# };
{
matches = [ { app-id = "dev.noctalia.Noctalia.Settings"; } ];
open-floating = true;
default-column-width.fixed = 1080;
default-window-height.fixed = 920;
}
];
binds = lib.foldl' (acc: x: acc // x) { } (
[
{
"Mod+Shift+Slash" = {
action.show-hotkey-overlay = [ ];
};
"Mod+Return" = {
hotkey-overlay.title = "Open a Terminal: kitty";
action.spawn = "kitty";
};
"Mod+Z" = {
hotkey-overlay.title = "Open a launcher: walker";
action.spawn = "walker";
};
"Mod+Space" = {
action.spawn = "voxtoggle";
};
"Mod+Alt+L" = {
hotkey-overlay.title = "Lock the Screen: hyprlock";
action.spawn = ns-ipc [
"session"
"lock"
];
};
"XF86AudioRaiseVolume" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"volume-up"
];
};
"XF86AudioLowerVolume" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"volume-down"
];
};
"XF86AudioMute" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"volume-mute"
];
};
"XF86AudioMicMute" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"mic-mute"
];
};
"XF86AudioPlay" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"media"
"toggle"
];
};
"XF86AudioPrev" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"media"
"previous"
];
};
"XF86AudioNext" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"media"
"next"
];
};
"XF86MonBrightnessUp" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"brightness-up"
];
};
"XF86MonBrightnessDown" = {
allow-when-locked = true;
action.spawn = ns-ipc [
"brightness-down"
];
};
"Mod+O" = {
repeat = false;
action.toggle-overview = [ ];
};
"Mod+Backspace" = {
repeat = false;
action.close-window = [ ];
};
"Mod+BracketLeft".action.consume-or-expel-window-left = [ ];
"Mod+BracketRight".action.consume-or-expel-window-right = [ ];
"Mod+Comma".action.consume-window-into-column = [ ];
"Mod+Period".action.expel-window-from-column = [ ];
"Mod+R".action.switch-preset-column-width = [ ];
"Mod+Ctrl+R".action.switch-preset-window-height = [ ];
"Mod+Shift+R".action.reset-window-height = [ ];
"Mod+F".action.maximize-column = [ ];
"Mod+Shift+F".action.fullscreen-window = [ ];
"Mod+M".action.maximize-window-to-edges = [ ];
"Mod+Ctrl+F".action.expand-column-to-available-width = [ ];
"Mod+C".action.center-column = [ ];
"Mod+Ctrl+C".action.center-visible-columns = [ ];
"Mod+Minus".action.set-column-width = "-10%";
"Mod+Equal".action.set-column-width = "+10%";
"Mod+Shift+Minus".action.set-window-height = "-10%";
"Mod+Shift+Equal".action.set-window-height = "+10%";
"Mod+V".action.toggle-window-floating = [ ];
"Mod+Shift+V".action.switch-focus-between-floating-and-tiling = [ ];
"Mod+W".action.toggle-column-tabbed-display = [ ];
"Print".action.spawn = "scrot";
"Mod+Escape" = {
allow-inhibiting = true;
action.toggle-keyboard-shortcuts-inhibit = [ ];
};
"Mod+Shift+E".action.quit = [ ];
"Ctrl+Alt+Delete".action.quit = [ ];
"Mod+Shift+P".action.power-off-monitors = [ ];
}
]
++ (map
(
{
dir,
keys,
}:
(lib.mergeAttrsList (
map
(
{ modifiers, action }: (lib.mergeAttrsList (map (key: bind { inherit modifiers key action; }) keys))
)
[
{
modifiers = [ "Mod" ];
action = "focus-column-${dir}";
}
{
modifiers = [
"Mod"
"Shift"
];
action = "move-column-${dir}";
}
{
modifiers = [
"Mod"
"Ctrl"
];
action = "focus-monitor-${dir}";
}
{
modifiers = [
"Mod"
"Shift"
"Ctrl"
];
action = "move-column-to-monitor-${dir}";
}
]
))
)
[
{
dir = "left";
keys = [
"Left"
"H"
];
}
{
dir = "right";
keys = [
"Right"
"L"
];
}
]
)
++ (map
(
{
dir,
keys,
}:
(lib.mergeAttrsList (
map
(
{ modifiers, action }: (lib.mergeAttrsList (map (key: bind { inherit modifiers key action; }) keys))
)
[
{
modifiers = [ "Mod" ];
action = "focus-window-or-workspace-${dir}";
}
{
modifiers = [
"Mod"
"Shift"
];
action = "move-window-${dir}-or-to-workspace-${dir}";
}
{
modifiers = [
"Mod"
"Ctrl"
];
action = "focus-monitor-${dir}";
}
{
modifiers = [
"Mod"
"Shift"
"Ctrl"
];
action = "move-column-to-monitor-${dir}";
}
]
))
)
[
{
dir = "up";
keys = [
"Up"
"K"
];
}
{
dir = "down";
keys = [
"Down"
"J"
];
}
]
)
++ [
{
"Mod+Home".action.focus-column-first = [ ];
"Mod+End".action.focus-column-last = [ ];
"Mod+Shift+Home".action.move-column-to-first = [ ];
"Mod+Shift+End".action.move-column-to-last = [ ];
"Mod+Page_Down".action.focus-workspace-down = [ ];
"Mod+Page_Up".action.focus-workspace-up = [ ];
"Mod+U".action.focus-workspace-down = [ ];
"Mod+I".action.focus-workspace-up = [ ];
"Mod+Ctrl+Page_Down".action.move-column-to-workspace-down = [ ];
"Mod+Ctrl+Page_Up".action.move-column-to-workspace-up = [ ];
"Mod+Ctrl+U".action.move-column-to-workspace-down = [ ];
"Mod+Ctrl+I".action.move-column-to-workspace-up = [ ];
"Mod+Shift+Page_Down".action.move-workspace-down = [ ];
"Mod+Shift+Page_Up".action.move-workspace-up = [ ];
"Mod+Shift+U".action.move-workspace-down = [ ];
"Mod+Shift+I".action.move-workspace-up = [ ];
"Mod+WheelScrollDown" = {
cooldown-ms = 150;
action.focus-workspace-down = [ ];
};
"Mod+WheelScrollUp" = {
cooldown-ms = 150;
action.focus-workspace-up = [ ];
};
"Mod+Ctrl+WheelScrollDown" = {
cooldown-ms = 150;
action.move-column-to-workspace-down = [ ];
};
"Mod+Ctrl+WheelScrollUp" = {
cooldown-ms = 150;
action.move-column-to-workspace-up = [ ];
};
"Mod+WheelScrollRight".action.focus-column-right = [ ];
"Mod+WheelScrollLeft".action.focus-column-left = [ ];
"Mod+Ctrl+WheelScrollRight".action.move-column-right = [ ];
"Mod+Ctrl+WheelScrollLeft".action.move-column-left = [ ];
"Mod+Shift+WheelScrollDown".action.focus-column-right = [ ];
"Mod+Shift+WheelScrollUp".action.focus-column-left = [ ];
"Mod+Ctrl+Shift+WheelScrollDown".action.move-column-right = [ ];
"Mod+Ctrl+Shift+WheelScrollUp".action.move-column-left = [ ];
}
]
++ (map (number: {
"Mod+${toString number}".action.focus-workspace = number;
"Mod+Shift+${toString number}".action.move-column-to-workspace = number;
}) (lib.range 1 9))
);
};
};
xdg.configFile."niri/config.kdl".source = ./dotfiles/niri.kdl;
gnome.automaticTimeZone = true;
gtk.enable = true;
+641
View File
@@ -0,0 +1,641 @@
// This config is in the KDL format: https://kdl.dev
// "/-" comments out the following node.
// Check the wiki for a full description of the configuration:
// https://niri-wm.github.io/niri/Configuration:-Introduction
// Input device configuration.
// Find the full list of options on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Input
input {
keyboard {
xkb {
// You can set rules, model, layout, variant and options.
// For more information, see xkeyboard-config(7).
layout "us"
variant "altgr-intl"
// For example:
// layout "us,ru"
// options "grp:win_space_toggle,compose:ralt,ctrl:nocaps"
// If this section is empty, niri will fetch xkb settings
// from org.freedesktop.locale1. You can control these using
// localectl set-x11-keymap.
}
// Enable numlock on startup, omitting this setting disables it.
numlock
}
// Next sections include libinput settings.
// Omitting settings disables them, or leaves them at their default values.
// All commented-out settings here are examples, not defaults.
touchpad {
// off
tap
dwt
dwtp
// drag false
// drag-lock
natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "two-finger"
// disabled-on-external-mouse
}
mouse {
// off
// natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "no-scroll"
}
trackpoint {
// off
// natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "on-button-down"
// scroll-button 273
// scroll-button-lock
// middle-emulation
}
// Uncomment this to make the mouse warp to the center of newly focused windows.
// warp-mouse-to-focus
// Focus windows and outputs automatically when moving the mouse into them.
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
focus-follows-mouse max-scroll-amount="10%"
}
// You can configure outputs by their name, which you can find
// by running `niri msg outputs` while inside a niri instance.
// The built-in laptop monitor is usually called "eDP-1".
// Find more information on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Outputs
// Remember to uncomment the node by removing "/-"!
output "eDP-1" {
// Uncomment this line to disable this output.
// off
// Resolution and, optionally, refresh rate of the output.
// The format is "<width>x<height>" or "<width>x<height>@<refresh rate>".
// If the refresh rate is omitted, niri will pick the highest refresh rate
// for the resolution.
// If the mode is omitted altogether or is invalid, niri will pick one automatically.
// Run `niri msg outputs` while inside a niri instance to list all outputs and their modes.
// mode "1920x1080@120.030"
// You can use integer or fractional scale, for example use 1.5 for 150% scale.
scale 1.5
// Transform allows to rotate the output counter-clockwise, valid values are:
// normal, 90, 180, 270, flipped, flipped-90, flipped-180 and flipped-270.
// transform "normal"
// Position of the output in the global coordinate space.
// This affects directional monitor actions like "focus-monitor-left", and cursor movement.
// The cursor can only move between directly adjacent outputs.
// Output scale and rotation has to be taken into account for positioning:
// outputs are sized in logical, or scaled, pixels.
// For example, a 3840×2160 output with scale 2.0 will have a logical size of 1920×1080,
// so to put another output directly adjacent to it on the right, set its x to 1920.
// If the position is unset or results in an overlap, the output is instead placed
// automatically.
// position x=1280 y=0
}
output "DP-5" {
scale 1
}
output "DP-3" {
scale 1.2
}
// Settings that influence how windows are positioned and sized.
// Find more information on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Layout
layout {
// Set gaps around windows in logical pixels.
gaps 5
// When to center a column when changing focus, options are:
// - "never", default behavior, focusing an off-screen column will keep at the left
// or right edge of the screen.
// - "always", the focused column will always be centered.
// - "on-overflow", focusing a column will center it if it doesn't fit
// together with the previously focused column.
center-focused-column "never"
// You can customize the widths that "switch-preset-column-width" (Mod+R) toggles between.
preset-column-widths {
// Proportion sets the width as a fraction of the output width, taking gaps into account.
// For example, you can perfectly fit four windows sized "proportion 0.25" on an output.
// The default preset widths are 1/3, 1/2 and 2/3 of the output.
proportion 0.33333
proportion 0.5
proportion 0.66667
// Fixed sets the width in logical pixels exactly.
// fixed 1920
}
// You can also customize the heights that "switch-preset-window-height" (Mod+Shift+R) toggles between.
// preset-window-heights { }
// You can change the default width of the new windows.
default-column-width { proportion 0.5; }
// If you leave the brackets empty, the windows themselves will decide their initial width.
// default-column-width {}
// By default focus ring and border are rendered as a solid background rectangle
// behind windows. That is, they will show up through semitransparent windows.
// This is because windows using client-side decorations can have an arbitrary shape.
//
// If you don't like that, you should uncomment `prefer-no-csd` below.
// Niri will draw focus ring and border *around* windows that agree to omit their
// client-side decorations.
//
// Alternatively, you can override it with a window rule called
// `draw-border-with-background`.
// You can change how the focus ring looks.
focus-ring {
// Uncomment this line to disable the focus ring.
// off
// How many logical pixels the ring extends out from the windows.
width 4
// Colors can be set in a variety of ways:
// - CSS named colors: "red"
// - RGB hex: "#rgb", "#rgba", "#rrggbb", "#rrggbbaa"
// - CSS-like notation: "rgb(255, 127, 0)", rgba(), hsl() and a few others.
// Color of the ring on the active monitor.
active-color "#7fc8ff"
// Color of the ring on inactive monitors.
//
// The focus ring only draws around the active window, so the only place
// where you can see its inactive-color is on other monitors.
inactive-color "#505050"
// You can also use gradients. They take precedence over solid colors.
// Gradients are rendered the same as CSS linear-gradient(angle, from, to).
// The angle is the same as in linear-gradient, and is optional,
// defaulting to 180 (top-to-bottom gradient).
// You can use any CSS linear-gradient tool on the web to set these up.
// Changing the color space is also supported, check the wiki for more info.
//
// active-gradient from="#80c8ff" to="#c7ff7f" angle=45
// You can also color the gradient relative to the entire view
// of the workspace, rather than relative to just the window itself.
// To do that, set relative-to="workspace-view".
//
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view"
}
// You can also add a border. It's similar to the focus ring, but always visible.
border {
// The settings are the same as for the focus ring.
// If you enable the border, you probably want to disable the focus ring.
off
width 4
active-color "#ffc87f"
inactive-color "#505050"
// Color of the border around windows that request your attention.
urgent-color "#9b0000"
// Gradients can use a few different interpolation color spaces.
// For example, this is a pastel rainbow gradient via in="oklch longer hue".
//
// active-gradient from="#e5989b" to="#ffb4a2" angle=45 relative-to="workspace-view" in="oklch longer hue"
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view"
}
// You can enable drop shadows for windows.
shadow {
// Uncomment the next line to enable shadows.
// on
// By default, the shadow draws only around its window, and not behind it.
// Uncomment this setting to make the shadow draw behind its window.
//
// Note that niri has no way of knowing about the CSD window corner
// radius. It has to assume that windows have square corners, leading to
// shadow artifacts inside the CSD rounded corners. This setting fixes
// those artifacts.
//
// However, instead you may want to set prefer-no-csd and/or
// geometry-corner-radius. Then, niri will know the corner radius and
// draw the shadow correctly, without having to draw it behind the
// window. These will also remove client-side shadows if the window
// draws any.
//
// draw-behind-window true
// You can change how shadows look. The values below are in logical
// pixels and match the CSS box-shadow properties.
// Softness controls the shadow blur radius.
softness 30
// Spread expands the shadow.
spread 5
// Offset moves the shadow relative to the window.
offset x=0 y=5
// You can also change the shadow color and opacity.
color "#0007"
}
// Struts shrink the area occupied by windows, similarly to layer-shell panels.
// You can think of them as a kind of outer gaps. They are set in logical pixels.
// Left and right struts will cause the next window to the side to always be visible.
// Top and bottom struts will simply add outer gaps in addition to the area occupied by
// layer-shell panels and regular gaps.
struts {
// left 64
// right 64
// top 64
// bottom 64
}
}
// Add lines like this to spawn processes at startup.
// Note that running niri as a session supports xdg-desktop-autostart,
// which may be more convenient to use.
// See the binds section below for more spawn examples.
//spawn-at-startup "systemctl start --user niri-session.target"
// To run a shell command (with variables, pipes, etc.), use spawn-sh-at-startup:
// spawn-sh-at-startup "qs -c ~/source/qs/MyAwesomeShell"
hotkey-overlay {
// Uncomment this line to disable the "Important Hotkeys" pop-up at startup.
skip-at-startup
}
// Uncomment this line to ask the clients to omit their client-side decorations if possible.
// If the client will specifically ask for CSD, the request will be honored.
// Additionally, clients will be informed that they are tiled, removing some client-side rounded corners.
// This option will also fix border/focus ring drawing behind some semitransparent windows.
// After enabling or disabling this, you need to restart the apps for this to take effect.
// prefer-no-csd
// You can change the path where screenshots are saved.
// A ~ at the front will be expanded to the home directory.
// The path is formatted with strftime(3) to give you the screenshot date and time.
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
// You can also set this to null to disable saving screenshots to disk.
// screenshot-path null
// Animation settings.
// The wiki explains how to configure individual animations:
// https://niri-wm.github.io/niri/Configuration:-Animations
animations {
// Uncomment to turn off all animations.
// off
// Slow down all animations by this factor. Values below 1 speed them up instead.
// slowdown 3.0
}
// Window rules let you adjust behavior for individual windows.
// Find more information on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Window-Rules
// Work around WezTerm's initial configure bug
// by setting an empty default-column-width.
window-rule {
// This regular expression is intentionally made as specific as possible,
// since this is the default config, and we want no false positives.
// You can get away with just app-id="wezterm" if you want.
match app-id=r#"^org\.wezfurlong\.wezterm$"#
default-column-width {}
}
// Open the Firefox picture-in-picture player as floating by default.
window-rule {
// This app-id regular expression will work for both:
// - host Firefox (app-id is "firefox")
// - Flatpak Firefox (app-id is "org.mozilla.firefox")
match app-id=r#"firefox$"# title="^Picture-in-Picture$"
open-floating true
}
// Example: block out two password managers from screen capture.
// (This example rule is commented out with a "/-" in front.)
/-window-rule {
match app-id=r#"^org\.keepassxc\.KeePassXC$"#
match app-id=r#"^org\.gnome\.World\.Secrets$"#
block-out-from "screen-capture"
// Use this instead if you want them visible on third-party screenshot tools.
// block-out-from "screencast"
}
// Example: enable rounded corners for all windows.
// (This example rule is commented out with a "/-" in front.)
window-rule {
geometry-corner-radius 14
clip-to-geometry true
}
binds {
// Keys consist of modifiers separated by + signs, followed by an XKB key name
// in the end. To find an XKB name for a particular key, you may use a program
// like wev.
//
// "Mod" is a special modifier equal to Super when running on a TTY, and to Alt
// when running as a winit window.
//
// Most actions that you can bind here can also be invoked programmatically with
// `niri msg action do-something`.
// Mod-Shift-/, which is usually the same as Mod-?,
// shows a list of important hotkeys.
Mod+Shift+Slash { show-hotkey-overlay; }
// Suggested binds for running programs: terminal, app launcher, screen locker.
Mod+Return hotkey-overlay-title="Open a Terminal: kitty" { spawn "kitty"; }
Mod+Z hotkey-overlay-title="Open a launcher: walker" { spawn "walker"; }
Mod+Space { spawn "voxtoggle"; }
Mod+Alt+L hotkey-overlay-title="Lock the Screen: hyprlock" { spawn "hyprlock"; }
// Use spawn-sh to run a shell command. Do this if you need pipes, multiple commands, etc.
// Note: the entire command goes as a single argument. It's passed verbatim to `sh -c`.
// For example, this is a standard bind to toggle the screen reader (orca).
// Super+Alt+S allow-when-locked=true hotkey-overlay-title=null { spawn-sh "pkill orca || exec orca"; }
// Example volume keys mappings for PipeWire & WirePlumber.
// The allow-when-locked=true property makes them work even when the session is locked.
// Using spawn-sh allows to pass multiple arguments together with the command.
// "-l 1.0" limits the volume to 100%.
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1+ -l 1.0"; }
XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1-"; }
XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; }
XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; }
// Example media keys mapping using playerctl.
// This will work with any MPRIS-enabled media player.
XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; }
XF86AudioStop allow-when-locked=true { spawn-sh "playerctl stop"; }
XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; }
XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; }
// Example brightness key mappings for brightnessctl.
// You can use regular spawn with multiple arguments too (to avoid going through "sh"),
// but you need to manually put each argument in separate "" quotes.
XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "+10%"; }
XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "10%-"; }
// Open/close the Overview: a zoomed-out view of workspaces and windows.
// You can also move the mouse into the top-left hot corner,
// or do a four-finger swipe up on a touchpad.
Mod+O repeat=false { toggle-overview; }
Mod+Backspace repeat=false { close-window; }
Mod+Left { focus-column-left; }
Mod+Down { focus-window-down; }
Mod+Up { focus-window-up; }
Mod+Right { focus-column-right; }
Mod+H { focus-column-left; }
Mod+J { focus-window-or-workspace-down; }
Mod+K { focus-window-or-workspace-up; }
Mod+L { focus-column-right; }
Mod+Shift+Left { move-column-left; }
Mod+Shift+Down { move-window-down; }
Mod+Shift+Up { move-window-up; }
Mod+Shift+Right { move-column-right; }
Mod+Shift+H { move-column-left; }
Mod+Shift+J { move-window-down-or-to-workspace-down; }
Mod+Shift+K { move-window-up-or-to-workspace-up; }
Mod+Shift+L { move-column-right; }
Mod+Home { focus-column-first; }
Mod+End { focus-column-last; }
Mod+Shift+Home { move-column-to-first; }
Mod+Shift+End { move-column-to-last; }
Mod+Ctrl+Left { focus-monitor-left; }
Mod+Ctrl+Down { focus-monitor-down; }
Mod+Ctrl+Up { focus-monitor-up; }
Mod+Ctrl+Right { focus-monitor-right; }
Mod+Ctrl+H { focus-monitor-left; }
Mod+Ctrl+J { focus-monitor-down; }
Mod+Ctrl+K { focus-monitor-up; }
Mod+Ctrl+L { focus-monitor-right; }
Mod+Shift+Ctrl+Left { move-column-to-monitor-left; }
Mod+Shift+Ctrl+Down { move-column-to-monitor-down; }
Mod+Shift+Ctrl+Up { move-column-to-monitor-up; }
Mod+Shift+Ctrl+Right { move-column-to-monitor-right; }
Mod+Shift+Ctrl+H { move-column-to-monitor-left; }
Mod+Shift+Ctrl+J { move-column-to-monitor-down; }
Mod+Shift+Ctrl+K { move-column-to-monitor-up; }
Mod+Shift+Ctrl+L { move-column-to-monitor-right; }
// Alternatively, there are commands to move just a single window:
// Mod+Shift+Ctrl+Left { move-window-to-monitor-left; }
// ...
// And you can also move a whole workspace to another monitor:
// Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; }
// ...
Mod+Page_Down { focus-workspace-down; }
Mod+Page_Up { focus-workspace-up; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Ctrl+Page_Down { move-column-to-workspace-down; }
Mod+Ctrl+Page_Up { move-column-to-workspace-up; }
Mod+Ctrl+U { move-column-to-workspace-down; }
Mod+Ctrl+I { move-column-to-workspace-up; }
// Alternatively, there are commands to move just a single window:
// Mod+Ctrl+Page_Down { move-window-to-workspace-down; }
// ...
Mod+Shift+Page_Down { move-workspace-down; }
Mod+Shift+Page_Up { move-workspace-up; }
Mod+Shift+U { move-workspace-down; }
Mod+Shift+I { move-workspace-up; }
// You can bind mouse wheel scroll ticks using the following syntax.
// These binds will change direction based on the natural-scroll setting.
//
// To avoid scrolling through workspaces really fast, you can use
// the cooldown-ms property. The bind will be rate-limited to this value.
// You can set a cooldown on any bind, but it's most useful for the wheel.
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; }
Mod+Ctrl+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; }
Mod+Ctrl+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; }
Mod+WheelScrollRight { focus-column-right; }
Mod+WheelScrollLeft { focus-column-left; }
Mod+Ctrl+WheelScrollRight { move-column-right; }
Mod+Ctrl+WheelScrollLeft { move-column-left; }
// Usually scrolling up and down with Shift in applications results in
// horizontal scrolling; these binds replicate that.
Mod+Shift+WheelScrollDown { focus-column-right; }
Mod+Shift+WheelScrollUp { focus-column-left; }
Mod+Ctrl+Shift+WheelScrollDown { move-column-right; }
Mod+Ctrl+Shift+WheelScrollUp { move-column-left; }
// Similarly, you can bind touchpad scroll "ticks".
// Touchpad scrolling is continuous, so for these binds it is split into
// discrete intervals.
// These binds are also affected by touchpad's natural-scroll, so these
// example binds are "inverted", since we have natural-scroll enabled for
// touchpads by default.
// Mod+TouchpadScrollDown { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02+"; }
// Mod+TouchpadScrollUp { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02-"; }
// You can refer to workspaces by index. However, keep in mind that
// niri is a dynamic workspace system, so these commands are kind of
// "best effort". Trying to refer to a workspace index bigger than
// the current workspace count will instead refer to the bottommost
// (empty) workspace.
//
// For example, with 2 workspaces + 1 empty, indices 3, 4, 5 and so on
// will all refer to the 3rd workspace.
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
Mod+3 { focus-workspace 3; }
Mod+4 { focus-workspace 4; }
Mod+5 { focus-workspace 5; }
Mod+6 { focus-workspace 6; }
Mod+7 { focus-workspace 7; }
Mod+8 { focus-workspace 8; }
Mod+9 { focus-workspace 9; }
Mod+Shift+1 { move-column-to-workspace 1; }
Mod+Shift+2 { move-column-to-workspace 2; }
Mod+Shift+3 { move-column-to-workspace 3; }
Mod+Shift+4 { move-column-to-workspace 4; }
Mod+Shift+5 { move-column-to-workspace 5; }
Mod+Shift+6 { move-column-to-workspace 6; }
Mod+Shift+7 { move-column-to-workspace 7; }
Mod+Shift+8 { move-column-to-workspace 8; }
Mod+Shift+9 { move-column-to-workspace 9; }
// Alternatively, there are commands to move just a single window:
// Mod+Ctrl+1 { move-window-to-workspace 1; }
// Switches focus between the current and the previous workspace.
// Mod+Tab { focus-workspace-previous; }
// The following binds move the focused window in and out of a column.
// If the window is alone, they will consume it into the nearby column to the side.
// If the window is already in a column, they will expel it out.
Mod+BracketLeft { consume-or-expel-window-left; }
Mod+BracketRight { consume-or-expel-window-right; }
// Consume one window from the right to the bottom of the focused column.
Mod+Comma { consume-window-into-column; }
// Expel the bottom window from the focused column to the right.
Mod+Period { expel-window-from-column; }
Mod+R { switch-preset-column-width; }
// Cycling through the presets in reverse order is also possible.
// Mod+R { switch-preset-column-width-back; }
Mod+Ctrl+R { switch-preset-window-height; }
Mod+Shift+R { reset-window-height; }
Mod+F { maximize-column; }
Mod+Shift+F { fullscreen-window; }
// While maximize-column leaves gaps and borders around the window,
// maximize-window-to-edges doesn't: the window expands to the edges of the screen.
// This bind corresponds to normal window maximizing,
// e.g. by double-clicking on the titlebar.
Mod+M { maximize-window-to-edges; }
// Expand the focused column to space not taken up by other fully visible columns.
// Makes the column "fill the rest of the space".
Mod+Ctrl+F { expand-column-to-available-width; }
Mod+C { center-column; }
// Center all fully visible columns on screen.
Mod+Ctrl+C { center-visible-columns; }
// Finer width adjustments.
// This command can also:
// * set width in pixels: "1000"
// * adjust width in pixels: "-5" or "+5"
// * set width as a percentage of screen width: "25%"
// * adjust width as a percentage of screen width: "-10%" or "+10%"
// Pixel sizes use logical, or scaled, pixels. I.e. on an output with scale 2.0,
// set-column-width "100" will make the column occupy 200 physical screen pixels.
Mod+Minus { set-column-width "-10%"; }
Mod+Equal { set-column-width "+10%"; }
// Finer height adjustments when in column with other windows.
Mod+Shift+Minus { set-window-height "-10%"; }
Mod+Shift+Equal { set-window-height "+10%"; }
// Move the focused window between the floating and the tiling layout.
Mod+V { toggle-window-floating; }
Mod+Shift+V { switch-focus-between-floating-and-tiling; }
// Toggle tabbed column display mode.
// Windows in this column will appear as vertical tabs,
// rather than stacked on top of each other.
Mod+W { toggle-column-tabbed-display; }
// Actions to switch layouts.
// Note: if you uncomment these, make sure you do NOT have
// a matching layout switch hotkey configured in xkb options above.
// Having both at once on the same hotkey will break the switching,
// since it will switch twice upon pressing the hotkey (once by xkb, once by niri).
// Mod+Space { switch-layout "next"; }
// Mod+Shift+Space { switch-layout "prev"; }
Print { screenshot; }
Ctrl+Print { screenshot-screen; }
Alt+Print { screenshot-window; }
// Applications such as remote-desktop clients and software KVM switches may
// request that niri stops processing the keyboard shortcuts defined here
// so they may, for example, forward the key presses as-is to a remote machine.
// It's a good idea to bind an escape hatch to toggle the inhibitor,
// so a buggy application can't hold your session hostage.
//
// The allow-inhibiting=false property can be applied to other binds as well,
// which ensures niri always processes them, even when an inhibitor is active.
Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; }
// The quit action will show a confirmation dialog to avoid accidental exits.
Mod+Shift+E { quit; }
Ctrl+Alt+Delete { quit; }
// Powers off the monitors. To turn them back on, do any input like
// moving the mouse or pressing any other key.
Mod+Shift+P { power-off-monitors; }
}
switch-events {
lid-close {
spawn "handle-lid-close";
}
}
+2 -1
View File
@@ -14,13 +14,14 @@
ffmpeg
mediainfo
git-lfs
telegram-desktop
shards
deno
virt-manager
gimp
lftp
inkscape
wineWow64Packages.waylandFull
wineWowPackages.waylandFull
reaper
inputs.ksoloti-pr.legacyPackages.${pkgs.stdenv.hostPlatform.system}.ksoloti
calibre
+28 -23
View File
@@ -6,13 +6,6 @@
}:
let
homeDirectory = "/home/moco";
nmcli = lib.getExe' pkgs.networkmanager "nmcli";
ykman = lib.getExe pkgs.yubikey-manager;
isMocoVpnActive = lib.getExe (
pkgs.writeShellScriptBin "is-moco-vpn-active" ''
${nmcli} -t -f NAME connection show --active | grep -q '^moco$'
''
);
in
{
# Home Manager needs a bit of information about you and the paths it should
@@ -45,15 +38,6 @@ in
(writeShellScriptBin "moco-reto-otp" ''
ykman oath accounts code -s 'MOCO Reto' | wl-copy -n
'')
(writeShellScriptBin "toggle-moco-vpn" ''
if ${isMocoVpnActive}; then
${nmcli} c down moco
else
${ykman} oath accounts code -s 'MOCO Reto' | ${nmcli} c up moco --ask
fi
'')
kreuzberg-cli
tesseract
];
};
@@ -78,6 +62,34 @@ in
};
};
programs.ashell.settings.settings.CustomButton =
let
nmcli = lib.getExe' pkgs.networkmanager "nmcli";
ykman = lib.getExe pkgs.yubikey-manager;
isMocoVpnActive = lib.getExe (
pkgs.writeShellScriptBin "is-moco-vpn-active" ''
${nmcli} -t -f NAME connection show --active | grep -q '^moco$'
''
);
toggleMocoVpn = lib.getExe (
pkgs.writeShellScriptBin "toggle-moco-vpn" ''
if ${isMocoVpnActive}; then
${nmcli} c down moco
else
${ykman} oath accounts code -s 'MOCO Reto' | ${nmcli} c up moco --ask
fi
''
);
in
[
{
name = "MOCO VPN";
icon = "󰖂";
command = toggleMocoVpn;
status_command = isMocoVpnActive;
}
];
programs.zsh = {
cdpath = [
"${homeDirectory}/Code/mocoapp"
@@ -219,11 +231,4 @@ in
user = "butler";
};
};
programs.noctalia.settings.calendar.account.moco = {
type = "caldav";
provider = "custom";
name = "MOCO";
server_url = "https://intern.mocoapp.com/users/933620701/calendar/9df64d69e29c97a75cd6d93aeec0d5ae.ics";
};
}
+1 -9
View File
@@ -32,10 +32,6 @@ let
'';
meta.mainProgram = "voxtype-post-process";
};
postProcessingConfig = pkgs.writeText "voxtype-post-processing.json" (
builtins.toJSON cfg.postProcessing.settings
);
in
{
options.programs.voxtype = {
@@ -87,14 +83,10 @@ in
};
config = lib.mkIf cfg.postProcessing.enable {
xdg.configFile."voxtype/post-processing.json".source = postProcessingConfig;
xdg.configFile."voxtype/post-processing.json".text = builtins.toJSON cfg.postProcessing.settings;
programs.voxtype.settings.output.post_process = {
command = lib.getExe postProcess;
timeout_ms = 5 * 60 * 1000; # 5 minutes
};
systemd.user.services.voxtype.Service.X-Reload-If-Changed = [
postProcess
];
};
}
+174 -84
View File
@@ -3,70 +3,10 @@
lib,
pkgs,
pkgs-unstable,
inputs,
...
}:
let
cfg = config.programs.zed-editor;
models-dev = inputs.models-dev;
getModelsFromProvider =
provider:
let
providerPath = "${models-dev}/providers/${provider}";
modelsPath = "${providerPath}/models";
providerToml = builtins.fromTOML (builtins.readFile "${providerPath}/provider.toml");
findAllTomlFiles =
path:
let
entries = builtins.readDir path;
files = builtins.filter (name: builtins.match ".*\\.toml$" name != null) (
builtins.attrNames (lib.filterAttrs (_: type: type == "regular") entries)
);
dirs = builtins.attrNames (lib.filterAttrs (_: type: type == "directory") entries);
nestedFiles = builtins.concatMap (
dir:
let
nestedPath = "${path}/${dir}";
nestedTomlFiles = findAllTomlFiles nestedPath;
in
map (file: "${dir}/${file}") nestedTomlFiles
) dirs;
in
files ++ nestedFiles;
modelFiles = findAllTomlFiles modelsPath;
modelEntries = map (
file:
let
filePath = "${modelsPath}/${file}";
parsed = builtins.fromTOML (builtins.readFile filePath);
modelName = builtins.substring 0 (builtins.stringLength file - 5) file;
in
{
name = modelName;
display_name = parsed.name or modelName;
max_tokens = parsed.limit.context or parsed.limit.output or 128000;
}
) modelFiles;
in
{
api_url = providerToml.api;
available_models = builtins.sort (a: b: a.name < b.name) modelEntries;
name = providerToml.name;
};
opencodeProviders = map getModelsFromProvider [
"opencode"
"opencode-go"
"fireworks-ai"
];
openaiCompatibleProviders = builtins.listToAttrs (
map (p: lib.nameValuePair p.name p) opencodeProviders
);
ameba-ls = pkgs.stdenv.mkDerivation rec {
pname = "ameba-ls";
version = "0.1.0";
@@ -141,15 +81,8 @@ in
agent = {
enabled = true;
default_model = {
provider = "fireworks-ai";
model = "accounts/fireworks/models/glm-5p2";
};
};
agent_servers = {
OpenCode = {
command = "opencode";
args = [ "acp" ];
type = "custom";
provider = "openai";
model = "kimi-k2.5";
};
};
auto_update = false;
@@ -180,7 +113,7 @@ in
};
Nix = {
formatter.external = {
command = lib.getExe pkgs.nixfmt;
command = lib.getExe pkgs.nixfmt-rfc-style;
arguments = [ "-q" ];
};
};
@@ -208,10 +141,6 @@ in
];
};
};
TypeScript.language_servers = [
"tsgo"
"vtsls"
];
};
lsp = with pkgs; {
nixd.binary.path = lib.getExe nixd;
@@ -255,7 +184,7 @@ in
arguments = [ "--stdio" ];
};
json-language-server.binary = {
path = lib.getExe vscode-json-languageserver;
path = lib.getExe nodePackages.vscode-json-languageserver;
arguments = [ "--stdio" ];
};
package-version-server.binary.path = lib.getExe pkgs-unstable.package-version-server;
@@ -275,14 +204,9 @@ in
font_family = "IosevkaTerm Nerd Font";
};
edit_predictions = {
enabled = true;
mode = "eager";
provider = "open_ai_compatible_api";
open_ai_compatible_api = {
api_url = "https://api.fireworks.ai/inference/v1/completions";
model = "accounts/fireworks/models/qwen3p6-plus";
prompt_format = "qwen";
max_output_tokens = 1024;
};
provider = "copilot";
};
buffer_font_family = "Iosevka Nerd Font";
buffer_font_size = 16;
@@ -295,7 +219,173 @@ in
];
};
language_models = {
openai_compatible = openaiCompatibleProviders;
openai = {
api_url = "https://opencode.ai/zen/v1";
available_models = [
# Claude models
{
name = "claude-opus-4-6";
display_name = "OpenCode Zen - Claude Opus 4.6";
max_tokens = 1048576;
}
{
name = "claude-opus-4-5";
display_name = "OpenCode Zen - Claude Opus 4.5";
max_tokens = 200000;
}
{
name = "claude-opus-4-1";
display_name = "OpenCode Zen - Claude Opus 4.1";
max_tokens = 200000;
}
{
name = "claude-sonnet-4";
display_name = "OpenCode Zen - Claude Sonnet 4";
max_tokens = 1048576;
}
{
name = "claude-sonnet-4-5";
display_name = "OpenCode Zen - Claude Sonnet 4.5";
max_tokens = 1048576;
}
{
name = "claude-3-5-haiku";
display_name = "OpenCode Zen - Claude 3.5 Haiku";
max_tokens = 200000;
}
{
name = "claude-haiku-4-5";
display_name = "OpenCode Zen - Claude Haiku 4.5";
max_tokens = 200000;
}
# GPT models
{
name = "gpt-5.2";
display_name = "OpenCode Zen - GPT 5.2";
max_tokens = 400000;
}
{
name = "gpt-5.2-codex";
display_name = "OpenCode Zen - GPT 5.2 Codex";
max_tokens = 400000;
}
{
name = "gpt-5.1";
display_name = "OpenCode Zen - GPT 5.1";
max_tokens = 400000;
}
{
name = "gpt-5.1-codex-max";
display_name = "OpenCode Zen - GPT 5.1 Codex Max";
max_tokens = 400000;
}
{
name = "gpt-5.1-codex";
display_name = "OpenCode Zen - GPT 5.1 Codex";
max_tokens = 400000;
}
{
name = "gpt-5.1-codex-mini";
display_name = "OpenCode Zen - GPT 5.1 Codex Mini";
max_tokens = 400000;
}
{
name = "gpt-5";
display_name = "OpenCode Zen - GPT 5";
max_tokens = 400000;
}
{
name = "gpt-5-codex";
display_name = "OpenCode Zen - GPT 5 Codex";
max_tokens = 400000;
}
{
name = "gpt-5-nano";
display_name = "OpenCode Zen - GPT 5 Nano";
max_tokens = 400000;
}
# Gemini models
{
name = "gemini-3-pro";
display_name = "OpenCode Zen - Gemini 3 Pro";
max_tokens = 1048576;
}
{
name = "gemini-3-flash";
display_name = "OpenCode Zen - Gemini 3 Flash";
max_tokens = 1048576;
}
# GLM models
{
name = "glm-4.7";
display_name = "OpenCode Zen - GLM 4.7";
max_tokens = 205000;
}
{
name = "glm-4.6";
display_name = "OpenCode Zen - GLM 4.6";
max_tokens = 205000;
}
{
name = "glm-4.7-free";
display_name = "OpenCode Zen - GLM 4.7 Free";
max_tokens = 205000;
}
# Kimi models
{
name = "kimi-k2.5";
display_name = "OpenCode Zen - Kimi K2.5";
max_tokens = 262000;
}
{
name = "kimi-k2.5-free";
display_name = "OpenCode Zen - Kimi K2.5 Free";
max_tokens = 262000;
}
{
name = "kimi-k2";
display_name = "OpenCode Zen - Kimi K2";
max_tokens = 262000;
}
{
name = "kimi-k2-thinking";
display_name = "OpenCode Zen - Kimi K2 Thinking";
max_tokens = 262000;
}
# MiniMax models
{
name = "minimax-m2.1";
display_name = "OpenCode Zen - MiniMax M2.1";
max_tokens = 205000;
}
{
name = "minimax-m2.5-free";
display_name = "OpenCode Zen - MiniMax M2.5 Free";
max_tokens = 205000;
}
{
name = "minimax-m2.1-free";
display_name = "OpenCode Zen - MiniMax M2.1 Free";
max_tokens = 205000;
}
# Other models
{
name = "trinity-large-preview-free";
display_name = "OpenCode Zen - Trinity Large Preview Free";
max_tokens = 131000;
}
{
name = "big-pickle";
display_name = "OpenCode Zen - Big Pickle";
max_tokens = 200000;
}
{
name = "alpha-g5";
display_name = "OpenCode Zen - Alpha G5";
max_tokens = 200000;
}
];
};
};
};
userKeymaps = [
@@ -363,7 +453,7 @@ in
}
{
label = "opencode";
command = "opencode";
command = lib.getExe pkgs-unstable.opencode;
reveal = "always";
allow_concurrent_runs = true;
use_new_terminal = false;
+1 -1
View File
@@ -263,7 +263,7 @@
host = "0.0.0.0";
port = 8282;
secretKeyFile = config.age.secrets.invidious-companion.path;
binaryHash = "sha256-RU18Zt/dSMVF/hLeAg+J3BrmDUnO7L4mv/+g7A91WL8=";
binaryHash = "sha256-nZXKpExKCc2zgSdVT3qo05NyFdpM9H9NJB5UWo+MVWI=";
};
networking.firewall = {
-12
View File
@@ -20,8 +20,6 @@ in
"flakes"
];
auto-optimise-store = true;
min-free = 5 * 1024 * 1024 * 1024;
max-free = 10 * 1024 * 1024 * 1024;
};
gc = {
automatic = true;
@@ -30,8 +28,6 @@ in
};
};
services.journald.extraConfig = "SystemMaxUse=500M";
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.binfmt.emulatedSystems = [ "x86_64-linux" ];
@@ -110,12 +106,6 @@ in
};
};
services.resolved = {
enable = true;
dnsovertls = "opportunistic";
dnssec = "allow-downgrade";
};
services.networkd-dispatcher = {
enable = true;
rules."50-tailscale" = {
@@ -168,8 +158,6 @@ in
"builder"
];
modules.tempBuildStorage.enable = true;
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
-7
View File
@@ -32,9 +32,6 @@
"voidauth"
"gitea"
"gitea-actions-runner"
"searx"
"open-webui"
"open-terminal-api-key"
]
)
// {
@@ -58,9 +55,5 @@
file = ../../secrets/invidious.age;
owner = config.systemd.services.invidious.serviceConfig.User;
};
"grafana/secret-key" = lib.mkIf config.services.grafana.enable {
file = ../../secrets/grafana/secret-key.age;
owner = config.systemd.services.grafana.serviceConfig.User;
};
};
}
+8 -82
View File
@@ -19,7 +19,6 @@ let
secrets = config.age.secrets;
in
{
nixpkgs.config.allowUnfree = true;
imports = [
./glance.nix
];
@@ -120,7 +119,6 @@ in
user = smtp.username;
from_address = smtp.from;
};
security.secret_key = "$__file{${secrets."grafana/secret-key".path}}";
};
};
@@ -248,25 +246,7 @@ in
mosquitto = {
enable = true;
subdomain = "mqtt";
listeners = [
{
users = {
homie = {
acl = [
"readwrite homie/#"
];
hashedPasswordFile = secrets."mosquitto/homie".path;
};
};
port = 8084;
settings = {
protocol = "websockets";
cafile = "/run/credentials/mosquitto.service/fullchain.pem";
certfile = "/run/credentials/mosquitto.service/fullchain.pem";
keyfile = "/run/credentials/mosquitto.service/key.pem";
};
}
{
users = {
homie = {
@@ -328,6 +308,14 @@ in
subdomain = "irc";
};
sillytavern = {
enable = true;
subdomain = "st";
listen = true;
whitelist = false;
port = 3100;
};
nginx.virtualHosts."isarepomaa.com" = {
forceSSL = true;
enableACME = true;
@@ -399,73 +387,11 @@ in
};
};
searx = {
enable = true;
subdomain = "q";
port = 3400;
environmentFile = secrets.searx.path;
settings = {
general = {
instance_name = "freun.dev SearXNG";
};
server = {
public_instance = true;
image_proxy = true;
method = "GET";
secret_key = "$SEARX_SECRET_KEY";
};
engines = lib.mapAttrsToList (name: value: { inherit name; } // value) {
"google".disabled = true;
"duckduckgo".disabled = false;
"duckduckgo images".disabled = false;
"wolframalpha".disabled = false;
};
search = {
formats = [
"html"
"json"
];
};
enabled_plugins = [
"Basic Calculator"
"Hash plugin"
"Open Access DOI rewrite"
"Hostnames plugin"
"Unit converter plugin"
"Tracker URL remover"
];
};
};
open-webui = {
enable = true;
port = 3500;
environmentFile = secrets.open-webui.path;
environment = {
ENABLE_WEB_SEARCH = "True";
ENABLE_OLLAMA_API = "False";
};
subdomain = "owu";
};
webserver = {
acme.dnsChallenge = true;
tailscaleAuth.expectedTailnet = "tempel-vibes.ts.net";
};
};
virtualisation.oci-containers.containers.open-terminal = {
image = "ghcr.io/open-webui/open-terminal:latest";
autoStart = true;
ports = [ "127.0.0.1:3700:8000" ];
environmentFiles = [ config.age.secrets."open-terminal-api-key".path ];
environment = {
OPEN_TERMINAL_MULTI_USER = "true";
};
volumes = [
"open-terminal-data:/home/user"
];
};
virtualisation.docker.autoPrune.enable = true;
}
+10 -2
View File
@@ -4,7 +4,6 @@
containers = {
enable = true;
storage.settings.storage.driver = "btrfs";
containersConf.settings.compose_warning_logs = false;
};
oci-containers.backend = "podman";
@@ -13,7 +12,16 @@
enable = true;
autoPrune.enable = true;
defaultNetwork.settings.dns_enabled = true;
dockerCompat = true;
package = pkgs-unstable.podman;
};
docker = {
storageDriver = "btrfs";
enable = true;
autoPrune.enable = true;
};
};
users.users.jokke.extraGroups = [ "docker" ];
users.users.moco.extraGroups = [ "docker" ];
}
+3 -3
View File
@@ -1,8 +1,8 @@
{ lib, pkgs-unstable, ... }:
{ ... }:
{
services.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true;
services.displayManager.sessionPackages = [ pkgs-unstable.niri ];
programs.niri.enable = true;
services.printing.enable = true;
services.keyd = {
@@ -24,7 +24,7 @@
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = lib.mkForce false;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
};
-52
View File
@@ -69,15 +69,9 @@
enable = true;
extraPackages = with pkgs; [ rocmPackages.clr.icd ];
};
services.resolved = {
enable = true;
dnsovertls = "opportunistic";
dnssec = "allow-downgrade";
};
networking.networkmanager = {
enable = true;
wifi.backend = "iwd";
dns = "systemd-resolved";
plugins = with pkgs; [
networkmanager-openvpn
];
@@ -92,50 +86,4 @@
}
];
networking.firewall.allowedUDPPorts = [ 5353 ];
networking.wg-quick.interfaces = lib.mkMerge (
lib.map
(
{
region,
ipOctet,
publicKey,
}:
{
"everii-${region}" = {
address = [ "10.${toString ipOctet}.8.48/22" ];
dns = [ "10.${toString ipOctet}.1.1" ];
privateKeyFile = config.age.secrets."everii-vpn/${region}.key".path;
peers = [
{
allowedIPs = [ "10.${toString ipOctet}.0.0/16" ];
endpoint = "vpn.${region}.infra.everii.io:51821";
inherit publicKey;
}
];
postUp = ''
${pkgs.systemd}/bin/resolvectl domain everii-${region} ~${region}.everii ~${toString ipOctet}.10.in-addr.arpa
${pkgs.systemd}/bin/resolvectl dnssec everii-${region} no
'';
};
}
)
[
{
region = "de1";
ipOctet = 13;
publicKey = "uBUgSTZb6WbfE960S3qFP/UUMtdsgNWqtkTaBkp6Xxo=";
}
{
region = "fi1";
ipOctet = 14;
publicKey = "Yoakl0lrL6IK1nT8x5SGpaS39fQxRAsP9Zjpu8/1RRs=";
}
{
region = "ch1";
ipOctet = 15;
publicKey = "gfciqAk+X02zoEKXSvRamx5+TGL3i4GpT7oUvHMD0xo=";
}
]
);
}
-2
View File
@@ -33,8 +33,6 @@ in
nerd-fonts.iosevka-term
];
services = {
gnome.evolution-data-server.enable = true;
flatpak.enable = true;
pcscd = {
enable = true;
plugins = [ pkgs.ccid ];
-3
View File
@@ -10,9 +10,6 @@
})
[
"borgbackup-radish"
"everii-vpn/de1.key"
"everii-vpn/ch1.key"
"everii-vpn/fi1.key"
]
);
}
+2 -8
View File
@@ -4,10 +4,7 @@
users.users.jokke = {
uid = 1001;
isNormalUser = true;
extraGroups = [
"wheel"
"networkmanager"
];
extraGroups = [ "wheel" ];
shell = pkgs.zsh;
initialPassword = "changeme";
};
@@ -15,10 +12,7 @@
users.users.moco = {
uid = 1000;
isNormalUser = true;
extraGroups = [
"wheel"
"networkmanager"
];
extraGroups = [ "wheel" ];
shell = pkgs.zsh;
initialPassword = "changeme";
subUidRanges = [
+5 -154
View File
@@ -4,7 +4,6 @@
{
pkgs,
lib,
ssh,
config,
inputs,
@@ -58,10 +57,10 @@ in
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# Define a user account. Don't forget to set a password with 'passwd'.
# Define a user account. Don't forget to set a password with passwd.
users.users.jokke = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable 'sudo' for the user.
extraGroups = [ "wheel" ]; # Enable sudo for the user.
packages = [ pkgs.nh ];
openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ];
initialPassword = "changeme";
@@ -75,7 +74,7 @@ in
wget
htop
git
alsa-utils
inputs.turny.packages.${stdenv.hostPlatform.system}.default
];
# Enable the OpenSSH daemon.
@@ -89,159 +88,11 @@ in
allInterfaces = [ "ssh" ];
};
# Enable audio (ALSA)
hardware.alsa.enable = true;
# Complete config.txt settings: boot-critical u-boot lines plus SPI enable.
# The configtxt module generates the full config.txt, overriding the
# hardcoded one in sd-image-aarch64.nix which ignores these settings.
hardware.raspberry-pi.configtxt.settings = {
pi3 = {
kernel = "u-boot-rpi3.bin";
core_freq = 250;
};
pi02 = {
kernel = "u-boot-rpi3.bin";
};
pi4 = {
kernel = "u-boot-rpi4.bin";
enable_gic = true;
armstub = "armstub8-gic.bin";
};
all = {
arm_64bit = true;
enable_uart = true;
avoid_warnings = true;
dtparam = [
"audio=on"
"spi=on"
];
};
};
# Use the configtxt module's generated config.txt when building SD images
sdImage.populateFirmwareCommands = let
inherit (config.hardware.raspberry-pi.configtxt) file;
in ''
(cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
cp ${file} firmware/config.txt
cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-2-b.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-3-b.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-3-b-plus.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-cm3.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-zero-2.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-zero-2-w.dtb firmware/
cp ${pkgs.ubootRaspberryPi4_64bit}/u-boot.bin firmware/u-boot-rpi4.bin
cp ${pkgs.raspberrypi-armstubs}/armstub8-gic.bin firmware/armstub8-gic.bin
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-4-b.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-400.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-cm4.dtb firmware/
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-cm4s.dtb firmware/
'';
# Load SPI and GPIO kernel modules so /dev/spidev* and /dev/gpiomem exist
boot.kernelModules = [
"spi_bcm2835"
"bcm2835_gpiomem"
];
# Use the firmware-passed device tree (with dtparam/dtoverlay applied)
# instead of u-boot loading a fresh DTB from FDTDIR
boot.loader.generic-extlinux-compatible.useGenerationDeviceTree = false;
# Mount the firmware partition so config.txt is accessible and can be
# updated on deploys. The sd-image module defaults to noauto; we need it
# mounted so the activation script can sync config.txt.
fileSystems."/boot/firmware".options = [
"nofail"
"defaults"
];
# Sync the generated config.txt to the firmware partition on each deploy
# so dtparam/dtoverlay changes take effect without reflashing the SD card.
system.activationScripts.syncConfigTxt = {
text = ''
if mountpoint -q /boot/firmware; then
cp -f ${config.hardware.raspberry-pi.configtxt.file} /boot/firmware/config.txt
fi
'';
};
# Turny service
systemd.services.turny = {
description = "Turny Spotify RFID Controller";
documentation = [ "https://github.com/repomaa/turny" ];
after = [
"network.target"
"sound.target"
];
wants = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart = lib.getExe inputs.turny.packages.${pkgs.stdenv.hostPlatform.system}.default;
User = "turny";
Group = "turny";
WorkingDirectory = "/var/lib/turny";
Restart = "always";
RestartSec = 10;
RestartPreventExitStatus = 23;
TimeoutStopSec = 10;
KillSignal = "SIGINT";
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ReadWritePaths = [ "/var/lib/turny" ];
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
LimitNOFILE = 1024;
MemoryAccounting = true;
MemoryMax = "100M";
StandardOutput = "journal";
StandardError = "journal";
SyslogIdentifier = "turny";
};
environment = {
RUST_LOG = "info,turny=debug";
RUST_BACKTRACE = "1";
};
};
users.users.turny = {
isSystemUser = true;
group = "turny";
extraGroups = [
"gpio"
"spi"
"audio"
];
home = "/var/lib/turny";
createHome = true;
};
users.groups.turny = { };
users.groups.gpio = { };
users.groups.spi = { };
# Udev rules for GPIO and SPI access
services.udev.extraRules = ''
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"
SUBSYSTEM=="gpiomem", GROUP="gpio", MODE="0660"
SUBSYSTEM=="spidev", GROUP="spi", MODE="0660"
'';
# This value determines the NixOS release from which the default
# settings for stateful data, like user locations and database versions
# on the system were taken. It's perfectly fine and recommended to leave
# settings for stateful data, like file locations and database versions
# on your system were taken. It's perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
sdImage.compressImage = false;
}
-1
View File
@@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM3To9DeTGzwiQN+uqNWZXKzGG1BJnLvYRgLm0/iI92Y turny
-1
View File
@@ -11,7 +11,6 @@
./vlans.nix
./firewall.nix
./storage-box-mounts.nix
./temp-build-storage.nix
./services
inputs.agenix.nixosModules.default
];
+1 -12
View File
@@ -46,18 +46,7 @@ in
certificate_chain_path = "/run/credentials/adguardhome.service/fullchain.pem";
private_key_path = "/run/credentials/adguardhome.service/key.pem";
};
dns = {
bind_hosts = tailscaleIps;
upstream_dns = [
"https://dns10.quad9.net/dns-query"
"https://cloudflare-dns.com/dns-query"
"https://dns.google/dns-query"
];
fallback_dns = [
"https://cloudflare-dns.com/dns-query"
"https://dns.google/dns-query"
];
};
dns.bind_hosts = tailscaleIps;
};
};
+1 -2
View File
@@ -26,13 +26,12 @@
./weechat.nix
./hledger-web.nix
./glance.nix
./sillytavern.nix
./nqptp.nix
./actual.nix
./voidauth.nix
./gitea.nix
./dhcp-dns-sync
./invidious-companion.nix
./searx.nix
./open-webui.nix
];
}
@@ -86,18 +86,19 @@ def get_leases(interface : String, networkctl_path : String? = nil) : Array(Leas
cmd = networkctl_path ? "#{networkctl_path}" : "networkctl"
args = ["status", interface, "--json=short"]
process = Process.new(cmd, args, output: Process::Redirect::Pipe, error: Process::Redirect::Pipe)
output = process.output.gets_to_end
error = process.error.gets_to_end
Process.run(cmd, args, output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
result = process.wait
output = process.output.to_s
unless result.success?
error = process.error.to_s
raise "networkctl failed (exit code #{result.exit_code}): #{error.empty? ? output : error}"
end
status = NetworkStatus.from_json(output)
status.dhcp_server.try(&.leases) || [] of Lease
end
end
def write_if_changed(content : String, path : String) : Bool
# Check if content is the same
@@ -153,12 +154,12 @@ def reload_unbound(unbound_control_path : String?)
cmd = unbound_control_path ? "#{unbound_control_path}" : "unbound-control"
puts "Reloading Unbound..."
process = Process.new(cmd, ["reload"], output: Process::Redirect::Pipe, error: Process::Redirect::Pipe)
error = process.error.gets_to_end
Process.run(cmd, ["reload"], output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
result = process.wait
unless result.success?
raise "unbound reload failed (exit code #{result.exit_code}): #{error}"
raise "unbound reload failed (exit code #{result.exit_code}): #{process.error}"
end
end
puts "Unbound reloaded successfully."
+18 -5
View File
@@ -37,9 +37,6 @@ let
};
immutableEnvironment = {
HOME = "/var/lib/dnote";
XDG_DATA_HOME = "/var/lib/dnote";
XDG_CONFIG_HOME = "/var/lib/dnote";
GO_ENV = "PRODUCTION";
OnPremises = true;
DBHost = "/var/run/postgresql";
@@ -93,10 +90,26 @@ in
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
serviceConfig = {
ExecStart = "${pkgs-unstable.dnote}/bin/dnote-server start --port ${toString cfg.port}";
ExecStart = "${pkgs-unstable.dnote}/bin/dnote-server -port ${toString cfg.port} start";
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
User = cfg.user;
StateDirectory = "dnote";
BindPaths = [
"/var/run/postgresql"
];
BindReadOnlyPaths = [
/run/systemd/resolve/stub-resolv.conf
/etc/ssl
/etc/static/ssl
/etc/resolv.conf
/etc/static/resolv.conf
/etc/nsswitch.conf
/etc/static/nsswitch.conf
/etc/hosts
];
};
confinement = {
enable = true;
packages = [ pkgs.cacert ];
};
wantedBy = [ "multi-user.target" ];
};
+3 -28
View File
@@ -1,39 +1,14 @@
{
config,
lib,
...
}:
{ config, lib, ... }:
let
cfg = config.services.mosquitto;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
acme = config.security.acme;
in
{
options = {
services.mosquitto = {
openFirewall = lib.mkEnableOption "Open firewall port for Mosquitto";
subdomain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
};
config = lib.mkIf cfg.enable {
networking.firewall.allowedTCPPorts = map ({ port, ... }: port) cfg.listeners;
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{ networking.firewall.allowedTCPPorts = map ({ port, ... }: port) cfg.listeners; }
(lib.mkIf (cfg.subdomain != null) {
security.acme.certs.${fqdn}.postRun = ''
systemctl restart mosquitto
'';
systemd.services.mosquitto = {
requires = [ "acme-${fqdn}.service" ];
serviceConfig.LoadCredential = [
"fullchain.pem:${acme.certs.${fqdn}.directory}/fullchain.pem"
"key.pem:${acme.certs.${fqdn}.directory}/key.pem"
];
};
})
]
);
}
-1
View File
@@ -204,7 +204,6 @@ let
hetzner = {
class = "octodns_hetzner.HetznerProvider";
token = "env/HETZNER_API_TOKEN";
backend = "hcloud";
};
};
-78
View File
@@ -1,78 +0,0 @@
{
lib,
config,
pkgs-unstable,
...
}:
let
cfg = config.services.open-webui;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
open-webui-pkg = pkgs-unstable.open-webui.overridePythonAttrs (oldAttrs: {
dependencies =
oldAttrs.dependencies
++ (with pkgs-unstable.python3Packages; [
pgvector
psycopg2
])
++ [
pkgs-unstable.ffmpeg
];
});
in
{
options.services.open-webui = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services = {
open-webui = {
package = open-webui-pkg;
environment = {
ANONYMIZED_TELEMETRY = "False";
DO_NOT_TRACK = "True";
SCARF_NO_ANALYTICS = "True";
WEBUI_URL = "https://${fqdn}";
VECTOR_DB = "pgvector";
PGVECTOR_CREATE_EXTENSION = "False";
DATABASE_URL = "postgresql:///open-webui?host=/var/run/postgresql";
CORS_ALLOW_ORIGIN = "https://${fqdn};http://localhost";
};
};
webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ "open-webui" ];
ensureUsers = [
{
name = "open-webui";
ensureDBOwnership = true;
}
];
extensions = ps: with ps; [ pgvector ];
};
};
systemd.services.open-webui-pgvector-setup = {
description = "Ensure pgvector extension exists for open-webui";
wantedBy = [ "open-webui.service" ];
before = [ "open-webui.service" ];
after = [
"postgresql.service"
"postgresql-setup.service"
];
requires = [ "postgresql.service" ];
serviceConfig = {
Type = "oneshot";
User = "postgres";
Group = "postgres";
ExecStart = "${config.services.postgresql.package}/bin/psql -d open-webui -c 'CREATE EXTENSION IF NOT EXISTS vector;'";
RemainAfterExit = true;
};
};
};
}
-70
View File
@@ -1,70 +0,0 @@
{
pkgs-unstable,
config,
lib,
inputs,
...
}:
let
cfg = config.services.searx;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
disabledModules = [ "services/networking/searx.nix" ];
imports = [
"${inputs.nixpkgs-unstable}/nixos/modules/services/networking/searx.nix"
];
options.services.searx = {
port = lib.mkOption {
type = lib.types.int;
};
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services.searx = {
redisCreateLocally = true;
package = pkgs-unstable.searxng;
limiterSettings = {
botdetection = {
ipv4_prefix = 32;
ipv6_prefix = 56;
ip_limit = {
filter_link_local = true;
link_token = false;
};
ip_lists = {
pass_ip = [
"127.0.0.1/32"
"::1"
];
};
trusted_proxies = [
"127.0.0.0/8"
"::1"
];
};
};
settings = {
server = {
bind_address = "localhost";
port = cfg.port;
limiter = true;
};
};
};
services.webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
# searx expects limiter.toml in the same directory as settings.yml (/run/searx)
systemd.services.searx-init.script = lib.mkAfter ''
ln -sf /etc/searxng/limiter.toml /run/searx/limiter.toml
'';
users.groups.searx.members = [ "nginx" ];
};
}
+28
View File
@@ -0,0 +1,28 @@
{
config,
lib,
...
}:
let
cfg = config.services.sillytavern;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.sillytavern = {
subdomain = lib.mkOption {
type = lib.types.str;
default = "sillytavern";
};
};
config = lib.mkIf cfg.enable {
services.webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn} = {
tailscaleAuth = true;
proxyBuffering = false;
locations."/".proxyPort = cfg.port;
};
};
};
}
-38
View File
@@ -1,38 +0,0 @@
{
lib,
config,
...
}:
let
cfg = config.modules.tempBuildStorage;
in
{
options.modules.tempBuildStorage = {
enable = lib.mkEnableOption "temporary build storage volume";
device = lib.mkOption {
type = lib.types.str;
default = "/dev/sdb";
description = "Block device to mount for temporary nix build storage.";
};
mountPoint = lib.mkOption {
type = lib.types.str;
default = "/mnt/build";
description = "Mount point for the temporary build storage.";
};
};
config = lib.mkIf cfg.enable {
fileSystems.${cfg.mountPoint} = {
device = cfg.device;
fsType = "ext4";
options = [
"nofail"
"defaults"
];
};
nix.settings.build-dir = cfg.mountPoint;
};
}
+9 -8
View File
@@ -1,9 +1,10 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA tKc6OuybMhchwMgREDpacwxz/IlBCOOD9OQp/UMLBys
7mOmTzMUnLYnQPHOWwIiZ4vFyPxQpwzPmnQMuWsQWfA
-> ssh-ed25519 DFiohQ +ysb8W20OD73awl7we7/XfiX5q8sRtMlerr5r4gavTA
5OHmlJHoVRXho2Swe/XDTtQKn0pW9PH1Ky4rvhR7o/8
-> ssh-ed25519 PT7ffg 0vvEHkjNJFH/UUcwX8O+F0StVBqNkQNp7u2qasHZLCE
xKySMrX3T59dCKEUHyR/cdT+8KCFdCerUyUg7r7ZBYQ
--- ielXPF0xxxqSrBc1yeOxLdz50ZjCLnZJ45Wsg84yPso
×S;ëqø»äá û˜…Å@ðŒ±S,a¾¬ÛÜH3;(¥¡%o#‘ýx$Qgw”žãÊ>ôias  °ù»%fjr¼ÊÜ^cïrô©´Ñ‚â°`ä—e›¼níû_–w›×¶çõvÓCØÅ‡x}|µFnÿjݹ°²À­ ß
-> ssh-ed25519 osOCZA n8TUqlE5mEfKa7aPdefBjIKAhydL1vrCZK8cLTs+j3w
ayrirgnGn6dTBKShBRDHJAI3t040hf5LEo8ZuHUGNHg
-> ssh-ed25519 DFiohQ +rVCacN11divc55NH2fKsYXa6IJ0ieW18riYz9nkrwo
4qv06+QnvPKYRRkQauDwKfnT1c4/GHDkxHVrgkfrHNw
-> ssh-ed25519 PT7ffg wklac2cEzzfMQaItzsAv6I1az8HxZ15s3ANwxYO59To
abdf65f0BT2sE52T60lnfqY/iDCgLAzgRQlPqjRp6Y4
--- Wi4+L0OzsduT8T7G4+H8pp05EzbTjNlJowwBDWUN1mU
ÛŸþf޹“Íå¸ÁºTL®›`‚ý¬—F„F»‡í$S1†ºqÐv3nÙOu‰Xªf␍AþÂn‡e’¥Sý©ªEãM_¿ ï^VÀŽ‘ä4p.øäBIO¹Ô÷
Bñ`Œsï)Z4œÜHŸßRvÓ/MÓÕ-> 5òÓå îµ²⢃;ü~¹÷ÿ
Binary file not shown.
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA IY+dU1oe5ps4gli4bqRVdIaKaOWfEieIX0b/8q6+Pys
L5Bkf3dTH9k0HUYrrTvLq5hvS7CK1nJp1mJumBlVGb8
-> ssh-ed25519 DFiohQ BjbGXY9CmrAEcv4+hk+A1AIP06NeP4NhymTeByfxi3c
94qpDg6nEowmkNUzeDGtNw8VgT8vsWR4y28/lMyfZmc
-> ssh-ed25519 hRPDBg 7Lq1rjiz1FhgFkWaZJaLR24k+HAKLraw9YjVsWPspVE
3eBnzsN96eLmIlu9MNiBhv41csudd4WQUF5Q9vpGkj0
--- l10qNh0gcX57lTZsiTsuzwhVGnKM5hH5+ZbjI9yfr/Q
ûK¿;n},ijæ6uîȧMŤ'ülÿÓÂò–?ÜÜå'„MtÙš.¢ßšžô.IQó³öy)&|=¨½@1ãmSRD"&&áWUgið}'ˆ¹¶¥
-> ssh-ed25519 osOCZA 9ZHiyWiFLySuINTKXSjysD9u7LKyhK2bJo6JmVskDGc
IDUB5fbYwEwZDIRwOAAWdDkHOrmMwlZT+2UhObhHVXg
-> ssh-ed25519 DFiohQ OFE+xiH2zIXccj3axHbN8TBKS927ldBtlC80ZCVH2To
E50Dr6cyllIN66QVGYvonpSRV0nERdstsEGUeXrVDoE
-> ssh-ed25519 hRPDBg Z9ItEkrFay5918/EY2RCl0a9PZx3z8amW4yMyfgEqQI
rEs5eqfdnpwnxzPRlMi1vBrtzsbQCwCupkGEXFs7rVA
--- op5RVwxhxkxflXvoAM7ZcEXLutkF9Op70tUxsJundN4
è$À;Åå1ÒO`bBMÆLŸf“<îã%>1pkvMØ]?x|ù¬zäFƶ~ÖÙÕ¬pL␍úIÝ(␍u¢ÉDØÈZNž÷ªÏ•Qv˜îŒr88ð¹[²Ãü
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA /DM/QULRCY6cCkuqi0h+PVXW+lFcvUwTaNI4vVHoG3w
f/U+q+Y6zoIcg0ttN+JgFc0GqXKWV08nmWUeeeC+b5k
-> ssh-ed25519 DFiohQ kgrVGGqMrBn8W1trnR116ODUHTeDR60BLnWfqANr/QA
ys8CLK1XHkOQvp3xlcQkQ3me2a2/Nu94o5L9swWhsAs
-> ssh-ed25519 PT7ffg 3jg8xsvEsjZ8Gzap1Q0T5cZpi/lmk8/DuE1Zsk7ByGY
gfrFh5M2GWWa+5IaO7yL8LRlMThRWt/XdZIYDXxo8zU
--- U2Rbi2kHkOIW8AErxfFCUwf2HayX+J4QjKVz/4aGn9k
¤c Ý“øÉ‘ôºœ2kž7ˆÿ)ýí,h~Ó1!¦ÓÁjaÍé`ÊVz Æ-␍n}<ÎØºKä+Rrw¿’Wð†Æ®3éCRªòV©yÙ{
-> ssh-ed25519 osOCZA sWXC8tg44GfsKCFIDhvmkSfO1NnQ/GdP7s7yKnn/wwk
tulj+EsIi91rXw7XV0elKyVpamDNnEdy1GikcmPGLlE
-> ssh-ed25519 DFiohQ 8Cxy6QZkviBPw51+3m6JYtcOPTbYy6C7m7uomdFwAgY
4GuwAQKMtKI7cZCg2/yl4IAd98Zeai+/rXScSRRmS9E
-> ssh-ed25519 PT7ffg dnDE79EVRVys94oHOSduzhcyd8oHA8/scK98By5g9FE
RFfMpq56KDvIJcOg5JdaOiEN6wupKFUNbcHYCS/kyp0
--- Tg+Gw0uAEFItHT00VDs9BmhvX2f8afcRwuh8OSqLEh4
È*ͦciðÜ~P+c÷{p™“²Û…£yé\©â 'a“<x6Xsá¡zógŸ~=´è0¸–6=sÞ5ÐÆlØ5BŒÎ3ÌÜ5í»š§
Binary file not shown.
-9
View File
@@ -1,9 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA q6tH0FVLeK0IAAlBAQfpQJScvePOK3+TSZ38NAfQHWo
63rJwFLyI3TRkjR8j7Ek4ELE4Se+alHCl1tO5ujmzMA
-> ssh-ed25519 DFiohQ fc1aSau4DbXn8XzqgAFYbgw1T3U3IMva/RpUTqlBR2Y
G+qY9wIDOvyOWmQnNmnaasdlFVUGR6cBWpK2Zce5LHM
-> ssh-ed25519 hRPDBg FT7EbwtMJF9hjfcCc2PqWmRoYYFWkCvQ06DbVfpOg1Y
8j8Rwfmu+tckffMKMQ0h3/un09mUCi7/hymgqjSNz8U
--- C03Wle22666W4khdQVHeC9eP+ZJ8AcP3dB+jfCHpeP0
öÚ†µi•lÎ|G~¸­DåÐóä"׆ÎÁ¸}j,Ì\|w,°–0ÜO5ζ¹?~•"ÊÃÖy»fl¢nËõÍÆT
-10
View File
@@ -1,10 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA ylc8vGyAOgdlAWSFGDQ7rjIoPCXtCgTdjyq7jiJLtF4
HG7y2EyaHKB9664gfH3Q+YxL2ArAEKIIlwtC8YKtHh8
-> ssh-ed25519 DFiohQ XPjQk3y/Dmsh8mHqUD3oLdRR9drDB29sCRjoaYlbGkw
BfGPDs5n1IoYgAZzEgRnmvhxqvY+BrYLVASJOR/szeI
-> ssh-ed25519 hRPDBg s1AkS3NGpW8yAQwXqx9UgHc2SaiA+AcE4vCbYlHENH0
e9bhxPNYUct/dQEVirOkX1A+ulPiE1PHGh8M6qrM67k
--- Ov/CzZKVSYIKK4Z4W5duNQd/Bu9tkMwXhPXhzW9KOjk
žã«1ÿkVyôG||8ÊÚD„dÙÓ òk*ŽÅhɽ~{»Ð1T,¾?ht%Þ!ƃ§°ýÎÜbÍ·µË¼ßtÚ
ø5ÀH{ÿä
-9
View File
@@ -1,9 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA KJyp6aDTicchEwD0n3htOSM/F7YD/Fn3pVG71Gi3/0s
kcfL1a3T0dfsJ2/kdHjRf1fDuTaQbcMpcYPAs5BcPQA
-> ssh-ed25519 DFiohQ PTdr7UeBsPa/M9ygKNxIBrno/a62AgDrn+jV8pWA7UE
V1YGZVaxnarJmtBIFPuB1l/GYApJlrE2fVERJEH+yng
-> ssh-ed25519 hRPDBg HhuQcUjwuqeRGuHxEL+/TLjEnwmQsXo8oaBvRYgLES8
t9KoYWdtMugNwCRETs72y2PR5xvN9Vi3wyj0lndd0vE
--- GlYbPI0bPreYxtImgTl/lZPOY+0DKDruuqZ/jTmWhgA
ÍÓt³ö H5 ¢>Â*'únæÂ0x£·2A Є=%¶ _´ó+”0zØÃI ©qŘo=>Üã␍¬æžM ‹¾e!ÙÜ‚j<žÁak
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA Wnc3PL++j3i07tkKyKdfwjUwZEg5tnXQfWW+JhRSIQU
eOYYoPvzsta+BdGqRGUw60upA7da+xAKkgY5SULrz98
-> ssh-ed25519 DFiohQ Zw+DAeYPLTSTm64f/xoxaSHaBjfeuX977Hw2xBNLPlc
pNl3S10RF2N9lcgMtGuodnyaIyGgqC1vuYqyqaeRiJA
-> ssh-ed25519 PT7ffg KUcvZ2YR4pV1pJrQzIUmEs2U0azDcu/PCxBKaiuYrC0
EX9rhh1XtpGOWjQ4OzBVxD+hRtRc1caLgB2EJ1isYKg
--- OUJ/fR74TEgXceG3Dml7d9o0+rzdzUQu83Q3IkW//TY
Ú6␍ãѱ¤ëÉ<£xiYWèª!™út–ß=CÚ;Ù'üéR½ÚÓMŠÖaš—:?(]†–dY·ü<…␍r0ÑD1”ÜOŸAÿˆX‹ÑÅVÍG␍\"îŒzzõ·Ÿ­ÅØúD‹P’Óø
-> ssh-ed25519 osOCZA wpN3yvRvjDjs45jISvWHluVYjgt7KBlDBbWubMtgn3U
SzZZWcc8NpYA08kafMwsvYQWoAsNBN+9AXYxqQmiEkA
-> ssh-ed25519 DFiohQ R2vygwm3+reW5mJTUdFrEhExA0uI3yhHngNhp4hLBRI
tmY3D32TOK/CMrLlzwE8vYr2vIxYXkcqHPgq/a0QO0M
-> ssh-ed25519 PT7ffg IkM8NFdkSef9sB1LuBB3joDyKNwcJkKa7B30mrCufVk
nzj1ccl0DU/Ubt6I0PMscftvra0Y4n2gRB7oBtTGoaY
--- OtT3fPqZXKVCWQ/35+9XZMfhtEuUHaio+zxqVBeyJVs
æ÷Ðc¯cÛ‰Œ¨©¤iB©sò8ÐaDJb•,¡åHz-yÕ6lÅNÓ{Ó#•¤ &)x+M1¹û‰=ñ,3%»ý8ãXªÛª¿î,Øo8ã×Ý¡úªfšÛˆZô˜]ZõoWO›Ã>T
+9 -8
View File
@@ -1,9 +1,10 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA X80qdaNMdIz7sy+tLmTYlati/SKucc03gW5FXE9/xlM
5ZvMFxd3MOl4SyBEYR9xawjREi8UwEmW9DyuCC+E8jk
-> ssh-ed25519 DFiohQ NPghTlceQTcZh4VGASPeFZy8G2VLURxykc0JjvAdW2s
MkIN+sOI3yD8UzSmHOabeVnFHfXDDWMSWwFIuHGJGFE
-> ssh-ed25519 PT7ffg mc6TmlrgBEBQeOECY6SzHaLhpi4SrYc/9m9nml/2GGY
VDNZtQSSGG1+/84Y5TcyiJytoAoPCyj9PlYV7WZiKE8
--- GPLaSoO3+AZXfY9gIweM+dDAb+hBSadz++DqYEkR2ug
;ܬl‘öj…Ãå˜æ?çÚøÆQ² „NÁìû/ªº)à·[xØö%‹F!¼0µ†ª
-> ssh-ed25519 osOCZA wVGyI0bVEaVUbXMiumjhqdno2VD6ZHmvJM0NRUoesk0
kW8Zs6KV+KBmyg/imOzVHHPg/K3oHyB05ayMQsT01CQ
-> ssh-ed25519 DFiohQ h+C13tChA1OrCdmJO9rkjW3ER/d8OW7P2IGd5spsd2Y
7LOIjam4k8lmglNEuNs7lkRpt3UbYkxFxXPwkj2Fbc8
-> ssh-ed25519 PT7ffg 33/HLpIkjfw+QC1qm1Tmr4YqMjgtt4bsVoFqUNinhno
8+PGKzffQT2DuM8l9mqgGieHiQCpBsbWbHDIdxFapBk
--- lzyw/qQSELbYc8TWYFGD57JBF4JorTLhBPNdlxYjVqY
lÞHºaEM9ÅóüÀS0(Š¥¦lâJxø-
—sÔ¸éžc¬H EÖ˜Í0_{|Kp2
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA t6j6GnCmxIYZKfYZ+dthOAaJqjHtbsIZSOYaonDvDxo
KWhOeO95ExUHgcHtm5BYwZnG4EOMZpcKB7h/UM7yxZ8
-> ssh-ed25519 DFiohQ XlRVp8vYYbCbrCGIYpXLsewZkceQJRqUlegTnAp4gSo
4yu21bl6xEodz/lVBV/imCresblG89uyz8fzWhSjBJ8
-> ssh-ed25519 PT7ffg UN3WxuZZrVq4xG/fOYdfyMBBvqO5aKLF+0DZ+8SYjFk
DSGXD5GeYKIwT/GLG2n16660x3R4JASnzPI0vBvi1kw
--- mobBb6GglqgnpHy9IaTX6FwjIuv+g31FpC0V4tKJ9mg
ábwøysqéõfØÖ½Çøâ÷ùE§KvYt&²1ÿWzÙì«ÊZ+ɳ×rœ¼ ”o·‹ƒŠG
-> ssh-ed25519 osOCZA tO/H34qaDDLUWG7ovv4wD8At0v+nRLxgozIHFRCxIG4
AU0szD79hzM5rLSYQhki+Ukc2CbATwdU5uWPNLn1Oa0
-> ssh-ed25519 DFiohQ hwThntCOC9a5eSAay4zWW044NoC4JggBXgh3KAZdGAA
z2EDIRJH9jGTKYuxm++ns1yqFyUYrA091Q22VMNgqec
-> ssh-ed25519 PT7ffg tNGBQvJnGvnGB0Mk24Hyh75ZJAMbZMKk60wkc/PMEiE
ijewL+qajDYN7abihSAvAySqcx3idkstsriVVw9Fj+A
--- HuLF80Z9VHyeMVTyN9CLKIRn0HFUpIhaWC9KgLHXrlI
:™Â3ÿö:ïžéÔRàöó1™Ä„ɰŠBÀo&Qd1BXU¤ìí¨q Ñ ßšüÙf€êQPÝ—8
+9 -8
View File
@@ -1,9 +1,10 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA OKLTpDP78xbkZ2uxMaJtOIVCd3/jbAV98E9yFGg0SHk
1RwoL09ZccJhYWJxy/JgMxJSHc74RsKD22L3c13BARo
-> ssh-ed25519 DFiohQ us/uB+O7gBPPJ3ewzn1Vpkc59obSQcwSORFIhPsw0Wo
dW/6eaPCKI0Z59PPtoNiOWoaNPNFqy1WPhI2y+AXXok
-> ssh-ed25519 PT7ffg Shf65NGS/njVgmsKYzH0G+R3Lbul24ov1YX0D8yCVlE
+V8lgVMK4tIIvjWXDRSK15Ko4lf9dBbd3BVnLm4zl7Y
--- YLhHK7p69aBksuFUcoRIfh9xej8HsQcDgrCUEyw4x64
#\)V×¹ç{÷ËÆ½OøHK1lNæ(ˆß*ÒQ ^\aÉz(Ì?A»ý©·(#$Õè3Q” ·„¸£K ã…p"Is)¼‡‹;½É1%
-> ssh-ed25519 osOCZA VAbqavKlZzuLT1XwcdWuRX39bNPbuVvsGPXNv5lwd2g
4mMBgBCfnnbY6lo7WAnppqEk8tA1A5MGvXjjZ84cr5k
-> ssh-ed25519 DFiohQ hzICSaoG/iPl1VEm7gF49mXCzhVZypQN00/9dqbAfgI
VM5O8wLvzFyFkttqP4YWQ0bR5iKkAvpYFPsep2nYt7E
-> ssh-ed25519 PT7ffg +FbUxPiTEBDl4QdwYUSuqD0+xzHWe4X9l1cqRePwZzQ
tfKlhCQ0mK67E8UcXki9y4SmgTLsrZwD6kKBQz1NiSI
--- tnLRJm+/eP6KzNV7vD1d2KtDe6SRyo2MoIsow4CDyrU
±lù•þe–bö±¹D.Û␍c‰Y.êÓdÎNáWä݃ξ^®Å´ï©PщSFd.Ú=ýŸ3øA@I9/޼˜læó›Í~;FÒR5[|
s
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -10
View File
@@ -1,11 +1,12 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA MFFVCO90Gx4QdHymLlxU4yjRdJr3JQR1Bvvjz57+D34
xgGfp/ECiRGiWyp8sAjriHvy9b7GsiHQMCKYVHl1CSA
-> ssh-ed25519 DFiohQ A5iL8qI5AcaXUmpplE5J0xQIWAryxM1E82VwWc2DhGo
1dv2EqTkz6Z4HswQOYU5Q79Be0GZvc4HUyyqHWthtPk
-> ssh-ed25519 wU682A qPkbSVvvQKJI0wcw7syZfKl7ZxTQpZdCwyT4CwjnbFw
eT/8Z3Ei3/1oi0MEuqiBN/e9Rg8r3zRlUm3vtigl8cE
--- 3hc/9smt2J3sIJLMDC/QByjE8bqlcNjR7FE9NBgOQp8
¾>IÑY
7wgö¼
;ÏÇyî,øpå43@B3F;k(¼\ÊN&àÔÿÑÇý4Q•8Iúž‡MÿÁg!ÃîÜÚ£³
-> ssh-ed25519 osOCZA 9wZ3G4vjwJhYungj/utZ/jgnQRD7qGHsMXM51gNFLyY
SvdeK7R1AxveXXFJng21JK1fy+y7lh6OINB4CtUdS1Q
-> ssh-ed25519 DFiohQ 1NIsoZWR4fY+bcROkw7iq+X0cYIE9g5IiWOqO0FvymQ
igfAuxzfUSlhE3jaTMjqCYeF8ccKVyuUW+uD8JdH75c
-> ssh-ed25519 wU682A g5y8TFpeJ0myejb8r7gL96JBk/q21KlDOBE6ZpCqv2A
I/3aFKq2ne3gVeg+/1LmlKoDyg723yyjUdVdzgFzhV4
--- JsRdNjJ285V+RGktIxJv29Alef95kpB2TOnYH66Wr4Q
z¿
n
´²xÇ‘␍Û¸"‹KA›x)ñÑ8 é…
bçµ.)9„è#Š?ØxÔfMW/<Òîy• ÔHÜçݶOøš
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA xdLLgxnXF6YwP6PA158Jk5PLd9DqMdD7iWzgxcig5ks
mmjqlmU/6MyTaLUD63nyubvr+62SWik0a5wnlhuLQXY
-> ssh-ed25519 DFiohQ BbxdetgH/z1ZUmzdKYLYFbSPx9y6vhqwZmMwlwRuCWM
IpNVv3/EtML29RT9Ca8zrWM8gnGfk2iweW5RnXrTm94
-> ssh-ed25519 PT7ffg f2BPj7TYxAnf1Os/VgMmxQ8g7DIhMdohUUe5y97WK1A
zmNAbvgrV3tYQ8ZylZZ2P8v5FW7kklQt7XIkPqeIY9k
--- 5RXgn5pqVJqKRAiZ9aA5uVL83P7fJHAs98R/iUC3tLs
Ÿòb÷q§ÇA‰3ÇÒDâ”&kâK±Lï‰,û§|¤(Àn8·žH‚'Q-µùJ$”ãÐZ°ÒïÓµ␍²ûÀ¾ b;ˆ¾ññ Ñt®ç?E ÿ.±Ï=š’E`åy£ÀcOËAcÍUt¨A.Úºýsº57¢²—CÖMÖemš‡÷˜ö ô
-> ssh-ed25519 osOCZA rqLqt3IrvBiIXneOWHFLJ3sBz3+dmNsl8LO6whM3Y2s
5MSFsCQoaF97Ve6ossfYA+JezYy5Rod3Se4/y1kl7Go
-> ssh-ed25519 DFiohQ 33r7cd+b7CKR2rVcPFlRIVwcLfHrEgXgD/bD//86IG4
Jg/Nripa9RCtbXaS+1vHBwVBG2q3VMi56lnhEnZvqmo
-> ssh-ed25519 PT7ffg uEatAaPQspVG41/O6d1oTkvhZUn2Au1TeJOlxBaVsXA
0IATY8BEgVuVpYneH12F59Y+wIxnTh4QOTTajbQmWgY
--- 22zYI0N7UXX09jMLZ6FXgXaeijamAiKWVSv02kF/HKY
9$éN[”mÞŽò–ñN’fîq)`qgúÐÃnÏÅ?˜ Ô1’«õs,´ü°u(x…µf Ŷ¨é›[\‘£vø:ÉákµòƒB1½ƒlš+5ß[Ûò莠 ¹5ðž<æió1gÿ!ÆEµ8SŠ ¶xü.kÔaÒƒNåáãÍÁ‹ìÿN­
+8 -9
View File
@@ -1,10 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA kxEAkm22oi+LCgW2w7T1GtuqBxz4m9aU4ceOf+0KInw
oJ43lhfBnq03g4OWyIclsoyUjJM4IRXQcN+H8xUfOzs
-> ssh-ed25519 DFiohQ UcmlGnwNYUjALCKI/90wC96JaeBU5NwX742yZ4JBpXQ
l8Wwh7zdA6Ww/CSqI0UffuuL6/oD3nW5/ZcDm62a2b8
-> ssh-ed25519 PT7ffg ViIZQL9IEiMKxsYkZSewoQlEtHCS2K66HWs22dCtsHM
LkZwHCQWPP11iacPOC2HtGq6qpp+5owzSulTzGpDhzg
--- gsZQ6BVOUwRjzTAED6JZHNo/Vz+m0wgN1VrCbJwS3Vo
·‘ù
{V-ó/Ûñ]Ã"•)œ¹×ÕA'YÔ6>ؾíUý␍eÎp³¦<¬±úɺ@ ŽpèwHÒ©e­ß蟖ñNןótsæq[Cí-£°õ{‘p 2ޝ“Ä»7ÀFÇZÔWYŠ1ïéû¯ž•£9jQS/9'A £8•f×þuª
-> ssh-ed25519 osOCZA 5eUNORBIp4Nm9KJYBGsirBGNmJQTw2jjUW6qNzZZ+XU
kEnw58503E8dVhigC/gq5LdsMCf/cQ6JE7qDVP04Gf4
-> ssh-ed25519 DFiohQ IGSUfX4LH1HkCI+Na95AK4uoDi9sribs4ViL4B2Xo2Q
X/BG7S7fU5b9K0tgeGJxLaeFUljaMCt8+y7+67M2zH0
-> ssh-ed25519 PT7ffg OlYiDqfh3auTa8QvOmK3RMmCu3rCnrqxh2tyCwkQcRg
/KFTAcZaTgq5Obq+27dTmI1F3gXxuW2leoUeLB+Xptg
--- 2GuL6Gd+bx/V7KoFwa00YxEFS/WRoKCjYP89i9RoYOY
„Öe44IÕ»3¦Ô¹jóB½”G€9VýïìX¶Ò-ñ[£/… ãà0E®Á8ºøð­8lTF™â¡DCép MßÂNòN¬ªKžP‡ö¦[s{vúØ3QˆùƒöhÇiàÍ0™ç6Š$ÓÍrx/?ÉeØ“áM@˜2‘FRÖ2‰‚˜ÿ‘AB³+sÈÒ
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-9
View File
@@ -1,9 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA M9L73b+MHSMCFYxR4e46y8BC6nId6H60+CFSRitUuDU
6Al0NVlujTdIrujXBWrjXRq06WhEtxCBwrxLpDmqUcY
-> ssh-ed25519 DFiohQ 8LsAZ+hNANvGQxoSD0Dxix4H7blGLruwUG3WiTLyaGw
0AyCxIEa8WcwThEt7IIrl3OEo9DdcqwR1BK4HxB4xBQ
-> ssh-ed25519 PT7ffg 2CZY6UYI+8SWwXAAtcLJQM4vNmTnKuLWUqxduk3KhgM
bKqTlHfSSSoLJRve93osz/aiJQ0dYqdLnDuAR8Hxcqs
--- PyiJQa1AZlZ1ukWSykQ07tn4wDZ2pmGr5EKRwCQ46cA
–àèW(^>‡æ$:3‡o†+d¨Qdјò…k´"1LW³ËÚSGí•/­û¥qf÷òš +E_ÀÑAÄ,”'qœ]ÕÚZw“Á
+1 -8
View File
@@ -9,7 +9,7 @@ let
apu = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICAZkIuXtpP9a9bHkBl+MJI//q3ClMqzx03Rd/Xe4rjc";
freun-dev = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEvCSjIjipog1Xf9mPc683r5VSGSjVc8v1UZg5VrbbxM";
radish = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQ0fy4n3yyD64+g55eZazeI5g9FurJnlC6fRiOXbbks";
turny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM3To9DeTGzwiQN+uqNWZXKzGG1BJnLvYRgLm0/iI92Y";
turny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIODeWhDvzDGyTGkCoxay80NtgU2OVPL37qXjbhJP7oPJ";
in
{
"gotosocial.age".publicKeys = users ++ [ freun-dev ];
@@ -46,11 +46,4 @@ in
"gitea-actions-runner.age".publicKeys = users ++ [ freun-dev ];
"invidious-companion.age".publicKeys = users ++ [ apu ];
"invidious.age".publicKeys = users ++ [ freun-dev ];
"open-webui.age".publicKeys = users ++ [ freun-dev ];
"open-terminal-api-key.age".publicKeys = users ++ [ freun-dev ];
"searx.age".publicKeys = users ++ [ freun-dev ];
"grafana/secret-key.age".publicKeys = users ++ [ freun-dev ];
"everii-vpn/de1.key.age".publicKeys = users ++ [ radish ];
"everii-vpn/ch1.key.age".publicKeys = users ++ [ radish ];
"everii-vpn/fi1.key.age".publicKeys = users ++ [ radish ];
}
Binary file not shown.
+8 -9
View File
@@ -1,10 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA PQtiqZbb169B6Ovwy86AExmBbrVteTQOFbjnDGJq3UM
LFCbg+9gFnt4p5xHGHrfJXMJRjSGPPmpXd4pEIFtp2A
-> ssh-ed25519 DFiohQ RqRirsYRaJ3RRLzvQIOohbF3uX6/5axomxhrO/SAjzA
jUMEf1YYetb1JWYzeWT4cyT81O36KNm0fN8I5rmuRrE
-> ssh-ed25519 PT7ffg ISuGZoBIQVdZv/17EKV+MNQHvqSTcj4ChLGvGew4KGQ
QzjajsmaW+6CQc28cYAOuCLCwFY3c9qP0/uSa4z9yPM
--- aDY1WIHOhshB9Yfv6tZ9Xip7Ms+88v4D++tGiSSI1A4
mÎÔr¼&³A@j×
B¯_W½r»1°½RúÆôv|Ð|ú|·Õd-TD¤VÝ&ÎãƒÍêɧˆøá3sÓŸ(S#*åF¾
-> ssh-ed25519 osOCZA PDFhOJbsfxCd5u8kqaeCMyFkeAYwRZriF1/OaF/4NU4
4D63DStz2EiqVqKs75v2WqZb5yFJC6dyNw9g2Ew7flA
-> ssh-ed25519 DFiohQ LedJzIxbcY0X/avsMfMmpFf+bzobyqVQbCJq+EzWiwg
CNJIzwEETb9fkcnwUD6wSlhVEgXsZCX2vceHVWHC1lA
-> ssh-ed25519 PT7ffg DdwjbqndzOwW9T8KR3HAWuL11UaGLwBLUl9Xistu8WY
gBJSXMjqYsebwey4b0gT1Xv0FSsw6cLuQ09fOuOaDJQ
--- mcc4EdBkqiYh1L9QQV3wTQCnZDdxqCOLzkp4eNu3TDQ
!%ÛH0cSô¨ôÀÈ;¥èÙœCmk…<f¥¶–_Yð|ù␍ª72S°Êõ¹ñ€±–ujŒ E.¸„Zƒ‚Z„¤Øö]×0–T
Binary file not shown.
Binary file not shown.
+10 -8
View File
@@ -1,9 +1,11 @@
age-encryption.org/v1
-> ssh-ed25519 osOCZA Ep20gMNYxJVHQMDKMp/TnHVy3Zmj/iwWtBfces/6AzQ
fraHe346gWGZ18nupF+XFFT9aztAJ09HbP6v9MjutLA
-> ssh-ed25519 DFiohQ UTM2LV9aG34x0hwvjYmY4HeYNUkZIxWbs/VX5/5W/WE
5pO0tJeI+MIs+MTR90wpHrzPLtJAKQ0VXhF+hAel53M
-> ssh-ed25519 ao8+sg 7fEP6GCczoek0RFPWoq06TbGnTJImS71p/otSAWR7hA
+QkTcWOBuhQSKFH+eiImuTsk9SGZ2dnU2ZhQS63Ux8A
--- gGwSfyEQWYSupnY3B4g8fwu9Pp2PpfPNPQ/mvtEpD8E
ÆvQ6Èôæ‰ãú>“^¥N"Æôÿzdùº&Xì÷u<È:üÔ%»u|>P¾­¨Èì.—aD×VÖ2LK=øz¥¸%ÁÜÎõ¤¦[Á!ïæ¾êT BÕ`7x/Ñd¦9ïN¤«'ïP
-> ssh-ed25519 osOCZA 10afz8Gsoaz0OhJcU2my2bpsgAWNdMGN9FNo4jWgVm0
yO9aFe0wbyp/5ZJeo3oK68FV3+C9wmp4o+PeMoRQ9qQ
-> ssh-ed25519 DFiohQ 4VXaRz0iCATfp06eq1hnqKpd45jEGGeD1yMXuusKkTc
eLQaUgEddJqyFwv8FZY9UufA5htvgYsNOvpNUWkVY/U
-> ssh-ed25519 dLdKaA Mmr7xPZNZpRToQJmXKTJCT1SFNMeqDj8exP3p72iSEI
ZPlltf6OR2yezsu1m58dx2H1Vr+2Gkh8ZYVEb508Zk8
--- qSi+xziv//Wk/qkyYLkLMFqouti465SB8HxwsjZVmHk
£žöwG<ýEaA|ÂâÉÛlv>nî²Î &(
!øE3ÒñbB»ä6ï¥nFð
Ú„B´â ª sÑC,ÃÀÑd‡ê”Úú§ˆ-“Þí¥ÁltÉ^ê†iºx‚¢§¸è©$²uô1
-497
View File
@@ -1,497 +0,0 @@
# Abdominal pain and tingling diagnosis
**Session ID:** ses_0fd155dc9ffew6VgVF3JAGjbxc
**Created:** 6/26/2026, 10:52:39 AM
**Updated:** 6/26/2026, 11:08:58 AM
---
## User
I describe a health issue and maybe you can try to make educated guesses what it might be. Research online if it helps.
Long enduring (months) very mild pain in the abdomen area. Feels a bit like the pain would be in the muscles. It's usually localized to one area but the area might be different on another day. At a later stage the symptoms included a feeling of stuffedness even though the person hasn't eaten much. Stool consistency and color normal. Latest symptom: A tingling feeling in the penis, a bit like it would be numb after being cut off circulation. Blood test results:
P-Kreatiniini: 75 umol/l
P-Alaniiniaminotransferaasi: 31 U/l
P-C-reaktiivinen proteiini: <4 mg/l
B-Leukosyytit: 6.0 E9/l
B-Erytrosyytit: 5.37 E12/l
B-Hemoglobiini: 156 g/l
E-Erytrosyytit, keskitilavuus: 86 fl
E-Punasolujen kokojakauma: 12%
E-Hemoglobiini, keskimassa: 29 pg
E-Hemoglobiini, keskimassakonsentraatio: 339 g/l
B-Trombosyytit: 267 E9/l
Pt-Glomerulussuodatusnopeus, estimoitu: 111 ml/min/1.7
---
## Assistant
I'll research some of these symptoms to give you better-informed guesses. Let me look up a few things.
**Important disclaimer:** I'm a coding assistant, not a doctor. This is for informational purposes only — please see a healthcare professional.
Let me analyze the blood work first and research the symptom combination.
**Tool: webfetch**
**Tool: webfetch**
---
## Assistant
Let me try some other sources for research.
**Tool: webfetch**
**Tool: webfetch**
**Tool: webfetch**
---
## Assistant
Let me search for a couple more conditions to round out my analysis.
**Tool: webfetch**
**Tool: webfetch**
**Tool: webfetch**
---
## Assistant
Now let me search for one more condition that might be relevant.
**Tool: webfetch**
---
## Assistant
Here's my comprehensive analysis. **Disclaimer: I'm not a doctor — this is for informational purposes only. Please consult a healthcare professional.**
---
## Blood Work Analysis
**All values are within normal ranges:**
| Test | Value | Reference (male) | Status |
|------|-------|-------------------|--------|
| Creatinine | 75 µmol/l | 60-100 | Normal |
| ALT | 31 U/l | <45 | Normal |
| CRP | <4 mg/l | <5 | Normal (no inflammation) |
| WBC | 6.0 E9/l | 3.4-8.5 | Normal (no infection) |
| RBC | 5.37 E12/l | 4.2-5.7 | Normal |
| Hemoglobin | 156 g/l | 134-167 | Normal (no anemia) |
| MCV | 86 fl | 82-98 | Normal |
| RDW | 12% | 11.5-14.5% | Normal |
| MCH | 29 pg | 27-33 | Normal |
| MCHC | 339 g/l | 320-360 | Normal |
| Platelets | 267 E9/l | 150-360 | Normal |
| eGFR | 111 ml/min/1.73m² | >90 | Excellent |
The completely normal panel **rules out** systemic infection, significant inflammation, anemia, liver dysfunction, and kidney dysfunction. This actually narrows things down considerably — it points toward functional, neurological, or musculoskeletal causes rather than inflammatory/infectious ones.
**Notably NOT tested** (worth requesting):
- **Fasting glucose / HbA1c** — diabetes/pre-diabetes can cause neuropathy *and* gastroparesis (early satiety)
- **PSA** — prostate issues
- **TSH** — thyroid dysfunction
- **Vitamin B12** — deficiency causes neuropathy
- **Urinalysis** — urinary tract issues
---
## Ranked Differential Diagnoses
### 1. Chronic Pelvic Pain Syndrome (CP/CPPS) — *Best overall fit*
This is my top candidate. Also called chronic nonbacterial prostatitis (NIH Category III). Affects ~2-6% of men, peak age 35-45.
**Why it fits almost everything:**
- **Mild, migrating abdominal pain** — Wikipedia lists "abdominal pain" as a symptom; pain can be in perineum, testicles, tip of penis, pubic/bladder area, and can radiate/wax and wane
- **"Feels muscular"** — A key mechanism is **pelvic floor dysfunction with myofascial trigger points** — chronic unconscious contraction of pelvic floor muscles. The pain literally *is* muscular
- **Migrating location** — Different trigger points in different muscle groups on different days explains why the pain area changes
- **Penile tingling/numbness** — Wikipedia explicitly mentions "pain can be present in... tip of penis" and "constant burning pain in the penis"; the UPOINT classification system includes a Neurologic category
- **Early satiety** — CP/CPPS is strongly associated with **IBS** (gut-bladder-brain axis); the MAPP Research Network specifically groups these conditions together
- **Normal stool** — CP/CPPS itself doesn't typically affect stool consistency
- **Normal blood work** — Characteristic; no infection found, no systemic inflammation
- **Progressive symptoms** — The condition can worsen/expand over time, which matches the progression you describe (abdomen → fullness → penile tingling)
**Key quote from Wikipedia:** *"Symptoms may wax and wane. Pain can range from mild to debilitating. Pain may radiate to the back and rectum... Pain can be present in the perineum, testicles, tip of penis, pubic or bladder area... abdominal pain, constant burning pain in the penis..."*
**Treatment direction:** Pelvic floor physical therapy (trigger point release), stress management, possibly alpha-blockers or quercetin. Not antibiotics (unless clear infection evidence).
---
### 2. Pudendal Neuralgia / Nerve Entrapment — *Best fit for penile symptoms*
Compression or irritation of the pudendal nerve (S2-S4 nerve roots). Relatively uncommon but often underdiagnosed.
**Why it fits:**
- **Penile tingling/numbness** — Wikipedia explicitly states: *"numbness of the penis"* and *"paresthesia (tingling / pins and needles)"* are characteristic symptoms. The dorsal nerve of the penis is a terminal branch of the pudendal nerve
- **Lower abdominal pain** — Pain can be perceived in the perineum, genitals, suprapubic region, and lower abdomen
- **Gradual onset** — *"The onset of pain symptoms is usually gradual without any single causative event"*
- **Normal blood work** — No systemic markers expected
- **Risk factor: prolonged sitting** — Wikipedia specifically lists **"programmers, office workers... drivers"** as at-risk. If you sit at a computer for long periods, this is highly relevant
**Why it's not #1:**
- Pain is typically **worsened by sitting and relieved by standing/toilet seat** — you didn't mention this pattern (but also didn't say it's absent)
- The **migrating** nature of the pain is somewhat atypical (pudendal pain is usually more fixed to one side/area)
- **Early satiety** is not well explained by pudendal nerve issues alone
- Pain character is usually more neuropathic (burning, electric shock-like) rather than "muscular"
**Diagnostic criteria (Nantes):** Pain in pudendal nerve territory, worsened by sitting, relieved by standing/lying, no objective sensory loss, positive pudendal nerve block.
---
### 3. Pelvic Floor Dysfunction / Myofascial Pain Syndrome — *Best fit for "muscular" feeling*
This is actually a key mechanism *behind* CP/CPPS, but can also exist independently.
**Why it fits:**
- **"Feels like muscle pain"** — This *is* muscle pain; chronic tension in pelvic floor muscles (levator ani, coccygeus, piriformis) creates trigger points
- **Migrating pain** — Different muscles/trigger points on different days
- **Referred pain to abdomen and genitals** — Pelvic floor trigger points refer pain to the lower abdomen, groin, and penis
- **Fullness/stuffedness** — Pelvic floor tension can affect bowel function and create a sensation of pelvic/abdominal fullness
- **Penile tingling** — Nerve compression from tense muscles can cause paresthesia
- **Normal blood work** — Expected
This often co-occurs with CP/CPPS and pudendal neuralgia — it may be the common underlying mechanism.
---
### 4. Irritable Bowel Syndrome (IBS) + Overlapping Pelvic Pain
**Why it partially fits:**
- **Migrating abdominal pain** — Classic IBS symptom
- **Early satiety/bloating** — Common in IBS
- **Normal blood work** — Characteristic
- **Comorbid with CP/CPPS** — Wikipedia on CP/CPPS: *"Men with CP/CPPS are more likely than the general population to have... irritable bowel syndrome (IBS)"*
**Why it's not higher:**
- **Normal stool** — Atypical for IBS, which usually involves changes in bowel habits (diarrhea/constipation/mixed). You specifically noted stool consistency and color are normal
- **Doesn't explain penile tingling** — IBS alone cannot cause this. IBS Wikipedia article mentions ~1/3 of IBS patients report sexual dysfunction (reduced libido), but not genital numbness/tingling
- The "muscular" quality of pain is less typical for IBS (which is usually described as crampy/visceral)
However, IBS could be a **co-existing condition** alongside a pelvic floor/nerve issue, explaining the GI symptoms while the pelvic floor issue explains the penile symptoms.
---
### 5. Functional Dyspepsia (Postprandial Distress Syndrome subtype)
**Why it partially fits:**
- **Early satiety** — This is the **hallmark symptom** of the PDS subtype. Wikipedia: *"troublesome early satiation"* is a diagnostic criterion
- **Epigastric pain/discomfort** — Matches abdominal discomfort
- **Normal blood work** — Characteristic
- **Chronic, sporadic** — Matches the months-long duration with varying intensity
**Why it's not higher:**
- Doesn't explain **migrating** pain (FD pain is typically epigastric)
- Doesn't explain **penile tingling** at all
- Doesn't explain the **"muscular"** quality
This could be a **separate co-existing condition** — one condition explaining the GI symptoms (functional dyspepsia) and another explaining the pelvic/penile symptoms (CP/CPPS or pudendal neuralgia).
---
### 6. Small Fiber Neuropathy — *Interesting unifying possibility*
Damage to small unmyelinated nerve fibers. Can be "patchy" and migrating.
**Why it's interesting:**
- **Tingling/numbness** — Characteristic symptom (penile tingling fits)
- **Migrating/patchy pain** — Non-length-dependent variant can affect many areas sporadically
- **Autonomic involvement** — If autonomic fibers are affected, could explain early satiety (gastroparesis-like) and possibly bowel/bladder issues
- **Normal routine blood work** — Standard panels don't detect it; requires skin biopsy for diagnosis
**Why it's lower:**
- Pain is usually described as **burning/stabbing**, not "mild muscular pain"
- Usually starts in **feet/hands** (length-dependent pattern), though non-length-dependent variants exist
- Common causes include **diabetes/pre-diabetes** — and glucose was **not tested** in this panel
- Would be a less common explanation for this symptom cluster
**If suspected:** Request fasting glucose, HbA1c, vitamin B12, and possibly a skin punch biopsy for intraepidermal nerve fiber density.
---
### 7. Lumbosacral Radiculopathy (Nerve Root Irritation)
**Why it's possible:**
- **Migrating pain** — Different nerve roots irritated on different days
- **Penile tingling** — Pudendal nerve roots (S2-S4) in the sacral spine
- **Abdominal wall pain** — Referred pain from thoracic/lumbar nerve roots
- **Normal blood work** — Expected
**Why it's lower:**
- Usually accompanied by **back pain** (not mentioned)
- Early satiety not well explained
- Would need MRI of lumbar/sacral spine to diagnose
- The "muscular" quality is less typical (radicular pain is usually sharp/shooting)
---
### 8. Abdominal Cutaneous Nerve Entrapment Syndrome (ACNES)
**Why it partially fits:**
- **Localized abdominal wall pain** that feels superficial/muscular
- Can change locations (different nerve branches)
- Normal blood work
- Often missed by doctors
**Why it's low:**
- Doesn't explain **penile tingling**
- Doesn't explain **early satiety**
- Only explains the abdominal pain component
---
### 9. Occult Inguinal Hernia
**Why it's possible:**
- Localized pain that can vary
- Can affect nerves (genital branch of genitofemoral nerve) → penile tingling
- May cause fullness sensation
- Normal blood work
**Why it's low:**
- Migrating pain is atypical
- Usually more groin-specific pain
- Early satiety not well explained
- Would need physical exam/ultrasound to detect
---
## Summary & Synthesis
The most coherent single explanation is **CP/CPPS with pelvic floor dysfunction**, possibly with overlapping **IBS or functional dyspepsia** explaining the early satiety. The symptom progression (abdominal pain → fullness → penile tingling) fits a pelvic floor/neurological condition that's gradually affecting more areas.
The **completely normal blood work** is reassuring — it rules out infection, inflammation, and organ dysfunction. This strongly points toward a **functional/neurological/musculoskeletal** cause rather than a structural or inflammatory disease.
**Key questions to consider:**
1. Is the pain **worsened by sitting** and relieved by standing? (→ pudendal neuralgia)
2. Do you sit for prolonged periods? (→ both pudendal and pelvic floor issues)
3. Has blood glucose ever been checked? (→ neuropathy/gastroparesis possibility)
4. Any urinary symptoms? (frequency, urgency, pain with urination?) (→ CP/CPPS)
5. Any lower back pain? (→ radiculopathy)
**Suggested next steps with a doctor:**
- **Urology referral** — for prostate/pelvic floor evaluation (CP/CPPS assessment)
- **Fasting glucose / HbA1c** — rule out diabetes/pre-diabetes
- **Pelvic floor physical therapy evaluation** — assess for trigger points and muscle tension
- Consider **abdominal/pelvic ultrasound** to rule out structural causes
- **PSA** if age-appropriate
- **Vitamin B12** — deficiency can cause neuropathy
The good news is that the normal blood work and absence of red flag symptoms (no weight loss, no bleeding, no anemia, normal stool) make serious organic disease unlikely.
---
## User
The patient rides a bike 60 minutes a day at least and reports of numbness in the penis after unmounting the bicycle some times if the saddle is in a bad position. Could this be a contributing factor or change your hypothesis?
---
## Assistant
This is a **major** piece of information that significantly strengthens the pudendal neuralgia/entrapment hypothesis and likely makes it the primary diagnosis.
## Why This Changes Things
The Wikipedia article on pudendal nerve entrapment literally has a section called **"In cyclists"** and lists the condition's alternate name as **"cyclist syndrome"**:
> *"In male competitive cyclists, it is often called 'cyclist syndrome'. This is a rare condition in which recurrent numbness of the penis and scrotum develops after prolonged cycling."*
> *"Nerve entrapment syndromes, presenting as genital numbness, are amongst the most common bicycling associated urogenital problems."*
> *"Pudendal nerve entrapment can develop in cyclists; likely due to both the compression and stretching of the pudendal nerve for prolonged time."*
The patient is **already experiencing the acute version** of this — temporary penile numbness after cycling with a bad saddle position. That's the pudendal nerve being compressed against the saddle/ischial rami through Alcock's canal. Over months of daily 60-minute rides, this repeated compression can progress to **chronic pudendal neuralgia** — which explains the symptom progression.
## Revised Hypothesis
**Pudendal nerve compression/neuralgia from cycling** is now the leading diagnosis, with **secondary pelvic floor dysfunction** explaining the muscular-quality migrating abdominal pain:
| Symptom | Explanation via Pudendal Neuralgia |
|---------|-------------------------------------|
| Penile tingling/numbness | **Direct** — dorsal nerve of penis is terminal branch of pudendal nerve; already demonstrably compressed by cycling |
| Migrating "muscular" abdominal pain | **Secondary** — chronic nerve irritation → pelvic floor muscle tension → trigger points in different muscle groups on different days |
| Early satiety/stuffedness | Pelvic floor tension affecting bowel function; pudendal nerve has autonomic fibers; possible overlap with IBS (common comorbidity) |
| Progressive course | Cumulative nerve damage from repeated daily compression |
| Normal blood work | Expected — nerve compression doesn't show in standard panels |
| Migrating location | Different nerve branches/muscle groups irritated on different days |
## What Doesn't Quite Fit Pudendal Neuralgia Alone
- **Migrating pain** — pudendal pain is usually more fixed (perineum/genitals/suprapubic), though central sensitization can spread it
- **Early satiety** — not a classic pudendal symptom, though pelvic floor secondary effects could explain it
- **Mild pain** — pudendal neuralgia is usually more severe/burning, but early-stage or mild compression could present this way
This is likely **pudendal nerve compression (from cycling) + secondary pelvic floor dysfunction**, possibly with an overlapping functional GI component (IBS/functional dyspepsia) explaining the satiety — the MAPP Network research groups all these as related urologic chronic pelvic pain syndromes (UCPPS).
## Practical Recommendations
**Immediate (before seeing a doctor):**
- **Saddle change** — get a saddle with a perineal cutout or a no-nose saddle (removes pressure from the pudendal canal area)
- **Saddle tilt** — tilt the nose slightly down to shift weight to the sit bones (ischial tuberosities) instead of the perineum
- **Saddle height/position** — have a bike shop do a proper fit; too high = more perineal pressure
- **Stand regularly** — get out of the saddle every 10-15 minutes during rides
- **Consider temporarily reducing** cycling frequency to see if symptoms improve
**Medical next steps:**
- **Urologist or pelvic pain specialist** — specifically one familiar with pudendal neuralgia / cyclist syndrome
- Mention the cycling and the post-ride numbness explicitly — this is the key diagnostic clue
- A **diagnostic pudendal nerve block** (local anesthetic injection) can confirm the diagnosis — if pain temporarily resolves, it's pudendal
- **Pelvic floor physical therapy** — to address secondary muscle tension/trigger points
- Still worth checking **fasting glucose/HbA1c** to rule out diabetic neuropathy as a cofactor
The encouraging part: if caught before permanent nerve damage, pudendal neuralgia from cycling often improves with conservative measures (saddle change, positioning, PT). The fact that the numbness is already position-dependent suggests the nerve isn't permanently damaged yet — it's still a dynamic compression issue.
---
## User
Any questions you'd ask the patient?
---
## Assistant
Here are the questions I'd ask, grouped by what they'd help distinguish:
## Pudendal Neuralgia vs. CP/CPPS vs. Other
1. **Is the pain worsened by sitting, and relieved by standing up or sitting on a toilet seat?** — The single most diagnostic question for pudendal nerve entrapment (Nantes criteria). If yes → strongly pudendal. If no → more likely CP/CPPS or pelvic floor dysfunction.
2. **Is the pain always on the same side, or does it switch sides?** — Pudendal neuralgia is usually unilateral or bilateral but fixed. Migrating sides suggests pelvic floor trigger points or a more systemic cause.
3. **Does the pain wake you up at night?** — Pudendal neuralgia typically does *not* wake patients at night (pain is position-dependent). Night pain that disrupts sleep would suggest other causes (inflammatory, neoplastic).
## Prostate / Urinary (CP/CPPS Assessment)
4. **Any urinary symptoms?** — Frequency, urgency, hesitancy, weak stream, pain with urination, or waking to urinate at night? These would point toward CP/CPPS or BPH.
5. **Any pain during or after ejaculation?** — A hallmark of CP/CPPS. Wikipedia: *"Post-ejaculatory pain is a hallmark of the condition."*
6. **Any pain in the perineum (area between scrotum and anus), testicles, or rectum?** — Core CP/CPPS and pudendal territories.
## Cycling Details
7. **How long has the post-ride numbness been happening, and has it gotten worse or more frequent over time?** — Progression from temporary numbness to persistent tingling would confirm cumulative nerve damage.
8. **Does the penile tingling persist even on days you don't cycle?** — If yes → the nerve irritation has become chronic/independent of cycling. If it only happens after riding → still purely positional/compression-related.
9. **What type of saddle, and have you tried different ones?** — Narrow racing saddles cause more perineal pressure than wider/cutout saddles.
10. **Do you use padded cycling shorts?** — No chamois = more direct pressure.
## GI / Functional Dyspepsia / IBS
11. **Is the "stuffedness" related to meals, or is it constant regardless of eating?** — Meal-related early satiety → functional dyspepsia. Constant fullness → pelvic floor tension or IBS overlap.
12. **Any nausea, heartburn, or bloating?** — Would support functional dyspepsia or gastroparesis.
13. **Has there been any change in bowel habits at all — even subtle?** — Frequency changes, urgency, or alternating constipation/diarrhea would suggest IBS. The patient says stool is normal, but frequency/urgency weren't specified.
14. **Any unintentional weight loss?** — Red flag for organic disease (malignancy, IBD). The normal blood work makes this unlikely but it's important to confirm.
## Neuropathy / Systemic
15. **Any tingling or numbness anywhere else — feet, hands, legs?** — Would suggest a more generalized small fiber neuropathy (diabetes, B12 deficiency) rather than isolated pudendal compression.
16. **Has blood sugar ever been checked? Any family history of diabetes?** — Pre-diabetes/diabetes is the most common cause of small fiber neuropathy and could also cause gastroparesis (early satiety).
17. **Any history of lower back injury, herniated disc, or sciatica?** — Could point to lumbosacral radiculopathy affecting S2-S4 roots (which include pudendal nerve roots).
## General / Red Flags
18. **How old is the patient?** — CP/CPPS peaks at 35-45. Pudendal neuralgia can occur at any age. Prostate issues (BPH, cancer) become more relevant >45-50.
19. **Any fever, night sweats, or fatigue?** — Would suggest infection or systemic illness (all absent from blood work, but worth confirming clinically).
20. **Has the patient taken any medications, especially PPIs (omeprazole etc.) or NSAIDs, long-term?** — PPIs can cause SIBO → IBS-like symptoms. Chronic NSAIDs can cause gastric issues → functional dyspepsia-like symptoms.
21. **Any prior abdominal surgeries or hernias?** — Could cause adhesions or nerve entrapment.
22. **Stress levels / anxiety?** — Both CP/CPPS and functional dyspepsia are strongly linked to the gut-brain axis and stress; Wikipedia on CP/CPPS: *"anxiety or stress results in chronic, unconscious contraction of the pelvic floor muscles."*
---
The **three highest-yield questions** would be #1 (sitting/standing pattern), #5 (ejaculatory pain), and #15 (tingling elsewhere) — those alone would substantially narrow the differential.
---
## User
1. Unconclusive, 2. not always on the same side. 3. No, 4. No, 5, No, 6. No, 7. Has been happening pretty much since the patient started bike commuting. 8. Since yesterday, yes. 9. It's a pretty comfortable one with suspension (commuter bike). 10. No, 11. Unconclusive, maybe unrelated to eating. 12. No. 13. Sometimes (rarely, maybe once every two weeks) diarrhea or very loose stool, no constipation. 14. No, 15. No, 16. No, but the patient has a blood sugar meter available and could check. 17. No, 18: 38, 19: No, 20: No, 21: No, 22. Very mild if at all.
---
## Assistant
This is very informative. Let me reassess.
## What the Answers Rule Out or Weaken
**CP/CPPS is now significantly less likely.** The absence of its three hallmark features is telling:
- No urinary symptoms (common in CP/CPPS)
- No ejaculatory pain (Wikipedia calls this a *"hallmark of the condition"*)
- No perineal/testicular/rectal pain (core CP/CPPS territory)
- Low stress (stress is a key driver)
**Functional dyspepsia is weaker** — stuffedness may not be meal-related, and there's no nausea, heartburn, or bloating.
**Small fiber neuropathy is much less likely** — no tingling anywhere else, no known diabetes.
**Radiculopathy is unlikely** — no back pain, no other neurological symptoms.
## What Remains Strong
**The critical new finding is answer #8** — tingling now persists on non-cycling days (started yesterday). This suggests the nerve irritation is transitioning from **reversible positional compression** to **chronic neuralgia**. This is a turning point and arguably a reason to act soon.
The picture now looks like **two related cycling overuse problems**:
### Problem 1: Pudendal nerve compression (specifically dorsal nerve of penis)
- Penile numbness after cycling (confirmed, long-standing)
- Now tingling persisting off-bike (new development as of yesterday)
- The dorsal nerve of the penis is a terminal branch of the pudendal nerve that runs along the ischiopubic ramus — directly where a saddle presses
- This could be **Type IV entrapment** (terminal branch, 13% of cases in the Wikipedia article) which would explain why there's no perineal/rectal pain — only the penile branch is affected
- **No padded shorts** (answer #10) is a significant risk factor — more direct perineal pressure every ride
### Problem 2: Pelvic floor / hip flexor muscle tension from cycling
- Migrating "muscular" abdominal pain, not always same side
- Sensation of fullness/stuffedness unrelated to meals
- Cycling engages hip flexors, adductors, and pelvic floor muscles continuously; chronic tension in these muscles refers pain to the lower abdomen and can create a sensation of pelvic fullness
- Occasional loose stools (every ~2 weeks) could be explained by pelvic floor dysfunction affecting bowel function
These two problems may be **independent consequences of the same activity** rather than one causing the other — simpler and more parsimonious than my earlier CP/CPPS hypothesis.
## Immediate Actions I'd Recommend
**Today:**
1. **Check fasting blood sugar** with the available meter — quick, free, rules out diabetes as a neuropathy cofactor
2. **Stop or significantly reduce cycling** temporarily — the transition to persistent off-bike tingling is a warning sign. Give the nerve a chance to recover before it becomes permanent
**This week:**
3. **Get padded cycling shorts** if continuing to ride — this is the lowest-hanging fruit
4. **Try a no-nose or cutout saddle** — removes perineal pressure entirely
5. **Book a urology appointment** — mention cycling, penile numbness, and that tingling is now persisting off-bike. This is the key clinical detail that should get their attention
**If symptoms don't improve within 2-4 weeks of stopping cycling:**
6. **Pelvic floor physical therapy** — for the muscular abdominal pain and fullness
7. **Consider a pudendal nerve block** (diagnostic) if a specialist suspects neuralgia
## One More Question
**Has the patient ever tried completely stopping cycling for a week or more, and if so, did the symptoms improve?** — This would be the natural experiment that confirms or refutes cycling as the cause. If the abdominal pain and fullness also improve with stopping, it strongly confirms that both problems are cycling-related.
---