Compare commits

..

1 Commits

Author SHA1 Message Date
6441352ed9 feat(skills): Improve parallel beads workflow with in_review status
- Add step to mark beads as 'in_review' after PR creation
- Add PR URL to bead notes for traceability
- Create reconcile_beads skill to close beads when PRs are merged
- Update summary table to show bead status instead of generic status

Implements bead: nixos-configs-85h
2026-01-10 12:38:06 -08:00
77 changed files with 1104 additions and 2708 deletions

5
.beads/.gitignore vendored
View File

@@ -32,11 +32,6 @@ beads.left.meta.json
beads.right.jsonl
beads.right.meta.json
# Sync state (local-only, per-machine)
# These files are machine-specific and should not be shared across clones
.sync.lock
sync_base.jsonl
# NOTE: Do NOT add negation patterns (e.g., !issues.jsonl) here.
# They would override fork protection in .git/info/exclude, allowing
# contributors to accidentally commit upstream issue databases.

View File

@@ -6,7 +6,7 @@
# Issue prefix for this repository (used by bd init)
# If not set, bd init will auto-detect from directory name
# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc.
issue-prefix: "x"
# issue-prefix: ""
# Use no-db mode: load from JSONL, no SQLite, write back after each command
# When true, bd will use .beads/issues.jsonl as the source of truth
@@ -59,6 +59,4 @@ sync-branch: "beads-sync"
# - linear.url
# - linear.api-key
# - github.org
# - github.repo
routing.mode: "explicit"
# - github.repo

View File

4
.beads/metadata.json Normal file
View File

@@ -0,0 +1,4 @@
{
"database": "beads.db",
"jsonl_export": "sync_base.jsonl"
}

26
.beads/sync_base.jsonl Normal file

File diff suppressed because one or more lines are too long

3
.gitattributes vendored
View File

@@ -0,0 +1,3 @@
# Use bd merge for beads JSONL files
.beads/issues.jsonl merge=beads

View File

@@ -1,107 +0,0 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: https://git.johnogle.info/johno/gitea-actions/nix-setup@v1
- name: Check flake
run: nix flake check
env:
NIX_CONFIG: "access-tokens = git.johnogle.info=${{ secrets.GITEA_ACCESS_TOKEN }}"
build-and-cache:
runs-on: ubuntu-latest
needs: check
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v6
- uses: https://git.johnogle.info/johno/gitea-actions/nix-setup@v1
- name: Setup SSH for cache
run: |
mkdir -p ~/.ssh
echo "${{ secrets.CACHE_SSH_KEY }}" > ~/.ssh/cache_key
chmod 600 ~/.ssh/cache_key
ssh-keyscan -H ${{ secrets.CACHE_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Setup signing key
run: |
echo "${{ secrets.NIX_SIGNING_KEY }}" > /tmp/signing-key
chmod 600 /tmp/signing-key
- name: Build, sign, and cache all packages
run: |
PACKAGES=(
custom-claude-code
custom-app-launcher-server
custom-mcrcon-rbw
custom-tea-rbw
custom-rclone-torbox-setup
custom-nextcloud-talk-desktop
qt-pinned-jellyfin-media-player
qt-pinned-stremio
nix-deck-kernel
plasma-bigscreen
)
FAILED=()
SKIPPED=()
for pkg in "${PACKAGES[@]}"; do
echo "::group::Building $pkg"
# Check if package is already cached by evaluating its store path and checking the remote
OUT_PATH=$(nix eval ".#$pkg.outPath" --raw 2>/dev/null)
if [ -n "$OUT_PATH" ] && ssh -i ~/.ssh/cache_key ${{ secrets.CACHE_USER }}@${{ secrets.CACHE_HOST }} \
"nix path-info '$OUT_PATH' >/dev/null 2>&1"; then
echo "⏭ $pkg already cached ($OUT_PATH), skipping"
SKIPPED+=("$pkg")
echo "::endgroup::"
continue
fi
# --cores 2 limits parallel jobs to reduce RAM pressure on john-endesktop
if BUILD_OUTPUT=$(nix build ".#$pkg" --no-link --print-out-paths --cores 2 2>&1); then
OUT_PATH=$(echo "$BUILD_OUTPUT" | grep '^/nix/store/' | tail -1)
echo "$BUILD_OUTPUT"
echo "Store path: $OUT_PATH"
# Sign the closure
nix store sign --key-file /tmp/signing-key -r "$OUT_PATH"
# Push to cache
nix copy --to "ssh-ng://${{ secrets.CACHE_USER }}@${{ secrets.CACHE_HOST }}?ssh-key=$HOME/.ssh/cache_key" "$OUT_PATH"
# Create GC root to prevent garbage collection
OUT_HASH=$(basename "$OUT_PATH" | cut -d'-' -f1)
ssh -i ~/.ssh/cache_key ${{ secrets.CACHE_USER }}@${{ secrets.CACHE_HOST }} \
"mkdir -p /nix/var/nix/gcroots/ci-cache && ln -sfn $OUT_PATH /nix/var/nix/gcroots/ci-cache/${OUT_HASH}"
echo "✓ $pkg cached successfully"
else
echo "✗ $pkg failed to build"
FAILED+=("$pkg")
fi
echo "::endgroup::"
done
if [ ${#SKIPPED[@]} -gt 0 ]; then
echo "Skipped (already cached): ${SKIPPED[*]}"
fi
if [ ${#FAILED[@]} -gt 0 ]; then
echo "::error::Failed packages: ${FAILED[*]}"
exit 1
fi
env:
NIX_CONFIG: "access-tokens = git.johnogle.info=${{ secrets.GITEA_ACCESS_TOKEN }}"

6
.gitignore vendored
View File

@@ -1,8 +1,2 @@
result
thoughts
.beads
# Gas Town (added by gt)
.runtime/
.claude/
.logs/

View File

@@ -9,7 +9,7 @@ Directory Structure:
----------------------
• packages/ - Custom Nix packages leveraged across various configurations.
• roles/ - Role-based configurations (e.g., kodi, bluetooth) each with its own module (default.nix) for inclusion in machine setups.
• machines/ - Machine-specific configurations (e.g., nix-book, zix790prors, boxy) including configuration.nix and hardware-configuration.nix tailored for each hardware.
• machines/ - Machine-specific configurations (e.g., nix-book, z790prors, boxy, wixos) including configuration.nix and hardware-configuration.nix tailored for each hardware.
• home/ - Home-manager configurations for personal environments and application settings (e.g., home-nix-book.nix, home-z790prors.nix).
Design Principles:

View File

@@ -10,7 +10,7 @@ This is a NixOS configuration repository using flakes, managing multiple machine
### Flake Structure
- **flake.nix**: Main entry point defining inputs (nixpkgs, home-manager, plasma-manager, etc.) and outputs for multiple NixOS configurations
- **Machines**: `nix-book`, `boxy`, `zix790prors`, `live-usb`, `johno-macbookpro` (Darwin/macOS)
- **Machines**: `nix-book`, `boxy`, `wixos` (WSL configuration), `zix790prors`, `live-usb`, `johno-macbookpro` (Darwin/macOS)
- **Home configurations**: Standalone home-manager configuration for user `johno`
### Directory Structure
@@ -74,6 +74,7 @@ The repository also uses a modular home-manager role system for user-space confi
- **nix-book**: Compact laptop → excludes office/media roles due to SSD space constraints
- **boxy**: Living room media center → optimized for media consumption, excludes sync/office (shared machine)
- **zix790prors**: All-purpose workstation → full desktop experience with all roles enabled
- **wixos**: WSL2 development → full desktop experience, inherits from zix790prors Windows host
- **live-usb**: Temporary environment → only base + desktop roles, no persistent services
- **johno-macbookpro**: macOS work laptop → Darwin-specific configuration with development tools
@@ -106,6 +107,7 @@ darwin-rebuild build --flake .#johno-macbookpro
- `nix-book`: Compact laptop with storage constraints, uses `home/home-laptop-compact.nix`
- `boxy`: Shared living room media center/gaming desktop with AMD GPU, uses `home/home-media-center.nix`
- `zix790prors`: Powerful all-purpose workstation (gaming, 3D modeling, development), dual-boots Windows 11 with shared btrfs /games partition, uses `home/home-desktop.nix`
- `wixos`: WSL2 development environment running in Windows partition of zix790prors, uses `home/home-desktop.nix`
- `live-usb`: Bootable ISO configuration, uses `home/home-live-usb.nix`
- `johno-macbookpro`: macOS work laptop, uses `home/home-darwin-work.nix`

4
scripts/bootstrap.sh → bootstrap.sh Normal file → Executable file
View File

@@ -1,7 +1,6 @@
#!/usr/bin/env bash
# bootstrap.sh
# Usage: nix run .#bootstrap -- <hostname>
# Or: sudo ./scripts/bootstrap.sh <hostname>
# Usage: sudo ./bootstrap.sh <hostname>
set -euo pipefail
NEW_HOSTNAME="${1:?missing hostname}"
@@ -9,3 +8,4 @@ FLAKE_URI="git+https://git.johnogle.info/johno/nixos-configs.git#${NEW_HOSTNAME}
export NIX_CONFIG="experimental-features = nix-command flakes"
nixos-rebuild switch --flake "$FLAKE_URI"

19
build-liveusb.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Build Live USB ISO from flake configuration
# Creates an uncompressed ISO suitable for Ventoy and other USB boot tools
set -e
echo "Building Live USB ISO..."
nix build .#nixosConfigurations.live-usb.config.system.build.isoImage --show-trace
if [ -f "./result/iso/"*.iso ]; then
iso_file=$(ls ./result/iso/*.iso)
echo "✅ Build complete!"
echo "📁 ISO location: $iso_file"
echo "💾 Ready for Ventoy or dd to USB"
else
echo "❌ Build failed - no ISO file found"
exit 1
fi

201
flake.lock generated
View File

@@ -1,41 +1,57 @@
{
"nodes": {
"doomemacs": {
"flake": false,
"locked": {
"lastModified": 1774080407,
"narHash": "sha256-FYbalilgDFjIVwK+D6DjDos1IMmMGA20lRf8k6Ykm1Y=",
"owner": "doomemacs",
"repo": "doomemacs",
"rev": "d8d75443d39d95f3c5256504eb838e0acc62ef44",
"type": "github"
},
"original": {
"owner": "doomemacs",
"repo": "doomemacs",
"type": "github"
}
},
"emacs-overlay": {
"beads": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nix-doom-emacs-unstraightened"
],
"nixpkgs-stable": [
"nix-doom-emacs-unstraightened"
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1774256052,
"narHash": "sha256-7OLaUBQCOCt4XXbjHq9xqBopOJJpbV6Cl8mWdMLzazc=",
"owner": "nix-community",
"repo": "emacs-overlay",
"rev": "c4b7915a9467aa611c7346d2322514cdf8c1ba45",
"lastModified": 1767911810,
"narHash": "sha256-0L4ATr01UsmBC0rSW62VIMVVSUihAQu2+ZOoHk9BQnA=",
"owner": "steveyegge",
"repo": "beads",
"rev": "28ff9fe9919a9665a0f00f5b3fcd084b43fb6cc3",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "emacs-overlay",
"owner": "steveyegge",
"repo": "beads",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1765121682,
"narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
@@ -46,11 +62,11 @@
]
},
"locked": {
"lastModified": 1768846578,
"narHash": "sha256-82f/+e8HAwmBukiLlr7I3HYvM/2GCd5SOc+BC+qzsOQ=",
"lastModified": 1761423376,
"narHash": "sha256-pMy3cnUFfue4vz/y0jx71BfcPGxZf+hk/DtnzWvfU0c=",
"ref": "refs/heads/main",
"rev": "c11ff9d3c67372a843a0fa6bf23132e986bd6955",
"revCount": 14,
"rev": "a1f695665771841a988afc965526cbf99160cd77",
"revCount": 11,
"type": "git",
"url": "https://git.johnogle.info/johno/google-cookie-retrieval.git"
},
@@ -66,11 +82,11 @@
]
},
"locked": {
"lastModified": 1774274588,
"narHash": "sha256-dnHvv5EMUgTzGZmA+3diYjQU2O6BEpGLEOgJ1Qe9LaY=",
"lastModified": 1767514898,
"narHash": "sha256-ONYqnKrPzfKEEPChoJ9qPcfvBqW9ZgieDKD7UezWPg4=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "cf9686ba26f5ef788226843bc31fda4cf72e373b",
"rev": "7a06e8a2f844e128d3b210a000a62716b6040b7f",
"type": "github"
},
"original": {
@@ -87,11 +103,11 @@
]
},
"locked": {
"lastModified": 1774292006,
"narHash": "sha256-RI5sjkDEwIiD2eZHd7iM6ZqPoPWZvn3KdBiMumA3IYI=",
"lastModified": 1767556355,
"narHash": "sha256-RDTUBDQBi9D4eD9iJQWtUDN/13MDLX+KmE+TwwNUp2s=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3cea83bf84abeb72581bdee380fa526d7fcd7e5b",
"rev": "f894bc4ffde179d178d8deb374fcf9855d1a82b7",
"type": "github"
},
"original": {
@@ -109,11 +125,11 @@
]
},
"locked": {
"lastModified": 1774168156,
"narHash": "sha256-+pwZSARdlM2RQQ6V0q76+WMKW9aNIcxkSOIThcz/f0A=",
"lastModified": 1767082077,
"narHash": "sha256-2tL1mRb9uFJThUNfuDm/ehrnPvImL/QDtCxfn71IEz4=",
"owner": "Jovian-Experiments",
"repo": "Jovian-NixOS",
"rev": "939caad56508542d0f19cab963e2bc693f5f2831",
"rev": "efd4b22e6fdc6d7fb4e186ae333a4b74e03da440",
"type": "github"
},
"original": {
@@ -129,11 +145,11 @@
]
},
"locked": {
"lastModified": 1772129556,
"narHash": "sha256-Utk0zd8STPsUJPyjabhzPc5BpPodLTXrwkpXBHYnpeg=",
"lastModified": 1765066094,
"narHash": "sha256-0YSU35gfRFJzx/lTGgOt6ubP8K6LeW0vaywzNNqxkl4=",
"owner": "nix-darwin",
"repo": "nix-darwin",
"rev": "ebec37af18215214173c98cf6356d0aca24a2585",
"rev": "688427b1aab9afb478ca07989dc754fa543e03d5",
"type": "github"
},
"original": {
@@ -143,27 +159,6 @@
"type": "github"
}
},
"nix-doom-emacs-unstraightened": {
"inputs": {
"doomemacs": "doomemacs",
"emacs-overlay": "emacs-overlay",
"nixpkgs": [],
"systems": "systems"
},
"locked": {
"lastModified": 1774265710,
"narHash": "sha256-ar8pFUSAxXhV7DpVRjNvgviWuqOqWPAImb4MM7lSh5Y=",
"owner": "marienz",
"repo": "nix-doom-emacs-unstraightened",
"rev": "f6022b9192e034a817373692ede18a9319cf9730",
"type": "github"
},
"original": {
"owner": "marienz",
"repo": "nix-doom-emacs-unstraightened",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
@@ -186,45 +181,49 @@
"type": "github"
}
},
"nixpkgs": {
"nixos-wsl": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1774244481,
"narHash": "sha256-4XfMXU0DjN83o6HWZoKG9PegCvKvIhNUnRUI19vzTcQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4590696c8693fea477850fe379a01544293ca4e2",
"lastModified": 1765841014,
"narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=",
"owner": "nix-community",
"repo": "NixOS-WSL",
"rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"owner": "nix-community",
"ref": "main",
"repo": "NixOS-WSL",
"type": "github"
}
},
"nixpkgs-qt": {
"nixpkgs": {
"locked": {
"lastModified": 1774244481,
"narHash": "sha256-4XfMXU0DjN83o6HWZoKG9PegCvKvIhNUnRUI19vzTcQ=",
"owner": "nixos",
"lastModified": 1765472234,
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4590696c8693fea477850fe379a01544293ca4e2",
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1774106199,
"narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=",
"lastModified": 1767379071,
"narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655",
"rev": "fb7944c166a3b630f177938e478f0378e64ce108",
"type": "github"
},
"original": {
@@ -234,6 +233,22 @@
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1767480499,
"narHash": "sha256-8IQQUorUGiSmFaPnLSo2+T+rjHtiNWc+OAzeHck7N48=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "30a3c519afcf3f99e2c6df3b359aec5692054d92",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"plasma-manager": {
"inputs": {
"home-manager": [
@@ -244,11 +259,11 @@
]
},
"locked": {
"lastModified": 1772361940,
"narHash": "sha256-B1Cz+ydL1iaOnGlwOFld/C8lBECPtzhiy/pP93/CuyY=",
"lastModified": 1763909441,
"narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=",
"owner": "nix-community",
"repo": "plasma-manager",
"rev": "a4b33606111c9c5dcd10009042bb710307174f51",
"rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4",
"type": "github"
},
"original": {
@@ -267,11 +282,11 @@
]
},
"locked": {
"lastModified": 1772361940,
"narHash": "sha256-B1Cz+ydL1iaOnGlwOFld/C8lBECPtzhiy/pP93/CuyY=",
"lastModified": 1763909441,
"narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=",
"owner": "nix-community",
"repo": "plasma-manager",
"rev": "a4b33606111c9c5dcd10009042bb710307174f51",
"rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4",
"type": "github"
},
"original": {
@@ -282,14 +297,14 @@
},
"root": {
"inputs": {
"beads": "beads",
"google-cookie-retrieval": "google-cookie-retrieval",
"home-manager": "home-manager",
"home-manager-unstable": "home-manager-unstable",
"jovian": "jovian",
"nix-darwin": "nix-darwin",
"nix-doom-emacs-unstraightened": "nix-doom-emacs-unstraightened",
"nixpkgs": "nixpkgs",
"nixpkgs-qt": "nixpkgs-qt",
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_2",
"nixpkgs-unstable": "nixpkgs-unstable",
"plasma-manager": "plasma-manager",
"plasma-manager-unstable": "plasma-manager-unstable"

566
flake.nix
View File

@@ -4,10 +4,8 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
# Separate nixpkgs for qt5webengine-dependent packages (jellyfin-media-player, etc.)
# Updates on separate Renovate schedule to avoid massive qt rebuilds
nixpkgs-qt.url = "github:nixos/nixpkgs/nixos-25.11";
nixos-wsl.url = "github:nix-community/NixOS-WSL/main";
nix-darwin = {
url = "github:nix-darwin/nix-darwin/nix-darwin-25.11";
inputs.nixpkgs.follows = "nixpkgs";
@@ -45,349 +43,245 @@
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
nix-doom-emacs-unstraightened = {
url = "github:marienz/nix-doom-emacs-unstraightened";
# Don't follow nixpkgs to avoid rebuild issues with emacs-overlay
inputs.nixpkgs.follows = "";
beads = {
url = "github:steveyegge/beads";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
};
outputs =
{
self,
nixpkgs,
nixpkgs-unstable,
...
}@inputs:
let
# Shared overlay function to reduce duplication across module sets
# Parameters:
# unstableOverlays: Additional overlays to apply when importing nixpkgs-unstable
mkBaseOverlay =
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, ... } @ inputs: let
nixosModules = [
./roles
] ++ [
inputs.home-manager.nixosModules.home-manager
{
nixpkgs.overlays = [
(final: prev: {
unstable = import nixpkgs-unstable {
system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
custom = prev.callPackage ./packages {};
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
})
];
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.sharedModules = [
inputs.plasma-manager.homeModules.plasma-manager
];
home-manager.extraSpecialArgs = {
globalInputs = inputs;
};
}
];
# Modules for unstable-based systems (like nix-deck)
nixosModulesUnstable = [
./roles
] ++ [
inputs.home-manager-unstable.nixosModules.home-manager
inputs.jovian.nixosModules.jovian
{
nixpkgs.overlays = [
(final: prev: {
unstable = import nixpkgs-unstable {
system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
custom = prev.callPackage ./packages {};
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
})
];
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.sharedModules = [
inputs.plasma-manager-unstable.homeModules.plasma-manager
];
home-manager.extraSpecialArgs = {
globalInputs = inputs;
};
}
];
darwinModules = [
./roles/darwin.nix
] ++ [
inputs.home-manager.darwinModules.home-manager
{
nixpkgs.overlays = [
(final: prev: {
unstable = import nixpkgs-unstable {
system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true;
overlays = [
# Override claude-code in unstable to use our custom GCS-based build
# (needed for corporate networks that block npm registry)
(ufinal: uprev: {
claude-code = prev.custom.claude-code or (prev.callPackage ./packages {}).claude-code;
})
];
};
custom = prev.callPackage ./packages {};
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
})
];
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = {
globalInputs = inputs;
};
}
];
in {
nixosConfigurations.nix-book = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/nix-book/configuration.nix
{
unstableOverlays ? [ ],
}:
(final: prev: {
unstable = import nixpkgs-unstable {
system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true;
overlays = unstableOverlays;
};
# Separate nixpkgs for qt5webengine-heavy packages to avoid rebuild churn
qt-pinned = import inputs.nixpkgs-qt {
system = prev.stdenv.hostPlatform.system;
config = {
allowUnfree = true;
permittedInsecurePackages = [ "qtwebengine-5.15.19" ];
home-manager.users.johno = {
imports = [ ./home/home-laptop-compact.nix ];
# Machine-specific overrides
home.roles.i3_sway.extraSwayConfig = {
output.eDP-1.scale = "1.75";
};
};
custom = prev.callPackage ./packages { };
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
});
# Shared home-manager configuration factory
# Parameters:
# sharedModules: Additional modules to include in home-manager.sharedModules
mkHomeManagerConfig =
{
sharedModules ? [ ],
}:
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.sharedModules = sharedModules ++ [
inputs.nix-doom-emacs-unstraightened.homeModule
];
home-manager.extraSpecialArgs = {
globalInputs = inputs;
};
};
# Shared unstable overlays for custom package builds
customUnstableOverlays = [
# Override claude-code in unstable to use our custom GCS-based build
# (needed for corporate networks that block npm registry)
(ufinal: uprev: {
claude-code = uprev.callPackage ./packages/claude-code { };
})
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
nixosModules = [
./roles
nixosConfigurations.boxy = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/boxy/configuration.nix
{
home-manager.users.johno = import ./home/home-media-center.nix;
home-manager.users.kodi = import ./home/home-kodi.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
nixosConfigurations.wixos = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
nixos-wsl.nixosModules.default
./machines/wixos/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
nixpkgs.overlays = [ (mkBaseOverlay { unstableOverlays = customUnstableOverlays; }) ];
home-manager.users.johno = import ./home/home-desktop.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
(mkHomeManagerConfig {
sharedModules = [ inputs.plasma-manager.homeModules.plasma-manager ];
})
];
# Modules for unstable-based systems (like nix-deck)
nixosModulesUnstable = [
./roles
inputs.home-manager-unstable.nixosModules.home-manager
inputs.jovian.nixosModules.jovian
{
nixpkgs.overlays = [ (mkBaseOverlay { unstableOverlays = customUnstableOverlays; }) ];
}
(mkHomeManagerConfig {
sharedModules = [ inputs.plasma-manager-unstable.homeModules.plasma-manager ];
})
];
darwinModules = [
./roles/darwin.nix
inputs.home-manager.darwinModules.home-manager
{
nixpkgs.overlays = [ (mkBaseOverlay { unstableOverlays = customUnstableOverlays; }) ];
}
(mkHomeManagerConfig { sharedModules = [ ]; })
];
in
{
nixosConfigurations.nix-book = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/nix-book/configuration.nix
{
home-manager.users.johno = {
imports = [ ./home/home-laptop-compact.nix ];
# Machine-specific overrides
home.roles.i3_sway.extraSwayConfig = {
output.eDP-1.scale = "1.75";
};
};
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
nixosConfigurations.boxy = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/boxy/configuration.nix
{
home-manager.users.johno = import ./home/home-media-center.nix;
# kodi user: AVR volume control + minimal Plasma config for Bigscreen session
home-manager.users.kodi = import ./home/home-kodi.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
nixosConfigurations.gym-box = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/gym-box/configuration.nix
{
home-manager.users.johno = import ./home/home-media-center.nix;
home-manager.users.kodi = import ./home/home-kodi.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
nixosConfigurations.zix790prors = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/zix790prors/configuration.nix
{
home-manager.users.johno = {
imports = [ ./home/home-desktop.nix ];
home.roles.i3_sway.extraSwayConfig = {
output = {
"DP-1" = {
mode = "3440x1440@164.900Hz";
};
};
};
};
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Live USB ISO configuration
nixosConfigurations.live-usb = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/live-usb/configuration.nix
{
home-manager.users.nixos = import ./home/home-live-usb.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Steam Deck configuration (using unstable for better Jovian compatibility)
nixosConfigurations.nix-deck = nixpkgs-unstable.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModulesUnstable ++ [
./machines/nix-deck/configuration.nix
{
home-manager.users.johno = import ./home/home-desktop.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# ZFS/NFS server configuration
nixosConfigurations.john-endesktop = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/john-endesktop/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
home-manager.users.johno = import ./home/home-server.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Darwin/macOS configurations
darwinConfigurations."BLKFV4YF49KT7" = inputs.nix-darwin.lib.darwinSystem rec {
system = "aarch64-darwin";
modules = darwinModules ++ [
./machines/johno-macbookpro/configuration.nix
{
home-manager.users.johno = import ./home/home-darwin-work.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Packages for CI caching (custom packages, flake inputs, and qt-pinned)
packages = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (
system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = [ (mkBaseOverlay { }) ];
};
pkgsQt = import inputs.nixpkgs-qt {
inherit system;
config = {
allowUnfree = true;
permittedInsecurePackages = [ "qtwebengine-5.15.19" ];
};
};
in
{
"custom-claude-code" = pkgs.custom.claude-code;
"custom-app-launcher-server" = pkgs.custom.app-launcher-server;
"custom-mcrcon-rbw" = pkgs.custom.mcrcon-rbw;
"custom-tea-rbw" = pkgs.custom.tea-rbw;
"custom-rclone-torbox-setup" = pkgs.custom.rclone-torbox-setup;
"custom-opencode" = pkgs.custom.opencode;
"qt-pinned-jellyfin-media-player" = pkgsQt.jellyfin-media-player;
"qt-pinned-stremio" = pkgsQt.stremio;
# Plasma Bigscreen — not yet in nixpkgs, built from upstream
"plasma-bigscreen" = pkgs.kdePackages.callPackage ./roles/plasma-bigscreen/package.nix { };
}
// (
if system == "x86_64-linux" then
{
"custom-nextcloud-talk-desktop" = pkgs.custom.nextcloud-talk-desktop;
# nix-deck kernel from Jovian-NixOS (Steam Deck) - expensive to build
"nix-deck-kernel" = self.nixosConfigurations.nix-deck.config.boot.kernelPackages.kernel;
}
else
{ }
)
);
# Flake apps
apps = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" ] (
system:
let
pkgs = import nixpkgs { inherit system; };
commonDeps = [
pkgs.curl
pkgs.jq
pkgs.nix
pkgs.git
pkgs.gnused
pkgs.gnugrep
pkgs.coreutils
pkgs.gawk
];
update-doomemacs = pkgs.writeShellScriptBin "update-doomemacs" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/update-doomemacs.sh}
'';
update-claude-code = pkgs.writeShellScriptBin "update-claude-code" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./packages/claude-code/update.sh}
'';
update-opencode = pkgs.writeShellScriptBin "update-opencode" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./packages/opencode/update.sh}
'';
rotate-wallpaper = pkgs.writeShellScriptBin "rotate-wallpaper" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/rotate-wallpaper.sh}
'';
upgrade = pkgs.writeShellScriptBin "upgrade" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/upgrade.sh}
'';
bootstrap = pkgs.writeShellScriptBin "bootstrap" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/bootstrap.sh}
'';
build-liveusb = pkgs.writeShellScriptBin "build-liveusb" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/build-liveusb.sh}
'';
in
{
update-doomemacs = {
type = "app";
program = "${update-doomemacs}/bin/update-doomemacs";
meta.description = "Update Doom Emacs configuration";
};
update-claude-code = {
type = "app";
program = "${update-claude-code}/bin/update-claude-code";
meta.description = "Update Claude Code package version";
};
update-opencode = {
type = "app";
program = "${update-opencode}/bin/update-opencode";
meta.description = "Update OpenCode package version";
};
rotate-wallpaper = {
type = "app";
program = "${rotate-wallpaper}/bin/rotate-wallpaper";
meta.description = "Rotate desktop wallpaper";
};
upgrade = {
type = "app";
program = "${upgrade}/bin/upgrade";
meta.description = "Upgrade NixOS configuration";
};
bootstrap = {
type = "app";
program = "${bootstrap}/bin/bootstrap";
meta.description = "Bootstrap a new NixOS machine";
};
build-liveusb = {
type = "app";
program = "${build-liveusb}/bin/build-liveusb";
meta.description = "Build a bootable Live USB ISO";
};
}
);
};
nixosConfigurations.zix790prors = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/zix790prors/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
home-manager.users.johno = import ./home/home-desktop.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Live USB ISO configuration
nixosConfigurations.live-usb = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/live-usb/configuration.nix
{
home-manager.users.nixos = import ./home/home-live-usb.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Steam Deck configuration (using unstable for better Jovian compatibility)
nixosConfigurations.nix-deck = nixpkgs-unstable.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModulesUnstable ++ [
./machines/nix-deck/configuration.nix
{
home-manager.users.johno = import ./home/home-desktop.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# ZFS/NFS server configuration
nixosConfigurations.john-endesktop = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/john-endesktop/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
home-manager.users.johno = import ./home/home-server.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Darwin/macOS configurations
darwinConfigurations."blkfv4yf49kt7" = inputs.nix-darwin.lib.darwinSystem rec {
system = "aarch64-darwin";
modules = darwinModules ++ [
./machines/johno-macbookpro/configuration.nix
{
home-manager.users.johno = import ./home/home-darwin-work.nix;
home-manager.extraSpecialArgs = { inherit system; };
}
];
};
# Flake apps
apps = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" ] (system:
let
pkgs = import nixpkgs { inherit system; };
commonDeps = [ pkgs.curl pkgs.jq pkgs.nix pkgs.git pkgs.gnused pkgs.gnugrep pkgs.coreutils pkgs.gawk ];
update-doomemacs = pkgs.writeShellScriptBin "update-doomemacs" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/update-doomemacs.sh}
'';
update-claude-code = pkgs.writeShellScriptBin "update-claude-code" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./packages/claude-code/update.sh}
'';
rotate-wallpaper = pkgs.writeShellScriptBin "rotate-wallpaper" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/rotate-wallpaper.sh}
'';
upgrade = pkgs.writeShellScriptBin "upgrade" ''
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
${builtins.readFile ./scripts/upgrade.sh}
'';
in {
update-doomemacs = {
type = "app";
program = "${update-doomemacs}/bin/update-doomemacs";
};
update-claude-code = {
type = "app";
program = "${update-claude-code}/bin/update-claude-code";
};
rotate-wallpaper = {
type = "app";
program = "${rotate-wallpaper}/bin/rotate-wallpaper";
};
upgrade = {
type = "app";
program = "${upgrade}/bin/upgrade";
};
}
);
};
}

View File

@@ -107,7 +107,7 @@
aerospace = {
enable = true;
leader = "cmd";
ctrlShortcuts.enable = false;
ctrlShortcuts.enable = true;
sketchybar.enable = true;
# Optional: Add per-machine userSettings overrides
# userSettings = {

View File

@@ -10,7 +10,6 @@
home.roles = {
"3d-printing".enable = true;
base.enable = true;
gaming.enable = true;
desktop.enable = true;
emacs.enable = true;
email.enable = true;

View File

@@ -12,7 +12,6 @@
home.roles = {
base.enable = true;
plasma-manager-kodi.enable = true;
kdeconnect.enable = true;
};
home.packages = with pkgs; [
@@ -25,6 +24,5 @@
imports = [
./roles
./roles/base-linux
];
}

View File

@@ -12,7 +12,6 @@
home.roles = {
base.enable = true;
desktop.enable = true;
gaming.enable = true;
development.enable = true;
communication.enable = true;
email.enable = true;

View File

@@ -14,12 +14,7 @@
desktop.enable = true;
tmux.enable = true;
plasma-manager.enable = true;
emacs = {
enable = true;
# Use pre-built Doom Emacs - all packages built at nix build time
# This means no doom sync is needed after booting the live USB
prebuiltDoom = true;
};
emacs.enable = true;
i3_sway.enable = true;
starship.enable = true;
# development.enable = false; # Not needed for live USB

View File

@@ -12,7 +12,6 @@
home.roles = {
base.enable = true;
desktop.enable = true;
gaming.enable = true;
media.enable = true;
communication.enable = true;
kdeconnect.enable = true;

View File

@@ -11,7 +11,6 @@
base.enable = true;
development.enable = true;
emacs.enable = true;
kubectl.enable = true;
starship.enable = true;
tmux.enable = true;
};

View File

@@ -632,9 +632,7 @@ in
text = ''
#!/bin/bash
# Monitor /System/Volumes/Data which contains user data on APFS
# The root / is a read-only snapshot with minimal usage
DISK_USAGE=$(df -H /System/Volumes/Data | grep -v Filesystem | awk '{print $5}')
DISK_USAGE=$(df -H / | grep -v Filesystem | awk '{print $5}')
${pkgs.sketchybar}/bin/sketchybar --set $NAME label="$DISK_USAGE"
'';

View File

@@ -3,7 +3,6 @@
# Includes Linux-specific roles that require Linux-only home-manager modules
imports = [
../plasma-manager
../plasma-manager-kodi
../i3+sway
];
}

View File

@@ -1,9 +1,4 @@
{
config,
lib,
pkgs,
...
}:
{ config, lib, pkgs, ... }:
with lib;
@@ -23,13 +18,10 @@ in
htop
killall
less
lnav
ncdu
shellcheck
tmux
tree
watch
custom.opencode
];
# Automatic garbage collection for user profile (home-manager generations).
@@ -60,7 +52,6 @@ in
programs.git = {
enable = true;
signing.format = null;
settings = {
user.name = "John Ogle";
user.email = "john@ogle.fyi";

View File

@@ -4,7 +4,6 @@ with lib;
let
cfg = config.home.roles.communication;
isLinux = pkgs.stdenv.isLinux;
in
{
options.home.roles.communication = {
@@ -13,14 +12,14 @@ in
config = mkIf cfg.enable {
home.packages = [
# For logging back into google chat (cross-platform)
globalInputs.google-cookie-retrieval.packages.${system}.default
] ++ optionals isLinux [
# Linux-only communication apps (Electron apps don't build on Darwin)
# Communication apps
pkgs.element-desktop
# Re-enabled in 25.11 after security issues were resolved
pkgs.fluffychat
pkgs.custom.nextcloud-talk-desktop
pkgs.nextcloud-talk-desktop
# For logging back into google chat
globalInputs.google-cookie-retrieval.packages.${system}.default
];
};
}

View File

@@ -15,6 +15,7 @@
./launchers
./media
./office
./plasma-manager-kodi
./sync
./tmux
./emacs

View File

@@ -4,7 +4,6 @@ with lib;
let
cfg = config.home.roles.desktop;
isLinux = pkgs.stdenv.isLinux;
in
{
options.home.roles.desktop = {
@@ -13,63 +12,61 @@ in
config = mkIf cfg.enable {
home.packages = with pkgs; [
# Cross-platform desktop applications
# Desktop applications
bitwarden-desktop
keepassxc
xdg-utils # XDG utilities for opening files/URLs with default applications
] ++ optionals isLinux [
# Linux-only desktop applications
dunst
keepassxc
unstable.ghostty
# Linux-only desktop utilities
# Desktop utilities
feh # Image viewer and wallpaper setter for X11
rofi # Application launcher for X11
solaar # Logitech management software
waybar
wofi # Application launcher for Wayland
# Linux-only system utilities with GUI components
xdg-utils # XDG utilities for opening files/URLs with default applications
# System utilities with GUI components
(snapcast.override { pulseaudioSupport = true; })
# KDE tiling window management (Linux-only)
# KDE tiling window management
kdePackages.krohnkite # Dynamic tiling extension for KWin 6
# KDE PIM applications for email, calendar, and contacts (Linux-only)
# KDE PIM applications for email, calendar, and contacts
kdePackages.kmail
kdePackages.kmail-account-wizard
kdePackages.kmailtransport
kdePackages.korganizer
kdePackages.kaddressbook
kdePackages.kontact
# KDE System components needed for proper integration (Linux-only)
# KDE System components needed for proper integration
kdePackages.kded
kdePackages.systemsettings
kdePackages.kmenuedit
# Desktop menu support (Linux-only)
# Desktop menu support
kdePackages.plasma-desktop # Contains applications.menu
# KDE Online Accounts support (Linux-only)
# KDE Online Accounts support
kdePackages.kaccounts-integration
kdePackages.kaccounts-providers
kdePackages.signond
# KDE Mapping (Linux-only)
# KDE Mapping
kdePackages.marble # Virtual globe and world atlas
# KDE Productivity (Linux-only)
# KDE Productivity
kdePackages.kate # Advanced text editor with syntax highlighting
kdePackages.okular # Universal document viewer (PDF, ePub, etc.)
kdePackages.spectacle # Screenshot capture utility
kdePackages.filelight # Visual disk usage analyzer
# KDE Multimedia (Linux-only)
# KDE Multimedia
kdePackages.gwenview # Image viewer and basic editor
kdePackages.elisa # Music player
# KDE System Utilities (Linux-only)
# KDE System Utilities
kdePackages.ark # Archive manager (zip, tar, 7z, etc.)
kdePackages.yakuake # Drop-down terminal emulator
];
@@ -80,66 +77,61 @@ in
programs.spotify-player.enable = true;
# Linux-only: GNOME keyring service
services.gnome-keyring = mkIf isLinux {
services.gnome-keyring = {
enable = true;
};
# Linux-only: systemd user services for rbw vault unlock
systemd.user.services = mkIf isLinux {
# rbw vault unlock on login
rbw-unlock-on-login = {
Unit = {
Description = "Unlock rbw vault at login";
After = [ "graphical-session.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.rbw}/bin/rbw unlock";
Environment = "RBW_AGENT=${pkgs.rbw}/bin/rbw-agent";
# KillMode = "process" prevents systemd from killing the rbw-agent daemon
# when this oneshot service completes. The agent is spawned by rbw unlock
# and needs to persist after the service exits.
KillMode = "process";
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
# rbw vault unlock on login and resume from suspend
systemd.user.services.rbw-unlock-on-login = {
Unit = {
Description = "Unlock rbw vault at login";
After = [ "graphical-session.target" ];
};
# rbw vault unlock on resume from suspend
rbw-unlock-on-resume = {
Unit = {
Description = "Unlock rbw vault after resume from suspend";
After = [ "suspend.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.rbw}/bin/rbw unlock";
Environment = "RBW_AGENT=${pkgs.rbw}/bin/rbw-agent";
# KillMode = "process" prevents systemd from killing the rbw-agent daemon
# when this oneshot service completes. The agent is spawned by rbw unlock
# and needs to persist after the service exits.
KillMode = "process";
};
Install = {
WantedBy = [ "suspend.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.rbw}/bin/rbw unlock";
Environment = "RBW_AGENT=${pkgs.rbw}/bin/rbw-agent";
# KillMode = "process" prevents systemd from killing the rbw-agent daemon
# when this oneshot service completes. The agent is spawned by rbw unlock
# and needs to persist after the service exits.
KillMode = "process";
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
# Linux-only: KDE environment variables for proper integration
home.sessionVariables = mkIf isLinux {
systemd.user.services.rbw-unlock-on-resume = {
Unit = {
Description = "Unlock rbw vault after resume from suspend";
After = [ "suspend.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.rbw}/bin/rbw unlock";
Environment = "RBW_AGENT=${pkgs.rbw}/bin/rbw-agent";
# KillMode = "process" prevents systemd from killing the rbw-agent daemon
# when this oneshot service completes. The agent is spawned by rbw unlock
# and needs to persist after the service exits.
KillMode = "process";
};
Install = {
WantedBy = [ "suspend.target" ];
};
};
# KDE environment variables for proper integration
home.sessionVariables = {
QT_QPA_PLATFORMTHEME = "kde";
KDE_SESSION_VERSION = "6";
};
xdg = {
enable = true;
# Ensure desktop files are made available for discovery
desktopEntries = {}; # This creates the desktop files directory structure
mimeApps = {
enable = true;
associations.added = {
@@ -149,14 +141,13 @@ in
"x-scheme-handler/https" = "firefox.desktop";
};
defaultApplications = {
# Web browsers (cross-platform)
# Web browsers
"text/html" = "firefox.desktop";
"x-scheme-handler/http" = "firefox.desktop";
"x-scheme-handler/https" = "firefox.desktop";
"x-scheme-handler/about" = "firefox.desktop";
"x-scheme-handler/unknown" = "firefox.desktop";
} // optionalAttrs isLinux {
# Linux-only: KDE application associations
# Documents
"application/pdf" = "okular.desktop";
"text/plain" = "kate.desktop";
@@ -164,7 +155,7 @@ in
"text/x-c" = "kate.desktop";
"text/x-python" = "kate.desktop";
"application/x-shellscript" = "kate.desktop";
# Images
"image/png" = "gwenview.desktop";
"image/jpeg" = "gwenview.desktop";
@@ -173,25 +164,25 @@ in
"image/bmp" = "gwenview.desktop";
"image/tiff" = "gwenview.desktop";
"image/webp" = "gwenview.desktop";
# Archives
"application/zip" = "ark.desktop";
"application/x-tar" = "ark.desktop";
"application/x-compressed-tar" = "ark.desktop";
"application/x-7z-compressed" = "ark.desktop";
"application/x-rar" = "ark.desktop";
# Audio
"audio/mpeg" = "elisa.desktop";
"audio/mp4" = "elisa.desktop";
"audio/flac" = "elisa.desktop";
"audio/ogg" = "elisa.desktop";
"audio/wav" = "elisa.desktop";
# Email
"message/rfc822" = "kmail.desktop";
"x-scheme-handler/mailto" = "kmail.desktop";
# Calendar
"text/calendar" = "korganizer.desktop";
"application/x-vnd.akonadi.calendar.event" = "korganizer.desktop";
@@ -199,11 +190,9 @@ in
};
};
# Linux-only: Fix for KDE applications.menu file issue on Plasma 6
# Fix for KDE applications.menu file issue on Plasma 6
# KDE still looks for applications.menu but Plasma 6 renamed it to plasma-applications.menu
xdg.configFile."menus/applications.menu" = mkIf isLinux {
source = "${pkgs.kdePackages.plasma-workspace}/etc/xdg/menus/plasma-applications.menu";
};
xdg.configFile."menus/applications.menu".source = "${pkgs.kdePackages.plasma-workspace}/etc/xdg/menus/plasma-applications.menu";
# Note: modules must be imported at top-level home config
};

View File

@@ -15,12 +15,6 @@ let
ref = "main";
};
# Claude Code statusline: shows model, cwd, git branch, and context usage %
claudeCodeStatusLineConfig = pkgs.writeText "claude-statusline.json" (builtins.toJSON {
type = "command";
command = ''input=$(cat); model=$(echo "$input" | jq -r '.model.display_name'); cwd=$(echo "$input" | jq -r '.workspace.current_dir'); if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then branch=$(git -C "$cwd" --no-optional-locks rev-parse --abbrev-ref HEAD 2>/dev/null || echo ""); if [ -n "$branch" ]; then git_info=" on $branch"; else git_info=""; fi; else git_info=""; fi; usage=$(echo "$input" | jq '.context_window.current_usage'); if [ "$usage" != "null" ]; then current=$(echo "$usage" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens'); size=$(echo "$input" | jq '.context_window.context_window_size'); pct=$((current * 100 / size)); context_info=" | ''${pct}% context"; else context_info=""; fi; printf "%s in %s%s%s" "$model" "$cwd" "$git_info" "$context_info"'';
});
in
{
options.home.roles.development = {
@@ -43,13 +37,14 @@ in
config = mkIf cfg.enable {
home.packages = [
globalInputs.beads.packages.${system}.default
pkgs.unstable.claude-code
pkgs.unstable.claude-code-router
pkgs.unstable.codex
pkgs.sqlite
# Custom packages
pkgs.custom.tea-rbw
pkgs.custom.pi-coding-agent
pkgs.custom.perles
];
# Install Claude Code humanlayer command and agent plugins
@@ -58,9 +53,6 @@ in
rm -f ~/.claude/commands/humanlayer:* 2>/dev/null || true
rm -f ~/.claude/agents/humanlayer:* 2>/dev/null || true
# Remove explicitly blocked commands that may have been installed previously
rm -f ~/.claude/commands/humanlayer:create_handoff.md 2>/dev/null || true
# Create directories if they don't exist
mkdir -p ~/.claude/commands
mkdir -p ~/.claude/agents
@@ -69,21 +61,13 @@ in
for file in ${claudePluginsRepo}/humanlayer/commands/*.md; do
if [ -f "$file" ]; then
filename=$(basename "$file" .md)
# Skip blocked commands
case "$filename" in
create_handoff) continue ;;
esac
dest="$HOME/.claude/commands/humanlayer:''${filename}.md"
rm -f "$dest" 2>/dev/null || true
# Copy file and conditionally remove the "model:" line from frontmatter
${if cfg.allowArbitraryClaudeCodeModelSelection
then "cp \"$file\" \"$dest\""
else "${pkgs.gnused}/bin/sed '/^model:/d' \"$file\" > \"$dest\""
}
chmod u+w "$dest" 2>/dev/null || true
fi
done
@@ -92,30 +76,44 @@ in
if [ -f "$file" ]; then
filename=$(basename "$file" .md)
dest="$HOME/.claude/agents/humanlayer:''${filename}.md"
rm -f "$dest" 2>/dev/null || true
# Copy file and conditionally remove the "model:" line from frontmatter
${if cfg.allowArbitraryClaudeCodeModelSelection
then "cp \"$file\" \"$dest\""
else "${pkgs.gnused}/bin/sed '/^model:/d' \"$file\" > \"$dest\""
}
chmod u+w "$dest" 2>/dev/null || true
fi
done
$DRY_RUN_CMD echo "Claude Code plugins installed: humanlayer commands/agents"
# Copy local skills from this repo (with retry for race conditions with running Claude)
for file in ${./skills}/*.md; do
if [ -f "$file" ]; then
filename=$(basename "$file" .md)
dest="$HOME/.claude/commands/''${filename}.md"
# Remove existing file first, then copy with retry on failure
rm -f "$dest" 2>/dev/null || true
if ! cp "$file" "$dest" 2>/dev/null; then
sleep 0.5
cp "$file" "$dest" || echo "Warning: Failed to copy $filename.md to commands"
fi
fi
done
$DRY_RUN_CMD echo "Claude Code humanlayer commands and agents installed successfully${
if cfg.allowArbitraryClaudeCodeModelSelection
then " (model specifications preserved)"
else " (model selection removed)"
} + local skills"
'';
# Configure Claude Code statusline (merge into existing settings.json)
home.activation.claudeCodeStatusLine = lib.hm.dag.entryAfter ["writeBoundary"] ''
SETTINGS="$HOME/.claude/settings.json"
mkdir -p "$HOME/.claude"
if [ -f "$SETTINGS" ]; then
${pkgs.jq}/bin/jq --slurpfile sl ${claudeCodeStatusLineConfig} '.statusLine = $sl[0]' "$SETTINGS" > "''${SETTINGS}.tmp" && mv "''${SETTINGS}.tmp" "$SETTINGS"
else
${pkgs.jq}/bin/jq -n --slurpfile sl ${claudeCodeStatusLineConfig} '{statusLine: $sl[0]}' > "$SETTINGS"
fi
$DRY_RUN_CMD echo "Claude Code statusline configured"
# Set up beads Claude Code integration (hooks for SessionStart/PreCompact)
# This uses the CLI + hooks approach which is recommended over MCP for Claude Code
home.activation.claudeCodeBeadsSetup = lib.hm.dag.entryAfter ["writeBoundary" "claudeCodeCommands"] ''
# Run bd setup claude to install hooks into ~/.claude/settings.json
# This is idempotent - safe to run multiple times
${globalInputs.beads.packages.${system}.default}/bin/bd setup claude 2>/dev/null || true
$DRY_RUN_CMD echo "Claude Code beads integration configured (hooks installed)"
'';
# Note: modules must be imported at top-level home config

View File

@@ -0,0 +1,205 @@
---
description: Orchestrate parallel bead processing with worktrees, PRs, and reviews
---
# Parallel Beads Workflow
This skill orchestrates parallel bead processing using subagents. Each bead gets its own worktree, implementation, PR, and review.
## Phase 1: Selection
1. **Get ready beads**: Run `bd ready` to list all beads with no blockers
2. **Present selection**: Use `AskUserQuestion` with `multiSelect: true` to let the user choose which beads to work on
- Include bead ID and title for each option
- Allow selection of multiple beads
Example:
```
AskUserQuestion with:
- question: "Which beads do you want to work on in parallel?"
- multiSelect: true
- options from bd ready output
```
## Phase 2: Parallel Implementation
For each selected bead, launch a subagent using the Task tool. All subagents should be launched in parallel (single message with multiple Task tool calls).
### Subagent Instructions Template
Each implementation subagent should receive these instructions:
```
Work on bead [BEAD_ID]: [BEAD_TITLE]
1. **Create worktree**:
- Branch name: `bead/[BEAD_ID]`
- Worktree path: `~/wt/[REPO_NAME]/[BEAD_ID]`
- Command: `git worktree add -b bead/[BEAD_ID] ~/wt/[REPO_NAME]/[BEAD_ID]`
2. **Review the bead requirements**:
- Run `bd show [BEAD_ID]` to understand the acceptance criteria
- Note any external issue references (GitHub issues, Linear tickets, etc.)
3. **Implement the changes**:
- Work in the worktree directory
- Complete all acceptance criteria listed in the bead
- Run any relevant tests or checks
4. **Commit and push**:
- Stage all changes: `git add -A`
- Create a descriptive commit message
- Push the branch: `git push -u origin bead/[BEAD_ID]`
5. **Create a PR**:
- Detect hosting provider from origin URL: `git remote get-url origin`
- If URL contains `github.com`, use `gh`; otherwise use `tea` (Gitea/Forgejo)
- PR title: "[BEAD_ID] [BEAD_TITLE]"
- PR body must include:
- Reference to bead ID: "Implements bead: [BEAD_ID]"
- Any external issue references from the bead (e.g., "Closes #123")
- Summary of changes
- For GitHub (`gh`):
```bash
gh pr create --title "[BEAD_ID] [BEAD_TITLE]" --body "$(cat <<'EOF'
## Summary
[Brief description of changes]
## Bead Reference
Implements bead: [BEAD_ID]
## External Issues
[Any linked issues from the bead]
## Changes
- [List of changes made]
EOF
)"
```
- For Gitea (`tea`):
```bash
tea pr create --head bead/[BEAD_ID] --base main \
--title "[BEAD_ID] [BEAD_TITLE]" \
--description "## Summary
[Brief description of changes]
## Bead Reference
Implements bead: [BEAD_ID]
## External Issues
[Any linked issues from the bead]
## Changes
- [List of changes made]"
```
6. **Update bead status**:
- Mark the bead as "in_review": `bd update [BEAD_ID] --status=in_review`
- Add the PR URL to the bead notes: `bd update [BEAD_ID] --notes="$(bd show [BEAD_ID] --json | jq -r '.notes')
PR: [PR_URL]"`
7. **Report results**:
- Return: PR URL, bead ID, success/failure status
- If blocked or unable to complete, explain what's blocking progress
```
### Launching Subagents
Use `subagent_type: "general-purpose"` for implementation subagents. Launch all selected beads' subagents in a single message for parallel execution:
```
<Task calls for each selected bead - all in one message>
```
Collect results from all subagents before proceeding.
## Phase 3: Parallel Review
After all implementation subagents complete, launch review subagents for each PR.
### Review Subagent Instructions Template
```
Review PR for bead [BEAD_ID]
1. **Detect hosting provider**: Run `git remote get-url origin` - if it contains `github.com` use `gh`, otherwise use `tea`
2. **Read the PR**:
- For GitHub: `gh pr view [PR_NUMBER] --json title,body,additions,deletions,files`
- For Gitea: `tea pr view [PR_NUMBER]`
- View the diff: `git diff main...bead/[BEAD_ID]`
3. **Review against acceptance criteria**:
- Run `bd show [BEAD_ID]` to get the acceptance criteria
- Verify each criterion is addressed
4. **Leave review comments**:
- For GitHub: `gh pr review [PR_NUMBER] --comment --body "[COMMENTS]"`
- For Gitea: `tea pr review [PR_NUMBER] --comment "[COMMENTS]"`
- Include:
- Acceptance criteria checklist (which are met, which might be missing)
- Code quality observations
- Suggestions for improvement
5. **Return summary**:
- Overall assessment (ready to merge / needs changes)
- Key findings
```
Launch all review subagents in parallel.
## Phase 4: Cleanup and Summary
After reviews complete:
1. **Clean up worktrees**:
```bash
git worktree remove ~/wt/[REPO_NAME]/[BEAD_ID] --force
```
Do this for each bead's worktree.
2. **Provide final summary**:
Present a table or list with:
- Bead ID
- PR URL
- Status (success / failed / blocked)
- Review summary
- Any failures or blockers encountered
Example output:
```
## Parallel Beads Summary
| Bead | PR | Bead Status | Review |
|------|-----|-------------|--------|
| beads-abc | #123 | in_review | Approved |
| beads-xyz | #124 | in_review | Needs changes |
| beads-123 | - | open (failed) | Blocked by missing dependency |
### Failures/Blockers
- beads-123: Could not complete because [reason]
### Next Steps
- Review PRs that need changes
- Address blockers for failed beads
- Run `/reconcile_beads` after PRs are merged to close beads
```
## Error Handling
- **Subagent failures**: If a subagent fails or times out, note it in the summary but continue with other beads
- **PR creation failures**: Report the error but continue with reviews of successful PRs
- **Worktree conflicts**: If a worktree already exists, ask the user if they want to remove it or skip that bead
## Resource Limits
- Consider limiting concurrent subagents to 3-5 to avoid overwhelming system resources
- If user selects more beads than the limit, process them in batches
## Notes
- This workflow integrates with the beads system (`bd` commands)
- Worktrees are created in `~/wt/[REPO_NAME]/` by convention
- Each bead gets its own isolated branch and worktree
- PRs automatically reference the bead ID for traceability

View File

@@ -0,0 +1,85 @@
---
description: Reconcile beads with merged PRs and close completed beads
---
# Reconcile Beads Workflow
This skill reconciles beads that are in `in_review` status with their corresponding PRs. If a PR has been merged, the bead is closed.
## Prerequisites
- Custom status `in_review` must be configured: `bd config set status.custom "in_review"`
- Beads in `in_review` status should have a PR URL in their notes
## Workflow
### Step 1: Find beads in review
```bash
bd list --status=in_review
```
### Step 2: For each bead, check PR status
1. **Get the PR URL from bead notes**:
```bash
bd show [BEAD_ID] --json | jq -r '.notes'
```
Extract the PR URL (look for lines starting with "PR:" or containing pull request URLs)
2. **Detect hosting provider**:
- Run `git remote get-url origin`
- If URL contains `github.com`, use `gh`; otherwise use `tea` (Gitea/Forgejo)
3. **Check PR status**:
- For GitHub:
```bash
gh pr view [PR_NUMBER] --json state,merged
```
- For Gitea:
```bash
tea pr view [PR_NUMBER]
```
Look for "State: merged" or "State: closed"
### Step 3: Close merged beads
If the PR is merged:
```bash
bd close [BEAD_ID] --reason="PR merged: [PR_URL]"
```
### Step 4: Report summary
Present results:
```
## Beads Reconciliation Summary
### Closed (PR Merged)
| Bead | PR | Title |
|------|-----|-------|
| beads-abc | #123 | Feature X |
| beads-xyz | #456 | Bug fix Y |
### Still in Review
| Bead | PR | Status | Title |
|------|-----|--------|-------|
| beads-def | #789 | Open | Feature Z |
### Issues Found
- beads-ghi: No PR URL found in notes
- beads-jkl: PR #999 not found (may have been deleted)
```
## Error Handling
- **Missing PR URL**: Skip the bead and report it
- **PR not found**: Report the error but continue with other beads
- **API errors**: Report and continue
## Notes
- This skill complements `/parallel_beads` which sets beads to `in_review` status
- Run this skill periodically or after merging PRs to keep beads in sync
- Beads with closed (but not merged) PRs are not automatically closed - they may need rework

View File

@@ -1,9 +1,4 @@
{
config,
lib,
pkgs,
...
}:
{ config, lib, pkgs, ... }:
with lib;
@@ -13,8 +8,8 @@ let
doomEmacs = pkgs.fetchFromGitHub {
owner = "doomemacs";
repo = "doomemacs";
rev = "d23bbe87721c61f4d5a605f2914b32780bb89949";
sha256 = "sha256-z+3c0AGkrMf1xZ+pq57aVp4Zo4KsqFMIjEVzSZinghc=";
rev = "38d94da67dc84897a4318714dcc48494c016d8c4";
sha256 = "sha256-Uc6qONH3jjUVDgW+pPBCGC7mh88ZY05u1y37fQrsxq0=";
};
# Shared emacs packages
@@ -25,94 +20,58 @@ let
# Default emacs configuration with vterm support
defaultEmacsPackage =
if pkgs.stdenv.isDarwin then
pkgs.emacs-macport.pkgs.withPackages emacsPackages
else
pkgs.emacs.pkgs.withPackages emacsPackages;
# Path to doom config directory (relative to this file)
doomConfigDir = ./doom;
if pkgs.stdenv.isDarwin
then pkgs.emacs-macport.pkgs.withPackages emacsPackages
else pkgs.emacs.pkgs.withPackages emacsPackages;
in
{
options.home.roles.emacs = {
enable = mkEnableOption "Doom Emacs with vterm and tree-sitter support";
prebuiltDoom = mkOption {
type = types.bool;
default = false;
description = ''
Use nix-doom-emacs-unstraightened to pre-build all Doom packages at
nix build time. This eliminates the need to run `doom sync` after
first boot, making it ideal for live USB images or immutable systems.
When enabled, the doom configuration is read-only (stored in nix store).
'';
};
};
config = mkIf cfg.enable (mkMerge [
# Common configuration for both modes
{
home.packages = [
pkgs.emacs-all-the-icons-fonts
pkgs.fira-code
pkgs.fontconfig
pkgs.graphviz
pkgs.isort
pkgs.nerd-fonts.fira-code
pkgs.nerd-fonts.droid-sans-mono
pkgs.nil # nix lsp language server
pkgs.nixfmt
(pkgs.ripgrep.override { withPCRE2 = true; })
pkgs.pipenv
pkgs.poetry
pkgs.python3
];
config = mkIf cfg.enable {
home.packages = [
pkgs.emacs-all-the-icons-fonts
pkgs.fira-code
pkgs.fontconfig
pkgs.graphviz
pkgs.isort
pkgs.nerd-fonts.fira-code
pkgs.nerd-fonts.droid-sans-mono
pkgs.nil # nix lsp language server
pkgs.nixfmt-rfc-style
(pkgs.ripgrep.override {withPCRE2 = true;})
pkgs.pipenv
pkgs.poetry
pkgs.python3
];
fonts.fontconfig.enable = true;
}
programs.emacs = {
enable = true;
package = defaultEmacsPackage;
};
# Standard Doom Emacs mode (requires doom sync at runtime)
(mkIf (!cfg.prebuiltDoom) {
programs.emacs = {
enable = true;
package = defaultEmacsPackage;
};
fonts.fontconfig.enable = true;
# Mount emacs and tree-sitter grammars from nix store
home.file = {
"${config.xdg.configHome}/emacs".source = doomEmacs;
};
# Mount emacs and tree-sitter grammars from nix store
home.file = {
"${config.xdg.configHome}/emacs".source = doomEmacs;
};
home.sessionPath = [
"${config.xdg.configHome}/emacs/bin"
];
home.sessionPath = [
"${config.xdg.configHome}/emacs/bin"
];
home.sessionVariables = {
DOOMDIR = "${config.xdg.configHome}/doom";
DOOMLOCALDIR = "${config.xdg.dataHome}/doom";
};
home.sessionVariables = {
DOOMDIR = "${config.xdg.configHome}/doom";
DOOMLOCALDIR = "${config.xdg.dataHome}/doom";
};
# TODO: Use mkOutOfStoreSymlink instead?
home.activation.doomConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
# Always remove and recreate the symlink to ensure it points to the source directory
rm -rf "${config.xdg.configHome}/doom"
ln -sf "${config.home.homeDirectory}/nixos-configs/home/roles/emacs/doom" "${config.xdg.configHome}/doom"
'';
})
# Pre-built Doom Emacs mode (no doom sync needed - ideal for live USB)
(mkIf cfg.prebuiltDoom {
programs.doom-emacs = {
enable = true;
doomDir = doomConfigDir;
doomLocalDir = "${config.xdg.dataHome}/doom";
# Add extra packages that aren't part of Doom but needed for our config
extraPackages = epkgs: [
epkgs.vterm
epkgs.treesit-grammars.with-all-grammars
];
};
})
]);
# TODO: Use mkOutOfStoreSymlink instead?
home.activation.doomConfig = lib.hm.dag.entryAfter ["writeBoundary"] ''
# Always remove and recreate the symlink to ensure it points to the source directory
rm -rf "${config.xdg.configHome}/doom"
ln -sf "${config.home.homeDirectory}/nixos-configs/home/roles/emacs/doom" "${config.xdg.configHome}/doom"
'';
};
}

View File

@@ -53,22 +53,6 @@
;; change `org-directory'. It must be set before org loads!
(setq org-directory "~/org/")
(after! org
;; Skip recurring events past their CALDAV_UNTIL date
;; org-caldav ignores UNTIL from RRULE, so we store it as a property
;; and filter here in the agenda
(defun my/skip-if-past-until ()
"Return non-nil if entry has CALDAV_UNTIL and current date is past it."
(let ((until-str (org-entry-get nil "CALDAV_UNTIL")))
(when (and until-str
(string-match "^\\([0-9]\\{4\\}\\)\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)" until-str))
(let* ((until-year (string-to-number (match-string 1 until-str)))
(until-month (string-to-number (match-string 2 until-str)))
(until-day (string-to-number (match-string 3 until-str)))
(until-time (encode-time 0 0 0 until-day until-month until-year))
(today (current-time)))
(when (time-less-p until-time today)
(org-end-of-subtree t))))))
(setq org-agenda-span 'week
org-agenda-start-with-log-mode t
my-agenda-dirs '("projects" "roam")
@@ -77,7 +61,6 @@
"\.org$"))
my-agenda-dirs))
org-log-done 'time
org-agenda-skip-function-global #'my/skip-if-past-until
org-agenda-custom-commands '(("n" "Agenda"
((agenda "")
(tags-todo "-someday-recurring")))
@@ -100,135 +83,25 @@
"d" #'org-agenda-day-view
"w" #'org-agenda-week-view))
;; org-caldav: Sync Org entries with Nextcloud CalDAV
;; Setup requirements:
;; 1. Create Nextcloud app password: Settings -> Security -> Devices & sessions
;; 2. Store in rbw: rbw add nextcloud-caldav (put app password as the secret)
;; 3. Run: doom sync
;; 4. Test: M-x my/org-caldav-sync-with-rbw (or SPC o a s)
;;
;; Note: Conflict resolution is "Org always wins" - treat Org as source of truth
;; for entries that originated in Org.
;; (use-package! org-caldav
;; :defer t
;; :config
;; (setq org-caldav-url "https://nextcloud.johnogle.info/remote.php/dav/calendars/johno"
;; org-caldav-calendar-id "personal"
;; org-icalendar-timezone "America/Los_Angeles"
;; org-caldav-inbox "~/org/calendar.org"
;; org-caldav-files nil
;; org-caldav-sync-direction 'cal->org))
;; Define sync wrapper before use-package (so keybinding works)
(defun my/org-caldav-sync-with-rbw ()
"Run org-caldav-sync with credentials from rbw embedded in URL."
(interactive)
(require 'org)
(require 'org-caldav)
(let* ((password (my/get-rbw-password "nextcloud-caldav"))
;; Embed credentials in URL (url-encode password in case of special chars)
(encoded-pass (url-hexify-string password)))
(setq org-caldav-url
(format "https://johno:%s@nextcloud.johnogle.info/remote.php/dav/calendars/johno"
encoded-pass))
(org-caldav-sync)))
(use-package! org-caldav
:after org
:commands (org-caldav-sync my/org-caldav-sync-with-rbw)
:init
(map! :leader
(:prefix ("o" . "open")
(:prefix ("a" . "agenda/calendar")
:desc "Sync CalDAV" "s" #'my/org-caldav-sync-with-rbw)))
:config
;; Nextcloud CalDAV base URL (credentials added dynamically by sync wrapper)
(setq org-caldav-url "https://nextcloud.johnogle.info/remote.php/dav/calendars/johno")
;; Timezone for iCalendar export
(setq org-icalendar-timezone "America/Los_Angeles")
;; Sync state storage (in org directory for multi-machine sync)
(setq org-caldav-save-directory (expand-file-name ".org-caldav/" org-directory))
;; Backup file for entries before modification
(setq org-caldav-backup-file (expand-file-name ".org-caldav/backup.org" org-directory))
;; Limit past events to 30 days (avoids uploading years of scheduled tasks)
(setq org-caldav-days-in-past 30)
;; Sync behavior: bidirectional by default
(setq org-caldav-sync-direction 'twoway)
;; What changes from calendar sync back to Org (conservative: title and timestamp only)
(setq org-caldav-sync-changes-to-org 'title-and-timestamp)
;; Deletion handling: never auto-delete to prevent accidental mass deletion
(setq org-caldav-delete-calendar-entries 'never)
(setq org-caldav-delete-org-entries 'never)
;; Enable TODO/VTODO sync
(setq org-icalendar-include-todo 'all)
(setq org-caldav-sync-todo t)
;; Map VTODO percent-complete to org-todo-keywords
;; Format: (PERCENT "KEYWORD") - percent thresholds map to states
(setq org-caldav-todo-percent-states
'((0 "TODO")
(25 "WAIT")
(50 "IN-PROGRESS")
(100 "DONE")
(100 "KILL")))
;; Allow export with broken links (mu4e links can't be resolved during export)
(setq org-export-with-broken-links 'mark)
;; Calendar-specific configuration
(setq org-caldav-calendars
'(;; Personal calendar: two-way sync with family-shared Nextcloud calendar
(:calendar-id "personal"
:inbox "~/org/personal-calendar.org"
:files ("~/org/personal-calendar.org"))
;; Tasks calendar: one-way sync (org → calendar only)
;; SCHEDULED/DEADLINE items from todo.org push to private Tasks calendar.
;; No inbox = no download from calendar (effectively one-way).
;; Note: Create 'tasks' calendar in Nextcloud first, keep it private.
(:calendar-id "tasks"
:files ("~/org/todo.org"))))
;; Handle UNTIL in recurring events
;; org-caldav ignores UNTIL from RRULE - events repeat forever.
;; This advice extracts UNTIL and stores it as a property for agenda filtering.
(defun my/org-caldav-add-until-property (orig-fun eventdata-alist)
"Advice to store CALDAV_UNTIL property for recurring events."
(let ((result (funcall orig-fun eventdata-alist)))
(let* ((rrule-props (alist-get 'rrule-props eventdata-alist))
(until-str (cadr (assoc 'UNTIL rrule-props)))
(summary (alist-get 'summary eventdata-alist)))
;; Debug: log what we're seeing
(message "CALDAV-DEBUG: %s | rrule-props: %S | until: %s"
(or summary "?") rrule-props until-str)
(when until-str
(save-excursion
(org-back-to-heading t)
(org-entry-put nil "CALDAV_UNTIL" until-str))))
result))
(advice-add 'org-caldav-insert-org-event-or-todo
:around #'my/org-caldav-add-until-property)
)
(defun my/get-rbw-password (alias &optional no-error)
"Return the password for ALIAS via rbw, unlocking the vault only if needed.
If NO-ERROR is non-nil, return nil instead of signaling an error when
rbw is unavailable or the entry is not found."
(if (not (executable-find "rbw"))
(if no-error
nil
(user-error "rbw: not installed or not in PATH"))
(let* ((cmd (format "rbw get %s 2>/dev/null" (shell-quote-argument alias)))
(output (string-trim (shell-command-to-string cmd))))
(if (string-empty-p output)
(if no-error
nil
(user-error "rbw: no entry found for '%s' - run: rbw add %s" alias alias))
output))))
(defun my/get-rbw-password (alias)
"Return the password for ALIAS via rbw, unlocking the vault only if needed."
(let* ((cmd (format "rbw get %s 2>&1" alias))
(output (shell-command-to-string cmd)))
(string-trim output)))
(after! gptel
:config
(setq! gptel-api-key (my/get-rbw-password "openai-api-key-chatgpt-el" t)
(setq! gptel-api-key (my/get-rbw-password "openai-api-key-chatgpt-el")
gptel-default-mode 'org-mode
gptel-use-tools t
gptel-confirm-tool-calls 'always
@@ -274,18 +147,6 @@ rbw is unavailable or the entry is not found."
(error (format "Error listing directory %s: %s" dirpath (error-message-string err)))))
:args (list '(:name "dirpath" :type "string" :description "Directory path to list"))))
(use-package! pi-coding-agent
:commands (pi-coding-agent pi-coding-agent-toggle)
:init
(defalias 'pi 'pi-coding-agent)
(map! :leader
(:prefix ("o" . "open")
:desc "Pi Coding Agent" "p" #'pi-coding-agent))
:config
;; Tree-sitter grammars are managed by Nix (treesit-grammars.with-all-grammars),
;; so suppress the auto-install prompt
(setq pi-coding-agent-essential-grammar-action 'warn))
(use-package! claude-code-ide
:commands (claude-code-ide-menu claude-code-ide-open-here)
:init
@@ -350,16 +211,11 @@ rbw is unavailable or the entry is not found."
mu4e-headers-time-format "%H:%M")
;; Sending mail via msmtp
;; NOTE: message-sendmail-f-is-evil and --read-envelope-from are required
;; to prevent msmtp from stripping the email body when processing headers.
;; Without these, multipart messages (especially from org-msg) may arrive
;; with empty bodies.
(setq sendmail-program (executable-find "msmtp")
send-mail-function #'message-send-mail-with-sendmail
message-send-mail-function #'message-send-mail-with-sendmail
message-sendmail-f-is-evil t
message-sendmail-extra-arguments '("--read-envelope-from")
message-sendmail-envelope-from 'header))
(setq message-send-mail-function 'message-send-mail-with-sendmail
sendmail-program (executable-find "msmtp")
message-sendmail-envelope-from 'header
mail-envelope-from 'header
mail-specify-envelope-from t))
;; Whenever you reconfigure a package, make sure to wrap your config in an
;; `after!' block, otherwise Doom's defaults may override your settings. E.g.

View File

@@ -49,26 +49,13 @@
;; ...Or *all* packages (NOT RECOMMENDED; will likely break things)
;; (unpin! t)
(package! org-caldav)
;; Pin org-msg - upstream doom pin references a force-pushed commit
(package! org-msg :pin "aa608b399586fb771ad37045a837f8286a0b6124")
;; Note: Packages with custom recipes must be pinned for nix-doom-emacs-unstraightened
;; to build deterministically. Update pins when upgrading packages.
;; (package! org-caldav)
(package! gptel :recipe (:nonrecursive t))
(package! claude-code-ide
:recipe (:host github :repo "manzaltu/claude-code-ide.el")
:pin "760240d7f03ff16f90ede9d4f4243cd94f3fed73")
:recipe (:host github :repo "manzaltu/claude-code-ide.el"))
(package! gptel-tool-library
:recipe (:host github :repo "aard-fi/gptel-tool-library"
:files ("*.el"))
:pin "baffc3b0d74a2b7cbda0d5cd6dd7726d6ccaca83")
(package! pi-coding-agent
:recipe (:host github :repo "dnouri/pi-coding-agent"
:files ("*.el"))
:pin "8d8158b0a6150ce13d91e561a1223790670acaa7")
:files ("*.el")))

View File

@@ -4,7 +4,6 @@ with lib;
let
cfg = config.home.roles.email;
isLinux = pkgs.stdenv.isLinux;
in
{
options.home.roles.email = {
@@ -90,38 +89,34 @@ in
account default : proton
'';
# Linux-only: Systemd service for mail sync (Darwin uses launchd instead)
systemd.user.services = mkIf isLinux {
mbsync = {
Unit = {
Description = "Mailbox synchronization service";
After = [ "network-online.target" ];
Wants = [ "network-online.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.bash}/bin/bash -c 'mkdir -p ~/Mail && ${pkgs.isync}/bin/mbsync -a && (${pkgs.mu}/bin/mu info >/dev/null 2>&1 || ${pkgs.mu}/bin/mu init --maildir ~/Mail --personal-address=john@ogle.fyi) && ${pkgs.mu}/bin/mu index'";
Environment = "PATH=${pkgs.rbw}/bin:${pkgs.coreutils}/bin";
StandardOutput = "journal";
StandardError = "journal";
};
# Systemd service for mail sync
systemd.user.services.mbsync = {
Unit = {
Description = "Mailbox synchronization service";
After = [ "network-online.target" ];
Wants = [ "network-online.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.bash}/bin/bash -c 'mkdir -p ~/Mail && ${pkgs.isync}/bin/mbsync -a && (${pkgs.mu}/bin/mu info >/dev/null 2>&1 || ${pkgs.mu}/bin/mu init --maildir ~/Mail --personal-address=john@ogle.fyi) && ${pkgs.mu}/bin/mu index'";
Environment = "PATH=${pkgs.rbw}/bin:${pkgs.coreutils}/bin";
StandardOutput = "journal";
StandardError = "journal";
};
};
# Linux-only: Systemd timer for automatic sync
systemd.user.timers = mkIf isLinux {
mbsync = {
Unit = {
Description = "Mailbox synchronization timer";
};
Timer = {
OnBootSec = "2min";
OnUnitActiveSec = "5min";
Unit = "mbsync.service";
};
Install = {
WantedBy = [ "timers.target" ];
};
# Systemd timer for automatic sync
systemd.user.timers.mbsync = {
Unit = {
Description = "Mailbox synchronization timer";
};
Timer = {
OnBootSec = "2min";
OnUnitActiveSec = "5min";
Unit = "mbsync.service";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
};

View File

@@ -12,7 +12,9 @@ in
config = mkIf cfg.enable {
home.packages = with pkgs; [
custom.mcrcon-rbw
# Gaming applications would go here
# This role is created for future expansion
# moonlight-qt is currently in media role but could be moved here
];
};
}

View File

@@ -4,15 +4,13 @@ with lib;
let
cfg = config.home.roles.kdeconnect;
isLinux = pkgs.stdenv.isLinux;
in
{
options.home.roles.kdeconnect = {
enable = mkEnableOption "Enable KDE Connect for device integration";
};
# KDE Connect services are Linux-only (requires D-Bus and systemd)
config = mkIf (cfg.enable && isLinux) {
config = mkIf cfg.enable {
services.kdeconnect = {
enable = true;
indicator = true;

View File

@@ -4,7 +4,6 @@ with lib;
let
cfg = config.home.roles.sync;
isLinux = pkgs.stdenv.isLinux;
in
{
options.home.roles.sync = {
@@ -12,10 +11,9 @@ in
};
config = mkIf cfg.enable {
# Linux-only: syncthingtray requires system tray support
home.packages = optionals isLinux (with pkgs; [
home.packages = with pkgs; [
syncthingtray
]);
];
services.syncthing = {
enable = true;

View File

@@ -2,7 +2,7 @@
# The currentIndex is incremented by `nix run .#rotate-wallpaper`
# and gets committed as part of `nix run .#upgrade`
{
currentIndex = 2; # Index into wallpapers list
currentIndex = 1; # Index into wallpapers list
wallpapers = [
{

View File

@@ -22,11 +22,11 @@ with lib;
sddm = true;
wayland = true;
};
plasma-bigscreen = {
kodi = {
enable = true;
autologin = true;
wayland = true;
jellyfinScaleFactor = 1.0;
appLauncherServer.enable = true;
};
nfs-mounts.enable = true;
users.enable = true;
@@ -71,3 +71,4 @@ with lib;
system.stateVersion = "24.05"; # Did you read the comment?
}

View File

@@ -1,74 +0,0 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ lib, pkgs, ... }:
with lib;
{
imports = [
# Include the results of the hardware scan.
./hardware-configuration.nix
];
roles = {
audio.enable = true;
bluetooth.enable = true;
desktop = {
enable = true;
gaming.enable = true;
kde = true;
sddm = true;
wayland = true;
};
plasma-bigscreen = {
enable = true;
autologin = true;
jellyfinScaleFactor = 1.0;
appLauncherServer.enable = true;
};
nfs-mounts.enable = true;
users.enable = true;
};
# Enable KDE Wallet PAM integration for auto-unlock
security.pam.services.sddm = {
kwallet = {
enable = true;
package = pkgs.kdePackages.kwallet-pam;
};
};
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "gym-box";
networking.networkmanager.enable = true;
services.xserver.videoDrivers = [ "amdgpu" ];
hardware.graphics.enable = true;
hardware.graphics.enable32Bit = true;
# RADV (AMD's Vulkan driver) is now enabled by default, amdvlk was removed
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
# to actually do that.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "24.05"; # Did you read the comment?
}

View File

@@ -1,31 +0,0 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "thunderbolt" "uas" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/59c0df78-c6fa-415d-8592-13547a3fada6";
fsType = "btrfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/DC66-D04C";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@@ -54,7 +54,6 @@ with lib;
4000 # nfs callback
4001 # nlockmgr
4002 # mountd
5000 # harmonia binary cache
20048 # mountd
];
allowedUDPPorts = [
@@ -91,8 +90,6 @@ with lib;
htop
tmux
zfs
rclone
custom.rclone-torbox-setup # Helper script to set up TorBox credentials via rbw
];
# Enable SSH
@@ -107,58 +104,6 @@ with lib;
# User configuration
roles.users.enable = true;
# Enable as remote builder (similar to zix790prors)
roles.remote-build.enableBuilder = true;
# k3s agent configuration
roles.k3s-node = {
enable = true;
role = "agent";
# serverAddr defaults to https://10.0.0.222:6443
# tokenFile defaults to /etc/k3s/token
extraFlags = [
# Node labels for workload scheduling
# fast-cpu: This node has a faster CPU than other cluster nodes
"--node-label=fast-cpu=true"
# fast-storage: This node is the NFS host with fast local storage access
"--node-label=fast-storage=true"
# k3s-upgrade=disabled: NixOS manages k3s upgrades via Nix, not system-upgrade-controller
"--node-label=k3s-upgrade=disabled"
];
};
roles.virtualisation.enable = true;
# TorBox WebDAV mount for rdt-client and Jellyfin
roles.rclone-mount = {
enable = true;
mounts.torbox = {
webdavUrl = "https://webdav.torbox.app";
username = "john@ogle.fyi"; # TorBox account email
mountPoint = "/media/media/torbox-rclone";
environmentFile = "/etc/rclone/torbox.env";
vfsCacheMode = "full"; # Best for streaming media
dirCacheTime = "5m";
extraArgs = [
"--buffer-size=64M"
"--vfs-read-chunk-size=32M"
"--vfs-read-chunk-size-limit=off"
];
# Wait for ZFS media pool to be mounted before starting
requiresMountsFor = [ "/media" ];
};
};
# Harmonia binary cache server
# Replaces the broken k8s deployment with native NixOS service
services.harmonia = {
enable = true;
signKeyPaths = [ "/etc/harmonia/signing-key.private" ];
settings = {
bind = "[::]:5000";
};
};
# Time zone
time.timeZone = "America/Los_Angeles"; # Adjust as needed

View File

@@ -21,41 +21,17 @@
};
nfs-mounts.enable = true;
printing.enable = true;
remote-build.builders = [
{
hostName = "zix790prors.oglehome";
maxJobs = 16;
speedFactor = 3;
}
{
hostName = "john-endesktop.oglehome";
maxJobs = 1;
speedFactor = 1;
}
];
remote-build.builders = [{
hostName = "zix790prors";
maxJobs = 16;
speedFactor = 3;
}];
spotifyd.enable = true;
users = {
enable = true;
extraGroups = [ "video" ];
};
virtualisation = {
enable = true;
waydroid = true;
};
wireguard = {
enable = true;
autostart = true;
interfaceName = "ogleNet";
address = [ "192.168.4.2/32" ];
privateKeyFile = "/etc/wireguard/oglehome-private-key";
dns = [ "192.168.4.1" ];
peers = [{
publicKey = "AWkmtaz0poyyKJGnRcabO5ecd6ESh1lKu+XRb3ObxBc=";
endpoint = "pi.johnogle.info:6666";
allowedIPs = [ "0.0.0.0/0" ];
persistentKeepalive = 25;
}];
};
virtualisation.enable = true;
};
# Bootloader.
@@ -65,9 +41,14 @@
boot.initrd.luks.devices."luks-b614167b-9045-4234-a441-ac6f60a96d81".device = "/dev/disk/by-uuid/b614167b-9045-4234-a441-ac6f60a96d81";
services.logind.settings.Login = {
HandleLidSwitch = "suspend-then-hibernate";
HandlePowerKey = "hibernate";
HandlePowerKeyLongPress = "poweroff";
};
systemd.sleep.extraConfig = ''
HibernateDelaySec=30m
SuspendState=mem
'';
networking.hostName = "nix-book"; # Define your hostname.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
@@ -75,6 +56,13 @@
# Enable networking
networking.networkmanager.enable = true;
# WireGuard setup
networking.wg-quick.interfaces = {
ogleNet = {
configFile = "/root/Oglehome-VPN-johno-nixbook.conf";
};
};
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [

View File

@@ -19,18 +19,11 @@
desktopSession = "plasma";
};
};
remote-build.builders = [
{
hostName = "zix790prors.oglehome";
maxJobs = 16;
speedFactor = 4;
}
{
hostName = "john-endesktop.oglehome";
maxJobs = 1;
speedFactor = 2;
}
];
remote-build.builders = [{
hostName = "zix790prors";
maxJobs = 16;
speedFactor = 4; # Prefer remote heavily on Steam Deck
}];
users = {
enable = true;
extraGroups = [ "video" ];

View File

@@ -0,0 +1,62 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
# NixOS-WSL specific options are documented on the NixOS-WSL repository:
# https://github.com/nix-community/NixOS-WSL
{ config, lib, pkgs, ... }:
{
imports = [
];
roles = {
audio.enable = true;
desktop = {
enable = true;
wayland = true;
};
users.enable = true;
};
networking.hostName = "wixos";
wsl.enable = true;
wsl.defaultUser = "johno";
wsl.startMenuLaunchers = true;
wsl.useWindowsDriver = true;
wsl.wslConf.network.hostname = "wixos";
wsl.wslConf.user.default = "johno";
services.xserver.videoDrivers = [ "nvidia" ];
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [
mesa
libvdpau-va-gl
libva-vdpau-driver
];
};
environment.sessionVariables = {
LD_LIBRARY_PATH = [
"/usr/lib/wsl/lib"
"/run/opengl-driver/lib"
];
};
hardware.nvidia = {
modesetting.enable = true;
nvidiaSettings = true;
open = true;
package = config.boot.kernelPackages.nvidiaPackages.latest;
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It's perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "24.05"; # Did you read the comment?
}

View File

@@ -25,12 +25,8 @@ with lib;
wayland = true;
x11 = true;
};
kodi.enable = true;
nfs-mounts.enable = true;
nvidia = {
enable = true;
graphics.enable32Bit = true;
};
nvidia.enable = true;
printing.enable = true;
remote-build.enableBuilder = true;
users.enable = true;
@@ -51,11 +47,27 @@ with lib;
# Fix dual boot clock sync - tell Linux to use local time for hardware clock
time.hardwareClockInLocalTime = true;
# NVIDIA Graphics configuration
services.xserver.videoDrivers = [ "nvidia" ];
hardware.graphics.enable = true;
hardware.graphics.enable32Bit = true;
# Set DP-0 as primary display with 164.90Hz refresh rate
services.xserver.displayManager.sessionCommands = ''
${pkgs.xorg.xrandr}/bin/xrandr --output DP-0 --mode 3440x1440 --rate 164.90 --primary
'';
hardware.nvidia = {
modesetting.enable = true;
nvidiaSettings = true;
package = pkgs.linuxPackages.nvidiaPackages.stable;
open = true;
# For gaming performance
powerManagement.enable = false;
powerManagement.finegrained = false;
};
services.ollama = {
enable = true;
acceleration = "cuda";

View File

@@ -1,29 +1,28 @@
{ lib
, stdenv
, fetchurl
, patchelf
, glibc
, autoPatchelfHook
}:
let
version = "2.1.75";
version = "2.0.76";
srcs = {
aarch64-darwin = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-arm64/claude";
sha256 = "8c541a5e924eda2070eaf1702a48047af671c4dff6a11a5e762076614a082675";
sha256 = "b76f6d4d09233e67295897b0a1ed2e22d7afa406431529d8b1b532b63b8cbcbd";
};
x86_64-darwin = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-x64/claude";
sha256 = "82c90b91a0a18f60191f817b9b42304d8b17dbed75795b715c41f4fdfe4c782d";
sha256 = "9d94582f0af5d2201f1c907bf24ff8d216104b897ee0b24795a6c081f40e08d7";
};
x86_64-linux = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-x64/claude";
sha256 = "328b0a429c05a04f911157d886be5123cf1824a19ba8ca1f9d594c004eac32c9";
sha256 = "5dcdb480f91ba0df0bc8bd6aff148d3dfd3883f0899eeb5b9427a8b0abe7a687";
};
aarch64-linux = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-arm64/claude";
sha256 = "ec8f4f7f7bb50611dae70c109a76ee1da6a3ab45511c65f117df215848ecc905";
sha256 = "f64a994c8e5bfb84d7242cebbec75d6919db2ee46d50b8fc7a88d5066db193f9";
};
};
@@ -39,14 +38,8 @@ in stdenv.mkDerivation {
dontUnpack = true;
dontBuild = true;
# Bun standalone binaries have JS code appended after the ELF sections
# stripping/patching would remove or corrupt this appended data
dontStrip = true;
dontPatchELF = true;
# Don't use autoPatchelfHook - it rewrites the ELF and strips the appended
# bun bundle (the JS code is appended after the ELF sections)
nativeBuildInputs = lib.optionals stdenv.isLinux [ patchelf ];
nativeBuildInputs = lib.optionals stdenv.isLinux [ autoPatchelfHook ];
installPhase = ''
runHook preInstall
@@ -56,14 +49,6 @@ in stdenv.mkDerivation {
runHook postInstall
'';
# Manually patch the interpreter for bun standalone binaries
# patchelf --set-interpreter modifies in-place without rewriting the entire ELF,
# preserving the appended JS bundle that bun needs at runtime
postFixup = lib.optionalString stdenv.isLinux ''
interpreter="${glibc}/lib/${if stdenv.hostPlatform.system == "aarch64-linux" then "ld-linux-aarch64.so.1" else "ld-linux-x86-64.so.2"}"
patchelf --set-interpreter "$interpreter" $out/bin/claude
'';
meta = with lib; {
description = "Terminal-based AI coding assistant from Anthropic";
homepage = "https://www.anthropic.com/claude-code";

View File

@@ -1,11 +1,8 @@
{ pkgs, ... }:
{
tea-rbw = pkgs.callPackage ./tea-rbw { };
app-launcher-server = pkgs.callPackage ./app-launcher-server { };
claude-code = pkgs.callPackage ./claude-code { };
mcrcon-rbw = pkgs.callPackage ./mcrcon-rbw { };
rclone-torbox-setup = pkgs.callPackage ./rclone-torbox-setup { };
pi-coding-agent = pkgs.callPackage ./pi-coding-agent { };
nextcloud-talk-desktop = pkgs.callPackage ./nextcloud-talk-desktop { };
opencode = pkgs.callPackage ./opencode { };
vulkanHDRLayer = pkgs.callPackage ./vulkan-hdr-layer {};
tea-rbw = pkgs.callPackage ./tea-rbw {};
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
claude-code = pkgs.callPackage ./claude-code {};
perles = pkgs.callPackage ./perles {};
}

View File

@@ -1,40 +0,0 @@
{ pkgs, ... }:
pkgs.writeShellScriptBin "mcrcon" ''
set -euo pipefail
# Configuration - can be overridden with environment variables
MINECRAFT_RCON_HOST="''${MCRCON_HOST:-10.0.0.165}"
MINECRAFT_RCON_PORT="''${MCRCON_PORT:-25575}"
RBW_ENTRY="minecraft-rcon"
# Check if rbw is available
if ! command -v rbw &> /dev/null; then
echo "Error: rbw is not available. Please ensure rbw is installed and configured."
exit 1
fi
# Retrieve password from Bitwarden
if ! MCRCON_PASS=$(rbw get "$RBW_ENTRY" 2>/dev/null); then
echo "Error: Failed to retrieve RCON password from rbw entry '$RBW_ENTRY'"
echo "Please ensure the entry exists in Bitwarden and rbw is synced."
echo ""
echo "To create the entry:"
echo " 1. Add 'minecraft-rcon' to Bitwarden with the RCON password"
echo " 2. Run 'rbw sync' to refresh the local cache"
exit 1
fi
# Export for mcrcon
export MCRCON_HOST="$MINECRAFT_RCON_HOST"
export MCRCON_PORT="$MINECRAFT_RCON_PORT"
export MCRCON_PASS
# If no arguments provided, start interactive terminal mode
if [[ $# -eq 0 ]]; then
exec ${pkgs.mcrcon}/bin/mcrcon -t
fi
# Execute mcrcon with all provided arguments
exec ${pkgs.mcrcon}/bin/mcrcon "$@"
''

View File

@@ -1,60 +0,0 @@
# Patched Nextcloud Talk Desktop with Wayland screen sharing support
# Applies the core change from upstream draft PR #1022:
# https://github.com/nextcloud/talk-desktop/pull/1022
#
# Patches the webpack bundle in app.asar to add setDisplayMediaRequestHandler
# with useSystemPicker: true, enabling native PipeWire/portal-based
# screen sharing on Wayland (Sway, Hyprland, etc.)
{ lib
, nextcloud-talk-desktop
, nodejs
, asar
}:
nextcloud-talk-desktop.overrideAttrs (old: {
pname = "nextcloud-talk-desktop-patched";
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [ asar nodejs ];
# Patch the asar after the main installPhase creates the output
postFixup = (old.postFixup or "") + ''
echo "Patching app.asar for Wayland screen sharing..."
ASAR_PATH="$out/opt/Nextcloud Talk-linux-x64/resources/app.asar"
WORK=$(mktemp -d)
asar extract "$ASAR_PATH" "$WORK/app"
# In the webpack bundle:
# session = l, desktopCapturer = a, app = n
# We inject setDisplayMediaRequestHandler right after n.whenReady().then((async()=>{
# useSystemPicker: true makes Electron use the native system picker
# (PipeWire/xdg-desktop-portal on Wayland)
node -e "
const fs = require('fs');
const p = '$WORK/app/.webpack/main/index.js';
let c = fs.readFileSync(p, 'utf8');
if (c.includes('setDisplayMediaRequestHandler')) {
console.log('Already patched');
process.exit(0);
}
const marker = 'n.whenReady().then((async()=>{';
const idx = c.indexOf(marker);
if (idx === -1) {
console.error('ERROR: Could not find whenReady marker in webpack bundle');
process.exit(1);
}
// Inject after the marker
const injection = 'l.defaultSession.setDisplayMediaRequestHandler(async(e,t)=>{const s=await a.getSources({types:[\"screen\",\"window\"]});s.length>0?t({video:s[0]}):t({})},{useSystemPicker:!0});';
c = c.slice(0, idx + marker.length) + injection + c.slice(idx + marker.length);
fs.writeFileSync(p, c, 'utf8');
console.log('Successfully patched main bundle for Wayland screen sharing');
"
asar pack "$WORK/app" "$ASAR_PATH"
rm -rf "$WORK"
'';
})

View File

@@ -1,82 +0,0 @@
{
lib,
stdenv,
fetchzip,
patchelf,
glibc,
}:
let
version = "1.4.0";
srcs = {
aarch64-darwin = {
url = "https://github.com/anomalyco/opencode/releases/download/v${version}/opencode-darwin-arm64.zip";
sha256 = "0m97j2vln8yhhvnsjl92phx6dac24y7hgh75csmbkbhawkz9xm4l";
};
x86_64-darwin = {
url = "https://github.com/anomalyco/opencode/releases/download/v${version}/opencode-darwin-x64.zip";
sha256 = "17n04j06pdc2raxjm91y6p87gwpnra0liabpbjwdmyd1iqgqv0q8";
};
x86_64-linux = {
url = "https://github.com/anomalyco/opencode/releases/download/v${version}/opencode-linux-x64.tar.gz";
sha256 = "16117lwfj2lb8wjbq5cyf77vhi52ada5ys3212hjqw3qw3wrcc0r";
};
aarch64-linux = {
url = "https://github.com/anomalyco/opencode/releases/download/v${version}/opencode-linux-arm64.tar.gz";
sha256 = "06lvm1qiji74xdd3psqn6lwxak65gqsbmkib1pjb4n65f9246jwm";
};
};
src =
srcs.${stdenv.hostPlatform.system} or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
in
stdenv.mkDerivation {
pname = "opencode";
inherit version;
src = fetchzip {
inherit (src) url sha256;
};
# Bun standalone binaries have JS code appended after the ELF sections
# stripping/patching would remove or corrupt this appended data
dontStrip = true;
dontPatchELF = true;
nativeBuildInputs = lib.optionals stdenv.isLinux [ patchelf ];
installPhase = ''
runHook preInstall
install -Dm755 $src/opencode $out/bin/opencode
runHook postInstall
'';
# Manually patch the interpreter for bun standalone binaries on Linux
postFixup = lib.optionalString stdenv.isLinux ''
interpreter="${glibc}/lib/${
if stdenv.hostPlatform.system == "aarch64-linux" then
"ld-linux-aarch64.so.1"
else
"ld-linux-x86-64.so.2"
}"
patchelf --set-interpreter "$interpreter" $out/bin/opencode
'';
meta = with lib; {
description = "Terminal-based AI coding assistant";
homepage = "https://opencode.ai";
license = licenses.mit;
maintainers = [ ];
platforms = [
"aarch64-darwin"
"x86_64-darwin"
"x86_64-linux"
"aarch64-linux"
];
mainProgram = "opencode";
};
}

View File

@@ -1,148 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run|-n)
DRY_RUN=true
shift
;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --dry-run, -n Show what would be updated without making changes"
echo " --help, -h Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
NIX_FILE="$REPO_ROOT/packages/opencode/default.nix"
echo "Fetching latest opencode version from GitHub API..."
RELEASE_INFO=$(curl -fsSL https://api.github.com/repos/anomalyco/opencode/releases/latest)
NEW_VERSION=$(echo "$RELEASE_INFO" | jq -r '.tag_name' | sed 's/^v//')
if [ -z "$NEW_VERSION" ] || [ "$NEW_VERSION" = "null" ]; then
echo -e "${RED}Error: Failed to fetch version from GitHub API${NC}"
exit 1
fi
CURRENT_VERSION=$(grep -m1 'version = ' "$NIX_FILE" | sed -E 's/.*version = "([^"]+)".*/\1/')
if [ "$CURRENT_VERSION" = "$NEW_VERSION" ]; then
echo -e "${GREEN}Already up to date: $CURRENT_VERSION${NC}"
exit 0
fi
echo -e "${YELLOW}Updating from $CURRENT_VERSION to $NEW_VERSION${NC}"
# Compute SHA256 hashes for each platform
# fetchzip hashes the unpacked directory, so we need to extract and hash
compute_unpacked_hash() {
local url="$1"
local ext="$2"
local tmpdir=$(mktemp -d)
local archive="/tmp/opencode-archive.$ext"
curl -fsSL "$url" -o "$archive"
if [ "$ext" = "zip" ]; then
(cd "$tmpdir" && unzip -q "$archive")
else
(cd "$tmpdir" && tar xzf "$archive")
fi
local sri_hash=$(nix hash path "$tmpdir")
local nix32_hash=$(nix hash convert --hash-algo sha256 --to nix32 "$sri_hash")
rm -rf "$tmpdir" "$archive"
echo "$nix32_hash"
}
echo "Computing SHA256 hashes (this may take a moment)..."
SHA_DARWIN_ARM=$(compute_unpacked_hash "https://github.com/anomalyco/opencode/releases/download/v${NEW_VERSION}/opencode-darwin-arm64.zip" "zip")
echo " aarch64-darwin: $SHA_DARWIN_ARM"
SHA_DARWIN_X64=$(compute_unpacked_hash "https://github.com/anomalyco/opencode/releases/download/v${NEW_VERSION}/opencode-darwin-x64.zip" "zip")
echo " x86_64-darwin: $SHA_DARWIN_X64"
SHA_LINUX_X64=$(compute_unpacked_hash "https://github.com/anomalyco/opencode/releases/download/v${NEW_VERSION}/opencode-linux-x64.tar.gz" "tar.gz")
echo " x86_64-linux: $SHA_LINUX_X64"
SHA_LINUX_ARM64=$(compute_unpacked_hash "https://github.com/anomalyco/opencode/releases/download/v${NEW_VERSION}/opencode-linux-arm64.tar.gz" "tar.gz")
echo " aarch64-linux: $SHA_LINUX_ARM64"
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}DRY RUN - No changes will be made${NC}"
echo ""
echo "Would update:"
echo " Version: $CURRENT_VERSION -> $NEW_VERSION"
echo " aarch64-darwin SHA: $SHA_DARWIN_ARM"
echo " x86_64-darwin SHA: $SHA_DARWIN_X64"
echo " x86_64-linux SHA: $SHA_LINUX_X64"
echo " aarch64-linux SHA: $SHA_LINUX_ARM64"
exit 0
fi
# Update version
sed -i.tmp "s/version = \".*\";/version = \"$NEW_VERSION\";/" "$NIX_FILE"
# Update SHA256 hashes using awk
awk -v sha_arm="$SHA_DARWIN_ARM" -v sha_x64="$SHA_DARWIN_X64" -v sha_linux_x64="$SHA_LINUX_X64" -v sha_linux_arm="$SHA_LINUX_ARM64" '
/aarch64-darwin = {/ { in_arm = 1 }
/x86_64-darwin = {/ { in_x64 = 1; in_arm = 0 }
/x86_64-linux = {/ { in_linux_x64 = 1; in_x64 = 0 }
/aarch64-linux = {/ { in_linux_arm = 1; in_linux_x64 = 0 }
/};/ {
in_arm = 0
in_x64 = 0
in_linux_x64 = 0
in_linux_arm = 0
}
/sha256 = / {
if (in_arm) {
sub(/sha256 = ".*";/, "sha256 = \"" sha_arm "\";")
} else if (in_x64) {
sub(/sha256 = ".*";/, "sha256 = \"" sha_x64 "\";")
} else if (in_linux_x64) {
sub(/sha256 = ".*";/, "sha256 = \"" sha_linux_x64 "\";")
} else if (in_linux_arm) {
sub(/sha256 = ".*";/, "sha256 = \"" sha_linux_arm "\";")
}
}
{ print }
' "$NIX_FILE" > "$NIX_FILE.new"
mv "$NIX_FILE.new" "$NIX_FILE"
rm -f "$NIX_FILE.tmp"
echo -e "${GREEN}Successfully updated to version $NEW_VERSION${NC}"
echo ""
echo "Updated SHA256 hashes:"
echo " aarch64-darwin: $SHA_DARWIN_ARM"
echo " x86_64-darwin: $SHA_DARWIN_X64"
echo " x86_64-linux: $SHA_LINUX_X64"
echo " aarch64-linux: $SHA_LINUX_ARM64"
echo ""
echo "Next steps:"
echo " 1. Review changes: git diff $NIX_FILE"
echo " 2. Test build: nix build .#custom-opencode"
echo " 3. Verify version: ./result/bin/opencode --version"
echo " 4. Commit: git add $NIX_FILE && git commit -m 'opencode: Update to version $NEW_VERSION'"

View File

@@ -0,0 +1,26 @@
{ lib, buildGoModule, fetchFromGitHub }:
buildGoModule rec {
pname = "perles";
version = "unstable-2025-01-09";
src = fetchFromGitHub {
owner = "zjrosen";
repo = "perles";
rev = "main";
hash = "sha256-JgRayb4+mJ1r0AtdnQfqAw2+QRte+licsfZOaRgYqcs=";
};
vendorHash = "sha256-R7UWTdBuPteneRqxrWK51nqLtZwDsqQoMAcohN4fyak=";
# Tests require a real git repository context
doCheck = false;
meta = with lib; {
description = "A TUI for the Beads issue tracking system with BQL query language";
homepage = "https://github.com/zjrosen/perles";
license = licenses.mit;
maintainers = [ ];
mainProgram = "perles";
};
}

View File

@@ -1,79 +0,0 @@
{ lib
, stdenv
, fetchurl
, patchelf
, glibc
, makeWrapper
}:
let
version = "0.55.4";
srcs = {
aarch64-darwin = {
url = "https://github.com/badlogic/pi-mono/releases/download/v${version}/pi-darwin-arm64.tar.gz";
sha256 = "0vsav9frvnzskk6p6j60i7klrs3m8lphhyi4c39mv2mvhpm8fkl5";
};
x86_64-darwin = {
url = "https://github.com/badlogic/pi-mono/releases/download/v${version}/pi-darwin-x64.tar.gz";
sha256 = "1377rvhsiiww1bbpgv2v46fjm7iz2smmh8g2yhm28kbsq3gwvvr0";
};
x86_64-linux = {
url = "https://github.com/badlogic/pi-mono/releases/download/v${version}/pi-linux-x64.tar.gz";
sha256 = "1wnfwnkfq5ffz6wyqyhciv4lz06bpxims0hv0dlhz0f9vliyc1md";
};
aarch64-linux = {
url = "https://github.com/badlogic/pi-mono/releases/download/v${version}/pi-linux-arm64.tar.gz";
sha256 = "00fp37hgjl40kc59jfpv189i7np53ymm037hvds6k9y2sz818wjy";
};
};
src = srcs.${stdenv.hostPlatform.system} or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
in stdenv.mkDerivation {
pname = "pi-coding-agent";
inherit version;
src = fetchurl {
inherit (src) url sha256;
};
sourceRoot = "pi";
# Bun standalone binaries have JS code appended after the ELF sections
dontStrip = true;
dontPatchELF = true;
nativeBuildInputs = [ makeWrapper ]
++ lib.optionals stdenv.isLinux [ patchelf ];
installPhase = ''
runHook preInstall
# Install the full pi directory structure (binary + supporting files)
mkdir -p $out/lib/pi-coding-agent
cp -r . $out/lib/pi-coding-agent/
# Create bin wrapper that runs the binary from its lib directory
# (pi expects supporting files like themes and wasm relative to itself)
mkdir -p $out/bin
makeWrapper $out/lib/pi-coding-agent/pi $out/bin/pi
runHook postInstall
'';
# Manually patch the interpreter for bun standalone binaries on Linux
postFixup = lib.optionalString stdenv.isLinux ''
interpreter="${glibc}/lib/${if stdenv.hostPlatform.system == "aarch64-linux" then "ld-linux-aarch64.so.1" else "ld-linux-x86-64.so.2"}"
patchelf --set-interpreter "$interpreter" $out/lib/pi-coding-agent/pi
'';
meta = with lib; {
description = "Minimal terminal coding agent with extensible tools and session management";
homepage = "https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent";
license = licenses.mit;
maintainers = [ ];
platforms = [ "aarch64-darwin" "x86_64-darwin" "x86_64-linux" "aarch64-linux" ];
mainProgram = "pi";
};
}

View File

@@ -1,98 +0,0 @@
{ pkgs, ... }:
pkgs.writeShellScriptBin "rclone-torbox-setup" ''
set -euo pipefail
# Default values
RBW_ENTRY="''${1:-torbox}"
ENV_FILE="''${2:-/etc/rclone/torbox.env}"
usage() {
echo "Usage: rclone-torbox-setup [rbw-entry] [env-file]"
echo ""
echo "Sets up rclone credentials for TorBox WebDAV mount."
echo "Retrieves password from rbw (Bitwarden), obscures it for rclone,"
echo "and writes it to the environment file for the systemd service."
echo ""
echo "Arguments:"
echo " rbw-entry Name of the Bitwarden entry containing the password (default: torbox)"
echo " env-file Path to write the environment file (default: /etc/rclone/torbox.env)"
echo ""
echo "The Bitwarden entry should contain your TorBox password as the password field."
echo ""
echo "Example:"
echo " rclone-torbox-setup torbox-password /etc/rclone/torbox.env"
exit 1
}
if [[ "''${1:-}" == "-h" ]] || [[ "''${1:-}" == "--help" ]]; then
usage
fi
echo "rclone TorBox credential setup"
echo "=============================="
echo ""
# Check if rbw is available
if ! command -v rbw &> /dev/null; then
echo "Error: rbw is not available. Please ensure rbw is installed and configured."
exit 1
fi
# Check if rclone is available
if ! command -v rclone &> /dev/null; then
echo "Error: rclone is not available. Please ensure rclone is installed."
exit 1
fi
echo "Retrieving password from rbw entry: $RBW_ENTRY"
# Retrieve password from Bitwarden
if ! TORBOX_PASS=$(rbw get "$RBW_ENTRY" 2>/dev/null); then
echo ""
echo "Error: Failed to retrieve password from rbw entry '$RBW_ENTRY'"
echo ""
echo "Please ensure:"
echo " 1. The entry '$RBW_ENTRY' exists in Bitwarden"
echo " 2. rbw is unlocked: rbw unlock"
echo " 3. rbw is synced: rbw sync"
echo ""
echo "To create the entry in Bitwarden:"
echo " - Name: $RBW_ENTRY"
echo " - Password: Your TorBox password"
exit 1
fi
echo "Password retrieved successfully"
# Obscure the password for rclone
echo "Obscuring password for rclone..."
if ! OBSCURED_PASS=$(echo -n "$TORBOX_PASS" | rclone obscure -); then
echo "Error: Failed to obscure password with rclone"
exit 1
fi
# Create the directory if needed (requires sudo)
ENV_DIR=$(dirname "$ENV_FILE")
if [[ ! -d "$ENV_DIR" ]]; then
echo "Creating directory $ENV_DIR (requires sudo)..."
sudo mkdir -p "$ENV_DIR"
fi
# Write the environment file
echo "Writing environment file to $ENV_FILE (requires sudo)..."
echo "RCLONE_WEBDAV_PASS=$OBSCURED_PASS" | sudo tee "$ENV_FILE" > /dev/null
sudo chmod 600 "$ENV_FILE"
echo ""
echo "Setup complete!"
echo ""
echo "The environment file has been created at: $ENV_FILE"
echo "The rclone-mount-torbox systemd service will use this file."
echo ""
echo "To activate the mount after NixOS rebuild:"
echo " sudo systemctl start rclone-mount-torbox"
echo ""
echo "To check status:"
echo " sudo systemctl status rclone-mount-torbox"
''

View File

@@ -0,0 +1,34 @@
{ lib, stdenv, fetchFromGitHub, meson, pkg-config, vulkan-loader, ninja, writeText, vulkan-headers, vulkan-utility-libraries, jq, libX11, libXrandr, libxcb, wayland, wayland-scanner }:
stdenv.mkDerivation rec {
pname = "vulkan-hdr-layer";
version = "63d2eec";
src = (fetchFromGitHub {
owner = "Zamundaaa";
repo = "VK_hdr_layer";
rev = "869199cd2746e7f69cf19955153080842b6dacfc";
fetchSubmodules = true;
hash = "sha256-xfVYI+Aajmnf3BTaY2Ysg5fyDO6SwDFGyU0L+F+E3is=";
}).overrideAttrs (_: {
GIT_CONFIG_COUNT = 1;
GIT_CONFIG_KEY_0 = "url.https://github.com/.insteadOf";
GIT_CONFIG_VALUE_0 = "git@github.com:";
});
nativeBuildInputs = [ vulkan-headers meson ninja pkg-config jq ];
buildInputs = [ vulkan-headers vulkan-loader vulkan-utility-libraries libX11 libXrandr libxcb wayland wayland-scanner ];
# Help vulkan-loader find the validation layers
setupHook = writeText "setup-hook" ''
addToSearchPath XDG_DATA_DIRS @out@/share
'';
meta = with lib; {
description = "Layers providing Vulkan HDR";
homepage = "https://github.com/Zamundaaa/VK_hdr_layer";
platforms = platforms.linux;
license = licenses.mit;
};
}

View File

@@ -1,82 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"timezone": "America/Los_Angeles",
"gitAuthor": "Renovate Bot <renovate@ogle.fyi>",
"nix": {
"enabled": true
},
"github-actions": {
"managerFilePatterns": [
"/.gitea/workflows/.+\\.ya?ml$/"
]
},
"lockFileMaintenance": {
"enabled": true,
"schedule": [
"after 5pm and before 7pm on Saturday"
]
},
"dependencyDashboard": true,
"dependencyDashboardAutoclose": false,
"dependencyDashboardTitle": "NixOS Configs Dependency Dashboard",
"packageRules": [
{
"description": "Group all GitHub Actions updates",
"matchManagers": [
"github-actions"
],
"groupName": "github-actions"
},
{
"description": "Group stable NixOS ecosystem inputs",
"matchManagers": [
"nix"
],
"groupName": "nix-stable-ecosystem",
"matchPackageNames": [
"/^nixpkgs$/",
"/^home-manager$/",
"/^nix-darwin$/"
],
"schedule": [
"after 5pm and before 7pm on Saturday"
]
},
{
"description": "Group unstable NixOS ecosystem inputs",
"matchManagers": [
"nix"
],
"groupName": "nix-unstable-ecosystem",
"matchPackageNames": [
"/nixpkgs-unstable/",
"/home-manager-unstable/"
],
"schedule": [
"after 5pm and before 7pm on Saturday"
]
},
{
"description": "nixpkgs-qt updates on Saturday (staggered from main ecosystem)",
"matchManagers": [
"nix"
],
"matchPackageNames": [
"/nixpkgs-qt/"
],
"schedule": [
"after 7pm and before 9pm on Saturday"
]
},
{
"description": "Ignore private Gitea inputs (handle separately)",
"matchManagers": [
"nix"
],
"enabled": false,
"matchPackageNames": [
"/google-cookie-retrieval/"
]
}
]
}

View File

@@ -21,11 +21,17 @@ in
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
services.pulseaudio = {
package = pkgs.pulseaudioFull;
extraConfig = ''
load-module module-combine-sink
load-module module-switch-on-connect
'';
};
services.squeezelite = {
#enable = true;
pulseAudio = true;

View File

@@ -12,8 +12,6 @@
tree
usbutils
vim
] ++ lib.optionals pkgs.stdenv.isLinux [
ghostty.terminfo # So tmux works when SSH'ing from ghostty
];
nix = {
@@ -23,13 +21,7 @@
max-jobs = "auto";
trusted-users = [ "johno" ];
substituters = [
"http://john-endesktop.oglehome:5000"
];
trusted-public-keys = [
"harmonia.john-endesktop:1iGr4xZrsR7WtXOlPCgFF3LcODYBpu+B3TS54MyBn4M="
];
fallback = true;
connect-timeout = 5;
};
gc = {

View File

@@ -9,18 +9,14 @@ with lib;
./bluetooth
./btrfs
./desktop
./k3s-node
./kodi
./nfs-mounts
./plasma-bigscreen
./nvidia
./printing
./rclone-mount
./remote-build
./spotifyd
./users
./virtualisation
./wireguard
];
config = {

View File

@@ -17,10 +17,9 @@ in
services.gnome.gnome-keyring.enable = true;
programs.kdeconnect.enable = true;
# XDG Desktop Portal for default application handling
# XDG Desktop Portal for default application handling in non-KDE environments
xdg.portal = {
enable = true;
wlr.enable = cfg.wayland; # xdg-desktop-portal-wlr for Sway screen sharing
extraPortals = with pkgs; [
kdePackages.xdg-desktop-portal-kde # For KDE application integration
xdg-desktop-portal-gtk # Fallback for GTK applications

View File

@@ -11,8 +11,9 @@ in
enable = true;
wrapperFeatures.gtk = true;
};
programs.light.enable = true;
environment.systemPackages = with pkgs; [
brightnessctl
grim
slurp
wl-clipboard

View File

@@ -1,81 +0,0 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.roles.k3s-node;
in
{
options.roles.k3s-node = {
enable = mkEnableOption "Enable k3s node";
role = mkOption {
type = types.enum [ "server" "agent" ];
default = "agent";
description = "k3s role: server (control plane) or agent (worker)";
};
serverAddr = mkOption {
type = types.str;
default = "https://10.0.0.222:6443";
description = "URL of k3s server to join (required for agents, used for HA servers)";
};
tokenFile = mkOption {
type = types.path;
default = "/etc/k3s/token";
description = "Path to file containing the cluster join token";
};
clusterInit = mkOption {
type = types.bool;
default = false;
description = "Initialize a new cluster (first server only)";
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [];
description = "Additional flags to pass to k3s";
};
gracefulNodeShutdown = mkOption {
type = types.bool;
default = true;
description = "Enable graceful node shutdown";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Open firewall ports for k3s";
};
};
config = mkIf cfg.enable {
# k3s service configuration
services.k3s = {
enable = true;
role = cfg.role;
tokenFile = cfg.tokenFile;
extraFlags = cfg.extraFlags;
gracefulNodeShutdown.enable = cfg.gracefulNodeShutdown;
serverAddr = if (cfg.role == "agent" || !cfg.clusterInit) then cfg.serverAddr else "";
clusterInit = cfg.role == "server" && cfg.clusterInit;
};
# Firewall rules for k3s
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [
6443 # k3s API server
10250 # kubelet metrics
] ++ optionals (cfg.role == "server") [
2379 # etcd clients (HA)
2380 # etcd peers (HA)
];
allowedUDPPorts = [
8472 # flannel VXLAN
];
};
};
}

View File

@@ -22,7 +22,7 @@ in
appLauncherServer = {
enable = mkOption {
type = types.bool;
default = false;
default = true;
description = "Enable HTTP app launcher server for remote control";
};
port = mkOption {
@@ -47,23 +47,23 @@ in
if cfg.jellyfinScaleFactor != null
then pkgs.symlinkJoin {
name = "jellyfin-media-player-scaled";
paths = [ pkgs.qt-pinned.jellyfin-media-player ];
paths = [ pkgs.jellyfin-media-player ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
mkdir -p $out/bin
rm -f $out/bin/jellyfin-desktop
makeWrapper ${pkgs.qt-pinned.jellyfin-media-player}/bin/jellyfin-desktop $out/bin/jellyfin-desktop \
makeWrapper ${pkgs.jellyfin-media-player}/bin/jellyfin-desktop $out/bin/jellyfin-desktop \
--add-flags "--tv --scale-factor ${toString cfg.jellyfinScaleFactor}"
# Update .desktop file to include scale factor and TV mode arguments
mkdir -p $out/share/applications
rm -f $out/share/applications/org.jellyfin.JellyfinDesktop.desktop
substitute ${pkgs.qt-pinned.jellyfin-media-player}/share/applications/org.jellyfin.JellyfinDesktop.desktop \
substitute ${pkgs.jellyfin-media-player}/share/applications/org.jellyfin.JellyfinDesktop.desktop \
$out/share/applications/org.jellyfin.JellyfinDesktop.desktop \
--replace-fail "Exec=jellyfin-desktop" "Exec=jellyfin-desktop --tv --scale-factor ${toString cfg.jellyfinScaleFactor}"
'';
}
else pkgs.qt-pinned.jellyfin-media-player;
else pkgs.jellyfin-media-player;
in mkIf cfg.enable
{
users.extraUsers.kodi = {
@@ -77,15 +77,14 @@ in
};
environment.systemPackages = with pkgs; [
firefox
jellyfinMediaPlayerPkg
kodiPkg
qt-pinned.stremio
wget
firefox
] ++ optional cfg.appLauncherServer.enable pkgs.custom.app-launcher-server;
nixpkgs.config.permittedInsecurePackages = lib.warn
"Allowing insecure package qtwebengine-5.15.19 as a jellyfin-media-player/stremio dependency. These are pinned to nixpkgs-qt to avoid rebuilds - update that input separately when you have time."
"Allowing insecure package qtwebengine-5.15.19 as a jellyfin-media-player dependency. Remove this once jellyfin is updated to use qt6"
[
"qtwebengine-5.15.19"
];

View File

@@ -8,21 +8,6 @@ in
{
options.roles.nfs-mounts = {
enable = mkEnableOption "Enable default NFS mounts";
server = mkOption {
type = types.str;
default = "10.0.0.43";
description = "IP address or hostname of the NFS server";
};
remotePath = mkOption {
type = types.str;
default = "/media";
description = "Remote path to mount from the NFS server";
};
mountPoint = mkOption {
type = types.str;
default = "/media";
description = "Local mount point for the NFS share";
};
# TODO: implement requireMount
requireMount = mkOption {
type = types.bool;
@@ -33,8 +18,8 @@ in
config = mkIf cfg.enable
{
fileSystems.${cfg.mountPoint} = {
device = "${cfg.server}:${cfg.remotePath}";
fileSystems."/media" = {
device = "10.0.0.43:/media";
fsType = "nfs";
options = [
"defaults"

View File

@@ -8,89 +8,9 @@ in
{
options.roles.nvidia = {
enable = mkEnableOption "Enable the nvidia role";
# Driver configuration options
open = mkOption {
type = types.bool;
default = true;
description = "Use the open source nvidia kernel driver (for Turing and newer GPUs).";
};
modesetting = mkOption {
type = types.bool;
default = true;
description = "Enable kernel modesetting for nvidia.";
};
nvidiaSettings = mkOption {
type = types.bool;
default = true;
description = "Enable the nvidia-settings GUI.";
};
package = mkOption {
type = types.enum [ "stable" "latest" "beta" "vulkan_beta" "production" ];
default = "stable";
description = "The nvidia driver package to use.";
};
powerManagement = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable nvidia power management (useful for laptops, not recommended for desktops).";
};
finegrained = mkOption {
type = types.bool;
default = false;
description = "Enable fine-grained power management for Turing and newer GPUs.";
};
};
graphics = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable hardware graphics support.";
};
enable32Bit = mkOption {
type = types.bool;
default = false;
description = "Enable 32-bit graphics libraries (needed for some games).";
};
extraPackages = mkOption {
type = types.listOf types.package;
default = [];
description = "Extra packages to add to hardware.graphics.extraPackages.";
};
};
};
config = mkIf cfg.enable {
# Set xserver video driver
services.xserver.videoDrivers = [ "nvidia" ];
# Graphics configuration
hardware.graphics = {
enable = cfg.graphics.enable;
enable32Bit = cfg.graphics.enable32Bit;
extraPackages = cfg.graphics.extraPackages;
};
# NVIDIA driver configuration
hardware.nvidia = {
modesetting.enable = cfg.modesetting;
nvidiaSettings = cfg.nvidiaSettings;
open = cfg.open;
package = config.boot.kernelPackages.nvidiaPackages.${cfg.package};
powerManagement.enable = cfg.powerManagement.enable;
powerManagement.finegrained = cfg.powerManagement.finegrained;
};
# Additional packages for nvidia support
environment.systemPackages = with pkgs; [
libva-utils
nvidia-vaapi-driver

View File

@@ -1,134 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.roles.plasma-bigscreen;
# Plasma Bigscreen package — not yet in nixpkgs, built from upstream master.
# TODO: Replace with pkgs.kdePackages.plasma-bigscreen once available.
plasma-bigscreen = pkgs.kdePackages.callPackage ./package.nix {};
jellyfinMediaPlayerPkg =
if cfg.jellyfinScaleFactor != null
then pkgs.symlinkJoin {
name = "jellyfin-media-player-scaled";
paths = [ pkgs.qt-pinned.jellyfin-media-player ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
mkdir -p $out/bin
rm -f $out/bin/jellyfin-desktop
makeWrapper ${pkgs.qt-pinned.jellyfin-media-player}/bin/jellyfin-desktop $out/bin/jellyfin-desktop \
--add-flags "--tv --scale-factor ${toString cfg.jellyfinScaleFactor}"
# Update .desktop file to include scale factor and TV mode arguments
mkdir -p $out/share/applications
rm -f $out/share/applications/org.jellyfin.JellyfinDesktop.desktop
substitute ${pkgs.qt-pinned.jellyfin-media-player}/share/applications/org.jellyfin.JellyfinDesktop.desktop \
$out/share/applications/org.jellyfin.JellyfinDesktop.desktop \
--replace-fail "Exec=jellyfin-desktop" "Exec=jellyfin-desktop --tv --scale-factor ${toString cfg.jellyfinScaleFactor}"
'';
}
else pkgs.qt-pinned.jellyfin-media-player;
in
{
options.roles.plasma-bigscreen = {
enable = mkEnableOption "Plasma Bigscreen TV interface";
autologin = mkOption {
type = types.bool;
default = false;
description = "Auto-login to Plasma Bigscreen session";
};
user = mkOption {
type = types.str;
default = "kodi";
description = "User account for the Bigscreen session";
};
jellyfinScaleFactor = mkOption {
type = types.nullOr types.float;
default = null;
description = "Scale factor for Jellyfin Media Player UI (e.g., 1.0 for 100% scaling)";
};
appLauncherServer = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable HTTP app launcher server for remote control";
};
port = mkOption {
type = types.int;
default = 8081;
description = "Port for the app launcher HTTP server";
};
};
};
config = mkIf cfg.enable {
# Create the bigscreen user
users.extraUsers.${cfg.user} = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "audio" "video" ];
};
# Plasma Bigscreen is a Plasma 6 shell — needs Plasma 6 desktop manager
services.desktopManager.plasma6.enable = true;
# Register the bigscreen session with the display manager
services.displayManager = {
sessionPackages = [ plasma-bigscreen ];
} // optionalAttrs cfg.autologin {
autoLogin.enable = true;
autoLogin.user = cfg.user;
defaultSession = "plasma-bigscreen-wayland";
};
xdg.portal.configPackages = [ plasma-bigscreen ];
# Fix homescreen not being focused after quitting app or on boot
# xwaylandvideobridge can interfere with focus; exclude if present
environment.plasma6.excludePackages =
lib.optional (pkgs.kdePackages ? xwaylandvideobridge) pkgs.kdePackages.xwaylandvideobridge;
# Firewall for remote control
networking.firewall = {
allowedTCPPorts = optional cfg.appLauncherServer.enable cfg.appLauncherServer.port;
};
environment.systemPackages = with pkgs; [
plasma-bigscreen
firefox
jellyfinMediaPlayerPkg
qt-pinned.stremio
wget
] ++ optional cfg.appLauncherServer.enable pkgs.custom.app-launcher-server;
nixpkgs.config.permittedInsecurePackages = lib.warn
"Allowing insecure package qtwebengine-5.15.19 as a jellyfin-media-player/stremio dependency."
[
"qtwebengine-5.15.19"
];
programs.kdeconnect.enable = true;
systemd.user.services = mkIf cfg.appLauncherServer.enable {
app-launcher-server = {
description = "HTTP App Launcher Server";
wantedBy = [ "graphical-session.target" ];
after = [ "graphical-session.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.custom.app-launcher-server}/bin/app-launcher-server ${toString cfg.appLauncherServer.port}";
Restart = "always";
RestartSec = "5s";
Environment = [
"PATH=${pkgs.firefox}/bin:/run/current-system/sw/bin"
];
};
};
};
};
}

View File

@@ -1,120 +0,0 @@
# Plasma Bigscreen — TV interface for Plasma 6
# Not yet released or packaged in nixpkgs; built from upstream master.
#
# TODO: Remove this file once plasma-bigscreen lands in nixpkgs.
# Tracking issue: https://github.com/NixOS/nixpkgs/issues/428077
# Draft nixpkgs PR: https://github.com/NixOS/nixpkgs/pull/428353
# When available, switch to pkgs.kdePackages.plasma-bigscreen.
#
# Upstream: https://invent.kde.org/plasma/plasma-bigscreen
{
mkKdeDerivation,
lib,
fetchFromGitLab,
pkg-config,
# KDE Frameworks 6
ki18n,
kdeclarative,
kcmutils,
knotifications,
kio,
kwayland,
kwindowsystem,
ksvg,
kiconthemes,
kglobalaccel,
kdbusaddons,
# KDE Plasma 6
plasma-workspace,
plasma-nano,
plasma-nm,
plasma-activities,
plasma-activities-stats,
milou,
libkscreen,
kdeconnect-kde,
# Qt 6
qtdeclarative, # needed for Qt6::QmlPrivate — see QCoro workaround in postPatch
qtmultimedia,
qtwebengine,
# Other
bluez-qt,
qcoro,
plasma-wayland-protocols,
wayland,
sdl3,
}:
mkKdeDerivation {
pname = "plasma-bigscreen";
version = "unstable-2026-03-07";
src = fetchFromGitLab {
domain = "invent.kde.org";
owner = "plasma";
repo = "plasma-bigscreen";
rev = "bd143fea7e386bac1652b8150a3ed3d5ef7cf93c";
hash = "sha256-y439IX7e0+XqxqFj/4+P5le0hA7DiwA+smDsD0UH/fI=";
};
extraNativeBuildInputs = [
pkg-config
];
extraBuildInputs = [
# KDE Frameworks (auto-injected by mkKdeDerivation: ki18n, kcmutils,
# knotifications, kio, kwayland, kwindowsystem, ksvg, kiconthemes)
kdeclarative
kglobalaccel
kdbusaddons
# Plasma (auto-injected: plasma-workspace, plasma-activities,
# plasma-activities-stats, libkscreen)
plasma-nano
plasma-nm
milou
kdeconnect-kde
# Qt — qtdeclarative is needed for Qt6::QmlPrivate (see postPatch)
qtdeclarative
qtmultimedia
qtwebengine
# Other (auto-injected: bluez-qt)
qcoro
plasma-wayland-protocols
wayland
sdl3
];
postPatch = ''
substituteInPlace bin/plasma-bigscreen-wayland.in \
--replace @KDE_INSTALL_FULL_LIBEXECDIR@ "${plasma-workspace}/libexec"
# WORKAROUND: Plasma version numbers must match; we're building an
# unreleased package against a stable Plasma release. Remove once
# bigscreen is part of the Plasma release cycle.
substituteInPlace CMakeLists.txt \
--replace-fail 'set(PROJECT_VERSION "6.5.80")' 'set(PROJECT_VERSION "${plasma-workspace.version}")'
# WORKAROUND: QCoro6Qml's cmake config links against Qt6::QmlPrivate but
# doesn't call find_package to import the target. This is arguably a QCoro
# packaging bug in nixpkgs (it should propagate qtdeclarative). Remove
# once QCoro or the nixpkgs plasma-bigscreen package is fixed upstream.
substituteInPlace CMakeLists.txt \
--replace-fail 'find_package(QCoro6' 'find_package(Qt6 ''${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS QmlPrivate)
find_package(QCoro6'
'';
preFixup = ''
wrapQtApp $out/bin/plasma-bigscreen-wayland
'';
passthru.providedSessions = [
"plasma-bigscreen-wayland"
];
meta = {
description = "Plasma shell for TVs (Plasma Bigscreen)";
homepage = "https://plasma-bigscreen.org";
license = lib.licenses.gpl2Plus;
platforms = lib.platforms.linux;
};
}

View File

@@ -8,21 +8,6 @@ in
{
options.roles.printing = {
enable = mkEnableOption "Enable default printing setup";
printerName = mkOption {
type = types.str;
default = "MFC-L8900CDW_series";
description = "Name for the default printer";
};
printerUri = mkOption {
type = types.str;
default = "ipp://brother.oglehome/ipp/print";
description = "Device URI for the default printer (e.g., ipp://hostname/ipp/print)";
};
printerModel = mkOption {
type = types.str;
default = "everywhere";
description = "PPD model for the printer (use 'everywhere' for driverless IPP)";
};
};
config = mkIf cfg.enable
@@ -36,11 +21,11 @@ in
};
hardware.printers.ensurePrinters = [{
name = cfg.printerName;
deviceUri = cfg.printerUri;
model = cfg.printerModel;
name = "MFC-L8900CDW_series";
deviceUri = "ipp://brother.oglehome/ipp/print";
model = "everywhere";
}];
hardware.printers.ensureDefaultPrinter = cfg.printerName;
hardware.printers.ensureDefaultPrinter = "MFC-L8900CDW_series";
# Fix ensure-printers service to wait for network availability
systemd.services.ensure-printers = {

View File

@@ -1,149 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.roles.rclone-mount;
# Generate systemd service for a single mount
mkMountService = name: mountCfg: {
description = "rclone mount for ${name}";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
# Wait for parent mount points (e.g., ZFS pools) to be available
unitConfig = mkIf (mountCfg.requiresMountsFor != []) {
RequiresMountsFor = mountCfg.requiresMountsFor;
};
serviceConfig = {
Type = "notify";
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${mountCfg.mountPoint}";
ExecStart = concatStringsSep " " ([
"${pkgs.rclone}/bin/rclone mount"
":webdav:${mountCfg.remotePath}"
"${mountCfg.mountPoint}"
"--webdav-url=${mountCfg.webdavUrl}"
"--webdav-vendor=${mountCfg.webdavVendor}"
"--webdav-user=${mountCfg.username}"
"--allow-other"
"--vfs-cache-mode=${mountCfg.vfsCacheMode}"
"--dir-cache-time=${mountCfg.dirCacheTime}"
"--poll-interval=${mountCfg.pollInterval}"
"--log-level=${mountCfg.logLevel}"
] ++ mountCfg.extraArgs);
ExecStop = "${pkgs.fuse}/bin/fusermount -uz ${mountCfg.mountPoint}";
Restart = "on-failure";
RestartSec = "10s";
EnvironmentFile = mountCfg.environmentFile;
};
};
in
{
options.roles.rclone-mount = {
enable = mkEnableOption "Enable rclone WebDAV mounts";
mounts = mkOption {
type = types.attrsOf (types.submodule {
options = {
webdavUrl = mkOption {
type = types.str;
description = "WebDAV server URL (e.g., https://webdav.torbox.app)";
};
webdavVendor = mkOption {
type = types.enum [ "other" "nextcloud" "owncloud" "sharepoint" "sharepoint-ntlm" "fastmail" ];
default = "other";
description = "WebDAV server vendor for optimizations";
};
username = mkOption {
type = types.str;
description = "WebDAV username (often email address)";
};
environmentFile = mkOption {
type = types.path;
description = ''
Path to environment file containing RCLONE_WEBDAV_PASS.
The password should be obscured using: rclone obscure <password>
File format: RCLONE_WEBDAV_PASS=<obscured_password>
'';
};
mountPoint = mkOption {
type = types.str;
description = "Local mount point path";
};
remotePath = mkOption {
type = types.str;
default = "/";
description = "Remote path on WebDAV server to mount";
};
vfsCacheMode = mkOption {
type = types.enum [ "off" "minimal" "writes" "full" ];
default = "full";
description = ''
VFS cache mode. For streaming media, 'full' is recommended.
- off: No caching (direct reads/writes)
- minimal: Cache open files only
- writes: Cache writes and open files
- full: Full caching of all files
'';
};
dirCacheTime = mkOption {
type = types.str;
default = "5m";
description = "Time to cache directory entries";
};
pollInterval = mkOption {
type = types.str;
default = "1m";
description = "Poll interval for remote changes";
};
logLevel = mkOption {
type = types.enum [ "DEBUG" "INFO" "NOTICE" "ERROR" ];
default = "INFO";
description = "rclone log level";
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [];
description = "Extra arguments to pass to rclone mount";
};
requiresMountsFor = mkOption {
type = types.listOf types.str;
default = [];
description = ''
List of mount points that must be available before this service starts.
Use this when the mount point's parent is on a ZFS pool or other filesystem
that may not be mounted at boot time.
Example: [ "/media" ] to wait for the media ZFS pool to mount.
'';
};
};
});
default = {};
description = "Attribute set of rclone WebDAV mounts to configure";
};
};
config = mkIf cfg.enable {
# Ensure FUSE is available
environment.systemPackages = [ pkgs.rclone pkgs.fuse ];
programs.fuse.userAllowOther = true;
# Create systemd services for each mount
systemd.services = mapAttrs' (name: mountCfg:
nameValuePair "rclone-mount-${name}" (mkMountService name mountCfg)
) cfg.mounts;
};
}

View File

@@ -1,66 +1,3 @@
# Remote Build Role
#
# This module configures Nix distributed builds, allowing machines to offload
# builds to more powerful remote machines.
#
# SETUP INSTRUCTIONS
# ==================
#
# 1. BUILDER MACHINE SETUP
# On machines that will serve as builders (e.g., zix790prors, john-endesktop):
#
# a) Enable the builder role in configuration.nix:
# roles.remote-build.enableBuilder = true;
#
# b) After nixos-rebuild, the nix-builder user is created automatically.
# You need to add client SSH public keys to the builder. Either:
#
# Option A - Manual (recommended for initial setup):
# sudo mkdir -p /var/lib/nix-builder/.ssh
# sudo bash -c 'cat >> /var/lib/nix-builder/.ssh/authorized_keys' << 'EOF'
# ssh-ed25519 AAAA... root@client-hostname
# EOF
# sudo chown -R nix-builder:nix-builder /var/lib/nix-builder/.ssh
# sudo chmod 700 /var/lib/nix-builder/.ssh
# sudo chmod 600 /var/lib/nix-builder/.ssh/authorized_keys
#
# Option B - Via NixOS config (if you store keys in the repo):
# users.users.nix-builder.openssh.authorizedKeys.keys = [
# "ssh-ed25519 AAAA... root@client-hostname"
# ];
#
# 2. CLIENT MACHINE SETUP
# On machines that will use remote builders (e.g., nix-book):
#
# a) Configure builders in configuration.nix:
# roles.remote-build.builders = [
# {
# hostName = "zix790prors.oglehome";
# maxJobs = 16; # Number of parallel build jobs
# speedFactor = 3; # Higher = prefer this builder
# }
# {
# hostName = "john-endesktop.oglehome";
# maxJobs = 1; # Conservative for busy machines
# speedFactor = 1;
# }
# ];
#
# b) Generate SSH key for root (if not exists) and copy to builders:
# sudo ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""
# sudo cat /root/.ssh/id_ed25519.pub # Add this to builder's authorized_keys
#
# c) Accept the builder's host key (as root):
# sudo ssh nix-builder@zix790prors echo "Connected!"
# sudo ssh nix-builder@john-endesktop echo "Connected!"
#
# 3. VERIFY SETUP
# Test that distributed builds work:
# nix build --rebuild nixpkgs#hello --print-build-logs
#
# Check builder connectivity:
# nix store ping --store ssh-ng://nix-builder@zix790prors
#
{ lib, config, pkgs, ... }:
with lib;

View File

@@ -8,16 +8,6 @@ in
{
options.roles.virtualisation = {
enable = mkEnableOption "Enable virtualisation";
dockerUsers = mkOption {
type = types.listOf types.str;
default = [ "johno" ];
description = "List of users to add to the docker group";
};
waydroid = mkOption {
type = types.bool;
default = false;
description = "Enable waydroid support";
};
};
config = mkIf cfg.enable
@@ -25,7 +15,6 @@ in
virtualisation.libvirtd.enable = true;
programs.virt-manager.enable = true;
virtualisation.docker.enable = true;
users.extraGroups.docker.members = cfg.dockerUsers;
virtualisation.waydroid.enable = cfg.waydroid;
users.extraGroups.docker.members = [ "johno" ];
};
}

View File

@@ -1,71 +0,0 @@
{ config, lib, ... }:
with lib;
let
cfg = config.roles.wireguard;
in
{
options.roles.wireguard = {
enable = mkEnableOption "Enable WireGuard VPN";
interfaceName = mkOption {
type = types.str;
default = "wg0";
description = "Name of the WireGuard interface";
};
address = mkOption {
type = types.listOf types.str;
description = "Address(es) for the WireGuard interface";
};
privateKeyFile = mkOption {
type = types.path;
description = "Path to a root-owned file containing the WireGuard private key";
};
dns = mkOption {
type = types.listOf types.str;
default = [];
description = "DNS servers to use when the tunnel is active";
};
peers = mkOption {
type = types.listOf (types.submodule {
options = {
publicKey = mkOption {
type = types.str;
description = "Public key of the peer";
};
endpoint = mkOption {
type = types.str;
description = "Endpoint address of the peer (host:port)";
};
allowedIPs = mkOption {
type = types.listOf types.str;
description = "List of allowed IP ranges for this peer";
};
persistentKeepalive = mkOption {
type = types.int;
default = 25;
description = "Persistent keepalive interval in seconds";
};
};
});
description = "WireGuard peers";
};
autostart = mkOption {
type = types.bool;
default = false;
description = "Whether to start the VPN automatically on boot";
};
};
config = mkIf cfg.enable {
networking.wg-quick.interfaces.${cfg.interfaceName} = {
inherit (cfg) address dns autostart peers;
privateKeyFile = cfg.privateKeyFile;
};
systemd.services."wg-quick-${cfg.interfaceName}" = {
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
};
};
}

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
# Build Live USB ISO from flake configuration
# Creates an uncompressed ISO suitable for Ventoy and other USB boot tools
# Usage: nix run .#build-liveusb
# Or: ./scripts/build-liveusb.sh
set -euo pipefail
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
echo "Building Live USB ISO..."
nix build "${REPO_ROOT}#nixosConfigurations.live-usb.config.system.build.isoImage" --show-trace
if ls "${REPO_ROOT}/result/iso/"*.iso 1> /dev/null 2>&1; then
iso_file=$(ls "${REPO_ROOT}/result/iso/"*.iso)
echo "Build complete!"
echo "ISO location: $iso_file"
echo "Ready for Ventoy or dd to USB"
else
echo "Build failed - no ISO file found"
exit 1
fi

24
scripts/rotate-wallpaper.sh Executable file → Normal file
View File

@@ -1,30 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Rotate to the next wallpaper in the configured list."
echo ""
echo "This script increments the currentIndex in home/wallpapers/default.nix,"
echo "cycling through available wallpapers. Rebuild your system to apply"
echo "the new wallpaper."
echo ""
echo "Options:"
echo " --help, -h Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'

View File

@@ -1,30 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Update Doom Emacs to the latest commit from the doomemacs repository."
echo ""
echo "This script fetches the latest commit SHA from the default branch,"
echo "updates the rev and sha256 in home/roles/emacs/default.nix, and"
echo "prepares the configuration for a system rebuild."
echo ""
echo "Options:"
echo " --help, -h Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'

View File

@@ -1,35 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Perform a major upgrade of the NixOS configuration."
echo ""
echo "This script runs the following steps:"
echo " 1. Update all flake inputs (nix flake update)"
echo " 2. Update Doom Emacs to the latest commit"
echo " 3. Update Claude Code to the latest version"
echo " 4. Rotate to the next wallpaper"
echo ""
echo "After completion, review changes with 'git diff' and rebuild"
echo "your system with 'sudo nixos-rebuild switch --flake .'"
echo ""
echo "Options:"
echo " --help, -h Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'