Compare commits

...

3 Commits

Author SHA1 Message Date
f8ee011d27 Attempt to fix custom package format 2025-10-13 14:25:23 -07:00
4a4ea6316d [app-launcher] Add app-launcher to boxy 2025-10-13 14:25:13 -07:00
b75c43257b [zix790prors] Increase boot partition size 2025-10-04 09:06:18 -07:00
7 changed files with 145 additions and 288 deletions

View File

@@ -1,284 +0,0 @@
# NixOS /boot Partition Expansion Plan
## CRITICAL CONTEXT & PROBLEM STATEMENT
**System:** `zix790prors` - Dual-boot Windows 11 + NixOS on Samsung SSD 990 PRO 4TB
**Problem:** Current /boot partition (/dev/nvme0n1p1) is only 100MB, insufficient for multiple NixOS generations
**Goal:** Create 1GB /boot partition to support 20+ NixOS generations (~38MB each)
**Current Partition Layout:**
```
Device Start End Sectors Size Type
/dev/nvme0n1p1 2048 206847 204800 100M EFI System (/boot)
/dev/nvme0n1p2 206848 239615 32768 16M Microsoft reserved
/dev/nvme0n1p3 239616 1953316863 1953077248 931.3G Microsoft basic data (Windows)
/dev/nvme0n1p6 1953316864 3906394111 1953077248 931.3G Linux filesystem (/nix/store)
/dev/nvme0n1p5 3906394112 7812548607 3906154496 1.8T Linux filesystem (/games - btrfs)
/dev/nvme0n1p4 7812548608 7814031359 1482752 724M Windows recovery environment
```
**Strategy:** Shrink /games partition (p5) by 1GB and create new 1GB EFI partition
---
## PLAN A: DETAILED STEP-BY-STEP INSTRUCTIONS
### PREREQUISITES
1. **Boot from NixOS live USB** (build with `./build-liveusb.sh`)
2. **Have Windows recovery media ready** (just in case)
3. **Backup important data** (though this shouldn't affect user data)
### PHASE 1: PREPARATION & BACKUP
```bash
# 1. Connect to internet
sudo systemctl start wpa_supplicant
# OR for ethernet: sudo systemctl start dhcpcd
# 2. Mount current filesystems
sudo mkdir -p /mnt/{boot,nix,games}
sudo mount /dev/nvme0n1p1 /mnt/boot
sudo mount /dev/nvme0n1p6 /mnt/nix
sudo mount /dev/nvme0n1p5 /mnt/games
# 3. Backup current /boot contents
sudo mkdir -p /tmp/boot-backup
sudo cp -a /mnt/boot/* /tmp/boot-backup/
sudo ls -la /tmp/boot-backup/ # Verify backup
# 4. Record current UUIDs for later
sudo blkid /dev/nvme0n1p1 > /tmp/original-boot-uuid.txt
cat /tmp/original-boot-uuid.txt
```
### PHASE 2: SHRINK /games PARTITION
```bash
# 1. Check /games filesystem
sudo btrfs filesystem show /mnt/games
sudo btrfs filesystem usage /mnt/games
# 2. Shrink btrfs filesystem first (by 1.1GB for safety margin)
sudo btrfs filesystem resize -1200M /mnt/games
# 3. Verify shrink was successful
sudo btrfs filesystem usage /mnt/games
# 4. Unmount to resize partition
sudo umount /mnt/games
# 5. Note current partition end sector
sudo fdisk -l /dev/nvme0n1 | grep nvme0n1p5
# Current: 3906394112 7812548607 (note the END: 7812548607)
# 6. Calculate new end sector (subtract ~2M sectors for 1GB)
# New end should be approximately: 7812548607 - 2097152 = 7810451455
```
### PHASE 3: RESIZE PARTITIONS
```bash
# 1. Use parted to resize /games partition
sudo parted /dev/nvme0n1
# In parted shell:
print # Show current layout
resizepart 5 7810451455 # Resize partition 5 to new end
print # Verify the change
quit
# 2. Verify partition table
sudo fdisk -l /dev/nvme0n1
```
### PHASE 4: CREATE NEW 1GB EFI PARTITION
```bash
# 1. Create new partition in freed space
sudo parted /dev/nvme0n1
# In parted shell:
print free # Show free space
mkpart primary fat32 7810451456 7812548607 # Create new partition
set 7 esp on # Set ESP flag (assuming it becomes p7)
print # Verify
quit
# 2. Format new partition as FAT32
sudo mkfs.fat -F32 -n "NIXBOOT" /dev/nvme0n1p7
# 3. Get new partition UUID
sudo blkid /dev/nvme0n1p7
```
### PHASE 5: COPY BOOT CONTENTS
```bash
# 1. Mount new boot partition
sudo mkdir -p /mnt/new-boot
sudo mount /dev/nvme0n1p7 /mnt/new-boot
# 2. Copy all boot contents
sudo cp -a /tmp/boot-backup/* /mnt/new-boot/
# 3. Verify copy
sudo ls -la /mnt/new-boot/
sudo du -sh /mnt/new-boot/*
```
### PHASE 6: UPDATE CONFIGURATION
```bash
# 1. Mount root filesystem
sudo mount /dev/nvme0n1p6 /mnt/nix
sudo mount /dev/nvme0n1p6 /mnt # For nixos-enter
# 2. Mount other needed filesystems for chroot
sudo mount --bind /dev /mnt/dev
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys /mnt/sys
sudo mount /dev/nvme0n1p7 /mnt/boot # Mount NEW boot partition
# 3. Enter NixOS environment
sudo nixos-enter
# 4. Inside chroot - update hardware configuration
# Edit /etc/nixos/hardware-configuration.nix or check current fstab
NEW_UUID=$(blkid /dev/nvme0n1p7 | grep -o 'UUID="[^"]*"' | cut -d'"' -f2)
echo "New boot UUID: $NEW_UUID"
# 5. Update boot partition UUID in configuration
# This step depends on your current setup - likely in hardware-configuration.nix
```
### PHASE 7: REGENERATE BOOTLOADER
```bash
# Still in nixos-enter chroot:
# 1. Rebuild bootloader configuration
nixos-rebuild switch --install-bootloader
# 2. Exit chroot
exit
# 3. Update EFI boot variables (if needed)
sudo efibootmgr -v # Check current entries
```
### PHASE 8: VERIFICATION & CLEANUP
```bash
# 1. Unmount everything
sudo umount /mnt/new-boot
sudo umount /mnt/boot
sudo umount /mnt/nix
sudo umount /mnt/games
# 2. Remount /games with new partition size
sudo mount /dev/nvme0n1p5 /mnt/games
sudo btrfs filesystem resize max /mnt/games # Expand btrfs to use available space
# 3. Final verification
sudo fdisk -l /dev/nvme0n1
sudo lsblk
```
### PHASE 9: TEST BOOT
```bash
# 1. Reboot and test
sudo reboot
# 2. After successful boot, update NixOS config
# Change configurationLimit to 20 (perfect for 1GB /boot)
# In /home/johno/nixos-configs/machines/zix790prors/configuration.nix:
# boot.loader.systemd-boot.configurationLimit = 20;
# 3. Test rebuild
sudo nixos-rebuild switch --flake .#zix790prors
```
---
## PLAN B: TROUBLESHOOTING & CONTEXT FOR ADDITIONAL HELP
### IF THINGS GO WRONG
**Scenario 1: System won't boot**
- Boot from live USB
- Mount old boot partition: `sudo mount /dev/nvme0n1p1 /mnt/boot`
- Check if bootloader entries exist: `ls /mnt/boot/loader/entries/`
- Regenerate from live USB using `nixos-install` with `--root /mnt`
**Scenario 2: Partition operations fail**
- **STOP IMMEDIATELY**
- Document exact error message
- Use `sudo fdisk -l` and `sudo parted /dev/nvme0n1 print` to check current state
- Do NOT proceed without understanding the error
**Scenario 3: Btrfs resize fails**
- Check available space: `sudo btrfs filesystem usage /mnt/games`
- May need to balance first: `sudo btrfs balance start /mnt/games`
- Or defragment: `sudo btrfs filesystem defragment -r /mnt/games`
### RECOVERY INFORMATION
**Original partition layout (before changes):**
```
/dev/nvme0n1p1: 100M EFI (/boot) - sectors 2048-206847
/dev/nvme0n1p5: 1.8T btrfs (/games) - sectors 3906394112-7812548607
```
**Key files to check:**
- `/etc/fstab` - filesystem mount configuration
- `/etc/nixos/hardware-configuration.nix` - NixOS hardware config
- `/boot/loader/loader.conf` - systemd-boot config
**Important UUIDs (save these before starting):**
```bash
# Run these BEFORE making changes:
sudo blkid /dev/nvme0n1p1 # Current boot partition
sudo blkid /dev/nvme0n1p5 # Games partition
sudo blkid /dev/nvme0n1p6 # Nix store partition
```
**Emergency boot options:**
- Boot from Windows (should be unaffected)
- Boot from NixOS live USB
- Use systemd-boot menu to select older generation (if available)
### ALTERNATIVE APPROACHES IF PLAN A FAILS
1. **Expand into Microsoft reserved instead** (lower risk, less space)
2. **Use external USB for /boot** (temporary solution)
3. **Recreate partition table** (nuclear option, requires full backup)
---
## TECHNICAL DETAILS FOR CLAUDE CODE ASSISTANCE
**Repository:** `/home/johno/nixos-configs` - NixOS flake-based configuration
**Machine config:** `machines/zix790prors/configuration.nix`
**Current configurationLimit:** 1 (temporarily reduced from 2)
**Target configurationLimit:** 20 (requires ~1GB /boot)
**Key commands for status checking:**
```bash
# Partition info
sudo fdisk -l /dev/nvme0n1
sudo parted /dev/nvme0n1 print free
sudo lsblk
# Filesystem info
sudo btrfs filesystem show
sudo btrfs filesystem usage /games
df -h /boot
# Boot info
sudo efibootmgr -v
ls -la /boot/loader/entries/
```
**This document created:** 2025-10-02 by Claude Code
**Hardware:** Samsung SSD 990 PRO 4TB, NixOS + Windows 11 dual boot

View File

@@ -34,7 +34,7 @@ with lib;
# Use the systemd-boot EFI boot loader. # Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
boot.loader.systemd-boot.configurationLimit = 1; # Temporarily reduced to 1 to free /boot space boot.loader.systemd-boot.configurationLimit = 20;
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
boot.loader.timeout = 10; boot.loader.timeout = 10;

View File

@@ -14,7 +14,7 @@
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
fileSystems."/boot" = fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/76B0-738E"; { device = "/dev/disk/by-uuid/11C1-EB58";
fsType = "vfat"; fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ]; options = [ "fmask=0077" "dmask=0077" ];
}; };

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
import json
import logging
import os
import subprocess
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Allowlisted applications that can be launched
ALLOWED_APPS = {
'firefox': 'firefox',
'kodi': 'kodi'
}
class AppLauncherHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
logger.info(format % args)
def do_GET(self):
if self.path == '/':
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {
'status': 'running',
'available_apps': list(ALLOWED_APPS.keys()),
'usage': 'POST /launch/<app_name> to launch an application'
}
self.wfile.write(json.dumps(response, indent=2).encode())
else:
self.send_error(404)
def do_POST(self):
parsed_path = urlparse(self.path)
path_parts = parsed_path.path.strip('/').split('/')
if len(path_parts) == 2 and path_parts[0] == 'launch':
app_name = path_parts[1]
self.launch_app(app_name)
else:
self.send_error(404, "Invalid endpoint. Use /launch/<app_name>")
def launch_app(self, app_name):
if app_name not in ALLOWED_APPS:
self.send_error(400, f"Application '{app_name}' not allowed. Available apps: {list(ALLOWED_APPS.keys())}")
return
command = ALLOWED_APPS[app_name]
try:
# Launch the application in the background
# Ensure we have the proper environment for GUI apps
env = os.environ.copy()
logger.info(f"Launching application: {command}")
process = subprocess.Popen(
[command],
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True
)
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {
'status': 'success',
'message': f'Successfully launched {app_name}',
'pid': process.pid
}
self.wfile.write(json.dumps(response).encode())
except FileNotFoundError:
logger.error(f"Application not found: {command}")
self.send_error(500, f"Application '{app_name}' not found on system")
except Exception as e:
logger.error(f"Error launching {command}: {e}")
self.send_error(500, f"Failed to launch {app_name}: {str(e)}")
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8081
server = HTTPServer(('0.0.0.0', port), AppLauncherHandler)
logger.info(f"App launcher server starting on port {port}")
logger.info(f"Available applications: {list(ALLOWED_APPS.keys())}")
try:
server.serve_forever()
except KeyboardInterrupt:
logger.info("Server shutting down...")
server.server_close()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,5 @@
{ pkgs }:
pkgs.writeShellScriptBin "app-launcher-server" ''
exec ${pkgs.python3}/bin/python3 ${./app-launcher-server.py} "$@"
''

View File

@@ -2,4 +2,5 @@
{ {
vulkanHDRLayer = pkgs.callPackage ./vulkan-hdr-layer {}; vulkanHDRLayer = pkgs.callPackage ./vulkan-hdr-layer {};
tea-rbw = pkgs.callPackage ./tea-rbw {}; tea-rbw = pkgs.callPackage ./tea-rbw {};
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
} }

View File

@@ -4,6 +4,7 @@ with lib;
let let
cfg = config.roles.kodi; cfg = config.roles.kodi;
customPkgs = pkgs.callPackage ../../packages {};
in in
{ {
options.roles.kodi = { options.roles.kodi = {
@@ -14,6 +15,18 @@ in
wayland = mkOption { wayland = mkOption {
default = true; default = true;
}; };
appLauncherServer = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable HTTP app launcher server for remote control";
};
port = mkOption {
type = types.int;
default = 8081;
description = "Port for the app launcher HTTP server";
};
};
}; };
@@ -33,17 +46,35 @@ in
}; };
networking.firewall = { networking.firewall = {
allowedTCPPorts = [ 8080 ]; allowedTCPPorts = [ 8080 ] ++ optional cfg.appLauncherServer.enable cfg.appLauncherServer.port;
allowedUDPPorts = [ 8080 ]; allowedUDPPorts = [ 8080 ];
}; };
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
kodiPkg kodiPkg
wget wget
]; firefox
] ++ optional cfg.appLauncherServer.enable customPkgs.app-launcher-server;
programs.kdeconnect.enable = true; programs.kdeconnect.enable = true;
systemd.user.services = mkIf cfg.appLauncherServer.enable {
app-launcher-server = {
description = "HTTP App Launcher Server";
wantedBy = [ "graphical-session.target" ];
after = [ "graphical-session.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${customPkgs.app-launcher-server}/bin/app-launcher-server ${toString cfg.appLauncherServer.port}";
Restart = "always";
RestartSec = "5s";
Environment = [
"PATH=${pkgs.firefox}/bin:${kodiPkg}/bin:/run/current-system/sw/bin"
];
};
};
};
services = if cfg.autologin then { services = if cfg.autologin then {
displayManager = { displayManager = {
autoLogin.enable = true; autoLogin.enable = true;