Initial: Nix flake reproducing MacBook capabilities on x86_64 NixOS

- pi coding agent (from sscoble/pi-mono Forgejo mirror)
- qmd vault search (via bunx @tobilu/qmd)
- zsh + oh-my-zsh + developer tooling
- home-manager config for sscoble user
- Forgejo Action to check pi version drift daily
This commit is contained in:
Scott Scoble 2026-05-12 20:13:50 -07:00
commit 8155b9b64c
5 changed files with 461 additions and 0 deletions

View file

@ -0,0 +1,51 @@
name: Check pi version drift
on:
schedule:
- cron: "0 6 * * *" # Daily at 6am
workflow_dispatch:
jobs:
check-pi:
runs-on: nixos
steps:
- name: Checkout laptop-config
uses: actions/checkout@v4
- name: Get latest pi-mono tag
id: tag
run: |
# Forgejo mirror of badlogic/pi-mono
LATEST=$(git ls-remote --tags https://os.scoble.me/forgejo/sscoble/pi-mono.git \
| grep -v '\^{}' \
| awk -F/ '{print $NF}' \
| grep -E '^v?[0-9]' \
| sort -V | tail -1)
echo "latest=$LATEST" >> $GITHUB_OUTPUT
echo "Latest pi-mono tag: $LATEST"
- name: Get current version in flake
id: current
run: |
CURRENT=$(grep -E 'version = "' flake.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/')
echo "current=$CURRENT" >> $GITHUB_OUTPUT
echo "Current flake version: $CURRENT"
- name: Open issue on drift
if: steps.tag.outputs.latest != '' && steps.tag.outputs.latest != steps.current.outputs.current
run: |
LATEST="${{ steps.tag.outputs.latest }}"
CURRENT="${{ steps.current.outputs.current }}"
echo "DRIFT: pi-mono $LATEST vs flake $CURRENT"
# Create issue via API if token is available
if [ -n "$FORGEJO_TOKEN" ]; then
curl -s -X POST "https://os.scoble.me/forgejo/api/v1/repos/sscoble/laptop-config/issues" \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"pi: upstream $LATEST available (flake has $CURRENT)\",
\"body\": \"The pi-mono mirror has tag \`$LATEST\`. The flake currently targets \`$CURRENT\`.\\n\\nAction: update \`flake.nix\` version string and rebuild.\",
\"labels\": [\"improvement\"]
}"
fi

69
README.md Normal file
View file

@ -0,0 +1,69 @@
# Scobleclaw Laptop Config
Nix flake that reproduces MacBook software capabilities on an x86\_64 NixOS laptop.
## What it installs
| Tool | Purpose | Source |
|------|---------|--------|
| **pi** | Coding agent (TUI) | `sscoble/pi-mono` (Forgejo mirror of `badlogic/pi-mono`) |
| **qmd** | Vault search | `@tobilu/qmd` (npm, wrapped via bunx) |
| **git** | Version control | nixpkgs |
| **nodejs** | JavaScript runtime | nixpkgs |
| **bun** | Fast JS runtime + bundler | nixpkgs |
| **zsh** | Shell (+ oh-my-zsh, autosuggestions, syntax highlighting) | nixpkgs |
| **python3 + uv** | Python tooling | nixpkgs |
| **docker** | Containers | nixpkgs |
| **tailscale** | Mesh VPN | nixpkgs |
| **ripgrep, fd, jq, bat, eza, delta** | Modern CLI tools | nixpkgs |
| **tmux** | Terminal multiplexer | nixpkgs |
| **nerd fonts** | JetBrains Mono, Fira Code | nixpkgs |
## First-time setup (new laptop)
```bash
# 1. Install NixOS (minimal ISO)
# 2. Clone this repo
git clone https://os.scoble.me/forgejo/sscoble/laptop-config.git ~/laptop-config
# 3. First build (this will fail — copy the npmDepsHash from error output)
nix build ~/laptop-config#pi-coding-agent
# 4. Edit flake.nix: set npmDepsHash to the value from step 3
# 5. Rebuild system
sudo nixos-rebuild switch --flake ~/laptop-config#laptop
# 6. Restore personal configs
# Copy from backup:
# ~/.pi/agent/SYSTEM.md
# ~/.pi/agent/models.json
# ~/.pi/agent/mcp.json
# ~/.pi/agent/settings.json
# ~/.ssh/id_ed25519
```
## Updating pi
The pi-mono repo is mirrored from upstream and syncs every 24h.
```bash
# Force a mirror sync (on n97-mini)
ssh n97-mini 'sudo -u forgejo forgejo admin repo sync --config /var/lib/forgejo/custom/conf/app.ini sscoble/pi-mono'
```
## Repo structure
```
laptop-config/
├── flake.nix # Main entry: overlay, nixosConfig, devShell
├── modules/
│ └── laptop.nix # NixOS system module (packages, shell, fonts, SSH)
├── home/
│ └── sscoble.nix # home-manager user config (git, tools, pi dirs)
└── README.md
```
## Forgejo Actions
An action keeps pi up to date — when upstream `badlogic/pi-mono` releases a new tag, the Forgejo mirror picks it up automatically. A scheduled workflow in this repo checks for tag drift daily.

106
flake.nix Normal file
View file

@ -0,0 +1,106 @@
{
description = "Scobleclaw laptop — NixOS configuration reproducing MacBook software capabilities on x86_64";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
pi-mono = {
url = "git+https://os.scoble.me/forgejo/sscoble/pi-mono.git";
flake = false;
};
};
outputs = { self, nixpkgs, home-manager, pi-mono, ... }@inputs: let
# System we're targeting (x86_64 NixOS laptop)
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
overlays = [ self.overlays.default ];
};
in {
# Overlay — add pi and qmd to nixpkgs
overlays.default = final: prev: {
# pi coding agent — built from Forgejo mirror of pi-mono
pi-coding-agent = final.buildNpmPackage {
pname = "pi-coding-agent";
version = "0.73.0";
src = pi-mono;
npmDepsHash = ""; # Set after first build: nix-build outputs the correct hash
npmWorkspace = "packages/coding-agent";
npmFlags = [ "--legacy-peer-deps" ];
dontNpmBuild = true;
preConfigure = ''
# Build dependencies first (monorepo workspace order)
cd packages/ai && npm run build && cd ../..
cd packages/agent && npm run build && cd ../..
cd packages/tui && npm run build && cd ../..
cd packages/coding-agent && npm run build && cd ../..
'';
installPhase = ''
runHook preInstall
mkdir -p $out/lib/node_modules/@mariozechner/pi-coding-agent
cp -r packages/coding-agent/dist $out/lib/node_modules/@mariozechner/pi-coding-agent/
cp packages/coding-agent/package.json $out/lib/node_modules/@mariozechner/pi-coding-agent/
mkdir -p $out/bin
cat > $out/bin/pi <<'EOF'
#!/usr/bin/env bash
exec ${final.nodejs}/bin/node $out/lib/node_modules/@mariozechner/pi-coding-agent/dist/cli.js "$@"
EOF
chmod +x $out/bin/pi
runHook postInstall
'';
};
# qmd — vault search tool, wraps npm package via bun
qmd = final.writeShellScriptBin "qmd" ''
exec ${final.bun}/bin/bunx --bun @tobilu/qmd "$@"
'';
# Pi config directory structure (without secrets)
pi-config-dir = final.runCommand "pi-config-dir" {} ''
mkdir -p $out
# Placeholder — real config should be restored from backup
echo "# Pi agent configuration" > $out/README.md
echo "# Defaults" > $out/settings.json
'';
};
# NixOS configuration for the laptop
nixosConfigurations.laptop = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
home-manager.nixosModules.home-manager
./modules/laptop.nix
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.sscoble = import ./home/sscoble.nix;
}
];
};
# Standalone dev shell for trying things out
devShells.${system}.default = pkgs.mkShell {
buildInputs = with pkgs; [
pi-coding-agent
qmd
nodejs
bun
git
zsh
];
shellHook = ''
echo "🦞 Scobleclaw laptop dev shell"
echo " pi: $(which pi)"
echo " qmd: $(which qmd)"
echo " node: $(node --version)"
echo " bun: $(bun --version)"
echo " git: $(git --version)"
'';
};
};
}

122
home/sscoble.nix Normal file
View file

@ -0,0 +1,122 @@
{ config, pkgs, ... }:
{
home.username = "sscoble";
home.homeDirectory = "/home/sscoble";
home.stateVersion = "25.11";
# Don't let home-manager manage these
manual.json.enable = false;
manual.manpages.enable = false;
# === Home Packages ===
home.packages = with pkgs; [
# Python for scripting
python3
uv
# Go
go
# Container tools
docker
dive
# Network tools
nmap
tcpdump
speedtest-cli
tailscale
# Extra editors / viewers
bat # better cat
eza # better ls
delta # better git diff
tldr # simplified man pages
# File sync
rsync
rclone
];
# === Git Config ===
programs.git = {
enable = true;
userName = "Scott Scoble";
userEmail = "sscoble@scobleclaw.dev";
extraConfig = {
init.defaultBranch = "main";
pull.rebase = true;
push.autoSetupRemote = true;
core.editor = "vim";
};
aliases = {
lol = "log --graph --oneline --decorate --all";
co = "checkout";
br = "branch";
st = "status";
ci = "commit";
};
};
# === Pi Config Directory ===
# Creates ~/.pi/agent/ with template files — user should restore real configs
home.file.".pi/agent/README.md" = {
text = ''
# Pi Agent Configuration
Restore these files from backup:
- SYSTEM.md (core agent directive)
- models.json (provider + model configuration)
- mcp.json (MCP server connections)
- settings.json (pi preferences)
- auth.json (API keys — keep secure!)
'';
};
# === Vault directory placeholder ===
home.file."vault/.gitkeep" = { text = ""; };
# === Shell Direnv ===
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
# === SSH config (symlink to system config) ===
home.file.".ssh/config".text = ''
Host n97-mini
HostName 192.168.168.41
User sscoble
ForwardAgent yes
Host n97-mini-ext
HostName os.scoble.me
Port 22
User sscoble
ForwardAgent yes
Host forgejo
HostName os.scoble.me
User git
'';
# === Zsh extra config ===
programs.zsh = {
enable = true;
initExtra = ''
# Pi agent alias
alias pi="pi"
# QMD vault search
alias vq="qmd query"
# Nix shortcuts
alias nr="sudo nixos-rebuild switch --flake ~/laptop-config#laptop"
alias ngc="nix-collect-garbage -d"
'';
envExtra = ''
export EDITOR=vim
export VISUAL=vim
'';
};
}

113
modules/laptop.nix Normal file
View file

@ -0,0 +1,113 @@
{ config, pkgs, ... }:
{
# === System Packages ===
environment.systemPackages = with pkgs; [
# Core development tools
pi-coding-agent
qmd
nodejs
bun
git
zsh
curl
wget
# System utilities
htop
neofetch
ripgrep
fd
jq
tree
tmux
unzip
# SSH essentials
openssh
mosh
];
# === Shell ===
programs.zsh = {
enable = true;
autosuggestions.enable = true;
syntaxHighlighting.enable = true;
shellAliases = {
ll = "ls -la";
la = "ls -A";
l = "ls -CF";
nix-rebuild = "sudo nixos-rebuild switch --flake ~/laptop-config#laptop";
nix-clean = "sudo nix-collect-garbage -d && nix-collect-garbage -d";
vault-search = "qmd query";
pi-run = "pi";
};
ohMyZsh = {
enable = true;
plugins = [ "git" "node" "npm" "ssh-agent" "sudo" "history" ];
theme = "robbyrussell";
};
};
# Default to zsh
users.defaultUserShell = pkgs.zsh;
# === SSH ===
programs.ssh = {
startAgent = true;
extraConfig = ''
Host n97-mini
HostName 192.168.168.41
User sscoble
ForwardAgent yes
IdentityFile ~/.ssh/id_ed25519
Host n97-mini-ext
HostName os.scoble.me
Port 22
User sscoble
ForwardAgent yes
IdentityFile ~/.ssh/id_ed25519
Host forgejo
HostName os.scoble.me
User git
IdentityFile ~/.ssh/id_ed25519
'';
};
# === Fonts (for pi TUI) ===
fonts.packages = with pkgs; [
nerd-fonts.jetbrains-mono
nerd-fonts.fira-code
];
# === Nix Settings ===
nix = {
settings = {
experimental-features = [ "nix-command" "flakes" ];
auto-optimise-store = true;
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
# === User ===
users.users.sscoble = {
isNormalUser = true;
description = "Scott Scoble";
extraGroups = [ "networkmanager" "wheel" "docker" ];
shell = pkgs.zsh;
};
# === Locale ===
time.timeZone = "America/Los_Angeles";
i18n.defaultLocale = "en_US.UTF-8";
# === Boot ===
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
}