small string to setup an ubuntu host with some personal settings
0
ubuntu-setup.sh
650 lines 17 kB view raw
1#!/usr/bin/env bash 2set -euo pipefail 3 4# Install nushell + shell integrations (carapace, atuin, starship, zoxide, direnv) 5# Designed for Ubuntu hosts 6 7echo "=> Updating apt and installing prerequisites..." 8sudo apt update && sudo apt install -y curl git unzip 9 10get_latest_version() { 11 curl -sL "https://api.github.com/repos/$1/releases/latest" \ 12 | grep '"tag_name"' \ 13 | head -1 \ 14 | sed -E 's/.*"([^"]+)".*/\1/' 15} 16 17ARCH=$(uname -m) 18 19# --- nushell --- 20if ! command -v nu &>/dev/null; then 21 echo "=> Installing nushell..." 22 case "$ARCH" in 23 x86_64) NU_ARCH="x86_64" ;; 24 aarch64) NU_ARCH="aarch64" ;; 25 *) echo "Unsupported arch: $ARCH"; exit 1 ;; 26 esac 27 NU_TAG=$(get_latest_version "nushell/nushell") 28 NU_VERSION=${NU_TAG#v} 29 NU_URL="https://github.com/nushell/nushell/releases/download/${NU_TAG}/nu-${NU_VERSION}-${NU_ARCH}-unknown-linux-gnu.tar.gz" 30 TMPDIR=$(mktemp -d) 31 echo " Downloading nushell ${NU_VERSION}..." 32 curl -sL "$NU_URL" | tar xz -C "$TMPDIR" 33 sudo cp "${TMPDIR}/nu-${NU_VERSION}-${NU_ARCH}-unknown-linux-gnu/nu" /usr/local/bin/ 34 sudo cp "${TMPDIR}/nu-${NU_VERSION}-${NU_ARCH}-unknown-linux-gnu/plugin_"* /usr/local/bin/ 2>/dev/null || true 35 rm -rf "$TMPDIR" 36 echo " Installed nushell $(nu --version)" 37else 38 echo "=> nushell already installed ($(nu --version))" 39fi 40 41# --- carapace --- 42if ! command -v carapace &>/dev/null; then 43 echo "=> Installing carapace..." 44 case "$ARCH" in 45 x86_64) CAP_ARCH="amd64" ;; 46 aarch64) CAP_ARCH="arm64" ;; 47 esac 48 CAP_TAG=$(get_latest_version "carapace-sh/carapace-bin") 49 CAP_VERSION=${CAP_TAG#v} 50 CAP_URL="https://github.com/carapace-sh/carapace-bin/releases/download/${CAP_TAG}/carapace-bin_${CAP_VERSION}_linux_${CAP_ARCH}.tar.gz" 51 TMPDIR=$(mktemp -d) 52 echo " Downloading carapace ${CAP_VERSION}..." 53 curl -sL "$CAP_URL" | tar xz -C "$TMPDIR" 54 sudo cp "${TMPDIR}/carapace" /usr/local/bin/ 55 rm -rf "$TMPDIR" 56 echo " Installed carapace ${CAP_VERSION}" 57else 58 echo "=> carapace already installed" 59fi 60 61# --- atuin --- 62if ! command -v atuin &>/dev/null; then 63 echo "=> Installing atuin..." 64 curl --proto '=https' --tlsv1.2 -sSf https://setup.atuin.sh | sh 65else 66 echo "=> atuin already installed ($(atuin --version))" 67fi 68 69# --- starship --- 70if ! command -v starship &>/dev/null; then 71 echo "=> Installing starship..." 72 curl -sS https://starship.rs/install.sh | sh -s -- -y 73else 74 echo "=> starship already installed ($(starship --version))" 75fi 76 77# --- zoxide --- 78if ! command -v zoxide &>/dev/null; then 79 echo "=> Installing zoxide..." 80 curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh 81else 82 echo "=> zoxide already installed" 83fi 84 85# --- direnv --- 86if ! command -v direnv &>/dev/null; then 87 echo "=> Installing direnv..." 88 sudo apt install -y direnv 89else 90 echo "=> direnv already installed" 91fi 92 93# --- fastfetch (optional, for pokefetch) --- 94if ! command -v fastfetch &>/dev/null; then 95 echo "=> Installing fastfetch..." 96 sudo apt install -y fastfetch 2>/dev/null || { 97 case "$ARCH" in 98 x86_64) FF_ARCH="amd64" ;; 99 aarch64) FF_ARCH="aarch64" ;; 100 esac 101 FF_TAG=$(get_latest_version "fastfetch-cli/fastfetch") 102 FF_VERSION=${FF_TAG#v} 103 TMPDIR=$(mktemp -d) 104 echo " Downloading fastfetch ${FF_VERSION}..." 105 curl -sL "https://github.com/fastfetch-cli/fastfetch/releases/download/${FF_TAG}/fastfetch-linux-${FF_ARCH}.deb" -o "${TMPDIR}/fastfetch.deb" 106 sudo dpkg -i "${TMPDIR}/fastfetch.deb" 107 rm -rf "$TMPDIR" 108 echo " Installed fastfetch ${FF_VERSION}" 109 } 110else 111 echo "=> fastfetch already installed" 112fi 113 114# --- pokeget-rs (optional, for pokefetch) --- 115if ! command -v pokeget &>/dev/null; then 116 echo "=> Installing pokeget-rs..." 117 case "$ARCH" in 118 x86_64) PG_ARCH="x86_64" ;; 119 aarch64) PG_ARCH="aarch64" ;; 120 esac 121 PG_TAG=$(get_latest_version "talwat/pokeget-rs") 122 PG_VERSION=${PG_TAG#v} 123 TMPDIR=$(mktemp -d) 124 echo " Downloading pokeget ${PG_VERSION}..." 125 curl -sL "https://github.com/talwat/pokeget-rs/releases/download/${PG_TAG}/pokeget-Linux-${PG_ARCH}.tar.gz" | tar xz -C "$TMPDIR" 126 sudo cp "${TMPDIR}/pokeget" /usr/local/bin/ 2>/dev/null || { echo " Could not find pokeget binary in archive, skipping"; } 127 rm -rf "$TMPDIR" 128 echo " Installed pokeget ${PG_VERSION}" 129else 130 echo "=> pokeget already installed" 131fi 132 133# --- nushell config --- 134echo "=> Setting up nushell config..." 135 136read -r -p " Reset all config files to defaults? [y/N] " RESET </dev/tty 137RESET=${RESET:-N} 138 139CONFIG_FILES=( 140 ~/.config/nushell/env.nu 141 ~/.config/nushell/config.nu 142 ~/.config/nushell/direnv.nu 143 ~/.config/nushell/vendor/zoxide.nu 144 ~/.config/atuin/config.toml 145 ~/.config/fastfetch/config.jsonc 146 ~/.config/starship.toml 147 ~/.cache/carapace/init.nu 148 ~/.local/share/atuin/init.nu 149) 150 151if [[ "${RESET,,}" == "y" ]]; then 152 echo " Removing existing config files..." 153 for f in "${CONFIG_FILES[@]}"; do 154 rm -f "$f" 155 done 156fi 157 158mkdir -p ~/.config/nushell ~/.config/nushell/vendor ~/.config/atuin ~/.cache/carapace ~/.local/share/atuin 159 160tee ~/.config/nushell/direnv.nu > /dev/null << 'DIRENV_EOF' 161$env.config = { 162 hooks: { 163 pre_prompt: [{ || 164 if (which direnv | is-empty) { 165 return 166 } 167 168 direnv export json | from json | default {} | load-env 169 if 'ENV_CONVERSIONS' in $env and 'PATH' in $env.ENV_CONVERSIONS { 170 $env.PATH = do $env.ENV_CONVERSIONS.PATH.from_string $env.PATH 171 } 172 }] 173 } 174} 175DIRENV_EOF 176 177zoxide init nushell > ~/.config/nushell/vendor/zoxide.nu 178 179tee ~/.config/nushell/config.nu > /dev/null << 'NU_CONFIG_EOF' 180use std "path add" 181 182$env.config.show_banner = false 183 184path add "~/bin" 185path add "~/.local/bin" 186path add "~/.atuin/bin" 187 188source ./direnv.nu 189 190# carapace completions 191source ~/.cache/carapace/init.nu 192 193# atuin 194source ~/.local/share/atuin/init.nu 195 196# starship prompt 197$env.STARSHIP_SHELL = "nu" 198$env.PROMPT_COMMAND = { || starship prompt --cmd-duration $env.CMD_DURATION_MS } 199$env.PROMPT_COMMAND_RIGHT = "" 200$env.PROMPT_INDICATOR = "" 201$env.PROMPT_INDICATOR_VI_INSERT = "" 202$env.PROMPT_INDICATOR_VI_NORMAL = "" 203 204# zoxide 205source ./vendor/zoxide.nu 206 207 208def cl [] { 209 clear 210 if $env.TMUX? == null { 211 pokefetch 212 } 213} 214 215def pokefetch [] { 216 let FETCHER_CMD = "fastfetch --logo none" 217 let EXTRA_PADDING_H = 2 218 let EXTRA_PADDING_W = 2 219 let WIDTH = 38 220 221 let pokemon_name = (hostname) 222 223 let sprite = (pokeget $pokemon_name --hide-name | complete | get stdout) 224 let fetcher_height = (bash -c $FETCHER_CMD | lines | length) 225 let sprite_height = ($sprite | lines | length) 226 227 let pad_top = (($fetcher_height - $sprite_height) / 2 + $EXTRA_PADDING_H) 228 let pad_top = (if $pad_top < 0 { 0 } else { $pad_top }) 229 230 let sprite_width = ($sprite | lines | each { |line| 231 $line | ansi strip | split chars | length 232 } | math max) 233 234 let pad_horizontal = (($WIDTH - $sprite_width) / 2 + $EXTRA_PADDING_W) 235 let pad_horizontal = (if $pad_horizontal < 0 { 0 } else { $pad_horizontal }) 236 let pad_horizontal = ($pad_horizontal | math floor) 237 let pad_top = ($pad_top | math floor) 238 239 $sprite | fastfetch --file-raw - --logo-padding $pad_horizontal --logo-padding-top $pad_top 240} 241 242def ghosttify [user: string, host: string] { 243 let command = ["infocmp -x xterm-ghostty | ssh ", $user, "@", $host, " -- tic -x -"] 244 bash -c ($command | str join) 245} 246 247def --env --wrapped zc [...args: string] { 248 if $args == null or $args == [] { 249 cd ~ 250 } else { 251 __zoxide_z ...$args 252 } 253} 254 255def --env --wrapped zic [...args: string] { 256 if $args == null or $args == [] { 257 __zoxide_zi 258 } else { 259 __zoxide_zi ...$args 260 } 261} 262 263if $env.TERM? == "xterm-ghostty" { 264 pokefetch 265} 266NU_CONFIG_EOF 267 268tee ~/.config/nushell/env.nu > /dev/null << 'NU_ENV_EOF' 269# env.nu - environment variables loaded before config.nu 270$env.ENV_CONVERSIONS = { 271 "PATH": { 272 from_string: { |s| $s | split row (char esep) | path expand --no-symlink } 273 to_string: { |v| $v | path expand --no-symlink | str join (char esep) } 274 } 275} 276NU_ENV_EOF 277 278# --- fastfetch config --- 279mkdir -p ~/.config/fastfetch 280tee ~/.config/fastfetch/config.jsonc > /dev/null << 'FASTFETCH_EOF' 281{ 282 "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", 283 "display": { 284 "separator": " " 285 }, 286 "modules": [ 287 { 288 "key": "\n", 289 "type": "custom" 290 }, 291 { 292 "key": "{#39}╭───────────╮", 293 "type": "custom" 294 }, 295 { 296 "key": "{#39}│ {#31} user {#39}│", 297 "type": "title", 298 "format": "{1}" 299 }, 300 { 301 "key": "{#39}│ {#32}󰇅 host {#39}│", 302 "type": "title", 303 "format": "{2}" 304 }, 305 { 306 "key": "{#39}│ {#34}󰟾 distro {#39}│", 307 "type": "os", 308 "format": "{3}" 309 }, 310 { 311 "key": "{#39}│ {#35} kernel {#39}│", 312 "type": "kernel", 313 "format": "{1} {2}" 314 }, 315 { 316 "key": "{#39}│ {#33}󰅐 uptime {#39}│", 317 "type": "uptime", 318 "format": "{2} Hrs {3} Mins" 319 }, 320 { 321 "key": "{#39}│ {#36}󰇄 desktop {#39}│", 322 "type": "de" 323 }, 324 { 325 "key": "{#39}│ {#31} term {#39}│", 326 "type": "terminal", 327 "format": "{5}" 328 }, 329 { 330 "key": "{#39}│ {#32} shell {#39}│", 331 "type": "shell", 332 "format": "{6}" 333 }, 334 { 335 "key": "{#39}│ {#34}󰉉 disk {#39}│", 336 "type": "disk", 337 "folders": "/", 338 "format": "{1} / {2} ({3})" 339 }, 340 { 341 "key": "{#39}│ {#35} memory {#39}│", 342 "type": "memory", 343 "format": "{1} / {2} ({3})" 344 }, 345 { 346 "key": "{#39}│ {#36}󰩟 network {#39}│", 347 "type": "localip", 348 "format": "{1}" 349 }, 350 { 351 "key": "{#39}├───────────┤", 352 "type": "custom" 353 }, 354 { 355 "key": "{#39}│ {#39} colors {#39}│ 󰮯 ", 356 "type": "colors", 357 "symbol": "circle" 358 }, 359 { 360 "key": "{#39}╰───────────╯", 361 "type": "custom" 362 } 363 ] 364} 365FASTFETCH_EOF 366 367# --- atuin config --- 368tee ~/.config/atuin/config.toml > /dev/null << 'ATUIN_EOF' 369search_mode = "fuzzy" 370filter_mode = "host" 371workspaces = true 372style = "compact" 373inline_height = 10 374invert = false 375show_preview = true 376show_help = false 377show_tabs = false 378enter_accept = true 379 380[stats] 381common_subcommands = [ 382 "apt", 383 "cargo", 384 "composer", 385 "dnf", 386 "docker", 387 "git", 388 "go", 389 "ip", 390 "kubectl", 391 "nix", 392 "nmcli", 393 "npm", 394 "pecl", 395 "pnpm", 396 "podman", 397 "port", 398 "systemctl", 399 "tmux", 400 "yarn", 401 "bun", 402 "hx", 403] 404common_prefix = ["sudo"] 405ignored_commands = ["cd", "ls", "l", "z"] 406 407[sync] 408records = true 409 410[theme] 411name = "oscura-midnight" 412ATUIN_EOF 413 414# --- starship config --- 415tee ~/.config/starship.toml > /dev/null << 'STARSHIP_EOF' 416format = "($nix_shell$container$git_metrics)$cmd_duration$hostname$localip$shlvl$shell$env_var$sudo$character\n" 417palette = "poimandres" 418right_format = "$singularity$kubernetes$directory$vcsh$fossil_branch$git_branch$git_commit$git_state$git_status$hg_branch$pijul_channel$docker_context$package$c$cpp$cmake$cobol$daml$dart$deno$dotnet$elixir$elm$erlang$fennel$fortran$golang$guix_shell$haskell$haxe$helm$java$julia$kotlin$gradle$lua$nim$nodejs$ocaml$opa$perl$php$pulumi$purescript$python$raku$rlang$red$ruby$rust$scala$solidity$swift$terraform$vlang$vagrant$xmake$zig$buf$conda$pixi$meson$spack$memory_usage$aws$gcloud$openstack$azure$crystal$custom$status$os$battery$time\n" 419 420[c] 421disabled = false 422format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 423style = "bg:overlay fg:pine" 424symbol = " " 425 426[directory] 427format = "[](fg:overlayd)[ $path ]($style)[](fg:overlayd) " 428style = "bg:overlayd fg:pine" 429truncate_to_repo = false 430truncation_length = 5 431truncation_symbol = "" 432 433[directory.substitutions] 434Documents = "󰈙" 435Downloads = " " 436Music = " " 437Pictures = " " 438nixos = " " 439 440[elixir] 441disabled = false 442format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 443style = "bg:overlay fg:pine" 444symbol = " " 445 446[elm] 447disabled = false 448format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 449style = "bg:overlay fg:pine" 450symbol = " " 451 452[fill] 453style = "fg:overlay" 454symbol = " " 455 456[git_branch] 457format = "[](fg:overlaydd)[ $symbol $branch ]($style)[](fg:overlaydd) " 458style = "bg:overlaydd fg:foam" 459symbol = "" 460 461[git_status] 462ahead = "[⇡(${count})](bg:overlaydd fg:foam)" 463behind = "[⇣(${count})](bg:overlaydd fg:rose)" 464deleted = "[✘($count)](style)" 465disabled = false 466diverged = "⇕[[](bg:overlaydd fg:iris)[⇡(${ahead_count})](bg:overlaydd fg:foam)[⇣(${behind_count})](bg:overlaydd fg:rose)[]](bg:overlaydd fg:iris)" 467format = "[](fg:overlaydd)([$all_status$ahead_behind]($style))[](fg:overlaydd) " 468modified = "[!($count)](bg:overlaydd fg:gold)" 469renamed = "[»($count)](bg:overlaydd fg:iris)" 470staged = "[++($count)](bg:overlaydd fg:gold)" 471stashed = "[($count)](bg:overlaydd fg:gold)" 472style = "bg:overlaydd fg:love" 473untracked = "[?($count)](bg:overlaydd fg:gold)" 474up_to_date = "[ ✓ ](bg:overlaydd fg:iris)" 475 476[golang] 477disabled = false 478format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 479style = "bg:overlay fg:pine" 480symbol = " " 481 482[haskell] 483disabled = false 484format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 485style = "bg:overlay fg:pine" 486symbol = " " 487 488[hostname] 489disabled = false 490format = "[](fg:overlayd)[  $hostname ]($style)[](fg:overlayd) " 491ssh_only = false 492style = "bg:overlayd fg:iris" 493 494[java] 495disabled = false 496format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 497style = "bg:overlay fg:pine" 498symbol = " " 499 500[julia] 501disabled = false 502format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 503style = "bg:overlay fg:pine" 504symbol = " " 505 506[nim] 507disabled = false 508format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 509style = "bg:overlay fg:pine" 510symbol = " " 511 512[nodejs] 513disabled = false 514format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 515style = "bg:overlay fg:pine" 516symbol = " " 517 518[palettes.base16] 519base00 = "#191724" 520base01 = "#1f1d2e" 521base02 = "#26233a" 522base03 = "#6e6a86" 523base04 = "#908caa" 524base05 = "#e0def4" 525base06 = "#e0def4" 526base07 = "#524f67" 527base08 = "#eb6f92" 528base09 = "#f6c177" 529base0A = "#ebbcba" 530base0B = "#31748f" 531base0C = "#9ccfd8" 532base0D = "#c4a7e7" 533base0E = "#f6c177" 534base0F = "#524f67" 535base10 = "#191724" 536base11 = "#191724" 537base12 = "#eb6f92" 538base13 = "#ebbcba" 539base14 = "#31748f" 540base15 = "#9ccfd8" 541base16 = "#c4a7e7" 542base17 = "#f6c177" 543black = "#191724" 544blue = "#c4a7e7" 545bright-black = "#6e6a86" 546bright-blue = "#c4a7e7" 547bright-cyan = "#9ccfd8" 548bright-green = "#31748f" 549bright-magenta = "#f6c177" 550bright-purple = "#f6c177" 551bright-red = "#eb6f92" 552bright-white = "#524f67" 553bright-yellow = "#ebbcba" 554brown = "#524f67" 555cyan = "#9ccfd8" 556green = "#31748f" 557magenta = "#f6c177" 558orange = "#f6c177" 559purple = "#f6c177" 560red = "#eb6f92" 561white = "#e0def4" 562yellow = "#ebbcba" 563 564[palettes.oscura-midnight] 565foam = "#54c0a3" 566gold = "#f9b98c" 567iris = "#e6e7a3" 568love = "#d84f68" 569overlay = "#232323" 570overlayd = "#161616" 571overlaydd = "#0b0b0f" 572pine = "#4ebe96" 573rose = "#e6e7a3" 574 575[palettes.poimandres] 576foam = "#54c0a3" 577gold = "#add7ff" 578iris = "#ffffff" 579love = "#d0679d" 580overlay = "#252b37" 581overlayd = "#171922" 582overlaydd = "#1b1e28" 583pine = "#4ebe96" 584rose = "#5de4c7" 585 586[python] 587disabled = false 588format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 589style = "bg:overlay fg:pine" 590symbol = " " 591 592[rust] 593disabled = false 594format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 595style = "bg:overlay fg:pine" 596symbol = " " 597 598[scala] 599disabled = false 600format = "[](fg:overlay)[$symbol$version]($style)[](fg:overlay) " 601style = "bg:overlay fg:pine" 602symbol = " " 603 604[time] 605disabled = false 606format = "[](fg:overlay)[ $time ]($style)[](fg:overlay)" 607style = "bg:overlay fg:rose" 608time_format = "%I:%M%P" 609use_12hr = true 610 611[username] 612disabled = false 613format = "[](fg:overlay)[ 󰧱 $user ]($style)[](fg:overlay) " 614show_always = true 615style_root = "bg:overlay fg:iris" 616style_user = "bg:overlay fg:iris" 617STARSHIP_EOF 618 619# --- carapace init --- 620echo "=> Initializing carapace..." 621carapace _carapace nushell | sed 's|"/homeless-shelter|$"($env.HOME)|g' > ~/.cache/carapace/init.nu 622 623# --- atuin init --- 624echo "=> Initializing atuin..." 625atuin init nu > ~/.local/share/atuin/init.nu 626 627# --- disable motd --- 628if [ -f ~/.hushlogin ]; then 629 echo "=> motd already disabled" 630else 631 echo "=> Disabling motd..." 632 touch ~/.hushlogin 633fi 634 635# --- set nushell as default shell --- 636CURRENT_SHELL=$(getent passwd "$USER" | cut -d: -f7) 637if [[ "$CURRENT_SHELL" != *"/nu"* ]]; then 638 echo "=> Setting nushell as default shell..." 639 NU_PATH=$(which nu) 640 if ! grep -q "$NU_PATH" /etc/shells 2>/dev/null; then 641 echo "$NU_PATH" | sudo tee -a /etc/shells 642 fi 643 sudo chsh -s "$NU_PATH" "$USER" 644 echo " Default shell changed to nushell (takes effect on next login)" 645else 646 echo "=> nushell is already the default shell" 647fi 648 649echo "" 650echo "=> Done! Log out and back in (or run 'nu') to start using nushell."