From 8155b9b64c3e1c5cd36f994ea02cc65ad3b261e1 Mon Sep 17 00:00:00 2001 From: Scott Scoble Date: Tue, 12 May 2026 20:13:50 -0700 Subject: [PATCH] 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 --- .forgejo/workflows/check-pi-version.yml | 51 ++++++++++ README.md | 69 ++++++++++++++ flake.nix | 106 ++++++++++++++++++++ home/sscoble.nix | 122 ++++++++++++++++++++++++ modules/laptop.nix | 113 ++++++++++++++++++++++ 5 files changed, 461 insertions(+) create mode 100644 .forgejo/workflows/check-pi-version.yml create mode 100644 README.md create mode 100644 flake.nix create mode 100644 home/sscoble.nix create mode 100644 modules/laptop.nix diff --git a/.forgejo/workflows/check-pi-version.yml b/.forgejo/workflows/check-pi-version.yml new file mode 100644 index 0000000..e0d9236 --- /dev/null +++ b/.forgejo/workflows/check-pi-version.yml @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..327cf9e --- /dev/null +++ b/README.md @@ -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. diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..3517e55 --- /dev/null +++ b/flake.nix @@ -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)" + ''; + }; + }; +} diff --git a/home/sscoble.nix b/home/sscoble.nix new file mode 100644 index 0000000..b6d5a91 --- /dev/null +++ b/home/sscoble.nix @@ -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 + ''; + }; +} diff --git a/modules/laptop.nix b/modules/laptop.nix new file mode 100644 index 0000000..e99c2ef --- /dev/null +++ b/modules/laptop.nix @@ -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; +}