Compare commits

...

74 Commits

Author SHA1 Message Date
f5ef23b2af feat(skills): Add import_gitea_issues skill for bead creation
Add a Claude Code skill that imports open Gitea issues as beads:
- Uses 'tea issues' to list open issues
- Checks existing beads to avoid duplicates
- Detects issue type (bug/feature/task) from content
- Creates beads with P2 priority and Gitea issue URL in notes
- Reports summary of imported vs skipped issues

Implements bead: nixos-configs-tdf
2026-01-10 12:56:33 -08:00
1d9249ea83 Ignore .beads in main 2026-01-10 12:45:53 -08:00
2fdd2d5345 fix(skills): Correct reconcile_beads instructions for bd and tea CLI
- Fix jq syntax: bd show --json returns array, use .[0].notes
- Add grep command to extract PR number from URL
- Correct Gitea workflow: tea pr view lists all PRs, use tea pr list --state=closed instead
2026-01-10 12:45:25 -08:00
722cb315dc Stop tracking sync_base.jsonl in 2026-01-10 12:42:26 -08:00
e042acff16 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:41:04 -08:00
4fe531f87f feat(emacs): Add prebuilt Doom option using nix-doom-emacs-unstraightened
Implement pre-built Doom Emacs packages for the live USB image, eliminating
the need to run `doom sync` after first boot.

Changes:
- Add nix-doom-emacs-unstraightened flake input
- Add homeModule to all three module sets (nixos, unstable, darwin)
- Add `prebuiltDoom` option to emacs role (default: false)
- Enable prebuiltDoom for live-usb configuration
- Pin custom packages in packages.el for deterministic builds:
  - claude-code-ide, gptel-tool-library, beads

When prebuiltDoom=true, all Doom packages are compiled at nix build time
using emacs-overlay. The doom configuration is stored in the nix store
(read-only), and no `doom sync` is required at runtime.

This is ideal for:
- Live USB images
- Immutable/reproducible systems
- Offline deployments

Closes: nixos-configs-1wd
2026-01-10 12:33:40 -08:00
266dee9f8f feat(home-server): Add starship prompt and alphabetize roles 2026-01-10 12:24:52 -08:00
38395c238f Fix race condition in Claude Code skill installation 2026-01-10 12:24:06 -08:00
e4a1771f48 sync beads 2026-01-10 12:15:40 -08:00
ff1fb245ac Add home-manager integration for john-endesktop server
Create home-server.nix with minimal development-focused configuration
enabling base, emacs, development, and tmux roles. Update flake.nix
to wire up home-manager for the johno user on the server.
2026-01-10 12:15:12 -08:00
82fb1738c1 feat(home): Add starship cross-shell prompt role
Add a new home-manager role for starship.rs, a fast and customizable
cross-shell prompt written in Rust.

Configuration includes:
- Bash and Zsh integration enabled
- Clean character symbols (> for success, x for error)
- Vi mode indicator support
- Smart directory truncation (4 levels, truncate to repo root)
- Git branch and status display
- Nix shell indicator with snowflake symbol
- Command duration for long-running commands (2s+)
- Disabled noisy modules (language runtimes, cloud providers)

Enabled in: home-desktop, home-laptop-compact, home-live-usb,
home-media-center configurations.

Closes: nixos-configs-uji
2026-01-10 11:46:43 -08:00
425e4f4cee Extract shared NixOS/Darwin base config into roles/common.nix
Create roles/common.nix containing shared configuration between NixOS and
Darwin: timezone, base packages (git, glances, pciutils, tree, usbutils, vim),
nix settings (experimental-features, max-jobs, trusted-users), gc config,
and allowUnfree setting.

Both roles/default.nix and roles/darwin.nix now import common.nix and only
contain platform-specific configuration.
2026-01-10 11:44:58 -08:00
0e5b11e55d Remove humanlayer prefix from local skills installation
Local skills don't need the humanlayer: prefix since they're not
from the humanlayer/claude-plugins repo.
2026-01-10 11:21:31 -08:00
1ba1a8fc9d [nixos-configs-7hd] Add parallel_beads skill for orchestrating bead processing
- Add skills/ directory for local Claude skills
- Create parallel_beads.md skill that orchestrates:
  - Phase 1: Multi-select bead selection from bd ready
  - Phase 2: Parallel subagents for implementation (worktree, implement, commit, PR)
  - Phase 3: Parallel review subagents
  - Phase 4: Cleanup and summary
- Update default.nix to install local skills alongside humanlayer plugins
- Support both gh (GitHub) and tea (Gitea/Forgejo) based on origin URL
2026-01-10 11:14:43 -08:00
009b84656f [john-endesktop] Update migration plan with completed pre-migration items 2026-01-10 10:49:04 -08:00
ef4e4509d3 [john-endesktop] Remove swap 2026-01-10 09:43:22 -08:00
cd6b528692 [john-endesktop] Update with actual disk ids 2026-01-10 09:34:28 -08:00
3914b54c73 actually actually finish for real? 2026-01-09 11:24:52 -08:00
9aa74258f9 actually finish beads-sync migration 2026-01-09 11:23:46 -08:00
64dda20aa4 finish migration to beads-sync 2026-01-09 11:22:58 -08:00
ac01548e89 chore(beads): commit untracked JSONL files
Auto-committed by bd doctor --fix
2026-01-09 11:22:13 -08:00
bb7f79843b bd sync: 2026-01-09 11:22:07 2026-01-09 11:22:07 -08:00
c1d6663a36 bd init 2026-01-09 11:21:49 -08:00
3cf4403ffa Add perles TUI package for Beads issue tracking
Adds a custom Nix package for perles, a terminal user interface for the
Beads issue tracking system with BQL query language support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 11:20:02 -08:00
4e6123de9a Simplify beads integration to use bd setup claude
Replace complex marketplace plugin installation with simple 'bd setup claude'
which installs hooks into ~/.claude/settings.json. This is the recommended
approach for Claude Code (CLI + hooks) vs the MCP server approach.
2026-01-08 19:28:17 -08:00
19ee298b71 Add beads Claude plugin installation via home-manager
- Add beadsRepo reference from flake input
- Add activation script to install beads as marketplace plugin
- Updates known_marketplaces.json and config.json declaratively
2026-01-08 19:21:58 -08:00
537f7831a7 Fix claude-code-ide keybinds and consolidate settings
Move keybinding to :init block so SPC o c works before first invocation.
Add :commands for proper autoloading. Migrate customizations from
custom.el to config.el.
2026-01-05 21:11:58 -08:00
bf0d16fe1a Add Mod+Shift+Return keybinding to open browser window in i3/sway 2026-01-05 21:07:11 -08:00
858b6009ae Style blocked org-agenda tasks in purple instead of grey 2026-01-05 19:48:47 -08:00
40b323dcfd Revert "Use direct hibernate on nix-book lid close"
This reverts commit 2c70504c43.
2026-01-05 09:00:48 -08:00
60bd89b02c Make Sent folder pull-only in mbsync config
Split mbsync channel into two: proton-main for bidirectional sync
(excluding Sent) and proton-sent for pull-only sync. This avoids
"far side refuses to store" errors from Proton Bridge rejecting
uploaded sent messages.
2026-01-05 09:00:08 -08:00
5a5de7353b Ensure Mail directory exists before mbsync runs 2026-01-04 17:23:32 -08:00
e25aa7acab Update flake inputs, doom emacs, and claude-code 2026-01-04 17:12:35 -08:00
4a284de8a1 Add wallpaper rotation system with upgrade workflow
- Add home/wallpapers/default.nix with per-wallpaper scaling options
- Add 5 new Metroid-themed wallpapers to the rotation
- Update i3+sway and plasma-manager to use wallpaper module
- Add scripts/rotate-wallpaper.sh to cycle through wallpapers
- Add scripts/upgrade.sh to chain: flake update, doom, claude-code, wallpaper
- Add flake apps: rotate-wallpaper, update-claude-code, upgrade
- Fix claude-code update.sh to use REPO_ROOT for flake app compatibility

Run `nix run .#upgrade` for full system update with wallpaper rotation.
2026-01-04 17:12:27 -08:00
7be694ef66 [doom] Increase default font size 2026-01-04 13:38:56 -08:00
0ccfc30c73 Add personal address to mu init for mu4e integration 2026-01-04 13:36:56 -08:00
dab96a1c50 Replace notmuch with mu4e for email
mu4e's refile moves messages between IMAP folders, enabling
cross-device sync via mbsync. notmuch tags are local-only.

- Switch from notmuch to mu/mu4e packages
- Auto-initialize mu database on first sync
- Configure mu4e with folder shortcuts and refile to Archive
- Add NixOS load-path for mu4e elisp files
2026-01-04 12:59:35 -08:00
7898def044 Fix notmuch inbox tag to match IMAP INBOX folder
Previously, the notmuch config applied the "inbox" tag to all new
messages regardless of which IMAP folder they were in. This caused
tag:inbox to return all 22k+ messages instead of just those in INBOX.

Changes:
- Use temporary "new" tag instead of "inbox" for newly indexed messages
- Add post-new hook that applies tags based on maildir folder location
- inbox tag now only applies to messages in INBOX folder
- Also adds sent, draft, spam, deleted, and archive tags based on folder
2026-01-04 12:23:39 -08:00
4ce48313f6 Update nixpkgs-unstable to fix tree-sitter-razor
tree-sitter-razor was marked as broken in the previous lock, causing
nix flake check to fail on nix-deck. The grammar has been fixed upstream.
2026-01-04 12:03:19 -08:00
2c70504c43 Use direct hibernate on nix-book lid close
S3 deep sleep is broken on 12th Gen Intel Alder Lake. Switch from
suspend-then-hibernate to direct hibernate on all lid close events.
2026-01-04 12:03:10 -08:00
a22c7fec28 Add Tea CLI documentation for Gitea issue tracking 2026-01-04 11:08:32 -08:00
ef3e9b8c82 Implement volume control for boxy via Home Assistant
Add remote volume control for the living room media center (boxy) that
controls the Denon AVR through Home Assistant instead of local audio.

- Create plasma-manager-kodi role with custom KDE hotkeys for volume
  up/down/mute that execute shell scripts
- Scripts retrieve HA token from KDE Wallet and send API requests to
  control media_player.denon_avr_s970h_2
- Add retry logic (3 attempts) and error notifications
- Enable KDE Wallet PAM integration for automatic wallet unlock
- Configure KDE settings: Breeze Dark theme, disable screen locking,
  empty session restore
- Add kconfig package for debugging tools
2026-01-03 17:11:43 -08:00
0590dad71e [gaming] Add yarg 2026-01-02 13:43:00 -08:00
c81594af01 Add suspend-then-hibernate power management for nix-book 2025-12-30 00:29:24 -08:00
6f999882d3 Reorder migration steps to export ZFS pools before shutdown 2025-12-30 00:29:21 -08:00
8bb20bf05e Add NixOS configuration for john-endesktop ZFS/NFS server
Create configuration to migrate john-endesktop from Arch Linux to NixOS while
maintaining existing ZFS pools (media JBOD and swarmvols mirror) and NFS
exports for k3s cluster.

Configuration includes:
- ZFS support with automatic pool import
- NFS server exporting both pools to 10.0.0.0/24
- Correct ZFS hostid (007f0101) to resolve hostid warnings
- Btrfs root filesystem on nvme0n1p5 (810GB)
- Comprehensive migration plan with rollback procedures

The migration is designed to be safe with Arch Linux remaining bootable
as a fallback until NixOS is verified stable.
2025-12-29 23:50:36 -08:00
c480bcdd1d Disable virtual surround
This was breaking microphone access when I was headed into a voice chat
2025-12-29 23:46:20 -08:00
05fed3ede1 Add virtual 4.1 surround sound configuration for zix790prors
Create a PipeWire virtual surround sink that routes audio to multiple
physical outputs:
- FL/FR channels → AmazonBasics USB speaker
- RL/RR channels → Fosi BT20A PRO Bluetooth speaker
- LFE channel → AmazonBasics (duplicated to both channels)

Uses loopback modules with systemd services to maintain correct routing,
as PipeWire's target.object parameter doesn't auto-connect properly.
A timer checks every 10 seconds and fixes incorrect connections.

Configuration is machine-specific and isolated in virtual-surround.nix.
2025-12-29 12:01:29 -08:00
0a9de8d159 Fix rbw-agent launching from systemd services
The rbw unlock systemd services were failing to launch the rbw-agent
daemon due to two issues:

1. Missing RBW_AGENT environment variable - rbw looks for this variable
   to locate the agent binary, falling back to PATH lookup. Systemd
   user services have minimal environments without the necessary PATH.

2. Default KillMode=control-group - when the oneshot service completed,
   systemd was killing all processes in the cgroup including the
   daemonized agent.

Fixed by:
- Setting RBW_AGENT environment variable to explicit agent binary path
- Using KillMode=process to only kill the main process, allowing the
  spawned agent daemon to persist after service completion
2025-12-29 10:21:58 -08:00
055d6ab421 Add systemd services to unlock rbw vault on login and resume
Adds two systemd user services to automatically unlock the rbw vault:
- rbw-unlock-on-login: Runs at graphical session start
- rbw-unlock-on-resume: Runs after resuming from suspend

This solves the issue of mbsync prompting for password every 5 minutes.
Once unlocked, the vault stays unlocked as long as mbsync syncs every
5 minutes (which resets the 1-hour lock timeout). Only prompts at login
or after long suspend periods.
2025-12-26 13:20:18 -08:00
d5c6342b84 [home-desktop] Add email role 2025-12-25 09:54:20 -08:00
e04dacdf65 Add email support with notmuch, mbsync, and msmtp
Set up complete email workflow in Emacs using:
- notmuch for email indexing and UI
- mbsync for IMAP synchronization from proton.johnogle.info
- msmtp for SMTP sending via port 25 with PLAIN auth
- systemd timer for automatic sync every 5 minutes

Configuration includes:
- New email role at home/roles/email with all mail tools
- Doom Emacs notmuch module enabled with saved searches
- Secure credential retrieval via rbw from Bitwarden
- Fixed systemd service PATH to access rbw-agent
- TLS/STARTTLS for secure connections

Email role enabled on nix-book (laptop-compact profile).
2025-12-22 23:00:23 -08:00
7d74917bdc [doom] update 2025-12-22 14:36:14 -08:00
5a4ab71849 Add script to update doomemacs to latest commit
Creates update-doomemacs.sh script that:
- Fetches latest commit SHA from doomemacs/doomemacs repo
- Automatically detects the default branch
- Updates both rev and sha256 in home/roles/emacs/default.nix
- Works from anywhere in the repo using git rev-parse

Also adds a flake app so it can be run with:
  nix run .#update-doomemacs
2025-12-22 14:34:56 -08:00
bcebf9b376 claude-code: Update to version 2.0.75 2025-12-22 14:27:10 -08:00
0f76939983 Increase jellyfinScaleFactor to 2.5 for boxy 2025-12-22 12:30:36 -08:00
a1da2f5cc1 Fix jellyfinScaleFactor for .desktop entry launches 2025-12-22 12:30:35 -08:00
175da48170 Fix jellyfinScaleFactor implementation in kodi role
The original implementation had several issues that prevented it from
building:
- Used buildInputs instead of nativeBuildInputs for makeWrapper
- Referenced wrong executable name (jellyfinmediaplayer vs jellyfin-desktop)
- Used wrapProgram which doesn't work with symlinks from symlinkJoin

Fixed by using makeWrapper directly with the correct executable path
after removing the symlink.

Also enabled jellyfinScaleFactor = 1.5 on boxy for UI scaling.
2025-12-22 12:17:54 -08:00
ac956ef48c [flake] update 2025-12-22 12:03:31 -08:00
0c1190f39c Add jellyfinScaleFactor option to kodi role for UI scaling 2025-12-22 12:02:15 -08:00
00f05d1bb2 [i3+sway] Setup brightness controll for ddc monitors 2025-12-20 11:52:27 -08:00
4e6c6ab81d Make nix gc options overridable with mkDefault 2025-12-08 14:34:53 -08:00
04e1a8563c Fix sketchybar memory indicator to show actual memory pressure
The previous implementation included inactive/cached pages and used
"Pages stored in compressor" (uncompressed size), resulting in inflated
percentages (~88%) that didn't reflect actual memory pressure.

Now uses:
- Anonymous pages (matches Activity Monitor's "App Memory")
- Pages wired down (system memory)
- Pages occupied by compressor (actual RAM used, not uncompressed size)

Also switches to awk for arithmetic to avoid bash integer overflow on
systems with >4GB RAM.
2025-12-08 14:34:22 -08:00
7278dc8306 Fix deprecated NixOS options to resolve flake check warnings
- Update system attribute to stdenv.hostPlatform.system in overlays
- Migrate git config to new settings structure (user.name, user.email)
- Move ssh.addKeysToAgent to matchBlocks configuration
- Disable ssh default config to prevent future deprecation warnings
2025-12-07 22:17:54 -08:00
066eea2999 [kodi] Fix lib.warn syntax for insecure package warning
Fix syntax error by wrapping permittedInsecurePackages list with
lib.warn function call. The warning now properly displays during
evaluation about qtwebengine-5.15.19 being required for
jellyfin-media-player until it migrates to qt6.
2025-12-07 21:11:25 -08:00
80633142fb [kodi] Fix jellyfin 2025-12-07 20:55:34 -08:00
3029e3d9a8 [kodi] jellyfin > jellyfin-media-player 2025-12-07 20:53:59 -08:00
3483e26bce Add 3D printing home role with orca-slicer and openscad
Create a new home-manager role for 3D printing applications including
orca-slicer for slicing and openscad-unstable for 3D modeling. Enable
the role in the desktop configuration for full-featured experience.
2025-12-06 19:22:37 -08:00
b3add6ddf8 Add automatic garbage collection for home-manager user profiles
Configure nix.gc to automatically clean up old home-manager generations
weekly, which complements the existing system-level gc. Uses
--delete-older-than 10d on Linux to maintain a rollback window, but
overrides to -d on Darwin to work around a launchd bug where multi-part
options aren't properly split into separate arguments.
2025-12-06 13:58:26 -08:00
89994e3fc8 Update SketchyBar styling to match Waybar appearance
- Change font from SF Mono to Fira Code for consistency with Waybar
- Reduce bar height from 32px to 30px
- Switch to solid dark gray background (#333333) instead of semi-transparent black
- Remove rounded corners on items (corner_radius 0 instead of 5)
- Make item backgrounds full height (30px) to match bar height
- Remove all separator items between modules for cleaner appearance
- Add topmost and sticky properties to prevent window shadows from darkening bar
- Adjust aerospace bottom gap from 40px to 38px to account for new bar height
- Set all workspace text to white with bold font for active workspace
2025-12-05 14:58:11 -08:00
0e9671a45f Fix SketchyBar workspace indicators not appearing after 25.11 upgrade
Add explicit `drawing=on` to all workspace item states (initial creation,
focused, non-empty, and empty) to prevent items from getting stuck with
`drawing=off`. With `updates=when_shown`, items with `drawing=off` never
run their update scripts, causing workspaces that start empty to never
appear even when focused or given windows.

Changes:
- Add drawing=on to initial workspace creation (line 393)
- Add drawing=on to focused workspace state (line 568)
- Add drawing=on to empty workspace state (line 582)
- Add drawing=on to non-empty workspace state (line 591)
- Update comment for empty workspace to explain drawing=on usage

This ensures workspace indicators automatically appear within 2 seconds
when focused or given windows, without needing manual `sketchybar --update`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 14:44:23 -08:00
f4078970b2 Merge branch '25.11'
* 25.11:
  Simplify aerospace launchd config and remove menu bar hiding
  Remove mbedtls_2 workaround for dolphin packages
  Re-enable packages after NixOS 25.11 upgrade
  Add --unsupported-gpu flag to Sway for zix790prors compatibility
  Fix deprecated package names for NixOS 25.11
  Upgrade NixOS to 25.11 and fix Jovian module organization
2025-12-05 14:20:49 -08:00
bc42c4dc77 Fix SketchyBar workspace indicators not reappearing
The aerospace workspace plugin had two issues preventing workspace
indicators from properly showing/hiding:

1. The script expected workspace number as $1 but update_freq routine
   calls only provide $NAME environment variable. Now extracts from
   either source.

2. Using drawing=off to hide workspaces was unreliable - items wouldn't
   consistently reappear. Now uses width=0 with cleared icon/label
   content to collapse items instead.

Workspaces now properly appear within 2 seconds when windows are
created or moved, without requiring manual sketchybar --update.
2025-12-04 16:31:44 -08:00
585f9ef5c7 Remove macOS menu bar hiding from aerospace SketchyBar config 2025-12-04 10:51:12 -08:00
62 changed files with 2818 additions and 241 deletions

39
.beads/.gitignore vendored Normal file
View File

@@ -0,0 +1,39 @@
# SQLite databases
*.db
*.db?*
*.db-journal
*.db-wal
*.db-shm
# Daemon runtime files
daemon.lock
daemon.log
daemon.pid
bd.sock
sync-state.json
last-touched
# Local version tracking (prevents upgrade notification spam after git ops)
.local_version
# Legacy database files
db.sqlite
bd.db
# Worktree redirect file (contains relative path to main repo's .beads/)
# Must not be committed as paths would be wrong in other clones
redirect
# Merge artifacts (temporary files from 3-way merge)
beads.base.jsonl
beads.base.meta.json
beads.left.jsonl
beads.left.meta.json
beads.right.jsonl
beads.right.meta.json
# 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.
# The JSONL files (issues.jsonl, interactions.jsonl) and config files
# are tracked by git by default since no pattern above ignores them.

0
.beads/.sync.lock Normal file
View File

81
.beads/README.md Normal file
View File

@@ -0,0 +1,81 @@
# Beads - AI-Native Issue Tracking
Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code.
## What is Beads?
Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git.
**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads)
## Quick Start
### Essential Commands
```bash
# Create new issues
bd create "Add user authentication"
# View all issues
bd list
# View issue details
bd show <issue-id>
# Update issue status
bd update <issue-id> --status in_progress
bd update <issue-id> --status done
# Sync with git remote
bd sync
```
### Working with Issues
Issues in Beads are:
- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code
- **AI-friendly**: CLI-first design works perfectly with AI coding agents
- **Branch-aware**: Issues can follow your branch workflow
- **Always in sync**: Auto-syncs with your commits
## Why Beads?
**AI-Native Design**
- Built specifically for AI-assisted development workflows
- CLI-first interface works seamlessly with AI coding agents
- No context switching to web UIs
🚀 **Developer Focused**
- Issues live in your repo, right next to your code
- Works offline, syncs when you push
- Fast, lightweight, and stays out of your way
🔧 **Git Integration**
- Automatic sync with git commits
- Branch-aware issue tracking
- Intelligent JSONL merge resolution
## Get Started with Beads
Try Beads in your own projects:
```bash
# Install Beads
curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
# Initialize in your repo
bd init
# Create your first issue
bd create "Try out Beads"
```
## Learn More
- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs)
- **Quick Start Guide**: Run `bd quickstart`
- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples)
---
*Beads: Issue tracking that moves at the speed of thought*

62
.beads/config.yaml Normal file
View File

@@ -0,0 +1,62 @@
# Beads Configuration File
# This file configures default behavior for all bd commands in this repository
# All settings can also be set via environment variables (BD_* prefix)
# or overridden with command-line flags
# 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: ""
# 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
# instead of SQLite database
# no-db: false
# Disable daemon for RPC communication (forces direct database access)
# no-daemon: false
# Disable auto-flush of database to JSONL after mutations
# no-auto-flush: false
# Disable auto-import from JSONL when it's newer than database
# no-auto-import: false
# Enable JSON output by default
# json: false
# Default actor for audit trails (overridden by BD_ACTOR or --actor)
# actor: ""
# Path to database (overridden by BEADS_DB or --db)
# db: ""
# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON)
# auto-start-daemon: true
# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE)
# flush-debounce: "5s"
# Git branch for beads commits (bd sync will commit to this branch)
# IMPORTANT: Set this for team projects so all clones use the same sync branch.
# This setting persists across clones (unlike database config which is gitignored).
# Can also use BEADS_SYNC_BRANCH env var for local override.
# If not set, bd sync will require you to run 'bd config set sync.branch <branch>'.
sync-branch: "beads-sync"
# Multi-repo configuration (experimental - bd-307)
# Allows hydrating from multiple repositories and routing writes to the correct JSONL
# repos:
# primary: "." # Primary repo (where this database lives)
# additional: # Additional repos to hydrate from (read-only)
# - ~/beads-planning # Personal planning repo
# - ~/work-planning # Work planning repo
# Integration settings (access with 'bd config get/set')
# These are stored in the database, not in this file:
# - jira.url
# - jira.project
# - linear.url
# - linear.api-key
# - github.org
# - github.repo

View File

4
.beads/metadata.json Normal file
View File

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

View File

@@ -0,0 +1,130 @@
---
description: Import open Gitea issues as beads, skipping already-imported ones
---
# Import Gitea Issues as Beads
This skill imports open Gitea issues as beads, checking for duplicates to avoid re-importing already tracked issues.
## Prerequisites
- `tea` CLI must be installed and configured for the repository
- `bd` (beads) CLI must be installed
- Must be in a git repository with a Gitea/Forgejo remote
## Workflow
### Step 1: Get open Gitea issues
List all open issues using `tea`:
```bash
tea issues
```
This returns a table with columns: INDEX, TITLE, LABELS, MILESTONE
### Step 2: Get existing beads
List all current beads to check what's already imported:
```bash
bd list
```
Also check bead notes for issue URLs to identify imports:
```bash
bd list --json | jq -r '.[] | select(.notes != null) | .notes' | grep -oP 'issues/\K\d+'
```
### Step 3: Check for already-linked PRs
Check if any open PRs reference beads (skip these issues as they're being worked on):
```bash
tea pr list
```
Look for PRs with:
- Bead ID in title: `[nixos-configs-xxx]`
- Bead reference in body: `Implements bead:` or `Bead ID:`
### Step 4: For each untracked issue, create a bead
For each issue not already tracked:
1. **Get full issue details**:
```bash
tea issue [ISSUE_NUMBER]
```
2. **Determine bead type** based on issue content:
- "bug" - if issue mentions bug, error, broken, fix, crash
- "feature" - if issue mentions feature, add, new, enhancement
- "task" - default for other issues
3. **Create the bead**:
```bash
bd add "[ISSUE_TITLE]" \
--type=[TYPE] \
--priority=P2 \
--notes="Gitea issue: [ISSUE_URL]
Original issue description:
[ISSUE_BODY]"
```
Note: The `--notes` flag accepts multi-line content.
### Step 5: Report results
Present a summary:
```
## Gitea Issues Import Summary
### Imported as Beads
| Issue | Title | Bead ID | Type |
|-------|-------|---------|------|
| #5 | Add dark mode | nixos-configs-abc | feature |
| #3 | Config broken on reboot | nixos-configs-def | bug |
### Skipped (Already Tracked)
| Issue | Title | Reason |
|-------|-------|--------|
| #4 | Update flake | Existing bead: nixos-configs-xyz |
| #2 | Refactor roles | PR #7 references bead |
### Skipped (Other)
| Issue | Title | Reason |
|-------|-------|--------|
| #1 | Discussion: future plans | No actionable work |
```
## Type Detection Heuristics
Keywords to detect issue type:
**Bug indicators** (case-insensitive):
- bug, error, broken, fix, crash, fail, issue, problem, wrong, not working
**Feature indicators** (case-insensitive):
- feature, add, new, enhancement, implement, support, request, want, would be nice
**Task** (default):
- Anything not matching bug or feature patterns
## Error Handling
- **tea not configured**: Report error and exit
- **bd not available**: Report error and exit
- **Issue already has bead**: Skip and report in summary
- **Issue is a PR**: Skip (tea shows PRs and issues separately)
## Notes
- Default priority is P2; adjust manually after import if needed
- Issue labels from Gitea are not automatically mapped to bead tags
- Run this periodically to catch new issues
- After import, use `bd ready` to see which beads can be worked on

3
.gitattributes vendored Normal file
View File

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

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
result result
thoughts thoughts
.beads

View File

@@ -172,6 +172,58 @@ Creates an ISO suitable for Ventoy and other USB boot tools in `./result/iso/`.
- **Garbage collection**: Automatic, deletes older than 10 days - **Garbage collection**: Automatic, deletes older than 10 days
- **Unfree packages**: Allowed globally - **Unfree packages**: Allowed globally
## Issue Tracking (Gitea)
**Tea CLI for Gitea:**
```bash
# Note: When using tea CLI, you must specify --repo johno/nixos-configs
# The CLI doesn't automatically detect the repo from git remote
# List all issues (open by default)
tea issues --repo johno/nixos-configs
# List closed issues
tea issues --repo johno/nixos-configs --state closed
# View specific issue
tea issue --repo johno/nixos-configs 2
# Create new issue
tea issues create --repo johno/nixos-configs --title "Issue title" --body "Description"
# Add comment to issue
tea comment --repo johno/nixos-configs 2 "Comment text"
# Close issue (note: 'issues' is plural, issue number comes last)
tea issues close --repo johno/nixos-configs 2
```
## Important Notes ## Important Notes
- **Sudo access**: Claude Code does not have sudo access. Ask the user to run elevated commands like `sudo nixos-rebuild switch` - **Sudo access**: Claude Code does not have sudo access. Ask the user to run elevated commands like `sudo nixos-rebuild switch`
## Landing the Plane (Session Completion)
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
**MANDATORY WORKFLOW:**
1. **File issues for remaining work** - Create issues for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **PUSH TO REMOTE** - This is MANDATORY:
```bash
git pull --rebase
bd sync
git push
git status # MUST show "up to date with origin"
```
5. **Clean up** - Clear stashes, prune remote branches
6. **Verify** - All changes committed AND pushed
7. **Hand off** - Provide context for next session
**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds
- NEVER stop before pushing - that leaves work stranded locally
- NEVER say "ready to push when you are" - YOU must push
- If push fails, resolve and retry until it succeeds

185
flake.lock generated
View File

@@ -1,13 +1,73 @@
{ {
"nodes": { "nodes": {
"beads": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1767911810,
"narHash": "sha256-0L4ATr01UsmBC0rSW62VIMVVSUihAQu2+ZOoHk9BQnA=",
"owner": "steveyegge",
"repo": "beads",
"rev": "28ff9fe9919a9665a0f00f5b3fcd084b43fb6cc3",
"type": "github"
},
"original": {
"owner": "steveyegge",
"repo": "beads",
"type": "github"
}
},
"doomemacs": {
"flake": false,
"locked": {
"lastModified": 1767773143,
"narHash": "sha256-QL/t9v2kFNxBDyNJb/s411o3mxujan+QX5IZglTdpTk=",
"owner": "doomemacs",
"repo": "doomemacs",
"rev": "3e15fb36d7f94f0a218bda977be4d3f5da983a71",
"type": "github"
},
"original": {
"owner": "doomemacs",
"repo": "doomemacs",
"type": "github"
}
},
"emacs-overlay": {
"inputs": {
"nixpkgs": [
"nix-doom-emacs-unstraightened"
],
"nixpkgs-stable": [
"nix-doom-emacs-unstraightened"
]
},
"locked": {
"lastModified": 1768011937,
"narHash": "sha256-SnU2XTo34vwVaijs+4VwcXTNwMWO4nwzzs08N39UagA=",
"owner": "nix-community",
"repo": "emacs-overlay",
"rev": "79abf71d9897cf3b5189f7175cda1b1102abc65c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "emacs-overlay",
"type": "github"
}
},
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1761588595, "lastModified": 1765121682,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", "narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", "rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -16,6 +76,24 @@
"type": "github" "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"
}
},
"google-cookie-retrieval": { "google-cookie-retrieval": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -43,11 +121,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1764866045, "lastModified": 1767514898,
"narHash": "sha256-0GsEtXV9OquDQ1VclQfP16cU5VZh7NEVIOjSH4UaJuM=", "narHash": "sha256-ONYqnKrPzfKEEPChoJ9qPcfvBqW9ZgieDKD7UezWPg4=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "f63d0fe9d81d36e5fc95497217a72e02b8b7bcab", "rev": "7a06e8a2f844e128d3b210a000a62716b6040b7f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -64,11 +142,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1764872372, "lastModified": 1767556355,
"narHash": "sha256-uZuXRz9CzeCHsRbc2MQvKomwoX6GcFC5BUMEk3ouSFU=", "narHash": "sha256-RDTUBDQBi9D4eD9iJQWtUDN/13MDLX+KmE+TwwNUp2s=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "05a56dbf24f195c62286e3273a2671d3b4904b00", "rev": "f894bc4ffde179d178d8deb374fcf9855d1a82b7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -86,11 +164,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1764746434, "lastModified": 1767082077,
"narHash": "sha256-6ymFuw+Z1C90ezf8H0BP3c2JFZhJYwMq31px2StwWHU=", "narHash": "sha256-2tL1mRb9uFJThUNfuDm/ehrnPvImL/QDtCxfn71IEz4=",
"owner": "Jovian-Experiments", "owner": "Jovian-Experiments",
"repo": "Jovian-NixOS", "repo": "Jovian-NixOS",
"rev": "b4c0b604148adacf119b89824ed26df8926ce42c", "rev": "efd4b22e6fdc6d7fb4e186ae333a4b74e03da440",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -106,11 +184,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1764161084, "lastModified": 1765066094,
"narHash": "sha256-HN84sByg9FhJnojkGGDSrcjcbeioFWoNXfuyYfJ1kBE=", "narHash": "sha256-0YSU35gfRFJzx/lTGgOt6ubP8K6LeW0vaywzNNqxkl4=",
"owner": "nix-darwin", "owner": "nix-darwin",
"repo": "nix-darwin", "repo": "nix-darwin",
"rev": "e95de00a471d07435e0527ff4db092c84998698e", "rev": "688427b1aab9afb478ca07989dc754fa543e03d5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -120,6 +198,27 @@
"type": "github" "type": "github"
} }
}, },
"nix-doom-emacs-unstraightened": {
"inputs": {
"doomemacs": "doomemacs",
"emacs-overlay": "emacs-overlay",
"nixpkgs": [],
"systems": "systems_2"
},
"locked": {
"lastModified": 1768034604,
"narHash": "sha256-62pIZMvGHhYJmMiiBsxHqZt/dFyENPcFHlJq5NJF3Sw=",
"owner": "marienz",
"repo": "nix-doom-emacs-unstraightened",
"rev": "9b3b8044fe4ccdcbb2d6f733d7dbe4d5feea18bc",
"type": "github"
},
"original": {
"owner": "marienz",
"repo": "nix-doom-emacs-unstraightened",
"type": "github"
}
},
"nix-github-actions": { "nix-github-actions": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -148,11 +247,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1764730608, "lastModified": 1765841014,
"narHash": "sha256-FxKIa3OCSRVC23qrk7VT68vExUcmSruJ8OobVlSWOxc=", "narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=",
"owner": "nix-community", "owner": "nix-community",
"repo": "NixOS-WSL", "repo": "NixOS-WSL",
"rev": "10124c58674360765adcb38c9a8b081fb72904e4", "rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -164,11 +263,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1764517877, "lastModified": 1765472234,
"narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c", "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -180,11 +279,11 @@
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1764667669, "lastModified": 1767379071,
"narHash": "sha256-7WUCZfmqLAssbDqwg9cUDAXrSoXN79eEEq17qhTNM/Y=", "narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "418468ac9527e799809c900eda37cbff999199b6", "rev": "fb7944c166a3b630f177938e478f0378e64ce108",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -196,11 +295,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1764677808, "lastModified": 1767480499,
"narHash": "sha256-H3lC7knbXOBrHI9hITQ7modLuX20mYJVhZORL5ioms0=", "narHash": "sha256-8IQQUorUGiSmFaPnLSo2+T+rjHtiNWc+OAzeHck7N48=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1aab89277eb2d87823d5b69bae631a2496cff57a", "rev": "30a3c519afcf3f99e2c6df3b359aec5692054d92",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -258,17 +357,49 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"beads": "beads",
"google-cookie-retrieval": "google-cookie-retrieval", "google-cookie-retrieval": "google-cookie-retrieval",
"home-manager": "home-manager", "home-manager": "home-manager",
"home-manager-unstable": "home-manager-unstable", "home-manager-unstable": "home-manager-unstable",
"jovian": "jovian", "jovian": "jovian",
"nix-darwin": "nix-darwin", "nix-darwin": "nix-darwin",
"nix-doom-emacs-unstraightened": "nix-doom-emacs-unstraightened",
"nixos-wsl": "nixos-wsl", "nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_2",
"nixpkgs-unstable": "nixpkgs-unstable", "nixpkgs-unstable": "nixpkgs-unstable",
"plasma-manager": "plasma-manager", "plasma-manager": "plasma-manager",
"plasma-manager-unstable": "plasma-manager-unstable" "plasma-manager-unstable": "plasma-manager-unstable"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@@ -42,6 +42,17 @@
url = "github:Jovian-Experiments/Jovian-NixOS"; url = "github:Jovian-Experiments/Jovian-NixOS";
inputs.nixpkgs.follows = "nixpkgs-unstable"; inputs.nixpkgs.follows = "nixpkgs-unstable";
}; };
beads = {
url = "github:steveyegge/beads";
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 = "";
};
}; };
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, ... } @ inputs: let outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, ... } @ inputs: let
@@ -53,7 +64,7 @@
nixpkgs.overlays = [ nixpkgs.overlays = [
(final: prev: { (final: prev: {
unstable = import nixpkgs-unstable { unstable = import nixpkgs-unstable {
system = prev.system; system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true; config.allowUnfree = true;
}; };
custom = prev.callPackage ./packages {}; custom = prev.callPackage ./packages {};
@@ -65,6 +76,7 @@
home-manager.useUserPackages = true; home-manager.useUserPackages = true;
home-manager.sharedModules = [ home-manager.sharedModules = [
inputs.plasma-manager.homeModules.plasma-manager inputs.plasma-manager.homeModules.plasma-manager
inputs.nix-doom-emacs-unstraightened.homeModule
]; ];
home-manager.extraSpecialArgs = { home-manager.extraSpecialArgs = {
globalInputs = inputs; globalInputs = inputs;
@@ -81,7 +93,7 @@
nixpkgs.overlays = [ nixpkgs.overlays = [
(final: prev: { (final: prev: {
unstable = import nixpkgs-unstable { unstable = import nixpkgs-unstable {
system = prev.system; system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true; config.allowUnfree = true;
}; };
custom = prev.callPackage ./packages {}; custom = prev.callPackage ./packages {};
@@ -93,6 +105,7 @@
home-manager.useUserPackages = true; home-manager.useUserPackages = true;
home-manager.sharedModules = [ home-manager.sharedModules = [
inputs.plasma-manager-unstable.homeModules.plasma-manager inputs.plasma-manager-unstable.homeModules.plasma-manager
inputs.nix-doom-emacs-unstraightened.homeModule
]; ];
home-manager.extraSpecialArgs = { home-manager.extraSpecialArgs = {
globalInputs = inputs; globalInputs = inputs;
@@ -107,7 +120,7 @@
nixpkgs.overlays = [ nixpkgs.overlays = [
(final: prev: { (final: prev: {
unstable = import nixpkgs-unstable { unstable = import nixpkgs-unstable {
system = prev.system; system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true; config.allowUnfree = true;
overlays = [ overlays = [
# Override claude-code in unstable to use our custom GCS-based build # Override claude-code in unstable to use our custom GCS-based build
@@ -124,6 +137,9 @@
]; ];
home-manager.useGlobalPkgs = true; home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true; home-manager.useUserPackages = true;
home-manager.sharedModules = [
inputs.nix-doom-emacs-unstraightened.homeModule
];
home-manager.extraSpecialArgs = { home-manager.extraSpecialArgs = {
globalInputs = inputs; globalInputs = inputs;
}; };
@@ -152,9 +168,9 @@
system = "x86_64-linux"; system = "x86_64-linux";
modules = nixosModules ++ [ modules = nixosModules ++ [
./machines/boxy/configuration.nix ./machines/boxy/configuration.nix
inputs.home-manager.nixosModules.home-manager
{ {
home-manager.users.johno = import ./home/home-media-center.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; }; home-manager.extraSpecialArgs = { inherit system; };
} }
]; ];
@@ -209,6 +225,19 @@
]; ];
}; };
# 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 # Darwin/macOS configurations
darwinConfigurations."blkfv4yf49kt7" = inputs.nix-darwin.lib.darwinSystem rec { darwinConfigurations."blkfv4yf49kt7" = inputs.nix-darwin.lib.darwinSystem rec {
system = "aarch64-darwin"; system = "aarch64-darwin";
@@ -220,5 +249,50 @@
} }
]; ];
}; };
# 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

@@ -8,8 +8,12 @@
# Enable all desktop roles for full-featured experience # Enable all desktop roles for full-featured experience
home.roles = { home.roles = {
"3d-printing".enable = true;
base.enable = true; base.enable = true;
desktop.enable = true; desktop.enable = true;
emacs.enable = true;
email.enable = true;
i3_sway.enable = true;
office.enable = true; office.enable = true;
media.enable = true; media.enable = true;
development.enable = true; development.enable = true;
@@ -19,8 +23,7 @@
kubectl.enable = true; kubectl.enable = true;
tmux.enable = true; tmux.enable = true;
plasma-manager.enable = true; plasma-manager.enable = true;
emacs.enable = true; starship.enable = true;
i3_sway.enable = true;
}; };
targets.genericLinux.enable = true; targets.genericLinux.enable = true;

28
home/home-kodi.nix Normal file
View File

@@ -0,0 +1,28 @@
{ pkgs, globalInputs, system, ... }:
{
# Home Manager configuration for kodi user on boxy
# Focused on media center volume control via Home Assistant
home.username = "kodi";
home.homeDirectory = "/home/kodi";
home.stateVersion = "24.05";
# Enable minimal roles for kodi user
home.roles = {
base.enable = true;
plasma-manager-kodi.enable = true;
};
home.packages = with pkgs; [
kdePackages.kconfig
];
targets.genericLinux.enable = true;
home.sessionVariables = {};
home.sessionPath = [];
imports = [
./roles
];
}

View File

@@ -14,6 +14,7 @@
desktop.enable = true; desktop.enable = true;
development.enable = true; development.enable = true;
communication.enable = true; communication.enable = true;
email.enable = true;
kdeconnect.enable = true; kdeconnect.enable = true;
media.enable = true; media.enable = true;
sync.enable = true; sync.enable = true;
@@ -22,6 +23,7 @@
plasma-manager.enable = true; plasma-manager.enable = true;
emacs.enable = true; emacs.enable = true;
i3_sway.enable = true; i3_sway.enable = true;
starship.enable = true;
# Launcher wrappers for excluded/optional packages # Launcher wrappers for excluded/optional packages
launchers = { launchers = {

View File

@@ -14,8 +14,14 @@
desktop.enable = true; desktop.enable = true;
tmux.enable = true; tmux.enable = true;
plasma-manager.enable = true; plasma-manager.enable = true;
emacs.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;
};
i3_sway.enable = true; i3_sway.enable = true;
starship.enable = true;
# development.enable = false; # Not needed for live USB # development.enable = false; # Not needed for live USB
# communication.enable = false; # Not needed for live USB # communication.enable = false; # Not needed for live USB
# office.enable = false; # Not needed for live USB # office.enable = false; # Not needed for live USB

View File

@@ -20,6 +20,7 @@
plasma-manager.enable = true; plasma-manager.enable = true;
emacs.enable = true; emacs.enable = true;
i3_sway.enable = true; i3_sway.enable = true;
starship.enable = true;
# office.enable = false; # Not needed for media center # office.enable = false; # Not needed for media center
# sync.enable = false; # Shared machine, no personal file sync # sync.enable = false; # Shared machine, no personal file sync
}; };

26
home/home-server.nix Normal file
View File

@@ -0,0 +1,26 @@
{ pkgs, globalInputs, system, ... }:
{
# Home Manager configuration for servers (minimal with development tools)
home.username = "johno";
home.homeDirectory = "/home/johno";
home.stateVersion = "24.05";
# Minimal roles for server with development capability
home.roles = {
base.enable = true;
development.enable = true;
emacs.enable = true;
starship.enable = true;
tmux.enable = true;
};
targets.genericLinux.enable = true;
home.sessionVariables = {};
home.sessionPath = [];
imports = [
./roles
./roles/base-linux
];
}

View File

@@ -0,0 +1,22 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.home.roles."3d-printing";
in
{
options.home.roles."3d-printing" = {
enable = mkEnableOption "Enable 3D printing applications and tools";
};
config = mkIf cfg.enable {
home.packages = with pkgs; [
# 3D Slicing Software
orca-slicer # G-code generator for 3D printers (Bambu, Prusa, Voron, etc.)
# 3D Modeling Software
openscad-unstable # 3D parametric model compiler (nightly build)
];
};
}

View File

@@ -298,7 +298,7 @@ in
gaps = { gaps = {
outer = { outer = {
top = 0; top = 0;
bottom = 40; bottom = 38;
left = 0; left = 0;
right = 0; right = 0;
}; };
@@ -337,8 +337,8 @@ in
FOCUSED=0xff285577 FOCUSED=0xff285577
# Background colors matching i3blocks bar # Background colors matching i3blocks bar
BAR_BG=0xcc000000 # Semi-transparent black BAR_BG=0xff333333 # Dark gray
ITEM_BG=0xff333333 # Dark gray for inactive items ITEM_BG=0xff333333 # Dark gray matching bar
# Text colors # Text colors
TEXT=0xffffffff # White text TEXT=0xffffffff # White text
@@ -351,29 +351,32 @@ in
# Configure the bar appearance # Configure the bar appearance
${pkgs.sketchybar}/bin/sketchybar --bar \ ${pkgs.sketchybar}/bin/sketchybar --bar \
position=bottom \ position=bottom \
height=32 \ height=30 \
color=$BAR_BG \ color=$BAR_BG \
border_width=0 \ border_width=0 \
corner_radius=0 \ corner_radius=0 \
padding_left=10 \ padding_left=10 \
padding_right=10 padding_right=10 \
shadow=off \
topmost=on \
sticky=on
# Set default properties for all items # Set default properties for all items
# Using monospace font to match waybar's Fira Code styling # Using monospace font to match waybar's Fira Code styling
${pkgs.sketchybar}/bin/sketchybar --default \ ${pkgs.sketchybar}/bin/sketchybar --default \
updates=when_shown \ updates=when_shown \
icon.font="SF Mono:Regular:13.0" \ icon.font="Fira Code:Regular:13.0" \
icon.color=$TEXT \ icon.color=$TEXT \
icon.padding_left=4 \ icon.padding_left=4 \
icon.padding_right=4 \ icon.padding_right=4 \
label.font="SF Mono:Regular:13.0" \ label.font="Fira Code:Regular:13.0" \
label.color=$TEXT \ label.color=$TEXT \
label.padding_left=4 \ label.padding_left=4 \
label.padding_right=4 \ label.padding_right=4 \
padding_left=4 \ padding_left=4 \
padding_right=4 \ padding_right=4 \
background.corner_radius=5 \ background.corner_radius=0 \
background.height=24 background.height=30
# Register aerospace workspace change event # Register aerospace workspace change event
${pkgs.sketchybar}/bin/sketchybar --add event aerospace_workspace_change ${pkgs.sketchybar}/bin/sketchybar --add event aerospace_workspace_change
@@ -390,11 +393,12 @@ in
${pkgs.sketchybar}/bin/sketchybar --add item space.$sid left \ ${pkgs.sketchybar}/bin/sketchybar --add item space.$sid left \
--subscribe space.$sid aerospace_workspace_change \ --subscribe space.$sid aerospace_workspace_change \
--set space.$sid \ --set space.$sid \
drawing=on \
update_freq=2 \ update_freq=2 \
width=32 \ width=32 \
background.color=$ITEM_BG \ background.color=$ITEM_BG \
background.corner_radius=5 \ background.corner_radius=0 \
background.height=20 \ background.height=30 \
background.drawing=on \ background.drawing=on \
icon="$display" \ icon="$display" \
icon.padding_left=13 \ icon.padding_left=13 \
@@ -405,15 +409,6 @@ in
script="$PLUGIN_DIR/aerospace.sh $sid" script="$PLUGIN_DIR/aerospace.sh $sid"
done done
# Separator after workspaces
${pkgs.sketchybar}/bin/sketchybar --add item separator_left left \
--set separator_left \
icon="" \
label="" \
background.drawing=off \
padding_left=10 \
padding_right=10
# System monitoring modules (right side) # System monitoring modules (right side)
# Note: Items added to 'right' appear in reverse order (last added = leftmost) # Note: Items added to 'right' appear in reverse order (last added = leftmost)
# Adding in reverse to get: disk | cpu | memory | battery | volume | calendar # Adding in reverse to get: disk | cpu | memory | battery | volume | calendar
@@ -425,14 +420,6 @@ in
background.drawing=on \ background.drawing=on \
script="$PLUGIN_DIR/calendar.sh" script="$PLUGIN_DIR/calendar.sh"
${pkgs.sketchybar}/bin/sketchybar --add item separator_media right \
--set separator_media \
icon="|" \
label="" \
background.drawing=off \
padding_left=5 \
padding_right=5
${pkgs.sketchybar}/bin/sketchybar --add item volume right \ ${pkgs.sketchybar}/bin/sketchybar --add item volume right \
--set volume \ --set volume \
background.color=$ITEM_BG \ background.color=$ITEM_BG \
@@ -448,14 +435,6 @@ in
script="$PLUGIN_DIR/battery.sh" \ script="$PLUGIN_DIR/battery.sh" \
--subscribe battery system_woke power_source_change --subscribe battery system_woke power_source_change
${pkgs.sketchybar}/bin/sketchybar --add item separator_sys right \
--set separator_sys \
icon="|" \
label="" \
background.drawing=off \
padding_left=5 \
padding_right=5
${pkgs.sketchybar}/bin/sketchybar --add item memory right \ ${pkgs.sketchybar}/bin/sketchybar --add item memory right \
--set memory \ --set memory \
update_freq=5 \ update_freq=5 \
@@ -528,8 +507,15 @@ in
# Get list of empty workspaces # Get list of empty workspaces
EMPTY_WORKSPACES=$(${pkgs.aerospace}/bin/aerospace list-workspaces --monitor all --empty) EMPTY_WORKSPACES=$(${pkgs.aerospace}/bin/aerospace list-workspaces --monitor all --empty)
# Clean up the workspace number parameter # Get workspace number - from $1 if provided (event-triggered), otherwise extract from $NAME (routine update)
# $NAME is always available (e.g., "space.1", "space.2", etc.)
# $1 is only available when called via event trigger with positional argument
if [ -n "$1" ]; then
WORKSPACE_NUM=$(echo "$1" | tr -d ' \n\r') WORKSPACE_NUM=$(echo "$1" | tr -d ' \n\r')
else
# Extract number from item name: "space.1" -> "1", "space.10" -> "10"
WORKSPACE_NUM=$(echo "$NAME" | sed 's/space\.//')
fi
# Check if workspace has windows (is NOT empty) # Check if workspace has windows (is NOT empty)
IS_EMPTY=false IS_EMPTY=false
@@ -543,37 +529,55 @@ in
IS_FOCUSED=true IS_FOCUSED=true
fi fi
# Determine display value (workspace 10 displays as "0")
if [ "$WORKSPACE_NUM" = "10" ]; then
DISPLAY="0"
else
DISPLAY="$WORKSPACE_NUM"
fi
# Determine visibility and styling # Determine visibility and styling
# Always show focused workspace (even if empty) with fixed width # Always show focused workspace (even if empty) with fixed width
# Hide non-focused empty workspaces by setting width to 0 (collapsed) # Hide non-focused empty workspaces by setting width to 0 (collapsed)
# Show non-focused non-empty workspaces with fixed width and inactive styling # Show non-focused non-empty workspaces with fixed width and inactive styling
if [ "$IS_FOCUSED" = "true" ]; then if [ "$IS_FOCUSED" = "true" ]; then
# Focused workspace - always show with focused styling # Focused workspace - always show with focused styling and bold font
${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \ ${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \
drawing=on \ drawing=on \
icon="$DISPLAY" \
width=32 \ width=32 \
icon.padding_left=13 \ icon.padding_left=13 \
icon.padding_right=11 \ icon.padding_right=11 \
icon.align=center \ icon.align=center \
background.color=$FOCUSED_COLOR \ background.color=$FOCUSED_COLOR \
background.drawing=on \ background.drawing=on \
icon.color=$TEXT icon.color=$TEXT \
icon.font="Fira Code:Bold:13.0"
elif [ "$IS_EMPTY" = "true" ]; then elif [ "$IS_EMPTY" = "true" ]; then
# Empty workspace (not focused) - hide by turning off drawing # Empty workspace (not focused) - hide by collapsing width and clearing content
${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \ # Using width=0 with drawing=on so updates=when_shown continues to run the script
drawing=off
else
# Non-empty workspace (not focused) - show with inactive styling
${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \ ${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \
drawing=on \ drawing=on \
icon="" \
label="" \
width=0 \
icon.padding_left=0 \
icon.padding_right=0 \
background.drawing=off
else
# Non-empty workspace (not focused) - show with inactive styling and white text
${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \
drawing=on \
icon="$DISPLAY" \
width=32 \ width=32 \
icon.padding_left=13 \ icon.padding_left=13 \
icon.padding_right=11 \ icon.padding_right=11 \
icon.align=center \ icon.align=center \
background.color=$ITEM_BG \ background.color=$ITEM_BG \
background.drawing=on \ background.drawing=on \
icon.color=$GRAY icon.color=$TEXT \
icon.font="Fira Code:Regular:13.0"
fi fi
''; '';
}; };
@@ -595,21 +599,28 @@ in
}; };
# SketchyBar memory monitoring plugin # SketchyBar memory monitoring plugin
# Shows actual memory pressure (excludes file cache/inactive pages)
home.file.".config/sketchybar/plugins/memory.sh" = mkIf cfg.sketchybar.enable { home.file.".config/sketchybar/plugins/memory.sh" = mkIf cfg.sketchybar.enable {
executable = true; executable = true;
text = '' text = ''
#!/bin/bash #!/bin/bash
MEMORY_STATS=$(vm_stat) # Use awk for all arithmetic to avoid bash integer overflow on large RAM systems
PAGES_FREE=$(echo "$MEMORY_STATS" | grep "Pages free" | awk '{print $3}' | tr -d '.') # Memory pressure = Anonymous (app memory) + Wired + Compressor RAM
PAGES_ACTIVE=$(echo "$MEMORY_STATS" | grep "Pages active" | awk '{print $3}' | tr -d '.') # - Anonymous pages: app-allocated memory (heap, stack) - matches Activity Monitor's "App Memory"
PAGES_INACTIVE=$(echo "$MEMORY_STATS" | grep "Pages inactive" | awk '{print $3}' | tr -d '.') # - Wired: kernel/system memory that can't be paged out
PAGES_WIRED=$(echo "$MEMORY_STATS" | grep "Pages wired down" | awk '{print $4}' | tr -d '.') # - Pages occupied by compressor: actual RAM used by compressor (NOT "stored in compressor")
PAGES_COMPRESSED=$(echo "$MEMORY_STATS" | grep "Pages stored in compressor" | awk '{print $5}' | tr -d '.') TOTAL_RAM=$(sysctl -n hw.memsize)
MEMORY_PERCENT=$(vm_stat | awk -v total_ram="$TOTAL_RAM" '
TOTAL_PAGES=$((PAGES_FREE + PAGES_ACTIVE + PAGES_INACTIVE + PAGES_WIRED + PAGES_COMPRESSED)) /page size of/ { page_size = $8 }
USED_PAGES=$((PAGES_ACTIVE + PAGES_INACTIVE + PAGES_WIRED + PAGES_COMPRESSED)) /Anonymous pages/ { anon = $3 + 0 }
MEMORY_PERCENT=$((USED_PAGES * 100 / TOTAL_PAGES)) /Pages wired/ { wired = $4 + 0 }
/Pages occupied by compressor/ { compressor = $5 + 0 }
END {
used = (anon + wired + compressor) * page_size
printf "%.0f", used / total_ram * 100
}
')
${pkgs.sketchybar}/bin/sketchybar --set $NAME label="$MEMORY_PERCENT%" ${pkgs.sketchybar}/bin/sketchybar --set $NAME label="$MEMORY_PERCENT%"
''; '';

View File

@@ -4,4 +4,8 @@
imports = [ imports = [
../aerospace ../aerospace
]; ];
# Override to use -d instead of --delete-older-than on Darwin due to launchd bug
# https://github.com/nix-community/home-manager/issues/7211
nix.gc.options = "-d";
} }

View File

@@ -24,6 +24,17 @@ in
tree tree
]; ];
# Automatic garbage collection for user profile (home-manager generations).
# This complements system-level gc which only cleans system generations.
# - Linux: Uses --delete-older-than to keep 10-day rollback window
# - Darwin: Overridden to use -d in base-darwin role to avoid launchd bug
# (https://github.com/nix-community/home-manager/issues/7211)
nix.gc = {
automatic = true;
randomizedDelaySec = mkIf pkgs.stdenv.isLinux "14m";
options = lib.mkDefault "--delete-older-than 10d";
};
# Essential programs everyone needs # Essential programs everyone needs
programs.bash = { programs.bash = {
enable = true; enable = true;
@@ -41,9 +52,9 @@ in
programs.git = { programs.git = {
enable = true; enable = true;
userName = "John Ogle"; settings = {
userEmail = "john@ogle.fyi"; user.name = "John Ogle";
extraConfig = { user.email = "john@ogle.fyi";
safe.directory = "/etc/nixos"; safe.directory = "/etc/nixos";
}; };
}; };
@@ -58,8 +69,11 @@ in
programs.ssh = { programs.ssh = {
enable = true; enable = true;
addKeysToAgent = "yes"; enableDefaultConfig = false;
matchBlocks = { matchBlocks = {
"*" = {
addKeysToAgent = "yes";
};
"nucdeb1" = { "nucdeb1" = {
hostname = "nucdeb1.oglehome"; hostname = "nucdeb1.oglehome";
user = "root"; user = "root";

View File

@@ -3,18 +3,22 @@
# Platform-specific roles are imported via base-linux or base-darwin # Platform-specific roles are imported via base-linux or base-darwin
# in each home configuration file # in each home configuration file
imports = [ imports = [
./3d-printing
./base ./base
./communication ./communication
./desktop ./desktop
./development ./development
./email
./gaming ./gaming
./kdeconnect ./kdeconnect
./kubectl ./kubectl
./launchers ./launchers
./media ./media
./office ./office
./plasma-manager-kodi
./sync ./sync
./tmux ./tmux
./emacs ./emacs
./starship
]; ];
} }

View File

@@ -81,6 +81,45 @@ in
enable = true; enable = true;
}; };
# 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" ];
};
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" ];
};
};
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 # KDE environment variables for proper integration
home.sessionVariables = { home.sessionVariables = {
QT_QPA_PLATFORMTHEME = "kde"; QT_QPA_PLATFORMTHEME = "kde";

View File

@@ -5,7 +5,7 @@ with lib;
let let
cfg = config.home.roles.development; cfg = config.home.roles.development;
# Fetch the claude-plugins repository # Fetch the claude-plugins repository (for humanlayer commands/agents)
# Update the rev to get newer versions of the commands # Update the rev to get newer versions of the commands
claudePluginsRepo = builtins.fetchGit { claudePluginsRepo = builtins.fetchGit {
url = "https://github.com/jeffh/claude-plugins.git"; url = "https://github.com/jeffh/claude-plugins.git";
@@ -14,6 +14,7 @@ let
rev = "5e3e4d937162185b6d78c62022cbfd1c8ad42c4c"; rev = "5e3e4d937162185b6d78c62022cbfd1c8ad42c4c";
ref = "main"; ref = "main";
}; };
in in
{ {
options.home.roles.development = { options.home.roles.development = {
@@ -36,12 +37,14 @@ in
config = mkIf cfg.enable { config = mkIf cfg.enable {
home.packages = [ home.packages = [
globalInputs.beads.packages.${system}.default
pkgs.unstable.claude-code pkgs.unstable.claude-code
pkgs.unstable.claude-code-router pkgs.unstable.claude-code-router
pkgs.unstable.codex pkgs.unstable.codex
# Custom packages # Custom packages
pkgs.custom.tea-rbw pkgs.custom.tea-rbw
pkgs.custom.perles
]; ];
# Install Claude Code humanlayer command and agent plugins # Install Claude Code humanlayer command and agent plugins
@@ -82,11 +85,35 @@ in
fi fi
done done
# 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${ $DRY_RUN_CMD echo "Claude Code humanlayer commands and agents installed successfully${
if cfg.allowArbitraryClaudeCodeModelSelection if cfg.allowArbitraryClaudeCodeModelSelection
then " (model specifications preserved)" then " (model specifications preserved)"
else " (model selection removed)" else " (model selection removed)"
}" } + local skills"
'';
# 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 # 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,88 @@
---
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 '.[0].notes'
```
Note: `bd show --json` returns an array, so use `.[0]` to access the first element.
Extract the PR URL (look for lines starting with "PR:" or containing pull request URLs).
Extract the PR number: `echo "$NOTES" | grep -oP '/pulls/\K\d+'`
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 list --state=closed
```
Look for the PR number in the INDEX column with STATE "merged".
Note: `tea pr view [PR_NUMBER]` lists all PRs, not a specific one. Use `tea pr list --state=closed` and look for your PR number in the results.
### 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

@@ -8,8 +8,8 @@ let
doomEmacs = pkgs.fetchFromGitHub { doomEmacs = pkgs.fetchFromGitHub {
owner = "doomemacs"; owner = "doomemacs";
repo = "doomemacs"; repo = "doomemacs";
rev = "8f55404781edacf66fa330205533b002de3fb5ee"; rev = "38d94da67dc84897a4318714dcc48494c016d8c4";
sha256 = "sha256-vHwgENjip2+AFzs4oZfnKEAJKwf5Zid7fakImvxxQUw="; sha256 = "sha256-Uc6qONH3jjUVDgW+pPBCGC7mh88ZY05u1y37fQrsxq0=";
}; };
# Shared emacs packages # Shared emacs packages
@@ -23,13 +23,30 @@ let
if pkgs.stdenv.isDarwin if pkgs.stdenv.isDarwin
then pkgs.emacs-macport.pkgs.withPackages emacsPackages then pkgs.emacs-macport.pkgs.withPackages emacsPackages
else pkgs.emacs.pkgs.withPackages emacsPackages; else pkgs.emacs.pkgs.withPackages emacsPackages;
# Path to doom config directory (relative to this file)
doomConfigDir = ./doom;
in in
{ {
options.home.roles.emacs = { options.home.roles.emacs = {
enable = mkEnableOption "Doom Emacs with vterm and tree-sitter support"; 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 { config = mkIf cfg.enable (mkMerge [
# Common configuration for both modes
{
home.packages = [ home.packages = [
pkgs.emacs-all-the-icons-fonts pkgs.emacs-all-the-icons-fonts
pkgs.fira-code pkgs.fira-code
@@ -46,13 +63,16 @@ in
pkgs.python3 pkgs.python3
]; ];
fonts.fontconfig.enable = true;
}
# Standard Doom Emacs mode (requires doom sync at runtime)
(mkIf (!cfg.prebuiltDoom) {
programs.emacs = { programs.emacs = {
enable = true; enable = true;
package = defaultEmacsPackage; package = defaultEmacsPackage;
}; };
fonts.fontconfig.enable = true;
# Mount emacs and tree-sitter grammars from nix store # Mount emacs and tree-sitter grammars from nix store
home.file = { home.file = {
"${config.xdg.configHome}/emacs".source = doomEmacs; "${config.xdg.configHome}/emacs".source = doomEmacs;
@@ -73,5 +93,20 @@ in
rm -rf "${config.xdg.configHome}/doom" rm -rf "${config.xdg.configHome}/doom"
ln -sf "${config.home.homeDirectory}/nixos-configs/home/roles/emacs/doom" "${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
];
}; };
})
]);
} }

View File

@@ -28,7 +28,7 @@
;; up, `M-x eval-region' to execute elisp code, and 'M-x doom/reload-font' to ;; up, `M-x eval-region' to execute elisp code, and 'M-x doom/reload-font' to
;; refresh your font settings. If Emacs still can't find your font, it likely ;; refresh your font settings. If Emacs still can't find your font, it likely
;; wasn't installed correctly. Font issues are rarely Doom issues! ;; wasn't installed correctly. Font issues are rarely Doom issues!
(setq doom-font (font-spec :family "Fira Code")) (setq doom-font (font-spec :family "Fira Code" :size 16))
;; Auto-install nerd-icons fonts if they're missing ;; Auto-install nerd-icons fonts if they're missing
(defun my/ensure-nerd-icons-fonts () (defun my/ensure-nerd-icons-fonts ()
@@ -71,7 +71,10 @@
org-journal-file-format "%Y-%m-%d.org" org-journal-file-format "%Y-%m-%d.org"
org-capture-templates org-capture-templates
'(("t" "Todo" entry (file+headline "~/org/todo.org" "Inbox") '(("t" "Todo" entry (file+headline "~/org/todo.org" "Inbox")
"* TODO %? \n %i \n%a" :prepend t)))) "* TODO %? \n %i \n%a" :prepend t)))
;; Make blocked tasks more visible in agenda (they have subtasks to do!)
(custom-set-faces!
'(org-agenda-dimmed-todo-face :foreground "#bb9af7" :weight normal)))
(map! :after org-agenda (map! :after org-agenda
:map org-agenda-mode-map :map org-agenda-mode-map
@@ -145,12 +148,24 @@
:args (list '(:name "dirpath" :type "string" :description "Directory path to list")))) :args (list '(:name "dirpath" :type "string" :description "Directory path to list"))))
(use-package! claude-code-ide (use-package! claude-code-ide
:defer t :commands (claude-code-ide-menu claude-code-ide-open-here)
:config :init
(claude-code-ide-emacs-tools-setup)
(map! :leader (map! :leader
(:prefix ("o" . "open") (:prefix ("o" . "open")
:desc "Claude Code IDE" "c" #'claude-code-ide-menu))) :desc "Claude Code IDE" "c" #'claude-code-ide-menu))
:config
(claude-code-ide-emacs-tools-setup)
(setq claude-code-ide-cli-path "claude"
claude-code-ide-cli-extra-flags "--dangerously-skip-permissions"
claude-code-ide-focus-claude-after-ediff t
claude-code-ide-focus-on-open t
claude-code-ide-show-claude-window-in-ediff t
claude-code-ide-switch-tab-on-ediff t
claude-code-ide-use-ide-diff t
claude-code-ide-use-side-window t
claude-code-ide-window-height 20
claude-code-ide-window-side 'right
claude-code-ide-window-width 90))
(after! gptel (after! gptel
(require 'gptel-tool-library) (require 'gptel-tool-library)
@@ -159,6 +174,49 @@
(dolist (module '("bbdb" "buffer" "elisp" "emacs" "gnus" "os" "search-and-replace" "url")) (dolist (module '("bbdb" "buffer" "elisp" "emacs" "gnus" "os" "search-and-replace" "url"))
(gptel-tool-library-load-module module))) (gptel-tool-library-load-module module)))
;; mu4e email configuration
;; Add NixOS mu4e to load-path (installed via mu.mu4e package)
(when-let ((mu-path (executable-find "mu")))
(add-to-list 'load-path
(expand-file-name "../share/emacs/site-lisp/mu4e"
(file-name-directory mu-path))))
(after! mu4e
;; User identity
(setq user-mail-address "john@ogle.fyi"
user-full-name "John Ogle")
;; Maildir location (no account prefix - single account)
(setq mu4e-maildir "~/Mail"
mu4e-attachment-dir "~/Downloads")
;; Folder config (matches ~/Mail/INBOX, ~/Mail/Sent, etc.)
(setq mu4e-sent-folder "/Sent"
mu4e-drafts-folder "/Drafts"
mu4e-trash-folder "/Trash"
mu4e-refile-folder "/Archive")
;; Shortcuts for common folders
(setq mu4e-maildir-shortcuts
'((:maildir "/INBOX" :key ?i)
(:maildir "/Archive" :key ?a)
(:maildir "/Sent" :key ?s)
(:maildir "/Trash" :key ?t)))
;; Behavior settings
(setq mu4e-get-mail-command "mbsync -a"
mu4e-update-interval 300 ; 5 minutes (matches systemd timer)
mu4e-change-filenames-when-moving t ; required for mbsync
mu4e-headers-date-format "%Y-%m-%d"
mu4e-headers-time-format "%H:%M")
;; Sending mail via msmtp
(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 ;; 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. ;; `after!' block, otherwise Doom's defaults may override your settings. E.g.
;; ;;

View File

@@ -176,7 +176,7 @@
;;zig ; C, but simpler ;;zig ; C, but simpler
:email :email
;;(mu4e +org +gmail) (mu4e +org)
;;notmuch ;;notmuch
;;(wanderlust +gmail) ;;(wanderlust +gmail)

View File

@@ -51,11 +51,21 @@
;; (package! org-caldav) ;; (package! org-caldav)
;; Note: Packages with custom recipes must be pinned for nix-doom-emacs-unstraightened
;; to build deterministically. Update pins when upgrading packages.
(package! gptel :recipe (:nonrecursive t)) (package! gptel :recipe (:nonrecursive t))
(package! claude-code-ide (package! claude-code-ide
:recipe (:host github :repo "manzaltu/claude-code-ide.el")) :recipe (:host github :repo "manzaltu/claude-code-ide.el")
:pin "760240d7f03ff16f90ede9d4f4243cd94f3fed73")
(package! gptel-tool-library (package! gptel-tool-library
:recipe (:host github :repo "aard-fi/gptel-tool-library" :recipe (:host github :repo "aard-fi/gptel-tool-library"
:files ("*.el"))) :files ("*.el"))
:pin "baffc3b0d74a2b7cbda0d5cd6dd7726d6ccaca83")
(package! beads
:recipe (:type git :repo "https://codeberg.org/ctietze/beads.el.git"
:files ("lisp/*.el"))
:pin "f40a6461d3c0fa0969311bbb6a1e30d1bba86c88")

View File

@@ -0,0 +1,123 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.home.roles.email;
in
{
options.home.roles.email = {
enable = mkEnableOption "Enable email with mu4e, mbsync, and msmtp";
};
config = mkIf cfg.enable {
home.packages = with pkgs; [
isync # provides mbsync for IMAP sync
msmtp # for SMTP sending
mu # email indexer for mu4e
mu.mu4e # mu4e elisp files for Emacs
openssl # for certificate management
];
# Ensure Mail directory exists
home.file."Mail/.keep".text = "";
# mbsync configuration
home.file.".mbsyncrc".text = ''
# IMAP Account Configuration
IMAPAccount proton
Host proton.johnogle.info
Port 143
User john@ogle.fyi
PassCmd "${pkgs.rbw}/bin/rbw get proton.johnogle.info"
TLSType STARTTLS
AuthMechs PLAIN
# Remote Storage
IMAPStore proton-remote
Account proton
# Local Storage
MaildirStore proton-local
Path ~/Mail/
Inbox ~/Mail/INBOX
SubFolders Verbatim
# Channel Configuration - Main (excludes Sent)
Channel proton-main
Far :proton-remote:
Near :proton-local:
Patterns * !Sent
Create Both
Expunge Both
SyncState *
# Channel Configuration - Sent (pull only)
Channel proton-sent
Far :proton-remote:Sent
Near :proton-local:Sent
Create Near
Expunge Near
Sync Pull
SyncState *
# Group both channels
Group proton
Channel proton-main
Channel proton-sent
'';
# msmtp configuration
home.file.".msmtprc".text = ''
# Default settings
defaults
auth plain
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ${config.home.homeDirectory}/.msmtp.log
# Proton mail account
account proton
host proton.johnogle.info
port 25
from john@ogle.fyi
user john@ogle.fyi
passwordeval rbw get proton.johnogle.info
# Set default account
account default : proton
'';
# 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";
};
};
# 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

@@ -4,6 +4,8 @@ with lib;
let let
cfg = config.home.roles.i3_sway; cfg = config.home.roles.i3_sway;
wallpaperConfig = import ../../wallpapers;
currentWallpaper = builtins.elemAt wallpaperConfig.wallpapers wallpaperConfig.currentIndex;
shared_config = recursiveUpdate rec { shared_config = recursiveUpdate rec {
modifier = "Mod4"; modifier = "Mod4";
@@ -12,6 +14,7 @@ let
keybindings = { keybindings = {
"${shared_config.modifier}+Return" = "exec ${terminal}"; "${shared_config.modifier}+Return" = "exec ${terminal}";
"${shared_config.modifier}+Shift+Return" = "exec ${cfg.browser}";
"${shared_config.modifier}+Shift+q" = "kill"; "${shared_config.modifier}+Shift+q" = "kill";
"${shared_config.modifier}+a" = "focus parent"; "${shared_config.modifier}+a" = "focus parent";
@@ -96,6 +99,12 @@ in {
options.home.roles.i3_sway = { options.home.roles.i3_sway = {
enable = mkEnableOption "i3 and Sway tiling window managers with waybar and rofi"; enable = mkEnableOption "i3 and Sway tiling window managers with waybar and rofi";
browser = mkOption {
type = types.str;
default = "firefox --new-window";
description = "Browser to use for new window keybinding";
};
extraSharedConfig = mkOption { extraSharedConfig = mkOption {
type = types.attrs; type = types.attrs;
default = {}; default = {};
@@ -281,7 +290,7 @@ in {
} }
# Set wallpaper with feh # Set wallpaper with feh
{ {
command = "feh --bg-scale ${../../wallpapers/metroid-samus-returns-kz-3440x1440.jpg}"; command = "feh ${currentWallpaper.feh} ${currentWallpaper.file}";
always = false; always = false;
notification = false; notification = false;
} }
@@ -314,7 +323,7 @@ in {
}; };
output = { output = {
"*" = { "*" = {
bg = "${../../wallpapers/metroid-samus-returns-kz-3440x1440.jpg} fill"; bg = "${currentWallpaper.file} ${currentWallpaper.sway}";
}; };
}; };
startup = [ startup = [
@@ -343,7 +352,7 @@ in {
modules-left = [ "sway/workspaces" "sway/mode" ]; modules-left = [ "sway/workspaces" "sway/mode" ];
modules-center = [ ]; modules-center = [ ];
modules-right = [ "disk" "cpu" "memory" "pulseaudio" "backlight" "network" "battery" "tray" "clock" ]; modules-right = [ "disk" "cpu" "memory" "pulseaudio" "custom/backlight-ddc" "backlight" "network" "battery" "tray" "clock" ];
"sway/workspaces" = { "sway/workspaces" = {
disable-scroll = true; disable-scroll = true;
@@ -400,6 +409,23 @@ in {
tooltip = false; tooltip = false;
}; };
"custom/backlight-ddc" = {
exec = pkgs.writeShellScript "waybar-backlight-ddc" ''
if command -v ddcutil &>/dev/null; then
# Display current brightness
brightness=$(ddcutil getvcp 10 --brief 2>/dev/null | awk '{print $4}')
if [ -n "$brightness" ]; then
echo " $brightness%"
fi
fi
'';
interval = 5;
format = "{}";
on-scroll-up = "ddcutil setvcp 10 + 5 2>/dev/null &";
on-scroll-down = "ddcutil setvcp 10 - 5 2>/dev/null &";
tooltip = false;
};
"network" = { "network" = {
format-wifi = "📶 {essid} ({signalStrength}%)"; format-wifi = "📶 {essid} ({signalStrength}%)";
format-ethernet = "🔌 {ipaddr}"; format-ethernet = "🔌 {ipaddr}";

View File

@@ -0,0 +1,199 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.home.roles.plasma-manager-kodi;
# Define the volume control scripts as derivations
volumeUpScript = pkgs.writeShellScript "avr-volume-up" ''
#!/usr/bin/env bash
# Configuration
HA_URL="https://home-assistant.johnogle.info"
ENTITY_ID="media_player.denon_avr_s970h_2"
MAX_RETRIES=3
# Read token from KDE Wallet and strip whitespace
TOKEN=$(${pkgs.kdePackages.kwallet}/bin/kwallet-query -r ha_avr_token kdewallet -f Passwords 2>/dev/null | tr -d '[:space:]')
if [ -z "$TOKEN" ]; then
${pkgs.libnotify}/bin/notify-send -u critical "Volume Control Error" "Failed to retrieve Home Assistant token from KDE Wallet"
exit 1
fi
# Send volume up command with retry logic
for i in $(seq 1 $MAX_RETRIES); do
RESPONSE=$(${pkgs.curl}/bin/curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"entity_id\": \"$ENTITY_ID\"}" \
"$HA_URL/api/services/media_player/volume_up" 2>&1)
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" = "200" ]; then
exit 0
fi
# Wait before retry (except on last attempt)
if [ $i -lt $MAX_RETRIES ]; then
sleep 0.5
fi
done
# All retries failed
${pkgs.libnotify}/bin/notify-send -u critical "Volume Control Error" "Failed to increase volume after $MAX_RETRIES attempts"
exit 1
'';
volumeDownScript = pkgs.writeShellScript "avr-volume-down" ''
#!/usr/bin/env bash
# Configuration
HA_URL="https://home-assistant.johnogle.info"
ENTITY_ID="media_player.denon_avr_s970h_2"
MAX_RETRIES=3
# Read token from KDE Wallet and strip whitespace
TOKEN=$(${pkgs.kdePackages.kwallet}/bin/kwallet-query -r ha_avr_token kdewallet -f Passwords 2>/dev/null | tr -d '[:space:]')
if [ -z "$TOKEN" ]; then
${pkgs.libnotify}/bin/notify-send -u critical "Volume Control Error" "Failed to retrieve Home Assistant token from KDE Wallet"
exit 1
fi
# Send volume down command with retry logic
for i in $(seq 1 $MAX_RETRIES); do
RESPONSE=$(${pkgs.curl}/bin/curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"entity_id\": \"$ENTITY_ID\"}" \
"$HA_URL/api/services/media_player/volume_down" 2>&1)
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" = "200" ]; then
exit 0
fi
# Wait before retry (except on last attempt)
if [ $i -lt $MAX_RETRIES ]; then
sleep 0.5
fi
done
# All retries failed
${pkgs.libnotify}/bin/notify-send -u critical "Volume Control Error" "Failed to decrease volume after $MAX_RETRIES attempts"
exit 1
'';
volumeMuteScript = pkgs.writeShellScript "avr-volume-mute" ''
#!/usr/bin/env bash
# Configuration
HA_URL="https://home-assistant.johnogle.info"
ENTITY_ID="media_player.denon_avr_s970h_2"
MAX_RETRIES=3
# Read token from KDE Wallet and strip whitespace
TOKEN=$(${pkgs.kdePackages.kwallet}/bin/kwallet-query -r ha_avr_token kdewallet -f Passwords 2>/dev/null | tr -d '[:space:]')
if [ -z "$TOKEN" ]; then
${pkgs.libnotify}/bin/notify-send -u critical "Volume Control Error" "Failed to retrieve Home Assistant token from KDE Wallet"
exit 1
fi
# Get current mute state
STATE_RESPONSE=$(${pkgs.curl}/bin/curl -s -H "Authorization: Bearer $TOKEN" \
"$HA_URL/api/states/$ENTITY_ID")
CURRENT_MUTE=$(echo "$STATE_RESPONSE" | ${pkgs.jq}/bin/jq -r '.attributes.is_volume_muted // false')
# Toggle: if currently muted (true), unmute (false), and vice versa
if [ "$CURRENT_MUTE" = "true" ]; then
NEW_MUTE="false"
NOTIFY_MSG="Unmuted"
else
NEW_MUTE="true"
NOTIFY_MSG="Muted"
fi
# Send mute toggle command with retry logic
for i in $(seq 1 $MAX_RETRIES); do
RESPONSE=$(${pkgs.curl}/bin/curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"entity_id\": \"$ENTITY_ID\", \"is_volume_muted\": $NEW_MUTE}" \
"$HA_URL/api/services/media_player/volume_mute" 2>&1)
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" = "200" ]; then
exit 0
fi
# Wait before retry (except on last attempt)
if [ $i -lt $MAX_RETRIES ]; then
sleep 0.5
fi
done
# All retries failed
${pkgs.libnotify}/bin/notify-send -u critical "Volume Control Error" "Failed to toggle mute after $MAX_RETRIES attempts"
exit 1
'';
in
{
options.home.roles.plasma-manager-kodi = {
enable = mkEnableOption "KDE Plasma volume control for kodi user via Home Assistant";
};
config = mkIf cfg.enable {
programs.plasma = {
enable = true;
overrideConfig = true;
# Disable default kmix volume shortcuts to prevent conflicts
shortcuts.kmix = {
"increase_volume" = "none";
"decrease_volume" = "none";
"mute" = "none";
};
# Define custom volume control commands with key bindings
hotkeys.commands = {
"volume-up-avr" = {
name = "Volume Up AVR";
key = "Volume Up";
command = toString volumeUpScript;
};
"volume-down-avr" = {
name = "Volume Down AVR";
key = "Volume Down";
command = toString volumeDownScript;
};
"volume-mute-avr" = {
name = "Mute Toggle AVR";
key = "Volume Mute";
command = toString volumeMuteScript;
};
};
# KDE Settings customization
configFile = {
# Session restore settings
"ksmserverrc"."General"."loginMode" = "emptySession";
# Screen locking settings
"kscreenlockerrc"."Daemon"."Autolock" = false;
"kscreenlockerrc"."Daemon"."LockOnResume" = false;
# Theme settings
"kdeglobals"."KDE"."LookAndFeelPackage" = "org.kde.breezedark.desktop";
};
};
};
}

View File

@@ -4,6 +4,8 @@ with lib;
let let
cfg = config.home.roles.plasma-manager; cfg = config.home.roles.plasma-manager;
wallpaperConfig = import ../../wallpapers;
currentWallpaper = builtins.elemAt wallpaperConfig.wallpapers wallpaperConfig.currentIndex;
in in
{ {
options.home.roles.plasma-manager = { options.home.roles.plasma-manager = {
@@ -181,7 +183,7 @@ in
plasma-localerc.Formats.LANG = "en_US.UTF-8"; plasma-localerc.Formats.LANG = "en_US.UTF-8";
# Set wallpaper for all desktops # Set wallpaper for all desktops
plasmarc.Wallpapers.usersWallpapers = "${../../wallpapers/metroid-samus-returns-kz-3440x1440.jpg}"; plasmarc.Wallpapers.usersWallpapers = "${currentWallpaper.file}";
}; };
}; };
}; };

View File

@@ -0,0 +1,72 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.home.roles.starship;
in
{
options.home.roles.starship = {
enable = mkEnableOption "starship cross-shell prompt";
};
config = mkIf cfg.enable {
programs.starship = {
enable = true;
enableBashIntegration = true;
enableZshIntegration = true;
settings = {
add_newline = true;
character = {
success_symbol = "[>](bold green)";
error_symbol = "[x](bold red)";
vimcmd_symbol = "[<](bold green)";
};
directory = {
truncation_length = 4;
truncate_to_repo = true;
};
git_branch = {
symbol = "";
format = "[$symbol$branch(:$remote_branch)]($style) ";
};
git_status = {
format = "([$all_status$ahead_behind]($style) )";
};
nix_shell = {
symbol = "";
format = "[$symbol$state( \\($name\\))]($style) ";
};
cmd_duration = {
min_time = 2000;
format = "[$duration]($style) ";
};
# Disable modules that are noisy or rarely needed
package.disabled = true;
nodejs.disabled = true;
python.disabled = true;
ruby.disabled = true;
java.disabled = true;
golang.disabled = true;
rust.disabled = true;
php.disabled = true;
lua.disabled = true;
perl.disabled = true;
terraform.disabled = true;
kubernetes.disabled = true;
docker_context.disabled = true;
aws.disabled = true;
gcloud.disabled = true;
azure.disabled = true;
};
};
};
}

View File

@@ -0,0 +1,45 @@
# Wallpaper rotation system
# The currentIndex is incremented by `nix run .#rotate-wallpaper`
# and gets committed as part of `nix run .#upgrade`
{
currentIndex = 1; # Index into wallpapers list
wallpapers = [
{
name = "metroid-samus-returns";
file = ./metroid-samus-returns-kz-3440x1440.jpg;
sway = "fill";
feh = "--bg-fill";
}
{
name = "metroid3_map";
file = ./metroid3_map.gif;
sway = "fit";
feh = "--bg-max";
}
{
name = "super-metroid-gunship-cavern";
file = ./super-metroid-gunship-cavern.jpg;
sway = "fit";
feh = "--bg-max";
}
{
name = "super-metroid-samus-statue";
file = ./super-metroid-samus-statue.png;
sway = "fit";
feh = "--bg-max";
}
{
name = "metroid-samus-action-4k";
file = ./metroid-samus-action-4k.jpg;
sway = "fit";
feh = "--bg-max";
}
{
name = "metroid-creature-minimalist";
file = ./metroid-creature-minimalist.jpg;
sway = "fit";
feh = "--bg-max";
}
];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

View File

@@ -26,10 +26,20 @@ with lib;
enable = true; enable = true;
autologin = true; autologin = true;
wayland = true; wayland = true;
jellyfinScaleFactor = 1.0;
}; };
nfs-mounts.enable = true;
users.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. # Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;

View File

@@ -0,0 +1,424 @@
# Migration Plan: Arch Linux to NixOS on john-endesktop (ZFS/NFS Server)
## Overview
This document outlines the plan to migrate the john-endesktop server from Arch Linux to NixOS while maintaining the existing ZFS pools and NFS exports that serve your k3s cluster.
## Current System State
### Hardware
- **Boot disk**: nvme0n1
- nvme0n1p3: 1000M EFI partition (UUID: F5C6-D570)
- nvme0n1p4: 120GB ext4 / (current Arch root)
- nvme0n1p5: 810GB - **Target for NixOS** (being removed from media pool)
- **Network**: enp0s31f6 @ 10.0.0.43/24 (DHCP)
### ZFS Pools
- **media**: ~3.5TB JBOD pool (2 drives after nvme0n1p5 removal)
- wwn-0x50014ee2ba653d70-part2
- ata-WDC_WD20EZBX-00AYRA0_WD-WX62D627X7Z8-part2
- Contains: /media/media/nix (bind mounted to /nix on Arch)
- NFS: Shared to 10.0.0.0/24 via ZFS sharenfs property
- **swarmvols**: 928GB mirror pool - **PRODUCTION DATA**
- wwn-0x5002538f52707e2d-part2
- wwn-0x5002538f52707e81-part2
- Contains: iocage jails and k3s persistent volumes
- NFS: Shared to 10.0.0.0/24 via ZFS sharenfs property
- Backed up nightly to remote borg
### Services
- NFS server exporting /media and /swarmvols to k3s cluster
- ZFS managing pools with automatic exports via sharenfs property
## Prerequisites
### Before Starting
1. ✅ Ensure nvme0n1p5 removal from media pool is complete
```bash
ssh 10.0.0.43 "zpool status media"
# Should show no "removing" devices
```
2. ✅ Verify recent backups exist
```bash
# Verify swarmvols backup is recent (< 24 hours)
# Check your borg backup system
```
3. ✅ Notify k3s cluster users of planned maintenance window
- NFS shares will be unavailable during migration
- Estimate: 30-60 minutes downtime
4. ✅ Build NixOS configuration from your workstation
```bash
cd ~/nixos-configs
nix build .#nixosConfigurations.john-endesktop.config.system.build.toplevel
```
## Migration Steps
### Phase 1: Prepare NixOS Installation Media
1. **Download NixOS minimal ISO**
```bash
wget https://channels.nixos.org/nixos-25.11/latest-nixos-minimal-x86_64-linux.iso
```
2. **Create bootable USB**
```bash
# Identify USB device (e.g., /dev/sdb)
lsblk
# Write ISO to USB
sudo dd if=latest-nixos-minimal-x86_64-linux.iso of=/dev/sdX bs=4M status=progress
sudo sync
```
### Phase 2: Backup and Shutdown
1. **On the server, verify ZFS pool status**
```bash
ssh 10.0.0.43 "zpool status"
ssh 10.0.0.43 "zfs list"
```
2. **Export ZFS pools cleanly**
```bash
ssh 10.0.0.43 "sudo zpool export media"
ssh 10.0.0.43 "sudo zpool export swarmvols"
```
3. **Shutdown Arch Linux**
```bash
ssh 10.0.0.43 "sudo shutdown -h now"
```
### Phase 3: Install NixOS
1. **Boot from NixOS USB**
- Insert USB drive
- Power on and select USB in boot menu
2. **Connect to network**
```bash
# If DHCP doesn't work automatically:
sudo systemctl start dhcpcd
ip a # Verify you have 10.0.0.43 or another IP
```
3. **Enable SSH for remote installation (recommended)**
```bash
# Set password for nixos user
sudo passwd nixos
# Start SSH
sudo systemctl start sshd
# From your workstation:
ssh nixos@10.0.0.43
```
4. **Partition nvme0n1p5 with btrfs**
```bash
# Verify the device is clear
lsblk
sudo wipefs -a /dev/nvme0n1p5
# Create btrfs filesystem
sudo mkfs.btrfs -L nixos /dev/nvme0n1p5
# Mount and create subvolumes
sudo mount /dev/nvme0n1p5 /mnt
sudo btrfs subvolume create /mnt/@
sudo btrfs subvolume create /mnt/@home
sudo btrfs subvolume create /mnt/@nix
sudo btrfs subvolume create /mnt/@log
sudo umount /mnt
# Mount root subvolume
sudo mount -o subvol=@,compress=zstd,noatime /dev/nvme0n1p5 /mnt
# Create mount points
sudo mkdir -p /mnt/{boot,home,nix,var/log}
# Mount other subvolumes
sudo mount -o subvol=@home,compress=zstd,noatime /dev/nvme0n1p5 /mnt/home
sudo mount -o subvol=@nix,compress=zstd,noatime /dev/nvme0n1p5 /mnt/nix
sudo mount -o subvol=@log,compress=zstd,noatime /dev/nvme0n1p5 /mnt/var/log
# Mount EFI partition
sudo mount /dev/nvme0n1p3 /mnt/boot
```
5. **Import ZFS pools**
```bash
# Import pools (should be visible)
sudo zpool import
# Import with force if needed due to hostid
sudo zpool import -f media
sudo zpool import -f swarmvols
# Verify pools are mounted
zfs list
ls -la /media /swarmvols
```
6. **Generate initial hardware configuration**
```bash
sudo nixos-generate-config --root /mnt
```
7. **Get the new root filesystem UUID**
```bash
blkid /dev/nvme0n1p5
# Note the UUID for updating hardware-configuration.nix
/dev/nvme0n1p5: LABEL="nixos" UUID="5f4ad025-bfab-4aed-a933-6638348059e5" UUID_SUB="4734d820-7b8a-4b7f-853a-026021c1d204" BLOCK_SIZE="4096" TYPE="btrfs" PARTLABEL="data" PARTUUID="9ea025df-cdb7-48fd-b5d4-37cd5d8588eb"
```
8. **Copy your NixOS configuration to the server**
```bash
# From your workstation:
scp -r ~/nixos-configs/machines/john-endesktop/* nixos@10.0.0.43:/tmp/
# On server:
sudo mkdir -p /mnt/etc/nixos
sudo cp /tmp/configuration.nix /mnt/etc/nixos/
sudo cp /tmp/hardware-configuration.nix /mnt/etc/nixos/
# Edit hardware-configuration.nix to update the root filesystem UUID
sudo nano /mnt/etc/nixos/hardware-configuration.nix
# Change: device = "/dev/disk/by-uuid/CHANGE-THIS-TO-YOUR-UUID";
# To: device = "/dev/disk/by-uuid/[UUID from blkid]";
```
9. **Install NixOS**
```bash
sudo nixos-install
# Set root password when prompted
# Set user password
sudo nixos-install --no-root-passwd
```
10. **Reboot into NixOS**
```bash
sudo reboot
# Remove USB drive
```
### Phase 4: Post-Installation Verification
1. **Boot into NixOS and verify system**
```bash
ssh johno@10.0.0.43
# Check NixOS version
nixos-version
# Verify hostname
hostname # Should be: john-endesktop
```
2. **Verify ZFS pools imported correctly**
```bash
zpool status
zpool list
zfs list
# Check for hostid mismatch warnings (should be gone)
# Verify both pools show ONLINE status
```
3. **Verify NFS exports are active**
```bash
sudo exportfs -v
systemctl status nfs-server
# Should see /media and /swarmvols exported to 10.0.0.0/24
```
4. **Test NFS mount from another machine**
```bash
# From a k3s node or your workstation:
sudo mount -t nfs 10.0.0.43:/swarmvols /mnt
ls -la /mnt
sudo umount /mnt
sudo mount -t nfs 10.0.0.43:/media /mnt
ls -la /mnt
sudo umount /mnt
```
5. **Verify ZFS sharenfs properties preserved**
```bash
zfs get sharenfs media
zfs get sharenfs swarmvols
# Should show: sec=sys,mountpoint,no_subtree_check,no_root_squash,rw=@10.0.0.0/24
```
6. **Check swap device**
```bash
swapon --show
free -h
# Should show /dev/zvol/media/swap
```
### Phase 5: Restore k3s Cluster Access
1. **Restart k3s nodes or remount NFS shares**
```bash
# On each k3s node:
sudo systemctl restart k3s # or k3s-agent
```
2. **Verify k3s pods have access to persistent volumes**
```bash
# On k3s master:
kubectl get pv
kubectl get pvc
# Check that volumes are bound and accessible
```
## Rollback Plan
If something goes wrong during migration, you can roll back to Arch Linux:
### Quick Rollback (If NixOS won't boot)
1. **Boot from NixOS USB (or Arch USB)**
2. **Import ZFS pools**
```bash
sudo zpool import -f media
sudo zpool import -f swarmvols
```
3. **Start NFS manually (temporary)**
```bash
sudo mkdir -p /media /swarmvols
sudo systemctl start nfs-server
sudo exportfs -o rw,sync,no_subtree_check,no_root_squash 10.0.0.0/24:/media
sudo exportfs -o rw,sync,no_subtree_check,no_root_squash 10.0.0.0/24:/swarmvols
sudo exportfs -v
```
This will restore k3s cluster access immediately while you diagnose.
4. **Boot back into Arch Linux**
```bash
# Reboot and select nvme0n1p4 (Arch) in GRUB/boot menu
sudo reboot
```
5. **Verify Arch boots and services start**
```bash
ssh johno@10.0.0.43
zpool status
systemctl status nfs-server
```
### Full Rollback (If needed)
1. **Follow Quick Rollback steps above**
2. **Re-add nvme0n1p5 to media pool (if desired)**
```bash
# Only if you want to restore the original configuration
sudo zpool add media /dev/nvme0n1p5
```
3. **Clean up NixOS partition**
```bash
# If you want to reclaim nvme0n1p5 for other uses
sudo wipefs -a /dev/nvme0n1p5
```
## Risk Mitigation
### Data Safety
- ✅ **swarmvols** (production): Mirrored + nightly borg backups
- ⚠️ **media** (important): JBOD - no redundancy, but not catastrophic
- ✅ **NixOS install**: Separate partition, doesn't touch ZFS pools
- ✅ **Arch Linux**: Remains bootable on nvme0n1p4 until verified
### Service Continuity
- Downtime: 30-60 minutes expected
- k3s cluster: Will reconnect automatically when NFS returns
- Rollback time: < 10 minutes to restore Arch
### Testing Approach
1. Test NFS exports from NixOS live environment before installation
2. Test single NFS mount from k3s node before full cluster restart
3. Keep Arch Linux boot option until 24-48 hours of stable NixOS operation
## Post-Migration Tasks
After successful migration and 24-48 hours of stable operation:
1. **Update k3s NFS mounts (if needed)**
- Verify no hardcoded references to old system
2. **Optional: Repurpose Arch partition**
```bash
# After you're confident NixOS is stable
# You can wipe nvme0n1p4 and repurpose it
```
3. **Update documentation**
- Update infrastructure docs with NixOS configuration
- Document any deviations from this plan
4. **Consider setting up NixOS remote deployment**
```bash
# From your workstation:
nixos-rebuild switch --target-host johno@10.0.0.43 --flake .#john-endesktop
```
## Timeline
- **Preparation**: 1-2 hours (testing config build, downloading ISO)
- **Migration window**: 1-2 hours (installation + verification)
- **Verification period**: 24-48 hours (before removing Arch)
- **Total**: ~3 days from start to declaring success
## Emergency Contacts
- Borg backup location: [Document your borg repo location]
- K3s cluster nodes: [Document your k3s nodes]
- Critical services on k3s: [Document what's running that depends on these NFS shares]
## Checklist
Pre-migration:
- [x] nvme0n1p5 removal from media pool complete
- [x] Recent backup verified (< 24 hours)
- [x] Maintenance window scheduled
- [x] NixOS ISO downloaded
- [x] Bootable USB created
- [x] NixOS config builds successfully
During migration:
- [ ] ZFS pools exported
- [ ] Arch Linux shutdown cleanly
- [ ] Booted from NixOS USB
- [ ] nvme0n1p5 formatted with btrfs
- [ ] Btrfs subvolumes created
- [ ] ZFS pools imported
- [ ] NixOS installed
- [ ] Root password set
Post-migration:
- [ ] NixOS boots successfully
- [ ] ZFS pools mounted automatically
- [ ] NFS server running
- [ ] NFS exports verified
- [ ] Test mount from k3s node successful
- [ ] k3s cluster reconnected
- [ ] Persistent volumes accessible
- [ ] No hostid warnings in zpool status
- [ ] Arch Linux still bootable (for rollback)
Final verification (after 24-48 hours):
- [ ] All services stable
- [ ] No unexpected issues
- [ ] Performance acceptable
- [ ] Ready to remove Arch partition (optional)
- [ ] Ready to remove /swarmvols/media-backup (optional)

View File

@@ -0,0 +1,112 @@
# NixOS configuration for john-endesktop (ZFS/NFS server)
# Migrated from Arch Linux to provide ZFS pools via NFS to k3s cluster
{ config, lib, pkgs, ... }:
with lib;
{
imports = [
./hardware-configuration.nix
];
# Boot configuration
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# ZFS support
boot.supportedFilesystems = [ "zfs" ];
boot.zfs.forceImportRoot = false;
boot.zfs.extraPools = [ "media" "swarmvols" ];
# Set ZFS hostid to match current system (from Arch Linux)
# This resolves the hostid mismatch warnings
networking.hostId = "007f0101";
# Hostname
networking.hostName = "john-endesktop";
# Network configuration - using DHCP on enp0s31f6
networking.useDHCP = false;
networking.interfaces.enp0s31f6.useDHCP = true;
# NFS Server configuration
services.nfs.server = {
enable = true;
# NFS protocol versions
# v3 for broader compatibility, v4 for better performance
exports = ''
# These are managed by ZFS sharenfs properties
# but we enable the NFS server here
'';
};
# Enable NFS4 with proper configuration
services.rpcbind.enable = true;
# Firewall configuration for NFS
networking.firewall = {
enable = true;
allowedTCPPorts = [
111 # rpcbind
2049 # nfs
4000 # nfs callback
4001 # nlockmgr
4002 # mountd
20048 # mountd
];
allowedUDPPorts = [
111 # rpcbind
2049 # nfs
4000 # nfs callback
4001 # nlockmgr
4002 # mountd
20048 # mountd
];
# Allow NFS from local network
extraCommands = ''
iptables -A nixos-fw -p tcp -s 10.0.0.0/24 -j ACCEPT
iptables -A nixos-fw -p udp -s 10.0.0.0/24 -j ACCEPT
'';
};
# ZFS maintenance
services.zfs = {
autoScrub = {
enable = true;
interval = "monthly";
};
trim = {
enable = true;
interval = "weekly";
};
};
# Basic system packages
environment.systemPackages = with pkgs; [
vim
git
htop
tmux
zfs
];
# Enable SSH
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = true;
};
};
# User configuration
roles.users.enable = true;
# Time zone
time.timeZone = "America/Los_Angeles"; # Adjust as needed
# NixOS version
system.stateVersion = "25.11";
}

View File

@@ -0,0 +1,63 @@
# Hardware configuration for john-endesktop
# This file should be regenerated after NixOS installation using:
# nixos-generate-config --show-hardware-config
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
# Boot configuration
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
# File systems - these will need to be updated after installation
# The nvme0n1p5 partition will be formatted as btrfs for NixOS root
fileSystems."/" = {
device = "/dev/disk/by-uuid/5f4ad025-bfab-4aed-a933-6638348059e5";
fsType = "btrfs";
options = [ "subvol=@" "compress=zstd" "noatime" ];
};
fileSystems."/home" = {
device = "/dev/disk/by-uuid/5f4ad025-bfab-4aed-a933-6638348059e5";
fsType = "btrfs";
options = [ "subvol=@home" "compress=zstd" "noatime" ];
};
fileSystems."/nix" = {
device = "/dev/disk/by-uuid/5f4ad025-bfab-4aed-a933-6638348059e5";
fsType = "btrfs";
options = [ "subvol=@nix" "compress=zstd" "noatime" ];
};
fileSystems."/var/log" = {
device = "/dev/disk/by-uuid/5f4ad025-bfab-4aed-a933-6638348059e5";
fsType = "btrfs";
options = [ "subvol=@log" "compress=zstd" "noatime" ];
};
fileSystems."/boot" = {
# This should match your current EFI partition
device = "/dev/disk/by-uuid/F5C6-D570";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
# ZFS pools - these are imported by ZFS, not managed by fileSystems
# The pools should be imported automatically via boot.zfs.extraPools
# /media and /swarmvols will be mounted by ZFS
# No swap needed - 23GB RAM is sufficient for this NFS/ZFS server
swapDevices = [ ];
# CPU microcode
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
# Networking
networking.useDHCP = lib.mkDefault true;
}

View File

@@ -39,6 +39,17 @@
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
boot.initrd.luks.devices."luks-b614167b-9045-4234-a441-ac6f60a96d81".device = "/dev/disk/by-uuid/b614167b-9045-4234-a441-ac6f60a96d81"; 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.hostName = "nix-book"; # Define your hostname.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.

View File

@@ -7,9 +7,9 @@
with lib; with lib;
{ {
imports = imports = [
[ # Include the results of the hardware scan.
./hardware-configuration.nix ./hardware-configuration.nix
#./virtual-surround.nix
]; ];
roles = { roles = {

View File

@@ -0,0 +1,132 @@
# Virtual 4.1 surround sound setup
# Routes FL/FR to AmazonBasics USB speaker, RL/RR to Fosi BT20A PRO Bluetooth speaker
{ pkgs, ... }:
{
services.pipewire.extraConfig.pipewire."10-virtual-surround" = {
"context.objects" = [
{
factory = "adapter";
args = {
"factory.name" = "support.null-audio-sink";
"node.name" = "virtual_surround_sink";
"node.description" = "Virtual 4.1 Surround (AmazonBasics + Fosi)";
"media.class" = "Audio/Sink";
"audio.position" = [ "FL" "FR" "RL" "RR" "LFE" ];
"monitor.channel-volumes" = true;
};
}
];
"context.modules" = [
{
name = "libpipewire-module-loopback";
args = {
"node.description" = "Route Front to AmazonBasics";
"capture.props" = {
"node.name" = "route_front_capture";
"audio.position" = [ "FL" "FR" ];
"stream.dont-remix" = true;
"node.passive" = true;
};
"playback.props" = {
"node.name" = "route_front_playback";
"node.target" = "alsa_output.usb-C-Media_Electronics_Inc._AmazonBasics_Professional_Mic_2-00.analog-stereo";
"audio.position" = [ "FL" "FR" ];
"stream.dont-remix" = true;
};
};
}
{
name = "libpipewire-module-loopback";
args = {
"node.description" = "Route Rear to Fosi Audio";
"capture.props" = {
"node.name" = "route_rear_capture";
"audio.position" = [ "RL" "RR" ];
"stream.dont-remix" = true;
"node.passive" = true;
};
"playback.props" = {
"node.name" = "route_rear_playback";
"node.target" = "bluez_output.F4_4E_FD_FB_58_62.1";
"audio.position" = [ "FL" "FR" ];
"stream.dont-remix" = true;
};
};
}
{
name = "libpipewire-module-loopback";
args = {
"node.description" = "Route Subwoofer to AmazonBasics";
"capture.props" = {
"node.name" = "route_lfe_capture";
"audio.position" = [ "LFE" ];
"stream.dont-remix" = true;
"node.passive" = true;
};
"playback.props" = {
"node.name" = "route_lfe_playback";
"node.target" = "alsa_output.usb-C-Media_Electronics_Inc._AmazonBasics_Professional_Mic_2-00.analog-stereo";
"audio.position" = [ "MONO" ];
"stream.dont-remix" = false;
};
};
}
];
};
# Systemd services to fix PipeWire loopback routing for virtual surround
systemd.user.services.pipewire-surround-link = {
description = "Link virtual surround sink to loopback captures";
after = [ "pipewire.service" "wireplumber.service" ];
requires = [ "pipewire.service" "wireplumber.service" ];
wantedBy = [ "pipewire.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = false;
ExecStart = pkgs.writeShellScript "surround-link" ''
sleep 2
# Disconnect wrong connections
${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX0 route_front_capture:input_FL 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX1 route_front_capture:input_FR 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX0 route_rear_capture:input_RL 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX1 route_rear_capture:input_RR 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX0 route_lfe_capture:input_LFE 2>/dev/null || true
# Create correct connections
${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_FL route_front_capture:input_FL 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_FR route_front_capture:input_FR 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_RL route_rear_capture:input_RL 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_RR route_rear_capture:input_RR 2>/dev/null || true
${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_LFE route_lfe_capture:input_LFE 2>/dev/null || true
'';
};
};
systemd.user.services.pipewire-surround-link-check = {
description = "Check and fix surround sink links";
after = [ "pipewire.service" "wireplumber.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "surround-link-check" ''
if ${pkgs.pipewire}/bin/pw-cli ls Node 2>/dev/null | grep -q "bluez_output.F4_4E_FD_FB_58_62"; then
if ${pkgs.pipewire}/bin/pw-link -l 2>/dev/null | grep -q "route_front_capture:input_FL.*alsa_input"; then
${pkgs.systemd}/bin/systemctl --user start pipewire-surround-link.service
fi
if ! ${pkgs.pipewire}/bin/pw-link -l 2>/dev/null | grep -q "virtual_surround_sink:monitor_FL.*route_front_capture"; then
${pkgs.systemd}/bin/systemctl --user start pipewire-surround-link.service
fi
fi
'';
};
};
systemd.user.timers.pipewire-surround-link-check = {
description = "Periodically check surround sink links";
wantedBy = [ "default.target" ];
timerConfig = {
OnStartupSec = "10s";
OnUnitActiveSec = "10s";
Unit = "pipewire-surround-link-check.service";
};
};
}

View File

@@ -5,24 +5,24 @@
}: }:
let let
version = "2.0.53"; version = "2.0.76";
srcs = { srcs = {
aarch64-darwin = { aarch64-darwin = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-arm64/claude"; url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-arm64/claude";
sha256 = "28c3ad73a20f3ae7ab23efa24d45a9791ccbe071284f1622d4e5e2b89c4a15b7"; sha256 = "b76f6d4d09233e67295897b0a1ed2e22d7afa406431529d8b1b532b63b8cbcbd";
}; };
x86_64-darwin = { x86_64-darwin = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-x64/claude"; url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-x64/claude";
sha256 = "a27f7b75a51514658640432a0afec8be130673eb7dbecc9a4d742527dd85d29a"; sha256 = "9d94582f0af5d2201f1c907bf24ff8d216104b897ee0b24795a6c081f40e08d7";
}; };
x86_64-linux = { x86_64-linux = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-x64/claude"; url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-x64/claude";
sha256 = "9c4cc19e207fb6bf7ea140a1580d5ed0dd0a481af471f23614d5a140a4abf1c6"; sha256 = "5dcdb480f91ba0df0bc8bd6aff148d3dfd3883f0899eeb5b9427a8b0abe7a687";
}; };
aarch64-linux = { aarch64-linux = {
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-arm64/claude"; url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-arm64/claude";
sha256 = "a5d4044034f3b63c38379bc2dd4067a4dd3c8ec48965ba8e66e3623774a93b72"; sha256 = "f64a994c8e5bfb84d7242cebbec75d6919db2ee46d50b8fc7a88d5066db193f9";
}; };
}; };

View File

@@ -33,7 +33,8 @@ YELLOW='\033[1;33m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
CASK_URL="https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/claude-code.rb" CASK_URL="https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/claude-code.rb"
NIX_FILE="$(dirname "$0")/default.nix" REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
NIX_FILE="$REPO_ROOT/packages/claude-code/default.nix"
echo "Fetching latest claude-code version from Homebrew cask..." echo "Fetching latest claude-code version from Homebrew cask..."

View File

@@ -4,4 +4,5 @@
tea-rbw = pkgs.callPackage ./tea-rbw {}; tea-rbw = pkgs.callPackage ./tea-rbw {};
app-launcher-server = pkgs.callPackage ./app-launcher-server {}; app-launcher-server = pkgs.callPackage ./app-launcher-server {};
claude-code = pkgs.callPackage ./claude-code {}; claude-code = pkgs.callPackage ./claude-code {};
perles = pkgs.callPackage ./perles {};
} }

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";
};
}

35
roles/common.nix Normal file
View File

@@ -0,0 +1,35 @@
# Common configuration shared between NixOS and Darwin
{ lib, pkgs, ... }:
{
config = {
time.timeZone = "America/Los_Angeles";
environment.systemPackages = with pkgs; [
git
glances
pciutils
tree
usbutils
vim
];
nix = {
package = pkgs.nix;
settings = {
experimental-features = [ "nix-command" "flakes" ];
max-jobs = "auto";
trusted-users = [ "johno" ];
substituters = [
];
};
gc = {
automatic = true;
options = "--delete-older-than 10d";
};
};
nixpkgs.config.allowUnfree = true;
};
}

View File

@@ -7,6 +7,10 @@ let
setEnvironmentPath = "${config.system.build.setEnvironment}"; setEnvironmentPath = "${config.system.build.setEnvironment}";
in in
{ {
imports = [
./common.nix
];
config = { config = {
# Salt manages /etc/bashrc, /etc/zshrc, /etc/zshenv # Salt manages /etc/bashrc, /etc/zshrc, /etc/zshenv
# nix-darwin writes to .local variants for nix-specific configuration # nix-darwin writes to .local variants for nix-specific configuration
@@ -43,8 +47,6 @@ in
fi fi
''; '';
time.timeZone = "America/Los_Angeles";
# System preferences # System preferences
system.defaults = { system.defaults = {
# Custom keyboard shortcuts # Custom keyboard shortcuts
@@ -79,42 +81,5 @@ in
}; };
}; };
}; };
environment.systemPackages = with pkgs; [
git
glances
pciutils
tree
usbutils
vim
];
nix = {
package = pkgs.nix;
# distributedBuilds = true;
# buildMachines = [{
# hostName = "z790prors.oglehome";
# system = "x86_64-linux";
# protocol = "ssh-ng";
# sshUser = "johno";
# sshKey = "/root/.ssh/id_ed25519";
# maxJobs = 3;
# speedFactor = 2;
# }];
settings = {
experimental-features = [ "nix-command" "flakes" ];
max-jobs = "auto";
trusted-users = [ "johno" ];
substituters = [
];
};
gc = {
automatic = true;
options = "--delete-older-than 10d";
};
};
nixpkgs.config.allowUnfree = true;
}; };
} }

View File

@@ -4,6 +4,7 @@ with lib;
{ {
imports = [ imports = [
./common.nix
./audio ./audio
./bluetooth ./bluetooth
./btrfs ./btrfs
@@ -31,7 +32,6 @@ with lib;
LC_TELEPHONE = "en_US.UTF-8"; LC_TELEPHONE = "en_US.UTF-8";
LC_TIME = "en_US.UTF-8"; LC_TIME = "en_US.UTF-8";
}; };
time.timeZone = "America/Los_Angeles";
services.xserver.xkb = { services.xserver.xkb = {
layout = "us"; layout = "us";
@@ -49,42 +49,7 @@ with lib;
# Enable the OpenSSH daemon. # Enable the OpenSSH daemon.
services.openssh.enable = true; services.openssh.enable = true;
environment.systemPackages = with pkgs; [ # NixOS-specific gc option (not available on Darwin)
git nix.gc.randomizedDelaySec = "14m";
glances
pciutils
tree
usbutils
vim
];
nix = {
package = pkgs.nix;
# distributedBuilds = true;
# buildMachines = [{
# hostName = "z790prors.oglehome";
# system = "x86_64-linux";
# protocol = "ssh-ng";
# sshUser = "johno";
# sshKey = "/root/.ssh/id_ed25519";
# maxJobs = 3;
# speedFactor = 2;
# }];
settings = {
experimental-features = [ "nix-command" "flakes" ];
max-jobs = "auto";
trusted-users = [ "johno" ];
substituters = [
];
};
gc = {
automatic = true;
randomizedDelaySec = "14m";
options = "--delete-older-than 10d";
};
};
nixpkgs.config.allowUnfree = true;
}; };
} }

View File

@@ -22,6 +22,8 @@ in
# indiviudal cores # indiviudal cores
#retroarch-full #retroarch-full
ryubing ryubing
yarg
]; ];
programs.steam = { programs.steam = {

View File

@@ -14,6 +14,11 @@ in
wayland = mkOption { wayland = mkOption {
default = true; default = true;
}; };
jellyfinScaleFactor = mkOption {
type = types.nullOr types.float;
default = null;
description = "Scale factor for Jellyfin Media Player UI (e.g., 1.5 for 150% scaling)";
};
appLauncherServer = { appLauncherServer = {
enable = mkOption { enable = mkOption {
type = types.bool; type = types.bool;
@@ -37,6 +42,28 @@ in
steam-library steam-library
youtube youtube
]); ]);
jellyfinMediaPlayerPkg =
if cfg.jellyfinScaleFactor != null
then pkgs.symlinkJoin {
name = "jellyfin-media-player-scaled";
paths = [ pkgs.jellyfin-media-player ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
mkdir -p $out/bin
rm -f $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.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.jellyfin-media-player;
in mkIf cfg.enable in mkIf cfg.enable
{ {
users.extraUsers.kodi = { users.extraUsers.kodi = {
@@ -50,11 +77,18 @@ in
}; };
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
jellyfinMediaPlayerPkg
kodiPkg kodiPkg
wget wget
firefox firefox
] ++ optional cfg.appLauncherServer.enable pkgs.custom.app-launcher-server; ] ++ 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 dependency. Remove this once jellyfin is updated to use qt6"
[
"qtwebengine-5.15.19"
];
programs.kdeconnect.enable = true; programs.kdeconnect.enable = true;
systemd.user.services = mkIf cfg.appLauncherServer.enable { systemd.user.services = mkIf cfg.appLauncherServer.enable {

View File

@@ -25,7 +25,7 @@ in
users.users.johno = { users.users.johno = {
isNormalUser = true; isNormalUser = true;
description = "John Ogle"; description = "John Ogle";
extraGroups = [ "wheel" "networkmanager" "audio" "video" ] ++ cfg.extraGroups; extraGroups = [ "wheel" "networkmanager" "audio" "video" "i2c" ] ++ cfg.extraGroups;
}; };
users.users.eli = mkIf cfg.kids { users.users.eli = mkIf cfg.kids {

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
WALLPAPER_FILE="$REPO_ROOT/home/wallpapers/default.nix"
echo -e "${GREEN}Rotating wallpaper...${NC}"
# Check if file exists
if [[ ! -f "$WALLPAPER_FILE" ]]; then
echo -e "${RED}Error: $WALLPAPER_FILE not found${NC}"
exit 1
fi
# Get current index
CURRENT_INDEX=$(grep -oP 'currentIndex = \K\d+' "$WALLPAPER_FILE")
echo -e "Current index: ${YELLOW}$CURRENT_INDEX${NC}"
# Count wallpapers (count occurrences of "name = " in the wallpapers list)
WALLPAPER_COUNT=$(grep -c 'name = "' "$WALLPAPER_FILE")
echo -e "Total wallpapers: ${YELLOW}$WALLPAPER_COUNT${NC}"
# Calculate next index (wrap around)
NEXT_INDEX=$(( (CURRENT_INDEX + 1) % WALLPAPER_COUNT ))
echo -e "Next index: ${YELLOW}$NEXT_INDEX${NC}"
# Update the currentIndex
sed -i "s/currentIndex = $CURRENT_INDEX;/currentIndex = $NEXT_INDEX;/" "$WALLPAPER_FILE"
echo -e "${GREEN}Successfully rotated wallpaper!${NC}"
echo -e " Old index: ${YELLOW}$CURRENT_INDEX${NC}"
echo -e " New index: ${YELLOW}$NEXT_INDEX${NC}"
echo ""
echo "Rebuild your system to apply the new wallpaper."

82
scripts/update-doomemacs.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
OWNER="doomemacs"
REPO="doomemacs"
FILE="home/roles/emacs/default.nix"
# Use current working directory as repo root (allows running from anywhere in the repo)
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
TARGET_FILE="$REPO_ROOT/$FILE"
echo -e "${GREEN}Updating DoomEmacs to latest commit...${NC}"
# Check if file exists
if [[ ! -f "$TARGET_FILE" ]]; then
echo -e "${RED}Error: $TARGET_FILE not found${NC}"
exit 1
fi
# Get the default branch first
echo "Fetching repository information..."
DEFAULT_BRANCH=$(curl -s "https://api.github.com/repos/$OWNER/$REPO" | jq -r '.default_branch')
if [[ -z "$DEFAULT_BRANCH" ]] || [[ "$DEFAULT_BRANCH" == "null" ]]; then
echo -e "${RED}Error: Failed to fetch default branch${NC}"
exit 1
fi
# Get the latest commit SHA from GitHub
echo "Fetching latest commit SHA from $DEFAULT_BRANCH branch..."
LATEST_SHA=$(curl -s "https://api.github.com/repos/$OWNER/$REPO/commits/$DEFAULT_BRANCH" | jq -r '.sha')
if [[ -z "$LATEST_SHA" ]] || [[ "$LATEST_SHA" == "null" ]]; then
echo -e "${RED}Error: Failed to fetch latest commit SHA${NC}"
exit 1
fi
echo -e "Latest commit: ${YELLOW}$LATEST_SHA${NC}"
# Get current SHA from file
CURRENT_SHA=$(grep -oP 'rev = "\K[^"]+' "$TARGET_FILE")
echo -e "Current commit: ${YELLOW}$CURRENT_SHA${NC}"
if [[ "$CURRENT_SHA" == "$LATEST_SHA" ]]; then
echo -e "${GREEN}Already up to date!${NC}"
exit 0
fi
# Update the rev field
echo "Updating rev in $FILE..."
sed -i "s/rev = \".*\"/rev = \"$LATEST_SHA\"/" "$TARGET_FILE"
# Fetch the new sha256 hash using nix-prefetch
echo "Fetching new sha256 hash..."
NEW_SHA256=$(nix-prefetch-url --unpack "https://github.com/$OWNER/$REPO/archive/$LATEST_SHA.tar.gz" 2>/dev/null)
if [[ -z "$NEW_SHA256" ]]; then
echo -e "${RED}Error: Failed to fetch sha256 hash${NC}"
# Revert the rev change
sed -i "s/rev = \".*\"/rev = \"$CURRENT_SHA\"/" "$TARGET_FILE"
exit 1
fi
# Convert to SRI hash format
SRI_HASH=$(nix hash to-sri --type sha256 "$NEW_SHA256")
echo -e "New sha256: ${YELLOW}$SRI_HASH${NC}"
# Update the sha256 field
sed -i "s|sha256 = \".*\"|sha256 = \"$SRI_HASH\"|" "$TARGET_FILE"
echo -e "${GREEN}Successfully updated DoomEmacs!${NC}"
echo -e " Old commit: ${YELLOW}$CURRENT_SHA${NC}"
echo -e " New commit: ${YELLOW}$LATEST_SHA${NC}"
echo -e " New sha256: ${YELLOW}$SRI_HASH${NC}"
echo ""
echo "You can now rebuild your system with the updated DoomEmacs."

46
scripts/upgrade.sh Normal file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} NixOS Configuration Major Upgrade${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Step 1: Update flake inputs
echo -e "${GREEN}[1/4] Updating flake inputs...${NC}"
cd "$REPO_ROOT"
nix flake update
echo ""
# Step 2: Update Doom Emacs
echo -e "${GREEN}[2/4] Updating Doom Emacs...${NC}"
"$REPO_ROOT/scripts/update-doomemacs.sh"
echo ""
# Step 3: Update Claude Code
echo -e "${GREEN}[3/4] Updating Claude Code...${NC}"
"$REPO_ROOT/packages/claude-code/update.sh"
echo ""
# Step 4: Rotate wallpaper
echo -e "${GREEN}[4/4] Rotating wallpaper...${NC}"
"$REPO_ROOT/scripts/rotate-wallpaper.sh"
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}Upgrade complete!${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo "Next steps:"
echo " 1. Review changes: git diff"
echo " 2. Rebuild system: sudo nixos-rebuild switch --flake ."
echo " 3. If satisfied, commit: git add -A && git commit -m 'chore: Major upgrade'"