commit 180c5838ead82e42df0addf91d26a22b459d1733 Author: Julian Prester Date: Fri Jun 5 21:21:46 2026 +1000 Initial commit: linux-provision repo Distribution-agnostic provisioning script that sets up a new Linux machine (Detected via lib/distro.sh - supports Debian/Ubuntu/Pop and Fedora families). 13 stages covering: - System packages, external repos, toolchains (nvm, uv, Python) - Shell config (zsh, oh-my-zsh, p10k), git, SSH - Custom uv tools from ~40 git repos - Desktop config (keybindings, hotkeys, ghostty, fonts) - Docker, system tweaks, browser/app installs - Custom systemd user services (porridge, swayidle, mempi-sync, etc.) - API keys loaded from Bitwarden at shell startup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fdcf02 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Secrets — these should never be committed +.zshrc.local +.env +*.pem + +# OS files +.DS_Store +Thumbs.db + +# Editor +*.swp +*.swo +*~ + +# Python +__pycache__/ +*.py[cod] +.venv/ + +# Node +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..16fe567 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# Linux Machine Provisioning + +**Purpose:** Rapidly set up a new Fedora Linux machine with Julian's toolchain, +config, and customisations. Generated from audit of Pop!_OS "thinkpad" machine. + +## Supported Distributions + +Auto-detected — no manual config needed: + +| Family | Distros | Package Manager | +|--------|---------|-----------------| +| **Debian** | Ubuntu, Pop!_OS, Debian, Linux Mint | `apt` | +| **Fedora** | Fedora Workstation, RHEL, CentOS | `dnf` | + +Detection is in `lib/distro.sh`. It sets variables like `$PKG_INSTALL`, +`$PKG_UPDATE`, `$GRUB_UPDATE` etc. that all stage scripts use. + +## Quick Start + +```bash +# Clone this repo on the new machine +git clone git@github.com:julianprester/linux-provision.git ~/linux-provision +cd ~/linux-provision + +# Review and edit config/shell/zshrc.local with your API keys +cp config/shell/zshrc.local.example ~/.zshrc.local +# Edit ~/.zshrc.local with real API keys + +# Run the full provisioning (will prompt for sudo) +bash provision.sh --all + +# Or run individual stages +bash provision.sh --stage 03-toolchains +bash provision.sh --stage 06-uv-projects + +# Or source it for interactive use +source provision.sh --interactive +``` + +## Structure + +``` +linux-provision/ +├── README.md # This file +├── provision.sh # Master orchestrator — run with --all or --stage N +├── stages/ # Modular stage scripts, sourced by provision.sh +│ ├── 00-envcheck.sh # OS/sudo/environment checks +│ ├── 01-repos.sh # DNF repos (RPM Fusion, COPR, Microsoft, etc.) +│ ├── 02-packages.sh # System packages via DNF +│ ├── 03-toolchains.sh # nvm, Node, uv, Python +│ ├── 04-shell.sh # zsh, oh-my-zsh, powerlevel10k, configs +│ ├── 05-git.sh # Git config, SSH key setup +│ ├── 06-uv-projects.sh # Clone + install Julian's uv tools from ~/Development +│ ├── 07-scripts.sh # ~/.local/bin (bw, zoom, env, etc.) +│ ├── 08-systemd.sh # User systemd services (porridge, swayidle, etc.) +│ ├── 09-desktop.sh # Keybindings, hotkeys, ghostty, fonts +│ ├── 10-docker.sh # Docker CE setup +│ ├── 11-tweaks.sh # sysctl, kernel params, TLP/powertop, modprobe +│ └── 12-other-apps.sh # Chrome, Signal, Zotero +├── config/ # Dotfiles and config files (installed by stages) +│ ├── git/gitconfig +│ ├── shell/{zshrc,zshrc.local.example,p10k.zsh} +│ ├── scripts/{bw-load-ssh.sh,idle-battery-suspend.sh,zoom.sh,env.sh} +│ ├── systemd/{porridge.service,...} +│ ├── sysctl/99-custom.conf +│ └── modprobe/{system76-power.conf,pop-default-settings-dirty-frag.conf} +├── etc/ # System-level configs (copied to /etc) +└── TODO.md # Post-provisioning manual steps +``` + +## Stages Overview + +| # | Stage | What it does | +| --- | --- | --- | +| 00 | envcheck | Verify Fedora, sudo access, directory setup | +| 01 | repos | RPM Fusion free/nonfree, COPRs, Microsoft, Docker, Google, Signal, Tailscale | +| 02 | packages | Install all system packages (distro-mapped names) | +| 03 | toolchains | Install nvm + Node LTS, uv, Python | +| 04 | shell | Install zsh, oh-my-zsh, p10k, deploy .zshrc, .p10k.zsh | +| 05 | git | Deploy .gitconfig, generate SSH key | +| 06 | uv-projects | Clone all Julian's Python tool repos from GitHub, uv install | +| 07 | scripts | Deploy ~/.local/bin scripts | +| 08 | systemd | Deploy and enable user systemd services | +| 09 | desktop | Configure keybindings, hotkeys, ghostty, fonts | +| 10 | docker | Install Docker CE, add user to docker group | +| 11 | tweaks | sysctl, kernel cmdline, TLP/powertop, modprobe blacklists | +| 12 | other-apps | Google Chrome, Signal, Zotero | + +## Post-Install Manual Steps + +See `TODO.md` for things that can't be automated: restoring SSH keys +from Bitwarden, configuring Tailscale, importing GPG keys, etc. + +## Design Notes + +- **Distribution-agnostic** — detects Debian/Ubuntu/Pop vs Fedora via + `lib/distro.sh`. Package manager commands, repo config, and package + names adapt automatically. +- **Idempotent** — safe to run multiple times. Stages check for existing + installations before repeating work. +- **Secrets out of repo** — API keys live in `~/.zshrc.local` (gitignored). + The repo ships `zshrc.local.example` with placeholder values. +- **One stage per concern** — comment out stages you don't need in + `provision.sh` or pass `--stage` individually. +- **Hardware-specific quirks commented out** — AMD GPU kernel params, + WiFi workarounds, etc. are documented but disabled by default. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..ce372e8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,173 @@ +# Post-Provisioning TODO + +Things that can't be fully automated (require manual setup, credentials, +or hardware-specific configuration). + +## 1. SSH Keys & GitHub + +- [ ] **Load SSH keys from Bitwarden** or generate a new key: + ```bash + # Option A: Generate fresh key + ssh-keygen -t ed25519 -C "hi@julianprester.com" + + # Option B: Set up Bitwarden+SSH loading + bw login + bw unlock --raw > ~/.config/Bitwarden\ CLI/.session + chmod 600 ~/.config/Bitwarden\ CLI/.session + bw-load-ssh.sh + + # Option C: Copy keys from old machine + # scp old-machine:~/.ssh/id_ed25519* ~/.ssh/ + ``` +- [ ] **Add SSH public key to GitHub**: https://github.com/settings/keys +- [ ] Clone this repo and remaining repos: + ```bash + git clone git@github.com:julianprester/linux-provision.git + ``` + +## 2. Bitwarden & Environment Variables + +API keys are loaded directly in `.zshrc` via `bw` + `jq`. No separate script. + +- [ ] **Run `bw login`** to authenticate with Bitwarden +- [ ] **Unlock vault and save session:** + ```bash + bw unlock --raw > ~/.config/Bitwarden\ CLI/.session + chmod 600 ~/.config/Bitwarden\ CLI/.session + ``` +- [ ] **Create a Bitwarden item** named "Environment" (type: Secure Note) + with custom fields for each API key: + + | Field Name | Type | Example Value | + |---|---|---| + | `GROQ_API_KEY` | Hidden | `gsk_your_key` | + | `ANTHROPIC_API_KEY` | Hidden | `sk-ant-your-key` | + | `GOOGLE_API_KEY` | Hidden | `AIza_your_key` | + | `CANVAS_API_KEY` | Hidden | `3156~your_key` | + | `NC_PASSWORD` | Hidden | `your_nextcloud_password` | + | ... (22 vars total — see `config/shell/zshrc.local.example` for the full list) | + +- [ ] **Open a new shell** — `.zshrc` exports them automatically +- [ ] **Verify:** `echo $GROQ_API_KEY` (should show your key) + +If you prefer a plain file instead of Bitwarden: +- [ ] Edit `~/.zshrc.local` with your API keys (template in `config/shell/zshrc.local.example`) +- [ ] Uncomment the alternate `source ~/.zshrc.local` line in the deployed `.zshrc` + +If you prefer a plain file instead of Bitwarden: +- [ ] Edit `~/.zshrc.local` with your API keys (template in `config/shell/zshrc.local.example`) +- [ ] Uncomment the `source ~/.zshrc.local` line in your deployed `.zshrc` + +## 3. Tailscale + +- [ ] Authenticate Tailscale: + ```bash + sudo tailscale up + ``` +- [ ] Verify connection: `tailscale status` +- [ ] Note your Tailscale IP for services (Actual Budget, Nextcloud, etc.) + +## 4. Nextcloud + +- [ ] Install Nextcloud Desktop Client (Flatpak or RPM) +- [ ] Connect to `https://nc.julianprester.com` +- [ ] Select sync folders (especially `Nextcloud/3_bibliography/`) +- [ ] Update `PandocCiter.DefaultBib` in VS Code settings if bib path changes + +## 5. Actual Budget + +- [ ] Verify connection: `actualpy accounts` +- [ ] Update URL/password in `~/.config/actualpy/config.yaml` + +## 6. Docker & WinBoat + +- [ ] Log out and back in for docker group to take effect +- [ ] Pull WinBoat image: `docker pull ghcr.io/dockur/windows:5.14` +- [ ] Set up `~/.winboat/docker-compose.yml` (see reference in repo notes) +- [ ] Pull grobid: `docker pull grobid/grobid` +- [ ] Run grobid: `docker run -d -p 8070:8070 grobid/grobid` + +## 7. Zotero + +- [ ] Install Zotero (Flatpak or tarball from zotero.org) +- [ ] Sign in to sync library +- [ ] Install Zotero browser connector +- [ ] Set ZOTERO_KEY env var in `~/.zshrc.local` + +## 8. GNOME Keybindings (if using GNOME) + +- [ ] Verify custom shortcuts were applied: + ```bash + gsettings list-recursively org.gnome.settings-daemon.plugins.media-keys.custom-keybinding + ``` +- [ ] Or add them manually via Settings → Keyboard → Keyboard Shortcuts + +## 9. Fonts + +- [ ] If Nerd Font download failed, install manually: + - Download from https://www.nerdfonts.com/font-downloads + - MesloLGS NF (recommended for Powerlevel10k) + - Extract to `~/.local/share/fonts/` and run `fc-cache -fv` + +## 10. Ghostty + +- [ ] Verify Ghostty runs and fonts look correct (nerd font icons in prompt) +- [ ] If not, set `font-family = "MesloLGS NF"` in `~/.config/ghostty/config` + +## 11. VS Code + +- [ ] Open VS Code and verify extensions are installed +- [ ] Sign in to GitHub → Settings → Sync (if you use Settings Sync) +- [ ] Verify PandocCiter path to bibliography + +## 12. Solaar (Logitech Peripherals) + +- [ ] Open Solaar from applications menu +- [ ] Pair your Logitech receiver or connect via Bluetooth +- [ ] The config will auto-save to `~/.config/solaar/config.yaml` + +## 13. Printer / Scanning + +- [ ] If using a printer, add via Settings → Printers +- [ ] If using a scanner, install `simple-scan`: + ```bash + sudo dnf install simple-scan + ``` + +## 14. Reboot to Apply Kernel Changes + +- [ ] `sudo reboot` — required for: + - GRUB kernel cmdline parameters (if uncommented) + - sysctl settings (most apply at runtime, but reboot ensures) + - Docker group membership + - Desktop environment changes + +## 15. Verify Everything + +Run a quick sanity check after reboot: + +```bash +# Development tools +node --version +npm --version +python3 --version +uv --version +git --version + +# Docker +docker run --rm hello-world + +# Shell +zsh --version +echo $SHELL + +# Services +systemctl --user status porridge.service 2>/dev/null | head -5 + +# Network +tailscale status +ping -c 1 google.com + +# Config files exist +ls -la ~/.zshrc ~/.zshrc.local ~/.gitconfig ~/.p10k.zsh ~/.local/bin/ +``` diff --git a/config/cosmic/cosmic-comp-settings.ron b/config/cosmic/cosmic-comp-settings.ron new file mode 100644 index 0000000..42eed45 --- /dev/null +++ b/config/cosmic/cosmic-comp-settings.ron @@ -0,0 +1,67 @@ +// ============================================================================= +// COSMIC Desktop — Compositor Settings (RON format) +// File location: ~/.config/cosmic/com.system76.CosmicComp/v1/... +// FOR REFERENCE ONLY — adapt to your target DE. +// ============================================================================= + +// ---- XKB keyboard config ---- +// Location: .../v1/xkb_config +( + rules: "", + model: "", + layout: "us", + variant: "", + options: Some("compose:ralt"), // Right Alt = Compose key + repeat_delay: 600, + repeat_rate: 25, +) + +// ---- Touchpad ---- +// Location: .../v1/input_touchpad +( + state: Enabled, + acceleration: Some((profile: Some(Flat), speed: 0.3213816999010042)), + click_method: Some(Clickfinger), + scroll_config: Some(( + method: Some(TwoFinger), + natural_scroll: Some(true), + scroll_button: None, + scroll_factor: None, + )), + tap_config: Some(( + enabled: true, + button_map: Some(LeftRightMiddle), + drag: true, + drag_lock: false, + )), +) + +// ---- Mouse ---- +// Location: .../v1/input_default +( + state: Enabled, + acceleration: Some((profile: Some(Flat), speed: 0.4628044123886297)), + scroll_config: Some(( + method: None, + natural_scroll: Some(false), + scroll_button: None, + scroll_factor: Some(1.0), + )), +) + +// ---- Workspaces ---- +// Location: .../v1/workspaces +( + workspace_mode: OutputBound, + workspace_layout: Horizontal, + action_on_typing: None, + workspace_wraparound: true, +) + +// ---- Autotile ---- +// Location: .../v1/autotile +false + +// ---- Autotile behavior ---- +// Location: .../v1/autotile_behavior +PerWorkspace diff --git a/config/cosmic/custom-shortcuts.ron b/config/cosmic/custom-shortcuts.ron new file mode 100644 index 0000000..88f9fb8 --- /dev/null +++ b/config/cosmic/custom-shortcuts.ron @@ -0,0 +1,88 @@ +// ============================================================================= +// COSMIC Desktop — Custom Shortcuts (RON format) +// File location: ~/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom +// This is the keybinding config from the Pop!_OS machine. +// ============================================================================= +// COSMIC is System76's Rust-based desktop environment (not shipped on Fedora). +// This file is FOR REFERENCE only — translate to your target DE: +// +// GNOME: Use gsettings (see stage 09-desktop.sh) +// KDE: Use kwriteconfig5 or System Settings +// Sway/Hypr: Use sway config or hyprland.conf +// swhkd: Use ~/.config/swhkd/swhkdrc +// +// The hotkey scripts (google.sh, scholar.sh, etc.) work on any DE/WM that +// supports Wayland. They use wl-clipboard, wofi, and xdg-open. +// ============================================================================= + +{ + // ---- Text selection searches ---- + // Select text anywhere, press shortcut → action with selected text + ( + modifiers: [ Ctrl, Alt ], + key: "e", + description: Some("Emoji"), + ): Spawn("/home/julian/Development/hotkeys/emoji.sh"), + + ( + modifiers: [ Ctrl, Alt ], + key: "o", + description: Some("PDF"), + ): Spawn("/home/julian/Development/hotkeys/pdf.sh"), + + ( + modifiers: [ Ctrl, Alt ], + key: "a", + description: Some("Hotstrings"), + ): Spawn("/home/julian/Development/hotkeys/hotstrings.sh"), + + ( + modifiers: [ Ctrl, Alt ], + key: "d", + description: Some("Dictionary"), + ): Spawn("/home/julian/Development/hotkeys/dictionary.sh"), + + ( + modifiers: [ Ctrl, Alt ], + key: "g", + description: Some("Google"), + ): Spawn("/home/julian/Development/hotkeys/google.sh"), + + ( + modifiers: [ Ctrl, Alt ], + key: "s", + description: Some("Scholar"), + ): Spawn("/home/julian/Development/hotkeys/scholar.sh"), + + // ---- Window management ---- + ( + modifiers: [ Super ], + key: "n", + ): Minimize, + + ( + modifiers: [ Super ], + key: "Escape", + ): Close, + + ( + modifiers: [ Super, Shift ], + key: "Escape", + ): Disable, + + ( + modifiers: [ Super ], + key: "q", + ): Disable, + + // ---- System actions ---- + ( + modifiers: [ Super ], + key: "l", + ): System(LockScreen), + + ( + modifiers: [ Super, Shift ], + key: "l", + ): System(LogOut), +} diff --git a/config/ghostty/config b/config/ghostty/config new file mode 100644 index 0000000..f394a3e --- /dev/null +++ b/config/ghostty/config @@ -0,0 +1,11 @@ +# ============================================================================= +# Ghostty terminal configuration +# ============================================================================= +# This is a minimal config. Ghostty ships with sensible defaults. +# Run `ghostty +show-config --default --docs` for all options. + +# Terminfo for SSH — fixes terminal issues on remote hosts +shell-integration-features = ssh-terminfo,ssh-env + +# Ignore super+shift+s (conflicts with Scribe/porridge dictation) +keybind = super+shift+s=ignore diff --git a/config/git/gitconfig b/config/git/gitconfig new file mode 100644 index 0000000..d7e8f42 --- /dev/null +++ b/config/git/gitconfig @@ -0,0 +1,49 @@ +[user] + name = Your Name + email = your.email@example.com + signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... your-public-key-comment +[init] + defaultBranch = main +[credential "https://git.julianprester.com"] + provider = generic +[core] + compression = 0 + excludesfile = ~/.gitignore + editor = nano +[column] + ui = auto +[branch] + sort = -committerdate +[tag] + sort = version:refname +[diff] + algorithm = histogram + colorMoved = plain + mnemonicPrefix = true + renames = true +[push] + default = simple + autoSetupRemote = true + followTags = true +[pull] + rebase = true +[fetch] + prune = true + pruneTags = true + all = true +[help] + autocorrect = prompt +[commit] + verbose = true + gpgsign = true +[rerere] + enabled = true + autoupdate = true +[rebase] + autoSquash = true + autoStash = true + updateRefs = true +[merge] + conflictstyle = zdiff3 +[gpg] + format = ssh diff --git a/config/scripts/bw-load-ssh.sh b/config/scripts/bw-load-ssh.sh new file mode 100644 index 0000000..5213c5f --- /dev/null +++ b/config/scripts/bw-load-ssh.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# =========================================================================== +# bw-load-ssh.sh — Load SSH keys from Bitwarden into ssh-agent +# Reads SSH key items from Bitwarden vault and loads private keys into +# the running ssh-agent. +# +# Dependencies: bw (Bitwarden CLI), jq, ssh-agent running +# Usage: +# 1. First, authenticate: bw login +# 2. Unlock and save session: bw unlock --raw > ~/.config/Bitwarden\ CLI/.session +# 3. Run: ./bw-load-ssh.sh +# Or run automatically via bw-ssh-keys.service (systemd user service). +# =========================================================================== + +set -euo pipefail + +CONFIG_DIR="${HOME}/.config/Bitwarden CLI" +SESSION_FILE="${CONFIG_DIR}/.session" + +# Check session file exists +if [ ! -f "$SESSION_FILE" ]; then + echo "ERROR: Session file not found at $SESSION_FILE" + echo "Run: bw unlock --raw > '$SESSION_FILE' && chmod 600 '$SESSION_FILE'" + exit 1 +fi + +# Check ssh-agent is running +if ! ssh-add -l >/dev/null 2>&1; then + echo "ERROR: ssh-agent is not running." + echo "Start it with: eval \$(ssh-agent)" + exit 1 +fi + +# Read session key +export BW_SESSION=$(cat "$SESSION_FILE") + +# Verify session is still valid +if ! bw status 2>/dev/null | jq -e '.status == "unlocked"' >/dev/null 2>&1; then + echo "ERROR: Session is no longer valid (vault is locked or logged out)." + echo "Regenerate with: bw unlock --raw > '$SESSION_FILE' && chmod 600 '$SESSION_FILE'" + exit 1 +fi + +# Find all SSH key items +echo "Fetching SSH keys from vault..." +ITEMS=$(bw list items 2>/dev/null | jq -c '.[] | select(.type == 5)') + +if [ -z "$ITEMS" ]; then + echo "No SSH keys found in vault." + exit 0 +fi + +LOADED=0 +SKIPPED=0 + +echo "$ITEMS" | while IFS= read -r item; do + NAME=$(echo "$item" | jq -r '.name') + PUBLIC_KEY=$(echo "$item" | jq -r '.sshKey.publicKey // ""') + PRIVATE_KEY=$(echo "$item" | jq -r '.sshKey.privateKey // ""') + + if [ -z "$PRIVATE_KEY" ]; then + echo " SKIP '$NAME' — no private key found" + continue + fi + + # Extract comment from public key for checking if already loaded + COMMENT=$(echo "$PUBLIC_KEY" | awk '{print $3}' | tr -d '\n') + + # Check if already loaded in ssh-agent + if [ -n "$COMMENT" ] && ssh-add -l 2>/dev/null | grep -q "$COMMENT"; then + echo " OK '$NAME' — already loaded" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + # Load into ssh-agent + if echo "$PRIVATE_KEY" | ssh-add - 2>/dev/null; then + echo " LOAD '$NAME'" + LOADED=$((LOADED + 1)) + else + echo " FAIL '$NAME'" + fi +done + +echo "Done. Loaded: $LOADED, Skipped (already loaded): $SKIPPED" diff --git a/config/scripts/env.sh b/config/scripts/env.sh new file mode 100644 index 0000000..ccf53a4 --- /dev/null +++ b/config/scripts/env.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# =========================================================================== +# env — PATH helper +# Sourced from ~/.profile and ~/.bashrc to ensure ~/.local/bin is in PATH. +# Idempotent — won't duplicate PATH entries. +# =========================================================================== +# affix colons on either side of $PATH to simplify matching +case ":${PATH}:" in + *:"$HOME/.local/bin":*) + ;; + *) + # Prepending path in case a system-installed binary needs overriding + export PATH="$HOME/.local/bin:$PATH" + ;; +esac diff --git a/config/scripts/idle-battery-suspend.sh b/config/scripts/idle-battery-suspend.sh new file mode 100644 index 0000000..5ae819c --- /dev/null +++ b/config/scripts/idle-battery-suspend.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# =========================================================================== +# idle-battery-suspend.sh — Suspend laptop only when on battery +# Checks AC power status before suspending. If on AC power, does nothing. +# Used by swayidle.service (systemd user service). +# =========================================================================== +# Only suspend if on battery (AC online = 0) +AC_ONLINE=$(cat /sys/class/power_supply/AC/online 2>/dev/null) +if [ "$AC_ONLINE" = "0" ]; then + systemctl suspend-then-hibernate +fi diff --git a/config/scripts/zoom.sh b/config/scripts/zoom.sh new file mode 100644 index 0000000..4625de9 --- /dev/null +++ b/config/scripts/zoom.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# =========================================================================== +# zoom — Zoom launcher wrapper for COSMIC/Wayland on AMD GPU +# Forces Wayland-native mode (avoids X11 event freezes via XWayland). +# Forces VA-API hardware video decoding (fixes screen share performance). +# +# Works with: AMD Radeon 680M (Rembrandt) and similar. +# For NVIDIA GPUs, use: export LIBVA_DRIVER_NAME=nvidia +# =========================================================================== + +export QT_QPA_PLATFORM=wayland +export LIBVA_DRIVER_NAME=radeonsi +export LIBVA_DRI3_DISABLE=0 + +exec /usr/bin/zoom "$@" diff --git a/config/shell/p10k.zsh b/config/shell/p10k.zsh new file mode 100644 index 0000000..7cfb52d --- /dev/null +++ b/config/shell/p10k.zsh @@ -0,0 +1,1713 @@ +# Generated by Powerlevel10k configuration wizard on 2025-12-14 at 17:29 AEDT. +# Based on romkatv/powerlevel10k/config/p10k-lean.zsh, checksum 37983. +# Wizard options: nerdfont-v3 + powerline, small icons, unicode, lean, 1 line, compact, +# few icons, concise, instant_prompt=verbose. +# Type `p10k configure` to generate another config. +# +# Config for Powerlevel10k with lean prompt style. Type `p10k configure` to generate +# your own config based on it. +# +# Tip: Looking for a nice color? Here's a one-liner to print colormap. +# +# for i in {0..255}; do print -Pn "%K{$i} %k%F{$i}${(l:3::0:)i}%f " ${${(M)$((i%6)):#3}:+$'\n'}; done + +# Temporarily change options. +'builtin' 'local' '-a' 'p10k_config_opts' +[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases') +[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob') +[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand') +'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' + +() { + emulate -L zsh -o extended_glob + + # Unset all configuration options. This allows you to apply configuration changes without + # restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`. + unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR' + + # Zsh >= 5.1 is required. + [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return + + # The list of segments shown on the left. Fill it with the most important segments. + typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=( + # os_icon # os identifier + dir # current directory + vcs # git status + prompt_char # prompt symbol + ) + + # The list of segments shown on the right. Fill it with less important segments. + # Right prompt on the last prompt line (where you are typing your commands) gets + # automatically hidden when the input line reaches it. Right prompt above the + # last prompt line gets hidden if it would overlap with left prompt. + typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=( + status # exit code of the last command + command_execution_time # duration of the last command + background_jobs # presence of background jobs + direnv # direnv status (https://direnv.net/) + asdf # asdf version manager (https://github.com/asdf-vm/asdf) + virtualenv # python virtual environment (https://docs.python.org/3/library/venv.html) + anaconda # conda environment (https://conda.io/) + pyenv # python environment (https://github.com/pyenv/pyenv) + goenv # go environment (https://github.com/syndbg/goenv) + nodenv # node.js version from nodenv (https://github.com/nodenv/nodenv) + nvm # node.js version from nvm (https://github.com/nvm-sh/nvm) + nodeenv # node.js environment (https://github.com/ekalinin/nodeenv) + # node_version # node.js version + # go_version # go version (https://golang.org) + # rust_version # rustc version (https://www.rust-lang.org) + # dotnet_version # .NET version (https://dotnet.microsoft.com) + # php_version # php version (https://www.php.net/) + # laravel_version # laravel php framework version (https://laravel.com/) + # java_version # java version (https://www.java.com/) + # package # name@version from package.json (https://docs.npmjs.com/files/package.json) + rbenv # ruby version from rbenv (https://github.com/rbenv/rbenv) + rvm # ruby version from rvm (https://rvm.io) + fvm # flutter version management (https://github.com/leoafarias/fvm) + luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv) + jenv # java version from jenv (https://github.com/jenv/jenv) + plenv # perl version from plenv (https://github.com/tokuhirom/plenv) + perlbrew # perl version from perlbrew (https://github.com/gugod/App-perlbrew) + phpenv # php version from phpenv (https://github.com/phpenv/phpenv) + scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv) + haskell_stack # haskell version from stack (https://haskellstack.org/) + kubecontext # current kubernetes context (https://kubernetes.io/) + terraform # terraform workspace (https://www.terraform.io) + # terraform_version # terraform version (https://www.terraform.io) + aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) + aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) + azure # azure account name (https://docs.microsoft.com/en-us/cli/azure) + gcloud # google cloud cli account and project (https://cloud.google.com/) + google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production) + toolbox # toolbox name (https://github.com/containers/toolbox) + context # user@hostname + nordvpn # nordvpn connection status, linux only (https://nordvpn.com/) + ranger # ranger shell (https://github.com/ranger/ranger) + yazi # yazi shell (https://github.com/sxyazi/yazi) + nnn # nnn shell (https://github.com/jarun/nnn) + lf # lf shell (https://github.com/gokcehan/lf) + xplr # xplr shell (https://github.com/sayanarijit/xplr) + vim_shell # vim shell indicator (:sh) + midnight_commander # midnight commander shell (https://midnight-commander.org/) + nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) + chezmoi_shell # chezmoi shell (https://www.chezmoi.io/) + # vpn_ip # virtual private network indicator + # load # CPU load + # disk_usage # disk usage + # ram # free RAM + # swap # used swap + todo # todo items (https://github.com/todotxt/todo.txt-cli) + timewarrior # timewarrior tracking status (https://timewarrior.net/) + taskwarrior # taskwarrior task count (https://taskwarrior.org/) + per_directory_history # Oh My Zsh per-directory-history local/global indicator + # cpu_arch # CPU architecture + # time # current time + # ip # ip address and bandwidth usage for a specified network interface + # public_ip # public IP address + # proxy # system-wide http/https/ftp proxy + # battery # internal battery + # wifi # wifi speed + # example # example user-defined segment (see prompt_example function below) + ) + + # Defines character set used by powerlevel10k. It's best to let `p10k configure` set it for you. + typeset -g POWERLEVEL9K_MODE=nerdfont-v3 + # When set to `moderate`, some icons will have an extra space after them. This is meant to avoid + # icon overlap when using non-monospace fonts. When set to `none`, spaces are not added. + typeset -g POWERLEVEL9K_ICON_PADDING=none + + # Basic style options that define the overall look of your prompt. You probably don't want to + # change them. + typeset -g POWERLEVEL9K_BACKGROUND= # transparent background + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol + + # When set to true, icons appear before content on both sides of the prompt. When set + # to false, icons go after content. If empty or not set, icons go before content in the left + # prompt and after content in the right prompt. + # + # You can also override it for a specific segment: + # + # POWERLEVEL9K_STATUS_ICON_BEFORE_CONTENT=false + # + # Or for a specific segment in specific state: + # + # POWERLEVEL9K_DIR_NOT_WRITABLE_ICON_BEFORE_CONTENT=false + typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT=true + + # Add an empty line before each prompt. + typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false + + # Connect left prompt lines with these symbols. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX= + typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX= + typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX= + # Connect right prompt lines with these symbols. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX= + typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX= + typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX= + + # The left end of left prompt. + typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= + # The right end of right prompt. + typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL= + + # Ruler, a.k.a. the horizontal line before each prompt. If you set it to true, you'll + # probably want to set POWERLEVEL9K_PROMPT_ADD_NEWLINE=false above and + # POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' ' below. + typeset -g POWERLEVEL9K_SHOW_RULER=false + typeset -g POWERLEVEL9K_RULER_CHAR='─' # reasonable alternative: '·' + typeset -g POWERLEVEL9K_RULER_FOREGROUND=242 + + # Filler between left and right prompt on the first prompt line. You can set it to '·' or '─' + # to make it easier to see the alignment between left and right prompt and to separate prompt + # from command output. It serves the same purpose as ruler (see above) without increasing + # the number of prompt lines. You'll probably want to set POWERLEVEL9K_SHOW_RULER=false + # if using this. You might also like POWERLEVEL9K_PROMPT_ADD_NEWLINE=false for more compact + # prompt. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' ' + if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then + # The color of the filler. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=242 + # Add a space between the end of left prompt and the filler. + typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=' ' + # Add a space between the filler and the start of right prompt. + typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL=' ' + # Start filler from the edge of the screen if there are no left segments on the first line. + typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}' + # End filler on the edge of the screen if there are no right segments on the first line. + typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}' + fi + + #################################[ os_icon: os identifier ]################################## + # OS identifier color. + typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND= + # Custom icon. + # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='⭐' + + ################################[ prompt_char: prompt symbol ]################################ + # Green prompt symbol if the last command succeeded. + typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=76 + # Red prompt symbol if the last command failed. + typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=196 + # Default prompt symbol. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯' + # Prompt symbol in command vi mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮' + # Prompt symbol in visual vi mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V' + # Prompt symbol in overwrite vi mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶' + typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true + # No line terminator if prompt_char is the last segment. + typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='' + # No line introducer if prompt_char is the first segment. + typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= + + ##################################[ dir: current directory ]################################## + # Default current directory color. + typeset -g POWERLEVEL9K_DIR_FOREGROUND=31 + # If directory is too long, shorten some of its segments to the shortest possible unique + # prefix. The shortened directory can be tab-completed to the original. + typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique + # Replace removed segment suffixes with this symbol. + typeset -g POWERLEVEL9K_SHORTEN_DELIMITER= + # Color of the shortened directory segments. + typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=103 + # Color of the anchor directory segments. Anchor segments are never shortened. The first + # segment is always an anchor. + typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=39 + # Display anchor directory segments in bold. + typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true + # Don't shorten directories that contain any of these files. They are anchors. + local anchor_files=( + .bzr + .citc + .git + .hg + .node-version + .python-version + .go-version + .ruby-version + .lua-version + .java-version + .perl-version + .php-version + .tool-versions + .mise.toml + .shorten_folder_marker + .svn + .terraform + CVS + Cargo.toml + composer.json + go.mod + package.json + stack.yaml + ) + typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})" + # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains + # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is + # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first) + # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers + # and other directories don't. + # + # Optionally, "first" and "last" can be followed by ":" where is an integer. + # This moves the truncation point to the right (positive offset) or to the left (negative offset) + # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0" + # respectively. + typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false + # Don't shorten this many last directory segments. They are anchors. + typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1 + # Shorten directory if it's longer than this even if there is space for it. The value can + # be either absolute (e.g., '80') or a percentage of terminal width (e.g, '50%'). If empty, + # directory will be shortened only when prompt doesn't fit or when other parameters demand it + # (see POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS and POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT below). + # If set to `0`, directory will always be shortened to its minimum length. + typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=80 + # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least this + # many columns for typing commands. + typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40 + # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least + # COLUMNS * POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT * 0.01 columns for typing commands. + typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50 + # If set to true, embed a hyperlink into the directory. Useful for quickly + # opening a directory in the file manager simply by clicking the link. + # Can also be handy when the directory is shortened, as it allows you to see + # the full directory that was used in previous commands. + typeset -g POWERLEVEL9K_DIR_HYPERLINK=false + + # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON + # and POWERLEVEL9K_DIR_CLASSES below. + typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3 + + # The default icon shown next to non-writable and non-existent directories when + # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3. + # typeset -g POWERLEVEL9K_LOCK_ICON='⭐' + + # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different + # directories. It must be an array with 3 * N elements. Each triplet consists of: + # + # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with + # extended_glob option enabled. + # 2. Directory class for the purpose of styling. + # 3. An empty string. + # + # Triplets are tried in order. The first triplet whose pattern matches $PWD wins. + # + # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories + # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_DIR_CLASSES=( + # '~/work(|/*)' WORK '' + # '~(|/*)' HOME '' + # '*' DEFAULT '') + # + # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one + # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or + # WORK_NON_EXISTENT. + # + # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an + # option to define custom colors and icons for different directory classes. + # + # # Styling for WORK. + # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31 + # typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103 + # typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39 + # + # # Styling for WORK_NOT_WRITABLE. + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31 + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103 + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39 + # + # # Styling for WORK_NON_EXISTENT. + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31 + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103 + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39 + # + # If a styling parameter isn't explicitly defined for some class, it falls back to the classless + # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls + # back to POWERLEVEL9K_DIR_FOREGROUND. + # + typeset -g POWERLEVEL9K_DIR_CLASSES=() + + # Custom prefix. + # typeset -g POWERLEVEL9K_DIR_PREFIX='%fin ' + + #####################################[ vcs: git status ]###################################### + # Branch icon. Set this parameter to '\UE0A0 ' for the popular Powerline branch icon. + typeset -g POWERLEVEL9K_VCS_BRANCH_ICON= + + # Untracked files icon. It's really a question mark, your font isn't broken. + # Change the value of this parameter to show a different icon. + typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?' + + # Formatter for Git status. + # + # Example output: master wip ⇣42⇡42 *42 merge ~42 +42 !42 ?42. + # + # You can edit the function to customize how Git status looks. + # + # VCS_STATUS_* parameters are set by gitstatus plugin. See reference: + # https://github.com/romkatv/gitstatus/blob/master/gitstatus.plugin.zsh. + function my_git_formatter() { + emulate -L zsh + + if [[ -n $P9K_CONTENT ]]; then + # If P9K_CONTENT is not empty, use it. It's either "loading" or from vcs_info (not from + # gitstatus plugin). VCS_STATUS_* parameters are not available in this case. + typeset -g my_git_format=$P9K_CONTENT + return + fi + + if (( $1 )); then + # Styling for up-to-date Git status. + local meta='%f' # default foreground + local clean='%76F' # green foreground + local modified='%178F' # yellow foreground + local untracked='%39F' # blue foreground + local conflicted='%196F' # red foreground + else + # Styling for incomplete and stale Git status. + local meta='%244F' # grey foreground + local clean='%244F' # grey foreground + local modified='%244F' # grey foreground + local untracked='%244F' # grey foreground + local conflicted='%244F' # grey foreground + fi + + local res + + if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then + local branch=${(V)VCS_STATUS_LOCAL_BRANCH} + # If local branch name is at most 32 characters long, show it in full. + # Otherwise show the first 12 … the last 12. + # Tip: To always show local branch name in full without truncation, delete the next line. + (( $#branch > 32 )) && branch[13,-13]="…" # <-- this line + res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}" + fi + + if [[ -n $VCS_STATUS_TAG + # Show tag only if not on a branch. + # Tip: To always show tag, delete the next line. + && -z $VCS_STATUS_LOCAL_BRANCH # <-- this line + ]]; then + local tag=${(V)VCS_STATUS_TAG} + # If tag name is at most 32 characters long, show it in full. + # Otherwise show the first 12 … the last 12. + # Tip: To always show tag name in full without truncation, delete the next line. + (( $#tag > 32 )) && tag[13,-13]="…" # <-- this line + res+="${meta}#${clean}${tag//\%/%%}" + fi + + # Display the current Git commit if there is no branch and no tag. + # Tip: To always display the current Git commit, delete the next line. + [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line + res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}" + + # Show tracking branch name if it differs from local branch. + if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then + res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" + fi + + # Display "wip" if the latest commit's summary contains "wip" or "WIP". + if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then + res+=" ${modified}wip" + fi + + if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then + # ⇣42 if behind the remote. + (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}" + # ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42. + (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" " + (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}" + elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then + # Tip: Uncomment the next line to display '=' if up to date with the remote. + # res+=" ${clean}=" + fi + + # ⇠42 if behind the push remote. + (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}" + (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" " + # ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42. + (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && res+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}" + # *42 if have stashes. + (( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}" + # 'merge' if the repo is in an unusual state. + [[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}" + # ~42 if have merge conflicts. + (( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}" + # +42 if have staged changes. + (( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}" + # !42 if have unstaged changes. + (( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}" + # ?42 if have untracked files. It's really a question mark, your font isn't broken. + # See POWERLEVEL9K_VCS_UNTRACKED_ICON above if you want to use a different icon. + # Remove the next line if you don't want to see untracked files at all. + (( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}" + # "─" if the number of unstaged files is unknown. This can happen due to + # POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY (see below) being set to a non-negative number lower + # than the number of files in the Git index, or due to bash.showDirtyState being set to false + # in the repository config. The number of staged and untracked files may also be unknown + # in this case. + (( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─" + + typeset -g my_git_format=$res + } + functions -M my_git_formatter 2>/dev/null + + # Don't count the number of unstaged, untracked and conflicted files in Git repositories with + # more than this many files in the index. Negative value means infinity. + # + # If you are working in Git repositories with tens of millions of files and seeing performance + # sagging, try setting POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY to a number lower than the output + # of `git ls-files | wc -l`. Alternatively, add `bash.showDirtyState = false` to the repository's + # config: `git config bash.showDirtyState false`. + typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1 + + # Don't show Git status in prompt for repositories whose workdir matches this pattern. + # For example, if set to '~', the Git repository at $HOME/.git will be ignored. + # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'. + typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~' + + # Disable the default Git status formatting. + typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true + # Install our own Git status formatter. + typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}' + typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter(0)))+${my_git_format}}' + # Enable counters for staged, unstaged, etc. + typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1 + + # Icon color. + typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_COLOR=76 + typeset -g POWERLEVEL9K_VCS_LOADING_VISUAL_IDENTIFIER_COLOR=244 + # Custom icon. + typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_EXPANSION= + # Custom prefix. + # typeset -g POWERLEVEL9K_VCS_PREFIX='%fon ' + + # Show status of repositories of these types. You can add svn and/or hg if you are + # using them. If you do, your prompt may become slow even when your current directory + # isn't in an svn or hg repository. + typeset -g POWERLEVEL9K_VCS_BACKENDS=(git) + + # These settings are used for repositories other than Git or when gitstatusd fails and + # Powerlevel10k has to fall back to using vcs_info. + typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76 + typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76 + typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=178 + + ##########################[ status: exit code of the last command ]########################### + # Enable OK_PIPE, ERROR_PIPE and ERROR_SIGNAL status states to allow us to enable, disable and + # style them independently from the regular OK and ERROR state. + typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true + + # Status on success. No content, just an icon. No need to show it if prompt_char is enabled as + # it will signify success by turning green. + typeset -g POWERLEVEL9K_STATUS_OK=false + typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=70 + typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔' + + # Status when some part of a pipe command fails but the overall exit status is zero. It may look + # like this: 1|0. + typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true + typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=70 + typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔' + + # Status when it's just an error code (e.g., '1'). No need to show it if prompt_char is enabled as + # it will signify error by turning red. + typeset -g POWERLEVEL9K_STATUS_ERROR=false + typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=160 + typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘' + + # Status when the last command was terminated by a signal. + typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true + typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=160 + # Use terse signal names: "INT" instead of "SIGINT(2)". + typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false + typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘' + + # Status when some part of a pipe command fails and the overall exit status is also non-zero. + # It may look like this: 1|0. + typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true + typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=160 + typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘' + + ###################[ command_execution_time: duration of the last command ]################### + # Show duration of the last command if takes at least this many seconds. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3 + # Show this many fractional digits. Zero means round to seconds. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0 + # Execution time color. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=101 + # Duration format: 1d 2h 3m 4s. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s' + # Custom icon. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_VISUAL_IDENTIFIER_EXPANSION= + # Custom prefix. + # typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PREFIX='%ftook ' + + #######################[ background_jobs: presence of background jobs ]####################### + # Don't show the number of background jobs. + typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false + # Background jobs color. + typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=70 + # Custom icon. + # typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######################[ direnv: direnv status (https://direnv.net/) ]######################## + # Direnv color. + typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=178 + # Custom icon. + # typeset -g POWERLEVEL9K_DIRENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]############### + # Default asdf color. Only used to display tools for which there is no color override (see below). + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND. + typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66 + + # There are four parameters that can be used to hide asdf tools. Each parameter describes + # conditions under which a tool gets hidden. Parameters can hide tools but not unhide them. If at + # least one parameter decides to hide a tool, that tool gets hidden. If no parameter decides to + # hide a tool, it gets shown. + # + # Special note on the difference between POWERLEVEL9K_ASDF_SOURCES and + # POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW. Consider the effect of the following commands: + # + # asdf local python 3.8.1 + # asdf global python 3.8.1 + # + # After running both commands the current python version is 3.8.1 and its source is "local" as + # it takes precedence over "global". If POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW is set to false, + # it'll hide python version in this case because 3.8.1 is the same as the global version. + # POWERLEVEL9K_ASDF_SOURCES will hide python version only if the value of this parameter doesn't + # contain "local". + + # Hide tool versions that don't come from one of these sources. + # + # Available sources: + # + # - shell `asdf current` says "set by ASDF_${TOOL}_VERSION environment variable" + # - local `asdf current` says "set by /some/not/home/directory/file" + # - global `asdf current` says "set by /home/username/file" + # + # Note: If this parameter is set to (shell local global), it won't hide tools. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SOURCES. + typeset -g POWERLEVEL9K_ASDF_SOURCES=(shell local global) + + # If set to false, hide tool versions that are the same as global. + # + # Note: The name of this parameter doesn't reflect its meaning at all. + # Note: If this parameter is set to true, it won't hide tools. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_PROMPT_ALWAYS_SHOW. + typeset -g POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW=false + + # If set to false, hide tool versions that are equal to "system". + # + # Note: If this parameter is set to true, it won't hide tools. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_SYSTEM. + typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true + + # If set to non-empty value, hide tools unless there is a file matching the specified file pattern + # in the current directory, or its parent directory, or its grandparent directory, and so on. + # + # Note: If this parameter is set to empty value, it won't hide tools. + # Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_ON_UPGLOB. + # + # Example: Hide nodejs version when there is no package.json and no *.js files in the current + # directory, in `..`, in `../..` and so on. + # + # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.js|package.json' + typeset -g POWERLEVEL9K_ASDF_SHOW_ON_UPGLOB= + + # Ruby version from asdf. + typeset -g POWERLEVEL9K_ASDF_RUBY_FOREGROUND=168 + # typeset -g POWERLEVEL9K_ASDF_RUBY_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_RUBY_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Python version from asdf. + typeset -g POWERLEVEL9K_ASDF_PYTHON_FOREGROUND=37 + # typeset -g POWERLEVEL9K_ASDF_PYTHON_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_PYTHON_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Go version from asdf. + typeset -g POWERLEVEL9K_ASDF_GOLANG_FOREGROUND=37 + # typeset -g POWERLEVEL9K_ASDF_GOLANG_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_GOLANG_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Node.js version from asdf. + typeset -g POWERLEVEL9K_ASDF_NODEJS_FOREGROUND=70 + # typeset -g POWERLEVEL9K_ASDF_NODEJS_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Rust version from asdf. + typeset -g POWERLEVEL9K_ASDF_RUST_FOREGROUND=37 + # typeset -g POWERLEVEL9K_ASDF_RUST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_RUST_SHOW_ON_UPGLOB='*.foo|*.bar' + + # .NET Core version from asdf. + typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_FOREGROUND=134 + # typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_DOTNET_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Flutter version from asdf. + typeset -g POWERLEVEL9K_ASDF_FLUTTER_FOREGROUND=38 + # typeset -g POWERLEVEL9K_ASDF_FLUTTER_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_FLUTTER_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Lua version from asdf. + typeset -g POWERLEVEL9K_ASDF_LUA_FOREGROUND=32 + # typeset -g POWERLEVEL9K_ASDF_LUA_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_LUA_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Java version from asdf. + typeset -g POWERLEVEL9K_ASDF_JAVA_FOREGROUND=32 + # typeset -g POWERLEVEL9K_ASDF_JAVA_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_JAVA_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Perl version from asdf. + typeset -g POWERLEVEL9K_ASDF_PERL_FOREGROUND=67 + # typeset -g POWERLEVEL9K_ASDF_PERL_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_PERL_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Erlang version from asdf. + typeset -g POWERLEVEL9K_ASDF_ERLANG_FOREGROUND=125 + # typeset -g POWERLEVEL9K_ASDF_ERLANG_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_ERLANG_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Elixir version from asdf. + typeset -g POWERLEVEL9K_ASDF_ELIXIR_FOREGROUND=129 + # typeset -g POWERLEVEL9K_ASDF_ELIXIR_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_ELIXIR_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Postgres version from asdf. + typeset -g POWERLEVEL9K_ASDF_POSTGRES_FOREGROUND=31 + # typeset -g POWERLEVEL9K_ASDF_POSTGRES_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_POSTGRES_SHOW_ON_UPGLOB='*.foo|*.bar' + + # PHP version from asdf. + typeset -g POWERLEVEL9K_ASDF_PHP_FOREGROUND=99 + # typeset -g POWERLEVEL9K_ASDF_PHP_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_PHP_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Haskell version from asdf. + typeset -g POWERLEVEL9K_ASDF_HASKELL_FOREGROUND=172 + # typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Julia version from asdf. + typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70 + # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar' + + ##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]########### + # NordVPN connection indicator color. + typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39 + # Hide NordVPN connection indicator when not connected. + typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION= + typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION= + # Custom icon. + # typeset -g POWERLEVEL9K_NORDVPN_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #################[ ranger: ranger shell (https://github.com/ranger/ranger) ]################## + # Ranger shell color. + typeset -g POWERLEVEL9K_RANGER_FOREGROUND=178 + # Custom icon. + # typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ####################[ yazi: yazi shell (https://github.com/sxyazi/yazi) ]##################### + # Yazi shell color. + typeset -g POWERLEVEL9K_YAZI_FOREGROUND=178 + # Custom icon. + # typeset -g POWERLEVEL9K_YAZI_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]####################### + # Nnn shell color. + typeset -g POWERLEVEL9K_NNN_FOREGROUND=72 + # Custom icon. + # typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######################[ lf: lf shell (https://github.com/gokcehan/lf) ]####################### + # lf shell color. + typeset -g POWERLEVEL9K_LF_FOREGROUND=72 + # Custom icon. + # typeset -g POWERLEVEL9K_LF_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]################## + # xplr shell color. + typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72 + # Custom icon. + # typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########################[ vim_shell: vim shell indicator (:sh) ]########################### + # Vim shell indicator color. + typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34 + # Custom icon. + # typeset -g POWERLEVEL9K_VIM_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######[ midnight_commander: midnight commander shell (https://midnight-commander.org/) ]###### + # Midnight Commander shell color. + typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=178 + # Custom icon. + # typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #[ nix_shell: nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) ]## + # Nix shell color. + typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=74 + + # Display the icon of nix_shell if PATH contains a subdirectory of /nix/store. + # typeset -g POWERLEVEL9K_NIX_SHELL_INFER_FROM_PATH=false + + # Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line. + # typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION= + + # Custom icon. + # typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##################[ chezmoi_shell: chezmoi shell (https://www.chezmoi.io/) ]################## + # chezmoi shell color. + typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=33 + # Custom icon. + # typeset -g POWERLEVEL9K_CHEZMOI_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##################################[ disk_usage: disk usage ]################################## + # Colors for different levels of disk usage. + typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35 + typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220 + typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=160 + # Thresholds for different levels of disk usage (percentage points). + typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90 + typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95 + # If set to true, hide disk usage when below $POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL percent. + typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=false + # Custom icon. + # typeset -g POWERLEVEL9K_DISK_USAGE_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######################################[ ram: free RAM ]####################################### + # RAM color. + typeset -g POWERLEVEL9K_RAM_FOREGROUND=66 + # Custom icon. + # typeset -g POWERLEVEL9K_RAM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #####################################[ swap: used swap ]###################################### + # Swap color. + typeset -g POWERLEVEL9K_SWAP_FOREGROUND=96 + # Custom icon. + # typeset -g POWERLEVEL9K_SWAP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######################################[ load: CPU load ]###################################### + # Show average CPU load over this many last minutes. Valid values are 1, 5 and 15. + typeset -g POWERLEVEL9K_LOAD_WHICH=5 + # Load color when load is under 50%. + typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=66 + # Load color when load is between 50% and 70%. + typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=178 + # Load color when load is over 70%. + typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=166 + # Custom icon. + # typeset -g POWERLEVEL9K_LOAD_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################[ todo: todo items (https://github.com/todotxt/todo.txt-cli) ]################ + # Todo color. + typeset -g POWERLEVEL9K_TODO_FOREGROUND=110 + # Hide todo when the total number of tasks is zero. + typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true + # Hide todo when the number of tasks after filtering is zero. + typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false + + # Todo format. The following parameters are available within the expansion. + # + # - P9K_TODO_TOTAL_TASK_COUNT The total number of tasks. + # - P9K_TODO_FILTERED_TASK_COUNT The number of tasks after filtering. + # + # These variables correspond to the last line of the output of `todo.sh -p ls`: + # + # TODO: 24 of 42 tasks shown + # + # Here 24 is P9K_TODO_FILTERED_TASK_COUNT and 42 is P9K_TODO_TOTAL_TASK_COUNT. + # + # typeset -g POWERLEVEL9K_TODO_CONTENT_EXPANSION='$P9K_TODO_FILTERED_TASK_COUNT' + + # Custom icon. + # typeset -g POWERLEVEL9K_TODO_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ timewarrior: timewarrior tracking status (https://timewarrior.net/) ]############ + # Timewarrior color. + typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=110 + # If the tracked task is longer than 24 characters, truncate and append "…". + # Tip: To always display tasks without truncation, delete the following parameter. + # Tip: To hide task names and display just the icon when time tracking is enabled, set the + # value of the following parameter to "". + typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}' + + # Custom icon. + # typeset -g POWERLEVEL9K_TIMEWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]############## + # Taskwarrior color. + typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74 + + # Taskwarrior segment format. The following parameters are available within the expansion. + # + # - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`. + # - P9K_TASKWARRIOR_OVERDUE_COUNT The number of overdue tasks: `task +OVERDUE count`. + # + # Zero values are represented as empty parameters. + # + # The default format: + # + # '${P9K_TASKWARRIOR_OVERDUE_COUNT:+"!$P9K_TASKWARRIOR_OVERDUE_COUNT/"}$P9K_TASKWARRIOR_PENDING_COUNT' + # + # typeset -g POWERLEVEL9K_TASKWARRIOR_CONTENT_EXPANSION='$P9K_TASKWARRIOR_PENDING_COUNT' + + # Custom icon. + # typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######[ per_directory_history: Oh My Zsh per-directory-history local/global indicator ]####### + # Color when using local/global history. + typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=135 + typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=130 + + # Tip: Uncomment the next two lines to hide "local"/"global" text and leave just the icon. + # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_CONTENT_EXPANSION='' + # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_CONTENT_EXPANSION='' + + # Custom icon. + # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################################[ cpu_arch: CPU architecture ]################################ + # CPU architecture color. + typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=172 + + # Hide the segment when on a specific CPU architecture. + # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_CONTENT_EXPANSION= + # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_VISUAL_IDENTIFIER_EXPANSION= + + # Custom icon. + # typeset -g POWERLEVEL9K_CPU_ARCH_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##################################[ context: user@hostname ]################################## + # Context color when running with privileges. + typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178 + # Context color in SSH without privileges. + typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=180 + # Default context color (no privileges, no SSH). + typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=180 + + # Context format when running with privileges: bold user@hostname. + typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%B%n@%m' + # Context format when in SSH without privileges: user@hostname. + typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m' + # Default context format (no privileges, no SSH): user@hostname. + typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n@%m' + + # Don't show context unless running with privileges or in SSH. + # Tip: Remove the next line to always show context. + typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_{CONTENT,VISUAL_IDENTIFIER}_EXPANSION= + + # Custom icon. + # typeset -g POWERLEVEL9K_CONTEXT_VISUAL_IDENTIFIER_EXPANSION='⭐' + # Custom prefix. + # typeset -g POWERLEVEL9K_CONTEXT_PREFIX='%fwith ' + + ###[ virtualenv: python virtual environment (https://docs.python.org/3/library/venv.html) ]### + # Python virtual environment color. + typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37 + # Don't show Python version next to the virtual environment name. + typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false + # If set to "false", won't show virtualenv if pyenv is already shown. + # If set to "if-different", won't show virtualenv if it's the same as pyenv. + typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false + # Separate environment name from Python version only with a space. + typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER= + # Custom icon. + # typeset -g POWERLEVEL9K_VIRTUALENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #####################[ anaconda: conda environment (https://conda.io/) ]###################### + # Anaconda environment color. + typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37 + + # Anaconda segment format. The following parameters are available within the expansion. + # + # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment. + # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment. + # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below). + # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version). + # + # CONDA_PROMPT_MODIFIER can be configured with the following command: + # + # conda config --set env_prompt '({default_env}) ' + # + # The last argument is a Python format string that can use the following variables: + # + # - prefix The same as CONDA_PREFIX. + # - default_env The same as CONDA_DEFAULT_ENV. + # - name The last segment of CONDA_PREFIX. + # - stacked_env Comma-separated list of names in the environment stack. The first element is + # always the same as default_env. + # + # Note: '({default_env}) ' is the default value of env_prompt. + # + # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER + # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former + # is empty. + typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}' + + # Custom icon. + # typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################[ pyenv: python environment (https://github.com/pyenv/pyenv) ]################ + # Pyenv color. + typeset -g POWERLEVEL9K_PYENV_FOREGROUND=37 + # Hide python version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global) + # If set to false, hide python version if it's the same as global: + # $(pyenv version-name) == $(pyenv global). + typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide python version if it's equal to "system". + typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true + + # Pyenv segment format. The following parameters are available within the expansion. + # + # - P9K_CONTENT Current pyenv environment (pyenv version-name). + # - P9K_PYENV_PYTHON_VERSION Current python version (python --version). + # + # The default format has the following logic: + # + # 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or + # starts with "$P9K_PYENV_PYTHON_VERSION/". + # 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION". + typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}' + + # Custom icon. + # typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################[ goenv: go environment (https://github.com/syndbg/goenv) ]################ + # Goenv color. + typeset -g POWERLEVEL9K_GOENV_FOREGROUND=37 + # Hide go version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global) + # If set to false, hide go version if it's the same as global: + # $(goenv version-name) == $(goenv global). + typeset -g POWERLEVEL9K_GOENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide go version if it's equal to "system". + typeset -g POWERLEVEL9K_GOENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_GOENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ nodenv: node.js version from nodenv (https://github.com/nodenv/nodenv) ]########## + # Nodenv color. + typeset -g POWERLEVEL9K_NODENV_FOREGROUND=70 + # Hide node version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_NODENV_SOURCES=(shell local global) + # If set to false, hide node version if it's the same as global: + # $(nodenv version-name) == $(nodenv global). + typeset -g POWERLEVEL9K_NODENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide node version if it's equal to "system". + typeset -g POWERLEVEL9K_NODENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_NODENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]############### + # Nvm color. + typeset -g POWERLEVEL9K_NVM_FOREGROUND=70 + # If set to false, hide node version if it's the same as default: + # $(nvm version current) == $(nvm version default). + typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false + # If set to false, hide node version if it's equal to "system". + typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ############[ nodeenv: node.js environment (https://github.com/ekalinin/nodeenv) ]############ + # Nodeenv color. + typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=70 + # Don't show Node version next to the environment name. + typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false + # Separate environment name from Node version only with a space. + typeset -g POWERLEVEL9K_NODEENV_{LEFT,RIGHT}_DELIMITER= + # Custom icon. + # typeset -g POWERLEVEL9K_NODEENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##############################[ node_version: node.js version ]############################### + # Node version color. + typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=70 + # Show node version only when in a directory tree containing package.json. + typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_NODE_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######################[ go_version: go version (https://golang.org) ]######################## + # Go version color. + typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=37 + # Show go version only when in a go project subdirectory. + typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_GO_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #################[ rust_version: rustc version (https://www.rust-lang.org) ]################## + # Rust version color. + typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=37 + # Show rust version only when in a rust project subdirectory. + typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_RUST_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###############[ dotnet_version: .NET version (https://dotnet.microsoft.com) ]################ + # .NET version color. + typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=134 + # Show .NET version only when in a .NET project subdirectory. + typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_DOTNET_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #####################[ php_version: php version (https://www.php.net/) ]###################### + # PHP version color. + typeset -g POWERLEVEL9K_PHP_VERSION_FOREGROUND=99 + # Show PHP version only when in a PHP project subdirectory. + typeset -g POWERLEVEL9K_PHP_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_PHP_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ laravel_version: laravel php framework version (https://laravel.com/) ]########### + # Laravel version color. + typeset -g POWERLEVEL9K_LARAVEL_VERSION_FOREGROUND=161 + # Custom icon. + # typeset -g POWERLEVEL9K_LARAVEL_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ####################[ java_version: java version (https://www.java.com/) ]#################### + # Java version color. + typeset -g POWERLEVEL9K_JAVA_VERSION_FOREGROUND=32 + # Show java version only when in a java project subdirectory. + typeset -g POWERLEVEL9K_JAVA_VERSION_PROJECT_ONLY=true + # Show brief version. + typeset -g POWERLEVEL9K_JAVA_VERSION_FULL=false + # Custom icon. + # typeset -g POWERLEVEL9K_JAVA_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###[ package: name@version from package.json (https://docs.npmjs.com/files/package.json) ]#### + # Package color. + typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=117 + # Package format. The following parameters are available within the expansion. + # + # - P9K_PACKAGE_NAME The value of `name` field in package.json. + # - P9K_PACKAGE_VERSION The value of `version` field in package.json. + # + # typeset -g POWERLEVEL9K_PACKAGE_CONTENT_EXPANSION='${P9K_PACKAGE_NAME//\%/%%}@${P9K_PACKAGE_VERSION//\%/%%}' + # Custom icon. + # typeset -g POWERLEVEL9K_PACKAGE_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #############[ rbenv: ruby version from rbenv (https://github.com/rbenv/rbenv) ]############## + # Rbenv color. + typeset -g POWERLEVEL9K_RBENV_FOREGROUND=168 + # Hide ruby version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global) + # If set to false, hide ruby version if it's the same as global: + # $(rbenv version-name) == $(rbenv global). + typeset -g POWERLEVEL9K_RBENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide ruby version if it's equal to "system". + typeset -g POWERLEVEL9K_RBENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_RBENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######################[ rvm: ruby version from rvm (https://rvm.io) ]######################## + # Rvm color. + typeset -g POWERLEVEL9K_RVM_FOREGROUND=168 + # Don't show @gemset at the end. + typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false + # Don't show ruby- at the front. + typeset -g POWERLEVEL9K_RVM_SHOW_PREFIX=false + # Custom icon. + # typeset -g POWERLEVEL9K_RVM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ fvm: flutter version management (https://github.com/leoafarias/fvm) ]############ + # Fvm color. + typeset -g POWERLEVEL9K_FVM_FOREGROUND=38 + # Custom icon. + # typeset -g POWERLEVEL9K_FVM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ luaenv: lua version from luaenv (https://github.com/cehoffman/luaenv) ]########### + # Lua color. + typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=32 + # Hide lua version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global) + # If set to false, hide lua version if it's the same as global: + # $(luaenv version-name) == $(luaenv global). + typeset -g POWERLEVEL9K_LUAENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide lua version if it's equal to "system". + typeset -g POWERLEVEL9K_LUAENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_LUAENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###############[ jenv: java version from jenv (https://github.com/jenv/jenv) ]################ + # Java color. + typeset -g POWERLEVEL9K_JENV_FOREGROUND=32 + # Hide java version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global) + # If set to false, hide java version if it's the same as global: + # $(jenv version-name) == $(jenv global). + typeset -g POWERLEVEL9K_JENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide java version if it's equal to "system". + typeset -g POWERLEVEL9K_JENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_JENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ plenv: perl version from plenv (https://github.com/tokuhirom/plenv) ]############ + # Perl color. + typeset -g POWERLEVEL9K_PLENV_FOREGROUND=67 + # Hide perl version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global) + # If set to false, hide perl version if it's the same as global: + # $(plenv version-name) == $(plenv global). + typeset -g POWERLEVEL9K_PLENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide perl version if it's equal to "system". + typeset -g POWERLEVEL9K_PLENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ perlbrew: perl version from perlbrew (https://github.com/gugod/App-perlbrew) ]############ + # Perlbrew color. + typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67 + # Show perlbrew version only when in a perl project subdirectory. + typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true + # Don't show "perl-" at the front. + typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false + # Custom icon. + # typeset -g POWERLEVEL9K_PERLBREW_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############ + # PHP color. + typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=99 + # Hide php version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_PHPENV_SOURCES=(shell local global) + # If set to false, hide php version if it's the same as global: + # $(phpenv version-name) == $(phpenv global). + typeset -g POWERLEVEL9K_PHPENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide php version if it's equal to "system". + typeset -g POWERLEVEL9K_PHPENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]####### + # Scala color. + typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160 + # Hide scala version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global) + # If set to false, hide scala version if it's the same as global: + # $(scalaenv version-name) == $(scalaenv global). + typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide scala version if it's equal to "system". + typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]########### + # Haskell color. + typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172 + # Hide haskell version if it doesn't come from one of these sources. + # + # shell: version is set by STACK_YAML + # local: version is set by stack.yaml up the directory tree + # global: version is set by the implicit global project (~/.stack/global-project/stack.yaml) + typeset -g POWERLEVEL9K_HASKELL_STACK_SOURCES=(shell local) + # If set to false, hide haskell version if it's the same as in the implicit global project. + typeset -g POWERLEVEL9K_HASKELL_STACK_ALWAYS_SHOW=true + # Custom icon. + # typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]############# + # Show kubecontext only when the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show kubecontext. + typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl' + + # Kubernetes context classes for the purpose of using different colors, icons and expansions with + # different contexts. + # + # POWERLEVEL9K_KUBECONTEXT_CLASSES is an array with even number of elements. The first element + # in each pair defines a pattern against which the current kubernetes context gets matched. + # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below) + # that gets matched. If you unset all POWERLEVEL9K_KUBECONTEXT_*CONTENT_EXPANSION parameters, + # you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_KUBECONTEXT_CLASSES defines the context class. Patterns are tried in order. The + # first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=( + # '*prod*' PROD + # '*test*' TEST + # '*' DEFAULT) + # + # If your current kubernetes context is "deathray-testing/default", its class is TEST + # because "deathray-testing/default" doesn't match the pattern '*prod*' but does match '*test*'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <' + typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=( + # '*prod*' PROD # These values are examples that are unlikely + # '*test*' TEST # to match your needs. Customize them as needed. + '*' DEFAULT) + typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_FOREGROUND=134 + # typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Use POWERLEVEL9K_KUBECONTEXT_CONTENT_EXPANSION to specify the content displayed by kubecontext + # segment. Parameter expansions are very flexible and fast, too. See reference: + # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion. + # + # Within the expansion the following parameters are always available: + # + # - P9K_CONTENT The content that would've been displayed if there was no content + # expansion defined. + # - P9K_KUBECONTEXT_NAME The current context's name. Corresponds to column NAME in the + # output of `kubectl config get-contexts`. + # - P9K_KUBECONTEXT_CLUSTER The current context's cluster. Corresponds to column CLUSTER in the + # output of `kubectl config get-contexts`. + # - P9K_KUBECONTEXT_NAMESPACE The current context's namespace. Corresponds to column NAMESPACE + # in the output of `kubectl config get-contexts`. If there is no + # namespace, the parameter is set to "default". + # - P9K_KUBECONTEXT_USER The current context's user. Corresponds to column AUTHINFO in the + # output of `kubectl config get-contexts`. + # + # If the context points to Google Kubernetes Engine (GKE) or Elastic Kubernetes Service (EKS), + # the following extra parameters are available: + # + # - P9K_KUBECONTEXT_CLOUD_NAME Either "gke" or "eks". + # - P9K_KUBECONTEXT_CLOUD_ACCOUNT Account/project ID. + # - P9K_KUBECONTEXT_CLOUD_ZONE Availability zone. + # - P9K_KUBECONTEXT_CLOUD_CLUSTER Cluster. + # + # P9K_KUBECONTEXT_CLOUD_* parameters are derived from P9K_KUBECONTEXT_CLUSTER. For example, + # if P9K_KUBECONTEXT_CLUSTER is "gke_my-account_us-east1-a_my-cluster-01": + # + # - P9K_KUBECONTEXT_CLOUD_NAME=gke + # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=my-account + # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east1-a + # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01 + # + # If P9K_KUBECONTEXT_CLUSTER is "arn:aws:eks:us-east-1:123456789012:cluster/my-cluster-01": + # + # - P9K_KUBECONTEXT_CLOUD_NAME=eks + # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=123456789012 + # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east-1 + # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01 + typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION= + # Show P9K_KUBECONTEXT_CLOUD_CLUSTER if it's not empty and fall back to P9K_KUBECONTEXT_NAME. + POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${P9K_KUBECONTEXT_CLOUD_CLUSTER:-${P9K_KUBECONTEXT_NAME}}' + # Append the current context's namespace if it's not "default". + POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${${:-/$P9K_KUBECONTEXT_NAMESPACE}:#/default}' + + # Custom prefix. + # typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat ' + + ################[ terraform: terraform workspace (https://www.terraform.io) ]################# + # Don't show terraform workspace if it's literally "default". + typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false + # POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element + # in each pair defines a pattern against which the current terraform workspace gets matched. + # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below) + # that gets matched. If you unset all POWERLEVEL9K_TERRAFORM_*CONTENT_EXPANSION parameters, + # you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_TERRAFORM_CLASSES defines the workspace class. Patterns are tried in order. The + # first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=( + # '*prod*' PROD + # '*test*' TEST + # '*' OTHER) + # + # If your current terraform workspace is "project_test", its class is TEST because "project_test" + # doesn't match the pattern '*prod*' but does match '*test*'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_TERRAFORM_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_TERRAFORM_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_TERRAFORM_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <' + typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=( + # '*prod*' PROD # These values are examples that are unlikely + # '*test*' TEST # to match your needs. Customize them as needed. + '*' OTHER) + typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38 + # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #############[ terraform_version: terraform version (https://www.terraform.io) ]############## + # Terraform version color. + typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38 + # Custom icon. + # typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]# + # Show aws only when the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show aws. + typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt' + + # POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element + # in each pair defines a pattern against which the current AWS profile gets matched. + # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below) + # that gets matched. If you unset all POWERLEVEL9K_AWS_*CONTENT_EXPANSION parameters, + # you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_AWS_CLASSES defines the profile class. Patterns are tried in order. The + # first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_AWS_CLASSES=( + # '*prod*' PROD + # '*test*' TEST + # '*' DEFAULT) + # + # If your current AWS profile is "company_test", its class is TEST + # because "company_test" doesn't match the pattern '*prod*' but does match '*test*'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_AWS_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_AWS_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_AWS_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <' + typeset -g POWERLEVEL9K_AWS_CLASSES=( + # '*prod*' PROD # These values are examples that are unlikely + # '*test*' TEST # to match your needs. Customize them as needed. + '*' DEFAULT) + typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208 + # typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # AWS segment format. The following parameters are available within the expansion. + # + # - P9K_AWS_PROFILE The name of the current AWS profile. + # - P9K_AWS_REGION The region associated with the current AWS profile. + typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}' + + #[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]# + # AWS Elastic Beanstalk environment color. + typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70 + # Custom icon. + # typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]########## + # Show azure only when the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show azure. + typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt' + + # POWERLEVEL9K_AZURE_CLASSES is an array with even number of elements. The first element + # in each pair defines a pattern against which the current azure account name gets matched. + # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below) + # that gets matched. If you unset all POWERLEVEL9K_AZURE_*CONTENT_EXPANSION parameters, + # you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_AZURE_CLASSES defines the account class. Patterns are tried in order. The + # first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_AZURE_CLASSES=( + # '*prod*' PROD + # '*test*' TEST + # '*' OTHER) + # + # If your current azure account is "company_test", its class is TEST because "company_test" + # doesn't match the pattern '*prod*' but does match '*test*'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_AZURE_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_AZURE_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_AZURE_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <' + typeset -g POWERLEVEL9K_AZURE_CLASSES=( + # '*prod*' PROD # These values are examples that are unlikely + # '*test*' TEST # to match your needs. Customize them as needed. + '*' OTHER) + + # Azure account name color. + typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=32 + # Custom icon. + # typeset -g POWERLEVEL9K_AZURE_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]########### + # Show gcloud only when the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show gcloud. + typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil' + # Google cloud color. + typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=32 + + # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or + # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative + # enough. You can use the following parameters in the expansions. Each of them corresponds to the + # output of `gcloud` tool. + # + # Parameter | Source + # -------------------------|-------------------------------------------------------------------- + # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)' + # P9K_GCLOUD_ACCOUNT | gcloud config get-value account + # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project + # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)' + # + # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'. + # + # Obtaining project name requires sending a request to Google servers. This can take a long time + # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud + # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets + # set and gcloud prompt segment transitions to state COMPLETE. + # + # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL + # and COMPLETE. You can also hide gcloud in state PARTIAL by setting + # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and + # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty. + typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}' + typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}' + + # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name + # this often. Negative value disables periodic polling. In this mode project name is retrieved + # only when the current configuration, account or project id changes. + typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60 + + # Custom icon. + # typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]# + # Show google_app_cred only when the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show google_app_cred. + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt' + + # Google application credentials classes for the purpose of using different colors, icons and + # expansions with different credentials. + # + # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES is an array with even number of elements. The first + # element in each pair defines a pattern against which the current kubernetes context gets + # matched. More specifically, it's P9K_CONTENT prior to the application of context expansion + # (see below) that gets matched. If you unset all POWERLEVEL9K_GOOGLE_APP_CRED_*CONTENT_EXPANSION + # parameters, you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES defines the context class. Patterns are tried in order. + # The first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=( + # '*:*prod*:*' PROD + # '*:*test*:*' TEST + # '*' DEFAULT) + # + # If your current Google application credentials is "service_account deathray-testing x@y.com", + # its class is TEST because it doesn't match the pattern '* *prod* *' but does match '* *test* *'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_CONTENT_EXPANSION='$P9K_GOOGLE_APP_CRED_PROJECT_ID' + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=( + # '*:*prod*:*' PROD # These values are examples that are unlikely + # '*:*test*:*' TEST # to match your needs. Customize them as needed. + '*' DEFAULT) + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=32 + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Use POWERLEVEL9K_GOOGLE_APP_CRED_CONTENT_EXPANSION to specify the content displayed by + # google_app_cred segment. Parameter expansions are very flexible and fast, too. See reference: + # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion. + # + # You can use the following parameters in the expansion. Each of them corresponds to one of the + # fields in the JSON file pointed to by GOOGLE_APPLICATION_CREDENTIALS. + # + # Parameter | JSON key file field + # ---------------------------------+--------------- + # P9K_GOOGLE_APP_CRED_TYPE | type + # P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id + # P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email + # + # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'. + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}' + + ##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]############### + # Toolbox color. + typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=178 + # Don't display the name of the toolbox if it matches fedora-toolbox-*. + typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}' + # Custom icon. + # typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='⭐' + # Custom prefix. + # typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%fin ' + + ###############################[ public_ip: public IP address ]############################### + # Public IP color. + typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94 + # Custom icon. + # typeset -g POWERLEVEL9K_PUBLIC_IP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ########################[ vpn_ip: virtual private network indicator ]######################### + # VPN IP color. + typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=81 + # When on VPN, show just an icon without the IP address. + # Tip: To display the private IP address when on VPN, remove the next line. + typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION= + # Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN + # to see the name of the interface. + typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)' + # If set to true, show one segment per matching network interface. If set to false, show only + # one segment corresponding to the first matching network interface. + # Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION. + typeset -g POWERLEVEL9K_VPN_IP_SHOW_ALL=false + # Custom icon. + # typeset -g POWERLEVEL9K_VPN_IP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ ip: ip address and bandwidth usage for a specified network interface ]########### + # IP color. + typeset -g POWERLEVEL9K_IP_FOREGROUND=38 + # The following parameters are accessible within the expansion: + # + # Parameter | Meaning + # ----------------------+------------------------------------------- + # P9K_IP_IP | IP address + # P9K_IP_INTERFACE | network interface + # P9K_IP_RX_BYTES | total number of bytes received + # P9K_IP_TX_BYTES | total number of bytes sent + # P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt + # P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt + # P9K_IP_RX_RATE | receive rate (since last prompt) + # P9K_IP_TX_RATE | send rate (since last prompt) + typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %70F⇣$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %215F⇡$P9K_IP_TX_RATE}' + # Show information for the first network interface whose name matches this regular expression. + # Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces. + typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*' + # Custom icon. + # typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #########################[ proxy: system-wide http/https/ftp proxy ]########################## + # Proxy color. + typeset -g POWERLEVEL9K_PROXY_FOREGROUND=68 + # Custom icon. + # typeset -g POWERLEVEL9K_PROXY_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################################[ battery: internal battery ]################################# + # Show battery in red when it's below this level and not connected to power supply. + typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20 + typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=160 + # Show battery in green when it's charging or fully charged. + typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=70 + # Show battery in yellow when it's discharging. + typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=178 + # Battery pictograms going from low to high level of charge. + typeset -g POWERLEVEL9K_BATTERY_STAGES='\UF008E\UF007A\UF007B\UF007C\UF007D\UF007E\UF007F\UF0080\UF0081\UF0082\UF0079' + # Don't show the remaining time to charge/discharge. + typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false + + #####################################[ wifi: wifi speed ]##################################### + # WiFi color. + typeset -g POWERLEVEL9K_WIFI_FOREGROUND=68 + # Custom icon. + # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Use different colors and icons depending on signal strength ($P9K_WIFI_BARS). + # + # # Wifi colors and icons for different signal strength levels (low to high). + # typeset -g my_wifi_fg=(68 68 68 68 68) # <-- change these values + # typeset -g my_wifi_icon=('WiFi' 'WiFi' 'WiFi' 'WiFi' 'WiFi') # <-- change these values + # + # typeset -g POWERLEVEL9K_WIFI_CONTENT_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}$P9K_WIFI_LAST_TX_RATE Mbps' + # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}${my_wifi_icon[P9K_WIFI_BARS+1]}' + # + # The following parameters are accessible within the expansions: + # + # Parameter | Meaning + # ----------------------+--------------- + # P9K_WIFI_SSID | service set identifier, a.k.a. network name + # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown + # P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second + # P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0 + # P9K_WIFI_NOISE | noise in dBm, from -120 to 0 + # P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE) + + ####################################[ time: current time ]#################################### + # Current time color. + typeset -g POWERLEVEL9K_TIME_FOREGROUND=66 + # Format for the current time: 09:51:02. See `man 3 strftime`. + typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' + # If set to true, time will update when you hit enter. This way prompts for the past + # commands will contain the start times of their commands as opposed to the default + # behavior where they contain the end times of their preceding commands. + typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false + # Custom icon. + typeset -g POWERLEVEL9K_TIME_VISUAL_IDENTIFIER_EXPANSION= + # Custom prefix. + # typeset -g POWERLEVEL9K_TIME_PREFIX='%fat ' + + # Example of a user-defined prompt segment. Function prompt_example will be called on every + # prompt if `example` prompt segment is added to POWERLEVEL9K_LEFT_PROMPT_ELEMENTS or + # POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS. It displays an icon and orange text greeting the user. + # + # Type `p10k help segment` for documentation and a more sophisticated example. + function prompt_example() { + p10k segment -f 208 -i '⭐' -t 'hello, %n' + } + + # User-defined prompt segments may optionally provide an instant_prompt_* function. Its job + # is to generate the prompt segment for display in instant prompt. See + # https://github.com/romkatv/powerlevel10k#instant-prompt. + # + # Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function + # and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k + # will replay these calls without actually calling instant_prompt_*. It is imperative that + # instant_prompt_* always makes the same `p10k segment` calls regardless of environment. If this + # rule is not observed, the content of instant prompt will be incorrect. + # + # Usually, you should either not define instant_prompt_* or simply call prompt_* from it. If + # instant_prompt_* is not defined for a segment, the segment won't be shown in instant prompt. + function instant_prompt_example() { + # Since prompt_example always makes the same `p10k segment` calls, we can call it from + # instant_prompt_example. This will give us the same `example` prompt segment in the instant + # and regular prompts. + prompt_example + } + + # User-defined prompt segments can be customized the same way as built-in segments. + # typeset -g POWERLEVEL9K_EXAMPLE_FOREGROUND=208 + # typeset -g POWERLEVEL9K_EXAMPLE_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt + # when accepting a command line. Supported values: + # + # - off: Don't change prompt when accepting a command line. + # - always: Trim down prompt when accepting a command line. + # - same-dir: Trim down prompt when accepting a command line unless this is the first command + # typed after changing current working directory. + typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off + + # Instant prompt mode. + # + # - off: Disable instant prompt. Choose this if you've tried instant prompt and found + # it incompatible with your zsh configuration files. + # - quiet: Enable instant prompt and don't print warnings when detecting console output + # during zsh initialization. Choose this if you've read and understood + # https://github.com/romkatv/powerlevel10k#instant-prompt. + # - verbose: Enable instant prompt and print a warning when detecting console output during + # zsh initialization. Choose this if you've never tried instant prompt, haven't + # seen the warning, or if you are unsure what this all means. + typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose + + # Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized. + # For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload + # can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you + # really need it. + typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true + + # If p10k is already loaded, reload configuration. + # This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true. + (( ! $+functions[p10k] )) || p10k reload +} + +# Tell `p10k configure` which file it should overwrite. +typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} + +(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} +'builtin' 'unset' 'p10k_config_opts' diff --git a/config/shell/zshrc b/config/shell/zshrc new file mode 100644 index 0000000..a1469e8 --- /dev/null +++ b/config/shell/zshrc @@ -0,0 +1,75 @@ +# ============================================================================= +# .zshrc — Julian's Zsh configuration +# Generated from Pop!_OS "thinkpad" machine audit. +# ============================================================================= +# This file is managed by the linux-provision repo. +# +# API keys and secrets are loaded from Bitwarden on shell startup. +# If Bitwarden is unlocked, keys are available immediately. +# If locked, bw-env fails silently and you just won't have env vars +# (run `bw && bw-env` when you need them). +# +# To set up: bw-env --setup (one-time interactive) +# See config/scripts/bw-env.sh for details. +# ============================================================================= + +# ---- Powerlevel10k instant prompt ---- +# Must stay close to the top. +if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then + source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" +fi + +# ---- PATH setup ---- +export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH + +# ---- Oh My Zsh ---- +export ZSH="$HOME/.oh-my-zsh" +ZSH_THEME="powerlevel10k/powerlevel10zsh" + +# ---- Plugins ---- +plugins=(git zsh-autosuggestions) + +source $ZSH/oh-my-zsh.sh + +# ---- Custom Aliases ---- + +# Bluetooth hard reset (ath11k WiFi/Bluetooth adapter hang workaround) +alias bt-reset='rfkill block bluetooth && sleep 1 && rfkill unblock bluetooth && sleep 1 && systemctl restart bluetooth' + +# ---- Key bindings ---- +# Ctrl+Backspace → delete word before cursor +bindkey "^H" backward-kill-word + +# ---- Powerlevel10k config ---- +[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh + +# ---- NVM (Node Version Manager) ---- +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + +# ---- Custom completions ---- +fpath+=~/.zfunc; autoload -Uz compinit; compinit + +# ============================================================================= +# Secrets — API keys and tokens +# Loaded from Bitwarden on every shell start. +# Fetches the "Environment" item and exports all custom fields as env vars. +# Falls back silently if vault is locked or item doesn't exist. +# +# Prerequisites: bw (Bitwarden CLI), jq +# To set up: +# 1. bw login +# 2. bw unlock --raw > ~/.config/Bitwarden\ CLI/.session +# 3. Create a Bitwarden item named "Environment" (type: Secure Note) +# with custom fields for each API key (name = VAR_NAME, value = the_key) +# ============================================================================= +if [ -z "${BW_SESSION:-}" ] && [ -f "$HOME/.config/Bitwarden CLI/.session" ]; then + export BW_SESSION=$(cat "$HOME/.config/Bitwarden CLI/.session") +fi +if [ -n "${BW_SESSION:-}" ]; then + eval "$(bw get item Environment 2>/dev/null | jq -r ' + .fields[] | select(.type != 0) | + "export " + (.name | gsub(" "; "_")) + "=" + (.value | @sh) + ' 2>/dev/null)" 2>/dev/null +fi diff --git a/config/shell/zshrc.local.example b/config/shell/zshrc.local.example new file mode 100644 index 0000000..563a317 --- /dev/null +++ b/config/shell/zshrc.local.example @@ -0,0 +1,65 @@ +# ============================================================================= +# ~/.zshrc.local — API keys and secrets (EXAMPLE) +# ===== THIS FILE IS OPTIONAL ===== +# The recommended approach is to store all secrets in Bitwarden and load +# them via bw-env.sh. See config/scripts/bw-env.sh for setup. +# +# If you prefer a plain file, copy this to ~/.zshrc.local and fill in +# your real keys. Then uncomment the "source ~/.zshrc.local" line in .zshrc. +# +# DO NOT commit this file to version control. +# ============================================================================= +# From the Pop machine audit, Julian stores ~17 API keys in his env. +# Using Bitwarden (bw-env.sh) is the recommended approach because: +# - Keys are encrypted at rest (not in dotfiles) +# - Syncs across machines automatically +# - One source of truth +# - Cache falls back silently if vault is locked +# ============================================================================= + +# ---- AI API Keys ---- +export GROQ_API_KEY="gsk_your_key_here" +export ANTHROPIC_API_KEY="sk-ant-your-key-here" +export GOOGLE_API_KEY="AIza-your-key-here" +export OPENCODE_API_KEY="sk-your-key-here" + +# ---- LiteLLM Proxy ---- +export OPENAI_BASE_URL="https://ai.julianprester.com" +export OPENAI_API_KEY="sk-your-key-here" + +# ---- Calendar (CalDAV) ---- +export CALDAV_URL="https://nc.julianprester.com/remote.php/dav" +export CALDAV_USERNAME="julian.prester@sydney.edu.au" +export CALDAV_PASSWORD="your-password-here" + +# ---- Thunderbird API ---- +export TB_API_HOSTS="thunderbird" + +# ---- Zotero ---- +export ZOTERO_KEY="" + +# ---- Canvas LMS ---- +export CANVAS_API_KEY="3156~your-key-here" +export CANVAS_API_URL="https://canvas.sydney.edu.au/" + +# ---- Nextcloud ---- +export NC_URL="https://nc.julianprester.com" +export NC_USERNAME="julian.prester@sydney.edu.au" +export NC_PASSWORD="your-password-here" + +# ---- Actual Budget ---- +export ACTUAL_URL="https://actual.your-domain.ts.net" +export ACTUAL_PASSWORD="your-password-here" +export ACTUAL_SYNC_ID="your-sync-id-here" + +# ---- Tavily (web search API) ---- +export TAVILY_API_KEY="tvly-your-key-here" + +# ---- FreshRSS ---- +export FRESHRSS_API_KEY="your-key-here" + +# ---- Semantic Scholar ---- +export SEMANTIC_SCHOLAR_API_KEY="your-key-here" + +# ---- OpenAlex ---- +export OPENALEX_API_KEY="your-key-here" diff --git a/config/systemd/bw-ssh-keys.service b/config/systemd/bw-ssh-keys.service new file mode 100644 index 0000000..1de6ea9 --- /dev/null +++ b/config/systemd/bw-ssh-keys.service @@ -0,0 +1,13 @@ +[Unit] +Description=Load Bitwarden SSH keys into ssh-agent +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=%h/.local/bin/bw-load-ssh.sh +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=default.target diff --git a/config/systemd/empty_downloads.service b/config/systemd/empty_downloads.service new file mode 100644 index 0000000..178acc2 --- /dev/null +++ b/config/systemd/empty_downloads.service @@ -0,0 +1,9 @@ +[Unit] +Description=Empty Downloads folder on login + +[Service] +Type=oneshot +ExecStart=/usr/bin/bash -c 'rm -rf %h/Downloads/* %h/Downloads/.* 2>/dev/null' + +[Install] +WantedBy=default.target diff --git a/config/systemd/mempi-sync.service b/config/systemd/mempi-sync.service new file mode 100644 index 0000000..c6c91ff --- /dev/null +++ b/config/systemd/mempi-sync.service @@ -0,0 +1,6 @@ +[Unit] +Description=Sync mempi database to Nextcloud + +[Service] +Type=oneshot +ExecStart=/bin/bash -c 'sqlite3 %h/.local/share/pi/pi.db "PRAGMA wal_checkpoint(TRUNCATE)" && cp %h/.local/share/pi/pi.db "%h/Nextcloud/2_resources/90-99 Misc/98 ocpa/mempi-pi.db"' diff --git a/config/systemd/mempi-sync.timer b/config/systemd/mempi-sync.timer new file mode 100644 index 0000000..757c875 --- /dev/null +++ b/config/systemd/mempi-sync.timer @@ -0,0 +1,8 @@ +[Unit] +Description=Sync mempi database on boot + +[Timer] +OnBootSec=5min + +[Install] +WantedBy=timers.target diff --git a/config/systemd/pi-overview.service b/config/systemd/pi-overview.service new file mode 100644 index 0000000..dde9e9c --- /dev/null +++ b/config/systemd/pi-overview.service @@ -0,0 +1,12 @@ +[Unit] +Description=Pi Overview — session dashboard +After=default.target + +[Service] +Environment=PATH=/home/julian/.nvm/versions/node/v24.11.1/bin:/usr/local/bin:/usr/bin:/bin +ExecStart=%h/.local/bin/pi-overview --port 3000 +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/config/systemd/porridge-dictate.service b/config/systemd/porridge-dictate.service new file mode 100644 index 0000000..f37d2de --- /dev/null +++ b/config/systemd/porridge-dictate.service @@ -0,0 +1,13 @@ +[Unit] +Description=Porridge dictate - push-to-talk transcription +After=graphical-session.target + +[Service] +Type=simple +EnvironmentFile=-%h/.config/porridge/env +ExecStart=%h/.local/bin/porridge dictate +Restart=on-failure +RestartSec=3 + +[Install] +WantedBy=default.target diff --git a/config/systemd/porridge.service b/config/systemd/porridge.service new file mode 100644 index 0000000..cd5c706 --- /dev/null +++ b/config/systemd/porridge.service @@ -0,0 +1,14 @@ +[Unit] +Description=Porridge - Zoom meeting transcriber daemon +After=graphical-session.target pulseaudio.service pipewire.service +Wants=graphical-session.target + +[Service] +Type=simple +EnvironmentFile=-%h/.config/porridge/env +ExecStart=%h/.local/bin/porridge daemon +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=graphical-session.target diff --git a/lib/distro.sh b/lib/distro.sh new file mode 100644 index 0000000..3430961 --- /dev/null +++ b/lib/distro.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# =========================================================================== +# Distribution Detection & Common Variables +# Sourced by 00-envcheck.sh, then available to all subsequent stages. +# +# After sourcing this, use these variables instead of hardcoding commands: +# $PKG_INSTALL — Install packages +# $PKG_UPDATE — Update package cache +# $PKG_REMOVE — Remove packages +# $PKG_GROUP_INSTALL — Install package group +# $DISTRO_FAMILY — "debian" or "fedora" +# $DISTRO_ID — "pop", "ubuntu", "fedora", etc. +# $DISTRO_VERSION — "24.04", "41", etc. +# See full list below. +# =========================================================================== + +# ---- Detect distribution ---- +DISTRO_ID="" +DISTRO_VERSION="" +DISTRO_FAMILY="" # "debian" (apt) or "fedora" (dnf) +DISTRO_LIKE="" # from ID_LIKE in os-release + +if [ -f /etc/os-release ]; then + . /etc/os-release + DISTRO_ID="$ID" + DISTRO_VERSION="$VERSION_ID" + DISTRO_LIKE="$ID_LIKE" +fi + +# ---- Determine distro family ---- +case "$DISTRO_ID" in + fedora|rhel|centos) + DISTRO_FAMILY="fedora" + ;; + pop|ubuntu|debian|linuxmint|elementary|zorin) + DISTRO_FAMILY="debian" + ;; + *) + # Fall back to ID_LIKE + case "$DISTRO_LIKE" in + *fedora*) DISTRO_FAMILY="fedora" ;; + *debian*) DISTRO_FAMILY="debian" ;; + *) DISTRO_FAMILY="unknown" ;; + esac + ;; +esac + +# ---- Fail early on unknown distro ---- +if [ "$DISTRO_FAMILY" = "unknown" ]; then + echo "[ERROR] Unknown distribution: $DISTRO_ID (ID_LIKE=$DISTRO_LIKE)" + echo " Edit lib/distro.sh to add support." + exit 1 +fi + +# =========================================================================== +# Package Manager Commands +# =========================================================================== +if [ "$DISTRO_FAMILY" = "debian" ]; then + PKG_MGR="apt" + PKG_UPDATE="sudo apt update" + PKG_INSTALL="sudo apt install -y" + PKG_INSTALL_NO_REC="sudo apt install -y --no-install-recommends" + PKG_REMOVE="sudo apt remove -y" + PKG_PURGE="sudo apt purge -y" + PKG_AUTOREMOVE="sudo apt autoremove -y" + PKG_SEARCH="apt search" + PKG_LIST_INSTALLED="dpkg -l" + + # Repo management + REPO_ADD_PPA="sudo add-apt-repository -y" + REPO_APT_KEY="sudo apt-key add -" # legacy, use signed-by when possible + REPO_ADD_LIST="sudo tee /etc/apt/sources.list.d" + + # Service / boot + SERVICE_ENABLE="sudo systemctl enable --now" + GRUB_UPDATE="sudo update-grub" + GRUB_FILE="/etc/default/grub" + + # Desktop environment detection + # Pop uses COSMIC; Ubuntu uses GNOME; Kubuntu uses KDE + case "$DISTRO_ID" in + pop) DEFAULT_DE="COSMIC" ;; + ubuntu|debian) DEFAULT_DE="GNOME" ;; + *) DEFAULT_DE="" ;; # unknown + esac + +elif [ "$DISTRO_FAMILY" = "fedora" ]; then + PKG_MGR="dnf" + PKG_UPDATE="sudo dnf makecache" + PKG_INSTALL="sudo dnf install -y" + PKG_INSTALL_NO_REC="sudo dnf install -y" # dnf has no --no-install-recommends equivalent + PKG_REMOVE="sudo dnf remove -y" + PKG_PURGE="sudo dnf remove -y" + PKG_AUTOREMOVE="sudo dnf autoremove -y" + PKG_SEARCH="dnf search" + PKG_LIST_INSTALLED="dnf list installed" + + # Repo management + REPO_ADD_COPR="sudo dnf copr enable -y" + REPO_ADD_RPM="sudo dnf config-manager --add-repo" + + # Service / boot + SERVICE_ENABLE="sudo systemctl enable --now" + GRUB_UPDATE="sudo grub2-mkconfig -o /boot/grub2/grub.cfg" + GRUB_EFI_UPDATE="sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg" + GRUB_FILE="/etc/default/grub" + + # Desktop environment detection + DEFAULT_DE="GNOME" # Fedora Workstation defaults to GNOME +fi + +# =========================================================================== +# Helper function: install package(s), silently skip if already installed +# =========================================================================== +pkg_install() { + if [ "$DISTRO_FAMILY" = "debian" ]; then + sudo apt install -y "$@" 2>/dev/null || return 1 + else + sudo dnf install -y "$@" 2>/dev/null || return 1 + fi +} + +# Wrapper for group installs (ignored on apt systems) +pkg_group_install() { + if [ "$DISTRO_FAMILY" = "fedora" ]; then + sudo dnf group install -y "$@" 2>/dev/null || return 1 + else + # On Debian, groups aren't a thing — just skip + return 0 + fi +} + +# =========================================================================== +# Package Name Mappings (Debian → Fedora and vice versa) +# Usage: pkg_name_fedora="foo" pkg_name_debian="bar" +# pkg_name "$pkg_name_fedora" "$pkg_name_debian" +# Or use the shorthand below for known differences. +# =========================================================================== + +# Known package name differences +resolve_pkg() { + local debian_name="$1" + local fedora_name="$2" + if [ "$DISTRO_FAMILY" = "debian" ]; then + echo "$debian_name" + else + echo "$fedora_name" + fi +} + +# Convenience: install a package that may have different names +pkg_install_mapped() { + local debian_name="$1" + local fedora_name="$2" + if [ "$DISTRO_FAMILY" = "debian" ]; then + pkg_install "$debian_name" + else + pkg_install "$fedora_name" + fi +} + +# Export all variables so sourced stages can use them +export DISTRO_FAMILY DISTRO_ID DISTRO_VERSION DISTRO_LIKE +export PKG_MGR PKG_UPDATE PKG_INSTALL PKG_INSTALL_NO_REC +export PKG_REMOVE PKG_PURGE PKG_AUTOREMOVE +export PKG_SEARCH PKG_LIST_INSTALLED +export SERVICE_ENABLE GRUB_UPDATE GRUB_EFI_UPDATE GRUB_FILE +export DEFAULT_DE + +# Export helpers +export -f pkg_install pkg_group_install pkg_install_mapped resolve_pkg diff --git a/provision.sh b/provision.sh new file mode 100755 index 0000000..9ed2482 --- /dev/null +++ b/provision.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +# =========================================================================== +# Linux Machine Provisioning — Master Orchestrator +# =========================================================================== +# Usage: +# bash provision.sh --all # Run all stages in order +# bash provision.sh --stage # Run a single stage +# bash provision.sh --list # List all stages +# source provision.sh --interactive # Source for manual calls +# +# Distribution-agnostic — detects Debian/Ubuntu/Pop/Fedora and adjusts +# package manager, repo config, etc. automatically. +# =========================================================================== + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +STAGES_DIR="${SCRIPT_DIR}/stages" +LIBS_DIR="${SCRIPT_DIR}/lib" +CONFIG_DIR="${SCRIPT_DIR}/config" + +# ---- Source distro detection first ---- +source "${LIBS_DIR}/distro.sh" + +# ---- Colour helpers ---- +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +info() { echo -e "${BLUE}[INFO]${NC} $*"; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +# ---- Available stages ---- +STAGES=( + "00-envcheck" # Environment checks (uses lib/distro.sh vars) + "01-repos" # Third-party repositories + "02-packages" # System packages + "03-toolchains" # nvm, Node, uv, Python + "04-shell" # Zsh, oh-my-zsh, p10k + "05-git" # Git config, SSH + "06-uv-projects" # Clone + install Julian's Python tools + "07-scripts" # ~/.local/bin custom scripts + "08-systemd" # User systemd services + "09-desktop" # Keybindings, hotkeys, desktop config + "10-docker" # Docker CE + "11-tweaks" # sysctl, kernel, TLP/powertop + "12-other-apps" # Chrome, Signal, Zotero +) + +# =========================================================================== +# Stage Runner +# =========================================================================== + +run_stage() { + local stage_name="$1" + local stage_file="${STAGES_DIR}/${stage_name}.sh" + + if [ ! -f "$stage_file" ]; then + error "Stage script not found: ${stage_file}" + return 1 + fi + + info "==============================================" + info " Running stage: ${stage_name}" + info "==============================================" + + source "$stage_file" + + ok "Stage '${stage_name}' completed." + echo "" +} + +# =========================================================================== +# Main +# =========================================================================== + +main() { + local mode="${1:-}" + + case "$mode" in + --all) + info "Starting full provisioning on ${DISTRO_ID} ${DISTRO_VERSION} (${DISTRO_FAMILY})" + info "You may be prompted for sudo password." + for stage in "${STAGES[@]}"; do + run_stage "$stage" || warn "Stage '${stage}' failed (continuing)." + done + ok "Provisioning complete!" + warn "Review TODO.md for manual post-install steps." + ;; + + --stage) + local target="${2:-}" + if [ -z "$target" ]; then + error "Usage: provision.sh --stage " + exit 1 + fi + run_stage "$target" + ;; + + --list) + echo "Available stages (distro: ${DISTRO_ID} / ${DISTRO_FAMILY}):" + for stage in "${STAGES[@]}"; do + echo " ${stage}" + done + ;; + + --interactive) + info "Sourcing stage functions for interactive use." + for stage in "${STAGES[@]}"; do + local f="${STAGES_DIR}/${stage}.sh" + [ -f "$f" ] && source "$f" + done + info "Stages loaded. Call: run_stage " + ;; + + *) + echo "Usage:" + echo " bash provision.sh --all # Run everything" + echo " bash provision.sh --stage # Run one stage" + echo " bash provision.sh --list # List stages" + echo " source provision.sh --interactive # Load for manual use" + exit 1 + ;; + esac +} + +main "$@" diff --git a/stages/00-envcheck.sh b/stages/00-envcheck.sh new file mode 100644 index 0000000..0e8060c --- /dev/null +++ b/stages/00-envcheck.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 00: Environment Checks +# Verifies we can proceed: distro detected, sudo available, dirs exist. +# Distro detection is handled by lib/distro.sh (sourced by provision.sh). +# =========================================================================== + +info "Distribution: ${DISTRO_ID} ${DISTRO_VERSION} (${DISTRO_FAMILY} family)" +info "Package manager: ${PKG_MGR}" + +# ---- Sudo access ---- +info "Checking sudo access..." +if ! sudo -n true 2>/dev/null; then + info "Sudo access required. You may be prompted for your password." + sudo -v || { error "Sudo required for provisioning."; exit 1; } +fi +ok "Sudo access confirmed." + +# Keep sudo timestamp fresh +while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null & + +# ---- Directory structure ---- +info "Setting up directory structure..." +mkdir -p "$HOME/Development" +mkdir -p "$HOME/.local/bin" +mkdir -p "$HOME/.config" +mkdir -p "$HOME/.local/share" +ok "Directory structure ready." + +# ---- Internet check ---- +info "Checking internet connectivity..." +if ! ping -c 1 -W 3 google.com &>/dev/null && ! ping -c 1 -W 3 github.com &>/dev/null; then + warn "No internet detected. Some steps may fail." +else + ok "Internet connectivity confirmed." +fi + +# ---- Package cache update (first time) ---- +info "Updating package cache (first run)..." +$PKG_UPDATE 2>/dev/null || warn "Package cache update had issues." + +ok "Stage 00 complete." diff --git a/stages/01-repos.sh b/stages/01-repos.sh new file mode 100644 index 0000000..73834a0 --- /dev/null +++ b/stages/01-repos.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 01: Third-Party Repositories +# Adds external package repos. Completely different per distro family. +# +# Debian/Ubuntu/Pop: uses PPAs and .list/.sources files in +# /etc/apt/sources.list.d/ +# Fedora: uses .repo files in /etc/yum.repos.d/ and COPRs +# =========================================================================== + +# ================================================================== +# COMMON: Add GPG keys (shared helper) +# ================================================================== +add_gpg_key() { + local url="$1" + local dest="$2" + if [ ! -f "$dest" ]; then + sudo curl -fsSL "$url" -o "$dest" 2>/dev/null || { + warn "Failed to download GPG key: $url" + return 1 + } + sudo chmod 644 "$dest" + fi +} + +# ================================================================== +# DEBIAN / UBUNTU / POP — APT-based +# ================================================================== +if [ "$DISTRO_FAMILY" = "debian" ]; then + + info "Configuring APT repositories..." + + # ---- VS Code ---- + info " Adding VS Code repo..." + if [ ! -f /etc/apt/sources.list.d/vscode.sources ]; then + add_gpg_key "https://packages.microsoft.com/keys/microsoft.asc" \ + "/usr/share/keyrings/microsoft.gpg" + cat << 'EOF' | sudo tee /etc/apt/sources.list.d/vscode.sources > /dev/null +Types: deb +URIs: https://packages.microsoft.com/repos/code +Suites: stable +Components: main +Architectures: amd64 +Signed-By: /usr/share/keyrings/microsoft.gpg +EOF + ok " VS Code repo added." + fi + + # ---- Google Chrome ---- + info " Adding Google Chrome repo..." + if [ ! -f /etc/apt/sources.list.d/google-chrome.sources ]; then + add_gpg_key "https://dl.google.com/linux/linux_signing_key.pub" \ + "/usr/share/keyrings/google-chrome.gpg" + cat << 'EOF' | sudo tee /etc/apt/sources.list.d/google-chrome.sources > /dev/null +Types: deb +URIs: https://dl.google.com/linux/chrome/deb/ +Suites: stable +Components: main +Architectures: amd64 +Signed-By: /usr/share/keyrings/google-chrome.gpg +EOF + ok " Google Chrome repo added." + fi + + # ---- Docker CE ---- + info " Adding Docker CE repo..." + if [ ! -f /etc/apt/sources.list.d/docker.list ]; then + add_gpg_key "https://download.docker.com/linux/${DISTRO_ID}/gpg" \ + "/etc/apt/keyrings/docker.asc" + echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${DISTRO_ID} ${DISTRO_VERSION_CODENAME:-$(. /etc/os-release && echo "$VERSION_CODENAME")} stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + ok " Docker CE repo added." + fi + + # ---- Tailscale ---- + info " Adding Tailscale repo..." + if [ ! -f /etc/apt/sources.list.d/tailscale.list ]; then + add_gpg_key "https://pkgs.tailscale.com/stable/${DISTRO_ID}/$(. /etc/os-release && echo "$VERSION_CODENAME").gz" \ + "/usr/share/keyrings/tailscale-archive-keyring.gpg" 2>/dev/null || \ + add_gpg_key "https://pkgs.tailscale.com/stable/${DISTRO_ID}/repo.gpg" \ + "/usr/share/keyrings/tailscale-archive-keyring.gpg" + echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/${DISTRO_ID} $(. /etc/os-release && echo "$VERSION_CODENAME") main" | \ + sudo tee /etc/apt/sources.list.d/tailscale.list > /dev/null + ok " Tailscale repo added." + fi + + # ---- Signal Desktop ---- + info " Adding Signal repo..." + if [ ! -f /etc/apt/sources.list.d/signal-desktop.sources ]; then + add_gpg_key "https://updates.signal.org/desktop/apt/keys.asc" \ + "/usr/share/keyrings/signal-desktop-keyring.gpg" + cat << 'EOF' | sudo tee /etc/apt/sources.list.d/signal-desktop.sources > /dev/null +Types: deb +URIs: https://updates.signal.org/desktop/apt +Suites: xenial +Components: main +Architectures: amd64 +Signed-By: /usr/share/keyrings/signal-desktop-keyring.gpg +EOF + ok " Signal repo added." + fi + + # ---- Papirus icon theme (PPA) ---- + info " Adding Papirus PPA..." + if [ ! -f /etc/apt/sources.list.d/papirus-ubuntu-papirus-*.sources ]; then + $REPO_ADD_PPA papirus/papirus 2>/dev/null && ok " Papirus PPA added." || warn " Papirus PPA failed." + fi + + # ---- Solaar (Logitech) PPA ---- + info " Adding Solaar PPA..." + if [ ! -f /etc/apt/sources.list.d/solaar-unifying-ubuntu-stable-*.sources ]; then + $REPO_ADD_PPA solaar-unifying/stable 2>/dev/null && ok " Solaar PPA added." || warn " Solaar PPA failed." + fi + + # ---- Ghostty terminal PPA ---- + info " Adding Ghostty PPA..." + $REPO_ADD_PPA ghostty/ghostty 2>/dev/null && ok " Ghostty PPA added." || warn " Ghostty PPA failed." + + # ---- Zotero repo ---- + info " Adding Zotero repo..." + if [ ! -f /etc/apt/sources.list.d/zotero.list ]; then + add_gpg_key "https://zotero.retorque.re/file/apt-package-archive/pubkey.gpg" \ + "/usr/share/keyrings/zotero-archive-keyring.gpg" + echo "deb [signed-by=/usr/share/keyrings/zotero-archive-keyring.gpg by-hash=force] https://zotero.retorque.re/file/apt-package-archive ./" | \ + sudo tee /etc/apt/sources.list.d/zotero.list > /dev/null + ok " Zotero repo added." + fi + + # ---- Yubico PPA ---- + info " Adding Yubico PPA..." + $REPO_ADD_PPA yubico/stable 2>/dev/null && ok " Yubico PPA added." || warn " Yubico PPA failed." + +# ================================================================== +# FEDORA / RHEL — DNF-based +# ================================================================== +elif [ "$DISTRO_FAMILY" = "fedora" ]; then + + info "Configuring DNF repositories..." + + # ---- RPM Fusion (free + nonfree) ---- + info " Enabling RPM Fusion..." + $PKG_INSTALL \ + "https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm" \ + "https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm" \ + 2>/dev/null && ok " RPM Fusion configured." || warn " RPM Fusion install failed." + + # ---- VS Code ---- + info " Adding VS Code repo..." + sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc 2>/dev/null || true + cat << 'EOF' | sudo tee /etc/yum.repos.d/vscode.repo > /dev/null +[code] +name=Visual Studio Code +baseurl=https://packages.microsoft.com/yumrepos/vscode +enabled=1 +gpgcheck=1 +gpgkey=https://packages.microsoft.com/keys/microsoft.asc +EOF + ok " VS Code repo added." + + # ---- Google Chrome ---- + info " Adding Google Chrome repo..." + cat << 'EOF' | sudo tee /etc/yum.repos.d/google-chrome.repo > /dev/null +[google-chrome] +name=Google Chrome +baseurl=https://dl.google.com/linux/chrome/rpm/stable/x86_64 +enabled=1 +gpgcheck=1 +gpgkey=https://dl.google.com/linux/linux_signing_key.pub +EOF + ok " Google Chrome repo added." + + # ---- Docker CE ---- + info " Adding Docker CE repo..." + $REPO_ADD_RPM https://download.docker.com/linux/fedora/docker-ce.repo 2>/dev/null && \ + ok " Docker repo added." || warn " Docker repo add failed." + + # ---- Tailscale ---- + info " Adding Tailscale repo..." + $REPO_ADD_RPM https://pkgs.tailscale.com/stable/fedora/tailscale.repo 2>/dev/null && \ + ok " Tailscale repo added." || warn " Tailscale repo add failed." + + # ---- Signal Desktop ---- + info " Adding Signal repo..." + cat << 'EOF' | sudo tee /etc/yum.repos.d/signal-desktop.repo > /dev/null +[signal-desktop] +name=Signal Desktop +baseurl=https://updates.signal.org/desktop/yum +enabled=1 +gpgcheck=1 +gpgkey=https://updates.signal.org/desktop/signal_pubkey.gpg +EOF + ok " Signal repo added." + + # ---- COPRs for extra packages ---- + # Papirus icon theme is in RPM Fusion nonfree. + # Solaar is in RPM Fusion. + # Yubico tools: use COPR + info " Adding COPR repos..." + # $REPO_ADD_COPR atim/papirus-icon-theme 2>/dev/null || true + # $REPO_ADD_COPR sergiomb/Solaar 2>/dev/null || true + + # ---- Zotero — no DNF repo, use Flatpak (handled in stage 13) ---- + info " Note: Zotero will be installed via Flatpak or tarball in stage 13." +fi + +# ---- Update package cache after adding repos ---- +info "Updating package cache..." +$PKG_UPDATE 2>/dev/null || warn "Package cache update had issues." +ok "Stage 01 complete: repositories configured." diff --git a/stages/02-packages.sh b/stages/02-packages.sh new file mode 100644 index 0000000..16697c0 --- /dev/null +++ b/stages/02-packages.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 02: System Packages +# Installs all non-default packages — distro-agnostic. +# +# Package names that differ between Debian and Fedora are handled via +# pkg_install_mapped() or conditional branches. +# Same-name packages are installed with pkg_install(). +# =========================================================================== + +info "Installing system packages (this may take a while)..." + +# =========================================================================== +# A. Development tools & compilers +# =========================================================================== +echo " Development tools..." +pkg_group_install "Development Tools" # Fedora only + +# Common dev packages (same name on both) +pkg_install cmake + +# Differently-named dev packages +pkg_install_mapped "build-essential" "@development-tools" +pkg_install_mapped "g++" "gcc-c++" + +# Kernel headers +if [ "$DISTRO_FAMILY" = "debian" ]; then + pkg_install "linux-headers-$(uname -r)" 2>/dev/null || pkg_install linux-headers-generic +else + pkg_install kernel-devel kernel-headers +fi + +pkg_install dkms + +# =========================================================================== +# B. CLI utilities +# =========================================================================== +echo " CLI utility packages..." + +# Same-name CLI tools +pkg_install \ + ripgrep \ + fd-find \ + jq \ + just \ + ffmpeg \ + wl-clipboard \ + wtype \ + wofi + +# fd-find may need a symlink (binary is fdfind on both distros) +if command -v fdfind &>/dev/null && ! command -v fd &>/dev/null; then + if [ "$DISTRO_FAMILY" = "debian" ]; then + sudo ln -sf "$(which fdfind)" /usr/local/bin/fd 2>/dev/null || true + else + sudo ln -sf "$(which fdfind)" /usr/local/bin/fd 2>/dev/null || true + fi +fi + +# Differently-named +pkg_install_mapped "imagemagick" "ImageMagick" + +# =========================================================================== +# C. Media & graphics +# =========================================================================== +echo " Media & graphics packages..." +pkg_install gimp vlc + +# =========================================================================== +# D. Fonts +# =========================================================================== +echo " Font packages..." +if [ "$DISTRO_FAMILY" = "debian" ]; then + pkg_install fonts-powerline fonts-noto-cjk fonts-noto-cjk-extra \ + fonts-noto-core fonts-noto-ui-core 2>/dev/null || true +else + pkg_install powerline-fonts google-noto-cjk-fonts \ + google-noto-fonts-common 2>/dev/null || true +fi + +# =========================================================================== +# E. System tools +# =========================================================================== +echo " System tool packages..." +pkg_install \ + powertop \ + smartmontools \ + solaar 2>/dev/null || warn " Some system tools failed." + +# TLP — laptop power management +if [ "$DISTRO_FAMILY" = "debian" ]; then + pkg_install tlp 2>/dev/null || warn " tlp not available." +else + pkg_install tlp tlp-rdw 2>/dev/null || warn " tlp not available." +fi + +# =========================================================================== +# F. General utilities +# =========================================================================== +echo " General utility packages..." +pkg_install \ + rsync \ + curl \ + wget \ + unzip \ + p7zip 2>/dev/null || true + +# ---- Ghostty terminal emulator ---- +# Fedora: in official repos since F40 +# Ubuntu/Pop: via PPA (added in stage 01) +echo " Ghostty terminal emulator..." +pkg_install ghostty 2>/dev/null || warn " ghostty install failed." + +# ---- VS Code ---- +echo " VS Code..." +pkg_install code 2>/dev/null || warn " code install failed." + +# =========================================================================== +# Start enabled services +# =========================================================================== +if command -v tlp &>/dev/null; then + $SERVICE_ENABLE tlp 2>/dev/null || true +fi + +ok "Stage 02 complete: system packages installed." diff --git a/stages/03-toolchains.sh b/stages/03-toolchains.sh new file mode 100644 index 0000000..7354a11 --- /dev/null +++ b/stages/03-toolchains.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 03: Language Toolchains +# Installs nvm + Node.js LTS, uv (Python), and sets up the toolchain. +# =========================================================================== +# On Pop/Ubuntu these are post-system-package installs. +# Fedora packages for Node/python are available but we use version managers +# for flexibility (nvm for Node, uv for Python). +# =========================================================================== + +# ---- nvm + Node.js ---- +# nvm is installed to ~/.nvm. Node LTS is installed and set as default. +# This matches the Pop machine which had v24.11.1 via nvm. +info "Installing nvm (Node Version Manager)..." +if [ -d "$HOME/.nvm" ] && [ -f "$HOME/.nvm/nvm.sh" ]; then + ok "nvm already installed." +else + # Install latest nvm + # curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash + # Use the install script from nvm's GitHub + export NVM_DIR="$HOME/.nvm" + curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash + ok "nvm installed." +fi + +# Source nvm for this script +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + +# Install latest LTS Node +info "Installing Node.js LTS via nvm..." +nvm install --lts 2>/dev/null || { + warn "nvm install --lts failed. Trying specific version..." + nvm install 24 2>/dev/null || warn "Could not install Node via nvm." +} +nvm alias default 'lts/*' 2>/dev/null || true +ok "Node.js $(node --version 2>/dev/null || echo 'installed')" + +# Install/update npm to latest +npm install -g npm@latest 2>/dev/null || true +ok "npm $(npm --version 2>/dev/null || echo 'installed')" + +# ---- npm global packages ---- +# Julian's daily tools: pi agent, browser automation, subagents +info "Installing npm global packages..." +npm install -g \ + @earendil-works/pi-coding-agent \ + agent-browser \ + pi-subagents \ + 2>/dev/null || warn "Some npm global packages failed." +ok "npm global packages installed." + +# ---- uv (Python toolchain) ---- +# uv is the recommended Python package + project manager. +# On Pop: uv was installed standalone from astral.sh — binary lives in ~/.local/bin +# The binary is self-updating via 'uv self update'. +info "Installing uv (Python project manager)..." +if command -v uv &>/dev/null; then + ok "uv already installed: $(uv --version)" + # Self-update to latest + uv self update 2>/dev/null || true +else + curl -fsSL https://astral.sh/uv/install.sh | bash + # Ensure ~/.local/bin is in PATH + export PATH="$HOME/.local/bin:$PATH" + ok "uv installed: $(uv --version)" +fi + +# Install system Python if not present (Fedora usually has it) +if ! command -v python3 &>/dev/null; then + info "Installing Python..." + pkg_install python3 python3-pip python3-devel 2>/dev/null || true +fi + +# ---- uv tool installs (from PyPI) ---- +# Julian's daily Python CLI tools, installed via uv. +info "Installing uv tools from PyPI..." +if command -v uv &>/dev/null; then + uv tool install markitdown 2>/dev/null && echo " markitdown" || warn " markitdown install failed." + uv tool install pre-commit 2>/dev/null && echo " pre-commit" || warn " pre-commit install failed." + uv tool install yq 2>/dev/null && echo " yq (tomlq, xq)" || warn " yq install failed." + ok "PyPI uv tools installed." +fi + +# ---- (Optional) Rust toolchain ---- +# The Pop machine did NOT have Rust installed. Uncomment if you want it. +# info "Installing Rust via rustup..." +# if command -v rustc &>/dev/null; then +# ok "Rust already installed: $(rustc --version)" +# else +# curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +# . "$HOME/.cargo/env" +# ok "Rust installed: $(rustc --version)" +# fi + +# ---- (Optional) Go toolchain ---- +# The Pop machine did NOT have Go installed. Uncomment if you want it. +# info "Installing Go..." +# sudo dnf install -y golang 2>/dev/null || warn "Go install failed." +# ok "Go installed: $(go version)" + +ok "Stage 03 complete: language toolchains installed." diff --git a/stages/04-shell.sh b/stages/04-shell.sh new file mode 100644 index 0000000..e90ea89 --- /dev/null +++ b/stages/04-shell.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 04: Shell Configuration (zsh, oh-my-zsh, powerlevel10k) +# Deploys .zshrc, .p10k.zsh, and .zshrc.local (from config/shell/). +# =========================================================================== +# On the Pop machine, Julian uses: +# - zsh as default shell +# - oh-my-zsh with powerlevel10k theme (lean style, 1 line) +# - Plugin: git, zsh-autosuggestions +# - Custom aliases: bt-reset (Bluetooth) +# - 17 environment variables (API keys) +# - NVM integration +# - Ctrl+Backspace → backward-kill-word +# +# API keys go into ~/.zshrc.local (NOT tracked in this repo). +# See config/shell/zshrc.local.example for the template. +# =========================================================================== + +# ---- Install zsh ---- +info "Installing zsh..." +if command -v zsh &>/dev/null; then + ok "zsh already installed: $(zsh --version | head -1)" +else + pkg_install zsh 2>/dev/null +fi + +# ---- Install oh-my-zsh ---- +info "Installing Oh My Zsh..." +if [ -d "$HOME/.oh-my-zsh" ]; then + ok "Oh My Zsh already installed." +else + # Non-interactive install (no chsh prompt) + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended 2>/dev/null || { + warn "Oh My Zsh install failed; trying without unattended flag." + # Fallback: set ZSH and clone directly + export ZSH="$HOME/.oh-my-zsh" + git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "$ZSH" 2>/dev/null || true + } + ok "Oh My Zsh installed." +fi + +# ---- Install Powerlevel10k theme ---- +info "Installing Powerlevel10k theme..." +if [ -d "${ZSH:-$HOME/.oh-my-zsh}/custom/themes/powerlevel10k" ]; then + ok "Powerlevel10k already installed." +else + git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \ + "${ZSH:-$HOME/.oh-my-zsh}/custom/themes/powerlevel10k" 2>/dev/null || warn "p10k clone failed." +fi + +# ---- Install zsh-autosuggestions plugin ---- +info "Installing zsh-autosuggestions plugin..." +if [ -d "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" ]; then + ok "zsh-autosuggestions already installed." +else + git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions \ + "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" 2>/dev/null || warn "clone failed." +fi + +# ---- Deploy .zshrc ---- +info "Deploying .zshrc..." +CONFIG_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/config" +if [ -f "$HOME/.zshrc" ]; then + # Backup existing + cp "$HOME/.zshrc" "$HOME/.zshrc.bak.$(date +%Y%m%d)" 2>/dev/null + warn "Backed up existing .zshrc to .zshrc.bak.$(date +%Y%m%d)" +fi +cp "${CONFIG_DIR}/shell/zshrc" "$HOME/.zshrc" +ok ".zshrc deployed." + +# ---- Deploy .p10k.zsh ---- +info "Deploying .p10k.zsh..." +if [ -f "$HOME/.p10k.zsh" ]; then + cp "$HOME/.p10k.zsh" "$HOME/.p10k.zsh.bak.$(date +%Y%m%d)" 2>/dev/null + warn "Backed up existing .p10k.zsh" +fi +cp "${CONFIG_DIR}/shell/p10k.zsh" "$HOME/.p10k.zsh" +ok ".p10k.zsh deployed." + +# ---- Deploy .zshrc.local (secrets template) ---- +# This file should contain your API keys. The example file has placeholders. +# It is NOT sourced by default. To enable, uncomment the "source ~/.zshrc.local" +# line in your deployed .zshrc. +info "Installing .zshrc.local example..." +if [ ! -f "$HOME/.zshrc.local" ]; then + cp "${CONFIG_DIR}/shell/zshrc.local.example" "$HOME/.zshrc.local" + warn "Created ~/.zshrc.local from example. EDIT IT with your API keys." +else + ok ".zshrc.local already exists (keeping existing)." +fi + +# ---- Change default shell to zsh ---- +info "Setting zsh as default shell..." +if [ "$SHELL" != "$(which zsh)" ]; then + chsh -s "$(which zsh)" 2>/dev/null || warn "Could not change shell (chsh)." + ok "Default shell set to zsh. Log out and back in to activate." +else + ok "zsh is already the default shell." +fi + +ok "Stage 04 complete: shell configured." diff --git a/stages/05-git.sh b/stages/05-git.sh new file mode 100644 index 0000000..c668f13 --- /dev/null +++ b/stages/05-git.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 05: Git Configuration & SSH Keys +# Deploys .gitconfig and optionally generates SSH keys. +# =========================================================================== +# The Pop machine's .gitconfig is well-optimised: +# - SSH key signing (gpg.format = ssh) +# - zdiff3 conflict style, histogram diff algorithm +# - rerere.enabled, autoSquash, autoStash +# - push.autoSetupRemote, pull.rebase, fetch.prune +# =========================================================================== + +CONFIG_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/config" + +# ---- Deploy .gitconfig ---- +info "Deploying .gitconfig..." +if [ -f "$HOME/.gitconfig" ]; then + cp "$HOME/.gitconfig" "$HOME/.gitconfig.bak.$(date +%Y%m%d)" 2>/dev/null + warn "Backed up existing .gitconfig" +fi + +# Use the template from config/git/gitconfig +# NOTE: This template does NOT contain your signing key or email. +# Edit it after deployment to set: +# [user] +# name = Your Name +# email = your.email@example.com +# signingkey = +cp "${CONFIG_DIR}/git/gitconfig" "$HOME/.gitconfig" +ok ".gitconfig deployed." +warn "REMINDER: Edit ~/.gitconfig to set your name, email, and signingkey." + +# ---- Deploy .gitignore_global ---- +info "Deploying global .gitignore..." +if [ -f "$HOME/.gitignore" ]; then + warn "Global .gitignore already exists (keeping)." +else + # A sensible global gitignore for common OS + editor files + cat > "$HOME/.gitignore" << 'EOF' +# OS files +.DS_Store +Thumbs.db +Desktop.ini + +# Editor/IDE +*.swp +*.swo +*~ +.vscode/ +.idea/ +*.sublime-* + +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +.venv/ +.eggs/ + +# Node +node_modules/ +.npm/ + +# Rust +target/ +EOF + ok "Global .gitignore deployed." +fi + +# ---- Git SSH key ---- +# The Pop machine has a single SSH key: id_ed25519_github +# Loading is handled by bw-load-ssh.sh (stage 07) from Bitwarden. +# This stage can generate a new key if one doesn't exist. +info "Checking SSH keys..." +if [ ! -f "$HOME/.ssh/id_ed25519" ]; then + warn "No SSH key found." + warn "Options:" + warn " 1. Generate a new key (recommended for new machine):" + warn " ssh-keygen -t ed25519 -C 'hi@julianprester.com'" + warn " 2. Restore from backup/Bitwarden (see TODO.md)" + warn " 3. Skip for now (run bw-load-ssh.sh later to load from Bitwarden)" + echo "" + read -r -p "Generate a new SSH key now? [y/N] " response + if [[ "$response" =~ ^[Yy]$ ]]; then + ssh-keygen -t ed25519 -C "hi@julianprester.com" -f "$HOME/.ssh/id_ed25519" -N "" 2>/dev/null || { + warn "Key generation skipped or failed." + } + ok "SSH key generated: ~/.ssh/id_ed25519.pub" + cat "$HOME/.ssh/id_ed25519.pub" + warn "Add this key to GitHub: https://github.com/settings/keys" + fi +else + ok "SSH key already exists." +fi + +# Ensure proper SSH permissions +chmod 700 "$HOME/.ssh" 2>/dev/null || true +find "$HOME/.ssh" -type f -name "id_*" -exec chmod 600 {} \; 2>/dev/null || true +find "$HOME/.ssh" -type f -name "*.pub" -exec chmod 644 {} \; 2>/dev/null || true + +ok "Stage 05 complete: Git configured." diff --git a/stages/06-uv-projects.sh b/stages/06-uv-projects.sh new file mode 100644 index 0000000..06a285d --- /dev/null +++ b/stages/06-uv-projects.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 06: Julian's uv Python Tools — Clone & Install +# Clones all custom Python tool repos (from GitHub) into ~/Development/ +# and installs them via 'uv tool install' (editable mode from local path). +# =========================================================================== +# These are Julian's own CLI tools — the ones installed on Pop via uv. +# Each has a remote on github.com/julianprester/ (or re3-work/) and a +# pyproject.toml defining the package. +# +# Tools without a public remote (oracle, panac, skill-eval, mondada) are +# noted — you'll need to push them to GitHub or copy them manually. +# +# Order: tools that depend on other tools should come after. Most are +# independent Python packages. +# =========================================================================== + +# Ensure uv is in PATH +export PATH="$HOME/.local/bin:$PATH" + +# ---- Define tool repos ---- +# Format: "repo_name:github_org:has_pyproject:has_package_json" +# repo_name = directory name under ~/Development/ +# github_org = GitHub org (julianprester or re3-work) +# has_pyproject = true if it has pyproject.toml and should be uv-installed +# has_package_json = true if it has package.json and should be npm-linked + +TOOLS=( + "porridge:julianprester:true:false" # Zoom meeting transcriber daemon + "deepis:julianprester:true:false" # Literature discovery CLI + "pi-persist:julianprester:true:false" # Memory persistence (mempi, pi-overview) + "panac:julianprester:true:false" # Pandoc wrapper CLI + "gromd:julianprester:true:false" # Gromd tool + "kannwas:julianprester:true:false" # Kannwas tool + "tb-api:julianprester:false:false" # Thunderbird REST API (not a Python/npm pkg — Firefox addon) + "hotkeys:julianprester:false:false" # Shell scripts for Wayland hotkeys (no install needed) + "ocpa:julianprester:true:false" # OpenCode pi agent Python package +) + +# =========================================================================== +info "Cloning & installing Julian's Python tools..." + +mkdir -p "$HOME/Development" + +# ---- Clone and install each tool ---- +for tool_entry in "${TOOLS[@]}"; do + IFS=':' read -r name org has_pyproject has_package_json <<< "$tool_entry" + target_dir="$HOME/Development/$name" + + if [ -d "$target_dir" ]; then + ok "Repo '$name' already cloned. Pulling latest..." + git -C "$target_dir" pull --ff-only 2>/dev/null || warn "Could not pull $name." + else + info "Cloning $org/$name..." + git clone "git@github.com:${org}/${name}.git" "$target_dir" 2>/dev/null || { + warn "Clone failed for ${org}/${name}. Trying HTTPS fallback..." + git clone "https://github.com/${org}/${name}.git" "$target_dir" 2>/dev/null || { + warn "Could not clone ${org}/${name}. SSH keys not set up? Skipping." + continue + } + } + ok "Cloned $org/$name → $target_dir" + fi + + # Install via uv if it has pyproject.toml + if [ "$has_pyproject" = "true" ] && [ -f "$target_dir/pyproject.toml" ]; then + info "Installing '$name' via uv tool..." + # Try editable install from local path; fall back to non-editable + uv tool install --editable "$target_dir" 2>/dev/null || \ + uv tool install "$target_dir" 2>/dev/null || \ + warn "uv tool install failed for '$name'. Check pyproject.toml." + ok "'$name' installed via uv." + fi +done + +# ---- Verify installations ---- +echo "" +info "Verifying uv tool installations..." +uv tool list 2>/dev/null || warn "No uv tools installed." + +ok "Stage 06 complete: uv tools installed." diff --git a/stages/07-scripts.sh b/stages/07-scripts.sh new file mode 100644 index 0000000..4dc5b33 --- /dev/null +++ b/stages/07-scripts.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 07: Custom Scripts (~/.local/bin/) +# Deploys Julian's custom scripts: Bitwarden SSH loader, Zoom wrapper, +# idle battery suspend, env PATH helper, and more. +# =========================================================================== +# These are the "glue" scripts that make the desktop work the way Julian +# expects. They were found in ~/.local/bin/ on the Pop machine. +# +# Config templates are in config/scripts/ +# =========================================================================== + +CONFIG_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/config" +SCRIPTS_DIR="${CONFIG_DIR}/scripts" +TARGET_DIR="$HOME/.local/bin" + +mkdir -p "$TARGET_DIR" + +info "Deploying custom scripts to $TARGET_DIR..." + +# ---- 1. env.sh — PATH helper (sourced by .profile / .bashrc) ---- +# Ensures ~/.local/bin is in PATH without duplicate entries. +if [ -f "$SCRIPTS_DIR/env.sh" ]; then + cp "$SCRIPTS_DIR/env.sh" "$TARGET_DIR/env" + chmod +x "$TARGET_DIR/env" + ok "env deployed." +fi + +# ---- 2. bw-load-ssh.sh — Load SSH keys from Bitwarden ---- +# Script that fetches SSH keys from Bitwarden vault and loads into ssh-agent. +# Depends on: bw (Bitwarden CLI), jq, ssh-agent running. +if [ -f "$SCRIPTS_DIR/bw-load-ssh.sh" ]; then + cp "$SCRIPTS_DIR/bw-load-ssh.sh" "$TARGET_DIR/bw-load-ssh.sh" + chmod +x "$TARGET_DIR/bw-load-ssh.sh" + ok "bw-load-ssh.sh deployed." +fi + +# ---- 3. zoom.sh — Zoom wrapper for Wayland + AMD GPU ---- +# Forces Wayland native mode and VAAPI hardware video decoding on AMD Radeon. +# Without this, Zoom would use XWayland and software decoding (bad perf). +if [ -f "$SCRIPTS_DIR/zoom.sh" ]; then + cp "$SCRIPTS_DIR/zoom.sh" "$TARGET_DIR/zoom" + chmod +x "$TARGET_DIR/zoom" + ok "zoom wrapper deployed." +else + # Create default Zoom wrapper + cat > "$TARGET_DIR/zoom" << 'SCRIPT' +#!/bin/bash +# Zoom wrapper — forces Wayland + HW acceleration on AMD GPU +export QT_QPA_PLATFORM=wayland +export LIBVA_DRIVER_NAME=radeonsi +export LIBVA_DRI3_DISABLE=0 +exec /usr/bin/zoom "$@" +SCRIPT + chmod +x "$TARGET_DIR/zoom" + ok "zoom wrapper created (default)." +fi + +# ---- 4. idle-battery-suspend.sh — Suspend on battery after idle ---- +# Checks if AC is disconnected before suspending. Prevents suspend on desktop. +# Used by swayidle.service (stage 08). +if [ -f "$SCRIPTS_DIR/idle-battery-suspend.sh" ]; then + cp "$SCRIPTS_DIR/idle-battery-suspend.sh" "$TARGET_DIR/idle-battery-suspend.sh" + chmod +x "$TARGET_DIR/idle-battery-suspend.sh" + ok "idle-battery-suspend.sh deployed." +fi + +# ---- 5. Bitwarden CLI (bw) ---- +# On Pop: ~/.local/bin/bw (138 MB standalone binary) +if ! command -v bw &>/dev/null; then + info "Installing Bitwarden CLI..." + # bw is a standalone binary — download it + BW_LATEST=$(curl -fsSL "https://api.github.com/repos/bitwarden/clients/releases?per_page=1" | grep -oP '"tag_name":\s*"cli-v\K[^"]+' | head -1) || BW_LATEST="2025.1.0" + curl -fsSL "https://github.com/bitwarden/clients/releases/download/cli-v${BW_LATEST}/bw-linux-${BW_LATEST}.zip" -o /tmp/bw.zip 2>/dev/null && { + unzip -o /tmp/bw.zip -d /tmp/bw-extract 2>/dev/null + cp /tmp/bw-extract/bw "$TARGET_DIR/bw" + chmod +x "$TARGET_DIR/bw" + rm -rf /tmp/bw.zip /tmp/bw-extract + ok "Bitwarden CLI installed." + } || warn "Bitwarden CLI download failed. Install manually: https://bitwarden.com/help/cli/" +fi + +# Ensure permissions +chmod -R 755 "$TARGET_DIR" 2>/dev/null || true + +ok "Stage 07 complete: custom scripts deployed to ~/.local/bin." diff --git a/stages/08-systemd.sh b/stages/08-systemd.sh new file mode 100644 index 0000000..7a54b26 --- /dev/null +++ b/stages/08-systemd.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 08: User Systemd Services +# Deploys and enables Julian's custom user systemd services. +# =========================================================================== +# On the Pop machine, Julian runs several custom services: +# - porridge.service : Zoom meeting transcriber daemon +# - porridge-dictate.service : Push-to-talk transcription +# - pi-overview.service : Session dashboard on port 3000 +# - bw-ssh-keys.service : Load Bitwarden SSH keys at boot +# - mempi-sync.service : Sync memory DB to Nextcloud +# - mempi-sync.timer : Run mempi-sync on boot +5min +# - empty_downloads.service : Clear Downloads folder at login +# =========================================================================== + +CONFIG_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/config" +SERVICES_DIR="${CONFIG_DIR}/systemd" +UNIT_DIR="$HOME/.config/systemd/user" + +mkdir -p "$UNIT_DIR" + +info "Deploying user systemd services..." + +# ---- Helper: install service file ---- +install_service_file() { + local src="$1" + local name="$2" + if [ -f "$src" ]; then + cp "$src" "$UNIT_DIR/$name" + ok "Installed: $name" + else + warn "Service file not found: $src (skipping)" + fi +} + +# ---- 1. porridge.service — Zoom transcriber daemon ---- +install_service_file "$SERVICES_DIR/porridge.service" "porridge.service" + +# ---- 2. porridge-dictate.service — Push-to-talk transcription ---- +install_service_file "$SERVICES_DIR/porridge-dictate.service" "porridge-dictate.service" + +# ---- 3. pi-overview.service — Session dashboard ---- +install_service_file "$SERVICES_DIR/pi-overview.service" "pi-overview.service" + +# ---- 4. bw-ssh-keys.service — Load Bitwarden SSH keys at boot ---- +install_service_file "$SERVICES_DIR/bw-ssh-keys.service" "bw-ssh-keys.service" + +# ---- 5. mempi-sync.service + timer — Sync memory DB to Nextcloud ---- +install_service_file "$SERVICES_DIR/mempi-sync.service" "mempi-sync.service" +install_service_file "$SERVICES_DIR/mempi-sync.timer" "mempi-sync.timer" + +# ---- 6. empty_downloads.service — Clear Downloads at login ---- +install_service_file "$SERVICES_DIR/empty_downloads.service" "empty_downloads.service" + +# ---- Enable and start services ---- +info "Enabling and starting services..." + +# Services that should start automatically (enabled) +systemctl --user daemon-reload + +# Check which scripts from stage 07 are available before enabling services. +# This avoids failures when running stages out of order. + +if [ -x "$HOME/.local/bin/porridge" ]; then + systemctl --user enable --now porridge.service 2>/dev/null && ok "porridge.service enabled" +else + warn "porridge.service skipped (binary not found — run stage 07 first)." +fi + +if [ -x "$HOME/.local/bin/porridge" ]; then + systemctl --user enable --now porridge-dictate.service 2>/dev/null && ok "porridge-dictate.service enabled" +else + warn "porridge-dictate.service skipped (binary not found — run stage 07 first)." +fi + +if [ -x "$HOME/.local/bin/pi-overview" ]; then + systemctl --user enable --now pi-overview.service 2>/dev/null && ok "pi-overview.service enabled" +else + warn "pi-overview.service skipped (binary not found — run stage 06-uv-projects first)." +fi + +if [ -f "$HOME/.local/bin/bw-load-ssh.sh" ]; then + systemctl --user enable bw-ssh-keys.service 2>/dev/null && ok "bw-ssh-keys.service enabled" +else + warn "bw-ssh-keys.service skipped (script not found — run stage 07 first)." +fi + +systemctl --user enable --now empty_downloads.service 2>/dev/null && ok "empty_downloads.service enabled" || warn "empty_downloads.service not started." + +# Timers +systemctl --user enable --now mempi-sync.timer 2>/dev/null && ok "mempi-sync.timer enabled" || warn "mempi-sync.timer not started." + +info "===== Service Status =====" +systemctl --user list-units --type=service --state=running 2>/dev/null | grep -E "(porridge|swayidle|pi-overview|mempi|bw-ssh|empty)" || true + +ok "Stage 08 complete: user systemd services deployed." diff --git a/stages/09-desktop.sh b/stages/09-desktop.sh new file mode 100644 index 0000000..2ba9695 --- /dev/null +++ b/stages/09-desktop.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 09: Desktop Configuration +# Keybindings, hotkey scripts, Ghostty, fonts, and desktop theme. +# =========================================================================== +# On Pop!_OS, Julian uses COSMIC desktop (Rust-based, System76's own DE). +# Fedora ships with GNOME by default. This stage provides keybinding config +# for both GNOME (via gsettings) and a fallback swhkd/keyd approach. +# +# If you install a different DE (KDE, COSMIC via COPR, Sway/Hyprland), +# adjust this stage accordingly. +# =========================================================================== + +CONFIG_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/config" + +# Detect desktop environment +DE="${XDG_CURRENT_DESKTOP:-unknown}" + +# =========================================================================== +# 1. Hotkey Scripts (from hotkeys repo) +# =========================================================================== +# These are shell scripts that use selected text for quick actions. +# They rely on: wl-clipboard, wtype, wofi, jq, xdg-open +# Installed from ~/Development/hotkeys/ (cloned in stage 06). +info "Setting up hotkey scripts..." +HOTKEYS_DIR="$HOME/Development/hotkeys" + +if [ -d "$HOTKEYS_DIR" ]; then + # Create a symlink or copy to ~/.local/bin for PATH access + for script in google.sh scholar.sh dictionary.sh pdf.sh emoji.sh hotstrings.sh; do + if [ -f "$HOTKEYS_DIR/$script" ]; then + ln -sf "$HOTKEYS_DIR/$script" "$HOME/.local/bin/${script%.sh}" 2>/dev/null || true + fi + done + ok "Hotkey scripts linked to ~/.local/bin/" +else + warn "hotkeys repo not cloned yet. Run stage 06 first." +fi + +# =========================================================================== +# 2. Desktop Environment Keybindings +# =========================================================================== +# Map Julian's COSMIC keybindings to the current DE. + +case "$DE" in + *GNOME*|*gnome*) + info "Configuring GNOME keybindings..." + + # ---- Custom keybindings (hotkey scripts) ---- + # Map Ctrl+Alt+{G,S,D,E,A,O} to the hotkey scripts + gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "[]" + + _add_gnome_kb() { + local name="$1" + local binding="$2" + local command="$3" + local path="/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/${name}/" + + gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:"${path}" \ + name "${name}" 2>/dev/null || true + gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:"${path}" \ + binding "${binding}" 2>/dev/null || true + gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:"${path}" \ + command "${command}" 2>/dev/null || true + } + + _add_gnome_kb "google" "g" "$HOME/.local/bin/google" + _add_gnome_kb "scholar" "s" "$HOME/.local/bin/scholar" + _add_gnome_kb "dictionary" "d" "$HOME/.local/bin/dictionary" + _add_gnome_kb "emoji" "e" "$HOME/.local/bin/emoji" + _add_gnome_kb "hotstrings" "a" "$HOME/.local/bin/hotstrings" + _add_gnome_kb "pdf" "o" "$HOME/.local/bin/pdf" + + # ---- Collect all custom paths into array for gsettings ---- + KB_PATHS="[" + for n in google scholar dictionary emoji hotstrings pdf; do + KB_PATHS+="'/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/${n}/', " + done + KB_PATHS="${KB_PATHS%, }]" + gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "${KB_PATHS}" + + # ---- GNOME-specific shortcuts ---- + # Super+L → lock (default on GNOME too) + # Super+Q → close window (default is Alt+F4; set Super+Q too) + gsettings set org.gnome.desktop.wm.keybindings close "['q', 'F4']" 2>/dev/null || true + + ok "GNOME keybindings configured." + ;; + + *COSMIC*) + info "Configuring COSMIC keybindings..." + # On COSMIC, keybindings are stored in: + # ~/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom + # This is a RON (Rust Object Notation) file. + # We deploy our custom bindings from the config template. + if [ -f "$CONFIG_DIR/cosmic/custom-shortcuts.ron" ]; then + mkdir -p "$HOME/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1" + cp "$CONFIG_DIR/cosmic/custom-shortcuts.ron" \ + "$HOME/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom" + # Set terminal command (COSMIC desktop) + mkdir -p "$HOME/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1" + echo '{ + Terminal: "/usr/bin/ghostty --gtk-single-instance=true", +}' > "$HOME/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/system_actions" + ok "COSMIC keybindings deployed." + else + warn "COSMIC shortcuts template not found. Run 'cp config/cosmic/* ~/.config/cosmic/' manually." + fi + ;; + + *Hyprland*|*sway*|*Sway*) + info "Configuring Wayland compositor keybindings..." + warn "No automatic config for ${DE}. Apply manually from config/hotkeys/hyprland.conf or config/hotkeys/sway.config" + warn "See hotkeys/README.md for config examples." + ;; + + *KDE*|*Plasma*) + info "KDE Plasma detected. Applying keybindings via kwriteconfig..." + warn "KDE keybinding automation not yet implemented. Configure manually via System Settings > Shortcuts." + ;; + + *) + warn "Unknown DE: ${DE}. Keybindings must be configured manually." + warn "Reference: Ctrl+Alt+G/S/D/E/A/O → hotkey scripts in ~/Development/hotkeys/" + ;; +esac + +# =========================================================================== +# 4. Ghostty terminal config +# =========================================================================== +info "Deploying Ghostty config..." +mkdir -p "$HOME/.config/ghostty" +if [ -f "$CONFIG_DIR/ghostty/config" ]; then + cp "$CONFIG_DIR/ghostty/config" "$HOME/.config/ghostty/config" + ok "Ghostty config deployed." +fi + +# =========================================================================== +# 5. Fonts (Nerd Fonts for terminal + dev) +# =========================================================================== +info "Installing Nerd Fonts..." +FONT_DIR="$HOME/.local/share/fonts" +mkdir -p "$FONT_DIR" + +# MesloLGS NF — recommended for Powerlevel10k +# This is the font used on the Pop machine (MesloLGS NF Regular). +install_nerd_font() { + local font_name="$1" + local repo_url="$2" + local font_dir="$FONT_DIR" + + # Check if already installed + if ls "$font_dir"/*"$font_name"* 2>/dev/null | grep -q .; then + ok "Font '$font_name' already installed." + return + fi + + info "Downloading $font_name Nerd Font..." + local tmpdir + tmpdir=$(mktemp -d) + # Download from Nerd Fonts GitHub releases + curl -fsSL "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/${font_name}.zip" \ + -o "$tmpdir/${font_name}.zip" 2>/dev/null && { + unzip -o "$tmpdir/${font_name}.zip" -d "$tmpdir" 2>/dev/null + cp "$tmpdir"/*.ttf "$font_dir"/ 2>/dev/null || true + ok "Font '$font_name' installed." + } || warn "Font download failed for $font_name." + rm -rf "$tmpdir" +} + +# On Pop, these were found: +# MesloLGS NF (Regular, Bold, Italic, Bold Italic) +# FiraCode Nerd Font Propo +# ApercuMonoPro-Regular.otf (proprietary — not distributed) +install_nerd_font "Meslo" "" +install_nerd_font "FiraCode" "" + +# Rebuild font cache +fc-cache -f "$FONT_DIR" 2>/dev/null || true +ok "Font cache rebuilt." + +# =========================================================================== +# 6. GTK Theme (dark mode + Papirus icons) +# =========================================================================== +info "Setting GTK theme and icons..." +gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null || true +gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3-dark' 2>/dev/null || true +gsettings set org.gnome.desktop.interface icon-theme 'Papirus-Dark' 2>/dev/null || true +ok "GTK dark theme + Papirus icons set." + +# =========================================================================== +# 7. Solaar (Logitech peripherals) config +# =========================================================================== +# The Pop machine had config for MX Keys Mini + MX Master 3. +# Solaar config is auto-generated when you pair devices. The config +# template (if supplied) can be placed at ~/.config/solaar/config.yaml +info "Solaar config will be generated automatically when you pair devices." + +ok "Stage 09 complete: desktop configured." diff --git a/stages/10-docker.sh b/stages/10-docker.sh new file mode 100644 index 0000000..a3dc06f --- /dev/null +++ b/stages/10-docker.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 10: Docker CE +# Installs Docker CE, enables on boot, and adds user to docker group. +# =========================================================================== +# On the Pop machine, Julian uses Docker extensively: +# - grobid (PDF extraction server) +# - rocker/rstudio (R environment) +# - pandoc/extra (document conversion) +# +# This stage covers Docker CE install only. Docker images are pulled +# on demand (too large to pre-pull in provisioning). +# =========================================================================== + +info "Installing Docker CE..." + +# Docker repo was added in stage 01. Install from it. +if command -v docker &>/dev/null; then + ok "Docker already installed: $(docker --version 2>/dev/null)" +else + $PKG_INSTALL docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 2>/dev/null || { + error "Docker install failed. Check repo config in stage 01." + exit 1 + } + ok "Docker CE packages installed." + + # Enable and start Docker + $SERVICE_ENABLE docker 2>/dev/null + ok "Docker service enabled and started." +fi + +# ---- Add user to docker group ---- +if groups | grep -q docker; then + ok "User is already in the docker group." +else + info "Adding user to docker group (required to run Docker without sudo)..." + sudo usermod -aG docker "$USER" 2>/dev/null || warn "Could not add user to docker group." + warn "You'll need to log out and back in for docker group changes to take effect." + warn "Alternatively, run: newgrp docker (temporary, current shell only)." +fi + +# ---- Verify Docker works ---- +info "Verifying Docker installation..." +# Use a simple test (may need re-login for group changes) +if sg docker -c "docker run --rm hello-world" 2>/dev/null; then + ok "Docker verified: hello-world ran successfully." +else + warn "Docker verification failed. You may need to log out and back in." + warn "Or run: sudo usermod -aG docker $USER && newgrp docker" +fi + +# ---- Install Docker Compose (plugin) ---- +# docker compose (v2, plugin) was installed with docker-compose-plugin above. +if docker compose version 2>/dev/null; then + ok "Docker Compose v2 plugin ready: $(docker compose version --short 2>/dev/null)" +fi + +# ---- (Optional) Pre-pull commonly used images ---- +# Uncomment to pre-pull images Julian uses frequently. +# Note: grobid image is large (~2GB each). +info "Note: Docker images are pulled on demand when you run containers." +info "Common images Julian uses:" +info " - grobid/grobid (PDF extraction)" +info " - rocker/tidyverse or rocker/verse (RStudio)" +info " - pandoc/extra" +echo "" + +ok "Stage 10 complete: Docker installed." diff --git a/stages/11-tweaks.sh b/stages/11-tweaks.sh new file mode 100644 index 0000000..bd71c9a --- /dev/null +++ b/stages/11-tweaks.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 11: System Tweaks +# sysctl tuning, kernel cmdline parameters, TLP/powertop, modprobe. +# Uses distro-agnostic variables from lib/distro.sh. +# =========================================================================== +# CAUTION: GPU kernel parameters are hardware-specific (AMD Radeon 680M). +# They are COMMENTED OUT by default. Uncomment only if you have the same GPU. +# =========================================================================== + +CONFIG_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/config" + +# =========================================================================== +# 1. TLP / PowerTOP +# =========================================================================== +info "Configuring power management..." + +$SERVICE_ENABLE tlp 2>/dev/null && ok "TLP enabled." || warn "TLP not available." + +if command -v powertop &>/dev/null; then + # Enable powertop auto-tune via systemd service + if [ ! -f /etc/systemd/system/powertop.service ]; then + sudo tee /etc/systemd/system/powertop.service > /dev/null << 'EOF' +[Unit] +Description=PowerTOP auto tune +[Service] +Type=oneshot +ExecStart=/usr/sbin/powertop --auto-tune +[Install] +WantedBy=multi-user.target +EOF + sudo systemctl daemon-reload + fi + $SERVICE_ENABLE powertop 2>/dev/null && ok "PowerTOP auto-tune enabled." || true +fi + +ok "Stage 11 complete: system tweaks applied." diff --git a/stages/12-other-apps.sh b/stages/12-other-apps.sh new file mode 100644 index 0000000..3e874c5 --- /dev/null +++ b/stages/12-other-apps.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# =========================================================================== +# Stage 12: Other Applications +# Depends on: stage 01 (repos), stage 02 (packages) +# Chrome, Signal, Zotero, Obsidian, Nextcloud client, FreeRDP. +# Distro-agnostic — uses pkg_install / Flatpak / tarball. +# =========================================================================== + +# ---- Google Chrome ---- +info "Installing Google Chrome..." +pkg_install google-chrome-stable 2>/dev/null && ok "Chrome installed." \ + || warn "Chrome not available. Install from https://www.google.com/chrome/" + +# ---- Signal Desktop ---- +info "Installing Signal Desktop..." +pkg_install signal-desktop 2>/dev/null && ok "Signal installed." \ + || warn "Signal not available. Check repo in stage 01." + +# ---- Zotero (reference manager) ---- +info "Installing Zotero..." +if command -v zotero &>/dev/null || flatpak list 2>/dev/null | grep -qi "zotero"; then + ok "Zotero already installed." +elif command -v flatpak &>/dev/null; then + flatpak install -y flathub org.zotero.Zotero 2>/dev/null && ok "Zotero installed via Flatpak." \ + || warn "Zotero Flatpak failed. Try manual tarball from zotero.org." +else + warn "Zotero not installed. Get it from https://www.zotero.org/download/" +fi + +# ---- Obsidian (knowledge base) ---- +info "Installing Obsidian..." +if command -v obsidian &>/dev/null || flatpak list 2>/dev/null | grep -qi "obsidian"; then + ok "Obsidian already installed." +elif command -v flatpak &>/dev/null; then + flatpak install -y flathub md.obsidian.Obsidian 2>/dev/null && ok "Obsidian installed." \ + || warn "Obsidian Flatpak failed. Download from https://obsidian.md/" +else + warn "Obsidian not installed." +fi + +# ---- Nextcloud Desktop Client ---- +info "Installing Nextcloud Desktop Client..." +if [ "$DISTRO_FAMILY" = "debian" ]; then + pkg_install nextcloud-desktop 2>/dev/null && ok "Nextcloud client installed." || \ + warn "Nextcloud client not available." +else + pkg_install nextcloud-client 2>/dev/null && ok "Nextcloud client installed." || \ + warn "Nextcloud client not available." +fi + +# ---- FreeRDP (for WinBoat RDP) ---- +info "Installing FreeRDP (Flatpak)..." +if command -v flatpak &>/dev/null; then + flatpak install -y flathub com.freerdp.FreeRDP 2>/dev/null && ok "FreeRDP installed." \ + || warn "FreeRDP Flatpak failed." +fi + +ok "Stage 12 complete: additional applications installed."