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
This commit is contained in:
2026-06-05 21:21:46 +10:00
commit 180c5838ea
36 changed files with 4176 additions and 0 deletions

View File

@@ -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

View File

@@ -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),
}

11
config/ghostty/config Normal file
View File

@@ -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

49
config/git/gitconfig Normal file
View File

@@ -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

View File

@@ -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"

15
config/scripts/env.sh Normal file
View File

@@ -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

View File

@@ -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

15
config/scripts/zoom.sh Normal file
View File

@@ -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 "$@"

1713
config/shell/p10k.zsh Normal file

File diff suppressed because it is too large Load Diff

75
config/shell/zshrc Normal file
View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"'

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Sync mempi database on boot
[Timer]
OnBootSec=5min
[Install]
WantedBy=timers.target

View File

@@ -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

View File

@@ -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

View File

@@ -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