Compare commits
59 Commits
nix-deck-s
...
411e521921
| Author | SHA1 | Date | |
|---|---|---|---|
| 411e521921 | |||
| 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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
result
|
||||
thoughts
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
# Steam Deck (nix-deck) Jovian-NixOS Setup Guide
|
||||
|
||||
This document describes the setup for installing and maintaining NixOS with Jovian-NixOS on a Steam Deck.
|
||||
|
||||
## Overview
|
||||
|
||||
The `nix-deck` configuration provides:
|
||||
- **Jovian-NixOS integration** for Steam Deck hardware support
|
||||
- **Remote building** using `zix790prors` as the build host
|
||||
- **SteamOS role** for easy Steam Deck UI configuration
|
||||
- **Compatibility shim** for using Jovian on NixOS 25.05 stable
|
||||
|
||||
## Architecture
|
||||
|
||||
### Remote Building
|
||||
|
||||
The setup uses distributed builds to avoid slow compilation on the Steam Deck:
|
||||
|
||||
- **Build Host**: `zix790prors` (powerful desktop)
|
||||
- Runs as a dedicated `nix-builder` user (not root)
|
||||
- Accepts SSH connections from client machines
|
||||
- Performs all heavy compilation work
|
||||
|
||||
- **Build Clients**: `nix-book` and `nix-deck`
|
||||
- Automatically offload builds to `zix790prors`
|
||||
- Fall back to local building if remote builder is unavailable
|
||||
- Steam Deck heavily prefers remote (speedFactor=4)
|
||||
|
||||
### Jovian-NixOS Integration
|
||||
|
||||
- **Jovian module**: Provides Steam Deck hardware support, drivers, and Steam UI
|
||||
- **Compatibility layer**: `roles/jovian-compat.nix` provides `services.logind.settings` for NixOS 25.05
|
||||
- **IMPORTANT**: Remove this when upgrading to NixOS 25.11+
|
||||
- An assertion will fail the build if used on 25.11+
|
||||
|
||||
- **SteamOS role**: `roles.desktop.steamos` abstracts Jovian configuration
|
||||
```nix
|
||||
roles.desktop.steamos = {
|
||||
enable = true;
|
||||
autoStart = false; # Set to true to boot directly to Steam UI
|
||||
desktopSession = "plasmawayland";
|
||||
};
|
||||
```
|
||||
|
||||
## Initial Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Steam Deck in recovery mode or booted to a live Linux environment
|
||||
2. SSH access enabled on the Steam Deck
|
||||
3. SSH key set up for passwordless authentication
|
||||
|
||||
### Option 1: Using nixos-anywhere (Initial Install Only)
|
||||
|
||||
```bash
|
||||
# From your main machine
|
||||
nix run github:nix-community/nixos-anywhere -- \
|
||||
--flake .#nix-deck \
|
||||
root@<steam-deck-ip>
|
||||
```
|
||||
|
||||
**Note**: This is only for the initial install. For updates, see below.
|
||||
|
||||
### Option 2: Manual Installation
|
||||
|
||||
1. Boot Steam Deck from NixOS installer USB
|
||||
2. Partition and format the disk
|
||||
3. Mount filesystems
|
||||
4. Clone this repository
|
||||
5. Generate hardware config:
|
||||
```bash
|
||||
nixos-generate-config --show-hardware-config > /tmp/hw.nix
|
||||
```
|
||||
6. Copy the hardware config content to `machines/nix-deck/hardware-configuration.nix`
|
||||
7. Keep the `jovian.devices.steamdeck` settings in the file
|
||||
8. Install:
|
||||
```bash
|
||||
nixos-install --flake .#nix-deck
|
||||
```
|
||||
|
||||
## Updates and Rebuilds
|
||||
|
||||
### Method 1: Remote Build and Deploy (Recommended)
|
||||
|
||||
Build on your main machine, deploy to Steam Deck:
|
||||
|
||||
```bash
|
||||
# From nix-book or zix790prors
|
||||
nixos-rebuild switch \
|
||||
--flake .#nix-deck \
|
||||
--target-host root@nix-deck \
|
||||
--build-host localhost
|
||||
```
|
||||
|
||||
### Method 2: On-Device Rebuild (Uses Remote Builder)
|
||||
|
||||
The Steam Deck is configured to automatically use `zix790prors` as a remote builder:
|
||||
|
||||
```bash
|
||||
# SSH into the Steam Deck
|
||||
ssh root@nix-deck
|
||||
|
||||
# This will automatically build on zix790prors
|
||||
nixos-rebuild switch --flake /path/to/nixos-configs#nix-deck
|
||||
```
|
||||
|
||||
The build will automatically happen on `zix790prors` and be deployed locally.
|
||||
|
||||
## Remote Builder Setup
|
||||
|
||||
### On the Build Host (zix790prors)
|
||||
|
||||
The configuration creates a `nix-builder` user that client machines connect to:
|
||||
|
||||
```nix
|
||||
roles.remote-build.enableBuilder = true;
|
||||
```
|
||||
|
||||
### On Client Machines (nix-book, nix-deck)
|
||||
|
||||
Configure the remote builder:
|
||||
|
||||
```nix
|
||||
roles.remote-build.builders = [{
|
||||
hostName = "zix790prors";
|
||||
maxJobs = 16;
|
||||
speedFactor = 4; # Higher = prefer remote more
|
||||
}];
|
||||
```
|
||||
|
||||
### SSH Key Setup
|
||||
|
||||
1. Generate SSH key on the builder host for the `nix-builder` user:
|
||||
```bash
|
||||
sudo -u nix-builder ssh-keygen -t ed25519 -f /var/lib/nix-builder/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
2. Copy the public key to client machines:
|
||||
```bash
|
||||
# Add to /var/lib/nix-builder/.ssh/authorized_keys on zix790prors
|
||||
```
|
||||
|
||||
3. On client machines, ensure you can connect:
|
||||
```bash
|
||||
ssh nix-builder@zix790prors
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### Key Files Created/Modified
|
||||
|
||||
- `flake.nix` - Added Jovian input and nix-deck configuration
|
||||
- `roles/jovian-compat.nix` - Compatibility shim (remove in 25.11+)
|
||||
- `roles/desktop/steamos.nix` - SteamOS/Jovian role abstraction
|
||||
- `roles/remote-build/default.nix` - Remote builder role
|
||||
- `machines/nix-deck/configuration.nix` - Steam Deck system config
|
||||
- `machines/nix-deck/hardware-configuration.nix` - Hardware config (placeholder)
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```nix
|
||||
# machines/nix-deck/configuration.nix
|
||||
{
|
||||
roles = {
|
||||
desktop = {
|
||||
enable = true;
|
||||
wayland = true;
|
||||
gaming.enable = true;
|
||||
kde = true;
|
||||
sddm = true;
|
||||
steamos = {
|
||||
enable = true;
|
||||
autoStart = false; # or true to boot to Steam UI
|
||||
desktopSession = "plasmawayland";
|
||||
};
|
||||
};
|
||||
remote-build.builders = [{
|
||||
hostName = "zix790prors";
|
||||
maxJobs = 16;
|
||||
speedFactor = 4;
|
||||
}];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Jovian Features
|
||||
|
||||
### Enabled by Default
|
||||
|
||||
- Steam Deck hardware support (`jovian.devices.steamdeck.enable`)
|
||||
- Steam UI (`jovian.steam.enable`)
|
||||
- Decky Loader plugin system (`jovian.decky-loader.enable`)
|
||||
|
||||
### Optional Features
|
||||
|
||||
Set in the hardware-configuration.nix:
|
||||
|
||||
```nix
|
||||
jovian.devices.steamdeck = {
|
||||
enable = true;
|
||||
autoUpdate = false; # Auto-update BIOS/controller firmware
|
||||
};
|
||||
```
|
||||
|
||||
### Manual Firmware Updates
|
||||
|
||||
```bash
|
||||
# BIOS update
|
||||
sudo jupiter-biosupdate
|
||||
|
||||
# Controller update
|
||||
sudo jupiter-controller-update
|
||||
|
||||
# Docking station (connect via USB-C first)
|
||||
jupiter-dock-updater
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Remote Builds Not Working
|
||||
|
||||
1. Check SSH connectivity:
|
||||
```bash
|
||||
ssh nix-builder@zix790prors
|
||||
```
|
||||
|
||||
2. Verify builder is trusted:
|
||||
```bash
|
||||
# On zix790prors
|
||||
nix show-config | grep trusted-users
|
||||
```
|
||||
|
||||
3. Check build logs:
|
||||
```bash
|
||||
journalctl -u nix-daemon -f
|
||||
```
|
||||
|
||||
### Jovian Not Working
|
||||
|
||||
1. Ensure you're on NixOS 25.05 or the compatibility layer is removed for 25.11+
|
||||
2. Check Jovian is imported in flake.nix
|
||||
3. Verify hardware config has `jovian.devices.steamdeck.enable = true`
|
||||
|
||||
### Compatibility Layer Issues
|
||||
|
||||
If you see an error about `jovian-compat.nix` being incompatible:
|
||||
|
||||
1. You're running NixOS 25.11 or later
|
||||
2. Remove `./roles/jovian-compat.nix` from `flake.nix`
|
||||
3. Jovian should work natively on 25.11+
|
||||
|
||||
## Future Upgrades
|
||||
|
||||
### Upgrading to NixOS 25.11
|
||||
|
||||
1. Update `nixpkgs` input in flake.nix to 25.11
|
||||
2. Remove `./roles/jovian-compat.nix` from flake.nix imports
|
||||
3. The assertion in jovian-compat.nix will prevent accidental use
|
||||
4. Test the build
|
||||
5. Deploy
|
||||
|
||||
### Switching to Unstable
|
||||
|
||||
If you need Jovian to follow unstable nixpkgs:
|
||||
|
||||
1. Edit `flake.nix`:
|
||||
```nix
|
||||
jovian = {
|
||||
url = "github:Jovian-Experiments/Jovian-NixOS";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||
};
|
||||
```
|
||||
|
||||
2. This only affects Jovian packages, not your base system
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Jovian-NixOS Documentation](https://jovian-experiments.github.io/Jovian-NixOS/)
|
||||
- [Jovian Steam Deck Guide](https://jovian-experiments.github.io/Jovian-NixOS/devices/valve-steam-deck/)
|
||||
- [NixOS Remote Builds](https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html)
|
||||
146
flake.lock
generated
146
flake.lock
generated
@@ -3,11 +3,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1761588595,
|
||||
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
|
||||
"lastModified": 1765121682,
|
||||
"narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
|
||||
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -43,16 +43,16 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1758463745,
|
||||
"narHash": "sha256-uhzsV0Q0I9j2y/rfweWeGif5AWe0MGrgZ/3TjpDYdGA=",
|
||||
"lastModified": 1766292113,
|
||||
"narHash": "sha256-sWTtmkQujRpjWYCnZc8LWdDiCzrRlSBPrGovkZpLkBI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "3b955f5f0a942f9f60cdc9cacb7844335d0f21c3",
|
||||
"rev": "fdec8815a86db36f42fc9c8cb2931cd8485f5aed",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "release-25.05",
|
||||
"ref": "release-25.11",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -64,11 +64,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763416652,
|
||||
"narHash": "sha256-8EBEEvtzQ11LCxpQHMNEBQAGtQiCu/pqP9zSovDSbNM=",
|
||||
"lastModified": 1766282146,
|
||||
"narHash": "sha256-0V/nKU93KdYGi+5LB/MVo355obBJw/2z9b2xS3bPJxY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "ea164b7c9ccdc2321379c2ff78fd4317b4c41312",
|
||||
"rev": "61fcc9de76b88e55578eb5d79fc80f2b236df707",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -86,11 +86,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763223001,
|
||||
"narHash": "sha256-Hi6XxTJJjKsDrO+D0fYXS88ehCYzQkZlp9qxX1zoM1s=",
|
||||
"lastModified": 1766225187,
|
||||
"narHash": "sha256-6hcaU8qtmixsaEUbjPiOFd5aJPZxAIBokl5d7dkab3k=",
|
||||
"owner": "Jovian-Experiments",
|
||||
"repo": "Jovian-NixOS",
|
||||
"rev": "68a1bcc019378272e601558719f82005a80ddab0",
|
||||
"rev": "bb53a85db9210204a98f771f10f1f5b4e06ccb2d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -106,16 +106,16 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762912391,
|
||||
"narHash": "sha256-4hpBE7bGd24SfD28rzMdUGXsLsNEYxCCrTipFdoqoNM=",
|
||||
"lastModified": 1765066094,
|
||||
"narHash": "sha256-0YSU35gfRFJzx/lTGgOt6ubP8K6LeW0vaywzNNqxkl4=",
|
||||
"owner": "nix-darwin",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "d76299b2cd01837c4c271a7b5186e3d5d8ebd126",
|
||||
"rev": "688427b1aab9afb478ca07989dc754fa543e03d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-darwin",
|
||||
"ref": "nix-darwin-25.05",
|
||||
"ref": "nix-darwin-25.11",
|
||||
"repo": "nix-darwin",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -148,11 +148,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763385941,
|
||||
"narHash": "sha256-99CBNgyMvg3Zu/hxqixtShevrF4Kfr/qjtizQ6oseVI=",
|
||||
"lastModified": 1765841014,
|
||||
"narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=",
|
||||
"owner": "nix-community",
|
||||
"repo": "NixOS-WSL",
|
||||
"rev": "cc6483354b236c2fc95cc1d4ba1f0f40b7345e69",
|
||||
"rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -164,11 +164,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1762977756,
|
||||
"narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
|
||||
"lastModified": 1765472234,
|
||||
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
|
||||
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -180,11 +180,11 @@
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1763283776,
|
||||
"narHash": "sha256-Y7TDFPK4GlqrKrivOcsHG8xSGqQx3A6c+i7novT85Uk=",
|
||||
"lastModified": 1766070988,
|
||||
"narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "50a96edd8d0db6cc8db57dab6bb6d6ee1f3dc49a",
|
||||
"rev": "c6245e83d836d0433170a16eb185cefe0572f8b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -196,16 +196,16 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1763049705,
|
||||
"narHash": "sha256-A5LS0AJZ1yDPTa2fHxufZN++n8MCmtgrJDtxFxrH4S8=",
|
||||
"lastModified": 1766201043,
|
||||
"narHash": "sha256-eplAP+rorKKd0gNjV3rA6+0WMzb1X1i16F5m5pASnjA=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3acb677ea67d4c6218f33de0db0955f116b7588c",
|
||||
"rev": "b3aad468604d3e488d627c0b43984eb60e75e782",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-25.05",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -220,11 +220,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762784320,
|
||||
"narHash": "sha256-odsk96Erywk5hs0dhArF38zb7Oe0q6LZ70gXbxAPKno=",
|
||||
"lastModified": 1763909441,
|
||||
"narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "plasma-manager",
|
||||
"rev": "7911a0f8a44c7e8b29d031be3149ee8943144321",
|
||||
"rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -243,11 +243,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762784320,
|
||||
"narHash": "sha256-odsk96Erywk5hs0dhArF38zb7Oe0q6LZ70gXbxAPKno=",
|
||||
"lastModified": 1763909441,
|
||||
"narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "plasma-manager",
|
||||
"rev": "7911a0f8a44c7e8b29d031be3149ee8943144321",
|
||||
"rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -256,6 +256,52 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pyproject-build-systems": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"pyproject-nix": [
|
||||
"pyproject-nix"
|
||||
],
|
||||
"uv2nix": [
|
||||
"uv2nix"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763662255,
|
||||
"narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=",
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "build-system-pkgs",
|
||||
"rev": "042904167604c681a090c07eb6967b4dd4dae88c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "build-system-pkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pyproject-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1764134915,
|
||||
"narHash": "sha256-xaKvtPx6YAnA3HQVp5LwyYG1MaN4LLehpQI8xEdBvBY=",
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "pyproject.nix",
|
||||
"rev": "2c8df1383b32e5443c921f61224b198a2282a657",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "pyproject.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"google-cookie-retrieval": "google-cookie-retrieval",
|
||||
@@ -267,7 +313,33 @@
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"plasma-manager": "plasma-manager",
|
||||
"plasma-manager-unstable": "plasma-manager-unstable"
|
||||
"plasma-manager-unstable": "plasma-manager-unstable",
|
||||
"pyproject-build-systems": "pyproject-build-systems",
|
||||
"pyproject-nix": "pyproject-nix",
|
||||
"uv2nix": "uv2nix"
|
||||
}
|
||||
},
|
||||
"uv2nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"pyproject-nix": [
|
||||
"pyproject-nix"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766021660,
|
||||
"narHash": "sha256-UUfz7qWB1Rb2KjGVCimt//Jncv3TgJwffPqbzqpkmgY=",
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "uv2nix",
|
||||
"rev": "19fa99be3409f55ec05e823c66c9769df7a8dd17",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "uv2nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
64
flake.nix
64
flake.nix
@@ -2,17 +2,17 @@
|
||||
description = "A very basic flake";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
|
||||
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";
|
||||
|
||||
nix-darwin = {
|
||||
url = "github:nix-darwin/nix-darwin/nix-darwin-25.05";
|
||||
url = "github:nix-darwin/nix-darwin/nix-darwin-25.11";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager/release-25.05";
|
||||
url = "github:nix-community/home-manager/release-25.11";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
@@ -42,22 +42,39 @@
|
||||
url = "github:Jovian-Experiments/Jovian-NixOS";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||
};
|
||||
|
||||
pyproject-nix = {
|
||||
url = "github:pyproject-nix/pyproject.nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
uv2nix = {
|
||||
url = "github:pyproject-nix/uv2nix";
|
||||
inputs.pyproject-nix.follows = "pyproject-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
pyproject-build-systems = {
|
||||
url = "github:pyproject-nix/build-system-pkgs";
|
||||
inputs.pyproject-nix.follows = "pyproject-nix";
|
||||
inputs.uv2nix.follows = "uv2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, ... } @ inputs: let
|
||||
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, pyproject-nix, uv2nix, pyproject-build-systems, ... } @ inputs: let
|
||||
nixosModules = [
|
||||
./roles
|
||||
] ++ [
|
||||
./roles/jovian-compat.nix
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
unstable = import nixpkgs-unstable {
|
||||
system = prev.system;
|
||||
system = prev.stdenv.hostPlatform.system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
custom = prev.callPackage ./packages {};
|
||||
custom = prev.callPackage ./packages { inherit uv2nix pyproject-nix pyproject-build-systems; };
|
||||
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
|
||||
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
|
||||
})
|
||||
@@ -82,10 +99,10 @@
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
unstable = import nixpkgs-unstable {
|
||||
system = prev.system;
|
||||
system = prev.stdenv.hostPlatform.system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
custom = prev.callPackage ./packages {};
|
||||
custom = prev.callPackage ./packages { inherit uv2nix pyproject-nix pyproject-build-systems; };
|
||||
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
|
||||
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
|
||||
})
|
||||
@@ -108,10 +125,17 @@
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
unstable = import nixpkgs-unstable {
|
||||
system = prev.system;
|
||||
system = prev.stdenv.hostPlatform.system;
|
||||
config.allowUnfree = true;
|
||||
overlays = [
|
||||
# Override claude-code in unstable to use our custom GCS-based build
|
||||
# (needed for corporate networks that block npm registry)
|
||||
(ufinal: uprev: {
|
||||
claude-code = prev.custom.claude-code or (prev.callPackage ./packages {}).claude-code;
|
||||
})
|
||||
];
|
||||
};
|
||||
custom = prev.callPackage ./packages {};
|
||||
custom = prev.callPackage ./packages { inherit uv2nix pyproject-nix pyproject-build-systems; };
|
||||
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
|
||||
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
|
||||
})
|
||||
@@ -133,7 +157,7 @@
|
||||
home-manager.users.johno = {
|
||||
imports = [ ./home/home-laptop-compact.nix ];
|
||||
# Machine-specific overrides
|
||||
home.i3_sway.extraSwayConfig = {
|
||||
home.roles.i3_sway.extraSwayConfig = {
|
||||
output.eDP-1.scale = "1.75";
|
||||
};
|
||||
};
|
||||
@@ -214,5 +238,21 @@
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# Flake apps
|
||||
apps = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" ] (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
update-doomemacs = pkgs.writeShellScriptBin "update-doomemacs" ''
|
||||
export PATH="${pkgs.lib.makeBinPath [ pkgs.curl pkgs.jq pkgs.nix pkgs.git pkgs.gnused pkgs.gnugrep pkgs.coreutils ]}:$PATH"
|
||||
${builtins.readFile ./scripts/update-doomemacs.sh}
|
||||
'';
|
||||
in {
|
||||
update-doomemacs = {
|
||||
type = "app";
|
||||
program = "${update-doomemacs}/bin/update-doomemacs";
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
{ config, lib, pkgs, globalInputs, system, ... }:
|
||||
|
||||
let
|
||||
leader = "cmd"; # Change this to experiment with different leader keys (e.g., "cmd", "ctrl")
|
||||
in
|
||||
{
|
||||
# Home Manager configuration for Darwin work laptop
|
||||
# Corporate-friendly setup with essential development tools
|
||||
@@ -13,39 +10,11 @@ in
|
||||
|
||||
# System packages
|
||||
home.packages = with pkgs; [
|
||||
autoraise
|
||||
google-cloud-sdk
|
||||
];
|
||||
|
||||
# Note: ghostty installed via Homebrew (managed outside of nix)
|
||||
|
||||
# Auto-start autoraise on login
|
||||
launchd.agents.autoraise = {
|
||||
enable = true;
|
||||
config = {
|
||||
ProgramArguments = [
|
||||
"${pkgs.autoraise}/bin/AutoRaise"
|
||||
"-pollMillis" "50"
|
||||
"-delay" "2"
|
||||
"-focusDelay" "2"
|
||||
];
|
||||
RunAtLoad = true;
|
||||
KeepAlive = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Auto-start aerospace on login
|
||||
# NOTE: In 25.11+, this can be simplified to `programs.aerospace.launchd.enable = true`
|
||||
launchd.agents.aerospace = {
|
||||
enable = true;
|
||||
config = {
|
||||
Program = "${pkgs.aerospace}/Applications/AeroSpace.app/Contents/MacOS/AeroSpace";
|
||||
RunAtLoad = true;
|
||||
KeepAlive = true;
|
||||
StandardOutPath = "/tmp/aerospace.log";
|
||||
StandardErrorPath = "/tmp/aerospace.err.log";
|
||||
};
|
||||
};
|
||||
|
||||
# Override Darwin-incompatible settings from base role
|
||||
programs.rbw.settings.pinentry = lib.mkForce pkgs.pinentry_mac;
|
||||
|
||||
@@ -127,107 +96,28 @@ in
|
||||
|
||||
home.shell.enableShellIntegration = true;
|
||||
|
||||
# TODO: Move this to its own role and/or module
|
||||
programs.aerospace = {
|
||||
enable = true;
|
||||
userSettings.mode.main.binding = {
|
||||
"${leader}-slash" = "layout tiles horizontal vertical";
|
||||
"${leader}-comma" = "layout accordion horizontal vertical";
|
||||
"${leader}-shift-q" = "close";
|
||||
"${leader}-shift-f" = "fullscreen";
|
||||
"${leader}-h" = "focus left";
|
||||
"${leader}-j" = "focus down";
|
||||
"${leader}-k" = "focus up";
|
||||
"${leader}-l" = "focus right";
|
||||
"${leader}-shift-h" = "move left";
|
||||
"${leader}-shift-j" = "move down";
|
||||
"${leader}-shift-k" = "move up";
|
||||
"${leader}-shift-l" = "move right";
|
||||
"${leader}-minus" = "resize smart -50";
|
||||
"${leader}-equal" = "resize smart +50";
|
||||
"${leader}-1" = "workspace 1";
|
||||
"${leader}-2" = "workspace 2";
|
||||
"${leader}-3" = "workspace 3";
|
||||
"${leader}-4" = "workspace 4";
|
||||
"${leader}-5" = "workspace 5";
|
||||
"${leader}-6" = "workspace 6";
|
||||
"${leader}-7" = "workspace 7";
|
||||
"${leader}-8" = "workspace 8";
|
||||
"${leader}-9" = "workspace 9";
|
||||
"${leader}-0" = "workspace 10";
|
||||
"${leader}-shift-1" = "move-node-to-workspace 1";
|
||||
"${leader}-shift-2" = "move-node-to-workspace 2";
|
||||
"${leader}-shift-3" = "move-node-to-workspace 3";
|
||||
"${leader}-shift-4" = "move-node-to-workspace 4";
|
||||
"${leader}-shift-5" = "move-node-to-workspace 5";
|
||||
"${leader}-shift-6" = "move-node-to-workspace 6";
|
||||
"${leader}-shift-7" = "move-node-to-workspace 7";
|
||||
"${leader}-shift-8" = "move-node-to-workspace 8";
|
||||
"${leader}-shift-9" = "move-node-to-workspace 9";
|
||||
"${leader}-shift-0" = "move-node-to-workspace 10";
|
||||
"${leader}-tab" = "workspace-back-and-forth";
|
||||
"${leader}-shift-tab" = "move-workspace-to-monitor --wrap-around next";
|
||||
|
||||
"${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
|
||||
'';
|
||||
|
||||
"${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
|
||||
'';
|
||||
|
||||
"${leader}-shift-e" = "exec-and-forget zsh --login -c \"emacsclient -c -n\"";
|
||||
|
||||
# Service mode: Deliberate aerospace window management
|
||||
"${leader}-i" = "mode service";
|
||||
|
||||
# Passthrough mode: Temporarily disable aerospace to use macOS shortcuts
|
||||
# Press Cmd-P, then use any macOS shortcut (like Cmd-K in Slack), then press Cmd-P again to exit
|
||||
"${leader}-p" = "mode passthrough";
|
||||
};
|
||||
|
||||
# Service mode: For deliberate aerospace window management operations
|
||||
userSettings.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"];
|
||||
|
||||
"${leader}-shift-h" = ["join-with left" "mode main"];
|
||||
"${leader}-shift-j" = ["join-with down" "mode main"];
|
||||
"${leader}-shift-k" = ["join-with up" "mode main"];
|
||||
"${leader}-shift-l" = ["join-with right" "mode main"];
|
||||
};
|
||||
|
||||
# Passthrough mode: All shortcuts pass through to macOS
|
||||
# This mode has minimal bindings - just ways to exit back to main mode
|
||||
userSettings.mode.passthrough.binding = {
|
||||
esc = "mode main";
|
||||
"${leader}-p" = "mode main"; # Toggle back with same key (Cmd-P)
|
||||
};
|
||||
};
|
||||
|
||||
home.roles = {
|
||||
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 = [
|
||||
./roles
|
||||
./modules/emacs
|
||||
./modules/kubectl
|
||||
./modules/tmux
|
||||
./roles/base-darwin
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,14 +8,21 @@
|
||||
|
||||
# Enable all desktop roles for full-featured experience
|
||||
home.roles = {
|
||||
"3d-printing".enable = true;
|
||||
base.enable = true;
|
||||
desktop.enable = true;
|
||||
emacs.enable = true;
|
||||
email.enable = true;
|
||||
i3_sway.enable = true;
|
||||
office.enable = true;
|
||||
media.enable = true;
|
||||
development.enable = true;
|
||||
communication.enable = true;
|
||||
sync.enable = true;
|
||||
kdeconnect.enable = true;
|
||||
kubectl.enable = true;
|
||||
tmux.enable = true;
|
||||
plasma-manager.enable = true;
|
||||
};
|
||||
|
||||
targets.genericLinux.enable = true;
|
||||
@@ -24,10 +31,6 @@
|
||||
|
||||
imports = [
|
||||
./roles
|
||||
./modules/emacs
|
||||
./modules/i3+sway
|
||||
./modules/kubectl
|
||||
./modules/plasma-manager
|
||||
./modules/tmux
|
||||
./roles/base-linux
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,23 @@
|
||||
desktop.enable = true;
|
||||
development.enable = true;
|
||||
communication.enable = true;
|
||||
email.enable = true;
|
||||
kdeconnect.enable = true;
|
||||
media.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;
|
||||
|
||||
# Launcher wrappers for excluded/optional packages
|
||||
launchers = {
|
||||
enable = true;
|
||||
packages = [
|
||||
"libreoffice"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
targets.genericLinux.enable = true;
|
||||
@@ -26,11 +39,6 @@
|
||||
|
||||
imports = [
|
||||
./roles
|
||||
./modules/emacs
|
||||
./modules/i3+sway
|
||||
./modules/kubectl
|
||||
./modules/plasma-manager
|
||||
./modules/tmux
|
||||
./roles/base-linux
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@@ -12,8 +12,12 @@
|
||||
home.roles = {
|
||||
base.enable = true;
|
||||
desktop.enable = true;
|
||||
tmux.enable = true;
|
||||
plasma-manager.enable = true;
|
||||
emacs.enable = true;
|
||||
i3_sway.enable = true;
|
||||
# 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
|
||||
# media.enable = false; # Not needed for live USB
|
||||
# sync.enable = false; # No persistent sync on live USB
|
||||
@@ -26,11 +30,7 @@
|
||||
|
||||
imports = [
|
||||
./roles
|
||||
./modules/emacs
|
||||
./modules/i3+sway
|
||||
./modules/kubectl
|
||||
./modules/plasma-manager
|
||||
./modules/tmux
|
||||
./roles/base-linux
|
||||
];
|
||||
|
||||
# Live USB specific overrides can go here if needed
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
communication.enable = true;
|
||||
kdeconnect.enable = true;
|
||||
development.enable = true;
|
||||
tmux.enable = true;
|
||||
plasma-manager.enable = true;
|
||||
emacs.enable = true;
|
||||
i3_sway.enable = true;
|
||||
# office.enable = false; # Not needed for media center
|
||||
# sync.enable = false; # Shared machine, no personal file sync
|
||||
};
|
||||
@@ -26,11 +30,7 @@
|
||||
|
||||
imports = [
|
||||
./roles
|
||||
./modules/emacs
|
||||
./modules/i3+sway
|
||||
./modules/kubectl
|
||||
./modules/plasma-manager
|
||||
./modules/tmux
|
||||
./roles/base-linux
|
||||
];
|
||||
|
||||
# Media center specific overrides can go here if needed
|
||||
|
||||
@@ -1,178 +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-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 = "${../../wallpapers/metroid-samus-returns-kz-3440x1440.jpg}";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
8
home/roles/base-linux/default.nix
Normal file
8
home/roles/base-linux/default.nix
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
# Base imports for Linux home configurations
|
||||
# Includes Linux-specific roles that require Linux-only home-manager modules
|
||||
imports = [
|
||||
../plasma-manager
|
||||
../i3+sway
|
||||
];
|
||||
}
|
||||
@@ -24,6 +24,17 @@ in
|
||||
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
|
||||
programs.bash = {
|
||||
enable = true;
|
||||
@@ -41,9 +52,9 @@ in
|
||||
|
||||
programs.git = {
|
||||
enable = true;
|
||||
userName = "John Ogle";
|
||||
userEmail = "john@ogle.fyi";
|
||||
extraConfig = {
|
||||
settings = {
|
||||
user.name = "John Ogle";
|
||||
user.email = "john@ogle.fyi";
|
||||
safe.directory = "/etc/nixos";
|
||||
};
|
||||
};
|
||||
@@ -58,8 +69,11 @@ in
|
||||
|
||||
programs.ssh = {
|
||||
enable = true;
|
||||
addKeysToAgent = "yes";
|
||||
enableDefaultConfig = false;
|
||||
matchBlocks = {
|
||||
"*" = {
|
||||
addKeysToAgent = "yes";
|
||||
};
|
||||
"nucdeb1" = {
|
||||
hostname = "nucdeb1.oglehome";
|
||||
user = "root";
|
||||
|
||||
@@ -14,7 +14,8 @@ in
|
||||
home.packages = [
|
||||
# Communication apps
|
||||
pkgs.element-desktop
|
||||
#pkgs.fluffychat #marked insecure as of nixos 25.05
|
||||
# Re-enabled in 25.11 after security issues were resolved
|
||||
pkgs.fluffychat
|
||||
pkgs.nextcloud-talk-desktop
|
||||
|
||||
# For logging back into google chat
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
{
|
||||
# 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 = [
|
||||
./3d-printing
|
||||
./base
|
||||
./communication
|
||||
./desktop
|
||||
./development
|
||||
./email
|
||||
./gaming
|
||||
./kdeconnect
|
||||
./kubectl
|
||||
./launchers
|
||||
./media
|
||||
./office
|
||||
./sync
|
||||
./tmux
|
||||
./emacs
|
||||
];
|
||||
}
|
||||
|
||||
@@ -81,6 +81,45 @@ in
|
||||
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";
|
||||
|
||||
@@ -4,22 +4,90 @@ with lib;
|
||||
|
||||
let
|
||||
cfg = config.home.roles.development;
|
||||
|
||||
# Fetch the claude-plugins repository
|
||||
# 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
|
||||
{
|
||||
options.home.roles.development = {
|
||||
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 {
|
||||
home.packages = [
|
||||
pkgs.unstable.claude-code
|
||||
pkgs.codex
|
||||
pkgs.unstable.claude-code-router
|
||||
pkgs.unstable.codex
|
||||
|
||||
# Custom packages
|
||||
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
|
||||
|
||||
$DRY_RUN_CMD echo "Claude Code humanlayer commands and agents installed successfully${
|
||||
if cfg.allowArbitraryClaudeCodeModelSelection
|
||||
then " (model specifications preserved)"
|
||||
else " (model selection removed)"
|
||||
}"
|
||||
'';
|
||||
|
||||
# Note: modules must be imported at top-level home config
|
||||
};
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.home.roles.emacs;
|
||||
|
||||
doomEmacs = pkgs.fetchFromGitHub {
|
||||
owner = "doomemacs";
|
||||
repo = "doomemacs";
|
||||
rev = "8f55404781edacf66fa330205533b002de3fb5ee";
|
||||
sha256 = "sha256-vHwgENjip2+AFzs4oZfnKEAJKwf5Zid7fakImvxxQUw=";
|
||||
rev = "762f47805ac2a6411e11747f86f7c19a03da326e";
|
||||
sha256 = "sha256-0w0eXGB2cgxu/hr5wTiJSZDJw0NF+fZvLbzEylH5URU=";
|
||||
};
|
||||
|
||||
# Shared emacs packages
|
||||
@@ -17,20 +19,23 @@ let
|
||||
];
|
||||
|
||||
# Default emacs configuration with vterm support
|
||||
defaultEmacsPackage =
|
||||
if pkgs.stdenv.isDarwin
|
||||
defaultEmacsPackage =
|
||||
if pkgs.stdenv.isDarwin
|
||||
then pkgs.emacs-macport.pkgs.withPackages emacsPackages
|
||||
else pkgs.emacs.pkgs.withPackages emacsPackages;
|
||||
in
|
||||
{
|
||||
config = {
|
||||
options.home.roles.emacs = {
|
||||
enable = mkEnableOption "Doom Emacs with vterm and tree-sitter support";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
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
|
||||
@@ -66,7 +71,7 @@ in
|
||||
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"
|
||||
ln -sf "${config.home.homeDirectory}/nixos-configs/home/roles/emacs/doom" "${config.xdg.configHome}/doom"
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -159,6 +159,32 @@
|
||||
(dolist (module '("bbdb" "buffer" "elisp" "emacs" "gnus" "os" "search-and-replace" "url"))
|
||||
(gptel-tool-library-load-module module)))
|
||||
|
||||
;; Notmuch email configuration
|
||||
(after! notmuch
|
||||
(setq notmuch-search-oldest-first nil
|
||||
notmuch-show-logo nil
|
||||
notmuch-fcc-dirs "proton/Sent"
|
||||
|
||||
;; User identity
|
||||
user-mail-address "john@ogle.fyi"
|
||||
user-full-name "John Ogle"
|
||||
|
||||
;; Sending mail via msmtp
|
||||
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
|
||||
|
||||
;; Saved searches for quick access
|
||||
notmuch-saved-searches
|
||||
'((:name "inbox" :query "tag:inbox" :key "i")
|
||||
(:name "unread" :query "tag:unread" :key "u")
|
||||
(:name "flagged" :query "tag:flagged" :key "f")
|
||||
(:name "sent" :query "tag:sent" :key "t")
|
||||
(:name "drafts" :query "tag:draft" :key "d")
|
||||
(:name "all" :query "*" :key "a"))))
|
||||
|
||||
;; 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.
|
||||
;;
|
||||
@@ -177,7 +177,7 @@
|
||||
|
||||
:email
|
||||
;;(mu4e +org +gmail)
|
||||
;;notmuch
|
||||
notmuch
|
||||
;;(wanderlust +gmail)
|
||||
|
||||
:app
|
||||
128
home/roles/email/default.nix
Normal file
128
home/roles/email/default.nix
Normal file
@@ -0,0 +1,128 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.home.roles.email;
|
||||
in
|
||||
{
|
||||
options.home.roles.email = {
|
||||
enable = mkEnableOption "Enable email with notmuch, mbsync, and msmtp";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
home.packages = with pkgs; [
|
||||
isync # provides mbsync for IMAP sync
|
||||
msmtp # for SMTP sending
|
||||
notmuch # email indexing and search
|
||||
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 - Sync All
|
||||
Channel proton
|
||||
Far :proton-remote:
|
||||
Near :proton-local:
|
||||
Patterns *
|
||||
Create Both
|
||||
Expunge Both
|
||||
SyncState *
|
||||
'';
|
||||
|
||||
# Notmuch configuration
|
||||
home.file.".notmuch-config".text = ''
|
||||
[database]
|
||||
path=${config.home.homeDirectory}/Mail
|
||||
|
||||
[user]
|
||||
name=John Ogle
|
||||
primary_email=john@ogle.fyi
|
||||
|
||||
[new]
|
||||
tags=unread;inbox;
|
||||
ignore=
|
||||
|
||||
[search]
|
||||
exclude_tags=deleted;spam;
|
||||
|
||||
[maildir]
|
||||
synchronize_flags=true
|
||||
'';
|
||||
|
||||
# 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 '${pkgs.isync}/bin/mbsync -a && ${pkgs.notmuch}/bin/notmuch new'";
|
||||
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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.home.i3_sway;
|
||||
cfg = config.home.roles.i3_sway;
|
||||
|
||||
shared_config = recursiveUpdate rec {
|
||||
modifier = "Mod4";
|
||||
@@ -14,6 +14,9 @@ let
|
||||
"${shared_config.modifier}+Return" = "exec ${terminal}";
|
||||
"${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";
|
||||
@@ -45,8 +48,6 @@ let
|
||||
"${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";
|
||||
|
||||
@@ -92,19 +93,29 @@ let
|
||||
};
|
||||
} cfg.extraSharedConfig;
|
||||
in {
|
||||
options.home.i3_sway = {
|
||||
options.home.roles.i3_sway = {
|
||||
enable = mkEnableOption "i3 and Sway tiling window managers with waybar and rofi";
|
||||
|
||||
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 = {
|
||||
config = mkIf cfg.enable {
|
||||
# i3blocks configuration file
|
||||
home.file.".config/i3blocks/config".text = ''
|
||||
# i3blocks config - replicating waybar setup
|
||||
@@ -316,6 +327,7 @@ in {
|
||||
};
|
||||
in {
|
||||
enable = true;
|
||||
extraOptions = [ "--unsupported-gpu" ];
|
||||
config = recursiveUpdate base_sway_config cfg.extraSwayConfig;
|
||||
};
|
||||
|
||||
@@ -331,7 +343,7 @@ in {
|
||||
|
||||
modules-left = [ "sway/workspaces" "sway/mode" ];
|
||||
modules-center = [ ];
|
||||
modules-right = [ "disk" "cpu" "memory" "pulseaudio" "backlight" "network" "battery" "tray" "clock" ];
|
||||
modules-right = [ "disk" "cpu" "memory" "pulseaudio" "custom/backlight-ddc" "backlight" "network" "battery" "tray" "clock" ];
|
||||
|
||||
"sway/workspaces" = {
|
||||
disable-scroll = true;
|
||||
@@ -388,6 +400,23 @@ in {
|
||||
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}";
|
||||
@@ -419,7 +448,7 @@ in {
|
||||
|
||||
#workspaces button {
|
||||
padding: 0 8px;
|
||||
background-color: transparent;
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, globalInputs, system, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.programs.kubectl-secure;
|
||||
cfg = config.home.roles.kubectl;
|
||||
in
|
||||
{
|
||||
options.programs.kubectl-secure = {
|
||||
enable = mkEnableOption "secure kubectl configuration with Bitwarden integration";
|
||||
options.home.roles.kubectl = {
|
||||
enable = mkEnableOption "management tools for the homelab k3s oglenet cluster with secure Bitwarden integration";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@@ -21,29 +21,29 @@ in
|
||||
programs.bash.initExtra = mkAfter ''
|
||||
# Kubectl secure session management
|
||||
export KUBECTL_SESSION_DIR="/dev/shm/kubectl-$$"
|
||||
|
||||
|
||||
kube-select() {
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: kube-select <context-name>"
|
||||
echo "Available contexts: $(kube-list)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
local context="$1"
|
||||
|
||||
|
||||
# Clean up any existing session first
|
||||
kube-clear 2>/dev/null
|
||||
|
||||
|
||||
# Create new session directory
|
||||
mkdir -p "$KUBECTL_SESSION_DIR"
|
||||
chmod 700 "$KUBECTL_SESSION_DIR"
|
||||
|
||||
|
||||
# Set cleanup trap for this shell session
|
||||
trap "rm -rf '$KUBECTL_SESSION_DIR' 2>/dev/null" EXIT
|
||||
|
||||
|
||||
# Set KUBECONFIG for this session
|
||||
export KUBECONFIG="$KUBECTL_SESSION_DIR/config"
|
||||
|
||||
|
||||
# Load config from Bitwarden secure notes
|
||||
if ! rbw get "kubectl-$context" > "$KUBECONFIG" 2>/dev/null; then
|
||||
echo "Error: Could not retrieve kubectl-$context from Bitwarden"
|
||||
@@ -51,37 +51,37 @@ in
|
||||
kube-clear
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
# Verify the kubeconfig is valid
|
||||
if ! kubectl config view >/dev/null 2>&1; then
|
||||
echo "Error: Invalid kubeconfig retrieved from Bitwarden"
|
||||
kube-clear
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
echo "✓ Loaded kubectl context: $context (session: $$)"
|
||||
echo " Config location: $KUBECONFIG"
|
||||
}
|
||||
|
||||
|
||||
kube-list() {
|
||||
echo "Available kubectl contexts in Bitwarden:"
|
||||
rbw search kubectl- 2>/dev/null | grep "^kubectl-" | sed 's/^kubectl-/ - /' || echo " (none found or rbw not accessible)"
|
||||
}
|
||||
|
||||
|
||||
kube-clear() {
|
||||
if [[ -n "$KUBECTL_TIMEOUT_PID" ]]; then
|
||||
kill "$KUBECTL_TIMEOUT_PID" 2>/dev/null
|
||||
unset KUBECTL_TIMEOUT_PID
|
||||
fi
|
||||
|
||||
|
||||
if [[ -d "$KUBECTL_SESSION_DIR" ]]; then
|
||||
rm -rf "$KUBECTL_SESSION_DIR"
|
||||
echo "Cleared kubectl session ($$)"
|
||||
fi
|
||||
|
||||
|
||||
unset KUBECONFIG
|
||||
}
|
||||
|
||||
|
||||
kube-status() {
|
||||
if [[ -f "$KUBECONFIG" ]]; then
|
||||
local current_context
|
||||
@@ -89,7 +89,7 @@ in
|
||||
if [[ -n "$current_context" ]]; then
|
||||
echo "Active kubectl context: $current_context"
|
||||
echo "Session: $$ | Config: $KUBECONFIG"
|
||||
|
||||
|
||||
# Show cluster info
|
||||
local cluster_server
|
||||
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"
|
||||
}
|
||||
'';
|
||||
|
||||
|
||||
programs.zsh.initExtra = mkAfter ''
|
||||
# Kubectl secure session management (zsh)
|
||||
export KUBECTL_SESSION_DIR="/dev/shm/kubectl-$$"
|
||||
|
||||
|
||||
kube-select() {
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: kube-select <context-name>"
|
||||
echo "Available contexts: $(kube-list)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
local context="$1"
|
||||
|
||||
|
||||
# Clean up any existing session first
|
||||
kube-clear 2>/dev/null
|
||||
|
||||
|
||||
# Create new session directory
|
||||
mkdir -p "$KUBECTL_SESSION_DIR"
|
||||
chmod 700 "$KUBECTL_SESSION_DIR"
|
||||
|
||||
|
||||
# Set cleanup trap for this shell session
|
||||
trap "rm -rf '$KUBECTL_SESSION_DIR' 2>/dev/null" EXIT
|
||||
|
||||
|
||||
# Set KUBECONFIG for this session
|
||||
export KUBECONFIG="$KUBECTL_SESSION_DIR/config"
|
||||
|
||||
|
||||
# Load config from Bitwarden secure notes
|
||||
if ! rbw get "kubectl-$context" > "$KUBECONFIG" 2>/dev/null; then
|
||||
echo "Error: Could not retrieve kubectl-$context from Bitwarden"
|
||||
@@ -162,43 +162,37 @@ in
|
||||
kube-clear
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
# Verify the kubeconfig is valid
|
||||
if ! kubectl config view >/dev/null 2>&1; then
|
||||
echo "Error: Invalid kubeconfig retrieved from Bitwarden"
|
||||
kube-clear
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
echo "✓ Loaded kubectl context: $context (session: $$)"
|
||||
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() {
|
||||
echo "Available kubectl contexts in Bitwarden:"
|
||||
rbw search kubectl- 2>/dev/null | grep "^kubectl-" | sed 's/^kubectl-/ - /' || echo " (none found or rbw not accessible)"
|
||||
}
|
||||
|
||||
|
||||
kube-clear() {
|
||||
if [[ -n "$KUBECTL_TIMEOUT_PID" ]]; then
|
||||
kill "$KUBECTL_TIMEOUT_PID" 2>/dev/null
|
||||
unset KUBECTL_TIMEOUT_PID
|
||||
fi
|
||||
|
||||
|
||||
if [[ -d "$KUBECTL_SESSION_DIR" ]]; then
|
||||
rm -rf "$KUBECTL_SESSION_DIR"
|
||||
echo "Cleared kubectl session ($$)"
|
||||
fi
|
||||
|
||||
|
||||
unset KUBECONFIG
|
||||
}
|
||||
|
||||
|
||||
kube-status() {
|
||||
if [[ -f "$KUBECONFIG" ]]; then
|
||||
local current_context
|
||||
@@ -206,7 +200,7 @@ in
|
||||
if [[ -n "$current_context" ]]; then
|
||||
echo "Active kubectl context: $current_context"
|
||||
echo "Session: $$ | Config: $KUBECONFIG"
|
||||
|
||||
|
||||
# Show cluster info
|
||||
local cluster_server
|
||||
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;
|
||||
};
|
||||
}
|
||||
188
home/roles/plasma-manager/default.nix
Normal file
188
home/roles/plasma-manager/default.nix
Normal file
@@ -0,0 +1,188 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.home.roles.plasma-manager;
|
||||
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 = "${../../wallpapers/metroid-samus-returns-kz-3440x1440.jpg}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
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
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -26,6 +26,7 @@ with lib;
|
||||
enable = true;
|
||||
autologin = true;
|
||||
wayland = true;
|
||||
jellyfinScaleFactor = 2.5;
|
||||
};
|
||||
users.enable = true;
|
||||
};
|
||||
@@ -39,12 +40,7 @@ with lib;
|
||||
services.xserver.videoDrivers = [ "amdgpu" ];
|
||||
hardware.graphics.enable = true;
|
||||
hardware.graphics.enable32Bit = true;
|
||||
hardware.graphics.extraPackages = with pkgs; [
|
||||
amdvlk
|
||||
];
|
||||
hardware.graphics.extraPackages32 = with pkgs; [
|
||||
driversi686Linux.amdvlk
|
||||
];
|
||||
# RADV (AMD's Vulkan driver) is now enabled by default, amdvlk was removed
|
||||
|
||||
# This option defines the first version of NixOS you have installed on this particular machine,
|
||||
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
desktop = {
|
||||
enable = true;
|
||||
wayland = true;
|
||||
gaming.enable = false;
|
||||
gaming.enable = true;
|
||||
kde = true;
|
||||
sddm = true;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
../../roles/desktop/steamos.nix
|
||||
];
|
||||
|
||||
roles = {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
extraPackages = with pkgs; [
|
||||
mesa
|
||||
libvdpau-va-gl
|
||||
vaapiVdpau
|
||||
libva-vdpau-driver
|
||||
];
|
||||
};
|
||||
environment.sessionVariables = {
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
with lib;
|
||||
|
||||
{
|
||||
imports =
|
||||
[ # Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./virtual-surround.nix
|
||||
];
|
||||
|
||||
roles = {
|
||||
audio.enable = true;
|
||||
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
||||
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.75";
|
||||
|
||||
srcs = {
|
||||
aarch64-darwin = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-arm64/claude";
|
||||
sha256 = "a96eb18218e112486b7ecebd1551d927ffb310ab5fb06d2e8db25fb31367537e";
|
||||
};
|
||||
x86_64-darwin = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-x64/claude";
|
||||
sha256 = "e27313053d3268a0bc1e0080f8c2ef7155325f0a95e72971163eef698a71e829";
|
||||
};
|
||||
x86_64-linux = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-x64/claude";
|
||||
sha256 = "62160f8766681d8c933e9133398d3dde6ad0df08038881a66eddb993b4b6a33f";
|
||||
};
|
||||
aarch64-linux = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-arm64/claude";
|
||||
sha256 = "681fbd1a84b2de883dc954441693766b43ea4faafb3e72b88c99a33645cd3507";
|
||||
};
|
||||
};
|
||||
|
||||
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";
|
||||
};
|
||||
}
|
||||
132
packages/claude-code/update.sh
Executable file
132
packages/claude-code/update.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/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"
|
||||
NIX_FILE="$(dirname "$0")/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,6 +1,8 @@
|
||||
{ pkgs, ... }:
|
||||
{ pkgs, uv2nix ? null, pyproject-nix ? null, pyproject-build-systems ? null, ... }:
|
||||
{
|
||||
vulkanHDRLayer = pkgs.callPackage ./vulkan-hdr-layer {};
|
||||
tea-rbw = pkgs.callPackage ./tea-rbw {};
|
||||
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
|
||||
claude-code = pkgs.callPackage ./claude-code {};
|
||||
sendspin-cli = pkgs.callPackage ./sendspin-cli { inherit uv2nix pyproject-nix pyproject-build-systems; };
|
||||
}
|
||||
|
||||
91
packages/sendspin-cli/default.nix
Normal file
91
packages/sendspin-cli/default.nix
Normal file
@@ -0,0 +1,91 @@
|
||||
{ pkgs
|
||||
, lib
|
||||
, fetchFromGitHub
|
||||
, uv2nix ? null
|
||||
, pyproject-nix ? null
|
||||
, pyproject-build-systems ? null
|
||||
}:
|
||||
|
||||
# Simple package build
|
||||
# Note: uv2nix would be ideal but requires uv.lock which sendspin-cli doesn't have yet
|
||||
let
|
||||
# Package aiosendspin from GitHub since it's only in nixpkgs-unstable
|
||||
aiosendspin = pkgs.python312Packages.buildPythonPackage rec {
|
||||
pname = "aiosendspin";
|
||||
version = "1.2.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Sendspin";
|
||||
repo = "aiosendspin";
|
||||
rev = version;
|
||||
sha256 = "sha256-3vTEfXeFqouPswRKST/9U7yg9ah7J9m2KAMoxaBZNR0=";
|
||||
};
|
||||
|
||||
build-system = with pkgs.python312Packages; [
|
||||
hatchling
|
||||
setuptools
|
||||
];
|
||||
|
||||
dependencies = with pkgs.python312Packages; [
|
||||
aiohttp
|
||||
av
|
||||
mashumaro
|
||||
orjson
|
||||
pillow
|
||||
zeroconf
|
||||
];
|
||||
|
||||
pythonImportsCheck = [ "aiosendspin" ];
|
||||
|
||||
meta = {
|
||||
description = "Async Python implementation of the Sendspin Protocol";
|
||||
homepage = "https://github.com/Sendspin-Protocol/aiosendspin";
|
||||
license = lib.licenses.asl20;
|
||||
};
|
||||
};
|
||||
|
||||
python = pkgs.python312.withPackages (ps: with ps; [
|
||||
# Core dependencies from pyproject.toml
|
||||
aiosendspin
|
||||
av
|
||||
numpy
|
||||
qrcode
|
||||
readchar
|
||||
rich
|
||||
sounddevice
|
||||
setuptools
|
||||
]);
|
||||
in
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "sendspin-cli";
|
||||
version = "0.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Sendspin";
|
||||
repo = "sendspin-cli";
|
||||
rev = "main";
|
||||
sha256 = "sha256-z8ieaDHv4C6WNLpPGybhcfB+E6Jj/rCc7zSRpL6vdk0=";
|
||||
};
|
||||
|
||||
buildInputs = [ python pkgs.portaudio pkgs.ffmpeg ];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin $out/lib
|
||||
cp -r sendspin $out/lib/
|
||||
cat > $out/bin/sendspin <<EOF
|
||||
#!/bin/sh
|
||||
export PYTHONPATH="$out/lib:\$PYTHONPATH"
|
||||
export LD_LIBRARY_PATH="${pkgs.portaudio}/lib:${pkgs.ffmpeg}/lib:\$LD_LIBRARY_PATH"
|
||||
exec ${python}/bin/python3 -m sendspin.cli "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/sendspin
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Synchronized audio player for Sendspin servers";
|
||||
homepage = "https://github.com/Sendspin/sendspin-cli";
|
||||
license = lib.licenses.asl20;
|
||||
platforms = lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,5 @@ with lib;
|
||||
./kde.nix
|
||||
./programs.nix
|
||||
./sddm.nix
|
||||
./steamos.nix
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,24 +9,27 @@ in
|
||||
config = mkMerge [
|
||||
(mkIf (cfg.enable && cfg.gaming.enable) {
|
||||
environment.systemPackages = with pkgs; [
|
||||
steam
|
||||
lutris
|
||||
moonlight
|
||||
|
||||
# Emulators
|
||||
dolphin-emu
|
||||
|
||||
# Re-enabled in 25.11 after binary build was fixed
|
||||
dolphin-emu-primehack
|
||||
retroarch-full
|
||||
|
||||
# Experimenting with just using the steam version + downloading
|
||||
# indiviudal cores
|
||||
#retroarch-full
|
||||
ryubing
|
||||
];
|
||||
# TODO: Remove me once dolphin-emu and dolphin-emu-primehack update
|
||||
# dependencies to mbedtls from mbedtls_2 (which is currently)
|
||||
# unmaintained
|
||||
nixpkgs.config.permittedInsecurePackages = [ "mbedtls-2.28.10" ];
|
||||
|
||||
warnings = [
|
||||
"Using insecure mbedtls-2.28.10 for Dolphin Emu - check for updates regularly"
|
||||
];
|
||||
programs.steam = {
|
||||
enable = true;
|
||||
remotePlay.openFirewall = true;
|
||||
dedicatedServer.openFirewall = true;
|
||||
localNetworkGameTransfers.openFirewall = true;
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
{ lib, config, ... }:
|
||||
|
||||
# Minimal Jovian compatibility layer for NixOS stable (25.05)
|
||||
# Defines only the Jovian options used by roles/desktop/steamos.nix
|
||||
# No actual implementation - just option definitions to prevent evaluation errors
|
||||
# REMOVE THIS FILE when all systems are on NixOS 25.11+ or unstable
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
nixosVersion = config.system.nixos.release;
|
||||
isCompatibleVersion = versionOlder nixosVersion "25.11";
|
||||
in
|
||||
{
|
||||
options.jovian = {
|
||||
steam = {
|
||||
enable = mkEnableOption "Steam (jovian-compat stub)";
|
||||
|
||||
autoStart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Auto-start Steam (jovian-compat stub)";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "user";
|
||||
description = "Steam user (jovian-compat stub)";
|
||||
};
|
||||
|
||||
desktopSession = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Desktop session (jovian-compat stub)";
|
||||
};
|
||||
};
|
||||
|
||||
decky-loader = {
|
||||
enable = mkEnableOption "Decky Loader (jovian-compat stub)";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = isCompatibleVersion;
|
||||
message = ''
|
||||
The Jovian compatibility shim (roles/jovian-compat.nix) is only needed for NixOS 25.05 and earlier.
|
||||
You are running NixOS ${nixosVersion}.
|
||||
Please remove 'roles/jovian-compat.nix' from your flake.nix nixosModules list.
|
||||
'';
|
||||
}
|
||||
];
|
||||
}
|
||||
# No config implementation - these options do nothing on stable systems
|
||||
# steamos role is only enabled on nix-deck which uses unstable anyway
|
||||
(mkIf config.jovian.steam.enable {
|
||||
warnings = [
|
||||
"Jovian is enabled but you're using the compatibility stub. This won't work correctly. Use NixOS unstable for Jovian support."
|
||||
];
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -14,6 +14,11 @@ in
|
||||
wayland = mkOption {
|
||||
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;
|
||||
@@ -37,6 +42,28 @@ in
|
||||
steam-library
|
||||
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
|
||||
{
|
||||
users.extraUsers.kodi = {
|
||||
@@ -50,11 +77,18 @@ in
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
jellyfinMediaPlayerPkg
|
||||
kodiPkg
|
||||
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;
|
||||
|
||||
systemd.user.services = mkIf cfg.appLauncherServer.enable {
|
||||
|
||||
@@ -25,7 +25,7 @@ in
|
||||
users.users.johno = {
|
||||
isNormalUser = true;
|
||||
description = "John Ogle";
|
||||
extraGroups = [ "wheel" "networkmanager" "audio" "video" ] ++ cfg.extraGroups;
|
||||
extraGroups = [ "wheel" "networkmanager" "audio" "video" "i2c" ] ++ cfg.extraGroups;
|
||||
};
|
||||
|
||||
users.users.eli = mkIf cfg.kids {
|
||||
|
||||
82
scripts/update-doomemacs.sh
Executable file
82
scripts/update-doomemacs.sh
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
OWNER="doomemacs"
|
||||
REPO="doomemacs"
|
||||
FILE="home/roles/emacs/default.nix"
|
||||
# Use current working directory as repo root (allows running from anywhere in the repo)
|
||||
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||
TARGET_FILE="$REPO_ROOT/$FILE"
|
||||
|
||||
echo -e "${GREEN}Updating DoomEmacs to latest commit...${NC}"
|
||||
|
||||
# Check if file exists
|
||||
if [[ ! -f "$TARGET_FILE" ]]; then
|
||||
echo -e "${RED}Error: $TARGET_FILE not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the default branch first
|
||||
echo "Fetching repository information..."
|
||||
DEFAULT_BRANCH=$(curl -s "https://api.github.com/repos/$OWNER/$REPO" | jq -r '.default_branch')
|
||||
|
||||
if [[ -z "$DEFAULT_BRANCH" ]] || [[ "$DEFAULT_BRANCH" == "null" ]]; then
|
||||
echo -e "${RED}Error: Failed to fetch default branch${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the latest commit SHA from GitHub
|
||||
echo "Fetching latest commit SHA from $DEFAULT_BRANCH branch..."
|
||||
LATEST_SHA=$(curl -s "https://api.github.com/repos/$OWNER/$REPO/commits/$DEFAULT_BRANCH" | jq -r '.sha')
|
||||
|
||||
if [[ -z "$LATEST_SHA" ]] || [[ "$LATEST_SHA" == "null" ]]; then
|
||||
echo -e "${RED}Error: Failed to fetch latest commit SHA${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "Latest commit: ${YELLOW}$LATEST_SHA${NC}"
|
||||
|
||||
# Get current SHA from file
|
||||
CURRENT_SHA=$(grep -oP 'rev = "\K[^"]+' "$TARGET_FILE")
|
||||
echo -e "Current commit: ${YELLOW}$CURRENT_SHA${NC}"
|
||||
|
||||
if [[ "$CURRENT_SHA" == "$LATEST_SHA" ]]; then
|
||||
echo -e "${GREEN}Already up to date!${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update the rev field
|
||||
echo "Updating rev in $FILE..."
|
||||
sed -i "s/rev = \".*\"/rev = \"$LATEST_SHA\"/" "$TARGET_FILE"
|
||||
|
||||
# Fetch the new sha256 hash using nix-prefetch
|
||||
echo "Fetching new sha256 hash..."
|
||||
NEW_SHA256=$(nix-prefetch-url --unpack "https://github.com/$OWNER/$REPO/archive/$LATEST_SHA.tar.gz" 2>/dev/null)
|
||||
|
||||
if [[ -z "$NEW_SHA256" ]]; then
|
||||
echo -e "${RED}Error: Failed to fetch sha256 hash${NC}"
|
||||
# Revert the rev change
|
||||
sed -i "s/rev = \".*\"/rev = \"$CURRENT_SHA\"/" "$TARGET_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Convert to SRI hash format
|
||||
SRI_HASH=$(nix hash to-sri --type sha256 "$NEW_SHA256")
|
||||
echo -e "New sha256: ${YELLOW}$SRI_HASH${NC}"
|
||||
|
||||
# Update the sha256 field
|
||||
sed -i "s|sha256 = \".*\"|sha256 = \"$SRI_HASH\"|" "$TARGET_FILE"
|
||||
|
||||
echo -e "${GREEN}Successfully updated DoomEmacs!${NC}"
|
||||
echo -e " Old commit: ${YELLOW}$CURRENT_SHA${NC}"
|
||||
echo -e " New commit: ${YELLOW}$LATEST_SHA${NC}"
|
||||
echo -e " New sha256: ${YELLOW}$SRI_HASH${NC}"
|
||||
echo ""
|
||||
echo "You can now rebuild your system with the updated DoomEmacs."
|
||||
660
thoughts/shared/plans/2025-12-29-sendspin-cli-integration.md
Normal file
660
thoughts/shared/plans/2025-12-29-sendspin-cli-integration.md
Normal file
@@ -0,0 +1,660 @@
|
||||
# Sendspin-CLI Integration Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Integrate sendspin-cli (https://github.com/Sendspin/sendspin-cli) into the NixOS configuration using uv2nix for Python packaging. Provide a flexible systemd service template that runs as the graphical user, supporting multiple use cases: standalone media centers (like `boxy` running as `kodi` user) and desktop workstations (like `zix790prors` running as `johno` user).
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
**Existing Infrastructure:**
|
||||
- Custom package system using overlays at flake.nix:54-59
|
||||
- Packages defined in `packages/default.nix` and exposed as `pkgs.custom.<name>`
|
||||
- Role-based configuration system with audio role at roles/audio/default.nix:1-41
|
||||
- User-level systemd services pattern demonstrated in machines/zix790prors/virtual-surround.nix:79-103
|
||||
- Python packaging pattern using `python3.withPackages` at packages/app-launcher-server/default.nix:1-10
|
||||
|
||||
**Sendspin-CLI Analysis:**
|
||||
- Python 3.12+ application with setuptools build system
|
||||
- Entry point: `sendspin.cli:main` (sendspin-cli/sendspin/cli.py:143)
|
||||
- Dependencies: aiosendspin, av, numpy, qrcode, readchar, rich, sounddevice (sendspin-cli/pyproject.toml:16-23)
|
||||
- Native dependencies needed: portaudio (for sounddevice), ffmpeg (for av)
|
||||
- Supports headless mode via `--headless` flag (sendspin-cli/sendspin/cli.py:115-117)
|
||||
- Device selection via `--audio-device <index|name>` (sendspin-cli/sendspin/cli.py:96-102)
|
||||
- Device discovery via `--list-audio-devices` (sendspin-cli/sendspin/cli.py:15-34)
|
||||
- Auto-discovers servers via mDNS unless `--url` specified (sendspin-cli/sendspin/cli.py:69-71)
|
||||
- No `uv.lock` file currently in repository (only `pyproject.toml`)
|
||||
|
||||
**Gap:**
|
||||
- No uv2nix flake inputs (pyproject-nix, uv2nix, pyproject-build-systems)
|
||||
- No sendspin-cli package definition
|
||||
- No sendspin role or systemd service configuration
|
||||
|
||||
## Desired End State
|
||||
|
||||
After implementation completion:
|
||||
1. **Package available**: `pkgs.custom.sendspin-cli` builds successfully with all dependencies
|
||||
2. **Role available**: `roles.sendspin.enable = true` provides sendspin with configurable service
|
||||
3. **Service template**: Systemd user service runs as graphical session user with configurable audio device
|
||||
4. **Audio device compatibility**: Uses sendspin's native device specification (index or name prefix)
|
||||
5. **Flexible user context**: Service can run as kodi, johno, or any graphical session user
|
||||
|
||||
### Verification Commands:
|
||||
```bash
|
||||
# Package builds successfully
|
||||
nix build .#nixosConfigurations.zix790prors.config.environment.systemPackages --no-link | grep sendspin-cli
|
||||
|
||||
# Package contains working executable
|
||||
$(nix-build -E '(import <nixpkgs> {}).callPackage ./packages/sendspin-cli {}')/bin/sendspin --help
|
||||
|
||||
# Service template is generated
|
||||
nixos-rebuild dry-build --flake .#zix790prors 2>&1 | grep sendspin
|
||||
```
|
||||
|
||||
## Key Discoveries
|
||||
|
||||
### uv2nix Integration Points
|
||||
- **No lock file**: sendspin-cli has `pyproject.toml` but no `uv.lock` - uv2nix will resolve from pyproject.toml
|
||||
- **Native dependencies**: sounddevice and av require portaudio and ffmpeg in buildInputs
|
||||
- **Workspace loading**: uv2nix.lib.workspace.loadWorkspace works with pyproject.toml-only projects
|
||||
- **Build system**: Uses setuptools (declared in pyproject.toml:1-3)
|
||||
|
||||
### Service Architecture
|
||||
- **User services**: Must use `systemd.user.services` (not system services) for audio access
|
||||
- **Automatic user detection**: User services run in the logged-in graphical user's session
|
||||
- **PipeWire dependency**: Service must start after `pipewire.service` and `wireplumber.service`
|
||||
- **Auto-restart**: Use `Restart=always` for persistent background operation
|
||||
|
||||
### Audio Device Handling
|
||||
- **Native format**: Sendspin expects device index (0, 1, 2) or name prefix ("AmazonBasics")
|
||||
- **Discovery**: `sendspin --list-audio-devices` shows available devices
|
||||
- **PipeWire independence**: No need to use PipeWire node names; sendspin queries via sounddevice library
|
||||
|
||||
## What We're NOT Doing
|
||||
|
||||
To prevent scope creep:
|
||||
|
||||
1. **Not creating uv.lock**: Using pyproject.toml directly; lock file can be added upstream later
|
||||
2. **Not implementing multi-instance configuration**: Providing single-instance template; machines can extend for multiple instances
|
||||
3. **Not configuring specific machines**: Template only; zix790prors multi-instance setup is future work
|
||||
4. **Not creating home-manager module**: Using system-level role with user services
|
||||
5. **Not implementing server mode**: Client-only integration; `sendspin serve` can be added later
|
||||
6. **Not auto-detecting graphical user**: Relying on systemd user service behavior; explicit user selection can be added later
|
||||
7. **Not packaging dev dependencies**: Only runtime dependencies; test tools (mypy, ruff) excluded
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
Use uv2nix to package sendspin-cli from its GitHub repository, accessing pyproject.toml for dependency resolution. Create a NixOS role following the spotifyd pattern with a systemd user service template. The service runs in the logged-in user's session (automatic user detection) and can be configured per-machine for different audio devices.
|
||||
|
||||
**Key Technical Decisions:**
|
||||
1. **uv2nix over python3.withPackages**: Better maintainability, automatic dependency resolution, aligns with upstream development
|
||||
2. **System-level role with user services**: Follows existing patterns (virtual-surround.nix), enables per-user configuration
|
||||
3. **Headless mode default**: Services always use `--headless`; TUI available via manual `sendspin` command
|
||||
4. **mDNS discovery default**: No `--url` by default; let sendspin auto-discover servers on the network
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Add uv2nix Flake Inputs
|
||||
|
||||
### Overview
|
||||
Add pyproject-nix, uv2nix, and pyproject-build-systems as flake inputs and thread them through to package definitions.
|
||||
|
||||
### Changes Required
|
||||
|
||||
#### 1. Flake Inputs
|
||||
**File**: `flake.nix`
|
||||
**Changes**: Add new inputs after existing inputs (after line 44)
|
||||
|
||||
```nix
|
||||
pyproject-nix = {
|
||||
url = "github:pyproject-nix/pyproject.nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
uv2nix = {
|
||||
url = "github:pyproject-nix/uv2nix";
|
||||
inputs.pyproject-nix.follows = "pyproject-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
pyproject-build-systems = {
|
||||
url = "github:pyproject-nix/build-system-pkgs";
|
||||
inputs.pyproject-nix.follows = "pyproject-nix";
|
||||
inputs.uv2nix.follows = "uv2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
```
|
||||
|
||||
#### 2. Outputs Signature
|
||||
**File**: `flake.nix`
|
||||
**Changes**: Update outputs function signature (line 47)
|
||||
|
||||
```nix
|
||||
# Before
|
||||
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, ... } @ inputs:
|
||||
|
||||
# After
|
||||
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-wsl, pyproject-nix, uv2nix, pyproject-build-systems, ... } @ inputs:
|
||||
```
|
||||
|
||||
#### 3. Pass Inputs to Packages
|
||||
**File**: `flake.nix`
|
||||
**Changes**: Update custom package overlay (line 59 and 87)
|
||||
|
||||
```nix
|
||||
# Before
|
||||
custom = prev.callPackage ./packages {};
|
||||
|
||||
# After
|
||||
custom = prev.callPackage ./packages { inherit uv2nix pyproject-nix pyproject-build-systems; };
|
||||
```
|
||||
|
||||
#### 4. Update Packages Default.nix Signature
|
||||
**File**: `packages/default.nix`
|
||||
**Changes**: Accept new parameters
|
||||
|
||||
```nix
|
||||
# Before
|
||||
{ pkgs, ... }:
|
||||
|
||||
# After
|
||||
{ pkgs, uv2nix ? null, pyproject-nix ? null, pyproject-build-systems ? null, ... }:
|
||||
```
|
||||
|
||||
**Note**: Parameters are optional to maintain compatibility with direct `nix-build` calls.
|
||||
|
||||
### Success Criteria
|
||||
|
||||
#### Automated Verification:
|
||||
- [x] Flake evaluation succeeds: `nix flake check`
|
||||
- [x] Custom packages still build: `nix build .#nixosConfigurations.zix790prors.config.environment.systemPackages`
|
||||
- [x] No evaluation errors: `nixos-rebuild dry-build --flake .#zix790prors`
|
||||
|
||||
#### Manual Verification:
|
||||
- [x] Flake inputs show pyproject-nix, uv2nix, and pyproject-build-systems: `nix flake metadata`
|
||||
- [x] Existing machines still build without errors
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation before proceeding to Phase 2.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Create Sendspin-CLI Package
|
||||
|
||||
### Overview
|
||||
Create uv2nix-based package for sendspin-cli that handles Python dependencies and native libraries (portaudio, ffmpeg).
|
||||
|
||||
### Changes Required
|
||||
|
||||
#### 1. Package Definition
|
||||
**File**: `packages/sendspin-cli/default.nix`
|
||||
**Changes**: Create new file
|
||||
|
||||
```nix
|
||||
{ pkgs
|
||||
, uv2nix ? null
|
||||
, pyproject-nix ? null
|
||||
, pyproject-build-systems ? null
|
||||
, lib
|
||||
, fetchFromGitHub
|
||||
}:
|
||||
|
||||
# Fallback to simple package if uv2nix not available
|
||||
if uv2nix == null || pyproject-nix == null || pyproject-build-systems == null then
|
||||
let
|
||||
python = pkgs.python312.withPackages (ps: with ps; [
|
||||
# Core dependencies from pyproject.toml
|
||||
# Note: aiosendspin may need to be packaged separately if not in nixpkgs
|
||||
av
|
||||
numpy
|
||||
qrcode
|
||||
readchar
|
||||
rich
|
||||
sounddevice
|
||||
# Build dependencies
|
||||
setuptools
|
||||
]);
|
||||
in
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "sendspin-cli";
|
||||
version = "0.0.0-fallback";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Sendspin";
|
||||
repo = "sendspin-cli";
|
||||
rev = "main";
|
||||
sha256 = lib.fakeSha256; # Replace with actual hash after first build
|
||||
};
|
||||
|
||||
buildInputs = [ python pkgs.portaudio pkgs.ffmpeg ];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin $out/lib
|
||||
cp -r sendspin $out/lib/
|
||||
cat > $out/bin/sendspin <<EOF
|
||||
#!/bin/sh
|
||||
export PYTHONPATH="$out/lib:\$PYTHONPATH"
|
||||
exec ${python}/bin/python3 -m sendspin.cli "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/sendspin
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Synchronized audio player for Sendspin servers (fallback build)";
|
||||
homepage = "https://github.com/Sendspin/sendspin-cli";
|
||||
license = lib.licenses.asl20;
|
||||
};
|
||||
}
|
||||
else
|
||||
let
|
||||
# Fetch sendspin-cli source
|
||||
src = fetchFromGitHub {
|
||||
owner = "Sendspin";
|
||||
repo = "sendspin-cli";
|
||||
rev = "main"; # TODO: Pin to specific release tag
|
||||
sha256 = lib.fakeSha256; # Replace with actual hash after first build
|
||||
};
|
||||
|
||||
# Load workspace from pyproject.toml
|
||||
workspace = uv2nix.lib.workspace.loadWorkspace {
|
||||
workspaceRoot = src;
|
||||
};
|
||||
|
||||
# Create overlay from pyproject.toml dependencies
|
||||
overlay = workspace.mkPyprojectOverlay {
|
||||
sourcePreference = "wheel"; # Prefer wheels for faster builds
|
||||
};
|
||||
|
||||
# Build Python package set with native dependency overrides
|
||||
pythonSet = (pkgs.callPackage pyproject-nix.build.packages {
|
||||
python = pkgs.python312;
|
||||
}).overrideScope (lib.composeManyExtensions [
|
||||
pyproject-build-systems.overlays.default
|
||||
overlay
|
||||
# Override for packages with native dependencies
|
||||
(final: prev: {
|
||||
# sounddevice needs portaudio
|
||||
sounddevice = prev.sounddevice.overrideAttrs (old: {
|
||||
buildInputs = (old.buildInputs or []) ++ [ pkgs.portaudio ];
|
||||
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [ pkgs.portaudio ];
|
||||
});
|
||||
# av (PyAV) needs ffmpeg
|
||||
av = prev.av.overrideAttrs (old: {
|
||||
buildInputs = (old.buildInputs or []) ++ [ pkgs.ffmpeg ];
|
||||
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [ pkgs.pkg-config ];
|
||||
});
|
||||
})
|
||||
]);
|
||||
|
||||
# Create virtual environment with all dependencies
|
||||
venv = pythonSet.mkVirtualEnv "sendspin-cli-env" workspace.deps.default;
|
||||
in
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "sendspin-cli";
|
||||
version = "0.0.0";
|
||||
inherit src;
|
||||
|
||||
buildInputs = [ venv pkgs.portaudio pkgs.ffmpeg ];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
# Copy virtual environment
|
||||
cp -r ${venv} $out/venv
|
||||
# Create wrapper script
|
||||
cat > $out/bin/sendspin <<EOF
|
||||
#!/bin/sh
|
||||
export LD_LIBRARY_PATH="${pkgs.portaudio}/lib:${pkgs.ffmpeg}/lib:\$LD_LIBRARY_PATH"
|
||||
exec $out/venv/bin/sendspin "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/sendspin
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Synchronized audio player for Sendspin servers";
|
||||
homepage = "https://github.com/Sendspin/sendspin-cli";
|
||||
license = lib.licenses.asl20;
|
||||
platforms = lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Register Package
|
||||
**File**: `packages/default.nix`
|
||||
**Changes**: Add sendspin-cli to exports (after line 6)
|
||||
|
||||
```nix
|
||||
{ pkgs, uv2nix ? null, pyproject-nix ? null, pyproject-build-systems ? null, ... }:
|
||||
{
|
||||
vulkanHDRLayer = pkgs.callPackage ./vulkan-hdr-layer {};
|
||||
tea-rbw = pkgs.callPackage ./tea-rbw {};
|
||||
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
|
||||
claude-code = pkgs.callPackage ./claude-code {};
|
||||
sendspin-cli = pkgs.callPackage ./sendspin-cli { inherit uv2nix pyproject-nix pyproject-build-systems; };
|
||||
}
|
||||
```
|
||||
|
||||
### Success Criteria
|
||||
|
||||
#### Automated Verification:
|
||||
- [x] Package builds successfully: `nix build .#nixosConfigurations.zix790prors.pkgs.custom.sendspin-cli`
|
||||
- [x] Binary exists in output: `nix path-info .#nixosConfigurations.zix790prors.pkgs.custom.sendspin-cli`
|
||||
- [x] No build errors in dry-run: `nixos-rebuild dry-build --flake .#zix790prors`
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] Help text displays correctly: `$(nix-build '<nixpkgs>' -A custom.sendspin-cli)/bin/sendspin --help`
|
||||
- [ ] List audio devices works: `$(nix-build '<nixpkgs>' -A custom.sendspin-cli)/bin/sendspin --list-audio-devices`
|
||||
- [ ] Version information is correct: `$(nix-build '<nixpkgs>' -A custom.sendspin-cli)/bin/sendspin --version` (if supported)
|
||||
- [ ] Dependencies are bundled: Check that output closure contains portaudio and ffmpeg libraries
|
||||
|
||||
**Implementation Note**: The first build will fail with `lib.fakeSha256` error. Copy the actual hash from the error message and replace `lib.fakeSha256` with the real hash. After completing this phase and all automated verification passes, pause here for manual confirmation before proceeding to Phase 3.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Create Sendspin Role with Service Template
|
||||
|
||||
### Overview
|
||||
Create NixOS role that provides sendspin-cli package and configurable systemd user service template for running sendspin as a background service.
|
||||
|
||||
### Changes Required
|
||||
|
||||
#### 1. Role Definition
|
||||
**File**: `roles/sendspin/default.nix`
|
||||
**Changes**: Create new file
|
||||
|
||||
```nix
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.roles.sendspin;
|
||||
in
|
||||
{
|
||||
options.roles.sendspin = {
|
||||
enable = mkEnableOption "Enable the sendspin role";
|
||||
|
||||
audioDevice = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Audio output device by index (e.g., "0", "1") or name prefix (e.g., "AmazonBasics").
|
||||
Use `sendspin --list-audio-devices` to see available devices.
|
||||
If null, uses system default audio device.
|
||||
'';
|
||||
example = "0";
|
||||
};
|
||||
|
||||
clientName = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Friendly name for this Sendspin client.
|
||||
Defaults to hostname if not specified.
|
||||
'';
|
||||
example = "Living Room Speakers";
|
||||
};
|
||||
|
||||
clientId = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Unique identifier for this Sendspin client.
|
||||
Defaults to sendspin-cli-<hostname> if not specified.
|
||||
'';
|
||||
example = "sendspin-livingroom";
|
||||
};
|
||||
|
||||
serverUrl = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
WebSocket URL of the Sendspin server.
|
||||
If null, auto-discovers servers via mDNS.
|
||||
'';
|
||||
example = "ws://192.168.1.100:8927";
|
||||
};
|
||||
|
||||
staticDelayMs = mkOption {
|
||||
type = types.float;
|
||||
default = 0.0;
|
||||
description = ''
|
||||
Extra playback delay in milliseconds applied after clock sync.
|
||||
Useful for compensating audio latency differences between devices.
|
||||
'';
|
||||
example = 50.0;
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum [ "DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL" ];
|
||||
default = "INFO";
|
||||
description = "Logging level for sendspin service";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Ensure audio infrastructure is available
|
||||
roles.audio.enable = true;
|
||||
|
||||
# Make sendspin-cli available system-wide
|
||||
environment.systemPackages = with pkgs; [
|
||||
custom.sendspin-cli
|
||||
];
|
||||
|
||||
# Systemd user service for running sendspin in headless mode
|
||||
systemd.user.services.sendspin = {
|
||||
description = "Sendspin Audio Sync Client";
|
||||
documentation = [ "https://github.com/Sendspin/sendspin-cli" ];
|
||||
|
||||
# Start after audio services are ready
|
||||
after = [ "pipewire.service" "wireplumber.service" ];
|
||||
requires = [ "pipewire.service" "wireplumber.service" ];
|
||||
|
||||
# Auto-start with pipewire (which starts with graphical session)
|
||||
wantedBy = [ "pipewire.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
|
||||
# Build command with configured options
|
||||
ExecStart = pkgs.writeShellScript "sendspin-start" ''
|
||||
exec ${pkgs.custom.sendspin-cli}/bin/sendspin \
|
||||
--headless \
|
||||
--log-level ${cfg.logLevel} \
|
||||
${optionalString (cfg.audioDevice != null) "--audio-device '${cfg.audioDevice}'"} \
|
||||
${optionalString (cfg.clientName != null) "--name '${cfg.clientName}'"} \
|
||||
${optionalString (cfg.clientId != null) "--id '${cfg.clientId}'"} \
|
||||
${optionalString (cfg.serverUrl != null) "--url '${cfg.serverUrl}'"} \
|
||||
${optionalString (cfg.staticDelayMs != 0.0) "--static-delay-ms ${toString cfg.staticDelayMs}"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Open firewall for mDNS discovery
|
||||
networking.firewall.allowedUDPPorts = [ 5353 ];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Register Role
|
||||
**File**: `roles/default.nix`
|
||||
**Changes**: Add sendspin to imports (after line 16)
|
||||
|
||||
```nix
|
||||
imports = [
|
||||
./audio
|
||||
./bluetooth
|
||||
./btrfs
|
||||
./desktop
|
||||
./kodi
|
||||
./nfs-mounts
|
||||
./nvidia
|
||||
./printing
|
||||
./remote-build
|
||||
./sendspin
|
||||
./spotifyd
|
||||
./users
|
||||
./virtualisation
|
||||
];
|
||||
```
|
||||
|
||||
### Success Criteria
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] Configuration evaluates: `nixos-rebuild dry-build --flake .#zix790prors`
|
||||
- [ ] Service unit is generated: `nixos-rebuild dry-build --flake .#zix790prors 2>&1 | grep -i sendspin`
|
||||
- [ ] No syntax errors: `nix eval .#nixosConfigurations.zix790prors.config.roles.sendspin.enable`
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] Role can be enabled in machine config without errors
|
||||
- [ ] Service dependencies are correct (after pipewire/wireplumber)
|
||||
- [ ] Firewall rule for mDNS is present
|
||||
- [ ] Sendspin-cli is in system packages when role is enabled
|
||||
- [ ] All configuration options (audioDevice, clientName, etc.) are exposed
|
||||
- [ ] Service starts successfully after enabling role and rebuilding
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation. Test the service by adding `roles.sendspin.enable = true;` to a machine configuration, rebuilding, and verifying the service runs as the logged-in user.
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Per-Phase)
|
||||
|
||||
**Phase 1 (Flake Inputs):**
|
||||
```bash
|
||||
# Verify flake is valid
|
||||
nix flake check
|
||||
|
||||
# Verify inputs are available
|
||||
nix flake metadata | grep -E "(pyproject-nix|uv2nix|pyproject-build-systems)"
|
||||
|
||||
# Verify existing builds still work
|
||||
nix build .#nixosConfigurations.zix790prors.config.system.build.toplevel
|
||||
```
|
||||
|
||||
**Phase 2 (Package):**
|
||||
```bash
|
||||
# Build package
|
||||
nix build .#nixosConfigurations.zix790prors.pkgs.custom.sendspin-cli
|
||||
|
||||
# Test executable
|
||||
result/bin/sendspin --help
|
||||
result/bin/sendspin --list-audio-devices
|
||||
|
||||
# Verify dependencies
|
||||
nix-store --query --requisites result | grep -E "(portaudio|ffmpeg)"
|
||||
```
|
||||
|
||||
**Phase 3 (Role):**
|
||||
```bash
|
||||
# Evaluate with role enabled
|
||||
nix eval .#nixosConfigurations.zix790prors.config.roles.sendspin.enable
|
||||
|
||||
# Check service definition
|
||||
nixos-rebuild dry-build --flake .#zix790prors
|
||||
systemctl --user cat sendspin.service # After rebuild
|
||||
|
||||
# Verify audio device option works
|
||||
nix eval '.#nixosConfigurations.zix790prors.config.roles.sendspin.audioDevice'
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**Basic Service Test:**
|
||||
1. Enable role on a test machine: `roles.sendspin.enable = true;`
|
||||
2. Rebuild system: `make switch`
|
||||
3. Check service status: `systemctl --user status sendspin.service`
|
||||
4. Verify logs show connection attempts: `journalctl --user -u sendspin -f`
|
||||
5. Run server locally: `sendspin serve --demo`
|
||||
6. Verify client connects and plays audio
|
||||
|
||||
**Multi-Device Test (Future - zix790prors):**
|
||||
1. Create multiple service instances with different audio devices
|
||||
2. Verify each instance targets correct device
|
||||
3. Test audio sync between devices
|
||||
|
||||
**User Context Test:**
|
||||
1. Test on `boxy` running as `kodi` user
|
||||
2. Test on `zix790prors` running as `johno` user
|
||||
3. Verify service runs in correct user session
|
||||
|
||||
### Manual Testing Steps
|
||||
|
||||
1. **Package Installation:**
|
||||
```bash
|
||||
# As root
|
||||
nix-shell -p 'pkgs.custom.sendspin-cli'
|
||||
sendspin --list-audio-devices
|
||||
```
|
||||
|
||||
2. **Service Functionality:**
|
||||
```bash
|
||||
# After enabling role and rebuilding
|
||||
systemctl --user status sendspin
|
||||
journalctl --user -u sendspin -n 50
|
||||
```
|
||||
|
||||
3. **Audio Device Selection:**
|
||||
```bash
|
||||
# Configure specific device
|
||||
roles.sendspin.audioDevice = "0";
|
||||
# Rebuild and verify service uses correct device
|
||||
```
|
||||
|
||||
4. **Server Discovery:**
|
||||
```bash
|
||||
# Without serverUrl, verify mDNS discovery
|
||||
sendspin --list-servers
|
||||
```
|
||||
|
||||
5. **Delay Calibration:**
|
||||
```bash
|
||||
# Test delay configuration
|
||||
roles.sendspin.staticDelayMs = 50.0;
|
||||
# Verify in service logs
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
**Build Time:**
|
||||
- uv2nix initial build may take 5-10 minutes (Python dependency resolution)
|
||||
- Subsequent builds use Nix cache
|
||||
- Consider using binary cache if building on multiple machines
|
||||
|
||||
**Runtime:**
|
||||
- Sendspin client is lightweight (~20-50 MB memory)
|
||||
- CPU usage minimal when not playing audio
|
||||
- Network: Uses mDNS (UDP 5353) and WebSocket connection to server
|
||||
|
||||
**Storage:**
|
||||
- Package closure size: ~200-300 MB (Python + dependencies + libraries)
|
||||
- No persistent cache by sendspin-cli itself
|
||||
|
||||
## Migration Notes
|
||||
|
||||
**For Existing Systems:**
|
||||
1. Add role to machine configuration
|
||||
2. Configure audio device if not using default
|
||||
3. Rebuild and enable service
|
||||
4. No data migration needed (stateless service)
|
||||
|
||||
**For Multi-Instance Setups (Future):**
|
||||
When implementing multiple instances for zix790prors:
|
||||
1. Disable default service: `systemd.user.services.sendspin.wantedBy = lib.mkForce [];`
|
||||
2. Create per-device service instances manually
|
||||
3. Each instance needs unique `--id` and `--audio-device`
|
||||
|
||||
## References
|
||||
|
||||
- Original research: `thoughts/shared/research/2025-12-29-sendspin-cli-integration.md`
|
||||
- Sendspin-CLI source: `~/src/sendspin-cli/` (GitHub: https://github.com/Sendspin/sendspin-cli)
|
||||
- Sendspin pyproject.toml: `~/src/sendspin-cli/pyproject.toml`
|
||||
- Sendspin CLI implementation: `~/src/sendspin-cli/sendspin/cli.py:143-222`
|
||||
- Custom packages pattern: `packages/default.nix:1-7`
|
||||
- Python package pattern: `packages/app-launcher-server/default.nix:1-10`
|
||||
- Audio role pattern: `roles/audio/default.nix:1-41`
|
||||
- Service role pattern: `roles/spotifyd/default.nix:1-40`
|
||||
- User service pattern: `machines/zix790prors/virtual-surround.nix:79-132`
|
||||
- Flake overlay: `flake.nix:54-59`
|
||||
- uv2nix documentation: https://pyproject-nix.github.io/uv2nix/
|
||||
- uv2nix getting started: https://pyproject-nix.github.io/uv2nix/usage/getting-started.html
|
||||
Reference in New Issue
Block a user