Compare commits
176 Commits
63d741e55e
...
bead/nixos
| Author | SHA1 | Date | |
|---|---|---|---|
| 214368da4b | |||
| 2c91cd23be | |||
| 3a2773dcce | |||
| f3af982304 | |||
| 47e2392a56 | |||
| c26a11a9a8 | |||
| 7ba1b52ec7 | |||
| 056c1a1e62 | |||
| c92a82b21a | |||
| b6e9de0f61 | |||
| ba4922981b | |||
| 47aaad2eb5 | |||
| 8eca8204ff | |||
| 082b0918af | |||
| 7a5f167a8c | |||
| 9e1003d4fc | |||
| bf600987e9 | |||
| 346ad3665d | |||
| 565acb1632 | |||
| b05c6d8c30 | |||
| 0f555fdd57 | |||
| 9973273b5e | |||
| f281384b69 | |||
| 4eec701729 | |||
| bbcb13881f | |||
| c28d6a7896 | |||
| 79ff0b8aa4 | |||
| 1d9249ea83 | |||
| 2fdd2d5345 | |||
| 722cb315dc | |||
| e042acff16 | |||
| 4fe531f87f | |||
| 266dee9f8f | |||
| 38395c238f | |||
| e4a1771f48 | |||
| ff1fb245ac | |||
| 82fb1738c1 | |||
| 425e4f4cee | |||
| 0e5b11e55d | |||
| 1ba1a8fc9d | |||
| 009b84656f | |||
| ef4e4509d3 | |||
| cd6b528692 | |||
| 3914b54c73 | |||
| 9aa74258f9 | |||
| 64dda20aa4 | |||
| ac01548e89 | |||
| bb7f79843b | |||
| c1d6663a36 | |||
| 3cf4403ffa | |||
| 4e6123de9a | |||
| 19ee298b71 | |||
| 537f7831a7 | |||
| bf0d16fe1a | |||
| 858b6009ae | |||
| 40b323dcfd | |||
| 60bd89b02c | |||
| 5a5de7353b | |||
| e25aa7acab | |||
| 4a284de8a1 | |||
| 7be694ef66 | |||
| 0ccfc30c73 | |||
| dab96a1c50 | |||
| 7898def044 | |||
| 4ce48313f6 | |||
| 2c70504c43 | |||
| a22c7fec28 | |||
| ef3e9b8c82 | |||
| 0590dad71e | |||
| c81594af01 | |||
| 6f999882d3 | |||
| 8bb20bf05e | |||
| c480bcdd1d | |||
| 05fed3ede1 | |||
| 0a9de8d159 | |||
| 055d6ab421 | |||
| d5c6342b84 | |||
| e04dacdf65 | |||
| 7d74917bdc | |||
| 5a4ab71849 | |||
| bcebf9b376 | |||
| 0f76939983 | |||
| a1da2f5cc1 | |||
| 175da48170 | |||
| ac956ef48c | |||
| 0c1190f39c | |||
| 00f05d1bb2 | |||
| 4e6c6ab81d | |||
| 04e1a8563c | |||
| 7278dc8306 | |||
| 066eea2999 | |||
| 80633142fb | |||
| 3029e3d9a8 | |||
| 3483e26bce | |||
| b3add6ddf8 | |||
| 89994e3fc8 | |||
| 0e9671a45f | |||
| f4078970b2 | |||
| 0ae4d84ca2 | |||
| 7c877fde84 | |||
| d53286e04c | |||
| bc42c4dc77 | |||
| 445b0cd558 | |||
| 6d9686f14b | |||
| 4164832eea | |||
| 585f9ef5c7 | |||
| ade60ba5ec | |||
| 48fb7cdada | |||
| 2d8cfe75a0 | |||
| 385fd798de | |||
| fe6558e0c1 | |||
| b9c48f9dd1 | |||
| 34351403d1 | |||
| 12820ce9ff | |||
| 0f5eb2e572 | |||
| f356c91fdb | |||
| 6b42612135 | |||
| 50a8c44d10 | |||
| 7011fb27a5 | |||
| 1ff8b81f44 | |||
| 55f13dfb08 | |||
| 63bf19b85f | |||
| 1f9e9138ab | |||
| e218822566 | |||
| e88f3580e9 | |||
| 5451e75480 | |||
| fc9474a7c9 | |||
| 20daebbd61 | |||
| 3be23304c4 | |||
| 9059a739a0 | |||
| 977125645b | |||
| a9772259f0 | |||
| 4f6d65316a | |||
| 0b8e3bf527 | |||
| d3c906134b | |||
| 30b616dd93 | |||
| c9252c42c2 | |||
| fa7cb55c78 | |||
| 2283b0a6df | |||
| 4ea9437bb0 | |||
| d0760a22bd | |||
| f67a12c29a | |||
| fc8a43504d | |||
| 4a73b3a5ae | |||
| be68202523 | |||
| 608fed35ab | |||
| c2e2dd8675 | |||
| 5750f737f1 | |||
| c27518e0dc | |||
| 7f318edc4d | |||
| f995240153 | |||
| d62bae0ddb | |||
| 79ae42f41d | |||
| 0c15aad5c0 | |||
| d87793d39b | |||
| fad6e61aac | |||
| 1bc65ceb51 | |||
| bda76c6abc | |||
| c42e09e972 | |||
| 4d986c0b48 | |||
| 4e3fdd78d2 | |||
| d3703fc5a9 | |||
| 35c2ebb592 | |||
| 569ac528a5 | |||
| 6cc8fa4f5d | |||
| 67a82f14fd | |||
| 4b68e3f051 | |||
| 81a3657759 | |||
| 32e1b81034 | |||
| 6f00c72540 | |||
| d26007aa61 | |||
| 1caa8bba3e | |||
| d3cb09040a | |||
| 4bfacffa17 | |||
| a6961f05ca | |||
| b75c43257b |
39
.beads/.gitignore
vendored
Normal file
39
.beads/.gitignore
vendored
Normal 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
0
.beads/.sync.lock
Normal file
81
.beads/README.md
Normal file
81
.beads/README.md
Normal 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
62
.beads/config.yaml
Normal 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
|
||||||
0
.beads/interactions.jsonl
Normal file
0
.beads/interactions.jsonl
Normal file
4
.beads/metadata.json
Normal file
4
.beads/metadata.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"database": "beads.db",
|
||||||
|
"jsonl_export": "sync_base.jsonl"
|
||||||
|
}
|
||||||
130
.claude/commands/import_gitea_issues.md
Normal file
130
.claude/commands/import_gitea_issues.md
Normal 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
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
# Use bd merge for beads JSONL files
|
||||||
|
.beads/issues.jsonl merge=beads
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
result
|
result
|
||||||
|
thoughts
|
||||||
|
.beads
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
This is a NixOS configuration repository using flakes, managing multiple machines and home-manager configurations. The repository follows a modular architecture with reusable "roles" that can be composed for different machines.
|
This is a NixOS configuration repository using flakes, managing multiple machines and home-manager configurations. The repository follows a modular architecture with reusable "roles" that can be composed for different machines.
|
||||||
|
|
||||||
|
## Issue Tracking
|
||||||
|
|
||||||
|
This repository uses `beads` for issue tracking and management. Run `bd quickstart` to get an overview of the system at the start of every session.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Flake Structure
|
### Flake Structure
|
||||||
@@ -172,6 +176,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
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Build Live USB ISO from flake configuration
|
|
||||||
# Creates an uncompressed ISO suitable for Ventoy and other USB boot tools
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Building Live USB ISO..."
|
|
||||||
nix build .#nixosConfigurations.live-usb.config.system.build.isoImage --show-trace
|
|
||||||
|
|
||||||
if [ -f "./result/iso/"*.iso ]; then
|
|
||||||
iso_file=$(ls ./result/iso/*.iso)
|
|
||||||
echo "✅ Build complete!"
|
|
||||||
echo "📁 ISO location: $iso_file"
|
|
||||||
echo "💾 Ready for Ventoy or dd to USB"
|
|
||||||
else
|
|
||||||
echo "❌ Build failed - no ISO file found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
294
flake.lock
generated
294
flake.lock
generated
@@ -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": 1747046372,
|
"lastModified": 1765121682,
|
||||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
"narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
"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": [
|
||||||
@@ -23,11 +101,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752428473,
|
"lastModified": 1761423376,
|
||||||
"narHash": "sha256-IsE7fdAYbRlZuc0H5FtPfhhuHvlxnDGoAxdlnjpVNCU=",
|
"narHash": "sha256-pMy3cnUFfue4vz/y0jx71BfcPGxZf+hk/DtnzWvfU0c=",
|
||||||
"ref": "refs/heads/main",
|
"ref": "refs/heads/main",
|
||||||
"rev": "1fad66b55144ab6beaecd900172a21ac3c34dc52",
|
"rev": "a1f695665771841a988afc965526cbf99160cd77",
|
||||||
"revCount": 10,
|
"revCount": 11,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.johnogle.info/johno/google-cookie-retrieval.git"
|
"url": "https://git.johnogle.info/johno/google-cookie-retrieval.git"
|
||||||
},
|
},
|
||||||
@@ -43,19 +121,62 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1759172751,
|
"lastModified": 1767514898,
|
||||||
"narHash": "sha256-E8W8sRXfrvkFW26GuuiWq6QfReU7m5+cngwHuRo/3jc=",
|
"narHash": "sha256-ONYqnKrPzfKEEPChoJ9qPcfvBqW9ZgieDKD7UezWPg4=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "12fa8548feefa9a10266ba65152fd1a787cdde8f",
|
"rev": "7a06e8a2f844e128d3b210a000a62716b6040b7f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
|
"ref": "release-25.11",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"home-manager-unstable": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs-unstable"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767556355,
|
||||||
|
"narHash": "sha256-RDTUBDQBi9D4eD9iJQWtUDN/13MDLX+KmE+TwwNUp2s=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "f894bc4ffde179d178d8deb374fcf9855d1a82b7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jovian": {
|
||||||
|
"inputs": {
|
||||||
|
"nix-github-actions": "nix-github-actions",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs-unstable"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767082077,
|
||||||
|
"narHash": "sha256-2tL1mRb9uFJThUNfuDm/ehrnPvImL/QDtCxfn71IEz4=",
|
||||||
|
"owner": "Jovian-Experiments",
|
||||||
|
"repo": "Jovian-NixOS",
|
||||||
|
"rev": "efd4b22e6fdc6d7fb4e186ae333a4b74e03da440",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Jovian-Experiments",
|
||||||
|
"repo": "Jovian-NixOS",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nix-darwin": {
|
"nix-darwin": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -63,30 +184,74 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1758805352,
|
"lastModified": 1765066094,
|
||||||
"narHash": "sha256-BHdc43Lkayd+72W/NXRKHzX5AZ+28F3xaUs3a88/Uew=",
|
"narHash": "sha256-0YSU35gfRFJzx/lTGgOt6ubP8K6LeW0vaywzNNqxkl4=",
|
||||||
"owner": "nix-darwin",
|
"owner": "nix-darwin",
|
||||||
"repo": "nix-darwin",
|
"repo": "nix-darwin",
|
||||||
"rev": "c48e963a5558eb1c3827d59d21c5193622a1477c",
|
"rev": "688427b1aab9afb478ca07989dc754fa543e03d5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-darwin",
|
"owner": "nix-darwin",
|
||||||
|
"ref": "nix-darwin-25.11",
|
||||||
"repo": "nix-darwin",
|
"repo": "nix-darwin",
|
||||||
"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": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"jovian",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1729697500,
|
||||||
|
"narHash": "sha256-VFTWrbzDlZyFHHb1AlKRiD/qqCJIripXKiCSFS8fAOY=",
|
||||||
|
"owner": "zhaofengli",
|
||||||
|
"repo": "nix-github-actions",
|
||||||
|
"rev": "e418aeb728b6aa5ca8c5c71974e7159c2df1d8cf",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "zhaofengli",
|
||||||
|
"ref": "matrix-name",
|
||||||
|
"repo": "nix-github-actions",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixos-wsl": {
|
"nixos-wsl": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1758785683,
|
"lastModified": 1765841014,
|
||||||
"narHash": "sha256-mRn51IeEBXeNh5a6xNLylk4PKBX0s/QQxgkEbYoPq/w=",
|
"narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "NixOS-WSL",
|
"repo": "NixOS-WSL",
|
||||||
"rev": "1bfb978f2f6261b6086e04af17f9418e1fe36d70",
|
"rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -98,11 +263,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1758277210,
|
"lastModified": 1765472234,
|
||||||
"narHash": "sha256-iCGWf/LTy+aY0zFu8q12lK8KuZp7yvdhStehhyX1v8w=",
|
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8eaee110344796db060382e15d3af0a9fc396e0e",
|
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -112,13 +277,13 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1759036355,
|
"lastModified": 1767379071,
|
||||||
"narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=",
|
"narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127",
|
"rev": "fb7944c166a3b630f177938e478f0378e64ce108",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -128,6 +293,22 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767480499,
|
||||||
|
"narHash": "sha256-8IQQUorUGiSmFaPnLSo2+T+rjHtiNWc+OAzeHck7N48=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "30a3c519afcf3f99e2c6df3b359aec5692054d92",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-25.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"plasma-manager": {
|
"plasma-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"home-manager": [
|
"home-manager": [
|
||||||
@@ -138,11 +319,34 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1759157415,
|
"lastModified": 1763909441,
|
||||||
"narHash": "sha256-Fg8cOnVoIe0uQ38UpR6XZzRCwDsjjozVwfevW9yCLI0=",
|
"narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "plasma-manager",
|
"repo": "plasma-manager",
|
||||||
"rev": "df5b3e6da631f732c26c6044c7cccb8706b4f479",
|
"rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "plasma-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plasma-manager-unstable": {
|
||||||
|
"inputs": {
|
||||||
|
"home-manager": [
|
||||||
|
"home-manager-unstable"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs-unstable"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763909441,
|
||||||
|
"narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "plasma-manager",
|
||||||
|
"rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -153,12 +357,48 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
|
"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",
|
||||||
"plasma-manager": "plasma-manager"
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
|
"plasma-manager": "plasma-manager",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
201
flake.nix
201
flake.nix
@@ -2,58 +2,129 @@
|
|||||||
description = "A very basic flake";
|
description = "A very basic flake";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
|
||||||
|
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
nixos-wsl.url = "github:nix-community/NixOS-WSL/main";
|
nixos-wsl.url = "github:nix-community/NixOS-WSL/main";
|
||||||
|
|
||||||
nix-darwin = {
|
nix-darwin = {
|
||||||
url = "github:nix-darwin/nix-darwin";
|
url = "github:nix-darwin/nix-darwin/nix-darwin-25.11";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
url = "github:nix-community/home-manager";
|
url = "github:nix-community/home-manager/release-25.11";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
home-manager-unstable = {
|
||||||
|
url = "github:nix-community/home-manager/master";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
plasma-manager = {
|
plasma-manager = {
|
||||||
url = "github:nix-community/plasma-manager";
|
url = "github:nix-community/plasma-manager";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.home-manager.follows = "home-manager";
|
inputs.home-manager.follows = "home-manager";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
plasma-manager-unstable = {
|
||||||
|
url = "github:nix-community/plasma-manager";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||||
|
inputs.home-manager.follows = "home-manager-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
google-cookie-retrieval = {
|
google-cookie-retrieval = {
|
||||||
url = "git+https://git.johnogle.info/johno/google-cookie-retrieval.git";
|
url = "git+https://git.johnogle.info/johno/google-cookie-retrieval.git";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jovian = {
|
||||||
|
url = "github:Jovian-Experiments/Jovian-NixOS";
|
||||||
|
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, nixos-wsl, ... } @ inputs: let
|
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, ... } @ inputs: let
|
||||||
|
# Shared overlay function to reduce duplication across module sets
|
||||||
|
# Parameters:
|
||||||
|
# unstableOverlays: Additional overlays to apply when importing nixpkgs-unstable
|
||||||
|
mkBaseOverlay = { unstableOverlays ? [] }: (final: prev: {
|
||||||
|
unstable = import nixpkgs-unstable {
|
||||||
|
system = prev.stdenv.hostPlatform.system;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
overlays = unstableOverlays;
|
||||||
|
};
|
||||||
|
custom = prev.callPackage ./packages {};
|
||||||
|
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
|
||||||
|
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
|
||||||
|
});
|
||||||
|
|
||||||
|
# Shared home-manager configuration factory
|
||||||
|
# Parameters:
|
||||||
|
# sharedModules: Additional modules to include in home-manager.sharedModules
|
||||||
|
mkHomeManagerConfig = { sharedModules ? [] }: {
|
||||||
|
home-manager.useGlobalPkgs = true;
|
||||||
|
home-manager.useUserPackages = true;
|
||||||
|
home-manager.sharedModules = sharedModules ++ [
|
||||||
|
inputs.nix-doom-emacs-unstraightened.homeModule
|
||||||
|
];
|
||||||
|
home-manager.extraSpecialArgs = {
|
||||||
|
globalInputs = inputs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
nixosModules = [
|
nixosModules = [
|
||||||
./roles
|
./roles
|
||||||
] ++ [
|
|
||||||
inputs.home-manager.nixosModules.home-manager
|
inputs.home-manager.nixosModules.home-manager
|
||||||
{
|
{
|
||||||
home-manager.useGlobalPkgs = true;
|
nixpkgs.overlays = [ (mkBaseOverlay {}) ];
|
||||||
home-manager.useUserPackages = true;
|
|
||||||
home-manager.sharedModules = [
|
|
||||||
inputs.plasma-manager.homeManagerModules.plasma-manager
|
|
||||||
];
|
|
||||||
home-manager.extraSpecialArgs = {
|
|
||||||
globalInputs = inputs;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
(mkHomeManagerConfig {
|
||||||
|
sharedModules = [ inputs.plasma-manager.homeModules.plasma-manager ];
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Modules for unstable-based systems (like nix-deck)
|
||||||
|
nixosModulesUnstable = [
|
||||||
|
./roles
|
||||||
|
inputs.home-manager-unstable.nixosModules.home-manager
|
||||||
|
inputs.jovian.nixosModules.jovian
|
||||||
|
{
|
||||||
|
nixpkgs.overlays = [ (mkBaseOverlay {}) ];
|
||||||
|
}
|
||||||
|
(mkHomeManagerConfig {
|
||||||
|
sharedModules = [ inputs.plasma-manager-unstable.homeModules.plasma-manager ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
darwinModules = [
|
darwinModules = [
|
||||||
./roles/darwin.nix
|
./roles/darwin.nix
|
||||||
] ++ [
|
|
||||||
inputs.home-manager.darwinModules.home-manager
|
inputs.home-manager.darwinModules.home-manager
|
||||||
{
|
{
|
||||||
home-manager.useGlobalPkgs = true;
|
nixpkgs.overlays = [
|
||||||
home-manager.useUserPackages = true;
|
(mkBaseOverlay {
|
||||||
home-manager.extraSpecialArgs = {
|
# Override claude-code in unstable to use our custom GCS-based build
|
||||||
globalInputs = inputs;
|
# (needed for corporate networks that block npm registry)
|
||||||
};
|
unstableOverlays = [
|
||||||
|
(ufinal: uprev: {
|
||||||
|
claude-code = uprev.callPackage ./packages/claude-code {};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
})
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
(mkHomeManagerConfig { sharedModules = []; })
|
||||||
];
|
];
|
||||||
|
|
||||||
in {
|
in {
|
||||||
@@ -65,7 +136,7 @@
|
|||||||
home-manager.users.johno = {
|
home-manager.users.johno = {
|
||||||
imports = [ ./home/home-laptop-compact.nix ];
|
imports = [ ./home/home-laptop-compact.nix ];
|
||||||
# Machine-specific overrides
|
# Machine-specific overrides
|
||||||
home.i3_sway.extraSwayConfig = {
|
home.roles.i3_sway.extraSwayConfig = {
|
||||||
output.eDP-1.scale = "1.75";
|
output.eDP-1.scale = "1.75";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -78,9 +149,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; };
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -123,6 +194,31 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Steam Deck configuration (using unstable for better Jovian compatibility)
|
||||||
|
nixosConfigurations.nix-deck = nixpkgs-unstable.lib.nixosSystem rec {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = nixosModulesUnstable ++ [
|
||||||
|
./machines/nix-deck/configuration.nix
|
||||||
|
{
|
||||||
|
home-manager.users.johno = import ./home/home-desktop.nix;
|
||||||
|
home-manager.extraSpecialArgs = { inherit system; };
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# ZFS/NFS server configuration
|
||||||
|
nixosConfigurations.john-endesktop = nixpkgs.lib.nixosSystem rec {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = nixosModules ++ [
|
||||||
|
./machines/john-endesktop/configuration.nix
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
{
|
||||||
|
home-manager.users.johno = import ./home/home-server.nix;
|
||||||
|
home-manager.extraSpecialArgs = { inherit system; };
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
# Darwin/macOS configurations
|
# 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";
|
||||||
@@ -134,5 +230,68 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
'';
|
||||||
|
|
||||||
|
bootstrap = pkgs.writeShellScriptBin "bootstrap" ''
|
||||||
|
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
|
||||||
|
${builtins.readFile ./scripts/bootstrap.sh}
|
||||||
|
'';
|
||||||
|
|
||||||
|
build-liveusb = pkgs.writeShellScriptBin "build-liveusb" ''
|
||||||
|
export PATH="${pkgs.lib.makeBinPath commonDeps}:$PATH"
|
||||||
|
${builtins.readFile ./scripts/build-liveusb.sh}
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
update-doomemacs = {
|
||||||
|
type = "app";
|
||||||
|
program = "${update-doomemacs}/bin/update-doomemacs";
|
||||||
|
};
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
bootstrap = {
|
||||||
|
type = "app";
|
||||||
|
program = "${bootstrap}/bin/bootstrap";
|
||||||
|
};
|
||||||
|
build-liveusb = {
|
||||||
|
type = "app";
|
||||||
|
program = "${build-liveusb}/bin/build-liveusb";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
{ config, lib, pkgs, globalInputs, system, ... }:
|
{ config, lib, pkgs, globalInputs, system, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
customPkgs = pkgs.callPackage ../packages {};
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
# Provide arguments to role modules
|
|
||||||
_module.args = { inherit customPkgs; };
|
|
||||||
# Home Manager configuration for Darwin work laptop
|
# Home Manager configuration for Darwin work laptop
|
||||||
# Corporate-friendly setup with essential development tools
|
# Corporate-friendly setup with essential development tools
|
||||||
|
|
||||||
@@ -13,28 +8,86 @@ in
|
|||||||
home.homeDirectory = lib.mkForce "/Users/johno";
|
home.homeDirectory = lib.mkForce "/Users/johno";
|
||||||
home.stateVersion = "24.05";
|
home.stateVersion = "24.05";
|
||||||
|
|
||||||
|
# System packages
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
google-cloud-sdk
|
||||||
|
];
|
||||||
|
|
||||||
|
# Note: ghostty installed via Homebrew (managed outside of nix)
|
||||||
|
|
||||||
# Override Darwin-incompatible settings from base role
|
# Override Darwin-incompatible settings from base role
|
||||||
programs.rbw.settings.pinentry = lib.mkForce pkgs.pinentry_mac;
|
programs.rbw.settings.pinentry = lib.mkForce pkgs.pinentry_mac;
|
||||||
|
|
||||||
programs.bash.initExtra = ''
|
# Disable Home Manager from managing shell RC files
|
||||||
export NODE_EXTRA_CA_CERTS=/opt/homebrew/etc/ca-certificates/cert.pem
|
# topsoil/compost will manage these files instead
|
||||||
export COREPACK_NPM_REGISTRY=https://global.block-artifacts.com/artifactory/api/npm/square-npm/
|
programs.bash.enable = lib.mkForce false;
|
||||||
export COREPACK_INTEGRITY_KEYS=0
|
programs.zsh.enable = lib.mkForce false;
|
||||||
|
|
||||||
export NVM_DIR="$HOME/.nvm"
|
# Create a local nix integration file that topsoil-managed configs can source
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
home.file.".nix-integration.sh" = {
|
||||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
|
text = ''
|
||||||
'';
|
# Source Home Manager session variables (nix paths, environment, etc.)
|
||||||
|
if [ -e /etc/profiles/per-user/johno/etc/profile.d/hm-session-vars.sh ]; then
|
||||||
|
. /etc/profiles/per-user/johno/etc/profile.d/hm-session-vars.sh
|
||||||
|
fi
|
||||||
|
|
||||||
programs.zsh.enable = true;
|
# Setup bash completions from nix profiles
|
||||||
programs.zsh.initContent = ''
|
if [[ ! -v BASH_COMPLETION_VERSINFO ]] && [ -n "$NIX_PROFILES" ]; then
|
||||||
export NODE_EXTRA_CA_CERTS=/opt/homebrew/etc/ca-certificates/cert.pem
|
for profile in $NIX_PROFILES; do
|
||||||
export COREPACK_NPM_REGISTRY=https://global.block-artifacts.com/artifactory/api/npm/square-npm/
|
if [ -f "$profile/etc/profile.d/bash_completion.sh" ]; then
|
||||||
export COREPACK_INTEGRITY_KEYS=0
|
. "$profile/etc/profile.d/bash_completion.sh"
|
||||||
export NVM_DIR="$HOME/.nvm"
|
break
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
|
fi
|
||||||
[ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion" # This loads nvm bash_completion
|
done
|
||||||
'';
|
fi
|
||||||
|
|
||||||
|
# command-not-found handler
|
||||||
|
command_not_found_handle() {
|
||||||
|
local p=/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite
|
||||||
|
if [ -n "$NIX_PROFILES" ]; then
|
||||||
|
for profile in $NIX_PROFILES; do
|
||||||
|
if [ -x "$profile/bin/command-not-found" ] && [ -f "$p" ]; then
|
||||||
|
"$profile/bin/command-not-found" "$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo "$1: command not found" >&2
|
||||||
|
return 127
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
home.file.".nix-integration.zsh" = {
|
||||||
|
text = ''
|
||||||
|
# Source Home Manager session variables (nix paths, environment, etc.)
|
||||||
|
if [ -e /etc/profiles/per-user/johno/etc/profile.d/hm-session-vars.sh ]; then
|
||||||
|
. /etc/profiles/per-user/johno/etc/profile.d/hm-session-vars.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Setup zsh completions from nix profiles
|
||||||
|
typeset -U path cdpath fpath manpath
|
||||||
|
for profile in ''${(z)NIX_PROFILES}; do
|
||||||
|
fpath+=($profile/share/zsh/site-functions $profile/share/zsh/$ZSH_VERSION/functions $profile/share/zsh/vendor-completions)
|
||||||
|
done
|
||||||
|
autoload -U compinit && compinit
|
||||||
|
|
||||||
|
# command-not-found handler
|
||||||
|
command_not_found_handler() {
|
||||||
|
local p=/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite
|
||||||
|
if [ -n "$NIX_PROFILES" ]; then
|
||||||
|
for profile in ''${(z)NIX_PROFILES}; do
|
||||||
|
if [ -x "$profile/bin/command-not-found" ] && [ -f "$p" ]; then
|
||||||
|
"$profile/bin/command-not-found" "$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo "$1: command not found" >&2
|
||||||
|
return 127
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
# Keep SSH and Git disabled to avoid conflicts with work environment
|
# Keep SSH and Git disabled to avoid conflicts with work environment
|
||||||
programs.ssh.enable = lib.mkForce false;
|
programs.ssh.enable = lib.mkForce false;
|
||||||
@@ -45,12 +98,26 @@ in
|
|||||||
|
|
||||||
home.roles = {
|
home.roles = {
|
||||||
base.enable = true;
|
base.enable = true;
|
||||||
|
development = {
|
||||||
|
enable = true;
|
||||||
|
allowArbitraryClaudeCodeModelSelection = true;
|
||||||
|
};
|
||||||
|
tmux.enable = true;
|
||||||
|
emacs.enable = true;
|
||||||
|
aerospace = {
|
||||||
|
enable = true;
|
||||||
|
leader = "cmd";
|
||||||
|
ctrlShortcuts.enable = true;
|
||||||
|
sketchybar.enable = true;
|
||||||
|
# Optional: Add per-machine userSettings overrides
|
||||||
|
# userSettings = {
|
||||||
|
# mode.main.binding."${leader}-custom" = "custom-command";
|
||||||
|
# };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
./roles
|
./roles
|
||||||
./modules/emacs
|
./roles/base-darwin
|
||||||
./modules/kubectl
|
|
||||||
./modules/tmux
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
{ pkgs, globalInputs, system, ... }:
|
{ pkgs, globalInputs, system, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
customPkgs = pkgs.callPackage ../packages {};
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
# Provide arguments to role modules
|
|
||||||
_module.args = { inherit customPkgs; };
|
|
||||||
# Home Manager configuration for full desktop experience
|
# Home Manager configuration for full desktop experience
|
||||||
home.username = "johno";
|
home.username = "johno";
|
||||||
home.homeDirectory = "/home/johno";
|
home.homeDirectory = "/home/johno";
|
||||||
@@ -13,14 +8,22 @@ in
|
|||||||
|
|
||||||
# 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;
|
||||||
communication.enable = true;
|
communication.enable = true;
|
||||||
sync.enable = true;
|
sync.enable = true;
|
||||||
kdeconnect.enable = true;
|
kdeconnect.enable = true;
|
||||||
|
kubectl.enable = true;
|
||||||
|
tmux.enable = true;
|
||||||
|
plasma-manager.enable = true;
|
||||||
|
starship.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
targets.genericLinux.enable = true;
|
targets.genericLinux.enable = true;
|
||||||
@@ -29,10 +32,6 @@ in
|
|||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
./roles
|
./roles
|
||||||
./modules/emacs
|
./roles/base-linux
|
||||||
./modules/i3+sway
|
|
||||||
./modules/kubectl
|
|
||||||
./modules/plasma-manager
|
|
||||||
./modules/tmux
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
29
home/home-kodi.nix
Normal file
29
home/home-kodi.nix
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{ 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
|
||||||
|
./roles/base-linux
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
{ config, lib, pkgs, globalInputs, system, ... }:
|
{ config, lib, pkgs, globalInputs, system, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
customPkgs = pkgs.callPackage ../packages {};
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
# Provide arguments to role modules
|
|
||||||
_module.args = { inherit customPkgs; };
|
|
||||||
# Home Manager configuration for compact laptop setups
|
# Home Manager configuration for compact laptop setups
|
||||||
# Optimized for space-constrained environments
|
# Optimized for space-constrained environments
|
||||||
|
|
||||||
@@ -19,10 +14,24 @@ in
|
|||||||
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;
|
||||||
# office.enable = false; # Excluded for storage constraints
|
kubectl.enable = true;
|
||||||
|
tmux.enable = true;
|
||||||
|
plasma-manager.enable = true;
|
||||||
|
emacs.enable = true;
|
||||||
|
i3_sway.enable = true;
|
||||||
|
starship.enable = true;
|
||||||
|
|
||||||
|
# Launcher wrappers for excluded/optional packages
|
||||||
|
launchers = {
|
||||||
|
enable = true;
|
||||||
|
packages = [
|
||||||
|
"libreoffice"
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
targets.genericLinux.enable = true;
|
targets.genericLinux.enable = true;
|
||||||
@@ -31,11 +40,6 @@ in
|
|||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
./roles
|
./roles
|
||||||
./modules/emacs
|
./roles/base-linux
|
||||||
./modules/i3+sway
|
|
||||||
./modules/kubectl
|
|
||||||
./modules/plasma-manager
|
|
||||||
./modules/tmux
|
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
{ pkgs, globalInputs, system, ... }:
|
{ pkgs, globalInputs, system, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
customPkgs = pkgs.callPackage ../packages {};
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
# Provide arguments to role modules
|
|
||||||
_module.args = { inherit customPkgs; };
|
|
||||||
# Home Manager configuration for live USB environments
|
# Home Manager configuration for live USB environments
|
||||||
# Minimal setup without persistent services
|
# Minimal setup without persistent services
|
||||||
|
|
||||||
@@ -17,8 +12,18 @@ in
|
|||||||
home.roles = {
|
home.roles = {
|
||||||
base.enable = true;
|
base.enable = true;
|
||||||
desktop.enable = true;
|
desktop.enable = true;
|
||||||
|
tmux.enable = true;
|
||||||
|
plasma-manager.enable = true;
|
||||||
|
emacs = {
|
||||||
|
enable = true;
|
||||||
|
# Use pre-built Doom Emacs - all packages built at nix build time
|
||||||
|
# This means no doom sync is needed after booting the live USB
|
||||||
|
prebuiltDoom = true;
|
||||||
|
};
|
||||||
|
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
|
||||||
# media.enable = false; # Not needed for live USB
|
# media.enable = false; # Not needed for live USB
|
||||||
# sync.enable = false; # No persistent sync on live USB
|
# sync.enable = false; # No persistent sync on live USB
|
||||||
@@ -31,11 +36,7 @@ in
|
|||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
./roles
|
./roles
|
||||||
./modules/emacs
|
./roles/base-linux
|
||||||
./modules/i3+sway
|
|
||||||
./modules/kubectl
|
|
||||||
./modules/plasma-manager
|
|
||||||
./modules/tmux
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Live USB specific overrides can go here if needed
|
# Live USB specific overrides can go here if needed
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
{ pkgs, globalInputs, system, ... }:
|
{ pkgs, globalInputs, system, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
customPkgs = pkgs.callPackage ../packages {};
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
# Provide arguments to role modules
|
|
||||||
_module.args = { inherit customPkgs; };
|
|
||||||
# Home Manager configuration for media center setups
|
# Home Manager configuration for media center setups
|
||||||
# Optimized for living room media consumption and gaming
|
# Optimized for living room media consumption and gaming
|
||||||
|
|
||||||
@@ -21,6 +16,11 @@ in
|
|||||||
communication.enable = true;
|
communication.enable = true;
|
||||||
kdeconnect.enable = true;
|
kdeconnect.enable = true;
|
||||||
development.enable = true;
|
development.enable = true;
|
||||||
|
tmux.enable = true;
|
||||||
|
plasma-manager.enable = true;
|
||||||
|
emacs.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
|
||||||
};
|
};
|
||||||
@@ -31,11 +31,7 @@ in
|
|||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
./roles
|
./roles
|
||||||
./modules/emacs
|
./roles/base-linux
|
||||||
./modules/i3+sway
|
|
||||||
./modules/kubectl
|
|
||||||
./modules/plasma-manager
|
|
||||||
./modules/tmux
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Media center specific overrides can go here if needed
|
# Media center specific overrides can go here if needed
|
||||||
|
|||||||
27
home/home-server.nix
Normal file
27
home/home-server.nix
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{ 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;
|
||||||
|
kubectl.enable = true;
|
||||||
|
starship.enable = true;
|
||||||
|
tmux.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
targets.genericLinux.enable = true;
|
||||||
|
home.sessionVariables = {};
|
||||||
|
home.sessionPath = [];
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
./roles
|
||||||
|
./roles/base-linux
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
|
|
||||||
let
|
|
||||||
doomEmacs = pkgs.fetchFromGitHub {
|
|
||||||
owner = "doomemacs";
|
|
||||||
repo = "doomemacs";
|
|
||||||
rev = "8f55404781edacf66fa330205533b002de3fb5ee";
|
|
||||||
sha256 = "sha256-vHwgENjip2+AFzs4oZfnKEAJKwf5Zid7fakImvxxQUw=";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Shared emacs packages
|
|
||||||
emacsPackages = epkgs: [
|
|
||||||
epkgs.vterm
|
|
||||||
epkgs.treesit-grammars.with-all-grammars
|
|
||||||
];
|
|
||||||
|
|
||||||
# Default emacs configuration with vterm support
|
|
||||||
defaultEmacsPackage =
|
|
||||||
if pkgs.stdenv.isDarwin
|
|
||||||
then pkgs.emacs-macport.pkgs.withPackages emacsPackages
|
|
||||||
else pkgs.emacs.pkgs.withPackages emacsPackages;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
home.packages = [
|
|
||||||
pkgs.emacs-all-the-icons-fonts
|
|
||||||
pkgs.fira-code
|
|
||||||
pkgs.fontconfig
|
|
||||||
pkgs.graphviz
|
|
||||||
pkgs.isort
|
|
||||||
#pkgs.libvterm # native vterm library
|
|
||||||
pkgs.nerd-fonts.fira-code
|
|
||||||
pkgs.nerd-fonts.droid-sans-mono
|
|
||||||
pkgs.nil # nix lsp language server
|
|
||||||
pkgs.nixfmt-rfc-style
|
|
||||||
(pkgs.ripgrep.override {withPCRE2 = true;})
|
|
||||||
pkgs.pipenv
|
|
||||||
pkgs.poetry
|
|
||||||
pkgs.python3
|
|
||||||
];
|
|
||||||
|
|
||||||
programs.emacs = {
|
|
||||||
enable = true;
|
|
||||||
package = defaultEmacsPackage;
|
|
||||||
};
|
|
||||||
|
|
||||||
fonts.fontconfig.enable = true;
|
|
||||||
|
|
||||||
# Mount emacs and tree-sitter grammars from nix store
|
|
||||||
home.file = {
|
|
||||||
"${config.xdg.configHome}/emacs".source = doomEmacs;
|
|
||||||
};
|
|
||||||
|
|
||||||
home.sessionPath = [
|
|
||||||
"${config.xdg.configHome}/emacs/bin"
|
|
||||||
];
|
|
||||||
|
|
||||||
home.sessionVariables = {
|
|
||||||
DOOMDIR = "${config.xdg.configHome}/doom";
|
|
||||||
DOOMLOCALDIR = "${config.xdg.dataHome}/doom";
|
|
||||||
};
|
|
||||||
|
|
||||||
# TODO: Use mkOutOfStoreSymlink instead?
|
|
||||||
home.activation.doomConfig = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
|
||||||
# Always remove and recreate the symlink to ensure it points to the source directory
|
|
||||||
rm -rf "${config.xdg.configHome}/doom"
|
|
||||||
ln -sf "${config.home.homeDirectory}/nixos-configs/home/modules/emacs/doom" "${config.xdg.configHome}/doom"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.home.i3_sway;
|
|
||||||
i3_cfg = config.xsession.windowManager.i3.config;
|
|
||||||
|
|
||||||
shared_config = recursiveUpdate rec {
|
|
||||||
modifier = "Mod4";
|
|
||||||
terminal = "kitty";
|
|
||||||
defaultWorkspace = "workspace number 1";
|
|
||||||
|
|
||||||
keybindings = {
|
|
||||||
"${shared_config.modifier}+Return" = "exec ${terminal}";
|
|
||||||
"${shared_config.modifier}+Shift+q" = "kill";
|
|
||||||
"${shared_config.modifier}+d" = "exec ${i3_cfg.menu}";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+h" = "focus left";
|
|
||||||
"${shared_config.modifier}+j" = "focus down";
|
|
||||||
"${shared_config.modifier}+k" = "focus up";
|
|
||||||
"${shared_config.modifier}+l" = "focus right";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+Shift+h" = "move left";
|
|
||||||
"${shared_config.modifier}+Shift+j" = "move down";
|
|
||||||
"${shared_config.modifier}+Shift+k" = "move up";
|
|
||||||
"${shared_config.modifier}+Shift+l" = "move right";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+Left" = "focus left";
|
|
||||||
"${shared_config.modifier}+Down" = "focus down";
|
|
||||||
"${shared_config.modifier}+Up" = "focus up";
|
|
||||||
"${shared_config.modifier}+Right" = "focus right";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+Shift+Left" = "move left";
|
|
||||||
"${shared_config.modifier}+Shift+Down" = "move down";
|
|
||||||
"${shared_config.modifier}+Shift+Up" = "move up";
|
|
||||||
"${shared_config.modifier}+Shift+Right" = "move right";
|
|
||||||
|
|
||||||
#"${shared_config.modifier}+h" = "split h";
|
|
||||||
"${shared_config.modifier}+v" = "split v";
|
|
||||||
"${shared_config.modifier}+f" = "fullscreen toggle";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+s" = "layout stacking";
|
|
||||||
"${shared_config.modifier}+w" = "layout tabbed";
|
|
||||||
"${shared_config.modifier}+e" = "layout toggle split";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+Shift+space" = "floating toggle";
|
|
||||||
"${shared_config.modifier}+space" = "focus mode_toggle";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+a" = "focus parent";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+Shift+minus" = "move scratchpad";
|
|
||||||
"${shared_config.modifier}+minus" = "scratchpad show";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+1" = "workspace number 1";
|
|
||||||
"${shared_config.modifier}+2" = "workspace number 2";
|
|
||||||
"${shared_config.modifier}+3" = "workspace number 3";
|
|
||||||
"${shared_config.modifier}+4" = "workspace number 4";
|
|
||||||
"${shared_config.modifier}+5" = "workspace number 5";
|
|
||||||
"${shared_config.modifier}+6" = "workspace number 6";
|
|
||||||
"${shared_config.modifier}+7" = "workspace number 7";
|
|
||||||
"${shared_config.modifier}+8" = "workspace number 8";
|
|
||||||
"${shared_config.modifier}+9" = "workspace number 9";
|
|
||||||
"${shared_config.modifier}+0" = "workspace number 10";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+Shift+1" =
|
|
||||||
"move container to workspace number 1";
|
|
||||||
"${shared_config.modifier}+Shift+2" =
|
|
||||||
"move container to workspace number 2";
|
|
||||||
"${shared_config.modifier}+Shift+3" =
|
|
||||||
"move container to workspace number 3";
|
|
||||||
"${shared_config.modifier}+Shift+4" =
|
|
||||||
"move container to workspace number 4";
|
|
||||||
"${shared_config.modifier}+Shift+5" =
|
|
||||||
"move container to workspace number 5";
|
|
||||||
"${shared_config.modifier}+Shift+6" =
|
|
||||||
"move container to workspace number 6";
|
|
||||||
"${shared_config.modifier}+Shift+7" =
|
|
||||||
"move container to workspace number 7";
|
|
||||||
"${shared_config.modifier}+Shift+8" =
|
|
||||||
"move container to workspace number 8";
|
|
||||||
"${shared_config.modifier}+Shift+9" =
|
|
||||||
"move container to workspace number 9";
|
|
||||||
"${shared_config.modifier}+Shift+0" =
|
|
||||||
"move container to workspace number 10";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+Shift+c" = "reload";
|
|
||||||
"${shared_config.modifier}+Shift+r" = "restart";
|
|
||||||
|
|
||||||
"${shared_config.modifier}+r" = "mode resize";
|
|
||||||
|
|
||||||
"XF86MonBrightnessUp" = "exec brightnessctl s +5%";
|
|
||||||
"XF86MonBrightnessDown" = "exec brightnessctl s 5%-";
|
|
||||||
};
|
|
||||||
} cfg.extraSharedConfig;
|
|
||||||
in {
|
|
||||||
options.home.i3_sway = {
|
|
||||||
extraSharedConfig = mkOption {
|
|
||||||
default = {};
|
|
||||||
};
|
|
||||||
extraI3Config = mkOption {
|
|
||||||
default = {};
|
|
||||||
};
|
|
||||||
extraSwayConfig = mkOption {
|
|
||||||
default = {};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
xsession.windowManager.i3 = let
|
|
||||||
base_i3_config = recursiveUpdate shared_config {
|
|
||||||
keybindings = {
|
|
||||||
"${shared_config.modifier}+Shift+e" =
|
|
||||||
"exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
enable = true;
|
|
||||||
config = recursiveUpdate base_i3_config cfg.extraI3Config;
|
|
||||||
};
|
|
||||||
|
|
||||||
wayland.windowManager.sway = let
|
|
||||||
base_sway_config = recursiveUpdate shared_config {
|
|
||||||
keybindings = {
|
|
||||||
"${shared_config.modifier}+Shift+e" =
|
|
||||||
"exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
|
|
||||||
};
|
|
||||||
input = {
|
|
||||||
"type:keyboard" = {
|
|
||||||
xkb_options = "caps:escape";
|
|
||||||
};
|
|
||||||
"type:touchpad" = {
|
|
||||||
tap = "enabled";
|
|
||||||
tap_button_map = "lrm";
|
|
||||||
drag = "enabled";
|
|
||||||
natural_scroll = "disabled";
|
|
||||||
dwt = "enabled";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
enable = true;
|
|
||||||
config = recursiveUpdate base_sway_config cfg.extraSwayConfig;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
|
|
||||||
# The current KDE config can be output with the command:
|
|
||||||
# nix run github:nix-community/plasma-manager
|
|
||||||
#
|
|
||||||
# Plasma-manager options documentation
|
|
||||||
# https://nix-community.github.io/plasma-manager/options.xhtml
|
|
||||||
#
|
|
||||||
# TODO: (ambitious) Add Kmail support to plasma-manager
|
|
||||||
{
|
|
||||||
programs.plasma = {
|
|
||||||
enable = true;
|
|
||||||
overrideConfig = true;
|
|
||||||
|
|
||||||
hotkeys.commands."launch-konsole" = {
|
|
||||||
name = "Launch Konsole";
|
|
||||||
key = "Meta+Return";
|
|
||||||
command = "konsole";
|
|
||||||
};
|
|
||||||
|
|
||||||
shortcuts = {
|
|
||||||
kmix = {
|
|
||||||
"decrease_microphone_volume" = "Microphone Volume Down";
|
|
||||||
"decrease_volume" = "Volume Down";
|
|
||||||
"decrease_volume_small" = "Shift+Volume Down";
|
|
||||||
"increase_microphone_volume" = "Microphone Volume Up";
|
|
||||||
"increase_volume" = "Volume Up";
|
|
||||||
"increase_volume_small" = "Shift+Volume Up";
|
|
||||||
"mic_mute" = ["Microphone Mute" "Meta+Volume Mute,Microphone Mute" "Meta+Volume Mute,Mute Microphone"];
|
|
||||||
"mute" = "Volume Mute";
|
|
||||||
};
|
|
||||||
|
|
||||||
mediacontrol = {
|
|
||||||
"mediavolumedown" = "none,,Media volume down";
|
|
||||||
"mediavolumeup" = "none,,Media volume up";
|
|
||||||
"nextmedia" = "Media Next";
|
|
||||||
"pausemedia" = "Media Pause";
|
|
||||||
"playmedia" = "none,,Play media playback";
|
|
||||||
"playpausemedia" = "Media Play";
|
|
||||||
"previousmedia" = "Media Previous";
|
|
||||||
"stopmedia" = "Media Stop";
|
|
||||||
};
|
|
||||||
|
|
||||||
ksmserver = {
|
|
||||||
"Lock Session" = ["Meta+Ctrl+Q" "Screensaver" "Screensaver,Lock Session"];
|
|
||||||
};
|
|
||||||
|
|
||||||
kwin = {
|
|
||||||
"Window Close" = "Meta+Shift+Q";
|
|
||||||
"Kill Window" = "Meta+Ctrl+Esc";
|
|
||||||
"Window Operations Menu" = "Alt+F3";
|
|
||||||
"Window Resize" = "Meta+R,,Resize Window";
|
|
||||||
|
|
||||||
"Overview" = "Meta+Ctrl+W";
|
|
||||||
"Grid View" = "Meta+G";
|
|
||||||
"Edit Tiles" = "Meta+T";
|
|
||||||
|
|
||||||
"Activate Window Demanding Attention" = "Meta+Ctrl+A";
|
|
||||||
|
|
||||||
"Show Desktop" = "Meta+Ctrl+D";
|
|
||||||
|
|
||||||
"Walk Through Windows" = "Alt+Tab";
|
|
||||||
"Walk Through Windows (Reverse)" = "Alt+Shift+Tab";
|
|
||||||
"Walk Through Windows of Current Application" = "Alt+`";
|
|
||||||
"Walk Through Windows of Current Application (Reverse)" = "Alt+~";
|
|
||||||
|
|
||||||
"Window Quick Tile Bottom" = "Meta+Down";
|
|
||||||
"Window Quick Tile Left" = "Meta+Left";
|
|
||||||
"Window Quick Tile Right" = "Meta+Right";
|
|
||||||
"Window Quick Tile Top" = "Meta+Up";
|
|
||||||
|
|
||||||
"Switch to Desktop 1" = "Meta+1";
|
|
||||||
"Switch to Desktop 2" = "Meta+2";
|
|
||||||
"Switch to Desktop 3" = "Meta+3";
|
|
||||||
"Switch to Desktop 4" = "Meta+4";
|
|
||||||
"Switch to Desktop 5" = "Meta+5";
|
|
||||||
"Switch to Desktop 6" = "Meta+6";
|
|
||||||
"Switch to Desktop 7" = "Meta+7";
|
|
||||||
"Switch to Desktop 8" = "Meta+8";
|
|
||||||
"Switch to Desktop 9" = "Meta+9";
|
|
||||||
"Switch to Desktop 10" = "Meta+0";
|
|
||||||
|
|
||||||
"Window to Desktop 1" = "Meta+!"; # Meta+Shift+1
|
|
||||||
"Window to Desktop 2" = "Meta+@"; # Meta+Shift+2
|
|
||||||
"Window to Desktop 3" = "Meta+#"; # Meta+Shift+3
|
|
||||||
"Window to Desktop 4" = "Meta+$"; # Meta+Shift+4
|
|
||||||
"Window to Desktop 5" = "Meta+%"; # Meta+Shift+5
|
|
||||||
"Window to Desktop 6" = "Meta+^"; # Meta+Shift+6
|
|
||||||
"Window to Desktop 7" = "Meta+&"; # Meta+Shift+7
|
|
||||||
"Window to Desktop 8" = "Meta+*"; # Meta+Shift+8
|
|
||||||
"Window to Desktop 9" = "Meta+("; # Meta+Shift+9
|
|
||||||
"Window to Desktop 10" = "Meta+)"; # Meta+Shift+0
|
|
||||||
|
|
||||||
"view_actual_size" = "Meta+Ctrl+=";
|
|
||||||
"view_zoom_in" = ["Meta++" "Meta+=,Meta++" "Meta+=,Zoom In"];
|
|
||||||
"view_zoom_out" = "Meta+-";
|
|
||||||
};
|
|
||||||
"org_kde_powerdevil"."Decrease Keyboard Brightness" = "Keyboard Brightness Down";
|
|
||||||
"org_kde_powerdevil"."Decrease Screen Brightness" = "Monitor Brightness Down";
|
|
||||||
"org_kde_powerdevil"."Decrease Screen Brightness Small" = "Shift+Monitor Brightness Down";
|
|
||||||
"org_kde_powerdevil"."Hibernate" = "Hibernate";
|
|
||||||
"org_kde_powerdevil"."Increase Keyboard Brightness" = "Keyboard Brightness Up";
|
|
||||||
"org_kde_powerdevil"."Increase Screen Brightness" = "Monitor Brightness Up";
|
|
||||||
"org_kde_powerdevil"."Increase Screen Brightness Small" = "Shift+Monitor Brightness Up";
|
|
||||||
"org_kde_powerdevil"."PowerDown" = "Power Down";
|
|
||||||
"org_kde_powerdevil"."PowerOff" = "Power Off";
|
|
||||||
"org_kde_powerdevil"."Sleep" = "Sleep";
|
|
||||||
"org_kde_powerdevil"."Toggle Keyboard Backlight" = "Keyboard Light On/Off";
|
|
||||||
"org_kde_powerdevil"."Turn Off Screen" = [ ];
|
|
||||||
"org_kde_powerdevil"."powerProfile" = ["Battery" "Meta+B,Battery" "Meta+B,Switch Power Profile"];
|
|
||||||
|
|
||||||
plasmashell = {
|
|
||||||
"activate application launcher" = ["Meta" "Alt+F1,Meta" "Alt+F1,Activate Application Launcher"];
|
|
||||||
"activate task manager entry 1" = "none,,";
|
|
||||||
"activate task manager entry 2" = "none,,";
|
|
||||||
"activate task manager entry 3" = "none,,";
|
|
||||||
"activate task manager entry 4" = "none,,";
|
|
||||||
"activate task manager entry 5" = "none,,";
|
|
||||||
"activate task manager entry 6" = "none,,";
|
|
||||||
"activate task manager entry 7" = "none,,";
|
|
||||||
"activate task manager entry 8" = "none,,";
|
|
||||||
"activate task manager entry 9" = "none,,";
|
|
||||||
"activate task manager entry 10" = "none,,";
|
|
||||||
"show activity switcher" = "none,,";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
configFile = {
|
|
||||||
kwinrc.Desktops.Number = {
|
|
||||||
value = 10;
|
|
||||||
immutable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Enable KWin tiling features
|
|
||||||
kwinrc.Tiling = {
|
|
||||||
# Enable tiling functionality
|
|
||||||
"padding" = 4;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Enable krohnkite plugin automatically
|
|
||||||
kwinrc.Plugins = {
|
|
||||||
krohnkiteEnabled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
kwinrc.Effect-overview = {
|
|
||||||
# Configure overview effect for better tiling workflow
|
|
||||||
BorderActivate = 9; # Top-left corner activation
|
|
||||||
};
|
|
||||||
|
|
||||||
kcminputrc.Libinput = {
|
|
||||||
AccelerationProfile = "adaptive";
|
|
||||||
PointerAcceleration = 0.5;
|
|
||||||
};
|
|
||||||
|
|
||||||
kcminputrc.Mouse = {
|
|
||||||
X11LibInputXAccelProfileFlat = false;
|
|
||||||
XLbInptAccelProfileFlat = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
kdeglobals.KDE.LookAndFeelPackage = "org.kde.breezedark.desktop";
|
|
||||||
|
|
||||||
# Focus follows mouse configuration
|
|
||||||
kwinrc.Windows = {
|
|
||||||
FocusPolicy = "FocusFollowsMouse";
|
|
||||||
AutoRaise = true; # Set to true if you want windows to auto-raise on focus
|
|
||||||
AutoRaiseInterval = 750; # Delay in ms before auto-raise (if enabled)
|
|
||||||
DelayFocusInterval = 0; # Delay in ms before focus follows mouse
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
tokyo-night = pkgs.tmuxPlugins.mkTmuxPlugin {
|
|
||||||
pluginName = "tokyo-night";
|
|
||||||
rtpFilePath = "tokyo-night.tmux";
|
|
||||||
version = "1.6.1";
|
|
||||||
src = pkgs.fetchFromGitHub {
|
|
||||||
owner = "janoamaral";
|
|
||||||
repo = "tokyo-night-tmux";
|
|
||||||
rev = "d610ced20d5f602a7995854931440e4a1e0ab780";
|
|
||||||
sha256 = "sha256-17vEgkL7C51p/l5gpT9dkOy0bY9n8l0/LV51mR1k+V8=";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
programs.tmux.enable = true;
|
|
||||||
programs.tmux.terminal = "tmux-direct";
|
|
||||||
programs.tmux.keyMode = "vi";
|
|
||||||
programs.tmux.escapeTime = 0;
|
|
||||||
programs.tmux.mouse = true;
|
|
||||||
programs.tmux.newSession = true;
|
|
||||||
programs.tmux.historyLimit = 50000;
|
|
||||||
programs.tmux.clock24 = true;
|
|
||||||
programs.tmux.baseIndex = 1;
|
|
||||||
programs.tmux.prefix = "M-\\\\";
|
|
||||||
|
|
||||||
programs.tmux.plugins = with pkgs; [
|
|
||||||
tmuxPlugins.cpu
|
|
||||||
tmuxPlugins.battery
|
|
||||||
tmuxPlugins.better-mouse-mode
|
|
||||||
tmuxPlugins.net-speed
|
|
||||||
tmuxPlugins.online-status
|
|
||||||
tmuxPlugins.pain-control
|
|
||||||
tmuxPlugins.tilish
|
|
||||||
tmuxPlugins.yank
|
|
||||||
|
|
||||||
{
|
|
||||||
plugin = tmuxPlugins.resurrect;
|
|
||||||
extraConfig = "set -g @resurrect-strategy-nvim 'session'";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
plugin = tmuxPlugins.continuum;
|
|
||||||
extraConfig = ''
|
|
||||||
set -g @continuum-restore 'on'
|
|
||||||
set -g @continuum-save-interval '15' # minutes
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
|
|
||||||
tokyo-night
|
|
||||||
];
|
|
||||||
}
|
|
||||||
22
home/roles/3d-printing/default.nix
Normal file
22
home/roles/3d-printing/default.nix
Normal 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)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
727
home/roles/aerospace/default.nix
Normal file
727
home/roles/aerospace/default.nix
Normal file
@@ -0,0 +1,727 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.home.roles.aerospace;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.home.roles.aerospace = {
|
||||||
|
enable = mkEnableOption "AeroSpace tiling window manager for macOS";
|
||||||
|
|
||||||
|
leader = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "cmd";
|
||||||
|
description = "Leader key for aerospace shortcuts (e.g., 'cmd', 'ctrl', 'alt')";
|
||||||
|
example = "ctrl";
|
||||||
|
};
|
||||||
|
|
||||||
|
launchd.enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to enable launchd agent for auto-starting aerospace";
|
||||||
|
};
|
||||||
|
|
||||||
|
userSettings = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Additional aerospace configuration settings to merge with defaults.
|
||||||
|
Use this to override or extend the default configuration on a per-machine basis.
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
mode.main.binding."''${leader}-custom" = "custom-command";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
autoraise = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to enable autoraise (auto-focus window on hover)";
|
||||||
|
};
|
||||||
|
|
||||||
|
pollMillis = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 50;
|
||||||
|
description = "Polling interval in milliseconds";
|
||||||
|
};
|
||||||
|
|
||||||
|
delay = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
description = "Delay before raising window";
|
||||||
|
};
|
||||||
|
|
||||||
|
focusDelay = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
description = "Delay before focusing window";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
enableSpansDisplays = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Configure macOS Spaces to span displays (required for aerospace multi-monitor support).
|
||||||
|
Sets com.apple.spaces.spans-displays to true.
|
||||||
|
|
||||||
|
NOTE: This was previously set at the system level in modules/aerospace.nix,
|
||||||
|
but has been moved to home-manager for better modularity.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrlShortcuts = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Remap common macOS Cmd shortcuts to Ctrl equivalents for all operations.
|
||||||
|
This makes macOS behave more like Linux.
|
||||||
|
|
||||||
|
Shortcuts remapped globally:
|
||||||
|
- Ctrl+N: New Window
|
||||||
|
- Ctrl+T: New Tab
|
||||||
|
- Ctrl+W: Close Tab
|
||||||
|
- Ctrl+S: Save / Save As
|
||||||
|
- Ctrl+O: Open
|
||||||
|
- Ctrl+F: Find
|
||||||
|
- Ctrl+H: Find and Replace
|
||||||
|
- Ctrl+P: Print
|
||||||
|
- Ctrl+C/V/X: Copy/Paste/Cut
|
||||||
|
- Ctrl+Z: Undo
|
||||||
|
|
||||||
|
NOTE: Terminal emulators like Ghostty require per-app overrides (configured separately)
|
||||||
|
to preserve Ctrl+C as SIGINT instead of Copy.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sketchybar = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to enable SketchyBar status bar";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# Only apply on Darwin systems
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = pkgs.stdenv.isDarwin;
|
||||||
|
message = "Aerospace role is only supported on macOS (Darwin) systems";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Configure macOS preferences via targets.darwin.defaults
|
||||||
|
targets.darwin.defaults = mkMerge [
|
||||||
|
# Spaces span displays (required for multi-monitor aerospace)
|
||||||
|
(mkIf cfg.enableSpansDisplays {
|
||||||
|
"com.apple.spaces" = {
|
||||||
|
spans-displays = true;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ctrl shortcuts to make macOS behave more like Linux
|
||||||
|
(mkIf cfg.ctrlShortcuts.enable {
|
||||||
|
NSGlobalDomain.NSUserKeyEquivalents = {
|
||||||
|
# Window/Tab operations
|
||||||
|
"New Window" = "^n";
|
||||||
|
"New Tab" = "^t";
|
||||||
|
"Close Tab" = "^w";
|
||||||
|
# File operations
|
||||||
|
"Save" = "^s";
|
||||||
|
"Save As…" = "^$s"; # Ctrl+Shift+S
|
||||||
|
"Open" = "^o";
|
||||||
|
"Open…" = "^o";
|
||||||
|
# Find operations
|
||||||
|
"Find" = "^f";
|
||||||
|
"Find…" = "^f";
|
||||||
|
"Find and Replace" = "^h";
|
||||||
|
"Find and Replace…" = "^h";
|
||||||
|
# Print
|
||||||
|
"Print" = "^p";
|
||||||
|
"Print…" = "^p";
|
||||||
|
# Clipboard operations
|
||||||
|
"Copy" = "^c";
|
||||||
|
"Paste" = "^v";
|
||||||
|
"Cut" = "^x";
|
||||||
|
# Undo/Redo
|
||||||
|
"Undo" = "^z";
|
||||||
|
"Redo" = "^$z"; # Ctrl+Shift+Z
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ghostty-specific overrides to preserve terminal behavior
|
||||||
|
# Remap clipboard operations back to Cmd (macOS default) so Ctrl+C remains SIGINT
|
||||||
|
(mkIf cfg.ctrlShortcuts.enable {
|
||||||
|
"com.mitchellh.ghostty".NSUserKeyEquivalents = {
|
||||||
|
# Remap back to Cmd for clipboard operations
|
||||||
|
"Copy" = "@c"; # Cmd+C
|
||||||
|
"Paste" = "@v"; # Cmd+V
|
||||||
|
"Cut" = "@x"; # Cmd+X
|
||||||
|
"Undo" = "@z"; # Cmd+Z
|
||||||
|
"Redo" = "@$z"; # Cmd+Shift+Z
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
# Install aerospace package and optional tools if enabled
|
||||||
|
home.packages = [ pkgs.aerospace ]
|
||||||
|
++ optionals cfg.autoraise.enable [ pkgs.autoraise ]
|
||||||
|
++ optionals cfg.sketchybar.enable [ pkgs.sketchybar pkgs.sketchybar-app-font ];
|
||||||
|
|
||||||
|
# Enable and configure aerospace
|
||||||
|
programs.aerospace.enable = true;
|
||||||
|
programs.aerospace.launchd.enable = cfg.launchd.enable;
|
||||||
|
programs.aerospace.userSettings = mkMerge [
|
||||||
|
# Default configuration with leader key substitution
|
||||||
|
{
|
||||||
|
# Disable normalizations for i3-like behavior
|
||||||
|
enable-normalization-flatten-containers = false;
|
||||||
|
enable-normalization-opposite-orientation-for-nested-containers = false;
|
||||||
|
|
||||||
|
mode.main.binding = {
|
||||||
|
"${cfg.leader}-w" = "layout accordion horizontal"; # tabbed
|
||||||
|
"${cfg.leader}-s" = "layout accordion vertical"; # stacking
|
||||||
|
"${cfg.leader}-e" = "layout tiles horizontal vertical"; # tiles, toggles orientation
|
||||||
|
"${cfg.leader}-shift-q" = "close";
|
||||||
|
"${cfg.leader}-shift-f" = "fullscreen";
|
||||||
|
"${cfg.leader}-h" = "focus left";
|
||||||
|
"${cfg.leader}-j" = "focus down";
|
||||||
|
"${cfg.leader}-k" = "focus up";
|
||||||
|
"${cfg.leader}-l" = "focus right";
|
||||||
|
"${cfg.leader}-shift-h" = "move left";
|
||||||
|
"${cfg.leader}-shift-j" = "move down";
|
||||||
|
"${cfg.leader}-shift-k" = "move up";
|
||||||
|
"${cfg.leader}-shift-l" = "move right";
|
||||||
|
"${cfg.leader}-r" = "mode resize";
|
||||||
|
"${cfg.leader}-1" = "workspace 1";
|
||||||
|
"${cfg.leader}-2" = "workspace 2";
|
||||||
|
"${cfg.leader}-3" = "workspace 3";
|
||||||
|
"${cfg.leader}-4" = "workspace 4";
|
||||||
|
"${cfg.leader}-5" = "workspace 5";
|
||||||
|
"${cfg.leader}-6" = "workspace 6";
|
||||||
|
"${cfg.leader}-7" = "workspace 7";
|
||||||
|
"${cfg.leader}-8" = "workspace 8";
|
||||||
|
"${cfg.leader}-9" = "workspace 9";
|
||||||
|
"${cfg.leader}-0" = "workspace 10";
|
||||||
|
"${cfg.leader}-shift-1" = "move-node-to-workspace 1";
|
||||||
|
"${cfg.leader}-shift-2" = "move-node-to-workspace 2";
|
||||||
|
"${cfg.leader}-shift-3" = "move-node-to-workspace 3";
|
||||||
|
"${cfg.leader}-shift-4" = "move-node-to-workspace 4";
|
||||||
|
"${cfg.leader}-shift-5" = "move-node-to-workspace 5";
|
||||||
|
"${cfg.leader}-shift-6" = "move-node-to-workspace 6";
|
||||||
|
"${cfg.leader}-shift-7" = "move-node-to-workspace 7";
|
||||||
|
"${cfg.leader}-shift-8" = "move-node-to-workspace 8";
|
||||||
|
"${cfg.leader}-shift-9" = "move-node-to-workspace 9";
|
||||||
|
"${cfg.leader}-shift-0" = "move-node-to-workspace 10";
|
||||||
|
"${cfg.leader}-tab" = "workspace-back-and-forth";
|
||||||
|
"${cfg.leader}-shift-tab" = "move-workspace-to-monitor --wrap-around next";
|
||||||
|
|
||||||
|
"${cfg.leader}-enter" = ''
|
||||||
|
exec-and-forget osascript <<'APPLESCRIPT'
|
||||||
|
tell application "Ghostty"
|
||||||
|
activate
|
||||||
|
tell application "System Events"
|
||||||
|
keystroke "n" using {command down}
|
||||||
|
end tell
|
||||||
|
end tell
|
||||||
|
APPLESCRIPT
|
||||||
|
'';
|
||||||
|
|
||||||
|
"${cfg.leader}-shift-enter" = ''
|
||||||
|
exec-and-forget osascript <<'APPLESCRIPT'
|
||||||
|
tell application "Google Chrome"
|
||||||
|
set newWindow to make new window
|
||||||
|
activate
|
||||||
|
tell newWindow to set index to 1
|
||||||
|
end tell
|
||||||
|
APPLESCRIPT
|
||||||
|
'';
|
||||||
|
|
||||||
|
"${cfg.leader}-shift-e" = "exec-and-forget zsh --login -c \"emacsclient -c -n\"";
|
||||||
|
|
||||||
|
# Service mode: Deliberate aerospace window management
|
||||||
|
"${cfg.leader}-i" = "mode service";
|
||||||
|
|
||||||
|
# Passthrough mode: Temporarily disable aerospace to use macOS shortcuts
|
||||||
|
"${cfg.leader}-p" = "mode passthrough";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Resize mode: For window resizing operations
|
||||||
|
mode.resize.binding = {
|
||||||
|
h = "resize width -50";
|
||||||
|
j = "resize height +50";
|
||||||
|
k = "resize height -50";
|
||||||
|
l = "resize width +50";
|
||||||
|
|
||||||
|
minus = "resize smart -50";
|
||||||
|
equal = "resize smart +50";
|
||||||
|
|
||||||
|
esc = "mode main";
|
||||||
|
enter = "mode main";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Service mode: For deliberate aerospace window management operations
|
||||||
|
mode.service.binding = {
|
||||||
|
esc = ["reload-config" "mode main"];
|
||||||
|
r = ["flatten-workspace-tree" "mode main"]; # reset layout
|
||||||
|
f = ["layout floating tiling" "mode main"]; # Toggle between floating and tiling layout
|
||||||
|
backspace = ["close-all-windows-but-current" "mode main"];
|
||||||
|
|
||||||
|
"${cfg.leader}-shift-h" = ["join-with left" "mode main"];
|
||||||
|
"${cfg.leader}-shift-j" = ["join-with down" "mode main"];
|
||||||
|
"${cfg.leader}-shift-k" = ["join-with up" "mode main"];
|
||||||
|
"${cfg.leader}-shift-l" = ["join-with right" "mode main"];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Passthrough mode: All shortcuts pass through to macOS
|
||||||
|
mode.passthrough.binding = {
|
||||||
|
esc = "mode main";
|
||||||
|
"${cfg.leader}-p" = "mode main";
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar integration - notify bar of workspace changes
|
||||||
|
exec-on-workspace-change = mkIf cfg.sketchybar.enable [
|
||||||
|
"/bin/bash" "-c"
|
||||||
|
"${pkgs.sketchybar}/bin/sketchybar --trigger aerospace_workspace_change FOCUSED=$AEROSPACE_FOCUSED_WORKSPACE PREV=$AEROSPACE_PREV_WORKSPACE"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
# Gaps configuration - prevent windows from overlapping SketchyBar
|
||||||
|
(mkIf cfg.sketchybar.enable {
|
||||||
|
gaps = {
|
||||||
|
outer = {
|
||||||
|
top = 0;
|
||||||
|
bottom = 38;
|
||||||
|
left = 0;
|
||||||
|
right = 0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
cfg.userSettings
|
||||||
|
];
|
||||||
|
|
||||||
|
# Launchd agent for autoraise
|
||||||
|
launchd.agents.autoraise = mkIf cfg.autoraise.enable {
|
||||||
|
enable = true;
|
||||||
|
config = {
|
||||||
|
ProgramArguments = [
|
||||||
|
"${pkgs.autoraise}/bin/AutoRaise"
|
||||||
|
"-pollMillis" (toString cfg.autoraise.pollMillis)
|
||||||
|
"-delay" (toString cfg.autoraise.delay)
|
||||||
|
"-focusDelay" (toString cfg.autoraise.focusDelay)
|
||||||
|
];
|
||||||
|
RunAtLoad = true;
|
||||||
|
KeepAlive = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar configuration
|
||||||
|
home.file.".config/sketchybar/sketchybarrc" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
onChange = "${pkgs.sketchybar}/bin/sketchybar --reload";
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Plugin directory
|
||||||
|
PLUGIN_DIR="$HOME/.config/sketchybar/plugins"
|
||||||
|
|
||||||
|
# Colors - i3/sway theme with exact color matching
|
||||||
|
# Focused window/workspace color from i3/sway
|
||||||
|
FOCUSED=0xff285577
|
||||||
|
|
||||||
|
# Background colors matching i3blocks bar
|
||||||
|
BAR_BG=0xff333333 # Dark gray
|
||||||
|
ITEM_BG=0xff333333 # Dark gray matching bar
|
||||||
|
|
||||||
|
# Text colors
|
||||||
|
TEXT=0xffffffff # White text
|
||||||
|
GRAY=0xff888888 # Muted text for inactive items
|
||||||
|
|
||||||
|
# Accent colors for warnings
|
||||||
|
WARNING=0xffff9900
|
||||||
|
CRITICAL=0xff900000
|
||||||
|
|
||||||
|
# Configure the bar appearance
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --bar \
|
||||||
|
position=bottom \
|
||||||
|
height=30 \
|
||||||
|
color=$BAR_BG \
|
||||||
|
border_width=0 \
|
||||||
|
corner_radius=0 \
|
||||||
|
padding_left=10 \
|
||||||
|
padding_right=10 \
|
||||||
|
shadow=off \
|
||||||
|
topmost=on \
|
||||||
|
sticky=on
|
||||||
|
|
||||||
|
# Set default properties for all items
|
||||||
|
# Using monospace font to match waybar's Fira Code styling
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --default \
|
||||||
|
updates=when_shown \
|
||||||
|
icon.font="Fira Code:Regular:13.0" \
|
||||||
|
icon.color=$TEXT \
|
||||||
|
icon.padding_left=4 \
|
||||||
|
icon.padding_right=4 \
|
||||||
|
label.font="Fira Code:Regular:13.0" \
|
||||||
|
label.color=$TEXT \
|
||||||
|
label.padding_left=4 \
|
||||||
|
label.padding_right=4 \
|
||||||
|
padding_left=4 \
|
||||||
|
padding_right=4 \
|
||||||
|
background.corner_radius=0 \
|
||||||
|
background.height=30
|
||||||
|
|
||||||
|
# Register aerospace workspace change event
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add event aerospace_workspace_change
|
||||||
|
|
||||||
|
# Create workspace indicators for workspaces 1-10
|
||||||
|
for sid in 1 2 3 4 5 6 7 8 9 10; do
|
||||||
|
# Display "0" for workspace 10
|
||||||
|
if [ "$sid" = "10" ]; then
|
||||||
|
display="0"
|
||||||
|
else
|
||||||
|
display="$sid"
|
||||||
|
fi
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add item space.$sid left \
|
||||||
|
--subscribe space.$sid aerospace_workspace_change \
|
||||||
|
--set space.$sid \
|
||||||
|
drawing=on \
|
||||||
|
update_freq=2 \
|
||||||
|
width=32 \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.corner_radius=0 \
|
||||||
|
background.height=30 \
|
||||||
|
background.drawing=on \
|
||||||
|
icon="$display" \
|
||||||
|
icon.padding_left=13 \
|
||||||
|
icon.padding_right=11 \
|
||||||
|
icon.align=center \
|
||||||
|
label.drawing=off \
|
||||||
|
click_script="${pkgs.aerospace}/bin/aerospace workspace $sid" \
|
||||||
|
script="$PLUGIN_DIR/aerospace.sh $sid"
|
||||||
|
done
|
||||||
|
|
||||||
|
# System monitoring modules (right side)
|
||||||
|
# Note: Items added to 'right' appear in reverse order (last added = leftmost)
|
||||||
|
# Adding in reverse to get: disk | cpu | memory | battery | volume | calendar
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add item calendar right \
|
||||||
|
--set calendar \
|
||||||
|
icon="📅" \
|
||||||
|
update_freq=30 \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.drawing=on \
|
||||||
|
script="$PLUGIN_DIR/calendar.sh"
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add item volume right \
|
||||||
|
--set volume \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.drawing=on \
|
||||||
|
script="$PLUGIN_DIR/volume.sh" \
|
||||||
|
--subscribe volume volume_change
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add item battery right \
|
||||||
|
--set battery \
|
||||||
|
update_freq=120 \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.drawing=on \
|
||||||
|
script="$PLUGIN_DIR/battery.sh" \
|
||||||
|
--subscribe battery system_woke power_source_change
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add item memory right \
|
||||||
|
--set memory \
|
||||||
|
update_freq=5 \
|
||||||
|
icon="🐏" \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.drawing=on \
|
||||||
|
script="$PLUGIN_DIR/memory.sh"
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add item cpu right \
|
||||||
|
--set cpu \
|
||||||
|
update_freq=2 \
|
||||||
|
icon="🧠" \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.drawing=on \
|
||||||
|
script="$PLUGIN_DIR/cpu.sh"
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add item disk right \
|
||||||
|
--set disk \
|
||||||
|
update_freq=60 \
|
||||||
|
icon="💾" \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.drawing=on \
|
||||||
|
script="$PLUGIN_DIR/disk.sh"
|
||||||
|
|
||||||
|
# Menu bar extras / system tray items (rightmost)
|
||||||
|
# Note: Requires Screen Recording permission for SketchyBar in System Settings
|
||||||
|
# Use 'sketchybar --query default_menu_items' to discover available items
|
||||||
|
|
||||||
|
# Bluetooth
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add alias "Control Center,Bluetooth" right \
|
||||||
|
--set "Control Center,Bluetooth" \
|
||||||
|
alias.update_freq=1 \
|
||||||
|
padding_left=0 \
|
||||||
|
padding_right=0
|
||||||
|
|
||||||
|
# WiFi
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --add alias "Control Center,WiFi" right \
|
||||||
|
--set "Control Center,WiFi" \
|
||||||
|
alias.update_freq=1 \
|
||||||
|
padding_left=0 \
|
||||||
|
padding_right=0
|
||||||
|
|
||||||
|
# Add other menu bar apps as discovered
|
||||||
|
# Common examples:
|
||||||
|
# - Cloudflare WARP: --add alias "Cloudflare WARP,Item-0" right
|
||||||
|
# - Notion Calendar: --add alias "Notion Calendar,Item-0" right
|
||||||
|
# Run 'sketchybar --query default_menu_items' to find exact names
|
||||||
|
|
||||||
|
# Update the bar
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --update
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar aerospace workspace plugin
|
||||||
|
home.file.".config/sketchybar/plugins/aerospace.sh" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
FOCUSED_COLOR=0xff285577
|
||||||
|
ITEM_BG=0xff333333
|
||||||
|
TEXT=0xffffffff
|
||||||
|
GRAY=0xff555555
|
||||||
|
|
||||||
|
# Get the currently focused workspace directly from aerospace
|
||||||
|
# Trim whitespace to ensure clean comparison
|
||||||
|
FOCUSED=$(${pkgs.aerospace}/bin/aerospace list-workspaces --focused | tr -d ' \n\r')
|
||||||
|
|
||||||
|
# Get list of empty workspaces
|
||||||
|
EMPTY_WORKSPACES=$(${pkgs.aerospace}/bin/aerospace list-workspaces --monitor all --empty)
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
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)
|
||||||
|
IS_EMPTY=false
|
||||||
|
if echo "$EMPTY_WORKSPACES" | grep -q "^$WORKSPACE_NUM$"; then
|
||||||
|
IS_EMPTY=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this workspace is focused
|
||||||
|
IS_FOCUSED=false
|
||||||
|
if [ "$WORKSPACE_NUM" = "$FOCUSED" ]; then
|
||||||
|
IS_FOCUSED=true
|
||||||
|
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
|
||||||
|
# Always show focused workspace (even if empty) with fixed width
|
||||||
|
# Hide non-focused empty workspaces by setting width to 0 (collapsed)
|
||||||
|
# Show non-focused non-empty workspaces with fixed width and inactive styling
|
||||||
|
|
||||||
|
if [ "$IS_FOCUSED" = "true" ]; then
|
||||||
|
# Focused workspace - always show with focused styling and bold font
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \
|
||||||
|
drawing=on \
|
||||||
|
icon="$DISPLAY" \
|
||||||
|
width=32 \
|
||||||
|
icon.padding_left=13 \
|
||||||
|
icon.padding_right=11 \
|
||||||
|
icon.align=center \
|
||||||
|
background.color=$FOCUSED_COLOR \
|
||||||
|
background.drawing=on \
|
||||||
|
icon.color=$TEXT \
|
||||||
|
icon.font="Fira Code:Bold:13.0"
|
||||||
|
elif [ "$IS_EMPTY" = "true" ]; then
|
||||||
|
# Empty workspace (not focused) - hide by collapsing width and clearing content
|
||||||
|
# Using width=0 with drawing=on so updates=when_shown continues to run the script
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --set space.$WORKSPACE_NUM \
|
||||||
|
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 \
|
||||||
|
icon.padding_left=13 \
|
||||||
|
icon.padding_right=11 \
|
||||||
|
icon.align=center \
|
||||||
|
background.color=$ITEM_BG \
|
||||||
|
background.drawing=on \
|
||||||
|
icon.color=$TEXT \
|
||||||
|
icon.font="Fira Code:Regular:13.0"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar CPU monitoring plugin
|
||||||
|
home.file.".config/sketchybar/plugins/cpu.sh" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
CORE_COUNT=$(sysctl -n machdep.cpu.thread_count)
|
||||||
|
CPU_INFO=$(ps -eo pcpu,user)
|
||||||
|
CPU_SYS=$(echo "$CPU_INFO" | grep -v $(whoami) | sed "s/[^ 0-9\.]//g" | awk "{sum+=\$1} END {print sum/(100.0 * $CORE_COUNT)}")
|
||||||
|
CPU_USER=$(echo "$CPU_INFO" | grep $(whoami) | sed "s/[^ 0-9\.]//g" | awk "{sum+=\$1} END {print sum/(100.0 * $CORE_COUNT)}")
|
||||||
|
CPU_PERCENT="$(echo "$CPU_SYS $CPU_USER" | awk '{printf "%.0f\n", ($1 + $2)*100}')"
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --set $NAME label="$CPU_PERCENT%"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar memory monitoring plugin
|
||||||
|
# Shows actual memory pressure (excludes file cache/inactive pages)
|
||||||
|
home.file.".config/sketchybar/plugins/memory.sh" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Use awk for all arithmetic to avoid bash integer overflow on large RAM systems
|
||||||
|
# Memory pressure = Anonymous (app memory) + Wired + Compressor RAM
|
||||||
|
# - Anonymous pages: app-allocated memory (heap, stack) - matches Activity Monitor's "App Memory"
|
||||||
|
# - Wired: kernel/system memory that can't be paged out
|
||||||
|
# - Pages occupied by compressor: actual RAM used by compressor (NOT "stored in compressor")
|
||||||
|
TOTAL_RAM=$(sysctl -n hw.memsize)
|
||||||
|
MEMORY_PERCENT=$(vm_stat | awk -v total_ram="$TOTAL_RAM" '
|
||||||
|
/page size of/ { page_size = $8 }
|
||||||
|
/Anonymous pages/ { anon = $3 + 0 }
|
||||||
|
/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%"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar disk monitoring plugin
|
||||||
|
home.file.".config/sketchybar/plugins/disk.sh" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DISK_USAGE=$(df -H / | grep -v Filesystem | awk '{print $5}')
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --set $NAME label="$DISK_USAGE"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar battery monitoring plugin
|
||||||
|
home.file.".config/sketchybar/plugins/battery.sh" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PERCENTAGE=$(pmset -g batt | grep -Eo "\d+%" | cut -d% -f1)
|
||||||
|
CHARGING=$(pmset -g batt | grep 'AC Power')
|
||||||
|
|
||||||
|
if [ "$PERCENTAGE" = "" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Select icon based on battery level
|
||||||
|
case ''${PERCENTAGE} in
|
||||||
|
9[0-9]|100) ICON="🔋"
|
||||||
|
;;
|
||||||
|
[6-8][0-9]) ICON="🔋"
|
||||||
|
;;
|
||||||
|
[3-5][0-9]) ICON="🔋"
|
||||||
|
;;
|
||||||
|
[1-2][0-9]) ICON="🔋"
|
||||||
|
;;
|
||||||
|
*) ICON="🪫"
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Show charging icon if connected to power
|
||||||
|
if [[ $CHARGING != "" ]]; then
|
||||||
|
ICON="⚡"
|
||||||
|
fi
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --set $NAME icon="$ICON" label="''${PERCENTAGE}%"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar volume monitoring plugin
|
||||||
|
home.file.".config/sketchybar/plugins/volume.sh" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "$SENDER" = "volume_change" ]; then
|
||||||
|
VOLUME=$(osascript -e "output volume of (get volume settings)")
|
||||||
|
MUTED=$(osascript -e "output muted of (get volume settings)")
|
||||||
|
|
||||||
|
if [ "$MUTED" = "true" ]; then
|
||||||
|
ICON="🔇"
|
||||||
|
LABEL=""
|
||||||
|
else
|
||||||
|
case $VOLUME in
|
||||||
|
[6-9][0-9]|100) ICON="🔊"
|
||||||
|
;;
|
||||||
|
[3-5][0-9]) ICON="🔉"
|
||||||
|
;;
|
||||||
|
*) ICON="🔈"
|
||||||
|
esac
|
||||||
|
LABEL="$VOLUME%"
|
||||||
|
fi
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --set $NAME icon="$ICON" label="$LABEL"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# SketchyBar calendar/clock plugin
|
||||||
|
home.file.".config/sketchybar/plugins/calendar.sh" = mkIf cfg.sketchybar.enable {
|
||||||
|
executable = true;
|
||||||
|
text = ''
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
${pkgs.sketchybar}/bin/sketchybar --set $NAME label="$(date '+%Y-%m-%d %H:%M')"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Launchd agent for auto-starting sketchybar
|
||||||
|
launchd.agents.sketchybar = mkIf cfg.sketchybar.enable {
|
||||||
|
enable = true;
|
||||||
|
config = {
|
||||||
|
ProgramArguments = [ "${pkgs.sketchybar}/bin/sketchybar" ];
|
||||||
|
RunAtLoad = true;
|
||||||
|
KeepAlive = true;
|
||||||
|
StandardOutPath = "/tmp/sketchybar.log";
|
||||||
|
StandardErrorPath = "/tmp/sketchybar.err.log";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
11
home/roles/base-darwin/default.nix
Normal file
11
home/roles/base-darwin/default.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
# Base imports for Darwin home configurations
|
||||||
|
# Includes Darwin-specific roles that only work on macOS
|
||||||
|
imports = [
|
||||||
|
../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";
|
||||||
|
}
|
||||||
9
home/roles/base-linux/default.nix
Normal file
9
home/roles/base-linux/default.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
# Base imports for Linux home configurations
|
||||||
|
# Includes Linux-specific roles that require Linux-only home-manager modules
|
||||||
|
imports = [
|
||||||
|
../plasma-manager
|
||||||
|
../plasma-manager-kodi
|
||||||
|
../i3+sway
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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,11 +69,14 @@ in
|
|||||||
|
|
||||||
programs.ssh = {
|
programs.ssh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
enableDefaultConfig = false;
|
||||||
matchBlocks = {
|
matchBlocks = {
|
||||||
|
"*" = {
|
||||||
|
addKeysToAgent = "yes";
|
||||||
|
};
|
||||||
"nucdeb1" = {
|
"nucdeb1" = {
|
||||||
hostname = "nucdeb1.oglehome";
|
hostname = "nucdeb1.oglehome";
|
||||||
user = "root";
|
user = "root";
|
||||||
addKeysToAgent = "yes";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ in
|
|||||||
home.packages = [
|
home.packages = [
|
||||||
# Communication apps
|
# Communication apps
|
||||||
pkgs.element-desktop
|
pkgs.element-desktop
|
||||||
|
# Re-enabled in 25.11 after security issues were resolved
|
||||||
pkgs.fluffychat
|
pkgs.fluffychat
|
||||||
pkgs.nextcloud-talk-desktop
|
pkgs.nextcloud-talk-desktop
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
{
|
{
|
||||||
|
# Shared roles that work across all platforms (Linux, Darwin, etc.)
|
||||||
|
# Platform-specific roles are imported via base-linux or base-darwin
|
||||||
|
# in each home configuration file
|
||||||
imports = [
|
imports = [
|
||||||
|
./3d-printing
|
||||||
./base
|
./base
|
||||||
./communication
|
./communication
|
||||||
./desktop
|
./desktop
|
||||||
./development
|
./development
|
||||||
|
./email
|
||||||
./gaming
|
./gaming
|
||||||
./kdeconnect
|
./kdeconnect
|
||||||
|
./kubectl
|
||||||
|
./launchers
|
||||||
./media
|
./media
|
||||||
./office
|
./office
|
||||||
./sync
|
./sync
|
||||||
|
./tmux
|
||||||
|
./emacs
|
||||||
|
./starship
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,18 @@ in
|
|||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
# Desktop applications
|
# Desktop applications
|
||||||
bitwarden
|
bitwarden-desktop
|
||||||
dunst
|
dunst
|
||||||
keepassxc
|
keepassxc
|
||||||
kitty
|
unstable.ghostty
|
||||||
|
|
||||||
# Desktop utilities
|
# Desktop utilities
|
||||||
|
feh # Image viewer and wallpaper setter for X11
|
||||||
|
rofi # Application launcher for X11
|
||||||
solaar # Logitech management software
|
solaar # Logitech management software
|
||||||
waybar
|
waybar
|
||||||
wofi
|
wofi # Application launcher for Wayland
|
||||||
|
xdg-utils # XDG utilities for opening files/URLs with default applications
|
||||||
|
|
||||||
# System utilities with GUI components
|
# System utilities with GUI components
|
||||||
(snapcast.override { pulseaudioSupport = true; })
|
(snapcast.override { pulseaudioSupport = true; })
|
||||||
@@ -37,6 +40,14 @@ in
|
|||||||
kdePackages.kaddressbook
|
kdePackages.kaddressbook
|
||||||
kdePackages.kontact
|
kdePackages.kontact
|
||||||
|
|
||||||
|
# KDE System components needed for proper integration
|
||||||
|
kdePackages.kded
|
||||||
|
kdePackages.systemsettings
|
||||||
|
kdePackages.kmenuedit
|
||||||
|
|
||||||
|
# Desktop menu support
|
||||||
|
kdePackages.plasma-desktop # Contains applications.menu
|
||||||
|
|
||||||
# KDE Online Accounts support
|
# KDE Online Accounts support
|
||||||
kdePackages.kaccounts-integration
|
kdePackages.kaccounts-integration
|
||||||
kdePackages.kaccounts-providers
|
kdePackages.kaccounts-providers
|
||||||
@@ -70,8 +81,119 @@ in
|
|||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
xdg.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
|
||||||
|
home.sessionVariables = {
|
||||||
|
QT_QPA_PLATFORMTHEME = "kde";
|
||||||
|
KDE_SESSION_VERSION = "6";
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
# Ensure desktop files are made available for discovery
|
||||||
|
desktopEntries = {}; # This creates the desktop files directory structure
|
||||||
|
|
||||||
|
mimeApps = {
|
||||||
|
enable = true;
|
||||||
|
associations.added = {
|
||||||
|
# Ensure associations are properly registered
|
||||||
|
"text/html" = "firefox.desktop";
|
||||||
|
"x-scheme-handler/http" = "firefox.desktop";
|
||||||
|
"x-scheme-handler/https" = "firefox.desktop";
|
||||||
|
};
|
||||||
|
defaultApplications = {
|
||||||
|
# Web browsers
|
||||||
|
"text/html" = "firefox.desktop";
|
||||||
|
"x-scheme-handler/http" = "firefox.desktop";
|
||||||
|
"x-scheme-handler/https" = "firefox.desktop";
|
||||||
|
"x-scheme-handler/about" = "firefox.desktop";
|
||||||
|
"x-scheme-handler/unknown" = "firefox.desktop";
|
||||||
|
|
||||||
|
# Documents
|
||||||
|
"application/pdf" = "okular.desktop";
|
||||||
|
"text/plain" = "kate.desktop";
|
||||||
|
"text/x-tex" = "kate.desktop";
|
||||||
|
"text/x-c" = "kate.desktop";
|
||||||
|
"text/x-python" = "kate.desktop";
|
||||||
|
"application/x-shellscript" = "kate.desktop";
|
||||||
|
|
||||||
|
# Images
|
||||||
|
"image/png" = "gwenview.desktop";
|
||||||
|
"image/jpeg" = "gwenview.desktop";
|
||||||
|
"image/jpg" = "gwenview.desktop";
|
||||||
|
"image/gif" = "gwenview.desktop";
|
||||||
|
"image/bmp" = "gwenview.desktop";
|
||||||
|
"image/tiff" = "gwenview.desktop";
|
||||||
|
"image/webp" = "gwenview.desktop";
|
||||||
|
|
||||||
|
# Archives
|
||||||
|
"application/zip" = "ark.desktop";
|
||||||
|
"application/x-tar" = "ark.desktop";
|
||||||
|
"application/x-compressed-tar" = "ark.desktop";
|
||||||
|
"application/x-7z-compressed" = "ark.desktop";
|
||||||
|
"application/x-rar" = "ark.desktop";
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
"audio/mpeg" = "elisa.desktop";
|
||||||
|
"audio/mp4" = "elisa.desktop";
|
||||||
|
"audio/flac" = "elisa.desktop";
|
||||||
|
"audio/ogg" = "elisa.desktop";
|
||||||
|
"audio/wav" = "elisa.desktop";
|
||||||
|
|
||||||
|
# Email
|
||||||
|
"message/rfc822" = "kmail.desktop";
|
||||||
|
"x-scheme-handler/mailto" = "kmail.desktop";
|
||||||
|
|
||||||
|
# Calendar
|
||||||
|
"text/calendar" = "korganizer.desktop";
|
||||||
|
"application/x-vnd.akonadi.calendar.event" = "korganizer.desktop";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Fix for KDE applications.menu file issue on Plasma 6
|
||||||
|
# KDE still looks for applications.menu but Plasma 6 renamed it to plasma-applications.menu
|
||||||
|
xdg.configFile."menus/applications.menu".source = "${pkgs.kdePackages.plasma-workspace}/etc/xdg/menus/plasma-applications.menu";
|
||||||
|
|
||||||
# Note: modules must be imported at top-level home config
|
# Note: modules must be imported at top-level home config
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,119 @@
|
|||||||
{ config, lib, pkgs, customPkgs, globalInputs, system, ... }:
|
{ config, lib, pkgs, globalInputs, system, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.home.roles.development;
|
cfg = config.home.roles.development;
|
||||||
|
|
||||||
|
# Fetch the claude-plugins repository (for humanlayer commands/agents)
|
||||||
|
# Update the rev to get newer versions of the commands
|
||||||
|
claudePluginsRepo = builtins.fetchGit {
|
||||||
|
url = "https://github.com/jeffh/claude-plugins.git";
|
||||||
|
# To update: change this to the latest commit hash
|
||||||
|
# You can find the latest commit at: https://github.com/jeffh/claude-plugins/commits/main
|
||||||
|
rev = "5e3e4d937162185b6d78c62022cbfd1c8ad42c4c";
|
||||||
|
ref = "main";
|
||||||
|
};
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.home.roles.development = {
|
options.home.roles.development = {
|
||||||
enable = mkEnableOption "Enable development tools and utilities";
|
enable = mkEnableOption "Enable development tools and utilities";
|
||||||
|
|
||||||
|
allowArbitraryClaudeCodeModelSelection = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to preserve model specifications in Claude Code humanlayer commands and agents.
|
||||||
|
|
||||||
|
When false (default), the model: line is stripped from frontmatter, allowing Claude Code
|
||||||
|
to use its default model selection.
|
||||||
|
|
||||||
|
When true, the model: specifications from the source files are preserved, allowing
|
||||||
|
commands to specify opus/sonnet/haiku explicitly.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
home.packages = [
|
home.packages = [
|
||||||
pkgs.claude-code
|
globalInputs.beads.packages.${system}.default
|
||||||
pkgs.codex
|
pkgs.unstable.claude-code
|
||||||
pkgs.goose-cli
|
pkgs.unstable.claude-code-router
|
||||||
|
pkgs.unstable.codex
|
||||||
|
|
||||||
# Custom packages
|
# Custom packages
|
||||||
customPkgs.tea-rbw
|
pkgs.custom.tea-rbw
|
||||||
];
|
];
|
||||||
|
|
||||||
programs.kubectl-secure.enable = true;
|
# Install Claude Code humanlayer command and agent plugins
|
||||||
|
home.activation.claudeCodeCommands = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
|
# Clean up old plugin-installed commands and agents to avoid duplicates
|
||||||
|
rm -f ~/.claude/commands/humanlayer:* 2>/dev/null || true
|
||||||
|
rm -f ~/.claude/agents/humanlayer:* 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create directories if they don't exist
|
||||||
|
mkdir -p ~/.claude/commands
|
||||||
|
mkdir -p ~/.claude/agents
|
||||||
|
|
||||||
|
# Copy all humanlayer command files and remove model specifications
|
||||||
|
for file in ${claudePluginsRepo}/humanlayer/commands/*.md; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
filename=$(basename "$file" .md)
|
||||||
|
dest="$HOME/.claude/commands/humanlayer:''${filename}.md"
|
||||||
|
|
||||||
|
# Copy file and conditionally remove the "model:" line from frontmatter
|
||||||
|
${if cfg.allowArbitraryClaudeCodeModelSelection
|
||||||
|
then "cp \"$file\" \"$dest\""
|
||||||
|
else "${pkgs.gnused}/bin/sed '/^model:/d' \"$file\" > \"$dest\""
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Copy all humanlayer agent files and remove model specifications
|
||||||
|
for file in ${claudePluginsRepo}/humanlayer/agents/*.md; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
filename=$(basename "$file" .md)
|
||||||
|
dest="$HOME/.claude/agents/humanlayer:''${filename}.md"
|
||||||
|
|
||||||
|
# Copy file and conditionally remove the "model:" line from frontmatter
|
||||||
|
${if cfg.allowArbitraryClaudeCodeModelSelection
|
||||||
|
then "cp \"$file\" \"$dest\""
|
||||||
|
else "${pkgs.gnused}/bin/sed '/^model:/d' \"$file\" > \"$dest\""
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
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${
|
||||||
|
if cfg.allowArbitraryClaudeCodeModelSelection
|
||||||
|
then " (model specifications preserved)"
|
||||||
|
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
|
||||||
};
|
};
|
||||||
|
|||||||
247
home/roles/development/skills/beads_implement.md
Normal file
247
home/roles/development/skills/beads_implement.md
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
---
|
||||||
|
description: Implement a plan from thoughts/ for a bead issue
|
||||||
|
---
|
||||||
|
|
||||||
|
# Beads Implement
|
||||||
|
|
||||||
|
You are tasked with implementing an approved plan for a bead issue. Plans are stored in `thoughts/beads-{id}/plan.md`.
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
When this command is invoked:
|
||||||
|
|
||||||
|
1. **Parse the input for bead ID**:
|
||||||
|
- If a bead ID is provided, use it
|
||||||
|
- If no bead ID, check for beads with plans:
|
||||||
|
```bash
|
||||||
|
bd list --status=in_progress
|
||||||
|
```
|
||||||
|
Then check which have plans in `thoughts/beads-{id}/plan.md`
|
||||||
|
|
||||||
|
2. **Load bead context**:
|
||||||
|
```bash
|
||||||
|
bd show {bead-id}
|
||||||
|
```
|
||||||
|
Note the bead **type** (bug, feature, task) from the output.
|
||||||
|
|
||||||
|
3. **Check for plan and handle by type**:
|
||||||
|
|
||||||
|
Check if plan exists:
|
||||||
|
```bash
|
||||||
|
ls thoughts/beads-{bead-id}/plan.md 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
**If plan exists**: Proceed normally (skip to step 4)
|
||||||
|
|
||||||
|
**If no plan**:
|
||||||
|
- **type=bug**: Proceed without plan (simple bugs can implement directly)
|
||||||
|
- **type=feature or type=task**: Warn and ask:
|
||||||
|
```
|
||||||
|
No plan found for this {type}.
|
||||||
|
|
||||||
|
Plans help ensure complex work is well-designed and verifiable.
|
||||||
|
Location expected: thoughts/beads-{bead-id}/plan.md
|
||||||
|
|
||||||
|
Options:
|
||||||
|
1. Create a plan first (recommended) - Run /beads_plan {bead-id}
|
||||||
|
2. Proceed without a plan (for simple changes)
|
||||||
|
|
||||||
|
How would you like to proceed?
|
||||||
|
```
|
||||||
|
Wait for user response before continuing.
|
||||||
|
|
||||||
|
4. **Load plan and research context** (if plan exists):
|
||||||
|
- Read `thoughts/beads-{bead-id}/plan.md` FULLY
|
||||||
|
- Check for any existing checkmarks (- [x]) indicating partial progress
|
||||||
|
- Read any research at `thoughts/beads-{bead-id}/research.md`
|
||||||
|
|
||||||
|
5. **Mark bead in progress** (if not already):
|
||||||
|
```bash
|
||||||
|
bd update {bead-id} --status=in_progress
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Respond with**:
|
||||||
|
```
|
||||||
|
Implementing plan for bead {bead-id}: {bead-title}
|
||||||
|
|
||||||
|
Plan location: thoughts/beads-{bead-id}/plan.md
|
||||||
|
{If partial progress: "Resuming from Phase X - previous phases completed."}
|
||||||
|
|
||||||
|
I'll implement each phase and verify success criteria before proceeding.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Process
|
||||||
|
|
||||||
|
### Step 1: Understand the Plan
|
||||||
|
|
||||||
|
1. **Read the plan completely**
|
||||||
|
2. **Check for existing progress** (checkmarked items)
|
||||||
|
3. **Read all files mentioned in the plan**
|
||||||
|
4. **Create a TodoWrite list** tracking each phase
|
||||||
|
|
||||||
|
### Step 2: Implement Each Phase
|
||||||
|
|
||||||
|
For each phase in the plan:
|
||||||
|
|
||||||
|
1. **Announce the phase**:
|
||||||
|
```
|
||||||
|
## Starting Phase {N}: {Phase Name}
|
||||||
|
|
||||||
|
This phase will: {overview from plan}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Make the changes**:
|
||||||
|
- Follow the plan's specific instructions
|
||||||
|
- Use Edit tool for modifications
|
||||||
|
- Create new files only when specified
|
||||||
|
|
||||||
|
3. **Run automated verification**:
|
||||||
|
- Execute each command in "Automated Verification"
|
||||||
|
- Fix any issues before proceeding
|
||||||
|
|
||||||
|
4. **Update plan checkboxes**:
|
||||||
|
- Use Edit tool to check off completed items in the plan
|
||||||
|
- This enables resume if session is interrupted
|
||||||
|
|
||||||
|
5. **Update bead notes** with progress:
|
||||||
|
```bash
|
||||||
|
bd update {bead-id} --notes="Phase {N} complete. Automated verification passed."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Per-Plan Checkpoint
|
||||||
|
|
||||||
|
**CRITICAL**: After completing ALL phases and ALL automated verification:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementation Complete - Ready for Manual Verification
|
||||||
|
|
||||||
|
All phases completed and automated verification passed:
|
||||||
|
- [ ] Phase 1: {name} - DONE
|
||||||
|
- [ ] Phase 2: {name} - DONE
|
||||||
|
- [ ] ...
|
||||||
|
|
||||||
|
**Automated checks passed:**
|
||||||
|
- {List of automated checks that passed}
|
||||||
|
|
||||||
|
**Please perform manual verification:**
|
||||||
|
- {List manual verification items from plan}
|
||||||
|
|
||||||
|
Let me know when manual testing is complete so I can close the bead.
|
||||||
|
```
|
||||||
|
|
||||||
|
**STOP HERE and wait for user confirmation.**
|
||||||
|
|
||||||
|
Do NOT:
|
||||||
|
- Close the bead automatically
|
||||||
|
- Proceed to "next steps" without confirmation
|
||||||
|
- Start additional work
|
||||||
|
|
||||||
|
### Step 4: After Manual Verification
|
||||||
|
|
||||||
|
When user confirms manual verification passed:
|
||||||
|
|
||||||
|
1. **Update plan status**:
|
||||||
|
- Edit the plan's frontmatter: `status: complete`
|
||||||
|
|
||||||
|
2. **Close the bead**:
|
||||||
|
```bash
|
||||||
|
bd close {bead-id} --reason="Implementation complete. All verification passed."
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Final summary**:
|
||||||
|
```
|
||||||
|
Bead {bead-id} closed.
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
- {What was implemented}
|
||||||
|
- {Key changes made}
|
||||||
|
|
||||||
|
Artifacts:
|
||||||
|
- Plan: thoughts/beads-{bead-id}/plan.md
|
||||||
|
- {Any other artifacts created}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Handling Issues
|
||||||
|
|
||||||
|
### When something doesn't match the plan:
|
||||||
|
|
||||||
|
```
|
||||||
|
Issue in Phase {N}:
|
||||||
|
|
||||||
|
Expected: {what the plan says}
|
||||||
|
Found: {actual situation}
|
||||||
|
Why this matters: {explanation}
|
||||||
|
|
||||||
|
Options:
|
||||||
|
1. Adapt the implementation to work with current state
|
||||||
|
2. Update the plan to reflect reality
|
||||||
|
3. Stop and investigate further
|
||||||
|
|
||||||
|
How should I proceed?
|
||||||
|
```
|
||||||
|
|
||||||
|
### When tests fail:
|
||||||
|
|
||||||
|
1. **Analyze the failure**
|
||||||
|
2. **Attempt to fix** if the fix is clear and within scope
|
||||||
|
3. **If fix is unclear**, report:
|
||||||
|
```
|
||||||
|
Test failure in Phase {N}:
|
||||||
|
|
||||||
|
Failing test: {test name}
|
||||||
|
Error: {error message}
|
||||||
|
|
||||||
|
I've attempted: {what you tried}
|
||||||
|
|
||||||
|
This may require: {your assessment}
|
||||||
|
```
|
||||||
|
|
||||||
|
### When blocked:
|
||||||
|
|
||||||
|
```
|
||||||
|
Blocked in Phase {N}:
|
||||||
|
|
||||||
|
Blocker: {description}
|
||||||
|
Impact: {what can't proceed}
|
||||||
|
|
||||||
|
Suggested resolution: {your recommendation}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resuming Work
|
||||||
|
|
||||||
|
If the plan has existing checkmarks:
|
||||||
|
|
||||||
|
1. **Trust completed work** - don't re-verify unless something seems off
|
||||||
|
2. **Pick up from first unchecked item**
|
||||||
|
3. **Verify previous work only if** current phase depends on it and seems broken
|
||||||
|
|
||||||
|
## Important Guidelines
|
||||||
|
|
||||||
|
1. **Follow the plan's intent** while adapting to reality
|
||||||
|
2. **Implement each phase fully** before moving to next
|
||||||
|
3. **Update checkboxes in real-time** as you complete items
|
||||||
|
4. **One checkpoint per plan** - not per phase
|
||||||
|
5. **Never close bead** without manual verification confirmation
|
||||||
|
6. **Keep bead notes updated** with progress
|
||||||
|
|
||||||
|
## Session Close Protocol
|
||||||
|
|
||||||
|
If you need to end the session before completion:
|
||||||
|
|
||||||
|
1. **Update plan** with current progress (checkboxes)
|
||||||
|
2. **Update bead notes**:
|
||||||
|
```bash
|
||||||
|
bd update {bead-id} --notes="In progress: Phase {N} partially complete. Next: {what's next}"
|
||||||
|
```
|
||||||
|
3. **Inform user** of status and how to resume
|
||||||
|
|
||||||
|
## Example Invocation
|
||||||
|
|
||||||
|
```
|
||||||
|
User: /beads:implement nixos-configs-abc123
|
||||||
|
Assistant: Implementing plan for bead nixos-configs-abc123...
|
||||||
|
|
||||||
|
## Starting Phase 1: Database Schema
|
||||||
|
|
||||||
|
This phase will add the new user_preferences table...
|
||||||
|
```
|
||||||
214
home/roles/development/skills/beads_iterate.md
Normal file
214
home/roles/development/skills/beads_iterate.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
---
|
||||||
|
description: Iterate on existing implementation plans for a bead issue
|
||||||
|
model: opus
|
||||||
|
---
|
||||||
|
|
||||||
|
# Beads Iterate
|
||||||
|
|
||||||
|
You are tasked with updating existing implementation plans based on feedback. Plans are stored in `thoughts/beads-{id}/plan.md`.
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
When this command is invoked:
|
||||||
|
|
||||||
|
1. **Parse the input**:
|
||||||
|
- Bead ID (required or ask for it)
|
||||||
|
- Requested changes/feedback (can be provided with command or after)
|
||||||
|
|
||||||
|
2. **Handle different scenarios**:
|
||||||
|
|
||||||
|
**No bead ID provided**:
|
||||||
|
```
|
||||||
|
Which bead's plan would you like to iterate on?
|
||||||
|
|
||||||
|
Recent beads with plans:
|
||||||
|
{list beads that have thoughts/beads-{id}/plan.md}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bead ID but no feedback**:
|
||||||
|
```
|
||||||
|
I've found the plan at thoughts/beads-{bead-id}/plan.md
|
||||||
|
|
||||||
|
What changes would you like to make? For example:
|
||||||
|
- "Add a phase for migration handling"
|
||||||
|
- "Update success criteria to include performance tests"
|
||||||
|
- "Adjust scope to exclude feature X"
|
||||||
|
- "Split Phase 2 into two separate phases"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Both bead ID and feedback provided**:
|
||||||
|
- Proceed immediately to Step 1
|
||||||
|
|
||||||
|
## Iteration Process
|
||||||
|
|
||||||
|
### Step 1: Understand Current Plan
|
||||||
|
|
||||||
|
1. **Read the existing plan COMPLETELY**:
|
||||||
|
```bash
|
||||||
|
cat thoughts/beads-{bead-id}/plan.md
|
||||||
|
```
|
||||||
|
- Understand current structure, phases, scope
|
||||||
|
- Note success criteria and approach
|
||||||
|
|
||||||
|
2. **Read the bead for context**:
|
||||||
|
```bash
|
||||||
|
bd show {bead-id}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Understand requested changes**:
|
||||||
|
- Parse what user wants to add/modify/remove
|
||||||
|
- Identify if changes require codebase research
|
||||||
|
|
||||||
|
### Step 2: Research If Needed
|
||||||
|
|
||||||
|
**Only if changes require new technical understanding:**
|
||||||
|
|
||||||
|
1. **Spawn parallel research tasks**:
|
||||||
|
- **codebase-locator**: Find relevant files
|
||||||
|
- **codebase-analyzer**: Understand implementation details
|
||||||
|
- **codebase-pattern-finder**: Find similar patterns
|
||||||
|
|
||||||
|
2. **Be specific about directories** in prompts
|
||||||
|
|
||||||
|
3. **Wait for ALL tasks** before proceeding
|
||||||
|
|
||||||
|
### Step 3: Present Understanding
|
||||||
|
|
||||||
|
Before making changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
Based on your feedback, I understand you want to:
|
||||||
|
- {Change 1 with specific detail}
|
||||||
|
- {Change 2 with specific detail}
|
||||||
|
|
||||||
|
{If research was needed:}
|
||||||
|
My research found:
|
||||||
|
- {Relevant discovery}
|
||||||
|
- {Important constraint}
|
||||||
|
|
||||||
|
I plan to update the plan by:
|
||||||
|
1. {Specific modification}
|
||||||
|
2. {Another modification}
|
||||||
|
|
||||||
|
Does this align with your intent?
|
||||||
|
```
|
||||||
|
|
||||||
|
Get user confirmation before proceeding.
|
||||||
|
|
||||||
|
### Step 4: Update the Plan
|
||||||
|
|
||||||
|
1. **Make focused, precise edits**:
|
||||||
|
- Use Edit tool for surgical changes
|
||||||
|
- Maintain existing structure unless explicitly changing it
|
||||||
|
- Keep file:line references accurate
|
||||||
|
|
||||||
|
2. **Ensure consistency**:
|
||||||
|
- New phases follow existing pattern
|
||||||
|
- Update "What We're NOT Doing" if scope changes
|
||||||
|
- Maintain automated vs manual success criteria distinction
|
||||||
|
|
||||||
|
3. **Update plan metadata**:
|
||||||
|
- Update frontmatter `date` to current timestamp
|
||||||
|
- Add `iteration: {N}` to frontmatter
|
||||||
|
- Add `iteration_reason: "{brief description}"` to frontmatter
|
||||||
|
|
||||||
|
4. **Preserve completed work**:
|
||||||
|
- Don't uncheck items that were already completed
|
||||||
|
- If changing completed phases, discuss with user first
|
||||||
|
|
||||||
|
### Step 5: Save Iteration History (Optional)
|
||||||
|
|
||||||
|
For significant changes, save the previous version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp thoughts/beads-{bead-id}/plan.md thoughts/beads-{bead-id}/plan-v{N}.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Then update the main plan.
|
||||||
|
|
||||||
|
### Step 6: Update Bead
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd update {bead-id} --notes="Plan iterated: {brief description of changes}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Present Changes
|
||||||
|
|
||||||
|
```
|
||||||
|
I've updated the plan at `thoughts/beads-{bead-id}/plan.md`
|
||||||
|
|
||||||
|
Changes made:
|
||||||
|
- {Specific change 1}
|
||||||
|
- {Specific change 2}
|
||||||
|
|
||||||
|
The updated plan now:
|
||||||
|
- {Key improvement}
|
||||||
|
- {Another improvement}
|
||||||
|
|
||||||
|
Would you like any further adjustments?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Guidelines
|
||||||
|
|
||||||
|
1. **Be Skeptical**:
|
||||||
|
- Don't blindly accept changes that seem problematic
|
||||||
|
- Question vague feedback - ask for clarification
|
||||||
|
- Point out conflicts with existing phases
|
||||||
|
|
||||||
|
2. **Be Surgical**:
|
||||||
|
- Make precise edits, not wholesale rewrites
|
||||||
|
- Preserve good content that doesn't need changing
|
||||||
|
- Only research what's necessary
|
||||||
|
|
||||||
|
3. **Be Thorough**:
|
||||||
|
- Read entire plan before making changes
|
||||||
|
- Ensure updated sections maintain quality
|
||||||
|
- Verify success criteria are still measurable
|
||||||
|
|
||||||
|
4. **Be Interactive**:
|
||||||
|
- Confirm understanding before making changes
|
||||||
|
- Allow course corrections
|
||||||
|
- Don't disappear into research without communicating
|
||||||
|
|
||||||
|
5. **No Open Questions**:
|
||||||
|
- If changes raise questions, ASK
|
||||||
|
- Don't update plan with unresolved questions
|
||||||
|
|
||||||
|
## Success Criteria Guidelines
|
||||||
|
|
||||||
|
When updating success criteria, maintain two categories:
|
||||||
|
|
||||||
|
**Automated Verification**:
|
||||||
|
- Commands: `make test`, `npm run lint`
|
||||||
|
- Prefer `make` commands when available
|
||||||
|
- File existence checks
|
||||||
|
|
||||||
|
**Manual Verification**:
|
||||||
|
- UI/UX functionality
|
||||||
|
- Performance under real conditions
|
||||||
|
- Edge cases hard to automate
|
||||||
|
|
||||||
|
## Handling Major Changes
|
||||||
|
|
||||||
|
If feedback requires significant restructuring:
|
||||||
|
|
||||||
|
1. **Discuss scope** before proceeding
|
||||||
|
2. **Consider if this should be a new plan** instead of iteration
|
||||||
|
3. **Preserve the original** in `plan-v{N}.md`
|
||||||
|
4. **Update bead description** if scope changed significantly
|
||||||
|
|
||||||
|
## Example Invocations
|
||||||
|
|
||||||
|
**With full context**:
|
||||||
|
```
|
||||||
|
User: /beads:iterate nixos-configs-abc123 - add error handling phase
|
||||||
|
Assistant: Based on your feedback, I understand you want to add a new phase for error handling...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interactive**:
|
||||||
|
```
|
||||||
|
User: /beads:iterate nixos-configs-abc123
|
||||||
|
Assistant: I've found the plan. What changes would you like to make?
|
||||||
|
User: Split Phase 2 into backend and frontend phases
|
||||||
|
Assistant: I'll split Phase 2 into two separate phases...
|
||||||
|
```
|
||||||
281
home/roles/development/skills/beads_plan.md
Normal file
281
home/roles/development/skills/beads_plan.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
---
|
||||||
|
description: Create detailed implementation plans for a bead issue
|
||||||
|
model: opus
|
||||||
|
---
|
||||||
|
|
||||||
|
# Beads Plan
|
||||||
|
|
||||||
|
You are tasked with creating detailed implementation plans for a bead issue. This skill integrates with the beads issue tracker and stores plans in the `thoughts/` directory.
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
When this command is invoked:
|
||||||
|
|
||||||
|
1. **Parse the input for bead ID**:
|
||||||
|
- If a bead ID is provided, use it
|
||||||
|
- If no bead ID, run `bd ready` and ask which bead to plan for
|
||||||
|
|
||||||
|
2. **Load bead context**:
|
||||||
|
```bash
|
||||||
|
bd show {bead-id}
|
||||||
|
```
|
||||||
|
- Read the bead description for requirements
|
||||||
|
- Check for existing research: `thoughts/beads-{bead-id}/research.md`
|
||||||
|
- Note any dependencies or blockers
|
||||||
|
|
||||||
|
3. **Create artifact directory**:
|
||||||
|
```bash
|
||||||
|
mkdir -p thoughts/beads-{bead-id}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Check for existing research**:
|
||||||
|
- If `thoughts/beads-{bead-id}/research.md` exists, read it fully
|
||||||
|
- This research provides crucial context for planning
|
||||||
|
|
||||||
|
5. **Respond with**:
|
||||||
|
```
|
||||||
|
Creating implementation plan for bead {bead-id}: {bead-title}
|
||||||
|
|
||||||
|
{If research exists: "Found existing research at thoughts/beads-{bead-id}/research.md - incorporating findings."}
|
||||||
|
|
||||||
|
Let me analyze the requirements and codebase to create a detailed plan.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Planning Process
|
||||||
|
|
||||||
|
### Step 1: Context Gathering
|
||||||
|
|
||||||
|
1. **Read all mentioned files FULLY**:
|
||||||
|
- Bead description references
|
||||||
|
- Existing research document
|
||||||
|
- Any linked tickets or docs
|
||||||
|
- Use Read tool WITHOUT limit/offset
|
||||||
|
|
||||||
|
2. **Spawn initial research tasks**:
|
||||||
|
- **codebase-locator**: Find all files related to the task
|
||||||
|
- **codebase-analyzer**: Understand current implementation
|
||||||
|
- **codebase-pattern-finder**: Find similar features to model after
|
||||||
|
- **thoughts-locator**: Find any existing plans or decisions
|
||||||
|
|
||||||
|
3. **Read all files identified by research**:
|
||||||
|
- Read them FULLY into main context
|
||||||
|
- Cross-reference with requirements
|
||||||
|
|
||||||
|
### Step 2: Present Understanding
|
||||||
|
|
||||||
|
Before writing the plan, confirm understanding:
|
||||||
|
|
||||||
|
```
|
||||||
|
Based on the bead and my research, I understand we need to [accurate summary].
|
||||||
|
|
||||||
|
I've found that:
|
||||||
|
- [Current implementation detail with file:line reference]
|
||||||
|
- [Relevant pattern or constraint discovered]
|
||||||
|
- [Potential complexity or edge case identified]
|
||||||
|
|
||||||
|
Questions that my research couldn't answer:
|
||||||
|
- [Specific technical question requiring human judgment]
|
||||||
|
- [Business logic clarification]
|
||||||
|
```
|
||||||
|
|
||||||
|
Only ask questions you genuinely cannot answer through code investigation.
|
||||||
|
|
||||||
|
### Step 3: Research & Discovery
|
||||||
|
|
||||||
|
After getting clarifications:
|
||||||
|
|
||||||
|
1. **If user corrects any misunderstanding**:
|
||||||
|
- Spawn new research tasks to verify
|
||||||
|
- Read specific files/directories mentioned
|
||||||
|
- Only proceed once verified
|
||||||
|
|
||||||
|
2. **Present design options**:
|
||||||
|
```
|
||||||
|
Based on my research:
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- [Key discovery about existing code]
|
||||||
|
- [Pattern or convention to follow]
|
||||||
|
|
||||||
|
**Design Options:**
|
||||||
|
1. [Option A] - [pros/cons]
|
||||||
|
2. [Option B] - [pros/cons]
|
||||||
|
|
||||||
|
Which approach aligns best?
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Plan Structure
|
||||||
|
|
||||||
|
Once aligned on approach:
|
||||||
|
|
||||||
|
```
|
||||||
|
Here's my proposed plan structure:
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
[1-2 sentence summary]
|
||||||
|
|
||||||
|
## Implementation Phases:
|
||||||
|
1. [Phase name] - [what it accomplishes]
|
||||||
|
2. [Phase name] - [what it accomplishes]
|
||||||
|
|
||||||
|
Does this phasing make sense?
|
||||||
|
```
|
||||||
|
|
||||||
|
Get feedback on structure before writing details.
|
||||||
|
|
||||||
|
### Step 5: Write the Plan
|
||||||
|
|
||||||
|
Write to `thoughts/beads-{bead-id}/plan.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
date: {ISO timestamp}
|
||||||
|
bead_id: {bead-id}
|
||||||
|
bead_title: "{bead title}"
|
||||||
|
author: claude
|
||||||
|
git_commit: {commit hash}
|
||||||
|
branch: {branch name}
|
||||||
|
repository: {repo name}
|
||||||
|
status: draft
|
||||||
|
---
|
||||||
|
|
||||||
|
# {Feature/Task Name} Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
{Brief description of what we're implementing and why}
|
||||||
|
|
||||||
|
## Current State Analysis
|
||||||
|
|
||||||
|
{What exists now, what's missing, key constraints}
|
||||||
|
|
||||||
|
### Key Discoveries:
|
||||||
|
- {Finding with file:line reference}
|
||||||
|
- {Pattern to follow}
|
||||||
|
|
||||||
|
## Desired End State
|
||||||
|
|
||||||
|
{Specification of desired end state and how to verify it}
|
||||||
|
|
||||||
|
## What We're NOT Doing
|
||||||
|
|
||||||
|
{Explicitly list out-of-scope items}
|
||||||
|
|
||||||
|
## Implementation Approach
|
||||||
|
|
||||||
|
{High-level strategy and reasoning}
|
||||||
|
|
||||||
|
## Phase 1: {Descriptive Name}
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
{What this phase accomplishes}
|
||||||
|
|
||||||
|
### Changes Required:
|
||||||
|
|
||||||
|
#### 1. {Component/File Group}
|
||||||
|
**File**: `path/to/file.ext`
|
||||||
|
**Changes**: {Summary}
|
||||||
|
|
||||||
|
```{language}
|
||||||
|
// Specific code to add/modify
|
||||||
|
```
|
||||||
|
|
||||||
|
### Success Criteria:
|
||||||
|
|
||||||
|
#### Automated Verification:
|
||||||
|
- [ ] Tests pass: `make test`
|
||||||
|
- [ ] Linting passes: `make lint`
|
||||||
|
- [ ] Type checking passes: `make typecheck`
|
||||||
|
|
||||||
|
#### Manual Verification:
|
||||||
|
- [ ] Feature works as expected in UI
|
||||||
|
- [ ] Edge cases handled correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: {Descriptive Name}
|
||||||
|
{Similar structure...}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Tests:
|
||||||
|
- {What to test}
|
||||||
|
- {Key edge cases}
|
||||||
|
|
||||||
|
### Integration Tests:
|
||||||
|
- {End-to-end scenarios}
|
||||||
|
|
||||||
|
### Manual Testing Steps:
|
||||||
|
1. {Specific step}
|
||||||
|
2. {Another step}
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Bead: {bead-id}
|
||||||
|
- Research: `thoughts/beads-{bead-id}/research.md`
|
||||||
|
- Similar implementation: {file:line}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Update the bead
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd update {bead-id} --notes="Plan created: thoughts/beads-{bead-id}/plan.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Create implementation bead (if appropriate)
|
||||||
|
|
||||||
|
If the planning bead is separate from implementation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd create --title="Implement: {feature name}" --type=task --priority=1 \
|
||||||
|
--description="Implement the plan at thoughts/beads-{original-bead-id}/plan.md
|
||||||
|
|
||||||
|
See bead {original-bead-id} for planning context."
|
||||||
|
|
||||||
|
# Link as dependency
|
||||||
|
bd dep add {new-bead-id} {original-bead-id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8: Present for Review
|
||||||
|
|
||||||
|
```
|
||||||
|
I've created the implementation plan at:
|
||||||
|
`thoughts/beads-{bead-id}/plan.md`
|
||||||
|
|
||||||
|
Please review it and let me know:
|
||||||
|
- Are the phases properly scoped?
|
||||||
|
- Are the success criteria specific enough?
|
||||||
|
- Any technical details that need adjustment?
|
||||||
|
- Missing edge cases or considerations?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Guidelines
|
||||||
|
|
||||||
|
1. **Be Skeptical**: Question vague requirements, identify potential issues early
|
||||||
|
2. **Be Interactive**: Don't write the full plan in one shot, get buy-in at each step
|
||||||
|
3. **Be Thorough**: Read all context files COMPLETELY, include specific file:line refs
|
||||||
|
4. **Be Practical**: Focus on incremental, testable changes
|
||||||
|
5. **No Open Questions**: If you have unresolved questions, STOP and ask
|
||||||
|
|
||||||
|
## Success Criteria Guidelines
|
||||||
|
|
||||||
|
Always separate into two categories:
|
||||||
|
|
||||||
|
**Automated Verification** (run by agents):
|
||||||
|
- Commands: `make test`, `npm run lint`, etc.
|
||||||
|
- File existence checks
|
||||||
|
- Type checking
|
||||||
|
|
||||||
|
**Manual Verification** (requires human):
|
||||||
|
- UI/UX functionality
|
||||||
|
- Performance under real conditions
|
||||||
|
- Edge cases hard to automate
|
||||||
|
|
||||||
|
## Example Invocation
|
||||||
|
|
||||||
|
```
|
||||||
|
User: /beads:plan nixos-configs-abc123
|
||||||
|
Assistant: Creating implementation plan for bead nixos-configs-abc123...
|
||||||
|
```
|
||||||
206
home/roles/development/skills/beads_research.md
Normal file
206
home/roles/development/skills/beads_research.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
---
|
||||||
|
description: Research a bead topic comprehensively and store findings in thoughts/
|
||||||
|
model: opus
|
||||||
|
---
|
||||||
|
|
||||||
|
# Beads Research
|
||||||
|
|
||||||
|
You are tasked with conducting comprehensive research for a bead issue. This skill integrates with the beads issue tracker and stores findings in the `thoughts/` directory.
|
||||||
|
|
||||||
|
## CRITICAL: YOUR ONLY JOB IS TO DOCUMENT AND EXPLAIN THE CODEBASE AS IT EXISTS TODAY
|
||||||
|
- DO NOT suggest improvements or changes unless the user explicitly asks for them
|
||||||
|
- DO NOT perform root cause analysis unless the user explicitly asks for them
|
||||||
|
- DO NOT propose future enhancements unless the user explicitly asks for them
|
||||||
|
- DO NOT critique the implementation or identify problems
|
||||||
|
- ONLY describe what exists, where it exists, how it works, and how components interact
|
||||||
|
- You are creating a technical map/documentation of the existing system
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
When this command is invoked:
|
||||||
|
|
||||||
|
1. **Parse the input for bead ID**:
|
||||||
|
- If a bead ID is provided (e.g., `nixos-configs-abc123`), use it
|
||||||
|
- If no bead ID provided, run `bd ready --type=research` to find research beads, or ask which bead to research
|
||||||
|
|
||||||
|
2. **Load bead context**:
|
||||||
|
```bash
|
||||||
|
bd show {bead-id}
|
||||||
|
```
|
||||||
|
- Read the bead description to understand the research question
|
||||||
|
- Note any linked files or references in the bead
|
||||||
|
|
||||||
|
3. **Create artifact directory**:
|
||||||
|
```bash
|
||||||
|
mkdir -p thoughts/beads-{bead-id}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Respond with**:
|
||||||
|
```
|
||||||
|
Starting research for bead {bead-id}: {bead-title}
|
||||||
|
|
||||||
|
Research question: {extracted from bead description}
|
||||||
|
|
||||||
|
I'll analyze this thoroughly and store findings in thoughts/beads-{bead-id}/research.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Research Process
|
||||||
|
|
||||||
|
### Step 1: Read any directly mentioned files
|
||||||
|
- If the bead or user mentions specific files, read them FULLY first
|
||||||
|
- Use the Read tool WITHOUT limit/offset parameters
|
||||||
|
- Read these files yourself in the main context before spawning sub-tasks
|
||||||
|
|
||||||
|
### Step 2: Analyze and decompose the research question
|
||||||
|
- Break down the query into composable research areas
|
||||||
|
- Identify specific components, patterns, or concepts to investigate
|
||||||
|
- Create a research plan using TodoWrite
|
||||||
|
- Consider which directories, files, or patterns are relevant
|
||||||
|
|
||||||
|
### Step 3: Spawn parallel sub-agent tasks
|
||||||
|
|
||||||
|
Use specialized agents for research:
|
||||||
|
|
||||||
|
**For codebase research:**
|
||||||
|
- **codebase-locator** - Find WHERE files and components live
|
||||||
|
- **codebase-analyzer** - Understand HOW specific code works
|
||||||
|
- **codebase-pattern-finder** - Find examples of existing patterns
|
||||||
|
|
||||||
|
**For thoughts directory:**
|
||||||
|
- **thoughts-locator** - Discover what documents exist about the topic
|
||||||
|
- **thoughts-analyzer** - Extract key insights from specific documents
|
||||||
|
|
||||||
|
**For web research (only if explicitly requested):**
|
||||||
|
- **web-search-researcher** - External documentation and resources
|
||||||
|
|
||||||
|
Key principles:
|
||||||
|
- Run multiple agents in parallel when searching for different things
|
||||||
|
- Each agent knows its job - tell it what you're looking for, not HOW to search
|
||||||
|
- Remind agents they are documenting, not evaluating
|
||||||
|
|
||||||
|
### Step 4: Synthesize findings
|
||||||
|
|
||||||
|
Wait for ALL sub-agents to complete, then:
|
||||||
|
- Compile all results (codebase and thoughts findings)
|
||||||
|
- Prioritize live codebase findings as primary source of truth
|
||||||
|
- Connect findings across different components
|
||||||
|
- Include specific file paths and line numbers
|
||||||
|
- Highlight patterns, connections, and architectural decisions
|
||||||
|
|
||||||
|
### Step 5: Gather metadata
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Git metadata
|
||||||
|
git rev-parse HEAD # Current commit
|
||||||
|
git branch --show-current # Current branch
|
||||||
|
basename $(git rev-parse --show-toplevel) # Repo name
|
||||||
|
date -Iseconds # Current timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Write research document
|
||||||
|
|
||||||
|
Write to `thoughts/beads-{bead-id}/research.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
date: {ISO timestamp with timezone}
|
||||||
|
bead_id: {bead-id}
|
||||||
|
bead_title: "{bead title}"
|
||||||
|
researcher: claude
|
||||||
|
git_commit: {commit hash}
|
||||||
|
branch: {branch name}
|
||||||
|
repository: {repo name}
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Research: {bead title}
|
||||||
|
|
||||||
|
**Bead**: {bead-id}
|
||||||
|
**Date**: {timestamp}
|
||||||
|
**Git Commit**: {commit hash}
|
||||||
|
**Branch**: {branch name}
|
||||||
|
|
||||||
|
## Research Question
|
||||||
|
{Original question from bead description}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
{High-level documentation answering the research question}
|
||||||
|
|
||||||
|
## Detailed Findings
|
||||||
|
|
||||||
|
### {Component/Area 1}
|
||||||
|
- Description of what exists (file.ext:line)
|
||||||
|
- How it connects to other components
|
||||||
|
- Current implementation details
|
||||||
|
|
||||||
|
### {Component/Area 2}
|
||||||
|
...
|
||||||
|
|
||||||
|
## Code References
|
||||||
|
- `path/to/file.py:123` - Description
|
||||||
|
- `another/file.ts:45-67` - Description
|
||||||
|
|
||||||
|
## Architecture Documentation
|
||||||
|
{Current patterns, conventions found in codebase}
|
||||||
|
|
||||||
|
## Historical Context (from thoughts/)
|
||||||
|
{Relevant insights from thoughts/ with references}
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
{Any areas needing further investigation}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Update the bead
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add artifact link to bead notes
|
||||||
|
bd update {bead-id} --notes="Research complete: thoughts/beads-{bead-id}/research.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8: Autonomy decision
|
||||||
|
|
||||||
|
**For research beads (type=research):**
|
||||||
|
- If research is complete and comprehensive, close the bead:
|
||||||
|
```bash
|
||||||
|
bd close {bead-id} --reason="Research complete. See thoughts/beads-{bead-id}/research.md"
|
||||||
|
```
|
||||||
|
- Present summary to user
|
||||||
|
|
||||||
|
**For other bead types:**
|
||||||
|
- Do NOT close the bead
|
||||||
|
- Present findings and ask how to proceed
|
||||||
|
|
||||||
|
### Step 9: Handle follow-up questions
|
||||||
|
|
||||||
|
If the user has follow-up questions:
|
||||||
|
- Append to the same research document
|
||||||
|
- Add a new section: `## Follow-up Research [{timestamp}]`
|
||||||
|
- Update the bead notes with the new findings
|
||||||
|
|
||||||
|
## Important Guidelines
|
||||||
|
|
||||||
|
- Always use parallel Task agents to maximize efficiency
|
||||||
|
- Always run fresh codebase research - never rely solely on existing documents
|
||||||
|
- Focus on finding concrete file paths and line numbers
|
||||||
|
- Research documents should be self-contained
|
||||||
|
- Document cross-component connections
|
||||||
|
- Include temporal context (when research was conducted)
|
||||||
|
- Keep the main agent focused on synthesis, not deep file reading
|
||||||
|
- **CRITICAL**: You and all sub-agents are documentarians, not evaluators
|
||||||
|
- **REMEMBER**: Document what IS, not what SHOULD BE
|
||||||
|
|
||||||
|
## Example Invocation
|
||||||
|
|
||||||
|
```
|
||||||
|
User: /beads:research nixos-configs-abc123
|
||||||
|
Assistant: Starting research for bead nixos-configs-abc123: Investigate auth flow
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Or without bead ID:
|
||||||
|
```
|
||||||
|
User: /beads:research
|
||||||
|
Assistant: Let me check for research beads...
|
||||||
|
[runs bd ready]
|
||||||
|
Which bead would you like me to research?
|
||||||
|
```
|
||||||
387
home/roles/development/skills/beads_workflow.md
Normal file
387
home/roles/development/skills/beads_workflow.md
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
---
|
||||||
|
description: Comprehensive guide for the beads + humanlayer integrated workflow
|
||||||
|
---
|
||||||
|
|
||||||
|
# Beads Workflow Guide
|
||||||
|
|
||||||
|
This document describes the integrated workflow combining **beads** (issue tracking) with **humanlayer-style skills** (deep research, planning, implementation).
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
### Two Systems, Complementary Purposes
|
||||||
|
|
||||||
|
| System | Purpose | Storage |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| **Beads** | Track WHAT work exists | `.beads/` (git-synced) |
|
||||||
|
| **Thoughts** | Store HOW to do the work | `thoughts/` (local or symlinked) |
|
||||||
|
|
||||||
|
### Autonomy Model
|
||||||
|
|
||||||
|
| Bead Type | Agent Autonomy | Checkpoint |
|
||||||
|
|-----------|----------------|------------|
|
||||||
|
| `research` | **Full** - agent closes when satisfied | None |
|
||||||
|
| `feature`, `task`, `bug` | **Checkpointed** - pause for validation | Per-plan |
|
||||||
|
|
||||||
|
**Key insight**: Research produces artifacts. Implementation produces commits. Commits are the review boundary.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
├── .beads/ # Beads database (git-synced)
|
||||||
|
│ ├── beads.db
|
||||||
|
│ ├── config.yaml
|
||||||
|
│ └── issues.jsonl
|
||||||
|
├── thoughts/ # Artifacts (local or symlink)
|
||||||
|
│ ├── beads-{id}/ # Per-bead artifacts
|
||||||
|
│ │ ├── research.md
|
||||||
|
│ │ ├── plan.md
|
||||||
|
│ │ └── plan-v1.md # Iteration history
|
||||||
|
│ └── shared/ # Legacy/non-bead artifacts
|
||||||
|
│ ├── research/
|
||||||
|
│ └── plans/
|
||||||
|
└── home/roles/development/skills/ # Skill definitions
|
||||||
|
├── beads_research.md
|
||||||
|
├── beads_plan.md
|
||||||
|
├── beads_implement.md
|
||||||
|
└── beads_iterate.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use What
|
||||||
|
|
||||||
|
### Use Beads When:
|
||||||
|
- Work spans multiple sessions
|
||||||
|
- Work has dependencies or blockers
|
||||||
|
- You need to track status across interruptions
|
||||||
|
- Multiple related tasks need coordination
|
||||||
|
- Context recovery after compaction matters
|
||||||
|
|
||||||
|
### Use TodoWrite When:
|
||||||
|
- Single-session execution tracking
|
||||||
|
- Breaking down work within a session
|
||||||
|
- Tracking progress on a single bead
|
||||||
|
|
||||||
|
### Use Both Together:
|
||||||
|
- Beads track the overall work items
|
||||||
|
- TodoWrite tracks progress within a session
|
||||||
|
- Example: Bead for "Implement auth", TodoWrite for each file being edited
|
||||||
|
|
||||||
|
## Workflow Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Research-First Approach
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Create research bead
|
||||||
|
bd create --title="Research auth patterns" --type=research --priority=1
|
||||||
|
|
||||||
|
2. Run research
|
||||||
|
/beads:research {bead-id}
|
||||||
|
→ Agent researches, writes to thoughts/beads-{id}/research.md
|
||||||
|
→ Agent closes bead when satisfied
|
||||||
|
|
||||||
|
3. Create implementation bead
|
||||||
|
bd create --title="Implement auth" --type=feature --priority=1
|
||||||
|
|
||||||
|
4. Plan the implementation
|
||||||
|
/beads:plan {bead-id}
|
||||||
|
→ Agent reads prior research, creates plan
|
||||||
|
→ Plan saved to thoughts/beads-{id}/plan.md
|
||||||
|
|
||||||
|
5. Implement
|
||||||
|
/beads:implement {bead-id}
|
||||||
|
→ Agent follows plan, pauses for manual verification
|
||||||
|
→ You validate, agent closes bead
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Direct Implementation
|
||||||
|
|
||||||
|
For well-understood tasks without research:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Create bead
|
||||||
|
bd create --title="Fix login bug" --type=bug --priority=0
|
||||||
|
|
||||||
|
2. Plan and implement
|
||||||
|
/beads:plan {bead-id}
|
||||||
|
→ Quick planning based on bead description
|
||||||
|
|
||||||
|
/beads:implement {bead-id}
|
||||||
|
→ Follow plan, pause at checkpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Iterative Planning
|
||||||
|
|
||||||
|
When requirements evolve:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Initial plan
|
||||||
|
/beads:plan {bead-id}
|
||||||
|
|
||||||
|
2. Iterate based on feedback
|
||||||
|
/beads:iterate {bead-id} - add error handling phase
|
||||||
|
|
||||||
|
3. Iterate again if needed
|
||||||
|
/beads:iterate {bead-id} - split phase 2 into backend/frontend
|
||||||
|
|
||||||
|
4. Implement when plan is solid
|
||||||
|
/beads:implement {bead-id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 4: Parallel Work
|
||||||
|
|
||||||
|
Using parallel_beads skill for multiple independent tasks:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Check what's ready
|
||||||
|
bd ready
|
||||||
|
|
||||||
|
2. Select multiple beads
|
||||||
|
/parallel_beads
|
||||||
|
→ Select beads to work on
|
||||||
|
→ Each gets worktree, PR, review
|
||||||
|
|
||||||
|
3. Reconcile after PRs merge
|
||||||
|
/reconcile_beads
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skills Reference
|
||||||
|
|
||||||
|
### /beads:research {bead-id}
|
||||||
|
- Conducts comprehensive codebase research
|
||||||
|
- Uses parallel sub-agents for efficiency
|
||||||
|
- Outputs to `thoughts/beads-{id}/research.md`
|
||||||
|
- **Autonomy**: Can close research beads automatically
|
||||||
|
|
||||||
|
### /beads:plan {bead-id}
|
||||||
|
- Creates detailed implementation plans
|
||||||
|
- Interactive process with checkpoints
|
||||||
|
- Outputs to `thoughts/beads-{id}/plan.md`
|
||||||
|
- Can create dependent implementation beads
|
||||||
|
|
||||||
|
### /beads:implement {bead-id}
|
||||||
|
- Follows plans from thoughts/
|
||||||
|
- Updates plan checkboxes for resumability
|
||||||
|
- **Checkpoint**: Pauses after plan completion for manual verification
|
||||||
|
- Only closes bead after human confirms
|
||||||
|
|
||||||
|
### /beads:iterate {bead-id}
|
||||||
|
- Updates existing plans based on feedback
|
||||||
|
- Preserves plan structure while making targeted changes
|
||||||
|
- Saves iteration history as `plan-v{N}.md`
|
||||||
|
|
||||||
|
### /parallel_beads
|
||||||
|
- Orchestrates parallel bead processing
|
||||||
|
- Creates worktrees, PRs, reviews for multiple beads
|
||||||
|
- Good for batching independent work
|
||||||
|
|
||||||
|
### /reconcile_beads
|
||||||
|
- Closes beads whose PRs have merged
|
||||||
|
- Run after merging PRs to keep beads in sync
|
||||||
|
|
||||||
|
## Session Protocols
|
||||||
|
|
||||||
|
### Starting a Session
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check what's available
|
||||||
|
bd ready
|
||||||
|
|
||||||
|
# Pick work and start
|
||||||
|
bd update {bead-id} --status=in_progress
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ending a Session
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Always run this checklist:
|
||||||
|
[ ] git status # Check changes
|
||||||
|
[ ] git add <files> # Stage code changes
|
||||||
|
[ ] bd sync # Sync beads
|
||||||
|
[ ] git commit -m "..." # Commit code
|
||||||
|
[ ] git push # Push to remote
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resuming Work
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find in-progress work
|
||||||
|
bd list --status=in_progress
|
||||||
|
|
||||||
|
# Check bead notes for context
|
||||||
|
bd show {bead-id}
|
||||||
|
|
||||||
|
# Check for partial plan progress
|
||||||
|
cat thoughts/beads-{id}/plan.md | grep "\[x\]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Thoughts Directory Patterns
|
||||||
|
|
||||||
|
### For Work Repos (via symlink)
|
||||||
|
```
|
||||||
|
project/thoughts → ~/thoughts/repos/{repo-name}/
|
||||||
|
```
|
||||||
|
- Syncs via codelayer to work remote
|
||||||
|
- Shared across projects on same machine
|
||||||
|
|
||||||
|
### For Personal Repos (local)
|
||||||
|
```
|
||||||
|
project/thoughts/ # Regular directory, not symlink
|
||||||
|
```
|
||||||
|
- Stays local to project
|
||||||
|
- Committed with project or gitignored
|
||||||
|
|
||||||
|
### Determining Which Pattern
|
||||||
|
```bash
|
||||||
|
# Check if thoughts is a symlink
|
||||||
|
ls -la thoughts
|
||||||
|
|
||||||
|
# If symlink, it points to ~/thoughts/repos/{repo}/
|
||||||
|
# If directory, it's local to this project
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Bead Descriptions Matter
|
||||||
|
Write clear descriptions - they're the input for research and planning:
|
||||||
|
```bash
|
||||||
|
bd create --title="Implement user preferences" --type=feature \
|
||||||
|
--description="Add user preferences storage and UI.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Store preferences in SQLite
|
||||||
|
- Expose via REST API
|
||||||
|
- Add settings page in UI
|
||||||
|
|
||||||
|
See related: thoughts/shared/research/preferences-patterns.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Link Artifacts in Beads
|
||||||
|
Always update bead notes with artifact locations:
|
||||||
|
```bash
|
||||||
|
bd update {id} --notes="Research: thoughts/beads-{id}/research.md
|
||||||
|
Plan: thoughts/beads-{id}/plan.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Use Dependencies
|
||||||
|
Structure work with dependencies:
|
||||||
|
```bash
|
||||||
|
# Research blocks planning
|
||||||
|
bd dep add {plan-bead} {research-bead}
|
||||||
|
|
||||||
|
# Planning blocks implementation
|
||||||
|
bd dep add {impl-bead} {plan-bead}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Trust the Checkpoint Model
|
||||||
|
- Research beads: Let agent close them
|
||||||
|
- Implementation beads: Always validate before closing
|
||||||
|
- If in doubt, err on the side of checkpoints
|
||||||
|
|
||||||
|
### 5. Keep Plans Updated
|
||||||
|
- Check off completed items as you go
|
||||||
|
- Update notes with progress
|
||||||
|
- This enables seamless resume across sessions
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "What bead should I work on?"
|
||||||
|
```bash
|
||||||
|
bd ready # Shows unblocked work
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Where did the research go?"
|
||||||
|
```bash
|
||||||
|
ls thoughts/beads-{id}/
|
||||||
|
bd show {id} # Check notes for artifact links
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Plan doesn't match reality"
|
||||||
|
```bash
|
||||||
|
/beads:iterate {id} # Update plan based on findings
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Session ended mid-implementation"
|
||||||
|
```bash
|
||||||
|
bd show {id} # Check notes for progress
|
||||||
|
cat thoughts/beads-{id}/plan.md | grep "\[x\]" # See completed items
|
||||||
|
/beads:implement {id} # Resume - will pick up from last checkpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Bead is blocked"
|
||||||
|
```bash
|
||||||
|
bd show {id} # See what's blocking
|
||||||
|
bd blocked # See all blocked beads
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
### From Pure Humanlayer to Beads+Humanlayer
|
||||||
|
|
||||||
|
Old pattern:
|
||||||
|
```
|
||||||
|
thoughts/shared/research/2025-01-01-topic.md
|
||||||
|
thoughts/shared/plans/2025-01-01-feature.md
|
||||||
|
```
|
||||||
|
|
||||||
|
New pattern:
|
||||||
|
```
|
||||||
|
thoughts/beads-{id}/research.md
|
||||||
|
thoughts/beads-{id}/plan.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The `shared/` structure still works for non-bead artifacts, but prefer per-bead directories for tracked work.
|
||||||
|
|
||||||
|
### Existing Content
|
||||||
|
- Keep existing `thoughts/shared/` content
|
||||||
|
- New bead-tracked work uses `thoughts/beads-{id}/`
|
||||||
|
- Reference old research from bead descriptions when relevant
|
||||||
|
|
||||||
|
## Design Decisions
|
||||||
|
|
||||||
|
### Phase Tracking: Artifacts vs Statuses
|
||||||
|
|
||||||
|
**Current approach**: Skills infer workflow phase from artifact presence:
|
||||||
|
- Has `research.md` → research done
|
||||||
|
- Has `plan.md` → planning done
|
||||||
|
- No artifacts → needs research/planning
|
||||||
|
|
||||||
|
**Alternative considered**: Explicit phase statuses (`needs_research`, `needs_plan`, `implementing`, etc.)
|
||||||
|
|
||||||
|
**Why artifacts win**:
|
||||||
|
1. **Single source of truth** - Status can't drift from reality
|
||||||
|
2. **Less state to maintain** - No need to update status when creating artifacts
|
||||||
|
3. **Works across repos** - No custom status config needed
|
||||||
|
4. **Skills already check artifacts** - Natural fit with existing behavior
|
||||||
|
|
||||||
|
**When explicit statuses would help**:
|
||||||
|
- Pipeline visualization (e.g., `bd list --status=needs_plan`)
|
||||||
|
- Agent self-selection by phase
|
||||||
|
- Team coordination dashboards
|
||||||
|
|
||||||
|
**Recommendation**: Keep artifact-inference as primary mechanism. If pipeline visibility becomes important, consider adding statuses that skills auto-set when creating artifacts (advisory, not enforced).
|
||||||
|
|
||||||
|
### One Bead Per Feature (Default)
|
||||||
|
|
||||||
|
**Current approach**: File one bead per logical feature. Skills handle phases internally.
|
||||||
|
|
||||||
|
**Alternative considered**: Separate beads for research → planning → implementation, linked by dependencies.
|
||||||
|
|
||||||
|
**Why single bead wins for most work**:
|
||||||
|
1. **Lower friction** - Quick idea dump without filing 3 tickets
|
||||||
|
2. **Simpler tracking** - One status to check
|
||||||
|
3. **Natural grouping** - Artifacts stay together in `thoughts/beads-{id}/`
|
||||||
|
|
||||||
|
**When to split into multiple beads**:
|
||||||
|
- Research reveals the work should be multiple features
|
||||||
|
- Different phases need different assignees
|
||||||
|
- Explicit dependency tracking matters (e.g., "auth must ship before payments")
|
||||||
|
|
||||||
|
**The discovered-work pattern**: Start with one bead. If research reveals split work, file additional beads with dependencies. Skills guide this naturally.
|
||||||
|
|
||||||
|
### Plan Requirements by Type
|
||||||
|
|
||||||
|
**Bug fixes** (`type=bug`): Can proceed without plans - usually well-scoped from bug report.
|
||||||
|
|
||||||
|
**Features/tasks** (`type=feature`, `type=task`): Should have plans - helps ensure design is sound before implementation.
|
||||||
|
|
||||||
|
This is advisory, not enforced. Skills warn but allow override for simple changes.
|
||||||
472
home/roles/development/skills/gitea_pr_review.md
Normal file
472
home/roles/development/skills/gitea_pr_review.md
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
---
|
||||||
|
description: Address Gitea/Forgejo PR review comments with code changes
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gitea PR Review
|
||||||
|
|
||||||
|
You are tasked with **addressing** PR review comments by making code changes, then summarizing what was done. This skill drives PR progress, not just conversation.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
**Comments are work items, not conversation starters.**
|
||||||
|
|
||||||
|
When a reviewer leaves a comment, they're identifying something that needs attention. This skill:
|
||||||
|
1. Categorizes comments by actionability
|
||||||
|
2. Makes code changes to address actionable comments
|
||||||
|
3. Commits and pushes those changes
|
||||||
|
4. Posts a single summary comment describing what was done
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- `tea` CLI configured with a Gitea/Forgejo instance
|
||||||
|
- Access token from tea config: `~/.config/tea/config.yml`
|
||||||
|
- Repository must be a Gitea/Forgejo remote (not GitHub)
|
||||||
|
- **Nix users**: All tools available via nixpkgs (`nix run nixpkgs#tea`)
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
When this command is invoked:
|
||||||
|
|
||||||
|
1. **Parse the input for PR number**:
|
||||||
|
- If a PR number is provided as argument, use it
|
||||||
|
- If no PR number, detect from current branch (see PR Detection section)
|
||||||
|
|
||||||
|
2. **Verify required tools are available**:
|
||||||
|
```bash
|
||||||
|
which tea
|
||||||
|
```
|
||||||
|
|
||||||
|
If tea is missing:
|
||||||
|
```
|
||||||
|
Error: `tea` CLI not found.
|
||||||
|
|
||||||
|
Please install:
|
||||||
|
- Nix: nix run nixpkgs#tea
|
||||||
|
- Other: https://gitea.com/gitea/tea
|
||||||
|
```
|
||||||
|
**STOP** if tea is missing.
|
||||||
|
|
||||||
|
3. **Extract configuration from tea config**:
|
||||||
|
```bash
|
||||||
|
# Read tea config (it's YAML but simple enough to grep)
|
||||||
|
TEA_CONFIG="$HOME/.config/tea/config.yml"
|
||||||
|
GITEA_URL=$(grep -A1 'logins:' "$TEA_CONFIG" | grep 'url:' | head -1 | sed 's/.*url: //')
|
||||||
|
TOKEN=$(grep -A5 'logins:' "$TEA_CONFIG" | grep 'token:' | head -1 | sed 's/.*token: //')
|
||||||
|
```
|
||||||
|
|
||||||
|
If config is missing or invalid:
|
||||||
|
```
|
||||||
|
Error: Could not read tea config at ~/.config/tea/config.yml
|
||||||
|
|
||||||
|
Please ensure `tea` is installed and configured:
|
||||||
|
1. Install tea
|
||||||
|
2. Log in: tea login add --url https://your-gitea-instance --token YOUR_TOKEN
|
||||||
|
```
|
||||||
|
**STOP** if config is invalid.
|
||||||
|
|
||||||
|
4. **Detect repository info from git remote**:
|
||||||
|
```bash
|
||||||
|
REMOTE_URL=$(git remote get-url origin)
|
||||||
|
# Parse owner and repo from URL (handles both SSH and HTTPS)
|
||||||
|
OWNER=$(echo "$REMOTE_URL" | sed -E 's#.*[:/]([^/]+)/[^/]+\.git$#\1#')
|
||||||
|
REPO=$(echo "$REMOTE_URL" | sed -E 's#.*/([^/]+)\.git$#\1#')
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Ensure we're on the PR branch**:
|
||||||
|
```bash
|
||||||
|
CURRENT_BRANCH=$(git branch --show-current)
|
||||||
|
# Verify this branch corresponds to the PR
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Respond with**:
|
||||||
|
```
|
||||||
|
Addressing PR review comments for PR #{PR_NUMBER}...
|
||||||
|
|
||||||
|
Repository: {OWNER}/{REPO}
|
||||||
|
Branch: {CURRENT_BRANCH}
|
||||||
|
Gitea URL: {GITEA_URL}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PR Detection
|
||||||
|
|
||||||
|
If no PR number is provided, detect from the current branch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CURRENT_BRANCH=$(git branch --show-current)
|
||||||
|
tea pr list --fields index,head --output simple | grep "$CURRENT_BRANCH"
|
||||||
|
```
|
||||||
|
|
||||||
|
If no PR exists for the current branch, use `AskUserQuestion`:
|
||||||
|
|
||||||
|
```
|
||||||
|
No PR found for branch '{CURRENT_BRANCH}'.
|
||||||
|
|
||||||
|
Would you like to:
|
||||||
|
1. Enter a PR number manually
|
||||||
|
2. Cancel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Step 1: Fetch and Parse Comments
|
||||||
|
|
||||||
|
Fetch all reviews and their comments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Fetch reviews (filter out dismissed reviews)
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" \
|
||||||
|
| jq '[.[] | select(.dismissed != true)]'
|
||||||
|
|
||||||
|
# For each review, fetch comments
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Filter resolved comments**: When processing comments, skip any that have been marked as resolved. Check the `resolver` field in the comment response - if it's not null, the comment has been resolved and should be skipped.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example: Filter to only unresolved comments
|
||||||
|
jq '[.[] | select(.resolver == null)]'
|
||||||
|
```
|
||||||
|
|
||||||
|
If no reviews found or all comments are resolved:
|
||||||
|
```
|
||||||
|
No unresolved reviews found for PR #{PR_NUMBER}.
|
||||||
|
Nothing to address.
|
||||||
|
```
|
||||||
|
**STOP** here.
|
||||||
|
|
||||||
|
### Step 2: Categorize Comments
|
||||||
|
|
||||||
|
For each comment, categorize it as one of:
|
||||||
|
|
||||||
|
| Category | Description | Action |
|
||||||
|
|----------|-------------|--------|
|
||||||
|
| **actionable** | Requests a code change, addition, or fix | Launch subagent to make change |
|
||||||
|
| **question** | Asks for clarification or explanation | Include answer in summary |
|
||||||
|
| **acknowledged** | FYI, self-resolved, or "no action needed" noted | Note in summary |
|
||||||
|
| **blocked** | Requires external input or is out of scope | Flag for user |
|
||||||
|
|
||||||
|
**Categorization heuristics**:
|
||||||
|
- Contains "add", "change", "fix", "update", "consider adding", "should be" → **actionable**
|
||||||
|
- Contains "?" or "why", "how", "what" → **question**
|
||||||
|
- Contains "no need to update", "will be separate", "acknowledged" → **acknowledged**
|
||||||
|
- Contains "discuss", "later", "out of scope", "blocked by" → **blocked**
|
||||||
|
|
||||||
|
Display the categorization:
|
||||||
|
```
|
||||||
|
## Comment Analysis
|
||||||
|
|
||||||
|
### Actionable (will make changes):
|
||||||
|
1. {file}:{line} - "{comment_summary}" → Will add nix note to prerequisites
|
||||||
|
|
||||||
|
### Questions (will answer in summary):
|
||||||
|
2. {file}:{line} - "{comment_summary}" → Explain CI token approach
|
||||||
|
|
||||||
|
### Acknowledged (no action needed):
|
||||||
|
3. {file}:{line} - "{comment_summary}" → Reviewer noted separate skill
|
||||||
|
|
||||||
|
### Blocked (needs input):
|
||||||
|
(none)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: User Confirmation
|
||||||
|
|
||||||
|
Use `AskUserQuestion` to confirm the plan:
|
||||||
|
|
||||||
|
```
|
||||||
|
I've categorized {N} comments. My plan:
|
||||||
|
|
||||||
|
**Will make changes for:**
|
||||||
|
- {file}:{line}: {planned_change}
|
||||||
|
|
||||||
|
**Will explain in summary:**
|
||||||
|
- {file}:{line}: {planned_explanation}
|
||||||
|
|
||||||
|
**No action needed:**
|
||||||
|
- {file}:{line}: {reason}
|
||||||
|
|
||||||
|
Proceed with this plan?
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
1. **Proceed** - Execute the plan
|
||||||
|
2. **Modify** - Let user adjust categorization
|
||||||
|
3. **Cancel** - Exit without changes
|
||||||
|
|
||||||
|
### Step 4: Address Actionable Comments (Parallel Subagents)
|
||||||
|
|
||||||
|
For each actionable comment, launch a subagent using the Task tool:
|
||||||
|
|
||||||
|
```
|
||||||
|
Launch Task subagent with:
|
||||||
|
- subagent_type: "general-purpose"
|
||||||
|
- prompt: |
|
||||||
|
You are addressing a PR review comment. Make the requested change and nothing else.
|
||||||
|
|
||||||
|
**File**: {file_path}
|
||||||
|
**Line**: {line_number}
|
||||||
|
**Comment**: {comment_body}
|
||||||
|
**Diff context**:
|
||||||
|
```
|
||||||
|
{diff_hunk}
|
||||||
|
```
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
1. Read the file to understand context
|
||||||
|
2. Make the minimal change to address the comment
|
||||||
|
3. Do NOT commit - just make the edit
|
||||||
|
4. Report what you changed
|
||||||
|
|
||||||
|
Be precise. Only change what's needed to address this specific comment.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Launch actionable comment subagents in parallel when they touch different files. For comments on the same file, run sequentially to avoid conflicts.
|
||||||
|
|
||||||
|
Wait for all subagents to complete and collect their results.
|
||||||
|
|
||||||
|
### Step 5: Commit and Push
|
||||||
|
|
||||||
|
After all subagents complete:
|
||||||
|
|
||||||
|
1. **Stage changes**:
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create commit with summary**:
|
||||||
|
```bash
|
||||||
|
git commit -m "Address PR review comments
|
||||||
|
|
||||||
|
Changes made:
|
||||||
|
- {file1}: {change_summary}
|
||||||
|
- {file2}: {change_summary}
|
||||||
|
|
||||||
|
Addresses comments from review by {reviewer}"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Push to remote**:
|
||||||
|
```bash
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Post Summary Comment
|
||||||
|
|
||||||
|
Post a single comment summarizing all actions taken:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tea comment $PR_NUMBER "$(cat <<'EOF'
|
||||||
|
## Review Comments Addressed
|
||||||
|
|
||||||
|
cc @{reviewer1} @{reviewer2}
|
||||||
|
|
||||||
|
**Changes made** (commit {SHORT_SHA}):
|
||||||
|
- `{file1}:{line}`: {what_was_changed}
|
||||||
|
- `{file2}:{line}`: {what_was_changed}
|
||||||
|
|
||||||
|
**Responses to questions**:
|
||||||
|
- `{file3}:{line}`: {answer_to_question}
|
||||||
|
|
||||||
|
**Acknowledged** (no action needed):
|
||||||
|
- `{file4}:{line}`: {reason_no_action}
|
||||||
|
|
||||||
|
---
|
||||||
|
*Automated response via /gitea_pr_review*
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Final Summary
|
||||||
|
|
||||||
|
Display to user:
|
||||||
|
|
||||||
|
```
|
||||||
|
## PR Review Complete
|
||||||
|
|
||||||
|
**Commit**: {SHA}
|
||||||
|
**Changes**: {N} files modified
|
||||||
|
|
||||||
|
### Actions Taken:
|
||||||
|
- [x] {file1}:{line} - Added nix prerequisite note
|
||||||
|
- [x] {file2}:{line} - Explained CI approach in comment
|
||||||
|
- [ ] {file3}:{line} - Acknowledged (separate skill)
|
||||||
|
|
||||||
|
**Reviewers tagged**: @{reviewer1}, @{reviewer2}
|
||||||
|
**Comment posted**: {comment_url}
|
||||||
|
|
||||||
|
PR URL: {GITEA_URL}/{OWNER}/{REPO}/pulls/{PR_NUMBER}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: When posting the summary comment, tag all reviewers who left comments so they receive notifications about the changes.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Subagent failed to make change
|
||||||
|
|
||||||
|
If a subagent fails:
|
||||||
|
```
|
||||||
|
Warning: Could not address comment on {file}:{line}
|
||||||
|
|
||||||
|
Reason: {error}
|
||||||
|
|
||||||
|
Options:
|
||||||
|
1. Skip this comment and continue
|
||||||
|
2. Retry with manual guidance
|
||||||
|
3. Abort all changes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Push failed
|
||||||
|
|
||||||
|
```
|
||||||
|
Error pushing changes: {error}
|
||||||
|
|
||||||
|
Your changes are committed locally. You may need to:
|
||||||
|
1. Pull and resolve conflicts: git pull --rebase
|
||||||
|
2. Push again: git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### No actionable comments
|
||||||
|
|
||||||
|
If all comments are questions/acknowledged:
|
||||||
|
```
|
||||||
|
No code changes needed.
|
||||||
|
|
||||||
|
All comments are either questions or acknowledged items.
|
||||||
|
Posting summary comment with explanations...
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Endpoints Used
|
||||||
|
|
||||||
|
| Action | Method | Endpoint |
|
||||||
|
|--------|--------|----------|
|
||||||
|
| List reviews | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews` |
|
||||||
|
| Get review comments | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments` |
|
||||||
|
| Create issue comment | POST | via `tea comment` |
|
||||||
|
|
||||||
|
### Review States
|
||||||
|
|
||||||
|
- `PENDING` - Draft review not yet submitted
|
||||||
|
- `COMMENT` - General comment without approval/rejection
|
||||||
|
- `APPROVE` - Approving the changes
|
||||||
|
- `REQUEST_CHANGES` - Requesting changes before merge
|
||||||
|
|
||||||
|
## Shell Command Patterns
|
||||||
|
|
||||||
|
Claude Code's bash execution has quirks. Use these patterns for reliability:
|
||||||
|
|
||||||
|
### curl requests
|
||||||
|
|
||||||
|
**DO** - Use single quotes for URL and header separately:
|
||||||
|
```bash
|
||||||
|
curl -s 'https://git.example.com/api/v1/repos/owner/repo/pulls/1/reviews' \
|
||||||
|
-H 'Authorization: token YOUR_TOKEN_HERE' | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
**DON'T** - Variable expansion in `-H` flag often fails:
|
||||||
|
```bash
|
||||||
|
# This may fail with "blank argument" errors
|
||||||
|
curl -s -H "Authorization: token $TOKEN" "$URL"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iterating over reviews
|
||||||
|
|
||||||
|
**DO** - Run separate commands for each review ID:
|
||||||
|
```bash
|
||||||
|
echo "=== Review 4 ===" && curl -s 'URL/reviews/4/comments' -H 'Authorization: token ...' | jq .
|
||||||
|
echo "=== Review 5 ===" && curl -s 'URL/reviews/5/comments' -H 'Authorization: token ...' | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
**DON'T** - For loops with multiline bodies often fail:
|
||||||
|
```bash
|
||||||
|
# This may cause syntax errors
|
||||||
|
for id in 4 5 6; do
|
||||||
|
curl -s "URL/reviews/$id/comments"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### tea comment
|
||||||
|
|
||||||
|
**DO** - Use single-quoted string for comment body:
|
||||||
|
```bash
|
||||||
|
tea comment 26 '## Summary
|
||||||
|
|
||||||
|
Changes made:
|
||||||
|
- Item 1
|
||||||
|
- Item 2'
|
||||||
|
```
|
||||||
|
|
||||||
|
**DON'T** - Heredocs may hang or timeout:
|
||||||
|
```bash
|
||||||
|
# This may hang indefinitely
|
||||||
|
tea comment 26 "$(cat <<'EOF'
|
||||||
|
...
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
1. **Thread replies**: Gitea API doesn't support inline thread replies. We post a single summary comment instead.
|
||||||
|
|
||||||
|
2. **Complex changes**: For comments requiring significant refactoring, the subagent may need guidance. The skill will flag these as "blocked" for user input.
|
||||||
|
|
||||||
|
3. **Merge conflicts**: If the branch is behind, you may need to rebase before changes can be pushed.
|
||||||
|
|
||||||
|
## Example Session
|
||||||
|
|
||||||
|
```
|
||||||
|
User: /gitea_pr_review 26
|
||||||
|
Assistant: Addressing PR review comments for PR #26...
|
||||||
|
|
||||||
|
Repository: johno/nixos-configs
|
||||||
|
Branch: bead/nixos-configs-vru
|
||||||
|
Gitea URL: https://git.johnogle.info
|
||||||
|
|
||||||
|
## Comment Analysis
|
||||||
|
|
||||||
|
### Actionable (will make changes):
|
||||||
|
1. gitea_pr_review.md:12 - "could we indicate nix+nixpkgs satisfies this?"
|
||||||
|
→ Will add note that nix users can get tools via nixpkgs
|
||||||
|
|
||||||
|
### Questions (will answer in summary):
|
||||||
|
(none)
|
||||||
|
|
||||||
|
### Acknowledged (no action needed):
|
||||||
|
2. gitea_pr_review.md:50 - "we eventually want to run this in CI..."
|
||||||
|
→ Reviewer noted this will be a separate skill
|
||||||
|
|
||||||
|
Proceed with this plan? [Proceed]
|
||||||
|
|
||||||
|
Launching subagent to address comment 1...
|
||||||
|
[Subagent completes edit]
|
||||||
|
|
||||||
|
Committing changes...
|
||||||
|
[abc1234] Address PR review comments
|
||||||
|
|
||||||
|
Pushing to remote...
|
||||||
|
Done.
|
||||||
|
|
||||||
|
Posting summary comment...
|
||||||
|
|
||||||
|
## PR Review Complete
|
||||||
|
|
||||||
|
**Commit**: abc1234
|
||||||
|
**Changes**: 1 file modified
|
||||||
|
|
||||||
|
### Actions Taken:
|
||||||
|
- [x] gitea_pr_review.md:12 - Added nix prerequisite note
|
||||||
|
- [ ] gitea_pr_review.md:50 - Acknowledged (separate skill)
|
||||||
|
|
||||||
|
**Comment posted**: https://git.johnogle.info/.../pulls/26#issuecomment-XXX
|
||||||
|
|
||||||
|
PR URL: https://git.johnogle.info/johno/nixos-configs/pulls/26
|
||||||
|
```
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `tea` CLI: https://gitea.com/gitea/tea
|
||||||
|
- Gitea API: https://docs.gitea.com/api/
|
||||||
|
- `/beads_workflow` for full development workflow
|
||||||
281
home/roles/development/skills/parallel_beads.md
Normal file
281
home/roles/development/skills/parallel_beads.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
---
|
||||||
|
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. **Filter by plan readiness**:
|
||||||
|
For each ready bead, check if it's ready for batch implementation:
|
||||||
|
- **Has plan** (`thoughts/beads-{id}/plan.md` exists): Include
|
||||||
|
- **type=bug** without plan: Include (simple bugs can implement directly)
|
||||||
|
- **type=feature/task** without plan: Exclude with warning
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for plan existence
|
||||||
|
ls thoughts/beads-{bead-id}/plan.md 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Report skipped beads**:
|
||||||
|
If any beads were skipped, inform the user:
|
||||||
|
```
|
||||||
|
Skipped beads (no plan):
|
||||||
|
- {bead-id}: {title} (type: feature) - Run /beads_plan {bead-id} first
|
||||||
|
- {bead-id}: {title} (type: task) - Run /beads_plan {bead-id} first
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **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
|
||||||
|
- Only show beads that passed the plan check
|
||||||
|
- Allow selection of multiple beads
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
AskUserQuestion with:
|
||||||
|
- question: "Which beads do you want to work on in parallel?"
|
||||||
|
- multiSelect: true
|
||||||
|
- options from filtered 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. **Extract validation criteria**:
|
||||||
|
- Check for a plan: `thoughts/beads-[BEAD_ID]/plan.md`
|
||||||
|
- If plan exists:
|
||||||
|
- Read the plan and find the "Automated Verification" section
|
||||||
|
- Extract each verification command (lines starting with `- [ ]` followed by a command)
|
||||||
|
- Example: `- [ ] Tests pass: \`make test\`` → extract `make test`
|
||||||
|
- If no plan exists, use best-effort validation:
|
||||||
|
- Check if `Makefile` exists → try `make test` and `make lint`
|
||||||
|
- Check if `flake.nix` exists → try `nix flake check`
|
||||||
|
- Check if `package.json` exists → try `npm test`
|
||||||
|
- If none found, note "No validation criteria found"
|
||||||
|
|
||||||
|
4. **Implement the changes**:
|
||||||
|
- Work in the worktree directory
|
||||||
|
- Complete all acceptance criteria listed in the bead
|
||||||
|
|
||||||
|
After implementation, run validation:
|
||||||
|
- Execute each validation command from step 3
|
||||||
|
- Track results in this format:
|
||||||
|
```
|
||||||
|
VALIDATION_RESULTS:
|
||||||
|
- make test: PASS
|
||||||
|
- make lint: FAIL (exit code 1: src/foo.ts:23 - missing semicolon)
|
||||||
|
- nix flake check: SKIP (command not found)
|
||||||
|
```
|
||||||
|
- If any validation fails:
|
||||||
|
- Continue with PR creation (don't block)
|
||||||
|
- Document failures in bead notes: `bd update [BEAD_ID] --notes="Validation failures: [list]"`
|
||||||
|
|
||||||
|
5. **Commit and push**:
|
||||||
|
- Stage all changes: `git add -A`
|
||||||
|
- Create a descriptive commit message
|
||||||
|
- Push the branch: `git push -u origin bead/[BEAD_ID]`
|
||||||
|
|
||||||
|
6. **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]
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
[Include validation results from step 4]
|
||||||
|
|
||||||
|
| Check | Status | Details |
|
||||||
|
|-------|--------|---------|
|
||||||
|
| make test | PASS | |
|
||||||
|
| make lint | FAIL | src/foo.ts:23 - missing semicolon |
|
||||||
|
| nix flake check | SKIP | command not found |
|
||||||
|
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]
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
[Include validation results from step 4]
|
||||||
|
|
||||||
|
| Check | Status | Details |
|
||||||
|
|-------|--------|---------|
|
||||||
|
| make test | PASS | |
|
||||||
|
| make lint | FAIL | src/foo.ts:23 - missing semicolon |
|
||||||
|
| nix flake check | SKIP | command not found |"
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **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]"`
|
||||||
|
|
||||||
|
8. **Report results**:
|
||||||
|
- Return:
|
||||||
|
- PR URL
|
||||||
|
- Bead ID
|
||||||
|
- Implementation status (success/failure/blocked)
|
||||||
|
- Validation summary: `X passed, Y failed, Z skipped`
|
||||||
|
- List of any validation failures with details
|
||||||
|
- If blocked or unable to complete, explain what's blocking progress
|
||||||
|
- If validation failed, include the specific failures so the main agent can summarize them for the user
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
- Validation summary (X/Y passed)
|
||||||
|
- Review summary
|
||||||
|
- Any failures or blockers encountered
|
||||||
|
|
||||||
|
If any validation failures occurred, list them in a "Validation Failures" section so the user can address them.
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```
|
||||||
|
## Parallel Beads Summary
|
||||||
|
|
||||||
|
| Bead | PR | Bead Status | Validation | Review |
|
||||||
|
|------|-----|-------------|------------|--------|
|
||||||
|
| beads-abc | #123 | in_review | 3/3 passed | Approved |
|
||||||
|
| beads-xyz | #124 | in_review | 2/3 passed | Needs changes |
|
||||||
|
| beads-123 | - | open (failed) | - | Blocked by missing dependency |
|
||||||
|
|
||||||
|
### Validation Failures
|
||||||
|
- beads-xyz: `make lint` failed - src/foo.ts:23 missing semicolon
|
||||||
|
|
||||||
|
### Failures/Blockers
|
||||||
|
- beads-123: Could not complete because [reason]
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Fix validation failures before merging
|
||||||
|
- 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
|
||||||
88
home/roles/development/skills/reconcile_beads.md
Normal file
88
home/roles/development/skills/reconcile_beads.md
Normal 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
|
||||||
112
home/roles/emacs/default.nix
Normal file
112
home/roles/emacs/default.nix
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.home.roles.emacs;
|
||||||
|
|
||||||
|
doomEmacs = pkgs.fetchFromGitHub {
|
||||||
|
owner = "doomemacs";
|
||||||
|
repo = "doomemacs";
|
||||||
|
rev = "38d94da67dc84897a4318714dcc48494c016d8c4";
|
||||||
|
sha256 = "sha256-Uc6qONH3jjUVDgW+pPBCGC7mh88ZY05u1y37fQrsxq0=";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Shared emacs packages
|
||||||
|
emacsPackages = epkgs: [
|
||||||
|
epkgs.vterm
|
||||||
|
epkgs.treesit-grammars.with-all-grammars
|
||||||
|
];
|
||||||
|
|
||||||
|
# Default emacs configuration with vterm support
|
||||||
|
defaultEmacsPackage =
|
||||||
|
if pkgs.stdenv.isDarwin
|
||||||
|
then pkgs.emacs-macport.pkgs.withPackages emacsPackages
|
||||||
|
else pkgs.emacs.pkgs.withPackages emacsPackages;
|
||||||
|
|
||||||
|
# Path to doom config directory (relative to this file)
|
||||||
|
doomConfigDir = ./doom;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.home.roles.emacs = {
|
||||||
|
enable = mkEnableOption "Doom Emacs with vterm and tree-sitter support";
|
||||||
|
|
||||||
|
prebuiltDoom = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Use nix-doom-emacs-unstraightened to pre-build all Doom packages at
|
||||||
|
nix build time. This eliminates the need to run `doom sync` after
|
||||||
|
first boot, making it ideal for live USB images or immutable systems.
|
||||||
|
|
||||||
|
When enabled, the doom configuration is read-only (stored in nix store).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
# Common configuration for both modes
|
||||||
|
{
|
||||||
|
home.packages = [
|
||||||
|
pkgs.emacs-all-the-icons-fonts
|
||||||
|
pkgs.fira-code
|
||||||
|
pkgs.fontconfig
|
||||||
|
pkgs.graphviz
|
||||||
|
pkgs.isort
|
||||||
|
pkgs.nerd-fonts.fira-code
|
||||||
|
pkgs.nerd-fonts.droid-sans-mono
|
||||||
|
pkgs.nil # nix lsp language server
|
||||||
|
pkgs.nixfmt-rfc-style
|
||||||
|
(pkgs.ripgrep.override {withPCRE2 = true;})
|
||||||
|
pkgs.pipenv
|
||||||
|
pkgs.poetry
|
||||||
|
pkgs.python3
|
||||||
|
];
|
||||||
|
|
||||||
|
fonts.fontconfig.enable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Standard Doom Emacs mode (requires doom sync at runtime)
|
||||||
|
(mkIf (!cfg.prebuiltDoom) {
|
||||||
|
programs.emacs = {
|
||||||
|
enable = true;
|
||||||
|
package = defaultEmacsPackage;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Mount emacs and tree-sitter grammars from nix store
|
||||||
|
home.file = {
|
||||||
|
"${config.xdg.configHome}/emacs".source = doomEmacs;
|
||||||
|
};
|
||||||
|
|
||||||
|
home.sessionPath = [
|
||||||
|
"${config.xdg.configHome}/emacs/bin"
|
||||||
|
];
|
||||||
|
|
||||||
|
home.sessionVariables = {
|
||||||
|
DOOMDIR = "${config.xdg.configHome}/doom";
|
||||||
|
DOOMLOCALDIR = "${config.xdg.dataHome}/doom";
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO: Use mkOutOfStoreSymlink instead?
|
||||||
|
home.activation.doomConfig = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
|
# Always remove and recreate the symlink to ensure it points to the source directory
|
||||||
|
rm -rf "${config.xdg.configHome}/doom"
|
||||||
|
ln -sf "${config.home.homeDirectory}/nixos-configs/home/roles/emacs/doom" "${config.xdg.configHome}/doom"
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
|
||||||
|
# Pre-built Doom Emacs mode (no doom sync needed - ideal for live USB)
|
||||||
|
(mkIf cfg.prebuiltDoom {
|
||||||
|
programs.doom-emacs = {
|
||||||
|
enable = true;
|
||||||
|
doomDir = doomConfigDir;
|
||||||
|
doomLocalDir = "${config.xdg.dataHome}/doom";
|
||||||
|
# Add extra packages that aren't part of Doom but needed for our config
|
||||||
|
extraPackages = epkgs: [
|
||||||
|
epkgs.vterm
|
||||||
|
epkgs.treesit-grammars.with-all-grammars
|
||||||
|
];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -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,38 @@
|
|||||||
: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))
|
||||||
|
|
||||||
|
(use-package! beads
|
||||||
|
:commands (beads)
|
||||||
|
:init
|
||||||
|
(map! :leader
|
||||||
|
(:prefix ("o" . "open")
|
||||||
|
(:prefix ("B" . "beads")
|
||||||
|
:desc "List issues" "B" (cmd! (require 'beads) (beads-list))
|
||||||
|
:desc "Project issues" "p" (cmd! (require 'beads) (beads-project-list))
|
||||||
|
:desc "Activity feed" "a" (cmd! (require 'beads) (beads-activity))
|
||||||
|
:desc "Stale issues" "s" (cmd! (require 'beads) (beads-stale))
|
||||||
|
:desc "Orphaned issues" "o" (cmd! (require 'beads) (beads-orphans))
|
||||||
|
:desc "Find duplicates" "d" (cmd! (require 'beads) (beads-duplicates))
|
||||||
|
:desc "Lint issues" "l" (cmd! (require 'beads) (beads-lint))))))
|
||||||
|
|
||||||
(after! gptel
|
(after! gptel
|
||||||
(require 'gptel-tool-library)
|
(require 'gptel-tool-library)
|
||||||
@@ -159,6 +188,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.
|
||||||
;;
|
;;
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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")
|
||||||
123
home/roles/email/default.nix
Normal file
123
home/roles/email/default.nix
Normal 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" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
496
home/roles/i3+sway/default.nix
Normal file
496
home/roles/i3+sway/default.nix
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.home.roles.i3_sway;
|
||||||
|
wallpaperConfig = import ../../wallpapers;
|
||||||
|
currentWallpaper = builtins.elemAt wallpaperConfig.wallpapers wallpaperConfig.currentIndex;
|
||||||
|
|
||||||
|
shared_config = recursiveUpdate rec {
|
||||||
|
modifier = "Mod4";
|
||||||
|
terminal = "ghostty";
|
||||||
|
defaultWorkspace = "workspace number 1";
|
||||||
|
|
||||||
|
keybindings = {
|
||||||
|
"${shared_config.modifier}+Return" = "exec ${terminal}";
|
||||||
|
"${shared_config.modifier}+Shift+Return" = "exec ${cfg.browser}";
|
||||||
|
"${shared_config.modifier}+Shift+q" = "kill";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+a" = "focus parent";
|
||||||
|
"${shared_config.modifier}+Shift+a" = "focus child";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+h" = "focus left";
|
||||||
|
"${shared_config.modifier}+j" = "focus down";
|
||||||
|
"${shared_config.modifier}+k" = "focus up";
|
||||||
|
"${shared_config.modifier}+l" = "focus right";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+Shift+h" = "move left";
|
||||||
|
"${shared_config.modifier}+Shift+j" = "move down";
|
||||||
|
"${shared_config.modifier}+Shift+k" = "move up";
|
||||||
|
"${shared_config.modifier}+Shift+l" = "move right";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+Left" = "focus left";
|
||||||
|
"${shared_config.modifier}+Down" = "focus down";
|
||||||
|
"${shared_config.modifier}+Up" = "focus up";
|
||||||
|
"${shared_config.modifier}+Right" = "focus right";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+Shift+Left" = "move left";
|
||||||
|
"${shared_config.modifier}+Shift+Down" = "move down";
|
||||||
|
"${shared_config.modifier}+Shift+Up" = "move up";
|
||||||
|
"${shared_config.modifier}+Shift+Right" = "move right";
|
||||||
|
|
||||||
|
#"${shared_config.modifier}+h" = "split h";
|
||||||
|
"${shared_config.modifier}+v" = "split v";
|
||||||
|
"${shared_config.modifier}+Shift+f" = "fullscreen toggle";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+s" = "layout stacking";
|
||||||
|
"${shared_config.modifier}+w" = "layout tabbed";
|
||||||
|
"${shared_config.modifier}+e" = "layout toggle split";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+Shift+space" = "floating toggle";
|
||||||
|
"${shared_config.modifier}+space" = "focus mode_toggle";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+Shift+minus" = "move scratchpad";
|
||||||
|
"${shared_config.modifier}+minus" = "scratchpad show";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+1" = "workspace number 1";
|
||||||
|
"${shared_config.modifier}+2" = "workspace number 2";
|
||||||
|
"${shared_config.modifier}+3" = "workspace number 3";
|
||||||
|
"${shared_config.modifier}+4" = "workspace number 4";
|
||||||
|
"${shared_config.modifier}+5" = "workspace number 5";
|
||||||
|
"${shared_config.modifier}+6" = "workspace number 6";
|
||||||
|
"${shared_config.modifier}+7" = "workspace number 7";
|
||||||
|
"${shared_config.modifier}+8" = "workspace number 8";
|
||||||
|
"${shared_config.modifier}+9" = "workspace number 9";
|
||||||
|
"${shared_config.modifier}+0" = "workspace number 10";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+Shift+1" =
|
||||||
|
"move container to workspace number 1";
|
||||||
|
"${shared_config.modifier}+Shift+2" =
|
||||||
|
"move container to workspace number 2";
|
||||||
|
"${shared_config.modifier}+Shift+3" =
|
||||||
|
"move container to workspace number 3";
|
||||||
|
"${shared_config.modifier}+Shift+4" =
|
||||||
|
"move container to workspace number 4";
|
||||||
|
"${shared_config.modifier}+Shift+5" =
|
||||||
|
"move container to workspace number 5";
|
||||||
|
"${shared_config.modifier}+Shift+6" =
|
||||||
|
"move container to workspace number 6";
|
||||||
|
"${shared_config.modifier}+Shift+7" =
|
||||||
|
"move container to workspace number 7";
|
||||||
|
"${shared_config.modifier}+Shift+8" =
|
||||||
|
"move container to workspace number 8";
|
||||||
|
"${shared_config.modifier}+Shift+9" =
|
||||||
|
"move container to workspace number 9";
|
||||||
|
"${shared_config.modifier}+Shift+0" =
|
||||||
|
"move container to workspace number 10";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+Shift+c" = "reload";
|
||||||
|
"${shared_config.modifier}+Shift+r" = "restart";
|
||||||
|
|
||||||
|
"${shared_config.modifier}+r" = "mode resize";
|
||||||
|
|
||||||
|
"XF86MonBrightnessUp" = "exec ddcutil setvcp 10 + 5";
|
||||||
|
"XF86MonBrightnessDown" = "exec ddcutil setvcp 10 - 5";
|
||||||
|
};
|
||||||
|
} cfg.extraSharedConfig;
|
||||||
|
in {
|
||||||
|
options.home.roles.i3_sway = {
|
||||||
|
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 {
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
description = "Extra configuration shared between i3 and sway";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraI3Config = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
description = "Extra i3-specific configuration";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraSwayConfig = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
description = "Extra sway-specific configuration";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# i3blocks configuration file
|
||||||
|
home.file.".config/i3blocks/config".text = ''
|
||||||
|
# i3blocks config - replicating waybar setup
|
||||||
|
separator_block_width=15
|
||||||
|
markup=pango
|
||||||
|
|
||||||
|
[disk]
|
||||||
|
command=df -h / | awk 'NR==2 {print "💾 " $5}'
|
||||||
|
interval=30
|
||||||
|
separator=true
|
||||||
|
|
||||||
|
[cpu]
|
||||||
|
command=top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print "🧠 " int(100 - $1) "%"}'
|
||||||
|
interval=2
|
||||||
|
separator=true
|
||||||
|
|
||||||
|
[memory]
|
||||||
|
command=free | awk 'NR==2 {printf "🐏 %.0f%%\n", $3*100/$2}'
|
||||||
|
interval=5
|
||||||
|
separator=true
|
||||||
|
|
||||||
|
[pulseaudio]
|
||||||
|
command=${pkgs.writeShellScript "i3blocks-pulseaudio" ''
|
||||||
|
volume=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -Po '\d+%' | head -1)
|
||||||
|
muted=$(pactl get-sink-mute @DEFAULT_SINK@ | grep -o 'yes')
|
||||||
|
if [ "$muted" = "yes" ]; then
|
||||||
|
echo "🔇"
|
||||||
|
else
|
||||||
|
vol_num=''${volume%\%}
|
||||||
|
if [ $vol_num -le 33 ]; then
|
||||||
|
echo "🔈 $volume"
|
||||||
|
elif [ $vol_num -le 66 ]; then
|
||||||
|
echo "🔉 $volume"
|
||||||
|
else
|
||||||
|
echo "🔊 $volume"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
interval=1
|
||||||
|
signal=10
|
||||||
|
separator=true
|
||||||
|
|
||||||
|
[backlight]
|
||||||
|
command=${pkgs.writeShellScript "i3blocks-backlight" ''
|
||||||
|
if command -v ddcutil &>/dev/null; then
|
||||||
|
# Handle mouse scroll events
|
||||||
|
case $BLOCK_BUTTON in
|
||||||
|
4) ddcutil setvcp 10 + 5 ;; # Scroll up - increase brightness
|
||||||
|
5) ddcutil setvcp 10 - 5 ;; # Scroll down - decrease brightness
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Display current brightness
|
||||||
|
brightness=$(ddcutil getvcp 10 2>/dev/null | grep -oP 'current value =\s*\K\d+')
|
||||||
|
if [ -n "$brightness" ]; then
|
||||||
|
echo "☀️ $brightness%"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
interval=5
|
||||||
|
separator=true
|
||||||
|
|
||||||
|
[network]
|
||||||
|
command=${pkgs.writeShellScript "i3blocks-network" ''
|
||||||
|
if iwgetid -r &>/dev/null; then
|
||||||
|
ssid=$(iwgetid -r)
|
||||||
|
signal=$(grep "^\s*w" /proc/net/wireless | awk '{print int($3 * 100 / 70)}')
|
||||||
|
echo "📶 $ssid ($signal%)"
|
||||||
|
else
|
||||||
|
ip=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '127.0.0.1' | head -1)
|
||||||
|
if [ -n "$ip" ]; then
|
||||||
|
echo "🔌 $ip"
|
||||||
|
else
|
||||||
|
echo "❌"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
interval=5
|
||||||
|
separator=true
|
||||||
|
|
||||||
|
[battery]
|
||||||
|
command=${pkgs.writeShellScript "i3blocks-battery" ''
|
||||||
|
if [ -d /sys/class/power_supply/BAT0 ]; then
|
||||||
|
capacity=$(cat /sys/class/power_supply/BAT0/capacity)
|
||||||
|
status=$(cat /sys/class/power_supply/BAT0/status)
|
||||||
|
|
||||||
|
if [ "$status" = "Charging" ]; then
|
||||||
|
echo "⚡ $capacity%"
|
||||||
|
else
|
||||||
|
echo "🔋 $capacity%"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
interval=10
|
||||||
|
separator=true
|
||||||
|
|
||||||
|
[time]
|
||||||
|
command=date '+%Y-%m-%d %H:%M'
|
||||||
|
interval=1
|
||||||
|
separator=false
|
||||||
|
'';
|
||||||
|
|
||||||
|
xsession.windowManager.i3 = let
|
||||||
|
base_i3_config = recursiveUpdate shared_config {
|
||||||
|
bars = [{
|
||||||
|
position = "bottom";
|
||||||
|
statusCommand = "${pkgs.i3blocks}/bin/i3blocks";
|
||||||
|
trayOutput = "primary"; # Enable system tray on primary output
|
||||||
|
fonts = {
|
||||||
|
names = [ "Fira Code" "monospace" ];
|
||||||
|
size = 11.0;
|
||||||
|
};
|
||||||
|
colors = {
|
||||||
|
background = "#000000";
|
||||||
|
statusline = "#ffffff";
|
||||||
|
separator = "#666666";
|
||||||
|
|
||||||
|
# Workspace button colors (matching waybar)
|
||||||
|
focusedWorkspace = {
|
||||||
|
border = "#285577";
|
||||||
|
background = "#285577";
|
||||||
|
text = "#ffffff";
|
||||||
|
};
|
||||||
|
activeWorkspace = {
|
||||||
|
border = "#5f676a";
|
||||||
|
background = "#5f676a";
|
||||||
|
text = "#ffffff";
|
||||||
|
};
|
||||||
|
inactiveWorkspace = {
|
||||||
|
border = "#222222";
|
||||||
|
background = "#222222";
|
||||||
|
text = "#888888";
|
||||||
|
};
|
||||||
|
urgentWorkspace = {
|
||||||
|
border = "#900000";
|
||||||
|
background = "#900000";
|
||||||
|
text = "#ffffff";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
keybindings = shared_config.keybindings // {
|
||||||
|
"${shared_config.modifier}+d" = "exec rofi -show drun";
|
||||||
|
"${shared_config.modifier}+Shift+e" =
|
||||||
|
"exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
|
||||||
|
};
|
||||||
|
startup = [
|
||||||
|
# GNOME polkit authentication agent
|
||||||
|
{
|
||||||
|
command = "/run/current-system/sw/libexec/polkit-gnome-authentication-agent-1";
|
||||||
|
always = false;
|
||||||
|
notification = false;
|
||||||
|
}
|
||||||
|
# Picom compositor for smooth rendering and no tearing (important for Nvidia)
|
||||||
|
{
|
||||||
|
command = "picom --backend glx -b";
|
||||||
|
always = false;
|
||||||
|
notification = false;
|
||||||
|
}
|
||||||
|
# NetworkManager system tray applet
|
||||||
|
{
|
||||||
|
command = "nm-applet";
|
||||||
|
always = false;
|
||||||
|
notification = false;
|
||||||
|
}
|
||||||
|
# Set wallpaper with feh
|
||||||
|
{
|
||||||
|
command = "feh ${currentWallpaper.feh} ${currentWallpaper.file}";
|
||||||
|
always = false;
|
||||||
|
notification = false;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
enable = true;
|
||||||
|
config = recursiveUpdate base_i3_config cfg.extraI3Config;
|
||||||
|
};
|
||||||
|
|
||||||
|
wayland.windowManager.sway = let
|
||||||
|
base_sway_config = recursiveUpdate shared_config {
|
||||||
|
bars = []; # Disable default bar, use waybar instead
|
||||||
|
keybindings = shared_config.keybindings // {
|
||||||
|
"${shared_config.modifier}+d" = "exec wofi --show drun";
|
||||||
|
"${shared_config.modifier}+Shift+e" =
|
||||||
|
"exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
|
||||||
|
};
|
||||||
|
input = {
|
||||||
|
"type:keyboard" = {
|
||||||
|
xkb_options = "caps:escape";
|
||||||
|
};
|
||||||
|
"type:touchpad" = {
|
||||||
|
tap = "enabled";
|
||||||
|
tap_button_map = "lrm";
|
||||||
|
drag = "enabled";
|
||||||
|
natural_scroll = "disabled";
|
||||||
|
dwt = "enabled";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
output = {
|
||||||
|
"*" = {
|
||||||
|
bg = "${currentWallpaper.file} ${currentWallpaper.sway}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
startup = [
|
||||||
|
# Launch waybar status bar
|
||||||
|
{
|
||||||
|
command = "waybar";
|
||||||
|
always = false;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
enable = true;
|
||||||
|
extraOptions = [ "--unsupported-gpu" ];
|
||||||
|
config = recursiveUpdate base_sway_config cfg.extraSwayConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.waybar = {
|
||||||
|
enable = true;
|
||||||
|
systemd.enable = false; # Don't auto-start via systemd - only launch in sway
|
||||||
|
settings = {
|
||||||
|
mainBar = {
|
||||||
|
layer = "top";
|
||||||
|
position = "bottom";
|
||||||
|
height = 30;
|
||||||
|
spacing = 4;
|
||||||
|
|
||||||
|
modules-left = [ "sway/workspaces" "sway/mode" ];
|
||||||
|
modules-center = [ ];
|
||||||
|
modules-right = [ "disk" "cpu" "memory" "pulseaudio" "custom/backlight-ddc" "backlight" "network" "battery" "tray" "clock" ];
|
||||||
|
|
||||||
|
"sway/workspaces" = {
|
||||||
|
disable-scroll = true;
|
||||||
|
all-outputs = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
"clock" = {
|
||||||
|
format = "{:%Y-%m-%d %H:%M}";
|
||||||
|
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
||||||
|
calendar = {
|
||||||
|
mode = "year";
|
||||||
|
mode-mon-col = 3;
|
||||||
|
weeks-pos = "right";
|
||||||
|
on-scroll = 1;
|
||||||
|
format = {
|
||||||
|
months = "<span color='#ffead3'><b>{}</b></span>";
|
||||||
|
days = "<span color='#ecc6d9'><b>{}</b></span>";
|
||||||
|
weeks = "<span color='#99ffdd'><b>W{}</b></span>";
|
||||||
|
weekdays = "<span color='#ffcc66'><b>{}</b></span>";
|
||||||
|
today = "<span color='#ff6699'><b><u>{}</u></b></span>";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"disk" = {
|
||||||
|
interval = 30;
|
||||||
|
format = "💾 {percentage_used}%";
|
||||||
|
path = "/";
|
||||||
|
tooltip-format = "Used: {used} / {total} ({percentage_used}%)\nFree: {free} ({percentage_free}%)";
|
||||||
|
};
|
||||||
|
|
||||||
|
"cpu" = {
|
||||||
|
format = "🧠 {usage}%";
|
||||||
|
tooltip = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
"memory" = {
|
||||||
|
format = "🐏 {percentage}%";
|
||||||
|
tooltip-format = "RAM: {used:0.1f}G / {total:0.1f}G";
|
||||||
|
};
|
||||||
|
|
||||||
|
"pulseaudio" = {
|
||||||
|
format = "{icon} {volume}%";
|
||||||
|
format-muted = "🔇";
|
||||||
|
format-icons = {
|
||||||
|
headphone = "🎧";
|
||||||
|
default = [ "🔈" "🔉" "🔊" ];
|
||||||
|
};
|
||||||
|
on-click = "pavucontrol";
|
||||||
|
};
|
||||||
|
|
||||||
|
"backlight" = {
|
||||||
|
format = "☀️ {percent}%";
|
||||||
|
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" = {
|
||||||
|
format-wifi = "📶 {essid} ({signalStrength}%)";
|
||||||
|
format-ethernet = "🔌 {ipaddr}";
|
||||||
|
format-disconnected = "❌";
|
||||||
|
tooltip-format = "{ifname}: {ipaddr}/{cidr}";
|
||||||
|
};
|
||||||
|
|
||||||
|
"battery" = {
|
||||||
|
states = {
|
||||||
|
warning = 30;
|
||||||
|
critical = 15;
|
||||||
|
};
|
||||||
|
format = "{icon} {capacity}%";
|
||||||
|
format-charging = "⚡ {capacity}%";
|
||||||
|
format-icons = [ "🪫" "🔋" "🔋" "🔋" "🔋" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
"tray" = {
|
||||||
|
spacing = 10;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
style = ''
|
||||||
|
* {
|
||||||
|
padding: 0 4px;
|
||||||
|
font-family: "Fira Code", monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button {
|
||||||
|
padding: 0 8px;
|
||||||
|
background-color: #333333;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.focused {
|
||||||
|
background-color: #285577;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.visible {
|
||||||
|
background-color: #5f676a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.urgent {
|
||||||
|
background-color: #900000;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.rofi = {
|
||||||
|
enable = true;
|
||||||
|
theme = "solarized";
|
||||||
|
extraConfig = {
|
||||||
|
modi = "drun,run,window";
|
||||||
|
show-icons = true;
|
||||||
|
drun-display-format = "{name}";
|
||||||
|
disable-history = false;
|
||||||
|
hide-scrollbar = true;
|
||||||
|
display-drun = " Apps";
|
||||||
|
display-run = " Run";
|
||||||
|
display-window = " Windows";
|
||||||
|
sidebar-mode = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, globalInputs, system, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.programs.kubectl-secure;
|
cfg = config.home.roles.kubectl;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.programs.kubectl-secure = {
|
options.home.roles.kubectl = {
|
||||||
enable = mkEnableOption "secure kubectl configuration with Bitwarden integration";
|
enable = mkEnableOption "management tools for the homelab k3s oglenet cluster with secure Bitwarden integration";
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
@@ -21,29 +21,29 @@ in
|
|||||||
programs.bash.initExtra = mkAfter ''
|
programs.bash.initExtra = mkAfter ''
|
||||||
# Kubectl secure session management
|
# Kubectl secure session management
|
||||||
export KUBECTL_SESSION_DIR="/dev/shm/kubectl-$$"
|
export KUBECTL_SESSION_DIR="/dev/shm/kubectl-$$"
|
||||||
|
|
||||||
kube-select() {
|
kube-select() {
|
||||||
if [[ $# -ne 1 ]]; then
|
if [[ $# -ne 1 ]]; then
|
||||||
echo "Usage: kube-select <context-name>"
|
echo "Usage: kube-select <context-name>"
|
||||||
echo "Available contexts: $(kube-list)"
|
echo "Available contexts: $(kube-list)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local context="$1"
|
local context="$1"
|
||||||
|
|
||||||
# Clean up any existing session first
|
# Clean up any existing session first
|
||||||
kube-clear 2>/dev/null
|
kube-clear 2>/dev/null
|
||||||
|
|
||||||
# Create new session directory
|
# Create new session directory
|
||||||
mkdir -p "$KUBECTL_SESSION_DIR"
|
mkdir -p "$KUBECTL_SESSION_DIR"
|
||||||
chmod 700 "$KUBECTL_SESSION_DIR"
|
chmod 700 "$KUBECTL_SESSION_DIR"
|
||||||
|
|
||||||
# Set cleanup trap for this shell session
|
# Set cleanup trap for this shell session
|
||||||
trap "rm -rf '$KUBECTL_SESSION_DIR' 2>/dev/null" EXIT
|
trap "rm -rf '$KUBECTL_SESSION_DIR' 2>/dev/null" EXIT
|
||||||
|
|
||||||
# Set KUBECONFIG for this session
|
# Set KUBECONFIG for this session
|
||||||
export KUBECONFIG="$KUBECTL_SESSION_DIR/config"
|
export KUBECONFIG="$KUBECTL_SESSION_DIR/config"
|
||||||
|
|
||||||
# Load config from Bitwarden secure notes
|
# Load config from Bitwarden secure notes
|
||||||
if ! rbw get "kubectl-$context" > "$KUBECONFIG" 2>/dev/null; then
|
if ! rbw get "kubectl-$context" > "$KUBECONFIG" 2>/dev/null; then
|
||||||
echo "Error: Could not retrieve kubectl-$context from Bitwarden"
|
echo "Error: Could not retrieve kubectl-$context from Bitwarden"
|
||||||
@@ -51,37 +51,37 @@ in
|
|||||||
kube-clear
|
kube-clear
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify the kubeconfig is valid
|
# Verify the kubeconfig is valid
|
||||||
if ! kubectl config view >/dev/null 2>&1; then
|
if ! kubectl config view >/dev/null 2>&1; then
|
||||||
echo "Error: Invalid kubeconfig retrieved from Bitwarden"
|
echo "Error: Invalid kubeconfig retrieved from Bitwarden"
|
||||||
kube-clear
|
kube-clear
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Loaded kubectl context: $context (session: $$)"
|
echo "✓ Loaded kubectl context: $context (session: $$)"
|
||||||
echo " Config location: $KUBECONFIG"
|
echo " Config location: $KUBECONFIG"
|
||||||
}
|
}
|
||||||
|
|
||||||
kube-list() {
|
kube-list() {
|
||||||
echo "Available kubectl contexts in Bitwarden:"
|
echo "Available kubectl contexts in Bitwarden:"
|
||||||
rbw search kubectl- 2>/dev/null | grep "^kubectl-" | sed 's/^kubectl-/ - /' || echo " (none found or rbw not accessible)"
|
rbw search kubectl- 2>/dev/null | grep "^kubectl-" | sed 's/^kubectl-/ - /' || echo " (none found or rbw not accessible)"
|
||||||
}
|
}
|
||||||
|
|
||||||
kube-clear() {
|
kube-clear() {
|
||||||
if [[ -n "$KUBECTL_TIMEOUT_PID" ]]; then
|
if [[ -n "$KUBECTL_TIMEOUT_PID" ]]; then
|
||||||
kill "$KUBECTL_TIMEOUT_PID" 2>/dev/null
|
kill "$KUBECTL_TIMEOUT_PID" 2>/dev/null
|
||||||
unset KUBECTL_TIMEOUT_PID
|
unset KUBECTL_TIMEOUT_PID
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -d "$KUBECTL_SESSION_DIR" ]]; then
|
if [[ -d "$KUBECTL_SESSION_DIR" ]]; then
|
||||||
rm -rf "$KUBECTL_SESSION_DIR"
|
rm -rf "$KUBECTL_SESSION_DIR"
|
||||||
echo "Cleared kubectl session ($$)"
|
echo "Cleared kubectl session ($$)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
unset KUBECONFIG
|
unset KUBECONFIG
|
||||||
}
|
}
|
||||||
|
|
||||||
kube-status() {
|
kube-status() {
|
||||||
if [[ -f "$KUBECONFIG" ]]; then
|
if [[ -f "$KUBECONFIG" ]]; then
|
||||||
local current_context
|
local current_context
|
||||||
@@ -89,7 +89,7 @@ in
|
|||||||
if [[ -n "$current_context" ]]; then
|
if [[ -n "$current_context" ]]; then
|
||||||
echo "Active kubectl context: $current_context"
|
echo "Active kubectl context: $current_context"
|
||||||
echo "Session: $$ | Config: $KUBECONFIG"
|
echo "Session: $$ | Config: $KUBECONFIG"
|
||||||
|
|
||||||
# Show cluster info
|
# Show cluster info
|
||||||
local cluster_server
|
local cluster_server
|
||||||
cluster_server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null)
|
cluster_server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null)
|
||||||
@@ -128,33 +128,33 @@ in
|
|||||||
echo "Note: Kubeconfigs are stored as secure notes in Bitwarden"
|
echo "Note: Kubeconfigs are stored as secure notes in Bitwarden"
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
programs.zsh.initExtra = mkAfter ''
|
programs.zsh.initExtra = mkAfter ''
|
||||||
# Kubectl secure session management (zsh)
|
# Kubectl secure session management (zsh)
|
||||||
export KUBECTL_SESSION_DIR="/dev/shm/kubectl-$$"
|
export KUBECTL_SESSION_DIR="/dev/shm/kubectl-$$"
|
||||||
|
|
||||||
kube-select() {
|
kube-select() {
|
||||||
if [[ $# -ne 1 ]]; then
|
if [[ $# -ne 1 ]]; then
|
||||||
echo "Usage: kube-select <context-name>"
|
echo "Usage: kube-select <context-name>"
|
||||||
echo "Available contexts: $(kube-list)"
|
echo "Available contexts: $(kube-list)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local context="$1"
|
local context="$1"
|
||||||
|
|
||||||
# Clean up any existing session first
|
# Clean up any existing session first
|
||||||
kube-clear 2>/dev/null
|
kube-clear 2>/dev/null
|
||||||
|
|
||||||
# Create new session directory
|
# Create new session directory
|
||||||
mkdir -p "$KUBECTL_SESSION_DIR"
|
mkdir -p "$KUBECTL_SESSION_DIR"
|
||||||
chmod 700 "$KUBECTL_SESSION_DIR"
|
chmod 700 "$KUBECTL_SESSION_DIR"
|
||||||
|
|
||||||
# Set cleanup trap for this shell session
|
# Set cleanup trap for this shell session
|
||||||
trap "rm -rf '$KUBECTL_SESSION_DIR' 2>/dev/null" EXIT
|
trap "rm -rf '$KUBECTL_SESSION_DIR' 2>/dev/null" EXIT
|
||||||
|
|
||||||
# Set KUBECONFIG for this session
|
# Set KUBECONFIG for this session
|
||||||
export KUBECONFIG="$KUBECTL_SESSION_DIR/config"
|
export KUBECONFIG="$KUBECTL_SESSION_DIR/config"
|
||||||
|
|
||||||
# Load config from Bitwarden secure notes
|
# Load config from Bitwarden secure notes
|
||||||
if ! rbw get "kubectl-$context" > "$KUBECONFIG" 2>/dev/null; then
|
if ! rbw get "kubectl-$context" > "$KUBECONFIG" 2>/dev/null; then
|
||||||
echo "Error: Could not retrieve kubectl-$context from Bitwarden"
|
echo "Error: Could not retrieve kubectl-$context from Bitwarden"
|
||||||
@@ -162,43 +162,37 @@ in
|
|||||||
kube-clear
|
kube-clear
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify the kubeconfig is valid
|
# Verify the kubeconfig is valid
|
||||||
if ! kubectl config view >/dev/null 2>&1; then
|
if ! kubectl config view >/dev/null 2>&1; then
|
||||||
echo "Error: Invalid kubeconfig retrieved from Bitwarden"
|
echo "Error: Invalid kubeconfig retrieved from Bitwarden"
|
||||||
kube-clear
|
kube-clear
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Loaded kubectl context: $context (session: $$)"
|
echo "✓ Loaded kubectl context: $context (session: $$)"
|
||||||
echo " Config location: $KUBECONFIG"
|
echo " Config location: $KUBECONFIG"
|
||||||
|
|
||||||
# Optional: Set timeout cleanup
|
|
||||||
if [[ ${toString cfg.sessionTimeout} -gt 0 ]]; then
|
|
||||||
(sleep ${toString cfg.sessionTimeout}; kube-clear 2>/dev/null) &
|
|
||||||
export KUBECTL_TIMEOUT_PID=$!
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kube-list() {
|
kube-list() {
|
||||||
echo "Available kubectl contexts in Bitwarden:"
|
echo "Available kubectl contexts in Bitwarden:"
|
||||||
rbw search kubectl- 2>/dev/null | grep "^kubectl-" | sed 's/^kubectl-/ - /' || echo " (none found or rbw not accessible)"
|
rbw search kubectl- 2>/dev/null | grep "^kubectl-" | sed 's/^kubectl-/ - /' || echo " (none found or rbw not accessible)"
|
||||||
}
|
}
|
||||||
|
|
||||||
kube-clear() {
|
kube-clear() {
|
||||||
if [[ -n "$KUBECTL_TIMEOUT_PID" ]]; then
|
if [[ -n "$KUBECTL_TIMEOUT_PID" ]]; then
|
||||||
kill "$KUBECTL_TIMEOUT_PID" 2>/dev/null
|
kill "$KUBECTL_TIMEOUT_PID" 2>/dev/null
|
||||||
unset KUBECTL_TIMEOUT_PID
|
unset KUBECTL_TIMEOUT_PID
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -d "$KUBECTL_SESSION_DIR" ]]; then
|
if [[ -d "$KUBECTL_SESSION_DIR" ]]; then
|
||||||
rm -rf "$KUBECTL_SESSION_DIR"
|
rm -rf "$KUBECTL_SESSION_DIR"
|
||||||
echo "Cleared kubectl session ($$)"
|
echo "Cleared kubectl session ($$)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
unset KUBECONFIG
|
unset KUBECONFIG
|
||||||
}
|
}
|
||||||
|
|
||||||
kube-status() {
|
kube-status() {
|
||||||
if [[ -f "$KUBECONFIG" ]]; then
|
if [[ -f "$KUBECONFIG" ]]; then
|
||||||
local current_context
|
local current_context
|
||||||
@@ -206,7 +200,7 @@ in
|
|||||||
if [[ -n "$current_context" ]]; then
|
if [[ -n "$current_context" ]]; then
|
||||||
echo "Active kubectl context: $current_context"
|
echo "Active kubectl context: $current_context"
|
||||||
echo "Session: $$ | Config: $KUBECONFIG"
|
echo "Session: $$ | Config: $KUBECONFIG"
|
||||||
|
|
||||||
# Show cluster info
|
# Show cluster info
|
||||||
local cluster_server
|
local cluster_server
|
||||||
cluster_server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null)
|
cluster_server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null)
|
||||||
36
home/roles/launchers/default.nix
Normal file
36
home/roles/launchers/default.nix
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.home.roles.launchers;
|
||||||
|
|
||||||
|
# Generate a wrapper script for a package
|
||||||
|
makeLauncher = packageName: pkgs.writeShellScriptBin packageName ''
|
||||||
|
exec env NIXPKGS_ALLOW_UNFREE=1 ${pkgs.nix}/bin/nix run --impure nixpkgs#${packageName} -- "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Generate all launcher scripts from the package list
|
||||||
|
launcherPackages = map makeLauncher cfg.packages;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.home.roles.launchers = {
|
||||||
|
enable = mkEnableOption "wrapper launchers for excluded packages";
|
||||||
|
|
||||||
|
packages = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
example = [ "steam" "libreoffice" "lutris" ];
|
||||||
|
description = ''
|
||||||
|
List of package names to create launcher wrappers for.
|
||||||
|
Each wrapper will run: NIXPKGS_ALLOW_UNFREE=1 nix run --impure nixpkgs#<package>
|
||||||
|
|
||||||
|
This is useful for occasionally running packages without permanently installing them.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
home.packages = launcherPackages;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -18,6 +18,10 @@ in
|
|||||||
delfin
|
delfin
|
||||||
moonlight-qt
|
moonlight-qt
|
||||||
vlc
|
vlc
|
||||||
|
|
||||||
|
# Spotify client
|
||||||
|
# Using unstable version for better authentication support
|
||||||
|
unstable.ncspot
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
199
home/roles/plasma-manager-kodi/default.nix
Normal file
199
home/roles/plasma-manager-kodi/default.nix
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
190
home/roles/plasma-manager/default.nix
Normal file
190
home/roles/plasma-manager/default.nix
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.home.roles.plasma-manager;
|
||||||
|
wallpaperConfig = import ../../wallpapers;
|
||||||
|
currentWallpaper = builtins.elemAt wallpaperConfig.wallpapers wallpaperConfig.currentIndex;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.home.roles.plasma-manager = {
|
||||||
|
enable = mkEnableOption "KDE Plasma desktop environment configuration";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# The current KDE config can be output with the command:
|
||||||
|
# nix run github:nix-community/plasma-manager
|
||||||
|
#
|
||||||
|
# Plasma-manager options documentation
|
||||||
|
# https://nix-community.github.io/plasma-manager/options.xhtml
|
||||||
|
#
|
||||||
|
# TODO: (ambitious) Add Kmail support to plasma-manager
|
||||||
|
programs.plasma = {
|
||||||
|
enable = true;
|
||||||
|
overrideConfig = true;
|
||||||
|
|
||||||
|
hotkeys.commands."launch-ghostty" = {
|
||||||
|
name = "Launch Ghostty";
|
||||||
|
key = "Meta+Return";
|
||||||
|
command = "ghostty";
|
||||||
|
};
|
||||||
|
|
||||||
|
shortcuts = {
|
||||||
|
kmix = {
|
||||||
|
"decrease_microphone_volume" = "Microphone Volume Down";
|
||||||
|
"decrease_volume" = "Volume Down";
|
||||||
|
"decrease_volume_small" = "Shift+Volume Down";
|
||||||
|
"increase_microphone_volume" = "Microphone Volume Up";
|
||||||
|
"increase_volume" = "Volume Up";
|
||||||
|
"increase_volume_small" = "Shift+Volume Up";
|
||||||
|
"mic_mute" = ["Microphone Mute" "Meta+Volume Mute,Microphone Mute" "Meta+Volume Mute,Mute Microphone"];
|
||||||
|
"mute" = "Volume Mute";
|
||||||
|
};
|
||||||
|
|
||||||
|
mediacontrol = {
|
||||||
|
"mediavolumedown" = "none,,Media volume down";
|
||||||
|
"mediavolumeup" = "none,,Media volume up";
|
||||||
|
"nextmedia" = "Media Next";
|
||||||
|
"pausemedia" = "Media Pause";
|
||||||
|
"playmedia" = "none,,Play media playback";
|
||||||
|
"playpausemedia" = "Media Play";
|
||||||
|
"previousmedia" = "Media Previous";
|
||||||
|
"stopmedia" = "Media Stop";
|
||||||
|
};
|
||||||
|
|
||||||
|
ksmserver = {
|
||||||
|
"Lock Session" = ["Meta+Ctrl+Q" "Screensaver" "Screensaver,Lock Session"];
|
||||||
|
};
|
||||||
|
|
||||||
|
kwin = {
|
||||||
|
"Window Close" = "Meta+Shift+Q";
|
||||||
|
"Kill Window" = "Meta+Ctrl+Esc";
|
||||||
|
"Window Operations Menu" = "Alt+F3";
|
||||||
|
"Window Resize" = "Meta+R,,Resize Window";
|
||||||
|
|
||||||
|
"Overview" = "Meta+Ctrl+W";
|
||||||
|
"Grid View" = "Meta+G";
|
||||||
|
"Edit Tiles" = "Meta+T";
|
||||||
|
|
||||||
|
"Activate Window Demanding Attention" = "Meta+Ctrl+A";
|
||||||
|
|
||||||
|
"Show Desktop" = "Meta+Ctrl+D";
|
||||||
|
|
||||||
|
"Walk Through Windows" = "Alt+Tab";
|
||||||
|
"Walk Through Windows (Reverse)" = "Alt+Shift+Tab";
|
||||||
|
"Walk Through Windows of Current Application" = "Alt+`";
|
||||||
|
"Walk Through Windows of Current Application (Reverse)" = "Alt+~";
|
||||||
|
|
||||||
|
"Window Quick Tile Bottom" = "Meta+Down";
|
||||||
|
"Window Quick Tile Left" = "Meta+Left";
|
||||||
|
"Window Quick Tile Right" = "Meta+Right";
|
||||||
|
"Window Quick Tile Top" = "Meta+Up";
|
||||||
|
|
||||||
|
"Switch to Desktop 1" = "Meta+1";
|
||||||
|
"Switch to Desktop 2" = "Meta+2";
|
||||||
|
"Switch to Desktop 3" = "Meta+3";
|
||||||
|
"Switch to Desktop 4" = "Meta+4";
|
||||||
|
"Switch to Desktop 5" = "Meta+5";
|
||||||
|
"Switch to Desktop 6" = "Meta+6";
|
||||||
|
"Switch to Desktop 7" = "Meta+7";
|
||||||
|
"Switch to Desktop 8" = "Meta+8";
|
||||||
|
"Switch to Desktop 9" = "Meta+9";
|
||||||
|
"Switch to Desktop 10" = "Meta+0";
|
||||||
|
|
||||||
|
"Window to Desktop 1" = "Meta+!"; # Meta+Shift+1
|
||||||
|
"Window to Desktop 2" = "Meta+@"; # Meta+Shift+2
|
||||||
|
"Window to Desktop 3" = "Meta+#"; # Meta+Shift+3
|
||||||
|
"Window to Desktop 4" = "Meta+$"; # Meta+Shift+4
|
||||||
|
"Window to Desktop 5" = "Meta+%"; # Meta+Shift+5
|
||||||
|
"Window to Desktop 6" = "Meta+^"; # Meta+Shift+6
|
||||||
|
"Window to Desktop 7" = "Meta+&"; # Meta+Shift+7
|
||||||
|
"Window to Desktop 8" = "Meta+*"; # Meta+Shift+8
|
||||||
|
"Window to Desktop 9" = "Meta+("; # Meta+Shift+9
|
||||||
|
"Window to Desktop 10" = "Meta+)"; # Meta+Shift+0
|
||||||
|
|
||||||
|
"view_actual_size" = "Meta+Ctrl+=";
|
||||||
|
"view_zoom_in" = ["Meta++" "Meta+=,Meta++" "Meta+=,Zoom In"];
|
||||||
|
"view_zoom_out" = "Meta+-";
|
||||||
|
};
|
||||||
|
"org_kde_powerdevil"."Decrease Keyboard Brightness" = "Keyboard Brightness Down";
|
||||||
|
"org_kde_powerdevil"."Decrease Screen Brightness" = "Monitor Brightness Down";
|
||||||
|
"org_kde_powerdevil"."Decrease Screen Brightness Small" = "Shift+Monitor Brightness Down";
|
||||||
|
"org_kde_powerdevil"."Hibernate" = "Hibernate";
|
||||||
|
"org_kde_powerdevil"."Increase Keyboard Brightness" = "Keyboard Brightness Up";
|
||||||
|
"org_kde_powerdevil"."Increase Screen Brightness" = "Monitor Brightness Up";
|
||||||
|
"org_kde_powerdevil"."Increase Screen Brightness Small" = "Shift+Monitor Brightness Up";
|
||||||
|
"org_kde_powerdevil"."PowerDown" = "Power Down";
|
||||||
|
"org_kde_powerdevil"."PowerOff" = "Power Off";
|
||||||
|
"org_kde_powerdevil"."Sleep" = "Sleep";
|
||||||
|
"org_kde_powerdevil"."Toggle Keyboard Backlight" = "Keyboard Light On/Off";
|
||||||
|
"org_kde_powerdevil"."Turn Off Screen" = [ ];
|
||||||
|
"org_kde_powerdevil"."powerProfile" = ["Battery" "Meta+B,Battery" "Meta+B,Switch Power Profile"];
|
||||||
|
|
||||||
|
plasmashell = {
|
||||||
|
"activate application launcher" = ["Meta" "Alt+F1,Meta" "Alt+F1,Activate Application Launcher"];
|
||||||
|
"activate task manager entry 1" = "none,,";
|
||||||
|
"activate task manager entry 2" = "none,,";
|
||||||
|
"activate task manager entry 3" = "none,,";
|
||||||
|
"activate task manager entry 4" = "none,,";
|
||||||
|
"activate task manager entry 5" = "none,,";
|
||||||
|
"activate task manager entry 6" = "none,,";
|
||||||
|
"activate task manager entry 7" = "none,,";
|
||||||
|
"activate task manager entry 8" = "none,,";
|
||||||
|
"activate task manager entry 9" = "none,,";
|
||||||
|
"activate task manager entry 10" = "none,,";
|
||||||
|
"show activity switcher" = "none,,";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
configFile = {
|
||||||
|
kwinrc.Desktops.Number = {
|
||||||
|
value = 10;
|
||||||
|
immutable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Enable KWin tiling features
|
||||||
|
kwinrc.Tiling = {
|
||||||
|
# Enable tiling functionality
|
||||||
|
"padding" = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Enable krohnkite plugin automatically
|
||||||
|
kwinrc.Plugins = {
|
||||||
|
krohnkiteEnabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
kwinrc.Effect-overview = {
|
||||||
|
# Configure overview effect for better tiling workflow
|
||||||
|
BorderActivate = 9; # Top-left corner activation
|
||||||
|
};
|
||||||
|
|
||||||
|
kcminputrc.Libinput = {
|
||||||
|
AccelerationProfile = "adaptive";
|
||||||
|
PointerAcceleration = 0.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
kcminputrc.Mouse = {
|
||||||
|
X11LibInputXAccelProfileFlat = false;
|
||||||
|
XLbInptAccelProfileFlat = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
kdeglobals.KDE.LookAndFeelPackage = "org.kde.breezedark.desktop";
|
||||||
|
|
||||||
|
# Focus follows mouse configuration
|
||||||
|
kwinrc.Windows = {
|
||||||
|
FocusPolicy = "FocusFollowsMouse";
|
||||||
|
AutoRaise = true; # Set to true if you want windows to auto-raise on focus
|
||||||
|
AutoRaiseInterval = 750; # Delay in ms before auto-raise (if enabled)
|
||||||
|
DelayFocusInterval = 0; # Delay in ms before focus follows mouse
|
||||||
|
};
|
||||||
|
|
||||||
|
# Desktop wallpaper configuration
|
||||||
|
plasma-localerc.Formats.LANG = "en_US.UTF-8";
|
||||||
|
|
||||||
|
# Set wallpaper for all desktops
|
||||||
|
plasmarc.Wallpapers.usersWallpapers = "${currentWallpaper.file}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
72
home/roles/starship/default.nix
Normal file
72
home/roles/starship/default.nix
Normal 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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
62
home/roles/tmux/default.nix
Normal file
62
home/roles/tmux/default.nix
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.home.roles.tmux;
|
||||||
|
|
||||||
|
tokyo-night = pkgs.tmuxPlugins.mkTmuxPlugin {
|
||||||
|
pluginName = "tokyo-night";
|
||||||
|
rtpFilePath = "tokyo-night.tmux";
|
||||||
|
version = "1.6.1";
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "janoamaral";
|
||||||
|
repo = "tokyo-night-tmux";
|
||||||
|
rev = "d610ced20d5f602a7995854931440e4a1e0ab780";
|
||||||
|
sha256 = "sha256-17vEgkL7C51p/l5gpT9dkOy0bY9n8l0/LV51mR1k+V8=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.home.roles.tmux = {
|
||||||
|
enable = mkEnableOption "tmux terminal multiplexer with Tokyo Night theme";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
programs.tmux.enable = true;
|
||||||
|
programs.tmux.terminal = "tmux-direct";
|
||||||
|
programs.tmux.keyMode = "vi";
|
||||||
|
programs.tmux.escapeTime = 0;
|
||||||
|
programs.tmux.mouse = true;
|
||||||
|
programs.tmux.newSession = true;
|
||||||
|
programs.tmux.historyLimit = 50000;
|
||||||
|
programs.tmux.clock24 = true;
|
||||||
|
programs.tmux.baseIndex = 1;
|
||||||
|
programs.tmux.prefix = "M-\\\\";
|
||||||
|
|
||||||
|
programs.tmux.plugins = with pkgs; [
|
||||||
|
tmuxPlugins.cpu
|
||||||
|
tmuxPlugins.battery
|
||||||
|
tmuxPlugins.better-mouse-mode
|
||||||
|
tmuxPlugins.net-speed
|
||||||
|
tmuxPlugins.online-status
|
||||||
|
tmuxPlugins.pain-control
|
||||||
|
tmuxPlugins.tilish
|
||||||
|
tmuxPlugins.yank
|
||||||
|
|
||||||
|
{
|
||||||
|
plugin = tmuxPlugins.resurrect;
|
||||||
|
extraConfig = "set -g @resurrect-strategy-nvim 'session'";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
plugin = tmuxPlugins.continuum;
|
||||||
|
extraConfig = ''
|
||||||
|
set -g @continuum-restore 'on'
|
||||||
|
set -g @continuum-save-interval '15' # minutes
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
||||||
|
tokyo-night
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
45
home/wallpapers/default.nix
Normal file
45
home/wallpapers/default.nix
Normal 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";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
BIN
home/wallpapers/metroid-creature-minimalist.jpg
Normal file
BIN
home/wallpapers/metroid-creature-minimalist.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
home/wallpapers/metroid-samus-action-4k.jpg
Normal file
BIN
home/wallpapers/metroid-samus-action-4k.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 MiB |
BIN
home/wallpapers/metroid-samus-returns-kz-3440x1440.jpg
Normal file
BIN
home/wallpapers/metroid-samus-returns-kz-3440x1440.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
BIN
home/wallpapers/metroid3_map.gif
Normal file
BIN
home/wallpapers/metroid3_map.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
BIN
home/wallpapers/super-metroid-gunship-cavern.jpg
Normal file
BIN
home/wallpapers/super-metroid-gunship-cavern.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 210 KiB |
BIN
home/wallpapers/super-metroid-samus-statue.png
Normal file
BIN
home/wallpapers/super-metroid-samus-statue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 MiB |
@@ -24,12 +24,23 @@ with lib;
|
|||||||
};
|
};
|
||||||
kodi = {
|
kodi = {
|
||||||
enable = true;
|
enable = true;
|
||||||
autologin = false;
|
autologin = true;
|
||||||
wayland = true;
|
wayland = true;
|
||||||
|
appLauncherServer.enable = 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;
|
||||||
@@ -39,12 +50,7 @@ with lib;
|
|||||||
services.xserver.videoDrivers = [ "amdgpu" ];
|
services.xserver.videoDrivers = [ "amdgpu" ];
|
||||||
hardware.graphics.enable = true;
|
hardware.graphics.enable = true;
|
||||||
hardware.graphics.enable32Bit = true;
|
hardware.graphics.enable32Bit = true;
|
||||||
hardware.graphics.extraPackages = with pkgs; [
|
# RADV (AMD's Vulkan driver) is now enabled by default, amdvlk was removed
|
||||||
amdvlk
|
|
||||||
];
|
|
||||||
hardware.graphics.extraPackages32 = with pkgs; [
|
|
||||||
driversi686Linux.amdvlk
|
|
||||||
];
|
|
||||||
|
|
||||||
# This option defines the first version of NixOS you have installed on this particular machine,
|
# This option defines the first version of NixOS you have installed on this particular machine,
|
||||||
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
|
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
|
||||||
|
|||||||
424
machines/john-endesktop/MIGRATION_PLAN.md
Normal file
424
machines/john-endesktop/MIGRATION_PLAN.md
Normal 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)
|
||||||
134
machines/john-endesktop/configuration.nix
Normal file
134
machines/john-endesktop/configuration.nix
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# 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;
|
||||||
|
|
||||||
|
# Enable as remote builder (similar to zix790prors)
|
||||||
|
roles.remote-build.enableBuilder = true;
|
||||||
|
|
||||||
|
# k3s agent configuration
|
||||||
|
roles.k3s-node = {
|
||||||
|
enable = true;
|
||||||
|
role = "agent";
|
||||||
|
# serverAddr defaults to https://10.0.0.222:6443
|
||||||
|
# tokenFile defaults to /etc/k3s/token
|
||||||
|
extraFlags = [
|
||||||
|
# Node labels for workload scheduling
|
||||||
|
# fast-cpu: This node has a faster CPU than other cluster nodes
|
||||||
|
"--node-label=fast-cpu=true"
|
||||||
|
# fast-storage: This node is the NFS host with fast local storage access
|
||||||
|
"--node-label=fast-storage=true"
|
||||||
|
# k3s-upgrade=disabled: NixOS manages k3s upgrades via Nix, not system-upgrade-controller
|
||||||
|
"--node-label=k3s-upgrade=disabled"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
roles.virtualisation.enable = true;
|
||||||
|
|
||||||
|
# Time zone
|
||||||
|
time.timeZone = "America/Los_Angeles"; # Adjust as needed
|
||||||
|
|
||||||
|
# NixOS version
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
}
|
||||||
63
machines/john-endesktop/hardware-configuration.nix
Normal file
63
machines/john-endesktop/hardware-configuration.nix
Normal 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;
|
||||||
|
}
|
||||||
@@ -65,6 +65,8 @@
|
|||||||
|
|
||||||
# Enable NetworkManager for easy wifi setup
|
# Enable NetworkManager for easy wifi setup
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
|
# Disable wireless networking (conflicts with NetworkManager)
|
||||||
|
networking.wireless.enable = false;
|
||||||
|
|
||||||
# Enable SSH daemon for remote access
|
# Enable SSH daemon for remote access
|
||||||
services.openssh = {
|
services.openssh = {
|
||||||
|
|||||||
@@ -15,12 +15,24 @@
|
|||||||
desktop = {
|
desktop = {
|
||||||
enable = true;
|
enable = true;
|
||||||
wayland = true;
|
wayland = true;
|
||||||
gaming.enable = false;
|
gaming.enable = true;
|
||||||
kde = true;
|
kde = true;
|
||||||
sddm = true;
|
sddm = true;
|
||||||
};
|
};
|
||||||
nfs-mounts.enable = true;
|
nfs-mounts.enable = true;
|
||||||
printing.enable = true;
|
printing.enable = true;
|
||||||
|
remote-build.builders = [
|
||||||
|
{
|
||||||
|
hostName = "zix790prors";
|
||||||
|
maxJobs = 16;
|
||||||
|
speedFactor = 3;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hostName = "john-endesktop";
|
||||||
|
maxJobs = 1;
|
||||||
|
speedFactor = 1;
|
||||||
|
}
|
||||||
|
];
|
||||||
spotifyd.enable = true;
|
spotifyd.enable = true;
|
||||||
users = {
|
users = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -34,12 +46,15 @@
|
|||||||
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 = {
|
||||||
|
HandlePowerKey = "hibernate";
|
||||||
|
HandlePowerKeyLongPress = "poweroff";
|
||||||
|
};
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
|
||||||
|
|
||||||
|
|
||||||
# Enable networking
|
# Enable networking
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
|
|||||||
41
machines/nix-deck/configuration.nix
Normal file
41
machines/nix-deck/configuration.nix
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./hardware-configuration.nix
|
||||||
|
../../roles/desktop/steamos.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
roles = {
|
||||||
|
audio.enable = true;
|
||||||
|
bluetooth.enable = true;
|
||||||
|
desktop = {
|
||||||
|
enable = true;
|
||||||
|
wayland = true;
|
||||||
|
gaming.enable = true;
|
||||||
|
kde = true;
|
||||||
|
steamos = {
|
||||||
|
enable = true;
|
||||||
|
autoStart = true;
|
||||||
|
desktopSession = "plasma";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
remote-build.builders = [{
|
||||||
|
hostName = "zix790prors";
|
||||||
|
maxJobs = 16;
|
||||||
|
speedFactor = 4; # Prefer remote heavily on Steam Deck
|
||||||
|
}];
|
||||||
|
users = {
|
||||||
|
enable = true;
|
||||||
|
extraGroups = [ "video" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Bootloader
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
|
||||||
|
networking.hostName = "nix-deck";
|
||||||
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
|
system.stateVersion = "25.05";
|
||||||
|
}
|
||||||
51
machines/nix-deck/hardware-configuration.nix
Normal file
51
machines/nix-deck/hardware-configuration.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Hardware configuration for Steam Deck (nix-deck)
|
||||||
|
# Generated from nixos-generate-config on 2025-11-17
|
||||||
|
|
||||||
|
{ config, lib, pkgs, modulesPath, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
# Steam Deck specific hardware configuration (Jovian)
|
||||||
|
jovian.devices.steamdeck = {
|
||||||
|
enable = true;
|
||||||
|
autoUpdate = false; # Set to true if you want automatic firmware updates
|
||||||
|
};
|
||||||
|
|
||||||
|
# Kernel modules detected by nixos-generate-config
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"nvme"
|
||||||
|
"xhci_pci"
|
||||||
|
"usb_storage"
|
||||||
|
"uas"
|
||||||
|
"usbhid"
|
||||||
|
"sd_mod"
|
||||||
|
"sdhci_pci"
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-amd" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
# IMPORTANT: Update these filesystem configurations based on your actual partition layout
|
||||||
|
# The configuration below is a placeholder - adjust according to how you partitioned the disk
|
||||||
|
fileSystems."/" = {
|
||||||
|
device = "/dev/disk/by-label/nixos";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" = {
|
||||||
|
device = "/dev/disk/by-label/boot";
|
||||||
|
fsType = "vfat";
|
||||||
|
};
|
||||||
|
|
||||||
|
swapDevices = [{
|
||||||
|
device = "/swapfile";
|
||||||
|
size = 8192; # 8GB swap file
|
||||||
|
}];
|
||||||
|
|
||||||
|
# AMD CPU microcode updates
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
}
|
||||||
@@ -17,6 +17,15 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
wayland = true;
|
wayland = true;
|
||||||
};
|
};
|
||||||
|
nvidia = {
|
||||||
|
enable = true;
|
||||||
|
package = "latest";
|
||||||
|
graphics.extraPackages = with pkgs; [
|
||||||
|
mesa
|
||||||
|
libvdpau-va-gl
|
||||||
|
libva-vdpau-driver
|
||||||
|
];
|
||||||
|
};
|
||||||
users.enable = true;
|
users.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,28 +38,13 @@
|
|||||||
wsl.wslConf.network.hostname = "wixos";
|
wsl.wslConf.network.hostname = "wixos";
|
||||||
wsl.wslConf.user.default = "johno";
|
wsl.wslConf.user.default = "johno";
|
||||||
|
|
||||||
services.xserver.videoDrivers = [ "nvidia" ];
|
# WSL-specific environment variables for graphics
|
||||||
hardware.graphics = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
extraPackages = with pkgs; [
|
|
||||||
mesa
|
|
||||||
libvdpau-va-gl
|
|
||||||
vaapiVdpau
|
|
||||||
];
|
|
||||||
};
|
|
||||||
environment.sessionVariables = {
|
environment.sessionVariables = {
|
||||||
LD_LIBRARY_PATH = [
|
LD_LIBRARY_PATH = [
|
||||||
"/usr/lib/wsl/lib"
|
"/usr/lib/wsl/lib"
|
||||||
"/run/opengl-driver/lib"
|
"/run/opengl-driver/lib"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
hardware.nvidia = {
|
|
||||||
modesetting.enable = true;
|
|
||||||
nvidiaSettings = true;
|
|
||||||
open = true;
|
|
||||||
package = config.boot.kernelPackages.nvidiaPackages.latest;
|
|
||||||
};
|
|
||||||
|
|
||||||
# This value determines the NixOS release from which the default
|
# This value determines the NixOS release from which the default
|
||||||
# settings for stateful data, like file locations and database versions
|
# settings for stateful data, like file locations and database versions
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
# NixOS /boot Partition Expansion Plan
|
|
||||||
|
|
||||||
## CRITICAL CONTEXT & PROBLEM STATEMENT
|
|
||||||
|
|
||||||
**System:** `zix790prors` - Dual-boot Windows 11 + NixOS on Samsung SSD 990 PRO 4TB
|
|
||||||
**Problem:** Current /boot partition (/dev/nvme0n1p1) is only 100MB, insufficient for multiple NixOS generations
|
|
||||||
**Goal:** Create 1GB /boot partition to support 20+ NixOS generations (~38MB each)
|
|
||||||
|
|
||||||
**Current Partition Layout:**
|
|
||||||
```
|
|
||||||
Device Start End Sectors Size Type
|
|
||||||
/dev/nvme0n1p1 2048 206847 204800 100M EFI System (/boot)
|
|
||||||
/dev/nvme0n1p2 206848 239615 32768 16M Microsoft reserved
|
|
||||||
/dev/nvme0n1p3 239616 1953316863 1953077248 931.3G Microsoft basic data (Windows)
|
|
||||||
/dev/nvme0n1p6 1953316864 3906394111 1953077248 931.3G Linux filesystem (/nix/store)
|
|
||||||
/dev/nvme0n1p5 3906394112 7812548607 3906154496 1.8T Linux filesystem (/games - btrfs)
|
|
||||||
/dev/nvme0n1p4 7812548608 7814031359 1482752 724M Windows recovery environment
|
|
||||||
```
|
|
||||||
|
|
||||||
**Strategy:** Shrink /games partition (p5) by 1GB and create new 1GB EFI partition
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PLAN A: DETAILED STEP-BY-STEP INSTRUCTIONS
|
|
||||||
|
|
||||||
### PREREQUISITES
|
|
||||||
1. **Boot from NixOS live USB** (build with `./build-liveusb.sh`)
|
|
||||||
2. **Have Windows recovery media ready** (just in case)
|
|
||||||
3. **Backup important data** (though this shouldn't affect user data)
|
|
||||||
|
|
||||||
### PHASE 1: PREPARATION & BACKUP
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Connect to internet
|
|
||||||
sudo systemctl start wpa_supplicant
|
|
||||||
# OR for ethernet: sudo systemctl start dhcpcd
|
|
||||||
|
|
||||||
# 2. Mount current filesystems
|
|
||||||
sudo mkdir -p /mnt/{boot,nix,games}
|
|
||||||
sudo mount /dev/nvme0n1p1 /mnt/boot
|
|
||||||
sudo mount /dev/nvme0n1p6 /mnt/nix
|
|
||||||
sudo mount /dev/nvme0n1p5 /mnt/games
|
|
||||||
|
|
||||||
# 3. Backup current /boot contents
|
|
||||||
sudo mkdir -p /tmp/boot-backup
|
|
||||||
sudo cp -a /mnt/boot/* /tmp/boot-backup/
|
|
||||||
sudo ls -la /tmp/boot-backup/ # Verify backup
|
|
||||||
|
|
||||||
# 4. Record current UUIDs for later
|
|
||||||
sudo blkid /dev/nvme0n1p1 > /tmp/original-boot-uuid.txt
|
|
||||||
cat /tmp/original-boot-uuid.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 2: SHRINK /games PARTITION
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Check /games filesystem
|
|
||||||
sudo btrfs filesystem show /mnt/games
|
|
||||||
sudo btrfs filesystem usage /mnt/games
|
|
||||||
|
|
||||||
# 2. Shrink btrfs filesystem first (by 1.1GB for safety margin)
|
|
||||||
sudo btrfs filesystem resize -1200M /mnt/games
|
|
||||||
|
|
||||||
# 3. Verify shrink was successful
|
|
||||||
sudo btrfs filesystem usage /mnt/games
|
|
||||||
|
|
||||||
# 4. Unmount to resize partition
|
|
||||||
sudo umount /mnt/games
|
|
||||||
|
|
||||||
# 5. Note current partition end sector
|
|
||||||
sudo fdisk -l /dev/nvme0n1 | grep nvme0n1p5
|
|
||||||
# Current: 3906394112 7812548607 (note the END: 7812548607)
|
|
||||||
|
|
||||||
# 6. Calculate new end sector (subtract ~2M sectors for 1GB)
|
|
||||||
# New end should be approximately: 7812548607 - 2097152 = 7810451455
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 3: RESIZE PARTITIONS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Use parted to resize /games partition
|
|
||||||
sudo parted /dev/nvme0n1
|
|
||||||
|
|
||||||
# In parted shell:
|
|
||||||
print # Show current layout
|
|
||||||
resizepart 5 7810451455 # Resize partition 5 to new end
|
|
||||||
print # Verify the change
|
|
||||||
quit
|
|
||||||
|
|
||||||
# 2. Verify partition table
|
|
||||||
sudo fdisk -l /dev/nvme0n1
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 4: CREATE NEW 1GB EFI PARTITION
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Create new partition in freed space
|
|
||||||
sudo parted /dev/nvme0n1
|
|
||||||
|
|
||||||
# In parted shell:
|
|
||||||
print free # Show free space
|
|
||||||
mkpart primary fat32 7810451456 7812548607 # Create new partition
|
|
||||||
set 7 esp on # Set ESP flag (assuming it becomes p7)
|
|
||||||
print # Verify
|
|
||||||
quit
|
|
||||||
|
|
||||||
# 2. Format new partition as FAT32
|
|
||||||
sudo mkfs.fat -F32 -n "NIXBOOT" /dev/nvme0n1p7
|
|
||||||
|
|
||||||
# 3. Get new partition UUID
|
|
||||||
sudo blkid /dev/nvme0n1p7
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 5: COPY BOOT CONTENTS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Mount new boot partition
|
|
||||||
sudo mkdir -p /mnt/new-boot
|
|
||||||
sudo mount /dev/nvme0n1p7 /mnt/new-boot
|
|
||||||
|
|
||||||
# 2. Copy all boot contents
|
|
||||||
sudo cp -a /tmp/boot-backup/* /mnt/new-boot/
|
|
||||||
|
|
||||||
# 3. Verify copy
|
|
||||||
sudo ls -la /mnt/new-boot/
|
|
||||||
sudo du -sh /mnt/new-boot/*
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 6: UPDATE CONFIGURATION
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Mount root filesystem
|
|
||||||
sudo mount /dev/nvme0n1p6 /mnt/nix
|
|
||||||
sudo mount /dev/nvme0n1p6 /mnt # For nixos-enter
|
|
||||||
|
|
||||||
# 2. Mount other needed filesystems for chroot
|
|
||||||
sudo mount --bind /dev /mnt/dev
|
|
||||||
sudo mount --bind /proc /mnt/proc
|
|
||||||
sudo mount --bind /sys /mnt/sys
|
|
||||||
sudo mount /dev/nvme0n1p7 /mnt/boot # Mount NEW boot partition
|
|
||||||
|
|
||||||
# 3. Enter NixOS environment
|
|
||||||
sudo nixos-enter
|
|
||||||
|
|
||||||
# 4. Inside chroot - update hardware configuration
|
|
||||||
# Edit /etc/nixos/hardware-configuration.nix or check current fstab
|
|
||||||
NEW_UUID=$(blkid /dev/nvme0n1p7 | grep -o 'UUID="[^"]*"' | cut -d'"' -f2)
|
|
||||||
echo "New boot UUID: $NEW_UUID"
|
|
||||||
|
|
||||||
# 5. Update boot partition UUID in configuration
|
|
||||||
# This step depends on your current setup - likely in hardware-configuration.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 7: REGENERATE BOOTLOADER
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Still in nixos-enter chroot:
|
|
||||||
|
|
||||||
# 1. Rebuild bootloader configuration
|
|
||||||
nixos-rebuild switch --install-bootloader
|
|
||||||
|
|
||||||
# 2. Exit chroot
|
|
||||||
exit
|
|
||||||
|
|
||||||
# 3. Update EFI boot variables (if needed)
|
|
||||||
sudo efibootmgr -v # Check current entries
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 8: VERIFICATION & CLEANUP
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Unmount everything
|
|
||||||
sudo umount /mnt/new-boot
|
|
||||||
sudo umount /mnt/boot
|
|
||||||
sudo umount /mnt/nix
|
|
||||||
sudo umount /mnt/games
|
|
||||||
|
|
||||||
# 2. Remount /games with new partition size
|
|
||||||
sudo mount /dev/nvme0n1p5 /mnt/games
|
|
||||||
sudo btrfs filesystem resize max /mnt/games # Expand btrfs to use available space
|
|
||||||
|
|
||||||
# 3. Final verification
|
|
||||||
sudo fdisk -l /dev/nvme0n1
|
|
||||||
sudo lsblk
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHASE 9: TEST BOOT
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Reboot and test
|
|
||||||
sudo reboot
|
|
||||||
|
|
||||||
# 2. After successful boot, update NixOS config
|
|
||||||
# Change configurationLimit to 20 (perfect for 1GB /boot)
|
|
||||||
# In /home/johno/nixos-configs/machines/zix790prors/configuration.nix:
|
|
||||||
# boot.loader.systemd-boot.configurationLimit = 20;
|
|
||||||
|
|
||||||
# 3. Test rebuild
|
|
||||||
sudo nixos-rebuild switch --flake .#zix790prors
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PLAN B: TROUBLESHOOTING & CONTEXT FOR ADDITIONAL HELP
|
|
||||||
|
|
||||||
### IF THINGS GO WRONG
|
|
||||||
|
|
||||||
**Scenario 1: System won't boot**
|
|
||||||
- Boot from live USB
|
|
||||||
- Mount old boot partition: `sudo mount /dev/nvme0n1p1 /mnt/boot`
|
|
||||||
- Check if bootloader entries exist: `ls /mnt/boot/loader/entries/`
|
|
||||||
- Regenerate from live USB using `nixos-install` with `--root /mnt`
|
|
||||||
|
|
||||||
**Scenario 2: Partition operations fail**
|
|
||||||
- **STOP IMMEDIATELY**
|
|
||||||
- Document exact error message
|
|
||||||
- Use `sudo fdisk -l` and `sudo parted /dev/nvme0n1 print` to check current state
|
|
||||||
- Do NOT proceed without understanding the error
|
|
||||||
|
|
||||||
**Scenario 3: Btrfs resize fails**
|
|
||||||
- Check available space: `sudo btrfs filesystem usage /mnt/games`
|
|
||||||
- May need to balance first: `sudo btrfs balance start /mnt/games`
|
|
||||||
- Or defragment: `sudo btrfs filesystem defragment -r /mnt/games`
|
|
||||||
|
|
||||||
### RECOVERY INFORMATION
|
|
||||||
|
|
||||||
**Original partition layout (before changes):**
|
|
||||||
```
|
|
||||||
/dev/nvme0n1p1: 100M EFI (/boot) - sectors 2048-206847
|
|
||||||
/dev/nvme0n1p5: 1.8T btrfs (/games) - sectors 3906394112-7812548607
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key files to check:**
|
|
||||||
- `/etc/fstab` - filesystem mount configuration
|
|
||||||
- `/etc/nixos/hardware-configuration.nix` - NixOS hardware config
|
|
||||||
- `/boot/loader/loader.conf` - systemd-boot config
|
|
||||||
|
|
||||||
**Important UUIDs (save these before starting):**
|
|
||||||
```bash
|
|
||||||
# Run these BEFORE making changes:
|
|
||||||
sudo blkid /dev/nvme0n1p1 # Current boot partition
|
|
||||||
sudo blkid /dev/nvme0n1p5 # Games partition
|
|
||||||
sudo blkid /dev/nvme0n1p6 # Nix store partition
|
|
||||||
```
|
|
||||||
|
|
||||||
**Emergency boot options:**
|
|
||||||
- Boot from Windows (should be unaffected)
|
|
||||||
- Boot from NixOS live USB
|
|
||||||
- Use systemd-boot menu to select older generation (if available)
|
|
||||||
|
|
||||||
### ALTERNATIVE APPROACHES IF PLAN A FAILS
|
|
||||||
|
|
||||||
1. **Expand into Microsoft reserved instead** (lower risk, less space)
|
|
||||||
2. **Use external USB for /boot** (temporary solution)
|
|
||||||
3. **Recreate partition table** (nuclear option, requires full backup)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## TECHNICAL DETAILS FOR CLAUDE CODE ASSISTANCE
|
|
||||||
|
|
||||||
**Repository:** `/home/johno/nixos-configs` - NixOS flake-based configuration
|
|
||||||
**Machine config:** `machines/zix790prors/configuration.nix`
|
|
||||||
**Current configurationLimit:** 1 (temporarily reduced from 2)
|
|
||||||
**Target configurationLimit:** 20 (requires ~1GB /boot)
|
|
||||||
|
|
||||||
**Key commands for status checking:**
|
|
||||||
```bash
|
|
||||||
# Partition info
|
|
||||||
sudo fdisk -l /dev/nvme0n1
|
|
||||||
sudo parted /dev/nvme0n1 print free
|
|
||||||
sudo lsblk
|
|
||||||
|
|
||||||
# Filesystem info
|
|
||||||
sudo btrfs filesystem show
|
|
||||||
sudo btrfs filesystem usage /games
|
|
||||||
df -h /boot
|
|
||||||
|
|
||||||
# Boot info
|
|
||||||
sudo efibootmgr -v
|
|
||||||
ls -la /boot/loader/entries/
|
|
||||||
```
|
|
||||||
|
|
||||||
**This document created:** 2025-10-02 by Claude Code
|
|
||||||
**Hardware:** Samsung SSD 990 PRO 4TB, NixOS + Windows 11 dual boot
|
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[ # Include the results of the hardware scan.
|
./hardware-configuration.nix
|
||||||
./hardware-configuration.nix
|
#./virtual-surround.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
roles = {
|
roles = {
|
||||||
audio.enable = true;
|
audio.enable = true;
|
||||||
@@ -19,22 +19,27 @@ with lib;
|
|||||||
enable = true;
|
enable = true;
|
||||||
gaming = {
|
gaming = {
|
||||||
enable = true;
|
enable = true;
|
||||||
emulation = true;
|
|
||||||
};
|
};
|
||||||
kde = true;
|
kde = true;
|
||||||
sddm = true;
|
sddm = true;
|
||||||
wayland = true;
|
wayland = true;
|
||||||
|
x11 = true;
|
||||||
};
|
};
|
||||||
|
kodi.enable = true;
|
||||||
nfs-mounts.enable = true;
|
nfs-mounts.enable = true;
|
||||||
nvidia.enable = true;
|
nvidia = {
|
||||||
|
enable = true;
|
||||||
|
graphics.enable32Bit = true;
|
||||||
|
};
|
||||||
printing.enable = true;
|
printing.enable = true;
|
||||||
|
remote-build.enableBuilder = true;
|
||||||
users.enable = true;
|
users.enable = true;
|
||||||
virtualisation.enable = true;
|
virtualisation.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# 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.systemd-boot.configurationLimit = 1; # Temporarily reduced to 1 to free /boot space
|
boot.loader.systemd-boot.configurationLimit = 20;
|
||||||
boot.loader.efi.canTouchEfiVariables = true;
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
boot.loader.timeout = 10;
|
boot.loader.timeout = 10;
|
||||||
|
|
||||||
@@ -46,30 +51,10 @@ with lib;
|
|||||||
# Fix dual boot clock sync - tell Linux to use local time for hardware clock
|
# Fix dual boot clock sync - tell Linux to use local time for hardware clock
|
||||||
time.hardwareClockInLocalTime = true;
|
time.hardwareClockInLocalTime = true;
|
||||||
|
|
||||||
# NVIDIA Graphics configuration
|
# Set DP-0 as primary display with 164.90Hz refresh rate
|
||||||
services.xserver.videoDrivers = [ "nvidia" ];
|
services.xserver.displayManager.sessionCommands = ''
|
||||||
hardware.graphics.enable = true;
|
${pkgs.xorg.xrandr}/bin/xrandr --output DP-0 --mode 3440x1440 --rate 164.90 --primary
|
||||||
hardware.graphics.enable32Bit = true;
|
'';
|
||||||
|
|
||||||
hardware.nvidia = {
|
|
||||||
# Modesetting is required.
|
|
||||||
modesetting.enable = true;
|
|
||||||
|
|
||||||
# Enable the Nvidia settings menu,
|
|
||||||
# accessible via `nvidia-settings`.
|
|
||||||
nvidiaSettings = true;
|
|
||||||
|
|
||||||
# Optionally, you may need to select the appropriate driver version for your specific GPU.
|
|
||||||
package = pkgs.linuxPackages.nvidiaPackages.stable;
|
|
||||||
|
|
||||||
# Use open source kernel modules (recommended for RTX/GTX 16xx and newer)
|
|
||||||
# Set to false if you have an older GPU
|
|
||||||
open = true;
|
|
||||||
|
|
||||||
# For gaming performance
|
|
||||||
powerManagement.enable = false;
|
|
||||||
powerManagement.finegrained = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.ollama = {
|
services.ollama = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
boot.extraModulePackages = [ ];
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
fileSystems."/boot" =
|
fileSystems."/boot" =
|
||||||
{ device = "/dev/disk/by-uuid/76B0-738E";
|
{ device = "/dev/disk/by-uuid/11C1-EB58";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
options = [ "fmask=0077" "dmask=0077" ];
|
options = [ "fmask=0077" "dmask=0077" ];
|
||||||
};
|
};
|
||||||
|
|||||||
132
machines/zix790prors/virtual-surround.nix
Normal file
132
machines/zix790prors/virtual-surround.nix
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
176
packages/app-launcher-server/app-launcher-server.py
Normal file
176
packages/app-launcher-server/app-launcher-server.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Allowlisted applications that can be launched
|
||||||
|
ALLOWED_APPS = {
|
||||||
|
'firefox': 'firefox',
|
||||||
|
'kodi': 'kodi'
|
||||||
|
}
|
||||||
|
|
||||||
|
def is_app_running(app_name):
|
||||||
|
"""Check if an application is already running, returns (is_running, pid)"""
|
||||||
|
command = ALLOWED_APPS.get(app_name)
|
||||||
|
if not command:
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
logger.debug(f"Looking for processes related to app '{app_name}' (command: '{command}')")
|
||||||
|
|
||||||
|
for proc in psutil.process_iter(['name', 'cmdline', 'pid']):
|
||||||
|
try:
|
||||||
|
proc_name = proc.info['name']
|
||||||
|
cmdline = proc.info['cmdline'] or []
|
||||||
|
|
||||||
|
logger.debug(f"Checking process PID {proc.info['pid']}: name='{proc_name}', cmdline={cmdline}")
|
||||||
|
|
||||||
|
# Check multiple patterns for the application:
|
||||||
|
# 1. Process name exactly matches command
|
||||||
|
# 2. Process name contains the command (e.g., "kodi.bin" contains "kodi")
|
||||||
|
# 3. Command line starts with the command
|
||||||
|
# 4. Command line contains the wrapped version (e.g., ".kodi-wrapped")
|
||||||
|
# 5. Any command line argument ends with the command executable
|
||||||
|
|
||||||
|
matches = False
|
||||||
|
match_reason = ""
|
||||||
|
|
||||||
|
if proc_name == command:
|
||||||
|
matches = True
|
||||||
|
match_reason = f"exact process name match: '{proc_name}'"
|
||||||
|
elif command in proc_name:
|
||||||
|
matches = True
|
||||||
|
match_reason = f"process name contains command: '{proc_name}' contains '{command}'"
|
||||||
|
elif cmdline and cmdline[0] == command:
|
||||||
|
matches = True
|
||||||
|
match_reason = f"exact cmdline match: '{cmdline[0]}'"
|
||||||
|
elif cmdline and cmdline[0].endswith('/' + command):
|
||||||
|
matches = True
|
||||||
|
match_reason = f"cmdline path ends with command: '{cmdline[0]}'"
|
||||||
|
elif cmdline and any(f'.{command}-wrapped' in arg for arg in cmdline):
|
||||||
|
matches = True
|
||||||
|
match_reason = f"wrapped command in cmdline: {cmdline}"
|
||||||
|
elif cmdline and any(f'{command}.bin' in arg for arg in cmdline):
|
||||||
|
matches = True
|
||||||
|
match_reason = f"binary command in cmdline: {cmdline}"
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
logger.info(f"Found running {app_name} process: PID {proc.info['pid']} ({match_reason})")
|
||||||
|
return True, proc.info['pid']
|
||||||
|
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.debug(f"No running process found for {app_name}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
class AppLauncherHandler(BaseHTTPRequestHandler):
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
logger.info(format % args)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == '/':
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
response = {
|
||||||
|
'status': 'running',
|
||||||
|
'available_apps': list(ALLOWED_APPS.keys()),
|
||||||
|
'usage': 'POST /launch/<app_name> to launch an application'
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response, indent=2).encode())
|
||||||
|
else:
|
||||||
|
self.send_error(404)
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
parsed_path = urlparse(self.path)
|
||||||
|
path_parts = parsed_path.path.strip('/').split('/')
|
||||||
|
|
||||||
|
if len(path_parts) == 2 and path_parts[0] == 'launch':
|
||||||
|
app_name = path_parts[1]
|
||||||
|
self.launch_app(app_name)
|
||||||
|
else:
|
||||||
|
self.send_error(404, "Invalid endpoint. Use /launch/<app_name>")
|
||||||
|
|
||||||
|
def launch_app(self, app_name):
|
||||||
|
if app_name not in ALLOWED_APPS:
|
||||||
|
self.send_error(400, f"Application '{app_name}' not allowed. Available apps: {list(ALLOWED_APPS.keys())}")
|
||||||
|
return
|
||||||
|
|
||||||
|
command = ALLOWED_APPS[app_name]
|
||||||
|
|
||||||
|
# Check if app is already running
|
||||||
|
is_running, existing_pid = is_app_running(app_name)
|
||||||
|
if is_running:
|
||||||
|
logger.info(f"Application {app_name} is already running (PID: {existing_pid}), skipping launch")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': f'{app_name} is already running',
|
||||||
|
'pid': existing_pid,
|
||||||
|
'already_running': True
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Launch the application in the background
|
||||||
|
# Ensure we have the proper environment for GUI apps
|
||||||
|
env = os.environ.copy()
|
||||||
|
|
||||||
|
logger.info(f"Launching application: {command}")
|
||||||
|
process = subprocess.Popen(
|
||||||
|
[command],
|
||||||
|
env=env,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
start_new_session=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': f'Successfully launched {app_name}',
|
||||||
|
'pid': process.pid,
|
||||||
|
'already_running': False
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"Application not found: {command}")
|
||||||
|
self.send_error(500, f"Application '{app_name}' not found on system")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error launching {command}: {e}")
|
||||||
|
self.send_error(500, f"Failed to launch {app_name}: {str(e)}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8081
|
||||||
|
|
||||||
|
server = HTTPServer(('0.0.0.0', port), AppLauncherHandler)
|
||||||
|
logger.info(f"App launcher server starting on port {port}")
|
||||||
|
logger.info(f"Available applications: {list(ALLOWED_APPS.keys())}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
server.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("Server shutting down...")
|
||||||
|
server.server_close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
10
packages/app-launcher-server/default.nix
Normal file
10
packages/app-launcher-server/default.nix
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
|
||||||
|
let
|
||||||
|
python = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
psutil
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
pkgs.writeShellScriptBin "app-launcher-server" ''
|
||||||
|
exec ${python}/bin/python3 ${./app-launcher-server.py} "$@"
|
||||||
|
''
|
||||||
116
packages/claude-code/README.md
Normal file
116
packages/claude-code/README.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# claude-cli
|
||||||
|
|
||||||
|
Custom Nix package for Claude Code CLI.
|
||||||
|
|
||||||
|
## Why This Package Exists
|
||||||
|
|
||||||
|
The official `claude-code` package in nixpkgs tries to fetch from npm registry, which is blocked by Block's corporate security (Cloudflare Teams dependency confusion protection). This custom package fetches directly from Anthropic's Google Cloud Storage distribution, bypassing the npm registry entirely.
|
||||||
|
|
||||||
|
## Updating to a New Version
|
||||||
|
|
||||||
|
### Automated Update (Recommended)
|
||||||
|
|
||||||
|
Run the update script to automatically fetch and update to the latest version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/claude-cli
|
||||||
|
./update.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- Fetch the latest version from Homebrew cask
|
||||||
|
- Update version and all SHA256 hashes in default.nix
|
||||||
|
- Show you what changed
|
||||||
|
|
||||||
|
For a dry-run to see what would change:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./update.sh --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
After the script completes, follow the "Test the Build" steps below.
|
||||||
|
|
||||||
|
### Manual Update
|
||||||
|
|
||||||
|
If you prefer to update manually, or if the automated script fails:
|
||||||
|
|
||||||
|
#### 1. Find the Latest Version and Hashes
|
||||||
|
|
||||||
|
Check the Homebrew cask formula for the latest version info:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s "https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/claude-code.rb" | head -50
|
||||||
|
```
|
||||||
|
|
||||||
|
This will show:
|
||||||
|
- The latest `version` number
|
||||||
|
- SHA256 hashes for all platforms (`arm64`, `x86_64`, `x86_64_linux`, `arm64_linux`)
|
||||||
|
|
||||||
|
#### 2. Update default.nix
|
||||||
|
|
||||||
|
Edit `default.nix` and update:
|
||||||
|
|
||||||
|
1. The `version` variable (line 9):
|
||||||
|
```nix
|
||||||
|
version = "2.0.51"; # Update this
|
||||||
|
```
|
||||||
|
|
||||||
|
2. All four platform sha256 hashes in the `srcs` attribute set (lines 11-27):
|
||||||
|
```nix
|
||||||
|
aarch64-darwin = {
|
||||||
|
sha256 = "..."; # Update from Homebrew cask "arm:" value
|
||||||
|
};
|
||||||
|
x86_64-darwin = {
|
||||||
|
sha256 = "..."; # Update from Homebrew cask "x86_64:" value
|
||||||
|
};
|
||||||
|
x86_64-linux = {
|
||||||
|
sha256 = "..."; # Update from Homebrew cask "x86_64_linux:" value
|
||||||
|
};
|
||||||
|
aarch64-linux = {
|
||||||
|
sha256 = "..."; # Update from Homebrew cask "arm64_linux:" value
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Test the Build
|
||||||
|
|
||||||
|
Before committing, test that the package builds successfully:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NIXPKGS_ALLOW_UNFREE=1 nix-build -E 'with import <nixpkgs> { config.allowUnfree = true; }; callPackage ./packages/claude-cli {}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./result/bin/claude --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Clean up the test build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm result
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Deploy
|
||||||
|
|
||||||
|
Commit your changes and rebuild:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add packages/claude-cli/
|
||||||
|
git commit -m "claude-cli: Update to version X.Y.Z"
|
||||||
|
darwin-rebuild switch --flake .#blkfv4yf49kt7
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternative: Automated Hash Fetching
|
||||||
|
|
||||||
|
If you prefer to fetch hashes automatically, you can use `nix-prefetch-url`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For macOS ARM64 (your current platform)
|
||||||
|
nix-prefetch-url "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/VERSION/darwin-arm64/claude"
|
||||||
|
|
||||||
|
# For other platforms, replace VERSION and adjust the platform string:
|
||||||
|
# darwin-x64, linux-x64, linux-arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download the file and output the SHA256 hash.
|
||||||
60
packages/claude-code/default.nix
Normal file
60
packages/claude-code/default.nix
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{ lib
|
||||||
|
, stdenv
|
||||||
|
, fetchurl
|
||||||
|
, autoPatchelfHook
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
version = "2.0.76";
|
||||||
|
|
||||||
|
srcs = {
|
||||||
|
aarch64-darwin = {
|
||||||
|
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-arm64/claude";
|
||||||
|
sha256 = "b76f6d4d09233e67295897b0a1ed2e22d7afa406431529d8b1b532b63b8cbcbd";
|
||||||
|
};
|
||||||
|
x86_64-darwin = {
|
||||||
|
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-x64/claude";
|
||||||
|
sha256 = "9d94582f0af5d2201f1c907bf24ff8d216104b897ee0b24795a6c081f40e08d7";
|
||||||
|
};
|
||||||
|
x86_64-linux = {
|
||||||
|
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-x64/claude";
|
||||||
|
sha256 = "5dcdb480f91ba0df0bc8bd6aff148d3dfd3883f0899eeb5b9427a8b0abe7a687";
|
||||||
|
};
|
||||||
|
aarch64-linux = {
|
||||||
|
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-arm64/claude";
|
||||||
|
sha256 = "f64a994c8e5bfb84d7242cebbec75d6919db2ee46d50b8fc7a88d5066db193f9";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
src = srcs.${stdenv.hostPlatform.system} or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
|
||||||
|
|
||||||
|
in stdenv.mkDerivation {
|
||||||
|
pname = "claude-code";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
src = fetchurl {
|
||||||
|
inherit (src) url sha256;
|
||||||
|
};
|
||||||
|
|
||||||
|
dontUnpack = true;
|
||||||
|
dontBuild = true;
|
||||||
|
|
||||||
|
nativeBuildInputs = lib.optionals stdenv.isLinux [ autoPatchelfHook ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
install -Dm755 $src $out/bin/claude
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Terminal-based AI coding assistant from Anthropic";
|
||||||
|
homepage = "https://www.anthropic.com/claude-code";
|
||||||
|
license = licenses.unfree;
|
||||||
|
maintainers = [ ];
|
||||||
|
platforms = [ "aarch64-darwin" "x86_64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||||
|
mainProgram = "claude";
|
||||||
|
};
|
||||||
|
}
|
||||||
34
packages/claude-code/npm.nix
Normal file
34
packages/claude-code/npm.nix
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{ lib
|
||||||
|
, buildNpmPackage
|
||||||
|
, fetchurl
|
||||||
|
, nodejs_18
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildNpmPackage {
|
||||||
|
pname = "claude-cli";
|
||||||
|
version = "0.2.65";
|
||||||
|
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-0.2.65.tgz";
|
||||||
|
sha256 = "0wwaqq7k9p5aw4vqhfpdgf3da09x64q55wibqaprk6kjvn130i92";
|
||||||
|
};
|
||||||
|
|
||||||
|
npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Will be updated after first build
|
||||||
|
|
||||||
|
nodejs = nodejs_18;
|
||||||
|
|
||||||
|
# Don't run npm audit or other network operations during build
|
||||||
|
npmConfigHook = ''
|
||||||
|
npm config set audit false
|
||||||
|
npm config set fund false
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Terminal-based AI coding assistant from Anthropic (npm distribution)";
|
||||||
|
homepage = "https://www.anthropic.com/claude-code";
|
||||||
|
license = licenses.unfree;
|
||||||
|
maintainers = [ ];
|
||||||
|
platforms = platforms.all;
|
||||||
|
mainProgram = "claude";
|
||||||
|
};
|
||||||
|
}
|
||||||
133
packages/claude-code/update.sh
Executable file
133
packages/claude-code/update.sh
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--dry-run|-n)
|
||||||
|
DRY_RUN=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --dry-run, -n Show what would be updated without making changes"
|
||||||
|
echo " --help, -h Show this help message"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
echo "Use --help for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
CASK_URL="https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/claude-code.rb"
|
||||||
|
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..."
|
||||||
|
|
||||||
|
# Fetch the cask file
|
||||||
|
CASK_CONTENT=$(curl -fsSL "$CASK_URL")
|
||||||
|
|
||||||
|
# Extract version (format: version "X.Y.Z")
|
||||||
|
NEW_VERSION=$(echo "$CASK_CONTENT" | grep -m1 'version' | sed -E 's/.*version "([^"]+)".*/\1/')
|
||||||
|
|
||||||
|
# Extract SHA256 hashes (be specific to match sha256 lines only)
|
||||||
|
SHA_ARM=$(echo "$CASK_CONTENT" | grep 'sha256 arm:' | sed -E 's/.*"([a-f0-9]{64})".*/\1/')
|
||||||
|
SHA_X86_64=$(echo "$CASK_CONTENT" | grep 'x86_64:' | sed -E 's/.*"([a-f0-9]{64})".*/\1/')
|
||||||
|
SHA_X86_64_LINUX=$(echo "$CASK_CONTENT" | grep 'x86_64_linux:' | sed -E 's/.*"([a-f0-9]{64})".*/\1/')
|
||||||
|
SHA_ARM64_LINUX=$(echo "$CASK_CONTENT" | grep 'arm64_linux:' | sed -E 's/.*"([a-f0-9]{64})".*/\1/')
|
||||||
|
|
||||||
|
# Get current version
|
||||||
|
CURRENT_VERSION=$(grep -m1 'version = ' "$NIX_FILE" | sed -E 's/.*version = "([^"]+)".*/\1/')
|
||||||
|
|
||||||
|
# Validate extracted data
|
||||||
|
if [ -z "$NEW_VERSION" ] || [ -z "$SHA_ARM" ] || [ -z "$SHA_X86_64" ] || [ -z "$SHA_X86_64_LINUX" ] || [ -z "$SHA_ARM64_LINUX" ]; then
|
||||||
|
echo -e "${RED}Error: Failed to extract all required values from Homebrew cask${NC}"
|
||||||
|
echo "Version: $NEW_VERSION"
|
||||||
|
echo "ARM: $SHA_ARM"
|
||||||
|
echo "x86_64: $SHA_X86_64"
|
||||||
|
echo "x86_64_linux: $SHA_X86_64_LINUX"
|
||||||
|
echo "arm64_linux: $SHA_ARM64_LINUX"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if update is needed
|
||||||
|
if [ "$CURRENT_VERSION" = "$NEW_VERSION" ]; then
|
||||||
|
echo -e "${GREEN}Already up to date: $CURRENT_VERSION${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Updating from $CURRENT_VERSION to $NEW_VERSION${NC}"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo -e "${YELLOW}DRY RUN - No changes will be made${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Would update:"
|
||||||
|
echo " Version: $CURRENT_VERSION -> $NEW_VERSION"
|
||||||
|
echo " aarch64-darwin SHA: $SHA_ARM"
|
||||||
|
echo " x86_64-darwin SHA: $SHA_X86_64"
|
||||||
|
echo " x86_64-linux SHA: $SHA_X86_64_LINUX"
|
||||||
|
echo " aarch64-linux SHA: $SHA_ARM64_LINUX"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update version
|
||||||
|
sed -i.tmp "s/version = \".*\";/version = \"$NEW_VERSION\";/" "$NIX_FILE"
|
||||||
|
|
||||||
|
# Update SHA256 hashes using awk for more reliable parsing
|
||||||
|
awk -v sha_arm="$SHA_ARM" -v sha_x86="$SHA_X86_64" -v sha_x86_linux="$SHA_X86_64_LINUX" -v sha_arm_linux="$SHA_ARM64_LINUX" '
|
||||||
|
/aarch64-darwin = {/ { in_arm = 1 }
|
||||||
|
/x86_64-darwin = {/ { in_x86 = 1 }
|
||||||
|
/x86_64-linux = {/ { in_x86_linux = 1 }
|
||||||
|
/aarch64-linux = {/ { in_arm_linux = 1 }
|
||||||
|
/};/ {
|
||||||
|
in_arm = 0
|
||||||
|
in_x86 = 0
|
||||||
|
in_x86_linux = 0
|
||||||
|
in_arm_linux = 0
|
||||||
|
}
|
||||||
|
/sha256 = / {
|
||||||
|
if (in_arm) {
|
||||||
|
sub(/sha256 = ".*";/, "sha256 = \"" sha_arm "\";")
|
||||||
|
} else if (in_x86) {
|
||||||
|
sub(/sha256 = ".*";/, "sha256 = \"" sha_x86 "\";")
|
||||||
|
} else if (in_x86_linux) {
|
||||||
|
sub(/sha256 = ".*";/, "sha256 = \"" sha_x86_linux "\";")
|
||||||
|
} else if (in_arm_linux) {
|
||||||
|
sub(/sha256 = ".*";/, "sha256 = \"" sha_arm_linux "\";")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$NIX_FILE" > "$NIX_FILE.new"
|
||||||
|
|
||||||
|
mv "$NIX_FILE.new" "$NIX_FILE"
|
||||||
|
|
||||||
|
# Clean up temp files
|
||||||
|
rm -f "$NIX_FILE.tmp"
|
||||||
|
|
||||||
|
echo -e "${GREEN}Successfully updated to version $NEW_VERSION${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Updated SHA256 hashes:"
|
||||||
|
echo " aarch64-darwin: $SHA_ARM"
|
||||||
|
echo " x86_64-darwin: $SHA_X86_64"
|
||||||
|
echo " x86_64-linux: $SHA_X86_64_LINUX"
|
||||||
|
echo " aarch64-linux: $SHA_ARM64_LINUX"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Review changes: git diff $NIX_FILE"
|
||||||
|
echo " 2. Test build: NIXPKGS_ALLOW_UNFREE=1 nix-build -E 'with import <nixpkgs> { config.allowUnfree = true; }; callPackage ./packages/claude-code {}'"
|
||||||
|
echo " 3. Verify version: ./result/bin/claude --version"
|
||||||
|
echo " 4. Commit: git add $NIX_FILE && git commit -m 'claude-code: Update to version $NEW_VERSION'"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
vulkanHDRLayer = pkgs.callPackage ./vulkan-hdr-layer {};
|
|
||||||
tea-rbw = pkgs.callPackage ./tea-rbw {};
|
tea-rbw = pkgs.callPackage ./tea-rbw {};
|
||||||
|
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
|
||||||
|
claude-code = pkgs.callPackage ./claude-code {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
{ lib, stdenv, fetchFromGitHub, meson, pkg-config, vulkan-loader, ninja, writeText, vulkan-headers, vulkan-utility-libraries, jq, libX11, libXrandr, libxcb, wayland, wayland-scanner }:
|
|
||||||
|
|
||||||
stdenv.mkDerivation rec {
|
|
||||||
pname = "vulkan-hdr-layer";
|
|
||||||
version = "63d2eec";
|
|
||||||
|
|
||||||
src = (fetchFromGitHub {
|
|
||||||
owner = "Zamundaaa";
|
|
||||||
repo = "VK_hdr_layer";
|
|
||||||
rev = "869199cd2746e7f69cf19955153080842b6dacfc";
|
|
||||||
fetchSubmodules = true;
|
|
||||||
hash = "sha256-xfVYI+Aajmnf3BTaY2Ysg5fyDO6SwDFGyU0L+F+E3is=";
|
|
||||||
}).overrideAttrs (_: {
|
|
||||||
GIT_CONFIG_COUNT = 1;
|
|
||||||
GIT_CONFIG_KEY_0 = "url.https://github.com/.insteadOf";
|
|
||||||
GIT_CONFIG_VALUE_0 = "git@github.com:";
|
|
||||||
});
|
|
||||||
|
|
||||||
nativeBuildInputs = [ vulkan-headers meson ninja pkg-config jq ];
|
|
||||||
|
|
||||||
buildInputs = [ vulkan-headers vulkan-loader vulkan-utility-libraries libX11 libXrandr libxcb wayland wayland-scanner ];
|
|
||||||
|
|
||||||
# Help vulkan-loader find the validation layers
|
|
||||||
setupHook = writeText "setup-hook" ''
|
|
||||||
addToSearchPath XDG_DATA_DIRS @out@/share
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = with lib; {
|
|
||||||
description = "Layers providing Vulkan HDR";
|
|
||||||
homepage = "https://github.com/Zamundaaa/VK_hdr_layer";
|
|
||||||
platforms = platforms.linux;
|
|
||||||
license = licenses.mit;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -24,14 +24,6 @@ in
|
|||||||
pulse.enable = true;
|
pulse.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
services.pulseaudio = {
|
|
||||||
package = pkgs.pulseaudioFull;
|
|
||||||
extraConfig = ''
|
|
||||||
load-module module-combine-sink
|
|
||||||
load-module module-switch-on-connect
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
services.squeezelite = {
|
services.squeezelite = {
|
||||||
#enable = true;
|
#enable = true;
|
||||||
pulseAudio = true;
|
pulseAudio = true;
|
||||||
|
|||||||
35
roles/common.nix
Normal file
35
roles/common.nix
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -15,6 +19,7 @@ in
|
|||||||
environment.etc."bashrc".enable = false;
|
environment.etc."bashrc".enable = false;
|
||||||
environment.etc."zshrc".enable = false;
|
environment.etc."zshrc".enable = false;
|
||||||
environment.etc."zshenv".enable = false;
|
environment.etc."zshenv".enable = false;
|
||||||
|
environment.etc."zprofile".enable = false;
|
||||||
|
|
||||||
# Create .local files with nix environment setup
|
# Create .local files with nix environment setup
|
||||||
environment.etc."bash.local".text = ''
|
environment.etc."bash.local".text = ''
|
||||||
@@ -42,43 +47,39 @@ in
|
|||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
time.timeZone = "America/Los_Angeles";
|
# System preferences
|
||||||
|
system.defaults = {
|
||||||
environment.systemPackages = with pkgs; [
|
# Custom keyboard shortcuts
|
||||||
git
|
CustomUserPreferences = {
|
||||||
glances
|
"com.apple.symbolichotkeys" = {
|
||||||
pciutils
|
AppleSymbolicHotKeys = {
|
||||||
tree
|
# Screenshot - Capture entire screen (Cmd+Ctrl+3)
|
||||||
usbutils
|
"28" = {
|
||||||
vim
|
enabled = true;
|
||||||
];
|
value = {
|
||||||
|
parameters = [ 51 20 1310720 ];
|
||||||
nix = {
|
type = "standard";
|
||||||
package = pkgs.nix;
|
};
|
||||||
# distributedBuilds = true;
|
};
|
||||||
# buildMachines = [{
|
# Screenshot - Capture selected portion (Cmd+Ctrl+4)
|
||||||
# hostName = "z790prors.oglehome";
|
"30" = {
|
||||||
# system = "x86_64-linux";
|
enabled = true;
|
||||||
# protocol = "ssh-ng";
|
value = {
|
||||||
# sshUser = "johno";
|
parameters = [ 52 21 1310720 ];
|
||||||
# sshKey = "/root/.ssh/id_ed25519";
|
type = "standard";
|
||||||
# maxJobs = 3;
|
};
|
||||||
# speedFactor = 2;
|
};
|
||||||
# }];
|
# Screenshot - Show screenshot toolbar (Cmd+Ctrl+5)
|
||||||
settings = {
|
"184" = {
|
||||||
experimental-features = [ "nix-command" "flakes" ];
|
enabled = true;
|
||||||
max-jobs = "auto";
|
value = {
|
||||||
trusted-users = [ "johno" ];
|
parameters = [ 53 23 1310720 ];
|
||||||
substituters = [
|
type = "standard";
|
||||||
];
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
gc = {
|
};
|
||||||
automatic = true;
|
|
||||||
options = "--delete-older-than 10d";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,17 @@ with lib;
|
|||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
./common.nix
|
||||||
./audio
|
./audio
|
||||||
./bluetooth
|
./bluetooth
|
||||||
./btrfs
|
./btrfs
|
||||||
./desktop
|
./desktop
|
||||||
|
./k3s-node
|
||||||
./kodi
|
./kodi
|
||||||
./nfs-mounts
|
./nfs-mounts
|
||||||
./nvidia
|
./nvidia
|
||||||
./printing
|
./printing
|
||||||
|
./remote-build
|
||||||
./spotifyd
|
./spotifyd
|
||||||
./users
|
./users
|
||||||
./virtualisation
|
./virtualisation
|
||||||
@@ -30,7 +33,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";
|
||||||
@@ -48,42 +50,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;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ with lib;
|
|||||||
kde = mkOption { type = types.bool; default = false; description = "Enable KDE."; };
|
kde = mkOption { type = types.bool; default = false; description = "Enable KDE."; };
|
||||||
gaming = {
|
gaming = {
|
||||||
enable = mkOption { type = types.bool; default = false; description = "Enable gaming support."; };
|
enable = mkOption { type = types.bool; default = false; description = "Enable gaming support."; };
|
||||||
emulation = mkOption { type = types.bool; default = false; description = "Enable emulation support."; };
|
|
||||||
};
|
};
|
||||||
sddm = mkOption { type = types.bool; default = false; description = "Enable SDDM greeter."; };
|
sddm = mkOption { type = types.bool; default = false; description = "Enable SDDM greeter."; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,19 +9,29 @@ in
|
|||||||
config = mkMerge [
|
config = mkMerge [
|
||||||
(mkIf (cfg.enable && cfg.gaming.enable) {
|
(mkIf (cfg.enable && cfg.gaming.enable) {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
steam
|
|
||||||
lutris
|
lutris
|
||||||
moonlight
|
moonlight
|
||||||
];
|
|
||||||
|
|
||||||
# Possibly other gaming specific services or settings
|
# Emulators
|
||||||
})
|
|
||||||
|
|
||||||
(mkIf (cfg.enable && cfg.gaming.emulation) {
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
ryubing
|
|
||||||
dolphin-emu
|
dolphin-emu
|
||||||
|
|
||||||
|
# Re-enabled in 25.11 after binary build was fixed
|
||||||
|
dolphin-emu-primehack
|
||||||
|
|
||||||
|
# Experimenting with just using the steam version + downloading
|
||||||
|
# indiviudal cores
|
||||||
|
#retroarch-full
|
||||||
|
ryubing
|
||||||
|
|
||||||
|
yarg
|
||||||
];
|
];
|
||||||
|
|
||||||
|
programs.steam = {
|
||||||
|
enable = true;
|
||||||
|
remotePlay.openFirewall = true;
|
||||||
|
dedicatedServer.openFirewall = true;
|
||||||
|
localNetworkGameTransfers.openFirewall = true;
|
||||||
|
};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,5 +16,22 @@ in
|
|||||||
programs.dconf.enable = true;
|
programs.dconf.enable = true;
|
||||||
services.gnome.gnome-keyring.enable = true;
|
services.gnome.gnome-keyring.enable = true;
|
||||||
programs.kdeconnect.enable = true;
|
programs.kdeconnect.enable = true;
|
||||||
|
|
||||||
|
# XDG Desktop Portal for default application handling in non-KDE environments
|
||||||
|
xdg.portal = {
|
||||||
|
enable = true;
|
||||||
|
extraPortals = with pkgs; [
|
||||||
|
kdePackages.xdg-desktop-portal-kde # For KDE application integration
|
||||||
|
xdg-desktop-portal-gtk # Fallback for GTK applications
|
||||||
|
];
|
||||||
|
config = {
|
||||||
|
common = {
|
||||||
|
default = "kde";
|
||||||
|
};
|
||||||
|
i3 = {
|
||||||
|
default = ["kde" "gtk"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
51
roles/desktop/steamos.nix
Normal file
51
roles/desktop/steamos.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{ lib, config, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.roles.desktop;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.roles.desktop.steamos = {
|
||||||
|
enable = mkEnableOption "SteamOS (Jovian) configuration";
|
||||||
|
|
||||||
|
autoStart = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Automatically start Steam Deck UI on boot";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "johno";
|
||||||
|
description = "User to run Steam as";
|
||||||
|
};
|
||||||
|
|
||||||
|
desktopSession = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Desktop session to launch when switching to Desktop Mode";
|
||||||
|
};
|
||||||
|
|
||||||
|
enableDeckyLoader = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable Decky Loader plugin system";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf (cfg.enable && cfg.steamos.enable) {
|
||||||
|
jovian.steam = {
|
||||||
|
enable = true;
|
||||||
|
autoStart = cfg.steamos.autoStart;
|
||||||
|
user = cfg.steamos.user;
|
||||||
|
desktopSession = cfg.steamos.desktopSession;
|
||||||
|
};
|
||||||
|
|
||||||
|
jovian.decky-loader.enable = cfg.steamos.enableDeckyLoader;
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
maliit-keyboard
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,8 +12,27 @@ in
|
|||||||
|
|
||||||
windowManager.i3 = {
|
windowManager.i3 = {
|
||||||
enable = true;
|
enable = true;
|
||||||
extraPackages = with pkgs; [ dmenu i3status i3lock ];
|
extraPackages = with pkgs; [
|
||||||
|
dmenu
|
||||||
|
i3status
|
||||||
|
i3lock
|
||||||
|
polkit_gnome # GNOME polkit authentication agent (more stable with i3)
|
||||||
|
picom # Compositor for smooth rendering (important for Nvidia)
|
||||||
|
networkmanagerapplet # NetworkManager system tray applet
|
||||||
|
ddcutil # DDC/CI monitor control for brightness
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Enable DDC/CI support for monitor brightness control
|
||||||
|
boot.kernelModules = [ "i2c-dev" ];
|
||||||
|
|
||||||
|
# Add ddcutil udev rules and user permissions
|
||||||
|
hardware.i2c.enable = true;
|
||||||
|
|
||||||
|
# Install ddcutil system-wide
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
ddcutil
|
||||||
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
81
roles/k3s-node/default.nix
Normal file
81
roles/k3s-node/default.nix
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{ lib, config, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.roles.k3s-node;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.roles.k3s-node = {
|
||||||
|
enable = mkEnableOption "Enable k3s node";
|
||||||
|
|
||||||
|
role = mkOption {
|
||||||
|
type = types.enum [ "server" "agent" ];
|
||||||
|
default = "agent";
|
||||||
|
description = "k3s role: server (control plane) or agent (worker)";
|
||||||
|
};
|
||||||
|
|
||||||
|
serverAddr = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://10.0.0.222:6443";
|
||||||
|
description = "URL of k3s server to join (required for agents, used for HA servers)";
|
||||||
|
};
|
||||||
|
|
||||||
|
tokenFile = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "/etc/k3s/token";
|
||||||
|
description = "Path to file containing the cluster join token";
|
||||||
|
};
|
||||||
|
|
||||||
|
clusterInit = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Initialize a new cluster (first server only)";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraFlags = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Additional flags to pass to k3s";
|
||||||
|
};
|
||||||
|
|
||||||
|
gracefulNodeShutdown = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable graceful node shutdown";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Open firewall ports for k3s";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# k3s service configuration
|
||||||
|
services.k3s = {
|
||||||
|
enable = true;
|
||||||
|
role = cfg.role;
|
||||||
|
tokenFile = cfg.tokenFile;
|
||||||
|
extraFlags = cfg.extraFlags;
|
||||||
|
gracefulNodeShutdown.enable = cfg.gracefulNodeShutdown;
|
||||||
|
serverAddr = if (cfg.role == "agent" || !cfg.clusterInit) then cfg.serverAddr else "";
|
||||||
|
clusterInit = cfg.role == "server" && cfg.clusterInit;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Firewall rules for k3s
|
||||||
|
networking.firewall = mkIf cfg.openFirewall {
|
||||||
|
allowedTCPPorts = [
|
||||||
|
6443 # k3s API server
|
||||||
|
10250 # kubelet metrics
|
||||||
|
] ++ optionals (cfg.role == "server") [
|
||||||
|
2379 # etcd clients (HA)
|
||||||
|
2380 # etcd peers (HA)
|
||||||
|
];
|
||||||
|
allowedUDPPorts = [
|
||||||
|
8472 # flannel VXLAN
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -14,6 +14,23 @@ 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 = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable HTTP app launcher server for remote control";
|
||||||
|
};
|
||||||
|
port = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 8081;
|
||||||
|
description = "Port for the app launcher HTTP server";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -25,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 = {
|
||||||
@@ -33,24 +72,46 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
networking.firewall = {
|
networking.firewall = {
|
||||||
allowedTCPPorts = [ 8080 ];
|
allowedTCPPorts = [ 8080 ] ++ optional cfg.appLauncherServer.enable cfg.appLauncherServer.port;
|
||||||
allowedUDPPorts = [ 8080 ];
|
allowedUDPPorts = [ 8080 ];
|
||||||
};
|
};
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
|
jellyfinMediaPlayerPkg
|
||||||
kodiPkg
|
kodiPkg
|
||||||
wget
|
wget
|
||||||
];
|
firefox
|
||||||
|
] ++ optional cfg.appLauncherServer.enable pkgs.custom.app-launcher-server;
|
||||||
|
|
||||||
|
nixpkgs.config.permittedInsecurePackages = lib.warn
|
||||||
|
"Allowing insecure package qtwebengine-5.15.19 as a jellyfin-media-player dependency. Remove this once jellyfin is updated to use qt6"
|
||||||
|
[
|
||||||
|
"qtwebengine-5.15.19"
|
||||||
|
];
|
||||||
|
|
||||||
programs.kdeconnect.enable = true;
|
programs.kdeconnect.enable = true;
|
||||||
|
|
||||||
services = if cfg.autologin then {
|
systemd.user.services = mkIf cfg.appLauncherServer.enable {
|
||||||
displayManager = {
|
app-launcher-server = {
|
||||||
autoLogin.enable = true;
|
description = "HTTP App Launcher Server";
|
||||||
autoLogin.user = "kodi";
|
wantedBy = [ "graphical-session.target" ];
|
||||||
defaultSession = "kodi";
|
after = [ "graphical-session.target" ];
|
||||||
sessionData.autologinSession = "plasma";
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${pkgs.custom.app-launcher-server}/bin/app-launcher-server ${toString cfg.appLauncherServer.port}";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "5s";
|
||||||
|
Environment = [
|
||||||
|
"PATH=${pkgs.firefox}/bin:${kodiPkg}/bin:/run/current-system/sw/bin"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
} else {};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.displayManager = mkIf cfg.autologin {
|
||||||
|
autoLogin.enable = true;
|
||||||
|
autoLogin.user = "kodi";
|
||||||
|
defaultSession = "plasma";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,89 @@ in
|
|||||||
{
|
{
|
||||||
options.roles.nvidia = {
|
options.roles.nvidia = {
|
||||||
enable = mkEnableOption "Enable the nvidia role";
|
enable = mkEnableOption "Enable the nvidia role";
|
||||||
|
|
||||||
|
# Driver configuration options
|
||||||
|
open = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Use the open source nvidia kernel driver (for Turing and newer GPUs).";
|
||||||
|
};
|
||||||
|
|
||||||
|
modesetting = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable kernel modesetting for nvidia.";
|
||||||
|
};
|
||||||
|
|
||||||
|
nvidiaSettings = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable the nvidia-settings GUI.";
|
||||||
|
};
|
||||||
|
|
||||||
|
package = mkOption {
|
||||||
|
type = types.enum [ "stable" "latest" "beta" "vulkan_beta" "production" ];
|
||||||
|
default = "stable";
|
||||||
|
description = "The nvidia driver package to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
powerManagement = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable nvidia power management (useful for laptops, not recommended for desktops).";
|
||||||
|
};
|
||||||
|
|
||||||
|
finegrained = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable fine-grained power management for Turing and newer GPUs.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
graphics = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable hardware graphics support.";
|
||||||
|
};
|
||||||
|
|
||||||
|
enable32Bit = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable 32-bit graphics libraries (needed for some games).";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraPackages = mkOption {
|
||||||
|
type = types.listOf types.package;
|
||||||
|
default = [];
|
||||||
|
description = "Extra packages to add to hardware.graphics.extraPackages.";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
# Set xserver video driver
|
||||||
|
services.xserver.videoDrivers = [ "nvidia" ];
|
||||||
|
|
||||||
|
# Graphics configuration
|
||||||
|
hardware.graphics = {
|
||||||
|
enable = cfg.graphics.enable;
|
||||||
|
enable32Bit = cfg.graphics.enable32Bit;
|
||||||
|
extraPackages = cfg.graphics.extraPackages;
|
||||||
|
};
|
||||||
|
|
||||||
|
# NVIDIA driver configuration
|
||||||
|
hardware.nvidia = {
|
||||||
|
modesetting.enable = cfg.modesetting;
|
||||||
|
nvidiaSettings = cfg.nvidiaSettings;
|
||||||
|
open = cfg.open;
|
||||||
|
package = config.boot.kernelPackages.nvidiaPackages.${cfg.package};
|
||||||
|
powerManagement.enable = cfg.powerManagement.enable;
|
||||||
|
powerManagement.finegrained = cfg.powerManagement.finegrained;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Additional packages for nvidia support
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
libva-utils
|
libva-utils
|
||||||
nvidia-vaapi-driver
|
nvidia-vaapi-driver
|
||||||
|
|||||||
@@ -26,5 +26,11 @@ in
|
|||||||
model = "everywhere";
|
model = "everywhere";
|
||||||
}];
|
}];
|
||||||
hardware.printers.ensureDefaultPrinter = "MFC-L8900CDW_series";
|
hardware.printers.ensureDefaultPrinter = "MFC-L8900CDW_series";
|
||||||
|
|
||||||
|
# Fix ensure-printers service to wait for network availability
|
||||||
|
systemd.services.ensure-printers = {
|
||||||
|
after = [ "cups.service" "network-online.target" ];
|
||||||
|
wants = [ "cups.service" "network-online.target" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
195
roles/remote-build/default.nix
Normal file
195
roles/remote-build/default.nix
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# Remote Build Role
|
||||||
|
#
|
||||||
|
# This module configures Nix distributed builds, allowing machines to offload
|
||||||
|
# builds to more powerful remote machines.
|
||||||
|
#
|
||||||
|
# SETUP INSTRUCTIONS
|
||||||
|
# ==================
|
||||||
|
#
|
||||||
|
# 1. BUILDER MACHINE SETUP
|
||||||
|
# On machines that will serve as builders (e.g., zix790prors, john-endesktop):
|
||||||
|
#
|
||||||
|
# a) Enable the builder role in configuration.nix:
|
||||||
|
# roles.remote-build.enableBuilder = true;
|
||||||
|
#
|
||||||
|
# b) After nixos-rebuild, the nix-builder user is created automatically.
|
||||||
|
# You need to add client SSH public keys to the builder. Either:
|
||||||
|
#
|
||||||
|
# Option A - Manual (recommended for initial setup):
|
||||||
|
# sudo mkdir -p /var/lib/nix-builder/.ssh
|
||||||
|
# sudo bash -c 'cat >> /var/lib/nix-builder/.ssh/authorized_keys' << 'EOF'
|
||||||
|
# ssh-ed25519 AAAA... root@client-hostname
|
||||||
|
# EOF
|
||||||
|
# sudo chown -R nix-builder:nix-builder /var/lib/nix-builder/.ssh
|
||||||
|
# sudo chmod 700 /var/lib/nix-builder/.ssh
|
||||||
|
# sudo chmod 600 /var/lib/nix-builder/.ssh/authorized_keys
|
||||||
|
#
|
||||||
|
# Option B - Via NixOS config (if you store keys in the repo):
|
||||||
|
# users.users.nix-builder.openssh.authorizedKeys.keys = [
|
||||||
|
# "ssh-ed25519 AAAA... root@client-hostname"
|
||||||
|
# ];
|
||||||
|
#
|
||||||
|
# 2. CLIENT MACHINE SETUP
|
||||||
|
# On machines that will use remote builders (e.g., nix-book):
|
||||||
|
#
|
||||||
|
# a) Configure builders in configuration.nix:
|
||||||
|
# roles.remote-build.builders = [
|
||||||
|
# {
|
||||||
|
# hostName = "zix790prors";
|
||||||
|
# maxJobs = 16; # Number of parallel build jobs
|
||||||
|
# speedFactor = 3; # Higher = prefer this builder
|
||||||
|
# }
|
||||||
|
# {
|
||||||
|
# hostName = "john-endesktop";
|
||||||
|
# maxJobs = 1; # Conservative for busy machines
|
||||||
|
# speedFactor = 1;
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
#
|
||||||
|
# b) Generate SSH key for root (if not exists) and copy to builders:
|
||||||
|
# sudo ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""
|
||||||
|
# sudo cat /root/.ssh/id_ed25519.pub # Add this to builder's authorized_keys
|
||||||
|
#
|
||||||
|
# c) Accept the builder's host key (as root):
|
||||||
|
# sudo ssh nix-builder@zix790prors echo "Connected!"
|
||||||
|
# sudo ssh nix-builder@john-endesktop echo "Connected!"
|
||||||
|
#
|
||||||
|
# 3. VERIFY SETUP
|
||||||
|
# Test that distributed builds work:
|
||||||
|
# nix build --rebuild nixpkgs#hello --print-build-logs
|
||||||
|
#
|
||||||
|
# Check builder connectivity:
|
||||||
|
# nix store ping --store ssh-ng://nix-builder@zix790prors
|
||||||
|
#
|
||||||
|
{ lib, config, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.roles.remote-build;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.roles.remote-build = {
|
||||||
|
enableBuilder = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable this machine as a remote build host for other machines";
|
||||||
|
};
|
||||||
|
|
||||||
|
builderUser = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "nix-builder";
|
||||||
|
description = "User account for remote builders to connect as";
|
||||||
|
};
|
||||||
|
|
||||||
|
builders = mkOption {
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
hostName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Hostname or IP address of the build machine";
|
||||||
|
};
|
||||||
|
|
||||||
|
systems = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "x86_64-linux" ];
|
||||||
|
description = "Supported systems";
|
||||||
|
};
|
||||||
|
|
||||||
|
maxJobs = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 8;
|
||||||
|
description = "Maximum number of parallel build jobs";
|
||||||
|
};
|
||||||
|
|
||||||
|
speedFactor = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
description = "Speed factor compared to local building (higher = prefer remote)";
|
||||||
|
};
|
||||||
|
|
||||||
|
supportedFeatures = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
|
||||||
|
description = "Supported build features";
|
||||||
|
};
|
||||||
|
|
||||||
|
sshUser = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "nix-builder";
|
||||||
|
description = "SSH user for connecting to the builder";
|
||||||
|
};
|
||||||
|
|
||||||
|
sshKey = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
description = "Path to SSH private key for authentication";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = [];
|
||||||
|
description = "List of remote build machines to use";
|
||||||
|
};
|
||||||
|
|
||||||
|
fallbackToLocalBuild = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Fallback to local building if remote builders are unavailable";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkMerge [
|
||||||
|
# Builder host configuration
|
||||||
|
(mkIf cfg.enableBuilder {
|
||||||
|
# Create dedicated builder user
|
||||||
|
users.users.${cfg.builderUser} = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = cfg.builderUser;
|
||||||
|
description = "Nix remote build user";
|
||||||
|
home = "/var/lib/${cfg.builderUser}";
|
||||||
|
createHome = true;
|
||||||
|
shell = pkgs.bashInteractive;
|
||||||
|
openssh.authorizedKeys.keyFiles = []; # Will be populated by client machines
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.${cfg.builderUser} = {};
|
||||||
|
|
||||||
|
# Ensure home directory has correct permissions
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/${cfg.builderUser} 0700 ${cfg.builderUser} ${cfg.builderUser} -"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Allow builder user to perform builds
|
||||||
|
nix.settings.trusted-users = [ cfg.builderUser ];
|
||||||
|
|
||||||
|
# Allow remote builds
|
||||||
|
services.openssh.enable = true;
|
||||||
|
|
||||||
|
# Ensure nix-daemon is accessible
|
||||||
|
nix.settings.allowed-users = [ "*" ];
|
||||||
|
})
|
||||||
|
|
||||||
|
# Client configuration (machines using remote builders)
|
||||||
|
(mkIf (cfg.builders != []) {
|
||||||
|
nix.buildMachines = map (builder: {
|
||||||
|
hostName = builder.hostName;
|
||||||
|
systems = builder.systems;
|
||||||
|
maxJobs = builder.maxJobs;
|
||||||
|
speedFactor = builder.speedFactor;
|
||||||
|
supportedFeatures = builder.supportedFeatures;
|
||||||
|
sshUser = builder.sshUser;
|
||||||
|
sshKey = builder.sshKey;
|
||||||
|
}) cfg.builders;
|
||||||
|
|
||||||
|
nix.distributedBuilds = true;
|
||||||
|
|
||||||
|
# Use substitutes from remote builders
|
||||||
|
nix.extraOptions = ''
|
||||||
|
builders-use-substitutes = true
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Fallback to local build if remote unavailable
|
||||||
|
nix.settings.fallback = cfg.fallbackToLocalBuild;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
4
bootstrap.sh → scripts/bootstrap.sh
Executable file → Normal file
4
bootstrap.sh → scripts/bootstrap.sh
Executable file → Normal file
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# bootstrap.sh
|
# bootstrap.sh
|
||||||
# Usage: sudo ./bootstrap.sh <hostname>
|
# Usage: nix run .#bootstrap -- <hostname>
|
||||||
|
# Or: sudo ./scripts/bootstrap.sh <hostname>
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
NEW_HOSTNAME="${1:?missing hostname}"
|
NEW_HOSTNAME="${1:?missing hostname}"
|
||||||
@@ -8,4 +9,3 @@ FLAKE_URI="git+https://git.johnogle.info/johno/nixos-configs.git#${NEW_HOSTNAME}
|
|||||||
|
|
||||||
export NIX_CONFIG="experimental-features = nix-command flakes"
|
export NIX_CONFIG="experimental-features = nix-command flakes"
|
||||||
nixos-rebuild switch --flake "$FLAKE_URI"
|
nixos-rebuild switch --flake "$FLAKE_URI"
|
||||||
|
|
||||||
22
scripts/build-liveusb.sh
Normal file
22
scripts/build-liveusb.sh
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build Live USB ISO from flake configuration
|
||||||
|
# Creates an uncompressed ISO suitable for Ventoy and other USB boot tools
|
||||||
|
# Usage: nix run .#build-liveusb
|
||||||
|
# Or: ./scripts/build-liveusb.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||||
|
|
||||||
|
echo "Building Live USB ISO..."
|
||||||
|
nix build "${REPO_ROOT}#nixosConfigurations.live-usb.config.system.build.isoImage" --show-trace
|
||||||
|
|
||||||
|
if ls "${REPO_ROOT}/result/iso/"*.iso 1> /dev/null 2>&1; then
|
||||||
|
iso_file=$(ls "${REPO_ROOT}/result/iso/"*.iso)
|
||||||
|
echo "Build complete!"
|
||||||
|
echo "ISO location: $iso_file"
|
||||||
|
echo "Ready for Ventoy or dd to USB"
|
||||||
|
else
|
||||||
|
echo "Build failed - no ISO file found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
41
scripts/rotate-wallpaper.sh
Normal file
41
scripts/rotate-wallpaper.sh
Normal 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."
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user