diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..b680747 --- /dev/null +++ b/install.sh @@ -0,0 +1,240 @@ +#!/bin/sh +# install.sh - bootstrap dotfiles on a fresh Debian or Alpine host. +# +# What it does: +# 1. Detects the OS and package manager (apt on Debian, apk on Alpine). +# 2. Installs base tooling (git, curl, zsh, vim) + extras (htop, neofetch, fzf). +# 3. Installs oh-my-zsh (unattended) and the zsh-autosuggestions plugin. +# 4. Deploys the server/ configs matching this machine's hostname. +# 5. Installs Vim plugins via Vundle (PluginInstall). +# +# Usage: +# ./install.sh # auto-detect host, deploy matching configs +# ./install.sh --host vega # force a specific host config +# ./install.sh --no-zsh # skip oh-my-zsh / shell setup +# ./install.sh --chsh # change the current user's login shell to zsh +# ./install.sh --dry-run # show what would happen, change nothing +# +# Run this from inside the cloned dotfiles repo (the dir that contains server/). +# Safe to re-run: every step is idempotent. + +set -eu + +# ---------------------------------------------------------------------------- +# Helpers +# ---------------------------------------------------------------------------- +DRY_RUN=0 +INSTALL_ZSH=1 +CHSH=0 +FORCE_HOST="" + +info() { printf '\033[0;34m==>\033[0m %s\n' "$1"; } +ok() { printf '\033[0;32m✔\033[0m %s\n' "$1"; } +warn() { printf '\033[0;33m!\033[0m %s\n' "$1"; } +die() { printf '\033[0;31m✘\033[0m %s\n' "$1" >&2; exit 1; } + +run() { + # Print the command, then run it (unless --dry-run). + if [ "$DRY_RUN" -eq 1 ]; then + printf ' $ %s\n' "$*" + else + "$@" + fi +} + +usage() { + sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' + exit 0 +} + +# ---------------------------------------------------------------------------- +# Argument parsing +# ---------------------------------------------------------------------------- +while [ $# -gt 0 ]; do + case "$1" in + --host) FORCE_HOST="$2"; shift 2 ;; + --no-zsh) INSTALL_ZSH=0; shift ;; + --chsh) CHSH=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + -h|--help) usage ;; + *) die "Unknown argument: $1 (try --help)" ;; + esac +done + +# ---------------------------------------------------------------------------- +# Locate the repo root (dir that holds this script and server/) +# ---------------------------------------------------------------------------- +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +if [ ! -d "$SCRIPT_DIR/server" ]; then + die "server/ not found next to this script. +Clone the dotfiles repo first, then run install.sh from its root: + git clone git@git.ivanch.me:ivanch/dotfiles.git + cd dotfiles && ./install.sh" +fi + +# ---------------------------------------------------------------------------- +# OS / package-manager detection +# ---------------------------------------------------------------------------- +detect_os() { + OS_RELEASE_FILE="${OS_RELEASE_FILE:-/etc/os-release}" + if [ -f "$OS_RELEASE_FILE" ]; then + . "$OS_RELEASE_FILE" + OS_ID="$ID" + else + OS_ID="$(uname -s)" + fi + + case "$OS_ID" in + alpine) PKG="apk"; MGR_UPDATE="apk update"; MGR_ADD="apk add --no-cache" ;; + debian|ubuntu|raspbian) + PKG="apt"; MGR_UPDATE="apt-get update"; MGR_ADD="apt-get install -y" ;; + *) die "Unsupported OS: $OS_ID (only Debian/Ubuntu and Alpine are supported)" ;; + esac + info "Detected OS: $OS_ID (package manager: $PKG)" +} + +# ---------------------------------------------------------------------------- +# Package installation +# ---------------------------------------------------------------------------- +install_packages() { + info "Installing base packages" + ESSENTIAL="git curl zsh vim" + EXTRA="htop neofetch fzf" + + if [ "$PKG" = "apk" ]; then + run $MGR_UPDATE + run $MGR_ADD $ESSENTIAL $EXTRA + else + run $MGR_UPDATE + run $MGR_ADD $ESSENTIAL + # Newer Debian dropped neofetch from the repos; don't fail the whole run on it. + info "Installing extra packages (best-effort)" + run $MGR_ADD $EXTRA || warn "Some extra packages failed to install (non-fatal)." + fi +} + +# ---------------------------------------------------------------------------- +# oh-my-zsh + zsh-autosuggestions +# ---------------------------------------------------------------------------- +install_oh_my_zsh() { + info "Installing oh-my-zsh" + if [ -d "$HOME/.oh-my-zsh" ]; then + ok "oh-my-zsh already present, skipping" + else + # --unattended => RUNZSH=no CHSH=no (no prompt, no shell change) + # Special-cased (not via run()) so --dry-run never hits the network. + if [ "$DRY_RUN" -eq 1 ]; then + printf ' $ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended\n' + else + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended + fi + ok "oh-my-zsh installed" + fi + + ZSH_CUSTOM_DIR="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}" + SUG_DIR="$ZSH_CUSTOM_DIR/plugins/zsh-autosuggestions" + if [ -d "$SUG_DIR" ]; then + ok "zsh-autosuggestions already present, skipping" + else + info "Installing zsh-autosuggestions plugin" + run git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "$SUG_DIR" + ok "zsh-autosuggestions installed" + fi +} + +# ---------------------------------------------------------------------------- +# Deploy the dotfiles for this host +# ---------------------------------------------------------------------------- +deploy_dotfiles() { + HOST_NAME="${FORCE_HOST:-$(hostname 2>/dev/null || uname -n)}" + HOST_DIR="$SCRIPT_DIR/server/$HOST_NAME" + + if [ ! -d "$HOST_DIR" ]; then + warn "No server/$HOST_NAME config found." + if [ -z "$FORCE_HOST" ]; then + avail=$(cd "$SCRIPT_DIR/server" && ls -1) + warn "Available hosts: $(echo "$avail" | tr '\n' ' ')" + warn "Skipping dotfile deploy. Re-run with --host to force one." + fi + return 0 + fi + + info "Deploying dotfiles for host: $HOST_NAME" + # Copy every file/dir from server// into $HOME, preserving structure. + # .zshrc/.vimrc/.bashrc land at $HOME; .config/* is nested. + for src in "$HOST_DIR"/* "$HOST_DIR"/.[!.]* "$HOST_DIR"/..?*; do + [ -e "$src" ] || continue + run cp -Rf "$src" "$HOME/" + ok "$(basename "$src") -> $HOME/" + done +} + +# ---------------------------------------------------------------------------- +# Vim plugins via Vundle +# ---------------------------------------------------------------------------- +install_vim_plugins() { + info "Installing Vim plugins (Vundle)" + if [ ! -f "$HOME/.vimrc" ]; then + warn "No .vimrc deployed; skipping Vim plugin install." + return 0 + fi + VUNDLE_DIR="$HOME/.vim/bundle/Vundle.vim" + if [ ! -d "$VUNDLE_DIR" ]; then + info "Cloning Vundle" + run git clone --depth=1 https://github.com/VundleVim/Vundle.vim.git "$VUNDLE_DIR" + fi + info "Running :PluginInstall (idempotent)" + # Headless, tolerant of the 'dracula' colorscheme-not-found warning at startup. + if [ "$DRY_RUN" -eq 1 ]; then + printf ' $ vim +PluginInstall +qall\n' + else + vim +PluginInstall +qall >/dev/null 2>&1 || \ + warn "Vim PluginInstall exited non-zero; verify with: vim +PluginInstall +qall" + fi + ok "Vim plugins installed" +} + +# ---------------------------------------------------------------------------- +# Optional: change login shell to zsh +# ---------------------------------------------------------------------------- +change_shell() { + if ! command -v zsh >/dev/null 2>&1; then + warn "zsh not found; cannot change shell." + return 0 + fi + if ! command -v chsh >/dev/null 2>&1; then + warn "chsh not found on this system; set your login shell manually (e.g. in /etc/passwd)." + return 0 + fi + ZSH_BIN="$(command -v zsh)" + cur="$(grep "^$(id -un):" /etc/passwd | cut -d: -f7)" + if [ "$cur" = "$ZSH_BIN" ]; then + ok "Login shell already $ZSH_BIN" + return 0 + fi + info "Changing login shell to $ZSH_BIN" + run chsh -s "$ZSH_BIN" + ok "Login shell set to zsh (re-login to apply)" +} + +# ---------------------------------------------------------------------------- +# Main +# ---------------------------------------------------------------------------- +info "Bootstrapping dotfiles from $SCRIPT_DIR" +[ "$DRY_RUN" -eq 1 ] && warn "DRY RUN - no changes will be made" + +detect_os +install_packages + +if [ "$INSTALL_ZSH" -eq 1 ]; then + install_oh_my_zsh +fi + +deploy_dotfiles +install_vim_plugins + +if [ "$CHSH" -eq 1 ]; then + change_shell +fi + +info "Done. Restart your shell (or re-login) to pick up the new configuration."