2 Commits
Author SHA1 Message Date
Joakim Repomaa c684beed09 debug environment
Build Images / build (push) Failing after 1m41s
Check / check (push) Successful in 3m43s
2026-02-22 17:37:45 +02:00
Joakim Repomaa 04dddc628f fix image build
Build Images / build (push) Failing after 44s
Check / check (push) Successful in 4m0s
2026-02-22 17:20:00 +02:00
90 changed files with 706 additions and 3274 deletions
+21 -10
View File
@@ -14,25 +14,36 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Debug environment
run: |
echo "=== System Info ==="
uname -a
nix --version
echo "=== Memory ==="
free -h || true
echo "=== Disk ==="
df -h /nix /tmp || true
echo "=== Nix Config ==="
nix show-config 2>&1 | head -20 || true
- name: Build aarch64 Image - name: Build aarch64 Image
run: nix build .#dockerImages.aarch64-linux.node --out-link ./image-aarch64.tar.gz --option build-hook "" run: nix build .#dockerImages.aarch64-linux.node --out-link ./image-aarch64.tar.gz --option build-hook ""
- name: Push to Gitea Registry - name: Push to Gitea Registry
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
REGISTRY="${{ github.server_url }}"
REGISTRY="${REGISTRY#https://}"
REGISTRY="${REGISTRY#http://}"
# Push aarch64 image # Push aarch64 image
skopeo copy \ skopeo copy \
--insecure-policy \ --insecure-policy \
--dest-creds "${{ github.repository_owner }}:${{ secrets.REGISTRY_TOKEN }}" \ --dest-creds "${{ github.actor }}:${GITEA_TOKEN}" \
"docker-archive:./image-aarch64.tar.gz" \ "docker-archive:./image-aarch64.tar.gz" \
"docker://${REGISTRY}/${{ github.repository }}/node:latest" "docker://${{ github.server_url }}/${{ github.repository }}/node:latest-arm64"
skopeo copy \ # Create and push manifest for arm64
skopeo manifest create \
--insecure-policy \ --insecure-policy \
--dest-creds "${{ github.repository_owner }}:${{ secrets.REGISTRY_TOKEN }}" \ --dest-creds "${{ github.actor }}:${GITEA_TOKEN}" \
"docker-archive:./image-aarch64.tar.gz" \ "docker://${{ github.server_url }}/${{ github.repository }}/node:latest" \
"docker://${REGISTRY}/${{ github.repository }}/node:latest-arm64" "docker://${{ github.server_url }}/${{ github.repository }}/node:latest-arm64"
-1
View File
@@ -2,4 +2,3 @@
/.direnv /.direnv
/.gcroots /.gcroots
*.qcow2 *.qcow2
hosts/turny/ssh_host_ed25519_key
+7 -7
View File
@@ -6,7 +6,7 @@
"allow_concurrent_runs": true, "allow_concurrent_runs": true,
"reveal": "no_focus", "reveal": "no_focus",
"reveal_target": "dock", "reveal_target": "dock",
"hide": "on_success", "hide": "on_success"
}, },
{ {
"label": "Apply remote", "label": "Apply remote",
@@ -16,26 +16,26 @@
"allow_concurrent_runs": true, "allow_concurrent_runs": true,
"reveal": "no_focus", "reveal": "no_focus",
"reveal_target": "dock", "reveal_target": "dock",
"hide": "on_success", "hide": "on_success"
}, },
{ {
"label": "Apply on freun-dev", "label": "Apply on freun-dev",
"command": "apply", "command": "apply",
"args": ["--on", "freun-dev"], "args": ["--build-on-target", "--on", "freun-dev"],
"use_new_terminal": false, "use_new_terminal": false,
"allow_concurrent_runs": true, "allow_concurrent_runs": true,
"reveal": "no_focus", "reveal": "no_focus",
"reveal_target": "dock", "reveal_target": "dock",
"hide": "on_success", "hide": "on_success"
}, },
{ {
"label": "Apply on apu", "label": "Apply on apu",
"command": "apply", "command": "apply",
"args": ["--on", "apu"], "args": ["--build-on-target", "--on", "apu"],
"use_new_terminal": false, "use_new_terminal": false,
"allow_concurrent_runs": true, "allow_concurrent_runs": true,
"reveal": "no_focus", "reveal": "no_focus",
"reveal_target": "dock", "reveal_target": "dock",
"hide": "on_success", "hide": "on_success"
}, }
] ]
+1 -1
View File
@@ -8,7 +8,7 @@
- `nix build .#nixosConfigurations.<host>.config.system.build.toplevel` - Build specific host - `nix build .#nixosConfigurations.<host>.config.system.build.toplevel` - Build specific host
## Code Style ## 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.*) - **Imports**: List imports first, group by: local modules, external inputs (inputs.*)
- **Function args**: Multi-line with trailing comma when >3 args: `{ config, lib, pkgs, ... }:` - **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 { ... }` - **Options**: Define in `options.<service>` block, implement in `config = lib.mkIf cfg.enable { ... }`
+1 -1
View File
@@ -1,3 +1,3 @@
# My NixOS Configurations # My NixOS Configurations
[![check workflow status](https://git.freun.dev/repomaa/nixos/actions/workflows/check.yml/badge.svg)](https://git.freun.dev/repomaa/nixos/actions?workflow=check.yml) [![build images workflow status](https://git.freun.dev/repomaa/nixos/actions/workflows/build-images.yml/badge.svg)](https://git.freun.dev/repomaa/nixos/actions?workflow=build-images.yml) [![builds.sr.ht status](https://builds.sr.ht/~repomaa/NixOS.svg)](https://builds.sr.ht/~repomaa/NixOS?)
+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
+61 -407
View File
@@ -23,64 +23,6 @@
"type": "github" "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": { "colmena": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
@@ -92,11 +34,11 @@
"stable": "stable" "stable": "stable"
}, },
"locked": { "locked": {
"lastModified": 1781446832, "lastModified": 1762034856,
"narHash": "sha256-/6+NHQstxRqkbNjtNcQgCojzu59SEbdwwpWqBw7Fm9s=", "narHash": "sha256-QVey3iP3UEoiFVXgypyjTvCrsIlA4ecx6Acaz5C8/PQ=",
"owner": "zhaofengli", "owner": "zhaofengli",
"repo": "colmena", "repo": "colmena",
"rev": "938cb5cdd9b25d9774b6168e5f03d0afd04d321f", "rev": "349b035a5027f23d88eeb3bc41085d7ee29f18ed",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -107,11 +49,11 @@
}, },
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1781825982, "lastModified": 1771121070,
"narHash": "sha256-SlXKwIRIhrOSAcTjCB3ftPLzJWZStQIPS7J1FlZPnKk=", "narHash": "sha256-aIlv7FRXF9q70DNJPI237dEDAznSKaXmL5lfK/Id/bI=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "469fd08d0bcf6926321fa973c6777fbc87785dd7", "rev": "a2812c19f1ed2e5ed5ce2ef7109798b575c180e1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -145,11 +87,11 @@
"dnote": { "dnote": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1774496111, "lastModified": 1765604939,
"narHash": "sha256-F7sP/QtZhfoNJX7LAZRmL/MjRByzJFNPUQBNpifSimQ=", "narHash": "sha256-5+grN/dsqRRFzUkz6ksvuEhgi4lYq64Rd0fejqzz8/Y=",
"owner": "dnote", "owner": "dnote",
"repo": "dnote", "repo": "dnote",
"rev": "1610b41e18a814b7aa7f4f784ba283794150853f", "rev": "9fa312e3fc6139788533ca6cd1ada8c16a10519c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -195,11 +137,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1778716662, "lastModified": 1769996383,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -244,27 +186,6 @@
"type": "github" "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": { "flake-utils": {
"locked": { "locked": {
"lastModified": 1659877975, "lastModified": 1659877975,
@@ -336,7 +257,7 @@
}, },
"flake-utils_5": { "flake-utils_5": {
"inputs": { "inputs": {
"systems": "systems_6" "systems": "systems_5"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1731533236,
@@ -465,11 +386,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1779506708, "lastModified": 1770260404,
"narHash": "sha256-QOD/CNm196nCJRheux/URi4/HE66fthdOMqCJoPP1Y0=", "narHash": "sha256-3iVX1+7YUIt23hBx1WZsUllhbmP2EnXrV8tCRbLxHc8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "3ee51fbdac8c8bdfe1e7e1fcaba6520a563f394f", "rev": "0d782ee42c86b196acff08acfbf41bb7d13eed5b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -519,18 +440,16 @@
"lanzaboote": { "lanzaboote": {
"inputs": { "inputs": {
"crane": "crane", "crane": "crane",
"nixpkgs": [ "nixpkgs": "nixpkgs_3",
"nixpkgs"
],
"pre-commit": "pre-commit", "pre-commit": "pre-commit",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1782160136, "lastModified": 1771492583,
"narHash": "sha256-APwmLI4UuSTim0JO/kZ6FXDPiZVp3zIj0uGDF2UZ5kU=", "narHash": "sha256-nQzvnU4BGu8dA6BsPPCqmVcab/3ebVmHtX3ZWbW3Hxc=",
"owner": "nix-community", "owner": "nix-community",
"repo": "lanzaboote", "repo": "lanzaboote",
"rev": "6650fb7c6b48177c8200e21ffac99a4adc72b08d", "rev": "5e9380994665ef66c87ab8e22c913ff837174ce4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -539,121 +458,6 @@
"type": "github" "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": { "nix-github-actions": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -676,17 +480,12 @@
} }
}, },
"nixos-hardware": { "nixos-hardware": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": { "locked": {
"lastModified": 1782379505, "lastModified": 1771423359,
"narHash": "sha256-zPvPiU+a7pqtH47xrtZLNRABJKpOjfZQclDbcvNtH+I=", "narHash": "sha256-yRKJ7gpVmXbX2ZcA8nFi6CMPkJXZGjie2unsiMzj3Ig=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "603d3afd1b6145bd66e97ae38a34d91c95df70cf", "rev": "740a22363033e9f1bb6270fbfb5a9574067af15b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -712,29 +511,13 @@
"type": "github" "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": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1777168982, "lastModified": 1769909678,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", "rev": "72716169fe93074c333e8d0173151350670b824c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -767,29 +550,13 @@
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" "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": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1781577229, "lastModified": 1771369470,
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=", "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", "rev": "0182a361324364ae3f436a63005877674cf45efb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -817,48 +584,33 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1782233679, "lastModified": 1771008912,
"narHash": "sha256-QyuGP5+QOtmXpy4i2X4DhBVBaySBdDKQEhqKcphcp34=", "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "667d5cf1c59585031d743c78b394b0a647537c35", "rev": "a82ccc39b39b621151d6732718e3e250109076fa",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-26.05", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1781577229, "lastModified": 1771419570,
"narHash": "sha256-rcUHdUtJEvMdNEl2Wq+YpHraHKfcer3KsBscpZEF2Yg=", "narHash": "sha256-bxAlQgre3pcQcaRUm/8A0v/X8d2nhfraWSFqVmMcBcU=",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", "owner": "NixOS",
"type": "tarball", "repo": "nixpkgs",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1017464.567a49d1913c/nixexprs.tar.xz" "rev": "6d41bc27aaf7b6a3ba6b169db3bd5d6159cfaa47",
},
"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",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "noctalia-dev", "owner": "NixOS",
"repo": "noctalia", "ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
@@ -891,11 +643,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1781733627, "lastModified": 1770726378,
"narHash": "sha256-U3yTuGBnmXvXoQI3qkpfEDsn9RovQPAjN7ndRco+3u0=", "narHash": "sha256-kck+vIbGOaM/dHea7aTBxdFYpeUl/jHOy5W3eyRvVx8=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "3bbec39bc90eadfa031e6f3b77272f3f60803e39", "rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -916,20 +668,13 @@
"ketchup": "ketchup", "ketchup": "ketchup",
"ksoloti-pr": "ksoloti-pr", "ksoloti-pr": "ksoloti-pr",
"lanzaboote": "lanzaboote", "lanzaboote": "lanzaboote",
"llm-agents": "llm-agents",
"models-dev": "models-dev",
"niri-flake": "niri-flake",
"nix-flatpak": "nix-flatpak",
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_3", "nixpkgs": "nixpkgs_4",
"nixpkgs-25_11": "nixpkgs-25_11",
"nixpkgs-unstable": "nixpkgs-unstable", "nixpkgs-unstable": "nixpkgs-unstable",
"noctalia": "noctalia",
"syntax-renderer": "syntax-renderer", "syntax-renderer": "syntax-renderer",
"tree_hugger": "tree_hugger", "tonearm": "tonearm",
"turny": "turny", "turny": "turny",
"voidauth": "voidauth", "voidauth": "voidauth",
"voxtype": "voxtype",
"workout-sync": "workout-sync" "workout-sync": "workout-sync"
} }
}, },
@@ -941,11 +686,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782012058, "lastModified": 1771125043,
"narHash": "sha256-9mWUnReOUXfKjZuJAL/bAFH3LUyECTRtXgSNVjRw3UY=", "narHash": "sha256-ldf/s49n6rOAxl7pYLJGGS1N/assoHkCOWdEdLyNZkc=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "8534567325bd8a8d2928e6afd81e0a87d19efd3c", "rev": "4912f951a26dc8142b176be2c2ad834319dc06e8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -1061,60 +806,23 @@
"type": "github" "type": "github"
} }
}, },
"systems_6": { "tonearm": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"tree_hugger": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_5",
"nixpkgs": [ "nixpkgs": [
"nixpkgs-unstable"
]
},
"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"
},
"original": {
"type": "git",
"url": "ssh://git@github.com/everii-Group/tree_hugger"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"llm-agents",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1780220602, "lastModified": 1769118914,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", "narHash": "sha256-ASM7MVDWxT+BBtcAJ1z8XNjyNdL5E1Kc0miCWIepf+o=",
"owner": "numtide", "rev": "68ea2190d1b37f9030b7586d2030f9dc48915a85",
"repo": "treefmt-nix", "type": "tarball",
"rev": "db947814a175b7ca6ded66e21383d938df01c227", "url": "https://codeberg.org/api/v1/repos/dergs/Tonearm/archive/68ea2190d1b37f9030b7586d2030f9dc48915a85.tar.gz"
"type": "github"
}, },
"original": { "original": {
"owner": "numtide", "type": "tarball",
"repo": "treefmt-nix", "url": "https://codeberg.org/dergs/Tonearm/archive/v1.0.0.tar.gz"
"type": "github"
} }
}, },
"turny": { "turny": {
@@ -1127,11 +835,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1783250457, "lastModified": 1752874301,
"narHash": "sha256-J7hZuGi7+SJRFeSpWJzzQy9IBH/hU8KokPV5A3rr65c=", "narHash": "sha256-A6IZz46Lfopm5UhMtFfBDimepEUt9lGwhWoEIEQHsgk=",
"owner": "~repomaa", "owner": "~repomaa",
"repo": "turny", "repo": "turny",
"rev": "4b711b8814c91bc053729c5903d1ee924b8ccb8d", "rev": "133c05151e77616c7973c1c1038506b2fdee8eab",
"type": "sourcehut" "type": "sourcehut"
}, },
"original": { "original": {
@@ -1164,27 +872,6 @@
"type": "github" "type": "github"
} }
}, },
"voxtype": {
"inputs": {
"flake-utils": "flake-utils_5",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1782311621,
"narHash": "sha256-K8mog5N+Qv01VyM5n74sOeXCn+2OYgrSZnVDAKQ33zc=",
"owner": "peteonrails",
"repo": "voxtype",
"rev": "31b7f38c4e22c4ce75d6350945729e5db001cb9a",
"type": "github"
},
"original": {
"owner": "peteonrails",
"repo": "voxtype",
"type": "github"
}
},
"workout-sync": { "workout-sync": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -1204,39 +891,6 @@
"repo": "workout-sync", "repo": "workout-sync",
"type": "sourcehut" "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", "root": "root",
+8 -81
View File
@@ -2,20 +2,13 @@
inputs = { inputs = {
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs-25_11.url = "github:NixOS/nixpkgs/nixos-25.11";
gtrackmap = { gtrackmap = {
url = "github:gtrackmap/gtrackmap"; url = "github:gtrackmap/gtrackmap";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
lanzaboote = { lanzaboote.url = "github:nix-community/lanzaboote";
url = "github:nix-community/lanzaboote"; nixos-hardware.url = "github:NixOS/nixos-hardware/master";
inputs.nixpkgs.follows = "nixpkgs";
};
nixos-hardware = {
url = "github:NixOS/nixos-hardware/master";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = { home-manager = {
url = "github:nix-community/home-manager/release-25.11"; url = "github:nix-community/home-manager/release-25.11";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -54,33 +47,15 @@
inputs.flake-parts.follows = "flake-parts"; inputs.flake-parts.follows = "flake-parts";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
tonearm = {
url = "https://codeberg.org/dergs/Tonearm/archive/v1.0.0.tar.gz";
inputs.nixpkgs.follows = "nixpkgs";
};
voidauth = { voidauth = {
url = "github:repomaa/voidauth/feat/nix-packaging"; url = "github:repomaa/voidauth/feat/nix-packaging";
inputs.nixpkgs.follows = "nixpkgs-unstable"; inputs.nixpkgs.follows = "nixpkgs-unstable";
inputs.flake-parts.follows = "flake-parts"; inputs.flake-parts.follows = "flake-parts";
}; };
voxtype = {
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 = outputs =
{ {
@@ -115,6 +90,7 @@
packages = with pkgs; [ packages = with pkgs; [
agenix.packages.${system}.default agenix.packages.${system}.default
colmena.packages.${system}.colmena colmena.packages.${system}.colmena
colmena.packages.${system}.manual
(writeShellScriptBin "build" '' (writeShellScriptBin "build" ''
colmena build --keep-result "$@" colmena build --keep-result "$@"
'') '')
@@ -124,49 +100,6 @@
(writeShellScriptBin "apply-local" '' (writeShellScriptBin "apply-local" ''
nh os switch . 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 +210,4 @@
}; };
} }
); );
nixConfig = {
extra-substituters = [ "https://noctalia.cachix.org" ];
extra-trusted-public-keys = [
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
];
};
} }
+97 -734
View File
@@ -7,31 +7,14 @@
self, 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 = [ imports = [
../gnome ../gnome
./dnote.nix ./dnote.nix
../modules/zed ../modules/zed
../modules/voxtype
../modules/elephant
./secrets.nix ./secrets.nix
inputs.hastebin.nixosModules.hm inputs.hastebin.nixosModules.hm
inputs.agenix.homeManagerModules.default 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 # This value determines the Home Manager release that your configuration is
@@ -43,184 +26,77 @@ in
# release notes. # release notes.
home.stateVersion = "23.11"; # Please read the comment before changing. home.stateVersion = "23.11"; # Please read the comment before changing.
home.packages = home.packages = with pkgs; [
with pkgs; htop
[ gnupg
atool pkgs-unstable.yubioath-flutter
unzip pkgs-unstable.yubikey-manager
htop gcc
gnupg sqlite
pkgs-unstable.yubioath-flutter noto-fonts
pkgs-unstable.yubikey-manager noto-fonts-cjk-sans
gcc noto-fonts-color-emoji
sqlite source-sans-pro
noto-fonts mosh
noto-fonts-cjk-sans docker-compose
noto-fonts-color-emoji signal-desktop
source-sans-pro cargo
mosh blanket
docker-compose wl-clipboard
(signal-desktop.override { gnumeric
commandLineArgs = "--password-store=gnome-libsecret"; gh
}) neovim-remote
cargo pkgs-unstable.gradia
blanket crystal
wl-clipboard shards
gnumeric moreutils
gh keymapp
neovim-remote gnumake
gradia tig
crystal jq
shards yt-dlp
moreutils ffmpeg
keymapp otp
gnumake manix
tig (writeShellScriptBin "fd" ''
jq ${fd}/bin/fd -H "$@"
yt-dlp '')
ffmpeg pkgs-unstable.rpi-imager
otp picocom
manix imagemagick
(writeShellScriptBin "fd" '' ghostscript
${fd}/bin/fd -H "$@" inkscape
'') jless
pkgs-unstable.rpi-imager scribus
picocom dnscontrol
imagemagick protonmail-bridge
ghostscript hydroxide
inkscape imapsync
jless nixfmt-rfc-style
scribus tree
dnscontrol virt-manager
protonmail-bridge pkgs-unstable.ncspot
hydroxide pkgs-unstable.devbox
imapsync pkgs-unstable.feishin
nixfmt openscad
tree pkgs-unstable.walker
virt-manager pkgs-unstable.shairport-sync-airplay2
pkgs-unstable.ncspot (writeShellScriptBin "pw" ''
pkgs-unstable.devbox ${lib.getExe rbw} ls --fields 'id,folder,name' | \
pkgs-unstable.feishin ${lib.getExe gawk} -F '\t' '{print $1 "\t" ($2 == "" ? "" : $2 "/") $3}' | \
openscad ${lib.getExe pkgs-unstable.walker} -d -l 2 | \
pkgs-unstable.shairport-sync-airplay2 xargs ${lib.getExe rbw} get "$@" | ${lib.getExe' wl-clipboard "wl-copy"}
(writeShellScriptBin "pw" '' '')
${lib.getExe rbw} ls --fields 'id,folder,name' | \ (google-fonts.override { fonts = [ "Tajawal" ]; })
${lib.getExe gawk} -F '\t' '{print $1 "\t" ($2 == "" ? "" : $2 "/") $3}' | \ pkgs-unstable.opencode
${lib.getExe config.services.walker.package} -d -l 2 | \ (writeShellScriptBin "nr" ''
xargs ${lib.getExe rbw} get "$@" | ${lib.getExe' wl-clipboard "wl-copy"} ${lib.getExe nix} run "nixpkgs#''${1}" "$@"
'') '')
(google-fonts.override { fonts = [ "Tajawal" ]; }) pkgs-unstable.tidal-hifi
(writeShellScriptBin "nr" '' inputs.tonearm.packages.${pkgs.stdenv.hostPlatform.system}.tonearm
${lib.getExe nix} run "nixpkgs#''${1}" "$@" ];
'')
blueman
pavucontrol
(writeShellScriptBin "voxtoggle" ''
status=$(${lib.getExe config.programs.voxtype.package} status)
pid=$(cat ''${XDG_RUNTIME_DIR}/voxtype/pid)
if [[ "$status" == "stopped" ]]; then
exit 1
elif [[ "$status" == "recording" ]]; then
kill -SIGUSR2 "$pid"
else
kill -SIGUSR1 "$pid"
fi
'')
(writeShellScriptBin "scrot" ''
set -eo pipefail
${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
]);
programs = { programs = {
noctalia = {
enable = true;
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"
];
};
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"; }
];
calendar = {
enabled = true;
account = {
mailbox = {
type = "caldav";
name = "Yhteinen Kalenteri";
provider = "custom";
server_url = "https://dav.mailbox.org/caldav/Y2FsOi8vMC82NA";
};
};
};
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 = { nh = {
enable = true; enable = true;
flake = self; flake = self;
@@ -425,11 +301,6 @@ in
gh pr checkout {{.Form.pr | quote}} gh pr checkout {{.Form.pr | quote}}
''; '';
} }
{
key = "D";
context = "localBranches";
command = "th prune {{.SelectedLocalBranch.Name}}";
}
]; ];
}; };
}; };
@@ -454,6 +325,7 @@ in
}; };
}; };
"apu" = { "apu" = {
hostname = "apu.tempel-vibes.ts.net";
user = "root"; user = "root";
}; };
}; };
@@ -555,50 +427,6 @@ in
enable = true; enable = true;
defaultEditor = true; defaultEditor = true;
}; };
voxtype = {
enable = true;
package = inputs.voxtype.packages.${pkgs.stdenv.hostPlatform.system}.vulkan;
model.name = "large-v3-turbo";
service.enable = true;
settings = {
hotkey.enabled = false;
whisper.language = "auto";
output.notification = {
on_recording_start = false;
on_recording_stop = false;
on_transcription = false;
};
};
postProcessing = {
enable = true;
settings = {
model = "qwen3:4b-instruct";
commonInstructions = "no quotes, no emojis, no explanations";
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";
}
{
title = "Make a title";
instructions = "First identify what language this is, then respond in that language. Create a concise and descriptive title";
}
{
title = "Summarize";
instructions = "First identify what language this is, then respond in that language. Summarize in a few sentences";
}
{
title = "Commit message";
instructions = "Write a concise and descriptive git commit message for this dictation in English";
}
{
title = "Translate to English";
instructions = "Translate this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the translation";
}
];
};
};
};
}; };
gnome = { gnome = {
@@ -746,20 +574,15 @@ in
# #
# /etc/profiles/per-user/jokke/etc/profile.d/hm-session-vars.sh # /etc/profiles/per-user/jokke/etc/profile.d/hm-session-vars.sh
# #
systemd.user.sessionVariables = {
home.sessionVariables = {
NIXOS_OZONE_WL = 1; NIXOS_OZONE_WL = 1;
NVIM_LISTEN_ADDRESS = "$XDG_RUNTIME_DIR/nvimsocket"; NVIM_LISTEN_ADDRESS = "$XDG_RUNTIME_DIR/nvimsocket";
PAGER = "bat --paging=always --style=plain"; PAGER = "bat --paging=always --style=plain";
MANPAGER = "sh -c 'col -bx | bat -l man -p'"; MANPAGER = "sh -c 'col -bx | bat -l man -p'";
MANROFFOPT = "-c"; MANROFFOPT = "-c";
DO_NOT_TRACK = 1; DO_NOT_TRACK = 1;
DOCKER_HOST = "unix://$XDG_RUNTIME_DIR/podman/podman.sock";
}; };
systemd.user.sessionVariables = lib.mapAttrs (_: v: toString v) config.home.sessionVariables;
programs.zsh.sessionVariables = config.home.sessionVariables;
home.shellAliases = { home.shellAliases = {
_ = "sudo"; _ = "sudo";
icr = "crystal i"; icr = "crystal i";
@@ -770,53 +593,12 @@ in
ls = "ls --color=auto"; ls = "ls --color=auto";
}; };
services = { services.gpg-agent = with pkgs; {
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 = {
enable = true; enable = true;
package = pkgs.symlinkJoin { enableSshSupport = true;
inherit (pkgs-unstable.walker) name meta; pinentry.package = pinentry-gnome3;
paths = [ pkgs-unstable.walker ];
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
postBuild = ''
wrapProgram $out/bin/walker \
--prefix PATH : ${lib.makeBinPath [ config.services.elephant.package ]}
'';
};
systemd = {
enable = true;
};
settings = {
providers = {
default = [
"desktopapplications"
"calc"
"websearch"
"bitwarden"
];
};
};
}; };
services.elephant.enable = true;
systemd.user.services.walker.Install.WantedBy = lib.mkForce [ "graphical-session.target" ];
systemd.user.services.shairport-sync = { systemd.user.services.shairport-sync = {
Unit = { Unit = {
Description = "AirPlay audio server"; Description = "AirPlay audio server";
@@ -844,12 +626,11 @@ in
xdg.configFile."opencode/opencode.jsonc".text = builtins.toJSON { xdg.configFile."opencode/opencode.jsonc".text = builtins.toJSON {
"$schema" = "https://opencode.ai/config.json"; "$schema" = "https://opencode.ai/config.json";
model = "fireworks-ai/accounts/fireworks/models/glm-5p2"; model = "opencode/kimi-k2.5";
small_model = "fireworks-ai/accounts/fireworks/models/minimax-m3"; small_model = "opencode/minimax-m2.1";
agent = { agent = {
explore.model = "fireworks-ai/accounts/fireworks/models/minimax-m3"; explore.model = "opencode/minimax-m2.1";
}; };
theme = "system";
lsp = { lsp = {
ruby-lsp = { ruby-lsp = {
initialization = { initialization = {
@@ -884,6 +665,7 @@ in
"bundle" "bundle"
"exec" "exec"
"ruby-lsp" "ruby-lsp"
"--lsp"
]; ];
}; };
rubocop = { rubocop = {
@@ -899,20 +681,27 @@ in
".haml" ".haml"
]; ];
}; };
nil = {
command = [
(lib.getExe pkgs.nil)
];
extensions = [ ".nix" ];
};
ameba-ls = {
command = [
(lib.getExe pkgs-unstable.ameba-ls)
];
extensions = [ ".cr" ];
};
}; };
mcp = { mcp = {
memory = {
type = "local";
command = [
"npx"
"-y"
"@modelcontextprotocol/server-memory"
];
};
browser = {
type = "local";
command = [
"npx"
"-y"
"@agent-infra/mcp-server-browser"
"--headless"
"--executable-path"
(lib.getExe pkgs-unstable.chromium)
];
};
context7 = { context7 = {
type = "remote"; type = "remote";
url = "https://mcp.context7.com/mcp"; url = "https://mcp.context7.com/mcp";
@@ -924,432 +713,6 @@ 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))
);
};
};
gnome.automaticTimeZone = true; gnome.automaticTimeZone = true;
gtk.enable = true; gtk.enable = true;
+2 -1
View File
@@ -14,13 +14,14 @@
ffmpeg ffmpeg
mediainfo mediainfo
git-lfs git-lfs
telegram-desktop
shards shards
deno deno
virt-manager virt-manager
gimp gimp
lftp lftp
inkscape inkscape
wineWow64Packages.waylandFull wineWowPackages.waylandFull
reaper reaper
inputs.ksoloti-pr.legacyPackages.${pkgs.stdenv.hostPlatform.system}.ksoloti inputs.ksoloti-pr.legacyPackages.${pkgs.stdenv.hostPlatform.system}.ksoloti
calibre calibre
+1 -29
View File
@@ -1,18 +1,6 @@
{ { pkgs, pkgs-unstable, ... }:
pkgs,
pkgs-unstable,
lib,
...
}:
let let
homeDirectory = "/home/moco"; 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 in
{ {
# Home Manager needs a bit of information about you and the paths it should # Home Manager needs a bit of information about you and the paths it should
@@ -45,15 +33,6 @@ in
(writeShellScriptBin "moco-reto-otp" '' (writeShellScriptBin "moco-reto-otp" ''
ykman oath accounts code -s 'MOCO Reto' | wl-copy -n 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
]; ];
}; };
@@ -219,11 +198,4 @@ in
user = "butler"; 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";
};
} }
-43
View File
@@ -1,43 +0,0 @@
{
config,
lib,
pkgs,
pkgs-unstable,
...
}:
let
cfg = config.services.elephant;
wrappedPackage = pkgs.symlinkJoin {
inherit (cfg.package) name meta;
paths = [ cfg.package ];
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
postBuild = ''
wrapProgram $out/bin/elephant \
--prefix PATH : ${lib.makeBinPath [ pkgs.bash ]}
'';
};
in
{
options.services.elephant = {
enable = lib.mkEnableOption "Enable Elephant";
package = lib.mkOption {
type = lib.types.package;
default = pkgs-unstable.elephant;
};
};
config = lib.mkIf cfg.enable {
systemd.user.services.elephant = {
Unit = {
Description = "Elephant";
After = [ "niri.service" ];
Wants = [ "niri.service" ];
};
Service = {
ExecStart = lib.getExe wrappedPackage;
Restart = "on-failure";
};
Install.WantedBy = [ "graphical-session.target" ];
};
};
}
-100
View File
@@ -1,100 +0,0 @@
{
config,
osConfig,
lib,
pkgs,
pkgs-unstable,
...
}:
let
cfg = config.programs.voxtype;
postProcessUnwrapped =
pkgs.runCommand "voxtype-post-process-unwrapped"
{
code = ./post-process.cr;
nativeBuildInputs = [
pkgs-unstable.crystal
];
}
''
mkdir -p $out/bin
crystal build $code -o $out/bin/voxtype-post-process
'';
postProcess = pkgs.symlinkJoin {
name = "voxtype-post-process";
paths = [ postProcessUnwrapped ];
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
postBuild = ''
wrapProgram $out/bin/voxtype-post-process \
--set OLLAMA_PORT ${toString osConfig.services.ollama.port} \
--set WALKER_BIN ${lib.getExe config.services.walker.package}
'';
meta.mainProgram = "voxtype-post-process";
};
postProcessingConfig = pkgs.writeText "voxtype-post-processing.json" (
builtins.toJSON cfg.postProcessing.settings
);
in
{
options.programs.voxtype = {
postProcessing = lib.mkOption {
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption "Enable post-processing of transcriptions";
settings = lib.mkOption {
type = lib.types.submodule {
options = {
model = lib.mkOption {
type = lib.types.str;
description = "The ollama model to use for post-processing";
};
commonInstructions = lib.mkOption {
type = lib.types.str;
default = "no quotes, no emojis, no explanations";
description = "Instructions to include in every post-processing prompt";
};
prompts = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
title = lib.mkOption {
type = lib.types.str;
description = "A title for this prompt, used in the selector";
};
instructions = lib.mkOption {
type = lib.types.str;
description = "Instructions to include in the post-processing prompt, in addition to the common instructions";
};
};
}
);
default = [
{
title = "Clean up";
instructions = "Clean up this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the cleaned text";
}
];
};
};
};
default = { };
};
};
};
};
};
config = lib.mkIf cfg.postProcessing.enable {
xdg.configFile."voxtype/post-processing.json".source = postProcessingConfig;
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
];
};
}
-54
View File
@@ -1,54 +0,0 @@
require "json"
require "http/client"
struct OllamaResponse
include JSON::Serializable
getter response : String
end
struct Prompt
include JSON::Serializable
getter title : String
getter instructions : String
end
struct Config
include JSON::Serializable
getter model : String
getter prompts : Array(Prompt)
@[JSON::Field(key: "commonInstructions")]
getter common_instructions : String
end
config_path = "#{ENV.fetch("XDG_CONFIG_HOME", "~/.config")}/voxtype/post-processing.json"
config = File.open(config_path) { |file| Config.from_json(file) }
client = HTTP::Client.new("localhost", ENV.fetch("OLLAMA_PORT", "11434").to_i)
prompt_selection = Process.run(ENV["WALKER_BIN"], ["--dmenu"]) do |process|
config.prompts.each do |prompt|
process.input.puts prompt.title
end
process.input.close
process.output.gets_to_end.chomp
end
instructions = config.prompts.find { |prompt| prompt.title == prompt_selection }.try(&.instructions) || prompt_selection
payload = {
model: config.model,
prompt: "#{instructions} - #{config.common_instructions}:\n\n#{STDIN.gets_to_end.chomp}",
think: false,
stream: false,
}
client.post("/api/generate", body: payload.to_json) do |response|
if response.status_code == 200
puts OllamaResponse.from_json(response.body_io).response.strip
else
abort "Ollama API error: #{response.status_code} #{response.body}"
end
end
+173 -98
View File
@@ -3,70 +3,10 @@
lib, lib,
pkgs, pkgs,
pkgs-unstable, pkgs-unstable,
inputs,
... ...
}: }:
let let
cfg = config.programs.zed-editor; 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 { ameba-ls = pkgs.stdenv.mkDerivation rec {
pname = "ameba-ls"; pname = "ameba-ls";
version = "0.1.0"; version = "0.1.0";
@@ -141,15 +81,8 @@ in
agent = { agent = {
enabled = true; enabled = true;
default_model = { default_model = {
provider = "fireworks-ai"; provider = "openai";
model = "accounts/fireworks/models/glm-5p2"; model = "kimi-k2.5";
};
};
agent_servers = {
OpenCode = {
command = "opencode";
args = [ "acp" ];
type = "custom";
}; };
}; };
auto_update = false; auto_update = false;
@@ -180,7 +113,7 @@ in
}; };
Nix = { Nix = {
formatter.external = { formatter.external = {
command = lib.getExe pkgs.nixfmt; command = lib.getExe pkgs.nixfmt-rfc-style;
arguments = [ "-q" ]; arguments = [ "-q" ];
}; };
}; };
@@ -208,10 +141,6 @@ in
]; ];
}; };
}; };
TypeScript.language_servers = [
"tsgo"
"vtsls"
];
}; };
lsp = with pkgs; { lsp = with pkgs; {
nixd.binary.path = lib.getExe nixd; nixd.binary.path = lib.getExe nixd;
@@ -255,7 +184,7 @@ in
arguments = [ "--stdio" ]; arguments = [ "--stdio" ];
}; };
json-language-server.binary = { json-language-server.binary = {
path = lib.getExe vscode-json-languageserver; path = lib.getExe nodePackages.vscode-json-languageserver;
arguments = [ "--stdio" ]; arguments = [ "--stdio" ];
}; };
package-version-server.binary.path = lib.getExe pkgs-unstable.package-version-server; package-version-server.binary.path = lib.getExe pkgs-unstable.package-version-server;
@@ -275,14 +204,9 @@ in
font_family = "IosevkaTerm Nerd Font"; font_family = "IosevkaTerm Nerd Font";
}; };
edit_predictions = { edit_predictions = {
enabled = true;
mode = "eager"; mode = "eager";
provider = "open_ai_compatible_api"; provider = "copilot";
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;
};
}; };
buffer_font_family = "Iosevka Nerd Font"; buffer_font_family = "Iosevka Nerd Font";
buffer_font_size = 16; buffer_font_size = 16;
@@ -295,7 +219,173 @@ in
]; ];
}; };
language_models = { 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 = [ userKeymaps = [
@@ -309,13 +399,6 @@ in
reveal_target = "center"; reveal_target = "center";
} }
]; ];
"space o" = [
"task::Spawn"
{
task_name = "opencode";
reveal_target = "center";
}
];
"space r" = "pane::DeploySearch"; "space r" = "pane::DeploySearch";
"space f" = [ "space f" = [
"file_finder::Toggle" "file_finder::Toggle"
@@ -361,14 +444,6 @@ in
use_new_terminal = false; use_new_terminal = false;
hide = "on_success"; hide = "on_success";
} }
{
label = "opencode";
command = "opencode";
reveal = "always";
allow_concurrent_runs = true;
use_new_terminal = false;
hide = "on_success";
}
]; ];
}; };
+45 -9
View File
@@ -258,25 +258,61 @@
openFirewall = true; openFirewall = true;
}; };
services.invidious-companion = { services.home-assistant = {
enable = true; enable = true;
host = "0.0.0.0"; extraComponents = [
port = 8282; # Components required to complete the onboarding
secretKeyFile = config.age.secrets.invidious-companion.path; "esphome"
binaryHash = "sha256-RU18Zt/dSMVF/hLeAg+J3BrmDUnO7L4mv/+g7A91WL8="; "met"
"radio_browser"
"yeelight"
"xiaomi_aqara"
"shelly"
];
subdomain = "home";
extraPackages =
python3Packages: with python3Packages; [
gtts
numpy
];
config = {
homeassistant = {
name = "Koti";
unit_system = "metric";
time_zone = "Europe/Helsinki";
};
http = {
use_x_forwarded_for = true;
trusted_proxies = "127.0.0.1";
};
default_config = { };
};
}; };
networking.firewall = { services = {
enable = true; webserver = {
interfaces.tailscale0.allowedTCPPorts = [ 8282 ]; enable = true;
acme.dnsChallenge = true;
vHosts."koti.repomaa.com" = {
proxyBuffering = false;
locations."/".proxyPort = 8123;
};
};
invidious = {
enable = true;
subdomain = "vid";
};
}; };
security.acme.defaults.environmentFile = config.age.secrets.hetzner.path; security.acme.defaults.environmentFile = config.age.secrets.hetzner.path;
networking = { networking = {
nftables.enable = true; nftables.enable = true;
firewall.enable = true;
useDHCP = false; useDHCP = false;
domain = "apu.home.arpa"; domain = "repomaa.com";
}; };
system.stateVersion = "24.05"; system.stateVersion = "24.05";
-1
View File
@@ -10,7 +10,6 @@
}) })
[ [
"hetzner" "hetzner"
"invidious-companion"
] ]
); );
} }
-13
View File
@@ -20,8 +20,6 @@ in
"flakes" "flakes"
]; ];
auto-optimise-store = true; auto-optimise-store = true;
min-free = 5 * 1024 * 1024 * 1024;
max-free = 10 * 1024 * 1024 * 1024;
}; };
gc = { gc = {
automatic = true; automatic = true;
@@ -30,11 +28,8 @@ in
}; };
}; };
services.journald.extraConfig = "SystemMaxUse=500M";
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
boot.binfmt.emulatedSystems = [ "x86_64-linux" ];
networking.hostName = "freun-dev"; # Define your hostname. networking.hostName = "freun-dev"; # Define your hostname.
networking.domain = "freun.dev"; networking.domain = "freun.dev";
@@ -110,12 +105,6 @@ in
}; };
}; };
services.resolved = {
enable = true;
dnsovertls = "opportunistic";
dnssec = "allow-downgrade";
};
services.networkd-dispatcher = { services.networkd-dispatcher = {
enable = true; enable = true;
rules."50-tailscale" = { rules."50-tailscale" = {
@@ -168,8 +157,6 @@ in
"builder" "builder"
]; ];
modules.tempBuildStorage.enable = true;
# List packages installed in system profile. To search, run: # List packages installed in system profile. To search, run:
# $ nix search wget # $ nix search wget
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
-11
View File
@@ -32,9 +32,6 @@
"voidauth" "voidauth"
"gitea" "gitea"
"gitea-actions-runner" "gitea-actions-runner"
"searx"
"open-webui"
"open-terminal-api-key"
] ]
) )
// { // {
@@ -54,13 +51,5 @@
file = ../../secrets/hledger-basic-auth.age; file = ../../secrets/hledger-basic-auth.age;
owner = if (config.services.hledger-web.enable) then "nginx" else "root"; owner = if (config.services.hledger-web.enable) then "nginx" else "root";
}; };
invidious = lib.mkIf config.services.invidious.enable {
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;
};
}; };
} }
+90 -92
View File
@@ -3,7 +3,6 @@
pkgs-unstable, pkgs-unstable,
config, config,
inputs, inputs,
lib,
... ...
}: }:
let let
@@ -19,7 +18,6 @@ let
secrets = config.age.secrets; secrets = config.age.secrets;
in in
{ {
nixpkgs.config.allowUnfree = true;
imports = [ imports = [
./glance.nix ./glance.nix
]; ];
@@ -120,7 +118,6 @@ in
user = smtp.username; user = smtp.username;
from_address = smtp.from; from_address = smtp.from;
}; };
security.secret_key = "$__file{${secrets."grafana/secret-key".path}}";
}; };
}; };
@@ -136,21 +133,14 @@ in
}; };
invidious = { invidious = {
enable = true; enable = false;
subdomain = "vid"; subdomain = "vid";
extraSettingsFile = secrets.invidious.path;
settings = {
invidious_companion = [
{ private_url = "http://apu:8282/companion"; }
];
};
}; };
syncthing = { syncthing = {
enable = true; enable = true;
subdomain = "sync"; subdomain = "sync";
dataDir = syncthingDataDir; dataDir = syncthingDataDir;
configDir = "/var/lib/syncthing";
}; };
tailscale.enable = true; tailscale.enable = true;
@@ -248,25 +238,7 @@ in
mosquitto = { mosquitto = {
enable = true; enable = true;
subdomain = "mqtt";
listeners = [ 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 = { users = {
homie = { homie = {
@@ -323,11 +295,99 @@ in
}; };
}; };
home-assistant = {
enable = false;
subdomain = "home";
config = {
homeassistant = {
name = "Koti";
unit_system = "metric";
time_zone = "Europe/Helsinki";
};
http = {
server_port = 8123;
use_x_forwarded_for = true;
trusted_proxies = [
"127.0.0.1"
"::1"
];
};
mqtt = [
{
climate = {
unique_id = "nappula";
name = "Nappula";
current_humidity_topic = "homie/nappula/humidity/value";
current_humidity_template = "{{ value | float }}";
current_temperature_topic = "homie/nappula/temperature/value";
current_temperature_template = "{{ value | float }}";
mode_state_topic = "homie/nappula/ac/trigger";
mode_state_template = "{% if value == 'true' %}heat{% else %}off{% endif %}";
availability = {
topic = "homie/nappula/$online";
payload_available = "true";
payload_not_available = "false";
};
modes = [
"off"
"heat"
];
};
}
{
button = {
unique_id = "nappula_button";
name = "Nappula anschalten";
command_topic = "homie/nappula/button/trigger/set";
payload_press = "true";
availability = {
topic = "homie/nappula/$online";
payload_available = "true";
payload_not_available = "false";
};
icon = "mdi:power";
};
}
{
sensor = {
unique_id = "nappula_pressure";
name = "Luftdruck";
state_topic = "homie/nappula/pressure/value";
device_class = "atmospheric_pressure";
unit_of_measurement = "hPa";
state_class = "measurement";
value_template = "{{ value | float // 100 }}";
};
}
];
};
extraComponents = [
"default_config"
"esphome"
"met"
"radio_browser"
"mqtt"
];
extraPackages = (
python3Packages: with python3Packages; [
paho-mqtt
]
);
};
weechat = { weechat = {
enable = true; enable = true;
subdomain = "irc"; subdomain = "irc";
}; };
sillytavern = {
enable = true;
subdomain = "st";
listen = true;
whitelist = false;
port = 3100;
};
nginx.virtualHosts."isarepomaa.com" = { nginx.virtualHosts."isarepomaa.com" = {
forceSSL = true; forceSSL = true;
enableACME = true; enableACME = true;
@@ -391,7 +451,7 @@ in
labels = [ labels = [
"linux_arm64" "linux_arm64"
"ubuntu-latest:docker://node:latest" "ubuntu-latest:docker://node:latest"
"nixos-latest:docker://git.freun.dev/repomaa/nixos/node:latest" "nixos-latest:docker://repomaa/node:latest"
]; ];
tokenFile = secrets.gitea-actions-runner.path; tokenFile = secrets.gitea-actions-runner.path;
url = "https://${config.services.gitea.subdomain}.${config.networking.domain}"; url = "https://${config.services.gitea.subdomain}.${config.networking.domain}";
@@ -399,73 +459,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 = { webserver = {
acme.dnsChallenge = true; acme.dnsChallenge = true;
tailscaleAuth.expectedTailnet = "tempel-vibes.ts.net"; 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; virtualisation.docker.autoPrune.enable = true;
} }
+1 -8
View File
@@ -8,14 +8,7 @@
maxJobs = 8; maxJobs = 8;
protocol = "ssh"; protocol = "ssh";
sshUser = "builder"; sshUser = "builder";
systems = [ system = "aarch64-linux";
"aarch64-linux"
];
supportedFeatures = [
"kvm"
"big-parallel"
"nixos-test"
];
} }
]; ];
settings = { settings = {
+8 -16
View File
@@ -1,19 +1,11 @@
{ pkgs-unstable, ... }: { ... }:
{ {
virtualisation = { virtualisation.docker = {
containers = { storageDriver = "btrfs";
enable = true; enable = true;
storage.settings.storage.driver = "btrfs"; autoPrune.enable = true;
containersConf.settings.compose_warning_logs = false;
};
oci-containers.backend = "podman";
podman = {
enable = true;
autoPrune.enable = true;
defaultNetwork.settings.dns_enabled = true;
dockerCompat = true;
};
}; };
users.users.jokke.extraGroups = [ "docker" ];
users.users.moco.extraGroups = [ "docker" ];
} }
+2 -17
View File
@@ -1,30 +1,15 @@
{ lib, pkgs-unstable, ... }: { ... }:
{ {
services.displayManager.gdm.enable = true; services.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true; services.desktopManager.gnome.enable = true;
services.displayManager.sessionPackages = [ pkgs-unstable.niri ];
services.printing.enable = true; services.printing.enable = true;
services.keyd = {
enable = true;
keyboards.default = {
ids = [ "*" ];
settings = {
global = {
overload_tap_timeout = 200;
};
main = {
leftmeta = "overload(meta, macro(leftmeta+z))";
};
};
};
};
security.rtkit.enable = true; security.rtkit.enable = true;
services.pipewire = { services.pipewire = {
enable = true; enable = true;
alsa.enable = true; alsa.enable = true;
alsa.support32Bit = lib.mkForce false; alsa.support32Bit = true;
pulse.enable = true; pulse.enable = true;
jack.enable = true; jack.enable = true;
}; };
-52
View File
@@ -69,15 +69,9 @@
enable = true; enable = true;
extraPackages = with pkgs; [ rocmPackages.clr.icd ]; extraPackages = with pkgs; [ rocmPackages.clr.icd ];
}; };
services.resolved = {
enable = true;
dnsovertls = "opportunistic";
dnssec = "allow-downgrade";
};
networking.networkmanager = { networking.networkmanager = {
enable = true; enable = true;
wifi.backend = "iwd"; wifi.backend = "iwd";
dns = "systemd-resolved";
plugins = with pkgs; [ plugins = with pkgs; [
networkmanager-openvpn networkmanager-openvpn
]; ];
@@ -92,50 +86,4 @@
} }
]; ];
networking.firewall.allowedUDPPorts = [ 5353 ]; 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=";
}
]
);
} }
+4 -35
View File
@@ -33,8 +33,6 @@ in
nerd-fonts.iosevka-term nerd-fonts.iosevka-term
]; ];
services = { services = {
gnome.evolution-data-server.enable = true;
flatpak.enable = true;
pcscd = { pcscd = {
enable = true; enable = true;
plugins = [ pkgs.ccid ]; plugins = [ pkgs.ccid ];
@@ -53,12 +51,10 @@ in
ollama = { ollama = {
enable = true; enable = true;
package = pkgs-unstable.ollama-vulkan; acceleration = "rocm";
syncModels = true; environmentVariables = {
loadModels = [ HSA_OVERRIDE_GFX_VERSION = "11.0.3";
"qwen3:4b-instruct" };
"qwen3:8b"
];
}; };
borgbackup.jobs.root = { borgbackup.jobs.root = {
@@ -143,31 +139,4 @@ in
environment.etc."1password/custom_allowed_browsers".text = '' environment.etc."1password/custom_allowed_browsers".text = ''
vivaldi vivaldi
''; '';
systemd.services.ollama-keep-alive =
let
ollamaURL = "http://localhost:${toString config.services.ollama.port}/api/generate";
payload = {
model = lib.elemAt config.services.ollama.loadModels 0;
keep_alive = -1;
};
in
{
enable = true;
description = "Keep Ollama primary model loaded by pinging it";
after = [
"ollama.service"
"network-online.target"
];
wants = [ "network-online.target" ];
bindsTo = [ "ollama.service" ];
wantedBy = [
"multi-user.target"
"ollama.service"
];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.curl}/bin/curl -s '${ollamaURL}' -d '${builtins.toJSON payload}'";
};
};
} }
-3
View File
@@ -10,9 +10,6 @@
}) })
[ [
"borgbackup-radish" "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 = { users.users.jokke = {
uid = 1001; uid = 1001;
isNormalUser = true; isNormalUser = true;
extraGroups = [ extraGroups = [ "wheel" ];
"wheel"
"networkmanager"
];
shell = pkgs.zsh; shell = pkgs.zsh;
initialPassword = "changeme"; initialPassword = "changeme";
}; };
@@ -15,10 +12,7 @@
users.users.moco = { users.users.moco = {
uid = 1000; uid = 1000;
isNormalUser = true; isNormalUser = true;
extraGroups = [ extraGroups = [ "wheel" ];
"wheel"
"networkmanager"
];
shell = pkgs.zsh; shell = pkgs.zsh;
initialPassword = "changeme"; initialPassword = "changeme";
subUidRanges = [ subUidRanges = [
+5 -154
View File
@@ -4,7 +4,6 @@
{ {
pkgs, pkgs,
lib,
ssh, ssh,
config, config,
inputs, inputs,
@@ -58,10 +57,10 @@ in
# Select internationalisation properties. # Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8"; 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 = { users.users.jokke = {
isNormalUser = true; isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable 'sudo' for the user. extraGroups = [ "wheel" ]; # Enable sudo for the user.
packages = [ pkgs.nh ]; packages = [ pkgs.nh ];
openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ]; openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ];
initialPassword = "changeme"; initialPassword = "changeme";
@@ -75,7 +74,7 @@ in
wget wget
htop htop
git git
alsa-utils inputs.turny.packages.${stdenv.hostPlatform.system}.default
]; ];
# Enable the OpenSSH daemon. # Enable the OpenSSH daemon.
@@ -89,159 +88,11 @@ in
allInterfaces = [ "ssh" ]; 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 # This value determines the NixOS release from which the default
# settings for stateful data, like user locations and database versions # settings for stateful data, like file locations and database versions
# on the system were taken. It's perfectly fine and recommended to leave # 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. # this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option # Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment? 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 ./vlans.nix
./firewall.nix ./firewall.nix
./storage-box-mounts.nix ./storage-box-mounts.nix
./temp-build-storage.nix
./services ./services
inputs.agenix.nixosModules.default inputs.agenix.nixosModules.default
]; ];
+1 -12
View File
@@ -46,18 +46,7 @@ in
certificate_chain_path = "/run/credentials/adguardhome.service/fullchain.pem"; certificate_chain_path = "/run/credentials/adguardhome.service/fullchain.pem";
private_key_path = "/run/credentials/adguardhome.service/key.pem"; private_key_path = "/run/credentials/adguardhome.service/key.pem";
}; };
dns = { dns.bind_hosts = tailscaleIps;
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"
];
};
}; };
}; };
+1 -3
View File
@@ -26,13 +26,11 @@
./weechat.nix ./weechat.nix
./hledger-web.nix ./hledger-web.nix
./glance.nix ./glance.nix
./sillytavern.nix
./nqptp.nix ./nqptp.nix
./actual.nix ./actual.nix
./voidauth.nix ./voidauth.nix
./gitea.nix ./gitea.nix
./dhcp-dns-sync ./dhcp-dns-sync
./invidious-companion.nix
./searx.nix
./open-webui.nix
]; ];
} }
+3 -11
View File
@@ -6,11 +6,6 @@
}: }:
let let
cfg = config.modules.services.dhcp-dns-sync; cfg = config.modules.services.dhcp-dns-sync;
ownAddress = (
lib.elemAt (lib.splitString "/"
config.systemd.network.networks."30-${cfg.interface}".networkConfig.Address
) 0
);
dhcp-leases-to-unbound = dhcp-leases-to-unbound =
pkgs.runCommand "dhcp-leases-to-unbound" pkgs.runCommand "dhcp-leases-to-unbound"
@@ -64,10 +59,9 @@ in
users.groups.dhcp-dns-sync = { }; users.groups.dhcp-dns-sync = { };
# Ensure directories and files exist with proper permissions # Ensure directories and files exist with proper permissions
# Directory needs to be group-writable for unbound group
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d /var/lib/unbound 0775 unbound unbound -" "d /var/lib/unbound 0755 unbound unbound -"
"f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync unbound -" "f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync dhcp-dns-sync -"
]; ];
# Extend Unbound configuration to include generated file # Extend Unbound configuration to include generated file
@@ -75,8 +69,6 @@ in
server = { server = {
local-zone = [ "${cfg.domain}. static" ]; local-zone = [ "${cfg.domain}. static" ];
include = cfg.unboundConfigPath; include = cfg.unboundConfigPath;
local-data = [ ''"apu.home.arpa. IN A ${ownAddress}"'' ];
local-data-ptr = [ ''"${ownAddress} apu.home.arpa."'' ];
}; };
}; };
@@ -96,7 +88,7 @@ in
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
User = "dhcp-dns-sync"; User = "dhcp-dns-sync";
Group = "unbound"; Group = "dhcp-dns-sync";
# Allow access to networkctl via D-Bus # Allow access to networkctl via D-Bus
SupplementaryGroups = [ "systemd-network" ]; SupplementaryGroups = [ "systemd-network" ];
# Read/write paths # Read/write paths
@@ -58,6 +58,13 @@ def sanitize_hostname(hostname : String) : String?
sanitized sanitized
end end
def reverse_ptr(ip : String) : String?
parts = ip.split('.')
return nil unless parts.size == 4
"#{parts[3]}.#{parts[2]}.#{parts[1]}.#{parts[0]}.in-addr.arpa."
end
def generate_unbound_config(leases : Array(Lease), domain : String) : String def generate_unbound_config(leases : Array(Lease), domain : String) : String
lines = [] of String lines = [] of String
@@ -75,25 +82,19 @@ def generate_unbound_config(leases : Array(Lease), domain : String) : String
# A record # A record
lines << %{local-data: "#{fqdn} IN A #{lease.address}"} lines << %{local-data: "#{fqdn} IN A #{lease.address}"}
# PTR record - local-data-ptr expects IP in normal form, unbound reverses it # PTR record
lines << %{local-data-ptr: "#{lease.address} #{fqdn}"} if ptr = reverse_ptr(lease.address)
lines << %{local-data-ptr: "#{ptr} #{fqdn}"}
end
end end
lines.join("\n") + "\n" lines.join("\n") + "\n"
end end
def get_leases(interface : String, networkctl_path : String? = nil) : Array(Lease) def get_leases(interface : String, networkctl_path : String? = nil) : Array(Lease)
cmd = networkctl_path ? "#{networkctl_path}" : "networkctl" cmd = networkctl_path ? "#{networkctl_path} status #{interface} --json=short" : "networkctl status #{interface} --json=short"
args = ["status", interface, "--json=short"] output = `#{cmd}`
raise "networkctl failed (exit code #{$?.exit_status}): #{output}" unless $?.success?
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
result = process.wait
unless result.success?
raise "networkctl failed (exit code #{result.exit_code}): #{error.empty? ? output : error}"
end
status = NetworkStatus.from_json(output) status = NetworkStatus.from_json(output)
status.dhcp_server.try(&.leases) || [] of Lease status.dhcp_server.try(&.leases) || [] of Lease
@@ -150,18 +151,13 @@ OptionParser.parse do |parser|
end end
def reload_unbound(unbound_control_path : String?) def reload_unbound(unbound_control_path : String?)
cmd = unbound_control_path ? "#{unbound_control_path}" : "unbound-control" cmd = unbound_control_path ? "#{unbound_control_path} reload" : "unbound-control reload"
puts "Reloading Unbound..." puts "Reloading Unbound..."
result = system(cmd)
process = Process.new(cmd, ["reload"], output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) unless result
error = process.error.gets_to_end # Fallback to systemctl
result = process.wait system("systemctl reload unbound")
unless result.success?
raise "unbound reload failed (exit code #{result.exit_code}): #{error}"
end end
puts "Unbound reloaded successfully."
end end
begin begin
+18 -5
View File
@@ -37,9 +37,6 @@ let
}; };
immutableEnvironment = { immutableEnvironment = {
HOME = "/var/lib/dnote";
XDG_DATA_HOME = "/var/lib/dnote";
XDG_CONFIG_HOME = "/var/lib/dnote";
GO_ENV = "PRODUCTION"; GO_ENV = "PRODUCTION";
OnPremises = true; OnPremises = true;
DBHost = "/var/run/postgresql"; DBHost = "/var/run/postgresql";
@@ -93,10 +90,26 @@ in
after = [ "postgresql.service" ]; after = [ "postgresql.service" ];
requires = [ "postgresql.service" ]; requires = [ "postgresql.service" ];
serviceConfig = { 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; EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
User = cfg.user; 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" ]; wantedBy = [ "multi-user.target" ];
}; };
+1 -4
View File
@@ -46,10 +46,7 @@ in
services = { services = {
webserver = { webserver = {
enable = lib.mkDefault true; enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/" = { vHosts.${fqdn}.locations."/".proxyPort = 3008;
proxyPort = 3008;
extraConfig = "client_max_body_size 0;";
};
}; };
postgresql = { postgresql = {
enable = lib.mkDefault true; enable = lib.mkDefault true;
-99
View File
@@ -1,99 +0,0 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.invidious-companion;
companionRelease = "release-master";
hostPlatform = pkgs.stdenv.hostPlatform.system;
# Invidious Companion package - fetches binary release and patches for NixOS
unwrappedCompanion = pkgs.stdenv.mkDerivation {
pname = "unwrapped-invidious-companion";
version = companionRelease;
src =
let
archMap = {
x86_64-linux = "x86_64-unknown-linux-gnu";
aarch64-linux = "aarch64-unknown-linux-gnu";
};
platform = archMap.${hostPlatform} or (throw "Unsupported platform: ${hostPlatform}");
in
pkgs.fetchzip {
url = "https://github.com/iv-org/invidious-companion/releases/download/${companionRelease}/invidious_companion-${platform}.tar.gz";
sha256 = cfg.binaryHash;
};
dontStrip = true;
dontPatchELF = true;
installPhase = ''
mkdir -p $out/bin
cp invidious_companion $out/bin/invidious_companion
chmod +x $out/bin/invidious_companion
'';
};
invidiousCompanion = pkgs.buildFHSEnv {
name = "invidious-companion";
targetPkgs = pkgs: [ unwrappedCompanion ];
runScript = "invidious_companion";
meta = {
description = "Invidious companion for handling video streams";
homepage = "https://github.com/iv-org/invidious-companion";
license = lib.licenses.agpl3Only;
};
};
in
{
options.services.invidious-companion = {
enable = lib.mkEnableOption "Enable Invidious Companion service";
host = lib.mkOption {
type = lib.types.str;
default = "localhost";
};
port = lib.mkOption {
type = lib.types.port;
default = 8282;
description = "Port for Invidious Companion to listen on";
};
secretKeyFile = lib.mkOption {
type = lib.types.str;
description = "Path to file containing the companion secret key";
};
binaryHash = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "SHA256 hash of the invidious companion binary release";
};
};
config = lib.mkIf cfg.enable {
systemd.services.invidious-companion = {
description = "Invidious Companion - video stream handler";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "simple";
User = "invidious";
Group = "invidious";
DynamicUser = true;
ExecStart = lib.getExe invidiousCompanion;
Environment = [
"HOST=${cfg.host}"
"PORT=${toString cfg.port}"
"TMPDIR=/var/cache/invidious-companion"
];
EnvironmentFile = [ cfg.secretKeyFile ];
CacheDirectory = "invidious-companion";
WorkingDirectory = "%C/invidious-companion";
Restart = "always";
RestartSec = 5;
};
};
};
}
+1 -14
View File
@@ -1,8 +1,4 @@
{ { config, lib, ... }:
config,
lib,
...
}:
let let
cfg = config.services.invidious; cfg = config.services.invidious;
fqdn = "${cfg.subdomain}.${config.networking.domain}"; fqdn = "${cfg.subdomain}.${config.networking.domain}";
@@ -36,14 +32,5 @@ in
vHosts.${fqdn}.locations."/".proxyPort = cfg.port; vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
}; };
}; };
systemd.services.invidious.serviceConfig.DynamicUser = lib.mkForce false;
users.groups.invidious = { };
users.users.invidious = {
isSystemUser = true;
group = "invidious";
description = "Invidious user";
};
}; };
} }
+4 -29
View File
@@ -1,39 +1,14 @@
{ { config, lib, ... }:
config,
lib,
...
}:
let let
cfg = config.services.mosquitto; cfg = config.services.mosquitto;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
acme = config.security.acme;
in in
{ {
options = { options = {
services.mosquitto = { services.mosquitto = {
openFirewall = lib.mkEnableOption "Open firewall port for 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 ( config = lib.mkIf cfg.enable {
lib.mkMerge [ networking.firewall.allowedTCPPorts = map ({ port, ... }: port) cfg.listeners;
{ 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 = { hetzner = {
class = "octodns_hetzner.HetznerProvider"; class = "octodns_hetzner.HetznerProvider";
token = "env/HETZNER_API_TOKEN"; 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;
};
};
};
}
-9
View File
@@ -27,14 +27,5 @@ in
vHosts.${fqdn}.locations."/".proxyPort = cfg.port; vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
}; };
}; };
systemd.services.syncthing.serviceConfig =
lib.mkIf
(
cfg.dataDir == "/var/lib/syncthing" || cfg.dataDir == null || cfg.configDir == "/var/lib/syncthing"
)
{
StateDirectory = "syncthing";
};
}; };
} }
-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 age-encryption.org/v1
-> ssh-ed25519 osOCZA tKc6OuybMhchwMgREDpacwxz/IlBCOOD9OQp/UMLBys -> ssh-ed25519 osOCZA OPYYw6SBBoMNoJ/DfIyvxLVpZo0n0v08uhrPa2Uk/xw
7mOmTzMUnLYnQPHOWwIiZ4vFyPxQpwzPmnQMuWsQWfA b9mSzuzgznPl5qu73kJie2PgJGIlxEOR/MSNzp2TZ2A
-> ssh-ed25519 DFiohQ +ysb8W20OD73awl7we7/XfiX5q8sRtMlerr5r4gavTA -> ssh-ed25519 DFiohQ rYD5ueKrIAbtphPZe7BxyHNPdRGCV0o4G3lXGGHFmD0
5OHmlJHoVRXho2Swe/XDTtQKn0pW9PH1Ky4rvhR7o/8 caGATSGVUaJkTDIHDk40oJnNh8oNRRQYTpKn4jET72c
-> ssh-ed25519 PT7ffg 0vvEHkjNJFH/UUcwX8O+F0StVBqNkQNp7u2qasHZLCE -> ssh-ed25519 PT7ffg 2HVnNQxY5BubUZGTuwcOHMKDZ7O1f+gji9d2mquLECs
xKySMrX3T59dCKEUHyR/cdT+8KCFdCerUyUg7r7ZBYQ rJzRjFokTzDkn/fjA1eXaZdISEXWtGqLSsYpUYEd0qw
--- ielXPF0xxxqSrBc1yeOxLdz50ZjCLnZJ45Wsg84yPso --- U6CH4TvqpEfQg4VuQ3fr7gcqBojVEV2r4+NPfMEVxbA
×S;ëqø»äá û˜…Å@ðŒ±S,a¾¬ÛÜH3;(¥¡%o#‘ýx$Qgw”žãÊ>ôias  °ù»%fjr¼ÊÜ^cïrô©´Ñ‚â°`ä—e›¼níû_–w›×¶çõvÓCØÅ‡x}|µFnÿjݹ°²À­ ß q5šòú4ÍÝMêÿ—Ô–³ÏEòx'í’6éu§¤eìëŠàÜh4D;óN¿ƒÔ<:P¡)Š."CÞ›èˆÿ
xgóv˜/ãN%W2™¥ÙPâQÖdA½VêÉ—üœ^_B|Íëío׌àkf¦ÛŠá›WÞ~ ³ú̪’ϽևÄÖDñõfo
Binary file not shown.
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA IY+dU1oe5ps4gli4bqRVdIaKaOWfEieIX0b/8q6+Pys -> ssh-ed25519 osOCZA wDdoFnPs/OUczxmB6WgzSQ0HtguSmIsX1di6gH5guFM
L5Bkf3dTH9k0HUYrrTvLq5hvS7CK1nJp1mJumBlVGb8 b6PAgnY1Kki92V+ky+/Cuq5UezYdchS3g5MtqtRxDMQ
-> ssh-ed25519 DFiohQ BjbGXY9CmrAEcv4+hk+A1AIP06NeP4NhymTeByfxi3c -> ssh-ed25519 DFiohQ oZmlLt1Em4PnIZAUwqxed5EH1ZG/Bkm3FPiZI9uxVUY
94qpDg6nEowmkNUzeDGtNw8VgT8vsWR4y28/lMyfZmc /dAEjEHp9mcygfYj+taalhqjJ6ywkWu45ymwnmtlzaY
-> ssh-ed25519 hRPDBg 7Lq1rjiz1FhgFkWaZJaLR24k+HAKLraw9YjVsWPspVE -> ssh-ed25519 hRPDBg MKpKkBgl0baf6qJh940ekh1z/0bKbzYTBb9In2yVd3Q
3eBnzsN96eLmIlu9MNiBhv41csudd4WQUF5Q9vpGkj0 1uX/3y+A/a+vxSl41DlUnZVdh6lpDYFwunzLV3DRblk
--- l10qNh0gcX57lTZsiTsuzwhVGnKM5hH5+ZbjI9yfr/Q --- KQncWqjni5UN+D3I1OSWdKxVf5/UiLBUNBwFMG/1+mg
ûK¿;n},ijæ6uîȧMŤ'ülÿÓÂò–?ÜÜå'„MtÙš.¢ßšžô.IQó³öy)&|=¨½@1ãmSRD"&&áWUgið}'ˆ¹¶¥ Š­öôó\ÄXã±zÐÿÌ®œ¬Û\~.¼©ûfå•w@÷␍IµÂÂÀqvE²‡µïÓÕæÄ÷žÇiMÇ›ûE×€„fçÒ9Çj< ÇY¾`œÔÎ<ÿ ã›]e
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA /DM/QULRCY6cCkuqi0h+PVXW+lFcvUwTaNI4vVHoG3w -> ssh-ed25519 osOCZA UCilO75OGmYABIVNjQ7JKdObSSC882S6blT35S8XXBE
f/U+q+Y6zoIcg0ttN+JgFc0GqXKWV08nmWUeeeC+b5k CEhIzJeE0Xa9jY1uboPVM1uK+U8N77e6pGsK3SCNBqY
-> ssh-ed25519 DFiohQ kgrVGGqMrBn8W1trnR116ODUHTeDR60BLnWfqANr/QA -> ssh-ed25519 DFiohQ aKyq1IPwKvgQniPEDt9tIw6KLeQePctFPY8wnSmcREk
ys8CLK1XHkOQvp3xlcQkQ3me2a2/Nu94o5L9swWhsAs 3OQH5iOGFbnJaUXn8JmMDItPfPjXUmuO1OFYw31ftGA
-> ssh-ed25519 PT7ffg 3jg8xsvEsjZ8Gzap1Q0T5cZpi/lmk8/DuE1Zsk7ByGY -> ssh-ed25519 PT7ffg 1XzR+XsNLJgf0itst++aN4GWpiUjPvlCPR/KcZzLNT4
gfrFh5M2GWWa+5IaO7yL8LRlMThRWt/XdZIYDXxo8zU Na1BKsjIoFgFbQI4LVppxW53yAQgxQfz5r31HrTNRLI
--- U2Rbi2kHkOIW8AErxfFCUwf2HayX+J4QjKVz/4aGn9k --- U8B89Fp3JNPBX18P5p3lVLYIz5CsbxldkvnjZ2Cx4+8
¤c Ý“øÉ‘ôºœ2kž7ˆÿ)ýí,h~Ó1!¦ÓÁjaÍé`ÊVz Æ-␍n}<ÎØºKä+Rrw¿’Wð†Æ®3éCRªòV©yÙ{ º“¨3z®#<Kî*ŽwÁMs! ­Ó2¼Êbqz>‰&€~u«¹‡Êi𧾘tYv§¾ÁþítÂøÒvÀ¬2Ïd)gfÇÃÙÔ
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.
Binary file not shown.
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA X80qdaNMdIz7sy+tLmTYlati/SKucc03gW5FXE9/xlM -> ssh-ed25519 osOCZA Y7ieflWxf67Jik747FnOu5aKcDjh6hd8OWE88oX7gWc
5ZvMFxd3MOl4SyBEYR9xawjREi8UwEmW9DyuCC+E8jk aQavFEvcW30cer2DutpkfSkKk1NqGzPiP2bYL2MCv+Q
-> ssh-ed25519 DFiohQ NPghTlceQTcZh4VGASPeFZy8G2VLURxykc0JjvAdW2s -> ssh-ed25519 DFiohQ J5HO9n4KTQ6cSBGHxrBHORvmYp4WYJZiATekbZvhuEU
MkIN+sOI3yD8UzSmHOabeVnFHfXDDWMSWwFIuHGJGFE QGAhwn6P5ByiNY8bMUI07hHvnA8diVI56Vg+6kVEJb0
-> ssh-ed25519 PT7ffg mc6TmlrgBEBQeOECY6SzHaLhpi4SrYc/9m9nml/2GGY -> ssh-ed25519 PT7ffg RpcpdzFtX5VyodX3lXKvjMRxMlpasRqqpjqCacXDcy4
VDNZtQSSGG1+/84Y5TcyiJytoAoPCyj9PlYV7WZiKE8 VJQuB8YfhB0DQ4TjwzBhdtl35eyFVLLW8O87mCuH828
--- GPLaSoO3+AZXfY9gIweM+dDAb+hBSadz++DqYEkR2ug --- qJnfQgOyw0CTBhXNSyIw3O33av2cjPxTntLsOW/bevA
;ܬl‘öj…Ãå˜æ?çÚøÆQ² „NÁìû/ªº)à·[xØö%‹F!¼0µ†ª -åž8^$-IŒ4EJæ…5R²<ÃI_²–Ò,Ý#í¹S¡FY¥Éò¯ì2gðuïË
+9 -8
View File
@@ -1,9 +1,10 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA t6j6GnCmxIYZKfYZ+dthOAaJqjHtbsIZSOYaonDvDxo -> ssh-ed25519 osOCZA y1DrZWN4jrO6xg7TkFqrjTT/k4L49IAZVf/NLw3jMiM
KWhOeO95ExUHgcHtm5BYwZnG4EOMZpcKB7h/UM7yxZ8 BB+Aq/80SFQpLJfZxRKFPBw9ylHb+Y6LLuZQAqG3WdQ
-> ssh-ed25519 DFiohQ XlRVp8vYYbCbrCGIYpXLsewZkceQJRqUlegTnAp4gSo -> ssh-ed25519 DFiohQ jPPM8ATBvlXn0F/9pDCsZB15yW+af0b2FQqV2gI8YyQ
4yu21bl6xEodz/lVBV/imCresblG89uyz8fzWhSjBJ8 8yJjKEvEGKDwtvBWP/yjiDql+HA6l5q+RfEgP8cAIGM
-> ssh-ed25519 PT7ffg UN3WxuZZrVq4xG/fOYdfyMBBvqO5aKLF+0DZ+8SYjFk -> ssh-ed25519 PT7ffg +R5KlrnH2gGW2Qc3V+RgYKtAXerbRB5j898+b41/VAU
DSGXD5GeYKIwT/GLG2n16660x3R4JASnzPI0vBvi1kw 7Lq3GE9CMGeXAmsf9WgSiJf/o+HyssF4xz94JNjbEgM
--- mobBb6GglqgnpHy9IaTX6FwjIuv+g31FpC0V4tKJ9mg --- k2xNMbWr5tntlbOOYLbt6DsWsW2mT/P3pg3IgXYcM58
ábwøysqéõfØÖ½Çøâ÷ùE§KvYt&²1ÿWzÙì«ÊZ+ɳ×rœ¼ ”o·‹ƒŠG |ìÍѰm7Æy‰z¡¥R©ÔíYê+qî§ü—1>ž‡Y?hHÖžÌÌÈ|ù7àÞþ̃
N¥zÚöØäëøð
+10 -8
View File
@@ -1,9 +1,11 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA OKLTpDP78xbkZ2uxMaJtOIVCd3/jbAV98E9yFGg0SHk -> ssh-ed25519 osOCZA 77EDoJKwYm80SsOFMaTeLVKyb60mpVafClV0oDfhnm0
1RwoL09ZccJhYWJxy/JgMxJSHc74RsKD22L3c13BARo gZ4EtoDbmI2iggyAooqWtd0Yhh5kebvR8vnHGz0Er6Y
-> ssh-ed25519 DFiohQ us/uB+O7gBPPJ3ewzn1Vpkc59obSQcwSORFIhPsw0Wo -> ssh-ed25519 DFiohQ dYdiyYPtjCH8YFpj4bAwTDSEB+0JjzDIz1jwRPDjAFg
dW/6eaPCKI0Z59PPtoNiOWoaNPNFqy1WPhI2y+AXXok NAdMoMfREupERqZ8t2lu6++Qjgq7LVIo5wsXabXKWU8
-> ssh-ed25519 PT7ffg Shf65NGS/njVgmsKYzH0G+R3Lbul24ov1YX0D8yCVlE -> ssh-ed25519 PT7ffg 6NFOq021rnHwv7BFIw9in6uQR39hgh5qVuPNvxtCd3k
+V8lgVMK4tIIvjWXDRSK15Ko4lf9dBbd3BVnLm4zl7Y 4UK6DCVbSi4yugpY9nLBSoZ7SHLkF3MMIeHVKYH3bkQ
--- YLhHK7p69aBksuFUcoRIfh9xej8HsQcDgrCUEyw4x64 --- 1BNBapkorRybzDSfmHCQjfkN0ss0GXnZGv99Ni/O/VM
#\)V×¹ç{÷ËÆ½OøHK1lNæ(ˆß*ÒQ ^\aÉz(Ì?A»ý©·(#$Õè3Q” ·„¸£K ã…p"Is)¼‡‹;½É1% wp3IvÄ"ßc}úÀ™W,0VÀÄæÂmØû±ù´˜ƒv¹„¯j’ªP#„”$Ž”›f¹ÜÌ
v9x["ú¶c ‡y²
~ .€[†/ko'z
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9 -9
View File
@@ -1,10 +1,10 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA RXa84TEMDWGN5boMxdkSqkSYADuFtdOLiPRG7l4NfQ4 -> ssh-ed25519 osOCZA 8VhILVP+borbW+8iFTyZTPlRAsdmoIUVa8DUWjrz/RI
5ZKIoAUvvXDQmQBqIlD8Hk/jU447XHzZr681Yz/Vpqw E9SbMJ+Tz9p4O2z33AOKhpKVla8u+O39nO35hclR6oo
-> ssh-ed25519 DFiohQ cgSbRK5wdZQc3Ns4czyUgvir5oQXbdAP6dzpbp0v2nI -> ssh-ed25519 DFiohQ +JeoeaQ62VmfBNN2wDEDGINspEK+e4YWNfjR7eJ7RAg
iTujuKOCwK8ajM6AMyXx1TC/jCHFByG0ts9wSxDDNMw KM4/0TfoOk1+CqXkx55uUbaylCFgNwTHRaTiPdCYmCI
-> ssh-ed25519 PT7ffg hOQSv4qpPf2UYAmoJse4oIco+Pehe2IBJsfmNM3jWgQ -> ssh-ed25519 PT7ffg AcIAWujVGx4ndNfqbp1wtDsHp5rHTTnz2gy+LLSzxEE
yC2/4stjYH5mqklHiSL/oDzx3JJ+9f98PaWFFtODJ2s wDsg4PytTFf2wY71BCaDYMpaHxo3UqjXC93SSqmBhlY
--- pCBHnbEEEgTwsGfiD21wq83WAjdw4T335TIJwuiqVIs --- U6Gn7J2Lh8Z83lYmHUEOgbbyDDEJqxSr/Kw2dcwhVgo
^±¥·Ì[ÐZImV ␍÷§_åÇÜþzÉËù@‚\•1;©üdŸ]ñiø¬d ƒ?Ýnb¶AVœ j#ân¶ä¿åG—LÍQdŸ wqø(0÷™Êºz†¤Œ:J(qè¨ =ݨ©xô},øÏrG´•Ët‡•uâ‘…“Ε"d„6 Ù¢²
½}§O§ö.°¤Ž–ïÛv›©-¤¦ ¥.U€øÑ¥bg'ÌzZ»8¯í«öšAZvBø‡.3sX[`#\ùÅ.;J
-11
View File
@@ -1,11 +0,0 @@
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!ÃîÜÚ£³
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -8
View File
@@ -1,9 +1,9 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA xdLLgxnXF6YwP6PA158Jk5PLd9DqMdD7iWzgxcig5ks -> ssh-ed25519 osOCZA eWLRpIyRno4qtjjwpXxlwsW4I5a59h+c8W4mJpb7rmg
mmjqlmU/6MyTaLUD63nyubvr+62SWik0a5wnlhuLQXY i1LmqRoWZ8wB1EYxNvtqoMSr1lqGbcHHqyAPK1Ldy3Y
-> ssh-ed25519 DFiohQ BbxdetgH/z1ZUmzdKYLYFbSPx9y6vhqwZmMwlwRuCWM -> ssh-ed25519 DFiohQ Wn2NMzQBdv6KsZnBUj82FGo3FdOcyZqd1A+KkQy5G1w
IpNVv3/EtML29RT9Ca8zrWM8gnGfk2iweW5RnXrTm94 ZCrFCEeikNUmG1pO/f0wy7GzTzwCYoNhQBTeofmo98g
-> ssh-ed25519 PT7ffg f2BPj7TYxAnf1Os/VgMmxQ8g7DIhMdohUUe5y97WK1A -> ssh-ed25519 PT7ffg RnEdUTw4G7dVL/YWr5vls5IEf1BbrdBCjgk+ZTABlQo
zmNAbvgrV3tYQ8ZylZZ2P8v5FW7kklQt7XIkPqeIY9k G2PEFcmClmcd8Ap6L4VEipULRZuGj3izzeB0l/cI6FU
--- 5RXgn5pqVJqKRAiZ9aA5uVL83P7fJHAs98R/iUC3tLs --- +Jmqn5CDZN3jaexEWZzZvuKvxjZfXfEdyUW3cQIIsnQ
Ÿò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š‡÷˜ö ô ƒ`‡ù€ÚôŒÅ¬8\ÃR ØYoç"¾Øó2ö/˜<[ÿ>ÈuÀyÿÉW¥ÝÞ`Ç® <¨™V¨Ø-úÌdÇBª*“ï±)n( ‘oB7sñ Õs—À¢à’)aE)ÆÐ…4‹½ŠÎ(îÂmQ¹Fy·xj'„wX,QµV”ìÈö
+8 -9
View File
@@ -1,10 +1,9 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 osOCZA kxEAkm22oi+LCgW2w7T1GtuqBxz4m9aU4ceOf+0KInw -> ssh-ed25519 osOCZA dkgMfjgrKalX7uGrncrep3rtVZFXUHeqwbPix7ngyFY
oJ43lhfBnq03g4OWyIclsoyUjJM4IRXQcN+H8xUfOzs a9jzF29C7Ltg7tn7Rcoi95847kRhWePylmMU7PGOkdo
-> ssh-ed25519 DFiohQ UcmlGnwNYUjALCKI/90wC96JaeBU5NwX742yZ4JBpXQ -> ssh-ed25519 DFiohQ CeZgWwo/TDb89fUVx2ueTArKGPuBjdp2sklqTpkgoj4
l8Wwh7zdA6Ww/CSqI0UffuuL6/oD3nW5/ZcDm62a2b8 7/H9QMGzIBXcSYTnzXfJwlvlKLI4B1miPU+LXzmiHtE
-> ssh-ed25519 PT7ffg ViIZQL9IEiMKxsYkZSewoQlEtHCS2K66HWs22dCtsHM -> ssh-ed25519 PT7ffg 456boso/C85lpir1PYUYD1pzb70vQvTrAN3gKy15s1s
LkZwHCQWPP11iacPOC2HtGq6qpp+5owzSulTzGpDhzg Sv2hsM/Yx1hUeGWih5zMYXzJaapm767IDzC/4wmKulU
--- gsZQ6BVOUwRjzTAED6JZHNo/Vz+m0wgN1VrCbJwS3Vo --- /iFmcxXywCLhEOLKLjzrKx/QW93++yzI7tXvn/asMUQ
·‘ù ¯`©òˆ:…*ŸÞ¥·œê')»{;tɤ`E¨`ŠØþ„♇  •Ï‘„šUà‹::›¿’hÓ<A±É¢”%ä ZgsE¼ol„¿cƒãÈj¾ Äuð‚ÔÔÌBÈ-¬³"{iëÒ Iпíe8NÃæ6§çºV÷SðFã$m¸Æ¥æ¼–oÈ
{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ª
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 -10
View File
@@ -9,7 +9,7 @@ let
apu = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICAZkIuXtpP9a9bHkBl+MJI//q3ClMqzx03Rd/Xe4rjc"; apu = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICAZkIuXtpP9a9bHkBl+MJI//q3ClMqzx03Rd/Xe4rjc";
freun-dev = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEvCSjIjipog1Xf9mPc683r5VSGSjVc8v1UZg5VrbbxM"; freun-dev = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEvCSjIjipog1Xf9mPc683r5VSGSjVc8v1UZg5VrbbxM";
radish = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQ0fy4n3yyD64+g55eZazeI5g9FurJnlC6fRiOXbbks"; radish = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQ0fy4n3yyD64+g55eZazeI5g9FurJnlC6fRiOXbbks";
turny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM3To9DeTGzwiQN+uqNWZXKzGG1BJnLvYRgLm0/iI92Y"; turny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIODeWhDvzDGyTGkCoxay80NtgU2OVPL37qXjbhJP7oPJ";
in in
{ {
"gotosocial.age".publicKeys = users ++ [ freun-dev ]; "gotosocial.age".publicKeys = users ++ [ freun-dev ];
@@ -44,13 +44,4 @@ in
"context7.age".publicKeys = users ++ [ radish ]; "context7.age".publicKeys = users ++ [ radish ];
"gitea.age".publicKeys = users ++ [ freun-dev ]; "gitea.age".publicKeys = users ++ [ freun-dev ];
"gitea-actions-runner.age".publicKeys = users ++ [ freun-dev ]; "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 age-encryption.org/v1
-> ssh-ed25519 osOCZA PQtiqZbb169B6Ovwy86AExmBbrVteTQOFbjnDGJq3UM -> ssh-ed25519 osOCZA Mq3JCuYFasHg3oSpWgGEjs5+cleWPog/eJCx1SqqXVQ
LFCbg+9gFnt4p5xHGHrfJXMJRjSGPPmpXd4pEIFtp2A SiJ8vHCSZoyhvbQQ3AUOGk54oOQZAqyNNvjRjKHm4Fg
-> ssh-ed25519 DFiohQ RqRirsYRaJ3RRLzvQIOohbF3uX6/5axomxhrO/SAjzA -> ssh-ed25519 DFiohQ xEJMTBuU1uMm3goNGUln63rUbddy96+SZR8K+/LkMB4
jUMEf1YYetb1JWYzeWT4cyT81O36KNm0fN8I5rmuRrE hFWZe+w2h6sxitweJ+dRNb6HY7YFt1k+/XDVNoDVw0Y
-> ssh-ed25519 PT7ffg ISuGZoBIQVdZv/17EKV+MNQHvqSTcj4ChLGvGew4KGQ -> ssh-ed25519 PT7ffg ErmwKHYuFoIS8rjMeq/5G6SWKaUhHG/N1Uy/KK7yZXU
QzjajsmaW+6CQc28cYAOuCLCwFY3c9qP0/uSa4z9yPM /Iz1mQp8mq7xH+kjP4S5m36GjTpzVRv40mcKPT+5rcc
--- aDY1WIHOhshB9Yfv6tZ9Xip7Ms+88v4D++tGiSSI1A4 --- 82a4oeNSWQhUtTJvV+ErC9nmT3YrATezQzY7m8G2JFU
mÎÔr¼&³A@j× aë‡*1¾êr¤ÏDwzŸ¨ *ŸÓ‡ê=®^¨ERÕÞ?Žb¡ô&`i¸˜¼`^lØ6N÷–á4¦Œ S½î-‰þÑ¥ Ù
B¯_W½r»1°½RúÆôv|Ð|ú|·Õd-TD¤VÝ&ÎãƒÍêɧˆøá3sÓŸ(S#*åF¾
Binary file not shown.
Binary file not shown.
Binary file not shown.
-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.
---