Merge pull request #9 from harivansh-afk/phase-2

phase 2
This commit is contained in:
Hari 2026-03-30 23:02:11 -04:00 committed by GitHub
commit d97aee6808
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 844 additions and 553 deletions

View file

@ -49,14 +49,20 @@ just fmt
## layout
```
hosts/darwin/ - macOS host entrypoint
hosts/netty/ - NixOS VPS entrypoint (disko + hardware)
modules/ - shared system modules + devshells
modules/hosts/ - flake-parts host output definitions
modules/nixpkgs.nix - shared flake context (hosts, args, pkgs helpers)
home/ - Home Manager modules
lib/hosts.nix - host metadata used by the flake
lib/ - shared package sets and theme system
config/ - repo-owned config files (nvim, tmux, etc.)
scripts/ - secret management and utility scripts
hosts/darwin/ - macOS host entrypoint
hosts/netty/ - NixOS VPS entrypoint (disko + hardware + services)
modules/ - shared system modules + devshells
modules/hosts/ - flake-parts host output definitions
modules/nixpkgs.nix - shared flake context (hosts, specialArgs, pkgs)
home/default.nix - unified home entry (conditional on hostConfig)
home/common.nix - modules shared across all hosts
home/xdg.nix - XDG compliance (env vars, config files)
home/security.nix - SSH/GPG permission enforcement
home/ - per-tool home-manager modules
lib/hosts.nix - host metadata + feature flags
lib/theme.nix - centralized color system (gruvbox)
lib/package-sets.nix - shared + host-gated package lists
config/ - repo-owned config files (nvim, tmux, etc.)
scripts/ - secret management and utility scripts
nix-maxxing.txt - architecture and operations guide
```

View file

@ -1,30 +0,0 @@
{
description = "CI stub for the local agentcomputer-cli flake input";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs =
{ nixpkgs, ... }:
let
systems = [
"aarch64-darwin"
"x86_64-darwin"
"aarch64-linux"
"x86_64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs systems;
in
{
packages = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
in
{
default = pkgs.writeShellScriptBin "aicomputer" ''
echo "agentcomputer-cli CI stub"
'';
}
);
};
}

View file

@ -1,183 +0,0 @@
# vim:ft=zsh ts=2 sw=2 sts=2
#
# agnoster's Theme - https://gist.github.com/3712874
# A Powerline-inspired theme for ZSH
### Segment drawing
CURRENT_BG='NONE'
case ${SOLARIZED_THEME:-dark} in
light)
CURRENT_FG=${CURRENT_FG:-'white'}
CURRENT_DEFAULT_FG=${CURRENT_DEFAULT_FG:-'white'}
;;
*)
CURRENT_FG=${CURRENT_FG:-'black'}
CURRENT_DEFAULT_FG=${CURRENT_DEFAULT_FG:-'default'}
;;
esac
### Theme Configuration Initialization
: ${AGNOSTER_DIR_FG:=${CURRENT_FG}}
: ${AGNOSTER_DIR_BG:=blue}
: ${AGNOSTER_CONTEXT_FG:=${CURRENT_DEFAULT_FG}}
: ${AGNOSTER_CONTEXT_BG:=black}
: ${AGNOSTER_GIT_CLEAN_FG:=${CURRENT_FG}}
: ${AGNOSTER_GIT_CLEAN_BG:=green}
: ${AGNOSTER_GIT_DIRTY_FG:=black}
: ${AGNOSTER_GIT_DIRTY_BG:=yellow}
: ${AGNOSTER_BZR_CLEAN_FG:=${CURRENT_FG}}
: ${AGNOSTER_BZR_CLEAN_BG:=green}
: ${AGNOSTER_BZR_DIRTY_FG:=black}
: ${AGNOSTER_BZR_DIRTY_BG:=yellow}
: ${AGNOSTER_HG_NEWFILE_FG:=white}
: ${AGNOSTER_HG_NEWFILE_BG:=red}
: ${AGNOSTER_HG_CHANGED_FG:=black}
: ${AGNOSTER_HG_CHANGED_BG:=yellow}
: ${AGNOSTER_HG_CLEAN_FG:=${CURRENT_FG}}
: ${AGNOSTER_HG_CLEAN_BG:=green}
: ${AGNOSTER_VENV_FG:=black}
: ${AGNOSTER_VENV_BG:=blue}
: ${AGNOSTER_AWS_PROD_FG:=yellow}
: ${AGNOSTER_AWS_PROD_BG:=red}
: ${AGNOSTER_AWS_FG:=black}
: ${AGNOSTER_AWS_BG:=green}
: ${AGNOSTER_STATUS_RETVAL_FG:=red}
: ${AGNOSTER_STATUS_ROOT_FG:=yellow}
: ${AGNOSTER_STATUS_JOB_FG:=cyan}
: ${AGNOSTER_STATUS_FG:=${CURRENT_DEFAULT_FG}}
: ${AGNOSTER_STATUS_BG:=black}
: ${AGNOSTER_STATUS_RETVAL_NUMERIC:=false}
: ${AGNOSTER_GIT_INLINE:=false}
: ${AGNOSTER_GIT_BRANCH_STATUS:=true}
() {
local LC_ALL="" LC_CTYPE="en_US.UTF-8"
SEGMENT_SEPARATOR=$'\ue0b0'
}
prompt_segment() {
local bg fg
[[ -n $1 ]] && bg="%K{$1}" || bg="%k"
[[ -n $2 ]] && fg="%F{$2}" || fg="%f"
if [[ $CURRENT_BG != 'NONE' && $1 != $CURRENT_BG ]]; then
echo -n " %{$bg%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR%{$fg%} "
else
echo -n "%{$bg%}%{$fg%} "
fi
CURRENT_BG=$1
[[ -n $3 ]] && echo -n $3
}
prompt_end() {
if [[ -n $CURRENT_BG ]]; then
echo -n " %{%k%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR"
else
echo -n "%{%k%}"
fi
echo -n "%{%f%}"
CURRENT_BG=''
}
git_toplevel() {
local repo_root=$(git rev-parse --show-toplevel)
if [[ $repo_root = '' ]]; then
repo_root=$(git rev-parse --git-dir)
[[ $repo_root = '.' ]] && repo_root=$PWD
fi
echo -n $repo_root
}
### Prompt components
prompt_context() {
if [[ -n "$SSH_CLIENT" ]]; then
prompt_segment "$AGNOSTER_CONTEXT_BG" "$AGNOSTER_CONTEXT_FG" "%(!.%{%F{$AGNOSTER_STATUS_ROOT_FG}%}.)%n@%m"
fi
}
prompt_git_relative() {
local repo_root=$(git_toplevel)
local path_in_repo=$(pwd | sed "s/^$(echo "$repo_root" | sed 's:/:\\/:g;s/\$/\\$/g')//;s:^/::;s:/$::;")
[[ $path_in_repo != '' ]] && prompt_segment "$AGNOSTER_DIR_BG" "$AGNOSTER_DIR_FG" "$path_in_repo"
}
prompt_git() {
(( $+commands[git] )) || return
[[ "$(command git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]] && return
local PL_BRANCH_CHAR
() { local LC_ALL="" LC_CTYPE="en_US.UTF-8"; PL_BRANCH_CHAR=$'\ue0a0' }
if [[ "$(command git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]]; then
local dirty ref
dirty=$(parse_git_dirty)
ref=$(command git symbolic-ref HEAD 2>/dev/null) || \
ref="➦ $(command git rev-parse --short HEAD 2>/dev/null)"
if [[ -n $dirty ]]; then
prompt_segment "$AGNOSTER_GIT_DIRTY_BG" "$AGNOSTER_GIT_DIRTY_FG"
else
prompt_segment "$AGNOSTER_GIT_CLEAN_BG" "$AGNOSTER_GIT_CLEAN_FG"
fi
echo -n "${${ref:gs/%/%%}/refs\/heads\//$PL_BRANCH_CHAR }"
fi
}
prompt_dir() {
if [[ $AGNOSTER_GIT_INLINE == 'true' ]] && $(git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
prompt_segment "$AGNOSTER_DIR_BG" "$AGNOSTER_DIR_FG" "$(git_toplevel | sed "s:^$HOME:~:")"
else
prompt_segment "$AGNOSTER_DIR_BG" "$AGNOSTER_DIR_FG" '%~'
fi
}
prompt_virtualenv() {
return
}
prompt_status() {
local -a symbols
[[ $AGNOSTER_STATUS_RETVAL_NUMERIC == 'true' && $RETVAL -ne 0 ]] && symbols+="%{%F{$AGNOSTER_STATUS_RETVAL_FG}%}$RETVAL"
[[ $AGNOSTER_STATUS_RETVAL_NUMERIC != 'true' && $RETVAL -ne 0 ]] && symbols+="%{%F{$AGNOSTER_STATUS_RETVAL_FG}%}✘"
[[ $UID -eq 0 ]] && symbols+="%{%F{$AGNOSTER_STATUS_ROOT_FG}%}⚡"
[[ $(jobs -l | wc -l) -gt 0 ]] && symbols+="%{%F{$AGNOSTER_STATUS_JOB_FG}%}⚙"
[[ -n "$symbols" ]] && prompt_segment "$AGNOSTER_STATUS_BG" "$AGNOSTER_STATUS_FG" "$symbols"
}
prompt_aws() {
[[ -z "$AWS_PROFILE" || "$SHOW_AWS_PROMPT" = false ]] && return
case "$AWS_PROFILE" in
*-prod|*production*) prompt_segment "$AGNOSTER_AWS_PROD_BG" "$AGNOSTER_AWS_PROD_FG" "AWS: ${AWS_PROFILE:gs/%/%%}" ;;
*) prompt_segment "$AGNOSTER_AWS_BG" "$AGNOSTER_AWS_FG" "AWS: ${AWS_PROFILE:gs/%/%%}" ;;
esac
}
prompt_terraform() {
local terraform_info=$(tf_prompt_info)
[[ -z "$terraform_info" ]] && return
prompt_segment magenta yellow "TF: $terraform_info"
}
build_prompt() {
RETVAL=$?
prompt_status
prompt_virtualenv
prompt_aws
prompt_terraform
prompt_context
prompt_dir
prompt_git
prompt_end
}
PROMPT='%{%f%b%k%}$(build_prompt) '

View file

@ -1,80 +0,0 @@
# Secrets
## Current Model
This repo does not store secret values in Nix.
Instead:
- Bitwarden vault items are the current source of truth for imported machine
secrets
- Nix/Home Manager owns the integration points
- generated runtime files live outside the repo under `~/.config/secrets`
That boundary matters because the Nix store is not the right place for real
secret values.
## What Is Already Wired
- [home/zsh.nix](/Users/rathi/Documents/GitHub/nix/home/zsh.nix) sources
`~/.config/secrets/shell.zsh` when present
- [scripts/render-bw-shell-secrets.sh](/Users/rathi/Documents/GitHub/nix/scripts/render-bw-shell-secrets.sh)
renders that file from Bitwarden vault items
- [scripts/restore-bw-files.sh](/Users/rathi/Documents/GitHub/nix/scripts/restore-bw-files.sh)
restores file-based credentials and SSH material from Bitwarden vault items
- [justfile](/Users/rathi/Documents/GitHub/nix/justfile) exposes this as
`just secrets-sync` and `just secrets-restore-files`
## Daily Shell Flow
```bash
export BW_SESSION="$(bw unlock --raw)"
just secrets-sync
exec zsh -l
```
That flow currently materializes:
- `OPENAI_API_KEY`
- `GREPTILE_API_KEY`
- `CONTEXT7_API_KEY`
- `MISTRAL_API_KEY`
## Machine Secret Coverage
The Bitwarden vault now holds:
- API keys and CLI tokens
- AWS default credentials
- GCloud ADC
- Stripe CLI config
- Codex auth
- Vercel auth
- SSH configs
- SSH private keys
The vault is currently the backup/recovery source of truth for those values.
## Sandbox Strategy
For a fresh sandbox or new machine, the clean bootstrap is:
1. `darwin-rebuild switch` or Home Manager activation
2. authenticate `bw`
3. `just secrets-sync`
4. `just secrets-restore-files`
That gives you a usable dev shell quickly without committing any secret values
into the repo.
## Future Upgrade
If you want fully non-interactive sandbox secret injection, the next step is to
move the env-style secrets from normal Bitwarden vault items into Bitwarden
Secrets Manager (`bws`) and keep file-based credentials and SSH material in the
normal vault.
That would give you:
- `bws` for machine/app secrets
- `bw` for human-managed vault items, SSH material, and recovery data

View file

@ -1,5 +1,5 @@
{
description = "Rathi's macOS nix-darwin + NixOS + Home Manager config";
description = "Hari's nix config";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
@ -25,11 +25,6 @@
inputs.nixpkgs.follows = "nixpkgs";
};
agentcomputer-cli = {
url = "path:/Users/rathi/Documents/GitHub/companion/agentcomputer/apps/cli";
inputs.nixpkgs.follows = "nixpkgs";
};
openspec = {
url = "github:Fission-AI/OpenSpec";
};

View file

@ -1,10 +1,10 @@
{ ... }:
{ theme, ... }:
{
programs.bat = {
enable = true;
config = {
theme = "gruvbox-dark";
theme = theme.batTheme theme.defaultMode;
};
};
}

View file

@ -7,17 +7,16 @@ let
claudePackage = inputs.claudeCode.packages.${pkgs.stdenv.hostPlatform.system}.default;
in
{
# Keep the managed Claude binary on the same path the live machine was using
# so the Nix package cleanly replaces the prior manual install.
home.file.".local/bin/claude".source = "${claudePackage}/bin/claude";
home.file.".claude/CLAUDE.md".source = ../config/claude/CLAUDE.md;
home.file.".claude/commands" = {
xdg.configFile."claude/CLAUDE.md".source = ../config/claude/CLAUDE.md;
xdg.configFile."claude/commands" = {
source = ../config/claude/commands;
recursive = true;
};
home.file.".claude/settings.json".source = ../config/claude/settings.json;
home.file.".claude/settings.local.json".source = ../config/claude/settings.local.json;
home.file.".claude/statusline.sh" = {
xdg.configFile."claude/settings.json".source = ../config/claude/settings.json;
xdg.configFile."claude/settings.local.json".source = ../config/claude/settings.local.json;
xdg.configFile."claude/statusline.sh" = {
source = ../config/claude/statusline.sh;
executable = true;
};

View file

@ -1,9 +1,13 @@
{ ... }:
{ config, ... }:
{
_module.args.theme = import ../lib/theme.nix { inherit config; };
imports = [
./bat.nix
./eza.nix
./claude.nix
./xdg.nix
./security.nix
./codex.nix
./fzf.nix
./gcloud.nix
@ -15,6 +19,7 @@
./mise.nix
./migration.nix
./nvim.nix
./prompt.nix
./skills.nix
./scripts.nix
./ssh.nix
@ -25,4 +30,9 @@
home.stateVersion = "24.11";
programs.home-manager.enable = true;
xdg.enable = true;
programs.zoxide = {
enable = true;
enableZshIntegration = true;
};
}

View file

@ -1,8 +1,18 @@
{ ... }:
{
lib,
hostConfig,
...
}:
{
imports = [
./common.nix
]
++ lib.optionals hostConfig.isDarwin [
./colima.nix
./rectangle.nix
./karabiner.nix
]
++ lib.optionals hostConfig.isLinux [
./netty-worktree.nix
];
}

View file

@ -1,7 +1,4 @@
{ config, ... }:
let
theme = import ../lib/theme.nix { inherit config; };
in
{ theme, ... }:
{
home.sessionVariables = {
FZF_DEFAULT_OPTS_FILE = theme.paths.fzfCurrentFile;

View file

@ -1,11 +1,11 @@
{
config,
lib,
pkgs,
hostConfig,
theme,
...
}:
let
theme = import ../lib/theme.nix { inherit config; };
ghosttyConfig = ''
theme = "cozybox-current"
font-family = Berkeley Mono
@ -40,7 +40,7 @@ let
keybind = vim/i=deactivate_key_table
keybind = vim/catch_all=ignore
mouse-hide-while-typing = true
${lib.optionalString pkgs.stdenv.isDarwin ''
${lib.optionalString hostConfig.isDarwin ''
macos-titlebar-style = hidden
macos-option-as-alt = true
''}
@ -57,7 +57,7 @@ in
{
programs.ghostty = {
enable = true;
package = if pkgs.stdenv.isDarwin then pkgs.ghostty-bin else pkgs.ghostty;
package = if hostConfig.isDarwin then pkgs.ghostty-bin else pkgs.ghostty;
installBatSyntax = true;
};
@ -69,7 +69,7 @@ in
xdg.configFile."ghostty/themes/cozybox-dark".text = theme.renderGhostty "dark";
xdg.configFile."ghostty/themes/cozybox-light".text = theme.renderGhostty "light";
home.file = lib.mkIf pkgs.stdenv.isDarwin {
home.file = lib.mkIf hostConfig.isDarwin {
"Library/Application Support/com.mitchellh.ghostty/config.ghostty" = {
text = ghosttyConfig;
force = true;

View file

@ -1,10 +1,37 @@
{ ... }:
{ theme, ... }:
{
programs.git = {
enable = true;
lfs.enable = true;
signing.format = "openpgp";
ignores = [
"*.swp"
"*.swo"
"*~"
".DS_Store"
"Thumbs.db"
".env"
".env.local"
".env.*.local"
".vscode/"
".idea/"
".claude/"
"CLAUDE.md"
"node_modules/"
"__pycache__/"
"*.pyc"
"venv/"
".venv/"
"build/"
"dist/"
"out/"
"target/"
"result"
"result-*"
".direnv/"
];
settings = {
user = {
name = "Harivansh Rathi";
@ -40,7 +67,7 @@
};
delta = {
"syntax-theme" = "gruvbox-dark";
"syntax-theme" = theme.deltaTheme theme.defaultMode;
"hunk-header-style" = "omit";
"minus-style" = ''syntax "#3c1f1e"'';
"minus-emph-style" = ''syntax "#72261d"'';

View file

@ -1,12 +1,12 @@
{
lib,
pkgs,
hostConfig,
...
}:
{
xdg.configFile."lazygit/config.yml".source = ../config/lazygit/config.yml;
home.file = lib.mkIf pkgs.stdenv.isDarwin {
home.file = lib.mkIf hostConfig.isDarwin {
"Library/Application Support/lazygit/config.yml".source = ../config/lazygit/config.yml;
};
}

86
home/prompt.nix Normal file
View file

@ -0,0 +1,86 @@
{
lib,
pkgs,
theme,
...
}:
{
home.packages = [ pkgs.pure-prompt ];
programs.zsh.initContent = lib.mkMerge [
(lib.mkOrder 800 ''
fpath+=("${pkgs.pure-prompt}/share/zsh/site-functions")
autoload -Uz promptinit && promptinit
export PURE_PROMPT_SYMBOL=$'\xe2\x9d\xaf'
export PURE_PROMPT_VICMD_SYMBOL=$'\xe2\x9d\xae'
export PURE_GIT_DIRTY=""
export PURE_GIT_UP_ARROW="^"
export PURE_GIT_DOWN_ARROW="v"
export PURE_GIT_STASH_SYMBOL="="
export PURE_CMD_MAX_EXEC_TIME=5
export PURE_GIT_PULL=0
export PURE_GIT_UNTRACKED_DIRTY=1
zstyle ':prompt:pure:git:stash' show yes
${theme.renderPurePrompt "dark"}
typeset -g prompt_newline=' '
prompt pure
prompt_pure_preprompt_render() {
setopt localoptions noshwordsplit
unset prompt_pure_async_render_requested
typeset -g prompt_pure_git_branch_color=$prompt_pure_colors[git:branch]
[[ -n ''${prompt_pure_git_last_dirty_check_timestamp+x} ]] && prompt_pure_git_branch_color=$prompt_pure_colors[git:branch:cached]
# Branch, arrows, and prompt symbol turn yellow when dirty
if [[ -n $prompt_pure_git_dirty ]]; then
prompt_pure_git_branch_color=$prompt_pure_colors[git:dirty]
prompt_pure_colors[git:arrow]=$prompt_pure_colors[git:dirty]
prompt_pure_colors[prompt:success]=$prompt_pure_colors[git:dirty]
else
prompt_pure_colors[git:arrow]=$_codex_pure_default_arrow
prompt_pure_colors[prompt:success]=$_codex_pure_default_success
fi
psvar[12]=; ((''${(M)#jobstates:#suspended:*} != 0)) && psvar[12]=''${PURE_SUSPENDED_JOBS_SYMBOL:-✦}
psvar[13]=; [[ -n $prompt_pure_state[username] ]] && psvar[13]=1
psvar[14]=''${prompt_pure_vcs_info[branch]}
psvar[15]=
psvar[16]=''${prompt_pure_vcs_info[action]}
psvar[17]=''${prompt_pure_git_arrows}
psvar[18]=; [[ -n $prompt_pure_git_stash ]] && psvar[18]=1
psvar[19]=''${prompt_pure_cmd_exec_time}
local expanded_prompt
expanded_prompt="''${(S%%)PROMPT}"
if [[ $1 != precmd && $prompt_pure_last_prompt != $expanded_prompt ]]; then
prompt_pure_reset_prompt
fi
typeset -g prompt_pure_last_prompt=$expanded_prompt
}
typeset -g _codex_pure_default_arrow=$prompt_pure_colors[git:arrow]
typeset -g _codex_pure_default_success=$prompt_pure_colors[prompt:success]
_codex_apply_prompt_theme() {
local mode="$(_codex_read_theme_mode)"
[[ "$mode" == "''${_CODEX_LAST_PROMPT_THEME:-}" ]] && return
if [[ "$mode" == light ]]; then
${theme.renderPurePrompt "light"}
else
${theme.renderPurePrompt "dark"}
fi
typeset -g _codex_pure_default_arrow=$prompt_pure_colors[git:arrow]
typeset -g _codex_pure_default_success=$prompt_pure_colors[prompt:success]
typeset -g _CODEX_LAST_PROMPT_THEME="$mode"
}
'')
];
}

View file

@ -2,6 +2,7 @@
config,
lib,
pkgs,
hostConfig,
...
}:
let
@ -10,7 +11,7 @@ in
{
home.packages =
builtins.attrValues customScripts.commonPackages
++ lib.optionals pkgs.stdenv.isDarwin (builtins.attrValues customScripts.darwinPackages);
++ lib.optionals hostConfig.isDarwin (builtins.attrValues customScripts.darwinPackages);
home.activation.initializeThemeState = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
mkdir -p "${customScripts.theme.paths.stateDir}" "${customScripts.theme.paths.fzfDir}" "${customScripts.theme.paths.ghosttyDir}" "${customScripts.theme.paths.tmuxDir}"

26
home/security.nix Normal file
View file

@ -0,0 +1,26 @@
{
config,
lib,
...
}:
{
home.activation.secretPermissions = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if [ -d "${config.home.homeDirectory}/.ssh" ]; then
$DRY_RUN_CMD chmod 700 "${config.home.homeDirectory}/.ssh"
for f in "${config.home.homeDirectory}/.ssh/"*; do
[ -f "$f" ] || continue
[ -L "$f" ] && continue
case "$f" in
*.pub|*/known_hosts|*/known_hosts.old)
$DRY_RUN_CMD chmod 644 "$f" ;;
*)
$DRY_RUN_CMD chmod 600 "$f" ;;
esac
done
fi
if [ -d "${config.home.homeDirectory}/.gnupg" ]; then
$DRY_RUN_CMD find "${config.home.homeDirectory}/.gnupg" -type d -exec chmod 700 {} +
$DRY_RUN_CMD find "${config.home.homeDirectory}/.gnupg" -type f -exec chmod 600 {} +
fi
'';
}

View file

@ -1,12 +1,9 @@
{
config,
lib,
pkgs,
theme,
...
}:
let
theme = import ../lib/theme.nix { inherit config; };
in
{
programs.tmux = {
enable = true;

93
home/xdg.nix Normal file
View file

@ -0,0 +1,93 @@
{
config,
lib,
hostConfig,
...
}:
let
f = hostConfig.features;
in
{
home.sessionVariables = lib.mkMerge [
{
LESSHISTFILE = "-";
WGETRC = "${config.xdg.configHome}/wgetrc";
}
(lib.mkIf (f.rust or false) {
CARGO_HOME = "${config.xdg.dataHome}/cargo";
RUSTUP_HOME = "${config.xdg.dataHome}/rustup";
})
(lib.mkIf (f.go or false) {
GOPATH = "${config.xdg.dataHome}/go";
GOMODCACHE = "${config.xdg.cacheHome}/go/mod";
})
(lib.mkIf (f.node or false) {
NPM_CONFIG_USERCONFIG = "${config.xdg.configHome}/npm/npmrc";
NODE_REPL_HISTORY = "${config.xdg.stateHome}/node_repl_history";
PNPM_HOME = "${config.xdg.dataHome}/pnpm";
PNPM_NO_UPDATE_NOTIFIER = "true";
})
(lib.mkIf (f.python or false) {
PYTHONSTARTUP = "${config.xdg.configHome}/python/pythonrc";
PYTHON_HISTORY = "${config.xdg.stateHome}/python_history";
PYTHONPYCACHEPREFIX = "${config.xdg.cacheHome}/python";
PYTHONUSERBASE = "${config.xdg.dataHome}/python";
})
(lib.mkIf (f.docker or false) {
DOCKER_CONFIG = "${config.xdg.configHome}/docker";
})
(lib.mkIf (f.aws or false) {
AWS_SHARED_CREDENTIALS_FILE = "${config.xdg.configHome}/aws/credentials";
AWS_CONFIG_FILE = "${config.xdg.configHome}/aws/config";
})
(lib.mkIf (f.claude or false) {
CLAUDE_CONFIG_DIR = "${config.xdg.configHome}/claude";
})
{
PSQL_HISTORY = "${config.xdg.stateHome}/psql_history";
SQLITE_HISTORY = "${config.xdg.stateHome}/sqlite_history";
}
];
home.sessionPath = lib.mkMerge [
[ "${config.home.homeDirectory}/.local/bin" ]
(lib.mkIf (f.rust or false) [ "${config.xdg.dataHome}/cargo/bin" ])
(lib.mkIf (f.go or false) [ "${config.xdg.dataHome}/go/bin" ])
(lib.mkIf (f.node or false) [ "${config.xdg.dataHome}/pnpm" ])
];
xdg.configFile."npm/npmrc" = lib.mkIf (f.node or false) {
text = ''
prefix=''${XDG_DATA_HOME}/npm
cache=''${XDG_CACHE_HOME}/npm
init-module=''${XDG_CONFIG_HOME}/npm/config/npm-init.js
'';
};
xdg.configFile."python/pythonrc" = lib.mkIf (f.python or false) {
text = ''
import atexit
import os
import readline
history = os.path.join(os.environ.get('XDG_STATE_HOME', os.path.expanduser('~/.local/state')), 'python_history')
try:
readline.read_history_file(history)
except OSError:
pass
def write_history():
try:
readline.write_history_file(history)
except OSError:
pass
atexit.register(write_history)
'';
};
xdg.configFile."wgetrc".text = ''
hsts_file = ${config.xdg.stateHome}/wget-hsts
'';
}

View file

@ -2,17 +2,11 @@
config,
lib,
pkgs,
hostConfig,
theme,
...
}:
{
home.file.".oh-my-zsh/custom/themes/agnoster.zsh-theme".source = ../config/agnoster.zsh-theme;
home.activation.ensureOhMyZshCache = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
mkdir -p "${config.xdg.cacheHome}/oh-my-zsh"
'';
home.packages = [ pkgs.oh-my-zsh ];
programs.zsh = {
enable = true;
dotDir = config.home.homeDirectory;
@ -22,6 +16,17 @@
autosuggestion.enable = true;
syntaxHighlighting.enable = true;
history = {
size = 50000;
save = 50000;
ignoreDups = true;
ignoreAllDups = true;
ignoreSpace = true;
extended = true;
append = true;
path = "${config.xdg.stateHome}/zsh_history";
};
shellAliases = {
co = "codex --dangerously-bypass-approvals-and-sandbox";
ca = "cursor-agent";
@ -38,7 +43,7 @@
lg = "lazygit";
nim = "nvim .";
}
// lib.optionalAttrs pkgs.stdenv.isDarwin {
// lib.optionalAttrs hostConfig.isDarwin {
tailscale = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
};
@ -48,9 +53,7 @@
fi
export NODE_NO_WARNINGS=1
''
+ lib.optionalString pkgs.stdenv.isDarwin ''
# Ghostty shell integration expects a resource directory; the Nix app
# bundle lives in the store instead of /Applications.
+ lib.optionalString hostConfig.isDarwin ''
export GHOSTTY_RESOURCES_DIR="${pkgs.ghostty-bin}/Applications/Ghostty.app/Contents/Resources/ghostty"
''
+ ''
@ -59,18 +62,9 @@
initContent = lib.mkMerge [
(lib.mkOrder 550 ''
# OpenSpec shell completions configuration
fpath=("$HOME/.oh-my-zsh/custom/completions" $fpath)
'')
(lib.mkOrder 800 ''
export ZSH="${pkgs.oh-my-zsh}/share/oh-my-zsh"
export ZSH_CUSTOM="$HOME/.oh-my-zsh/custom"
export ZSH_CACHE_DIR="${config.xdg.cacheHome}/oh-my-zsh"
ZSH_THEME="agnoster"
plugins=(git)
ZSH_DISABLE_COMPFIX=true
source "$ZSH/oh-my-zsh.sh"
autoload -U compinit && compinit -d "${config.xdg.stateHome}/zcompdump" -u
zmodload zsh/complist
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-za-z}'
'')
(lib.mkOrder 1000 ''
@ -80,29 +74,21 @@
source ~/.secrets
fi
eval "$(zoxide init zsh)"
[ -s "$HOME/.bun/_bun" ] && source "$HOME/.bun/_bun"
export BUN_INSTALL="$HOME/.bun"
export PNPM_HOME="${
if pkgs.stdenv.isDarwin then "$HOME/Library/pnpm" else "${config.xdg.dataHome}/pnpm"
}"
bindkey -v
typeset -U path PATH
path=(
"$HOME/.amp/bin"
"$PNPM_HOME"
"$BUN_INSTALL/bin"
"$HOME/.antigravity/antigravity/bin"
"$HOME/.opencode/bin"
"${pkgs.postgresql_17}/bin"
"$HOME/.local/bin"
"$HOME/.nix-profile/bin"
"/etc/profiles/per-user/${config.home.username}/bin"
"/run/current-system/sw/bin"
"/nix/var/nix/profiles/default/bin"
${lib.optionalString pkgs.stdenv.isDarwin ''
${lib.optionalString hostConfig.isDarwin ''
"/opt/homebrew/bin"
"/opt/homebrew/sbin"
''}
@ -119,78 +105,19 @@
return
fi
fi
printf 'dark'
}
_codex_apply_highlight_styles() {
local mode="$(_codex_read_theme_mode)"
if [[ "$mode" == "''${_CODEX_LAST_HIGHLIGHT_THEME:-}" ]]; then
return
fi
[[ "$mode" == "''${_CODEX_LAST_HIGHLIGHT_THEME:-}" ]] && return
typeset -gA ZSH_HIGHLIGHT_STYLES
if [[ "$mode" == light ]]; then
ZSH_HIGHLIGHT_STYLES[arg0]='fg=#427b58'
ZSH_HIGHLIGHT_STYLES[autodirectory]='fg=#427b58,underline'
ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]='fg=#076678'
ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]='fg=#076678'
ZSH_HIGHLIGHT_STYLES[back-quoted-argument-delimiter]='fg=#8f3f71'
ZSH_HIGHLIGHT_STYLES[bracket-error]='fg=#ea6962,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=#076678,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=#427b58,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=#8f3f71,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=#b57614,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-5]='fg=#076678,bold'
ZSH_HIGHLIGHT_STYLES[comment]='fg=#928374'
ZSH_HIGHLIGHT_STYLES[command-substitution-delimiter]='fg=#8f3f71'
ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]='fg=#076678'
ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=#b57614'
ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=#b57614'
ZSH_HIGHLIGHT_STYLES[global-alias]='fg=#076678'
ZSH_HIGHLIGHT_STYLES[globbing]='fg=#076678'
ZSH_HIGHLIGHT_STYLES[history-expansion]='fg=#076678'
ZSH_HIGHLIGHT_STYLES[path]='fg=#3c3836,underline'
ZSH_HIGHLIGHT_STYLES[precommand]='fg=#427b58,underline'
ZSH_HIGHLIGHT_STYLES[process-substitution-delimiter]='fg=#8f3f71'
ZSH_HIGHLIGHT_STYLES[rc-quote]='fg=#076678'
ZSH_HIGHLIGHT_STYLES[redirection]='fg=#b57614'
ZSH_HIGHLIGHT_STYLES[reserved-word]='fg=#b57614'
ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=#b57614'
ZSH_HIGHLIGHT_STYLES[suffix-alias]='fg=#427b58,underline'
ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=#ea6962,bold'
${theme.renderZshHighlights "light"}
else
ZSH_HIGHLIGHT_STYLES[arg0]='fg=#8ec97c'
ZSH_HIGHLIGHT_STYLES[autodirectory]='fg=#8ec97c,underline'
ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]='fg=#8ec07c'
ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]='fg=#8ec07c'
ZSH_HIGHLIGHT_STYLES[back-quoted-argument-delimiter]='fg=#d3869b'
ZSH_HIGHLIGHT_STYLES[bracket-error]='fg=#ea6962,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=#5b84de,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=#8ec97c,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=#d3869b,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=#d8a657,bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-5]='fg=#8ec07c,bold'
ZSH_HIGHLIGHT_STYLES[comment]='fg=#7c6f64'
ZSH_HIGHLIGHT_STYLES[command-substitution-delimiter]='fg=#d3869b'
ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]='fg=#8ec07c'
ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=#d8a657'
ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=#d8a657'
ZSH_HIGHLIGHT_STYLES[global-alias]='fg=#8ec07c'
ZSH_HIGHLIGHT_STYLES[globbing]='fg=#5b84de'
ZSH_HIGHLIGHT_STYLES[history-expansion]='fg=#5b84de'
ZSH_HIGHLIGHT_STYLES[path]='fg=#d4be98,underline'
ZSH_HIGHLIGHT_STYLES[precommand]='fg=#8ec97c,underline'
ZSH_HIGHLIGHT_STYLES[process-substitution-delimiter]='fg=#d3869b'
ZSH_HIGHLIGHT_STYLES[rc-quote]='fg=#8ec07c'
ZSH_HIGHLIGHT_STYLES[redirection]='fg=#d8a657'
ZSH_HIGHLIGHT_STYLES[reserved-word]='fg=#d8a657'
ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=#d8a657'
ZSH_HIGHLIGHT_STYLES[suffix-alias]='fg=#8ec97c,underline'
ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=#ea6962,bold'
${theme.renderZshHighlights "dark"}
fi
typeset -g _CODEX_LAST_HIGHLIGHT_THEME="$mode"
}
@ -199,7 +126,6 @@
git() {
command git "$@"
local exit_code=$?
case "$1" in
add|stage|reset|checkout)
if command -v critic >/dev/null 2>&1; then
@ -207,64 +133,32 @@
fi
;;
esac
return $exit_code
}
function _codex_set_cursor {
if [[ "$1" == block ]]; then
printf '\e[2 q'
else
printf '\e[6 q'
fi
}
function zle-keymap-select {
if [[ "$KEYMAP" == vicmd ]]; then
_codex_set_cursor block
else
_codex_set_cursor beam
fi
}
zle -N zle-keymap-select
function zle-line-init {
_codex_set_cursor beam
}
zle -N zle-line-init
function zle-line-finish {
_codex_set_cursor beam
}
zle -N zle-line-finish
autoload -Uz add-zle-hook-widget
_codex_cursor() { printf '\e[%s q' "''${1:-6}"; }
_codex_cursor_select() { [[ "$KEYMAP" == vicmd ]] && _codex_cursor 2 || _codex_cursor 6; }
_codex_cursor_beam() { _codex_cursor 6; }
add-zle-hook-widget zle-keymap-select _codex_cursor_select
add-zle-hook-widget zle-line-init _codex_cursor_beam
add-zle-hook-widget zle-line-finish _codex_cursor_beam
precmd() {
_codex_apply_prompt_theme
_codex_apply_highlight_styles
_codex_set_cursor beam
}
preexec() {
_codex_set_cursor beam
_codex_cursor_beam
}
preexec() { _codex_cursor_beam; }
_codex_apply_prompt_theme
_codex_apply_highlight_styles
${lib.optionalString pkgs.stdenv.isDarwin ''
if command -v wt >/dev/null 2>&1; then
eval "$(command wt config shell init zsh)"
# `wt` changes directories by sourcing directives into the current shell,
# so wrappers around it must stay shell functions instead of scripts.
wtc() {
wt switch --create --base @ "$@"
}
fi
''}
'')
(lib.mkAfter ''
bindkey '^k' forward-car
bindkey '^j' backward-car
bindkey '^k' forward-char
bindkey '^j' backward-char
'')
];
};

View file

@ -1,6 +1,7 @@
{
inputs,
lib,
modulesPath,
pkgs,
username,
self,
@ -14,6 +15,8 @@ in
./hardware-configuration.nix
./disk-config.nix
../../modules/base.nix
(modulesPath + "/profiles/minimal.nix")
(modulesPath + "/profiles/headless.nix")
];
boot.loader.grub = {
@ -21,12 +24,31 @@ in
efiSupport = true;
efiInstallAsRemovable = true;
device = "nodev";
configurationLimit = 5;
configurationLimit = 3;
};
documentation.enable = false;
fonts.fontconfig.enable = false;
networking = {
hostName = "netty";
useDHCP = true;
useDHCP = false;
interfaces.ens3 = {
ipv4.addresses = [
{
address = "152.53.195.59";
prefixLength = 22;
}
];
};
defaultGateway = {
address = "152.53.192.1";
interface = "ens3";
};
nameservers = [
"1.1.1.1"
"8.8.8.8"
];
firewall.allowedTCPPorts = [
22
80
@ -44,9 +66,13 @@ in
};
};
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM6tzq33IQcurWoQ7vhXOTLjv8YkdTGb7NoNsul3Sbfu rathi@mac"
];
# Emergency console access - generate hashed password and save to Bitwarden later
users.users.root = {
initialPassword = "temppass123";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM6tzq33IQcurWoQ7vhXOTLjv8YkdTGb7NoNsul3Sbfu rathi@mac"
];
};
users.users.${username} = {
isNormalUser = true;
@ -64,15 +90,182 @@ in
username
];
nix.gc.options = lib.mkForce "--delete-older-than 3d";
nix.extraOptions = ''
min-free = ${toString (100 * 1024 * 1024)}
max-free = ${toString (1024 * 1024 * 1024)}
'';
services.journald.extraConfig = "MaxRetainedFileSec=1week";
environment.systemPackages = packageSets.extras ++ [
pkgs.bubblewrap
pkgs.pnpm
pkgs.nodejs
];
systemd.tmpfiles.rules = [
"L /usr/bin/bwrap - - - - ${pkgs.bubblewrap}/bin/bwrap"
];
# --- ACME / Let's Encrypt ---
security.acme = {
acceptTerms = true;
defaults.email = "rathiharivansh@gmail.com";
};
# --- Nginx reverse proxy ---
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
clientMaxBodySize = "512m";
virtualHosts."sandbox.example.dev" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:2470";
};
virtualHosts."git.example.dev" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:3000";
};
};
# --- Forgejo ---
users.users.git = {
isSystemUser = true;
home = "/var/lib/forgejo";
group = "git";
shell = "${pkgs.bash}/bin/bash";
};
users.groups.git = { };
services.forgejo = {
enable = true;
user = "git";
group = "git";
settings = {
server = {
DOMAIN = "git.example.dev";
ROOT_URL = "https://git.example.dev/";
HTTP_PORT = 3000;
SSH_DOMAIN = "git.example.dev";
};
service.DISABLE_REGISTRATION = true;
session.COOKIE_SECURE = true;
mirror = {
DEFAULT_INTERVAL = "1h";
MIN_INTERVAL = "10m";
};
};
};
# --- Forgejo mirror sync (hourly) ---
systemd.services.forgejo-mirror-sync = {
description = "Sync GitHub mirrors to Forgejo";
after = [ "forgejo.service" ];
requires = [ "forgejo.service" ];
serviceConfig = {
Type = "oneshot";
EnvironmentFile = "/etc/forgejo-mirror.env";
};
path = [
pkgs.curl
pkgs.jq
pkgs.coreutils
];
script = ''
set -euo pipefail
# Fetch all GitHub repos
page=1
repos=""
while true; do
batch=$(curl -sf -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/user/repos?per_page=100&page=$page&affiliation=owner")
count=$(echo "$batch" | jq length)
[ "$count" -eq 0 ] && break
repos="$repos$batch"
page=$((page + 1))
done
echo "$repos" | jq -r '.[].clone_url' | while read -r clone_url; do
repo_name=$(basename "$clone_url" .git)
# Check if mirror already exists in Forgejo
status=$(curl -sf -o /dev/null -w '%{http_code}' \
-H "Authorization: token $FORGEJO_TOKEN" \
"$FORGEJO_URL/api/v1/repos/$FORGEJO_OWNER/$repo_name")
if [ "$status" = "404" ]; then
# Create mirror
curl -sf -X POST \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
"$FORGEJO_URL/api/v1/repos/migrate" \
-d "{
\"clone_addr\": \"$clone_url\",
\"auth_token\": \"$GITHUB_TOKEN\",
\"uid\": $(curl -sf -H "Authorization: token $FORGEJO_TOKEN" "$FORGEJO_URL/api/v1/user" | jq .id),
\"repo_name\": \"$repo_name\",
\"mirror\": true,
\"service\": \"github\"
}"
echo "Created mirror: $repo_name"
else
# Trigger sync on existing mirror
curl -sf -X POST \
-H "Authorization: token $FORGEJO_TOKEN" \
"$FORGEJO_URL/api/v1/repos/$FORGEJO_OWNER/$repo_name/mirror-sync" || true
echo "Synced mirror: $repo_name"
fi
done
'';
};
systemd.timers.forgejo-mirror-sync = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "hourly";
Persistent = true;
RandomizedDelaySec = "5m";
};
};
# --- Sandbox Agent (declarative systemd services) ---
systemd.services.sandbox-agent = {
description = "Sandbox Agent";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = username;
Group = "users";
EnvironmentFile = "/home/${username}/.config/sandbox-agent/agent.env";
ExecStart = "/home/${username}/.local/bin/sandbox-agent";
Restart = "on-failure";
RestartSec = 5;
};
};
systemd.services.sandbox-cors-proxy = {
description = "Sandbox CORS Proxy";
after = [ "sandbox-agent.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = username;
Group = "users";
ExecStart = "${pkgs.nodejs}/bin/node /home/${username}/.config/sandbox-agent/cors-proxy.js";
Restart = "on-failure";
RestartSec = 5;
};
};
system.configurationRevision = self.rev or self.dirtyRev or null;
system.stateVersion = "24.11";
}

View file

@ -22,6 +22,5 @@
virtualisation.hypervGuest.enable = false;
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

View file

@ -5,8 +5,20 @@
kind = "darwin";
system = "aarch64-darwin";
hostname = "hari-macbook-pro";
homeModule = ../home;
homeDirectory = "/Users/${username}";
isDarwin = true;
isLinux = false;
isNixOS = false;
features = {
rust = true;
go = true;
node = true;
python = true;
aws = true;
claude = true;
docker = true;
tex = true;
};
};
netty = {
@ -14,8 +26,19 @@
kind = "nixos";
system = "x86_64-linux";
hostname = "netty";
homeModule = ../home/netty.nix;
standaloneHomeModule = ../hosts/netty;
homeDirectory = "/home/${username}";
isDarwin = false;
isLinux = true;
isNixOS = true;
features = {
rust = true;
go = true;
node = true;
python = true;
aws = true;
claude = true;
docker = false;
tex = false;
};
};
}

View file

@ -6,56 +6,8 @@
let
gwsPackage = inputs.googleworkspace-cli.packages.${pkgs.stdenv.hostPlatform.system}.default;
claudePackage = inputs.claudeCode.packages.${pkgs.stdenv.hostPlatform.system}.default;
agentcomputerPackage = inputs.agentcomputer-cli.packages.${pkgs.stdenv.hostPlatform.system}.default;
openspecPackage = inputs.openspec.packages.${pkgs.stdenv.hostPlatform.system}.default;
graphite = pkgs.stdenvNoCC.mkDerivation rec {
pname = "graphite";
version = "1.7.20";
src = pkgs.fetchurl {
url = "https://github.com/withgraphite/homebrew-tap/releases/download/v${version}/gt-macos-arm64";
hash = "sha256-ho9VQw1ic3jhG3yxNwUL0W1WvNFku9zw6DQnGehs7+8=";
};
dontUnpack = true;
installPhase = ''
install -Dm755 "$src" "$out/bin/gt"
'';
meta = {
description = "Manage stacked Git changes and submit them for review";
homepage = "https://graphite.dev/";
license = lib.licenses.agpl3Only;
mainProgram = "gt";
platforms = lib.platforms.darwin;
};
};
worktrunk = pkgs.rustPlatform.buildRustPackage rec {
pname = "worktrunk";
version = "0.23.1";
src = pkgs.fetchurl {
url = "https://github.com/max-sixty/worktrunk/archive/refs/tags/v${version}.tar.gz";
hash = "sha256-cdQDUz7to3JkriWE9i5iJ2RftJFZivw7CTwGxDZPAqw=";
};
cargoHash = "sha256-DHjwNqMiVkWqL3CuOCITvyqkdKe+GOZ2nlMSstDIcTg=";
doCheck = false;
meta = {
description = "CLI for Git worktree management";
homepage = "https://worktrunk.dev";
license = with lib.licenses; [
asl20
mit
];
mainProgram = "wt";
platforms = lib.platforms.darwin;
};
};
in
{
core = with pkgs; [
@ -104,18 +56,13 @@ in
redis
tailscale
terraform
texliveFull
yt-dlp
])
++ lib.optionals pkgs.stdenv.isDarwin [
agentcomputerPackage
pkgs.texliveFull
]
++ [
openspecPackage
]
++ lib.optionals pkgs.stdenv.isDarwin [
graphite
worktrunk
];
fonts = with pkgs; [

View file

@ -140,14 +140,128 @@ let
--color=fg+:${theme.text},bg+:${theme.surface},hl+:${theme.blue}
--color=info:${theme.green},prompt:${theme.blue},pointer:${theme.text},marker:${theme.green},spinner:${theme.text}
'';
renderPurePrompt =
mode:
let
theme = themes.${mode};
c =
if mode == "light" then
{
path = "#4261a5";
branch = "#427b58";
dirty = sharedPalette.yellow;
arrow = sharedPalette.purpleNeutral;
stash = sharedPalette.aquaNeutral;
success = "#427b58";
error = "#c5524a";
execTime = sharedPalette.gray;
host = sharedPalette.gray;
user = sharedPalette.gray;
}
else
{
path = sharedPalette.blue;
branch = sharedPalette.green;
dirty = sharedPalette.yellowBright;
arrow = sharedPalette.purple;
stash = sharedPalette.aqua;
success = sharedPalette.green;
error = sharedPalette.red;
execTime = sharedPalette.gray;
host = sharedPalette.gray;
user = sharedPalette.gray;
};
in
''
zstyle ':prompt:pure:path' color '${c.path}'
zstyle ':prompt:pure:git:branch' color '${c.branch}'
zstyle ':prompt:pure:git:dirty' color '${c.dirty}'
zstyle ':prompt:pure:git:arrow' color '${c.arrow}'
zstyle ':prompt:pure:git:stash' color '${c.stash}'
zstyle ':prompt:pure:git:action' color '${c.dirty}'
zstyle ':prompt:pure:prompt:success' color '${c.success}'
zstyle ':prompt:pure:prompt:error' color '${c.error}'
zstyle ':prompt:pure:execution_time' color '${c.execTime}'
zstyle ':prompt:pure:host' color '${c.host}'
zstyle ':prompt:pure:user' color '${c.user}'
zstyle ':prompt:pure:user:root' color '${c.error}'
'';
batTheme = mode: if mode == "light" then "gruvbox-light" else "gruvbox-dark";
deltaTheme = mode: if mode == "light" then "gruvbox-light" else "gruvbox-dark";
renderZshHighlights =
mode:
let
# Light mode uses gruvbox-light specific colors
light = {
arg0 = "#427b58";
aqua = "#076678";
purple = "#8f3f71";
yellow = "#b57614";
text = "#3c3836";
comment = "#928374";
error = "#ea6962";
};
# Dark mode uses our theme palette
dark = {
arg0 = sharedPalette.green;
aqua = sharedPalette.aqua;
purple = sharedPalette.purple;
yellow = "#d8a657";
text = "#d4be98";
comment = "#7c6f64";
error = sharedPalette.red;
blue = sharedPalette.blue;
};
c = if mode == "light" then light else dark;
blueOrAqua = if mode == "light" then c.aqua else c.blue;
in
''
ZSH_HIGHLIGHT_STYLES[arg0]='fg=${c.arg0}'
ZSH_HIGHLIGHT_STYLES[autodirectory]='fg=${c.arg0},underline'
ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]='fg=${if mode == "light" then c.aqua else c.aqua}'
ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]='fg=${if mode == "light" then c.aqua else c.aqua}'
ZSH_HIGHLIGHT_STYLES[back-quoted-argument-delimiter]='fg=${c.purple}'
ZSH_HIGHLIGHT_STYLES[bracket-error]='fg=${c.error},bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=${blueOrAqua},bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=${c.arg0},bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=${c.purple},bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=${c.yellow},bold'
ZSH_HIGHLIGHT_STYLES[bracket-level-5]='fg=${if mode == "light" then c.aqua else c.aqua},bold'
ZSH_HIGHLIGHT_STYLES[comment]='fg=${c.comment}'
ZSH_HIGHLIGHT_STYLES[command-substitution-delimiter]='fg=${c.purple}'
ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]='fg=${
if mode == "light" then c.aqua else c.aqua
}'
ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=${c.yellow}'
ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=${c.yellow}'
ZSH_HIGHLIGHT_STYLES[global-alias]='fg=${if mode == "light" then c.aqua else c.aqua}'
ZSH_HIGHLIGHT_STYLES[globbing]='fg=${blueOrAqua}'
ZSH_HIGHLIGHT_STYLES[history-expansion]='fg=${blueOrAqua}'
ZSH_HIGHLIGHT_STYLES[path]='fg=${c.text},underline'
ZSH_HIGHLIGHT_STYLES[precommand]='fg=${c.arg0},underline'
ZSH_HIGHLIGHT_STYLES[process-substitution-delimiter]='fg=${c.purple}'
ZSH_HIGHLIGHT_STYLES[rc-quote]='fg=${if mode == "light" then c.aqua else c.aqua}'
ZSH_HIGHLIGHT_STYLES[redirection]='fg=${c.yellow}'
ZSH_HIGHLIGHT_STYLES[reserved-word]='fg=${c.yellow}'
ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=${c.yellow}'
ZSH_HIGHLIGHT_STYLES[suffix-alias]='fg=${c.arg0},underline'
ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=${c.error},bold'
'';
in
{
inherit
batTheme
defaultMode
deltaTheme
paths
renderFzf
renderGhostty
renderPurePrompt
renderTmux
renderZshHighlights
themes
;
}

View file

@ -22,11 +22,13 @@ in
username
];
use-xdg-base-directories = true;
max-jobs = "auto";
cores = 0;
};
nix.gc = {
automatic = true;
options = "--delete-older-than 14d";
options = lib.mkDefault "--delete-older-than 14d";
}
// (
if pkgs.stdenv.isDarwin then

View file

@ -1,7 +1,6 @@
{
hosts,
inputs,
mkPkgs,
mkSpecialArgs,
mkHomeManagerModule,
...
@ -21,13 +20,5 @@ in
(mkHomeManagerModule host)
];
};
homeConfigurations.${host.name} = inputs.home-manager.lib.homeManagerConfiguration {
pkgs = mkPkgs host.system;
extraSpecialArgs = mkSpecialArgs host;
modules = [
host.standaloneHomeModule
];
};
};
}

View file

@ -18,6 +18,7 @@ let
mkSpecialArgs = host: {
inherit inputs self username;
hostname = host.hostname;
hostConfig = host;
};
mkHomeManagerModule = host: {
@ -25,7 +26,7 @@ let
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = mkSpecialArgs host;
home-manager.backupCommand = "bash ${../scripts/home-manager-backup.sh}";
home-manager.users.${username} = import host.homeModule;
home-manager.users.${username} = import ../home;
};
in
{

173
nix-maxxing.txt Normal file
View file

@ -0,0 +1,173 @@
Nix Config - Architecture and Operations Guide
================================================
1. STATIC IP
----------------------------
DHCP on a VPS is dangerous. If the DHCP lease expires or the server
reboots while the DHCP server is unreachable, the machine loses its IP
and becomes inaccessible via SSH.
Static config in hosts/netty/configuration.nix:
- IP: 152.53.195.59/22
- Gateway: 152.53.192.1
- Interface: ens3
- DNS: 1.1.1.1, 8.8.8.8
Always verify the interface name with `ip link show` before changing
network config. Keep VNC console access available as a fallback.
2. HOST ABSTRACTION (hostConfig)
---------------------------------
lib/hosts.nix defines each machine with:
- isDarwin / isLinux / isNixOS booleans
- features map (rust, go, node, python, aws, claude, docker, tex)
modules/nixpkgs.nix passes hostConfig via specialArgs so all home-manager
modules can use it. This replaces scattered `pkgs.stdenv.isDarwin` checks.
To add a new host:
1. Add entry to lib/hosts.nix with all fields
2. Create hosts/<name>/configuration.nix (NixOS) or add darwin case
3. Add host output in modules/hosts/<name>.nix
4. home/default.nix auto-selects modules based on hostConfig flags
home/default.nix is the unified entry point - no separate per-host home
modules needed.
3. XDG COMPLIANCE
------------------
home/xdg.nix sets environment variables so tools respect XDG dirs:
CARGO_HOME -> $XDG_DATA_HOME/cargo
RUSTUP_HOME -> $XDG_DATA_HOME/rustup
GOPATH -> $XDG_DATA_HOME/go
GOMODCACHE -> $XDG_CACHE_HOME/go/mod
NPM_CONFIG_USERCONFIG -> $XDG_CONFIG_HOME/npm/npmrc
NODE_REPL_HISTORY -> $XDG_STATE_HOME/node_repl_history
PYTHON_HISTORY -> $XDG_STATE_HOME/python_history
AWS_CONFIG_FILE -> $XDG_CONFIG_HOME/aws/config
DOCKER_CONFIG -> $XDG_CONFIG_HOME/docker
CLAUDE_CONFIG_DIR -> $XDG_CONFIG_HOME/claude
PSQL_HISTORY -> $XDG_STATE_HOME/psql_history
SQLITE_HISTORY -> $XDG_STATE_HOME/sqlite_history
LESSHISTFILE -> "-" (disabled)
All gated by hostConfig.features so tools only get configured when
the feature flag is set for that host.
4. SECURITY MODULE
-------------------
home/security.nix runs activation scripts on every `home-manager switch`:
- ~/.ssh/ dir: 700, private keys: 600, pub/known_hosts/config: 644
- ~/.gnupg/ dirs: 700, files: 600
No manual chmod needed after restoring keys from Bitwarden.
5. THEME SYSTEM
----------------
lib/theme.nix is the single source of truth for colors.
Shared palette (gruvbox-inspired) used across:
- Ghostty terminal (renderGhostty)
- Tmux status bar (renderTmux)
- fzf color scheme (renderFzf)
- Zsh syntax highlighting (renderZshHighlights)
- Bat (batTheme)
- Git delta (deltaTheme)
Runtime toggle: `theme toggle` writes "light" or "dark" to
$XDG_STATE_HOME/theme/current, then updates Ghostty, tmux, fzf,
and Neovim (via RPC) live. Bat and delta are static at build time.
6. SHELL SETUP
---------------
Pure prompt with gruvbox-colored git integration. Async git status
(no blocking on large repos). Colors defined in lib/theme.nix via
renderPurePrompt - adapts to light/dark mode at runtime.
Vim mode via defaultKeymap = "viins" with cursor shape switching
(beam for insert, block for normal).
History: 50k entries, dedup, ignoreSpace, extended format, stored at
$XDG_STATE_HOME/zsh_history.
zoxide: declarative via programs.zoxide (no manual eval).
PATH: managed via home.sessionPath in xdg.nix + initContent block
in zsh.nix for entries that need conditional logic.
7. SERVER SERVICES (netty)
---------------------------
All in hosts/netty/configuration.nix:
Nginx reverse proxy with ACME SSL:
- sandbox.example.dev -> 127.0.0.1:2470 (sandbox agent)
- git.example.dev -> 127.0.0.1:3000 (forgejo)
Forgejo:
- Self-hosted git, registration disabled
- Runs as git user on port 3000
- GitHub mirror sync via hourly systemd timer
- Requires /etc/forgejo-mirror.env with GITHUB_TOKEN, FORGEJO_TOKEN,
FORGEJO_URL, FORGEJO_OWNER
Sandbox Agent:
- System-level systemd services (not user units)
- sandbox-agent on :2470, env from ~/.config/sandbox-agent/agent.env
- sandbox-cors-proxy on :2468 (Node.js)
- No cloudflared - nginx handles SSL termination
Garbage collection: 3-day retention (vs 14-day on darwin).
Disk guards: min-free 100MB, max-free 1GB.
Journald: 1-week retention.
8. DEPLOY COMMANDS
-------------------
Darwin (local):
just switch
Netty (from mac):
just switch-netty
First-time netty install:
nix run github:nix-community/nixos-anywhere -- \
--flake .#netty --target-host netty --build-on-remote
9. ROLLBACK
-------------
Each phase is a separate git commit.
NixOS rollback:
ssh netty "nixos-rebuild switch --rollback"
Or boot previous generation from GRUB (3 kept).
Darwin rollback:
git revert <commit> && just switch
Home Manager rollback:
home-manager generations # list
home-manager switch --flake .#<host> # after git revert
10. FEATURE FLAGS REFERENCE
-----------------------------
| Feature | darwin | netty |
|---------|--------|-------|
| rust | yes | yes |
| go | yes | yes |
| node | yes | yes |
| python | yes | yes |
| aws | yes | yes |
| claude | yes | yes |
| docker | yes | no |
| tex | yes | no |
Set in lib/hosts.nix, consumed by home/xdg.nix and lib/package-sets.nix.