Kieran's opinionated (and probably slightly dumb) nix config
1{ 2 lib, 3 pkgs, 4 config, 5 inputs, 6 ... 7}: 8{ 9 options.atelier.shell.enable = lib.mkEnableOption "Custom shell config"; 10 config = lib.mkIf config.atelier.shell.enable { 11 programs.oh-my-posh = { 12 enable = true; 13 enableZshIntegration = true; 14 settings = { 15 upgrade = { 16 notice = false; 17 interval = "2w"; 18 auto = false; 19 }; 20 version = 2; 21 final_space = true; 22 console_title_template = "{{ .Shell }} in {{ .Folder }}"; 23 blocks = [ 24 { 25 type = "prompt"; 26 alignment = "left"; 27 newline = true; 28 segments = [ 29 { 30 type = "session"; 31 background = "transparent"; 32 foreground = "yellow"; 33 template = "{{ if .SSHSession }}{{.HostName}} {{ end }}"; 34 } 35 { 36 type = "path"; 37 style = "plain"; 38 background = "transparent"; 39 foreground = "blue"; 40 template = "{{ .Path }} "; 41 properties = { 42 style = "full"; 43 }; 44 } 45 { 46 type = "git"; 47 style = "plain"; 48 foreground = "p:grey"; 49 background = "transparent"; 50 template = "{{if not .Detached}}{{ .HEAD }}{{else}}@{{ printf \"%.7s\" .Commit.Sha }}{{end}}{{ if .Staging.Changed }} ({{ .Staging.String }}){{ end }}{{ if .Working.Changed }}*{{ end }} <cyan>{{ if .BranchStatus }}{{ .BranchStatus }}{{ end }}</>"; 51 properties = { 52 branch_icon = ""; 53 branch_identical_icon = ""; 54 branch_gone_icon = ""; 55 branch_ahead_icon = ""; 56 branch_behind_icon = ""; 57 commit_icon = "@"; 58 fetch_status = true; 59 }; 60 } 61 ]; 62 } 63 { 64 type = "rprompt"; 65 overflow = "hidden"; 66 segments = [ 67 { 68 type = "executiontime"; 69 style = "plain"; 70 foreground = "yellow"; 71 background = "transparent"; 72 template = "{{ .FormattedMs }}"; 73 properties = { 74 threshold = 3000; 75 }; 76 } 77 { 78 type = "nix-shell"; 79 style = "plain"; 80 foreground = "red"; 81 background = "transparent"; 82 template = ''{{if ne .Type "unknown" }} {{ .Type }}{{ end }}''; 83 } 84 ]; 85 } 86 { 87 type = "prompt"; 88 alignment = "left"; 89 newline = true; 90 segments = [ 91 { 92 type = "text"; 93 style = "plain"; 94 foreground_templates = [ 95 "{{if gt .Code 0}}red{{end}}" 96 "{{if eq .Code 0}}magenta{{end}}" 97 ]; 98 background = "transparent"; 99 template = ""; 100 } 101 ]; 102 } 103 ]; 104 transient_prompt = { 105 foreground_templates = [ 106 "{{if gt .Code 0}}red{{end}}" 107 "{{if eq .Code 0}}magenta{{end}}" 108 ]; 109 background = "transparent"; 110 template = " "; 111 }; 112 secondary_prompt = { 113 foreground = "p:gray"; 114 background = "transparent"; 115 template = " "; 116 }; 117 palette = { 118 grey = "#6c6c6c"; 119 }; 120 }; 121 }; 122 123 programs.zsh = { 124 enable = true; 125 enableCompletion = true; 126 syntaxHighlighting.enable = true; 127 128 shellAliases = { 129 cat = "bat"; 130 ls = "eza"; 131 ll = "eza -l"; 132 la = "eza -la"; 133 gc = "git commit"; 134 gp = "git push"; 135 rr = "rm -Rf"; 136 ghrpc = "gh repo create -c"; 137 goops = "git commit --amend --no-edit && git push --force-with-lease"; 138 vi = "nvim"; 139 vim = "nvim"; 140 }; 141 initContent = '' 142 #ssh auto reconnect 143 assh() { 144 local host=$1 145 local port=$2 146 while true; do 147 ssh -p $port -o "BatchMode yes" $host || sleep 1 148 done 149 } 150 # hackatime summary 151 summary() { 152 local user_id=$1 153 curl -X 'GET' \ 154 "https://waka.hackclub.com/api/summary?user=''${user_id}&interval=month" \ 155 -H 'accept: application/json' \ 156 -H 'Authorization: Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608' | jq '. + { 157 "total_categories_sum": (.categories | map(.total) | add), 158 "total_categories_human_readable": ( 159 (.categories | map(.total) | add) as $total_seconds | 160 "\($total_seconds / 3600 | floor)h \(($total_seconds % 3600) / 60 | floor)m \($total_seconds % 60)s" 161 ), 162 "projectsKeys": ( 163 .projects | sort_by(-.total) | map(.key) 164 ) 165 }' 166 } 167 168 tangled() { 169 # Configuration variables - set these to your defaults 170 local default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul" 171 local default_github_username="taciturnaxolotl" 172 local default_knot_host="knot.dunkirk.sh" 173 local extracted_github_username="" 174 175 # Check if current directory is a git repository 176 if ! git rev-parse --is-inside-work-tree &>/dev/null; then 177 echo "Not a git repository" 178 return 1 179 fi 180 181 # Get the repository name from the current directory 182 local repo_name=$(basename "$(git rev-parse --show-toplevel)") 183 184 # Check if origin remote exists and points to knot 185 local origin_url=$(git remote get-url origin 2>/dev/null) 186 local origin_knot=false 187 188 if [[ -n "$origin_url" ]]; then 189 # Try to extract GitHub username if origin is a GitHub URL 190 if [[ "$origin_url" == *"github.com"* ]]; then 191 extracted_github_username=$(echo "$origin_url" | sed -E 's/.*github\.com[:/]([^/]+)\/.*$/\1/') 192 # Override the default username with the extracted one 193 default_github_username=$extracted_github_username 194 fi 195 196 if [[ "$origin_url" == *"$default_knot_host"* || "$origin_url" == *"knot.dunkirk.sh"* ]]; then 197 origin_knot=true 198 echo " Origin remote exists and points to knot" 199 else 200 echo " Origin remote exists but doesn't point to knot" 201 fi 202 else 203 echo " Origin remote doesn't exist" 204 fi 205 206 # Check if github remote exists 207 local github_exists=false 208 if git remote get-url github &>/dev/null; then 209 github_exists=true 210 echo " GitHub remote exists" 211 else 212 echo " GitHub remote doesn't exist" 213 fi 214 215 # Fix remotes if needed 216 if [[ "$origin_knot" = false || "$github_exists" = false ]]; then 217 # Prompt for PLC identifier if needed 218 local plc_id="" 219 local should_fix_origin=false 220 221 if [[ "$origin_knot" = false ]]; then 222 if [[ -n "$origin_url" ]]; then 223 echo -n "Migrate origin from $origin_url to knot.dunkirk.sh? [Y/n]: " 224 read fix_input 225 if [[ -z "$fix_input" || "$fix_input" =~ ^[Yy]$ ]]; then 226 should_fix_origin=true 227 fi 228 else 229 should_fix_origin=true 230 fi 231 232 if [[ "$should_fix_origin" = true ]]; then 233 echo -n "Enter your PLC identifier [default: $default_plc_id]: " 234 read plc_input 235 plc_id=''${plc_input:-$default_plc_id} 236 fi 237 fi 238 239 # Prompt for GitHub username with default from origin if available 240 local github_username="" 241 if [[ "$github_exists" = false ]]; then 242 echo -n "Enter your GitHub username [default: $default_github_username]: " 243 read github_input 244 github_username=''${github_input:-$default_github_username} 245 fi 246 247 # Set up origin remote if needed 248 if [[ "$should_fix_origin" = true && -n "$plc_id" ]]; then 249 if git remote get-url origin &>/dev/null; then 250 git remote remove origin 251 fi 252 git remote add origin "git@$default_knot_host:''${plc_id}/''${repo_name}" 253 echo " Set up origin remote: git@$default_knot_host:''${plc_id}/''${repo_name}" 254 fi 255 256 # Set up GitHub remote if needed 257 if [[ "$github_exists" = false && -n "$github_username" ]]; then 258 git remote add github "git@github.com:''${github_username}/''${repo_name}.git" 259 echo " Set up GitHub remote: git@github.com:''${github_username}/''${repo_name}.git" 260 fi 261 else 262 echo "Remotes are correctly configured" 263 fi 264 } 265 266 # Post AtProto status updates 267 now() { 268 local message="" 269 local prompt_message=true 270 local account1_name="" 271 local account2_name="" 272 local account1_jwt="" 273 local account2_jwt="" 274 275 # Load account information from agenix secrets 276 if [[ -f "/run/agenix/bluesky" ]]; then 277 source "/run/agenix/bluesky" 278 else 279 echo "Error: Bluesky credentials file not found at /run/agenix/bluesky" 280 return 1 281 fi 282 283 # Parse arguments 284 while [[ $# -gt 0 ]]; do 285 case "$1" in 286 -m|--message) 287 message="$2" 288 prompt_message=false 289 shift 2 290 ;; 291 *) 292 echo "Usage: now [-m|--message \"your message\"]" 293 return 1 294 ;; 295 esac 296 done 297 298 # Prompt for message if none provided 299 if [[ "$prompt_message" = true ]]; then 300 echo -n "$ACCOUNT1 is: " 301 read message 302 303 if [[ -z "$message" ]]; then 304 echo "No message provided. Aborting." 305 return 1 306 fi 307 fi 308 309 # Generate JWT for ACCOUNT1 310 local account1_response=$(curl -s -X POST \ 311 -H "Content-Type: application/json" \ 312 -d '{ 313 "identifier": "'$ACCOUNT1'", 314 "password": "'$ACCOUNT1_PASSWORD'" 315 }' \ 316 "https://bsky.social/xrpc/com.atproto.server.createSession") 317 318 account1_jwt=$(echo "$account1_response" | jq -r '.accessJwt') 319 320 if [[ -z "$account1_jwt" || "$account1_jwt" == "null" ]]; then 321 echo "Failed to authenticate account $ACCOUNT1" 322 echo "Response: $account1_response" 323 return 1 324 fi 325 326 # Generate JWT for ACCOUNT2 327 local account2_response=$(curl -s -X POST \ 328 -H "Content-Type: application/json" \ 329 -d '{ 330 "identifier": "'$ACCOUNT2'", 331 "password": "'$ACCOUNT2_PASSWORD'" 332 }' \ 333 "https://bsky.social/xrpc/com.atproto.server.createSession") 334 335 account2_jwt=$(echo "$account2_response" | jq -r '.accessJwt') 336 337 if [[ -z "$account2_jwt" || "$account2_jwt" == "null" ]]; then 338 echo "Failed to authenticate account $ACCOUNT2" 339 echo "Response: $account2_response" 340 return 1 341 fi 342 343 # Post to ACCOUNT1 as a.status.updates 344 local account1_post_response=$(curl -s -X POST \ 345 -H "Content-Type: application/json" \ 346 -H "Authorization: Bearer $account1_jwt" \ 347 -d '{ 348 "collection": "a.status.update", 349 "repo": "'$ACCOUNT1'", 350 "record": { 351 "$type": "a.status.update", 352 "text": "'"$message"'", 353 "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 354 } 355 }' \ 356 "https://bsky.social/xrpc/com.atproto.repo.createRecord") 357 358 if [[ $(echo "$account1_post_response" | jq -r 'has("error")') == "true" ]]; then 359 echo "Error posting to $ACCOUNT1:" 360 echo "$account1_post_response" | jq 361 return 1 362 fi 363 364 # Post to ACCOUNT2 as normal post 365 local account2_post_response=$(curl -s -X POST \ 366 -H "Content-Type: application/json" \ 367 -H "Authorization: Bearer $account2_jwt" \ 368 -d '{ 369 "collection": "app.bsky.feed.post", 370 "repo": "'$ACCOUNT2'", 371 "record": { 372 "$type": "app.bsky.feed.post", 373 "text": "'"$message"'", 374 "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 375 } 376 }' \ 377 "https://bsky.social/xrpc/com.atproto.repo.createRecord") 378 379 if [[ $(echo "$account2_post_response" | jq -r 'has("error")') == "true" ]]; then 380 echo "Error posting to $ACCOUNT2:" 381 echo "$account2_post_response" | jq 382 return 1 383 fi 384 385 echo "done" 386 } 387 388 ghostty_setup() { 389 local target="$1" 390 391 if [[ -z "$target" ]]; then 392 echo "Usage: ghostty_setup <user@host>" 393 return 1 394 fi 395 396 # Copy SSH key 397 echo "Copying SSH key to $target..." 398 ssh-copy-id "$target" || { echo "ssh-copy-id failed"; return 2; } 399 400 # Pipe infocmp output to tic on remote host 401 echo "Sending xterm-ghostty terminfo to $target..." 402 infocmp -x xterm-ghostty | ssh "$target" 'tic -x -' || { echo "Terminfo transfer failed"; return 3; } 403 404 echo "Done." 405 } 406 407 zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' 408 zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}" 409 zstyle ':completion:*' menu no 410 zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls --color $realpath' 411 zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath' 412 413 eval "$(terminal-wakatime init)" 414 ''; 415 history = { 416 size = 10000; 417 path = "${config.xdg.dataHome}/zsh/history"; 418 ignoreDups = true; 419 ignoreAllDups = true; 420 ignoreSpace = true; 421 expireDuplicatesFirst = true; 422 share = true; 423 extended = true; 424 append = true; 425 }; 426 427 oh-my-zsh = { 428 enable = true; 429 plugins = [ 430 "git" 431 "sudo" 432 "docker" 433 "git" 434 "command-not-found" 435 "colored-man-pages" 436 ]; 437 }; 438 439 plugins = [ 440 { 441 # will source zsh-autosuggestions.plugin.zsh 442 name = "zsh-autosuggestions"; 443 src = pkgs.fetchFromGitHub { 444 owner = "zsh-users"; 445 repo = "zsh-autosuggestions"; 446 rev = "v0.7.0"; 447 sha256 = "sha256-KLUYpUu4DHRumQZ3w59m9aTW6TBKMCXl2UcKi4uMd7w="; 448 }; 449 } 450 { 451 # will source zsh-sytax-highlighting 452 name = "zsh-sytax-highlighting"; 453 src = pkgs.fetchFromGitHub { 454 owner = "zsh-users"; 455 repo = "zsh-syntax-highlighting"; 456 rev = "0.8.0"; 457 sha256 = "sha256-iJdWopZwHpSyYl5/FQXEW7gl/SrKaYDEtTH9cGP7iPo="; 458 }; 459 } 460 { 461 # fzf tab completion 462 name = "fzf-tab"; 463 src = pkgs.fetchFromGitHub { 464 owner = "aloxaf"; 465 repo = "fzf-tab"; 466 rev = "v1.1.2"; 467 sha256 = "sha256-Qv8zAiMtrr67CbLRrFjGaPzFZcOiMVEFLg1Z+N6VMhg="; 468 }; 469 } 470 ]; 471 }; 472 473 programs.zoxide = { 474 enable = true; 475 enableZshIntegration = true; 476 }; 477 programs.fzf = { 478 enable = true; 479 enableZshIntegration = true; 480 colors = { 481 bg = lib.mkForce ""; 482 }; 483 }; 484 programs.atuin = { 485 enable = true; 486 settings = { 487 auto_sync = true; 488 sync_frequency = "5m"; 489 sync_address = "https://api.atuin.sh"; 490 search_mode = "fuzzy"; 491 update_check = false; 492 style = "auto"; 493 sync.records = true; 494 dotfiles.enabled = false; 495 }; 496 }; 497 programs.yazi = { 498 enable = true; 499 enableZshIntegration = true; 500 }; 501 502 home.packages = with pkgs; [ 503 pkgs.unstable.wakatime-cli 504 inputs.terminal-wakatime.packages.${pkgs.system}.default 505 unzip 506 dog 507 dust 508 wget 509 curl 510 jq 511 fd 512 eza 513 bat 514 ripgrep 515 ripgrep-all 516 neofetch 517 glow 518 ]; 519 520 atelier.shell.git.enable = lib.mkDefault true; 521 }; 522}