Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ cd ~/dotfiles

```
dotfiles/
├── .agents/ # Cross-agent skills
│ └── skills/
├── .codex/ # Codex defaults
│ └── AGENTS.md
├── zsh/ # Zsh configuration
│ └── .zshrc
├── bash/ # Bash configuration
Expand Down Expand Up @@ -45,6 +49,12 @@ stow zsh bash nushell git

To pick up new workstation dependencies later, use `update-home` or rerun `./install.sh`.

Audit without mutating packages or links:

```bash
./install.sh --dry-run
```

### On NixOS

The NixOS config uses `mkOutOfStoreSymlink` to point to this dotfiles repo:
Expand Down Expand Up @@ -99,5 +109,5 @@ These files are sourced at the end of the main configs.
- `claude` - `~/.claude/local/claude`
- `codex` - installed with npm into `~/.npm-global/bin`
- `gh copilot` - downloads the GitHub Copilot CLI via GitHub CLI
- HappyVertical agent workflows - `~/Work/happyvertical/repos/have-config/install.sh --live`
- `sops` / `age` / `gnupg` - local encrypted environment tooling where available
- `rebuild` / `update` - Platform-specific rebuild command
9 changes: 7 additions & 2 deletions bash/.bashrc
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ esac
# ==============================================================================
# PATH Setup
# ==============================================================================
# Add ~/.local/bin to PATH (for locally installed tools like claude-code)
export PATH="$HOME/.local/bin:$PATH"
# Add local bin and agent CLIs to PATH
export PATH="$HOME/.local/bin:$HOME/.claude/local:$PATH"

export PR_REVIEW_DIR="${PR_REVIEW_DIR:-$HOME/Work/happyvertical/repos/pr-review}"
if [[ -d "$PR_REVIEW_DIR/bin" ]]; then
export PATH="$PR_REVIEW_DIR/bin:$PATH"
fi

# Configure npm to use home directory for global packages (avoids read-only Nix store)
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
Expand Down
211 changes: 140 additions & 71 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,38 @@
set -e

DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DRY_RUN=0

usage() {
cat <<'EOF'
Usage: ./install.sh [--dry-run|--audit] [-h|--help]

Installs workstation tooling, shells, CLI tools, and personal dotfiles.

Options:
--dry-run, --audit Report platform/install intent without changing packages,
symlinks, or generated files.
-h, --help Show this help.
EOF
}

while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run|--audit)
DRY_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done

# ==============================================================================
# Platform Detection
Expand All @@ -29,6 +61,25 @@ detect_platform() {
fi
}

run_privileged() {
if [[ "$(id -u)" -eq 0 ]]; then
"$@"
return $?
fi

if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
sudo -n "$@"
return $?
fi

echo "Skipping privileged command (sudo unavailable or requires a password): $*"
return 0
}

can_run_privileged() {
[[ "$(id -u)" -eq 0 ]] || (command -v sudo &> /dev/null && sudo -n true 2>/dev/null)
}

# ==============================================================================
# Package Installation
# ==============================================================================
Expand Down Expand Up @@ -56,12 +107,6 @@ install_packages() {
jq # json processor
)

# Cloud CLI tools
local cloud_packages=(
gh # GitHub CLI
awscli # AWS CLI (awscli2 on some distros)
)

case "$PLATFORM" in
macos)
if ! command -v brew &> /dev/null; then
Expand Down Expand Up @@ -92,11 +137,11 @@ install_packages() {
linux)
case "$DISTRO" in
ubuntu|debian|pop)
sudo apt-get update
sudo apt-get install -y "${packages[@]}"
run_privileged apt-get update
run_privileged apt-get install -y "${packages[@]}"
# Optional packages (some may not be in default repos)
sudo apt-get install -y zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
sudo apt-get install -y fzf bat ripgrep fd-find jq unzip 2>/dev/null || true
run_privileged apt-get install -y zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
run_privileged apt-get install -y fzf bat ripgrep fd-find jq unzip 2>/dev/null || true
# Starship, zoxide need manual install on Debian/Ubuntu
install_starship
install_zoxide
Expand All @@ -106,9 +151,9 @@ install_packages() {
install_gcloud
;;
fedora|rhel|centos)
sudo dnf install -y "${packages[@]}"
sudo dnf install -y zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
sudo dnf install -y fzf bat ripgrep fd-find jq eza unzip 2>/dev/null || true
run_privileged dnf install -y "${packages[@]}"
run_privileged dnf install -y zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
run_privileged dnf install -y fzf bat ripgrep fd-find jq eza unzip 2>/dev/null || true
install_starship
install_zoxide
# Cloud CLI tools
Expand All @@ -117,21 +162,21 @@ install_packages() {
install_gcloud
;;
alpine)
sudo apk add "${packages[@]}"
sudo apk add zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
sudo apk add fzf bat ripgrep fd jq unzip 2>/dev/null || true
run_privileged apk add "${packages[@]}"
run_privileged apk add zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
run_privileged apk add fzf bat ripgrep fd jq unzip 2>/dev/null || true
install_starship
install_zoxide
# Cloud CLI tools
sudo apk add github-cli aws-cli 2>/dev/null || true
run_privileged apk add github-cli aws-cli 2>/dev/null || true
install_gcloud
;;
arch|manjaro)
sudo pacman -S --noconfirm "${packages[@]}"
sudo pacman -S --noconfirm zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
sudo pacman -S --noconfirm starship zoxide fzf bat eza ripgrep fd jq direnv 2>/dev/null || true
run_privileged pacman -S --noconfirm "${packages[@]}"
run_privileged pacman -S --noconfirm zsh-autosuggestions zsh-syntax-highlighting 2>/dev/null || true
run_privileged pacman -S --noconfirm starship zoxide fzf bat eza ripgrep fd jq direnv 2>/dev/null || true
# Cloud CLI tools
sudo pacman -S --noconfirm github-cli aws-cli 2>/dev/null || true
run_privileged pacman -S --noconfirm github-cli aws-cli 2>/dev/null || true
install_gcloud
;;
nixos)
Expand All @@ -146,6 +191,42 @@ install_packages() {
esac
}

install_sops_tools() {
echo "Installing SOPS tooling..."

case "$PLATFORM" in
macos)
brew install sops age gnupg 2>/dev/null || true
;;
linux)
case "$DISTRO" in
ubuntu|debian|pop)
run_privileged apt-get install -y age gnupg 2>/dev/null || true
run_privileged apt-get install -y sops 2>/dev/null || true
;;
fedora|rhel|centos)
run_privileged dnf install -y age gnupg2 sops 2>/dev/null || true
;;
alpine)
run_privileged apk add age gnupg sops 2>/dev/null || true
;;
arch|manjaro)
run_privileged pacman -S --noconfirm age gnupg sops 2>/dev/null || true
;;
nixos)
echo "NixOS detected - SOPS tooling should be managed by Nix"
;;
esac
;;
esac

if command -v sops &> /dev/null; then
echo "SOPS available: $(command -v sops)"
else
echo "SOPS not found after package install; configure it via your platform package manager."
fi
}

install_starship() {
if command -v starship &> /dev/null; then
echo "Starship already installed"
Expand Down Expand Up @@ -184,7 +265,7 @@ install_awscli() {
if [[ "$PLATFORM" == "linux" ]]; then
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip"
unzip -q /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install
run_privileged /tmp/aws/install
rm -rf /tmp/awscliv2.zip /tmp/aws
fi
}
Expand All @@ -197,12 +278,16 @@ install_gh() {
echo "Installing GitHub CLI..."
case "$DISTRO" in
ubuntu|debian|pop)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt-get update && sudo apt-get install -y gh
if can_run_privileged; then
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | run_privileged dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | run_privileged tee /etc/apt/sources.list.d/github-cli.list > /dev/null
run_privileged apt-get update && run_privileged apt-get install -y gh
else
echo "Skipping gh apt setup; root/sudo is not available noninteractively."
fi
;;
fedora|rhel|centos)
sudo dnf install -y gh 2>/dev/null || true
run_privileged dnf install -y gh 2>/dev/null || true
;;
*)
echo "Please install gh manually: https://cli.github.com/"
Expand All @@ -223,17 +308,17 @@ ensure_npm() {
linux)
case "$DISTRO" in
ubuntu|debian|pop)
sudo apt-get update
sudo apt-get install -y nodejs npm
run_privileged apt-get update
run_privileged apt-get install -y nodejs npm
;;
fedora|rhel|centos)
sudo dnf install -y nodejs npm
run_privileged dnf install -y nodejs npm
;;
alpine)
sudo apk add nodejs npm
run_privileged apk add nodejs npm
;;
arch|manjaro)
sudo pacman -S --noconfirm nodejs npm
run_privileged pacman -S --noconfirm nodejs npm
;;
nixos)
echo "NixOS detected - Node.js and npm should be managed by Nix"
Expand All @@ -250,6 +335,11 @@ ensure_npm() {
return 1
;;
esac

if ! command -v npm &> /dev/null; then
echo "npm still not available after install attempt"
return 1
fi
}

ensure_agent_paths() {
Expand All @@ -262,6 +352,7 @@ ensure_agent_paths() {
if [[ -d "$HOME/.claude/local" ]] && [[ ":$PATH:" != *":$HOME/.claude/local:"* ]]; then
export PATH="$HOME/.claude/local:$PATH"
fi

}

# ==============================================================================
Expand Down Expand Up @@ -350,45 +441,6 @@ install_copilot_cli() {
fi
}

install_have_config() {
ensure_agent_paths

local have_config_dir="${HAVE_CONFIG_DIR:-$HOME/Work/happyvertical/repos/have-config}"
local repo_url="${HAVE_CONFIG_REPO_URL:-git@github.com:happyvertical/have-config.git}"
local fallback_repo_url="https://github.com/happyvertical/have-config.git"
local install_args=()

if [[ "${HAVE_CONFIG_LIVE:-1}" != "0" ]]; then
install_args+=(--live)
fi

if [[ -d "$have_config_dir/.git" ]]; then
echo "Updating have-config..."
if ! git -C "$have_config_dir" pull --ff-only --quiet; then
echo " Could not fast-forward have-config; using existing checkout."
fi
else
echo "Cloning have-config..."
mkdir -p "$(dirname "$have_config_dir")"
if ! git clone --quiet "$repo_url" "$have_config_dir"; then
if [[ "$repo_url" != "$fallback_repo_url" ]]; then
echo " SSH clone failed, trying HTTPS..."
git clone --quiet "$fallback_repo_url" "$have_config_dir"
else
return 1
fi
fi
fi

if [[ ! -x "$have_config_dir/install.sh" ]]; then
echo "have-config installer not executable at $have_config_dir/install.sh"
return 1
fi

echo "Installing HappyVertical agent workflows..."
(cd "$have_config_dir" && ./install.sh "${install_args[@]}")
}

install_gemini_cli() {
ensure_agent_paths

Expand Down Expand Up @@ -589,6 +641,11 @@ set_default_shell() {
return 0
fi

if [[ "${DOTFILES_NONINTERACTIVE:-1}" == "1" || ! -t 0 ]]; then
echo "Skipping default shell prompt (noninteractive install)."
return 0
fi

local zsh_path
zsh_path=$(which zsh)

Expand Down Expand Up @@ -619,18 +676,30 @@ main() {
echo "Platform: $PLATFORM"
[[ -n "$DISTRO" ]] && echo "Distro: $DISTRO"
echo "Dotfiles directory: $DOTFILES_DIR"
[[ "$DRY_RUN" -eq 1 ]] && echo "Mode: dry-run"
echo

if [[ "$DRY_RUN" -eq 1 ]]; then
echo "Dry-run: would install packages, AI CLIs, shell tooling, and stowed dotfiles."
echo
echo "Core tools: zsh git curl stow starship zoxide direnv fzf bat eza ripgrep fd jq"
echo "AI CLIs: codex claude gemini kimi ralph"
echo "Package mutation, downloads, shell changes, and stow operations skipped."
echo "Dry-run complete!"
echo "========================================"
return 0
fi

# Install packages
install_packages
install_sops_tools
echo

# Install AI CLI tools
install_codex_cli
install_claude_code
install_claude_plugins
install_copilot_cli
install_have_config
install_kimi_code
install_gemini_cli
install_ralph
Expand Down
Loading