#!/usr/bin/env bash set -euo pipefail # ========================= # Settings (adjust freely) # ========================= PREFER_EDITOR="${PREFER_EDITOR:-vim}" PACKAGES=( bash-completion git curl wget ca-certificates gnupg vim tmux htop tree unzip zip ripgrep fd-find fzf build-essential gdebi-core ) APTUPDATE_PATH="/usr/bin/aptupdate" APTUPDATE_URL="https://gist.yais.me/yaisme/util/raw/HEAD/aptupdate" # ========================= # Helpers # ========================= msg() { printf "\033[1;32m[OK]\033[0m %s\n" "$*"; } warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } err() { printf "\033[1;31m[ERR]\033[0m %s\n" "$*" >&2; } backup_file() { local f="$1"; [ -f "$f" ] || return 0 local ts; ts="$(date +%Y%m%d-%H%M%S)" cp -a "$f" "${f}.bak.${ts}" msg "Backup: ${f}.bak.${ts}" } append_once() { # $1=file, $2=tag_line(unique), $3=payload local file="$1" tag="$2" payload="$3" grep -Fqx "$tag" "$file" 2>/dev/null && return 0 printf "\n%s\n%s\n" "$tag" "$payload" >>"$file" } ensure_local_bin() { mkdir -p "$HOME/.local/bin" case ":${PATH:-}:" in *":$HOME/.local/bin:"*) : ;; *) echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.profile" ;; esac } # ========================= # 0) System update/upgrade # ========================= cd "$HOME" export DEBIAN_FRONTEND=noninteractive sudo apt-get -y update sudo apt-get -y upgrade || true # Keep your style (and keep unattended-upgrades removed) sudo apt -y update sudo apt -y upgrade || true sudo apt -y autoremove sudo apt -y autoclean sudo apt -y clean sudo apt remove -y unattended-upgrades || true msg "Base update/upgrade complete (unattended-upgrades removed)." # ========================= # 1) Install /usr/bin/aptupdate # (download; if fails, write a built-in fallback) # ========================= tmpfile="$(mktemp)" if command -v curl >/dev/null 2>&1 && curl -fsSL "$APTUPDATE_URL" -o "$tmpfile"; then sudo mv "$tmpfile" "$APTUPDATE_PATH" msg "Installed aptupdate from gist." else warn "Could not fetch aptupdate; installing fallback script." cat >"$tmpfile" <<'EOF' #!/bin/sh sudo apt update sudo DEBIAN_FRONTEND=noninteractive apt -y upgrade sudo DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade sudo apt -y autoremove sudo apt -y autoclean sudo apt -y clean EOF sudo mv "$tmpfile" "$APTUPDATE_PATH" fi sudo chmod +x "$APTUPDATE_PATH" # ========================= # 2) Install common packages # ========================= sudo apt-get update -y sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "${PACKAGES[@]}" || warn "Some packages failed to install; continuing." # Map Ubuntu's fdfind → fd for consistency if command -v fdfind >/dev/null 2>&1 && ! command -v fd >/dev/null 2>&1; then ensure_local_bin ln -sf "$(command -v fdfind)" "$HOME/.local/bin/fd" msg "Linked fdfind → fd at ~/.local/bin/fd" fi # ========================= # 3) Shell customization # ========================= BASHRC="${HOME}/.bashrc" INPUTRC="${HOME}/.inputrc" [ -f "$BASHRC" ] || touch "$BASHRC" [ -f "$INPUTRC" ] || touch "$INPUTRC" backup_file "$BASHRC" backup_file "$INPUTRC" # 3a) force_color_prompt=yes (ensure near top for Ubuntu-style .bashrc) if grep -Eq '^[[:space:]]*#?[[:space:]]*force_color_prompt[[:space:]]*=' "$BASHRC"; then sed -i '0,/^[[:space:]]*#\{0,1\}[[:space:]]*force_color_prompt[[:space:]]*=.*/s//force_color_prompt=yes/' "$BASHRC" else sed -i '1iforce_color_prompt=yes' "$BASHRC" fi msg "Enabled force_color_prompt=yes." # 3b) bash-completion COMPLETION_TAG="# >>> chatgpt: bash-completion enable >>>" COMPLETION_BLOCK=$(cat <<'EOF' # >>> chatgpt: bash-completion enable >>> if [ -n "$BASH_VERSION" ] && ! shopt -oq posix; then if [ -f /usr/share/bash-completion/bash_completion ]; then . /usr/share/bash-completion/bash_completion elif [ -f /etc/bash_completion ]; then . /etc/bash_completion fi fi # <<< chatgpt: bash-completion enable <<< EOF ) append_once "$BASHRC" "$COMPLETION_TAG" "$COMPLETION_BLOCK" msg "Ensured bash-completion is sourced when available." # 3c) History: timestamps + safe/merged history HIST_TAG="# >>> chatgpt: history settings >>>" HIST_BLOCK=$(cat <<'EOF' # >>> chatgpt: history settings >>> # Timestamps (YYYY-MM-DD HH:MM:SS) export HISTTIMEFORMAT='%F %T ' # Safer history export HISTCONTROL=ignoredups:ignorespace:erasedups export HISTSIZE=200000 export HISTFILESIZE=400000 # Merge history across concurrent shells shopt -s histappend if [[ -n "${PROMPT_COMMAND:-}" ]]; then case "$PROMPT_COMMAND" in *'history -a; history -c; history -r'*) : ;; *) PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND" ;; esac else PROMPT_COMMAND='history -a; history -c; history -r' fi # <<< chatgpt: history settings <<< EOF ) append_once "$BASHRC" "$HIST_TAG" "$HIST_BLOCK" msg "Enabled history timestamps and merging." # 3d) QoL: prompt, aliases, shopt, editor/pager (no Kubernetes) QOL_TAG="# >>> chatgpt: QoL shell block >>>" QOL_BLOCK=$(cat <<'EOF' # >>> chatgpt: QoL shell block >>> # Minimal Git branch helper __chatgpt_git_branch() { local b b=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 0 [ "$b" = "HEAD" ] && b=$(git rev-parse --short HEAD 2>/dev/null) [ -n "$b" ] && printf ' (%s)' "$b" } # Prompt: exit code + user@host:cwd + (git) __chatgpt_set_ps1() { local exit="$?" local red="\[\e[31m\]" green="\[\e[32m\]" yellow="\[\e[33m\]" blue="\[\e[34m\]" magenta="\[\e[35m\]" cyan="\[\e[36m\]" reset="\[\e[0m\]" local code="" [ "$exit" -ne 0 ] && code="${red}↯${exit}${reset} " PS1="${code}${green}\u${reset}@${blue}\h${reset}:${magenta}\w${reset}\$(__chatgpt_git_branch)\n${cyan}\$ ${reset}" } case "${PROMPT_COMMAND:-}" in *"__chatgpt_set_ps1"*) : ;; "") PROMPT_COMMAND="__chatgpt_set_ps1" ;; *) PROMPT_COMMAND="__chatgpt_set_ps1; $PROMPT_COMMAND" ;; esac # shopt toggles shopt -s checkwinsize cdspell autocd globstar # Common aliases alias ls='ls --color=auto -F --group-directories-first' alias ll='ls -lah' alias la='ls -A' alias grep='grep --color=auto' alias df='df -h' alias free='free -h' alias watchd='watch -n1 -d' alias cp='cp -i' alias mv='mv -i' alias rm='rm -I' # prompt once if >3 files # Prefer modern tools if present command -v batcat >/dev/null 2>&1 && alias bat='batcat' command -v bat >/dev/null 2>&1 && alias cat='bat --paging=never' command -v lsd >/dev/null 2>&1 && alias ls='lsd' # Defaults export EDITOR="${EDITOR:-vim}" export VISUAL="$EDITOR" export PAGER="${PAGER:-less}" export LESS='-R' export GPG_TTY="$(tty 2>/dev/null || true)" # <<< chatgpt: QoL shell block <<< EOF ) append_once "$BASHRC" "$QOL_TAG" "$QOL_BLOCK" msg "Added prompt/aliases/shopt/editor QoL block." # 3e) Ensure desired EDITOR if grep -Fq 'export EDITOR=' "$BASHRC"; then sed -i "0,/export EDITOR=.*/s//export EDITOR=\"${PREFER_EDITOR}\"/" "$BASHRC" else echo "export EDITOR=\"${PREFER_EDITOR}\"" >> "$BASHRC" fi msg "Default EDITOR set to ${PREFER_EDITOR}" # 3f) ~/.inputrc readline tweaks INPUT_TAG="# >>> chatgpt: readline tweaks >>>" INPUT_BLOCK=$(cat <<'EOF' # >>> chatgpt: readline tweaks >>> set show-all-if-ambiguous on set menu-complete-display-prefix on set colored-stats on set mark-symlinked-directories on set completion-ignore-case on set completion-map-case on "\e[A": history-search-backward "\e[B": history-search-forward # <<< chatgpt: readline tweaks <<< EOF ) append_once "$INPUTRC" "$INPUT_TAG" "$INPUT_BLOCK" msg "Improved interactive completion in ~/.inputrc." # ========================= # 4) Wrap up # ========================= msg "First-run setup complete. Open a NEW terminal or run: source ~/.bashrc"