diff --git a/README.md b/README.md index 696c9b1..c9d0c77 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ----- -**Version:** v0.71 +**Version:** v0.72 **Last Updated:** 2025-10-20 @@ -24,7 +24,7 @@ This script automates the initial setup and security hardening of a fresh Debian ## Features -* **Secure User Management**: Creates a new `sudo` user and disables root SSH access. +* **Secure User Management**: Creates a new `sudo` user and disables root SSH access. Optionally installs a custom .bashrc for enhanced terminal experience. * **SSH Hardening**: Configures a custom SSH port, enforces key-based authentication, and applies security best practices. * **Firewall Configuration**: Sets up UFW with secure defaults and customizable rules. * **Intrusion Prevention**: Installs and configures **Fail2Ban** to block malicious IPs. @@ -87,12 +87,12 @@ sha256sum du_setup.sh Compare the output hash to the one below. They must match exactly. -`dfc27413d157a29510f2f516cf594a9940942ec8622d292d94a3936b4490a2a6` +`e402db17ee1738ec96b2f4e2bcd1d1756df8b6a7e49de0953681a49f7f42fe18` Or echo the hash to check, it should output: `du_setup.sh: OK` ```bash -echo dfc27413d157a29510f2f516cf594a9940942ec8622d292d94a3936b4490a2a6 du_setup.sh | sha256sum --check +echo e402db17ee1738ec96b2f4e2bcd1d1756df8b6a7e49de0953681a49f7f42fe18 du_setup.sh | sha256sum --check ``` ### 3. Run the Script diff --git a/du_setup.sh b/du_setup.sh index de97834..cc060e2 100644 --- a/du_setup.sh +++ b/du_setup.sh @@ -1,8 +1,9 @@ #!/bin/bash # Debian and Ubuntu Server Hardening Interactive Script -# Version: 0.71 | 2025-10-20 +# Version: 0.72 | 2025-10-20 # Changelog: +# - v0.72: Added configure_custom_bashrc() function that creates and installs a feature-rich .bashrc file during user creation. # - v0.71: Simplify test backup function to work reliably with Hetzner storagebox # - v0.70.1: Fix SSH port validation and improve firewall handling during SSH port transitions. # - v0.70: Option to remove cloud VPS provider packages (like cloud-init). @@ -75,7 +76,7 @@ set -euo pipefail # --- Update Configuration --- -CURRENT_VERSION="0.71" +CURRENT_VERSION="0.72" SCRIPT_URL="https://raw.githubusercontent.com/buildplan/du_setup/refs/heads/main/du_setup.sh" CHECKSUM_URL="${SCRIPT_URL}.sha256" @@ -226,7 +227,7 @@ print_header() { printf '%s\n' "${CYAN}╔═════════════════════════════════════════════════════════════════╗${NC}" printf '%s\n' "${CYAN}║ ║${NC}" printf '%s\n' "${CYAN}║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC}" - printf '%s\n' "${CYAN}║ v0.71 | 2025-10-20 ║${NC}" + printf '%s\n' "${CYAN}║ v0.72 | 2025-10-20 ║${NC}" printf '%s\n' "${CYAN}║ ║${NC}" printf '%s\n' "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}" printf '\n' @@ -1049,6 +1050,1257 @@ cleanup_provider_packages() { fi } +configure_custom_bashrc() { + local USER_HOME="$1" + local USERNAME="$2" + local BASHRC_PATH="$USER_HOME/.bashrc" + local temp_source_bashrc="" + local keep_temp_source_on_error=false + + trap 'rm -f "$temp_source_bashrc" 2>/dev/null' INT TERM + + if ! confirm "Replace default .bashrc for '$USERNAME' with a custom one?" "n"; then + print_info "Skipping custom .bashrc configuration." + log "Skipped custom .bashrc for $USERNAME." + return 0 + fi + + print_info "Preparing custom .bashrc for '$USERNAME'..." + + temp_source_bashrc=$(mktemp "/tmp/custom_bashrc_source.XXXXXX") + if [[ -z "$temp_source_bashrc" || ! -f "$temp_source_bashrc" ]]; then + print_error "Failed to create temporary file for .bashrc content." + log "Error: mktemp failed for bashrc source." + return 0 + fi + chmod 600 "$temp_source_bashrc" + + if ! cat > "$temp_source_bashrc" <<'EOF' +# shellcheck shell=bash +# =================================================================== +# Universal Portable .bashrc for Modern Terminals +# Optimized for Debian/Ubuntu servers with multi-terminal support +# =================================================================== + +# If not running interactively, don't do anything. +case $- in + *i*) ;; + *) return;; +esac + +# --- History Control --- +# Don't put duplicate lines or lines starting with space in the history. +HISTCONTROL=ignoreboth:erasedups +# Append to the history file, don't overwrite it. +shopt -s histappend +# Set history length with reasonable values for server use. +HISTSIZE=10000 +HISTFILESIZE=20000 +# Allow editing of commands recalled from history. +shopt -s histverify +# Add timestamp to history entries for audit trail (ISO 8601 format). +HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S " +# Ignore common commands from history to reduce clutter. +HISTIGNORE="ls:ll:la:l:cd:pwd:exit:clear:c:history:h" + +# --- General Shell Behavior & Options --- +# Check the window size after each command and update LINES and COLUMNS. +shopt -s checkwinsize +# Allow using '**' for recursive globbing (Bash 4.0+, suppress errors on older versions). +shopt -s globstar 2>/dev/null +# Allow changing to a directory by just typing its name (Bash 4.0+). +shopt -s autocd 2>/dev/null +# Autocorrect minor spelling errors in directory names (Bash 4.0+). +shopt -s cdspell 2>/dev/null +shopt -s dirspell 2>/dev/null +# Correct multi-line command editing. +shopt -s cmdhist 2>/dev/null +# Case-insensitive globbing (commented out to avoid unexpected behavior). +# shopt -s nocaseglob 2>/dev/null + +# Set command-line editing mode. Emacs (default) or Vi. +set -o emacs +# For vi keybindings, uncomment the following line and comment the one above: +# set -o vi + +# Make `less` more friendly for non-text input files. +[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + +# --- Better Less Configuration --- +# Make less more friendly - R shows colors, F quits if one screen, X prevents screen clear. +export LESS='-R -F -X -i -M -w' +# Colored man pages using less (TERMCAP sequences). +export LESS_TERMCAP_mb=$'\e[1;31m' # begin blink +export LESS_TERMCAP_md=$'\e[1;36m' # begin bold +export LESS_TERMCAP_me=$'\e[0m' # reset bold/blink +export LESS_TERMCAP_so=$'\e[01;44;33m' # begin reverse video +export LESS_TERMCAP_se=$'\e[0m' # reset reverse video +export LESS_TERMCAP_us=$'\e[1;32m' # begin underline +export LESS_TERMCAP_ue=$'\e[0m' # reset underline + +# --- Terminal & SSH Compatibility Fixes --- +# Handle Kitty terminal over SSH - fallback to xterm-256color if terminfo unavailable. +if [[ "$TERM" == "xterm-kitty" ]]; then + # Check if kitty terminfo is available, otherwise fallback. + if ! infocmp xterm-kitty &>/dev/null; then + export TERM=xterm-256color + fi + # Ensure the shell looks for user-specific terminfo files. + [[ -d "$HOME/.terminfo" ]] && export TERMINFO="$HOME/.terminfo" +fi + +# Fix for other modern terminals that might not be recognized on older servers. +case "$TERM" in + alacritty|wezterm) + if ! infocmp "$TERM" &>/dev/null; then + export TERM=xterm-256color + fi + ;; +esac + +# Optional: if kitty exists locally, provide a convenience alias for SSH. +# (No effect on hosts without kitty installed.) +if command -v kitty &>/dev/null; then + alias kssh='kitty +kitten ssh' +fi + +# --- Prompt Configuration --- +# Set variable identifying the chroot you work in (used in the prompt below). +if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(/dev/null; then + color_prompt=yes +fi + +# --- Function to parse git branch only if in a git repo --- +parse_git_branch() { + if git rev-parse --git-dir &>/dev/null; then + git branch 2>/dev/null | sed -n '/^\*/s/* \(.*\)/\1/p' + fi + return 0 +} + +# --- Main prompt command function --- +__bash_prompt_command() { + local rc=$? # Capture last command exit status + history -a + history -n + + # --- Initialize prompt components --- + local prompt_err="" prompt_git="" prompt_jobs="" prompt_venv="" + local git_branch job_count + + # Error indicator + (( rc != 0 )) && prompt_err="\[\e[31m\]✗\[\e[0m\]" + + # Git branch (dim yellow) + git_branch=$(parse_git_branch) + [[ -n "$git_branch" ]] && prompt_git="\[\e[2;33m\]($git_branch)\[\e[0m\]" + + # Background jobs (cyan) + job_count=$(jobs -p | wc -l) + (( job_count > 0 )) && prompt_jobs="\[\e[36m\]⚡${job_count}\[\e[0m\]" + + # Python virtualenv (dim green) + [[ -n "$VIRTUAL_ENV" ]] && prompt_venv="\[\e[2;32m\][${VIRTUAL_ENV##*/}]\[\e[0m\]" + + # Ensure spacing between components + [[ -n "$prompt_venv" ]] && prompt_venv=" $prompt_venv" + [[ -n "$prompt_git" ]] && prompt_git=" $prompt_git" + [[ -n "$prompt_jobs" ]] && prompt_jobs=" $prompt_jobs" + [[ -n "$prompt_err" ]] && prompt_err=" $prompt_err" + + # --- Assemble PS1 --- + if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\e[32m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]'"${prompt_venv}${prompt_git}${prompt_jobs}${prompt_err}"' \$ ' + else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w'"${prompt_venv}${git_branch}${prompt_jobs}${prompt_err}"' \$ ' + fi + + # --- Set Terminal Window Title --- + case "$TERM" in + xterm*|rxvt*|xterm-kitty|alacritty|wezterm) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; + esac +} + +# --- Activate dynamic prompt --- +PROMPT_COMMAND=__bash_prompt_command + +# --- Editor Configuration --- +# Set default editor with fallback chain. +if command -v vim &>/dev/null; then + export EDITOR=vim + export VISUAL=vim +elif command -v vi &>/dev/null; then + export EDITOR=vi + export VISUAL=vi +else + export EDITOR=nano + export VISUAL=nano +fi + +# --- Additional Environment Variables --- +# Set default pager. +export PAGER=less +# Prevent Ctrl+S from freezing the terminal. +stty -ixon 2>/dev/null + +# --- Useful Functions --- +# Create a directory and change into it. +mkcd() { + mkdir -p "$1" && cd "$1" +} + +# Create a backup of a file with timestamp. +backup() { + if [ -f "$1" ]; then + local backup_file="$1.backup-$(date +%Y%m%d-%H%M%S)" + cp "$1" "$backup_file" + echo "Backup created: $backup_file" + else + echo "'$1' is not a valid file" >&2 + return 1 + fi +} + +# Extract any archive file with a single command. +extract() { + if [ -f "$1" ]; then + case "$1" in + *.tar.bz2) tar xjf "$1" ;; + *.tar.gz) tar xzf "$1" ;; + *.tar.xz) tar xJf "$1" ;; + *.bz2) bunzip2 "$1" ;; + *.rar) unrar x "$1" ;; + *.gz) gunzip "$1" ;; + *.tar) tar xf "$1" ;; + *.tbz2) tar xjf "$1" ;; + *.tgz) tar xzf "$1" ;; + *.zip) unzip "$1" ;; + *.Z) uncompress "$1" ;; + *.7z) 7z x "$1" ;; + *.deb) ar x "$1" ;; + *.tar.zst) + if command -v zstd &>/dev/null; then + zstd -dc "$1" | tar xf - + else + tar --zstd -xf "$1" + fi + ;; + *) + echo "'$1' cannot be extracted via extract()" >&2 + return 1 + ;; + esac + else + echo "'$1' is not a valid file" >&2 + return 1 + fi +} + +# Quick directory navigation up multiple levels. +up() { + local d="" + local limit="${1:-1}" + for ((i=1; i<=limit; i++)); do + d="../$d" + done + cd "$d" || return +} + +# Find files by name in current directory tree. +ff() { + find . -type f -iname "*$1*" 2>/dev/null +} + +# Find directories by name in current directory tree. +fd() { + find . -type d -iname "*$1*" 2>/dev/null +} + +# Search for text in files recursively. +ftext() { + grep -rnw . -e "$1" 2>/dev/null +} + +# Create a tarball of a directory. +targz() { + if [ -d "$1" ]; then + tar czf "${1%%/}.tar.gz" "${1%%/}" + echo "Created ${1%%/}.tar.gz" + else + echo "'$1' is not a valid directory" >&2 + return 1 + fi +} + +# Show disk usage of current directory, sorted by size. +duh() { + du -h --max-depth=1 "${1:-.}" | sort -hr +} + +# Get the size of a file or directory. +sizeof() { + du -sh "$1" 2>/dev/null +} + +# Show most used commands from history. +histop() { + history | awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a;}' | grep -v "./" | column -c3 -s " " -t | sort -nr | nl | head -n20 +} + +# Quick server info display +sysinfo() { + # --- Self-Contained Color Detection --- + local color_support="" + case "$TERM" in + xterm-color|*-256color|xterm-kitty|alacritty|wezterm) color_support="yes";; + esac + if [ -z "$color_support" ] && [ -x /usr/bin/tput ] && tput setaf 1 &>/dev/null; then + color_support="yes" + fi + + # --- Color Definitions --- + if [ "$color_support" = "yes" ]; then + local CYAN='\e[1;36m' + local YELLOW='\e[1;33m' + local BOLD_RED='\e[1;31m' + local BOLD_WHITE='\e[1;37m' + local GREEN='\e[1;32m' + local DIM='\e[2m' + local RESET='\e[0m' + else + local CYAN='' YELLOW='' BOLD_RED='' BOLD_WHITE='' GREEN='' DIM='' RESET='' + fi + + # --- Header --- + printf "\n${BOLD_WHITE}=== System Information ===${RESET}\n" + + # --- CPU Info --- + local cpu_info + cpu_info=$(lscpu | awk -F: '/Model name/ {print $2; exit}' | xargs || grep -m1 'model name' /proc/cpuinfo | cut -d ':' -f2 | xargs) + [ -z "$cpu_info" ] && cpu_info="Unknown" + + # --- IP Detection (preferred interfaces first) --- + local ip_addr + for iface in eth0 wlan0 ens33 eno1 enp0s3 enp3s0; do + ip_addr=$(ip -4 addr show "$iface" 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) + [ -n "$ip_addr" ] && break + done + [ -z "$ip_addr" ] && ip_addr=$(ip -4 addr show scope global | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1) + + # --- System Info --- + if [ -n "$ip_addr" ]; then + printf "${CYAN}%-15s${RESET} %s ${YELLOW}[%s]${RESET}\n" "Hostname:" "$(hostname)" "$ip_addr" + else + printf "${CYAN}%-15s${RESET} %s\n" "Hostname:" "$(hostname)" + fi + printf "${CYAN}%-15s${RESET} %s\n" "OS:" "$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2 || echo 'Unknown')" + printf "${CYAN}%-15s${RESET} %s\n" "Kernel:" "$(uname -r)" + printf "${CYAN}%-15s${RESET} %s\n" "Uptime:" "$(uptime -p 2>/dev/null || uptime | sed 's/.*up //' | sed 's/,.*//')" + printf "${CYAN}%-15s${RESET} %s\n" "Server time:" "$(date '+%Y-%m-%d %H:%M:%S %Z')" + printf "${CYAN}%-15s${RESET} %s\n" "CPU:" "$cpu_info" + printf "${CYAN}%-15s${RESET} %s\n" "Memory:" "$(free -h | awk '/^Mem:/ {printf "%s / %s (%d%% used)", $3, $2, $3/$2*100}')" + printf "${CYAN}%-15s${RESET} %s\n" "Disk (/):" "$(df -h / | awk 'NR==2 {print $3 " / " $2 " (" $5 " used)"}')" + + # --- Reboot Status --- + if [ -f /var/run/reboot-required ]; then + printf "${CYAN}%-15s${RESET} ${BOLD_RED}⚠ REBOOT REQUIRED${RESET}\n" "System:" + [ -s /var/run/reboot-required.pkgs ] && \ + printf " ${DIM}Reason:${RESET} %s\n" "$(paste -sd ' ' /var/run/reboot-required.pkgs)" + fi + + # --- Available Updates (APT) --- + if command -v apt-get &>/dev/null; then + local total security + local upgradable_all upgradable_list security_list + if [ -x /usr/lib/update-notifier/apt-check ]; then + IFS=';' read -r total security < <(/usr/lib/update-notifier/apt-check 2>/dev/null) + elif [ -r /var/lib/update-notifier/updates-available ]; then + total=$(awk '/packages can be updated/ {print $1}' /var/lib/update-notifier/updates-available) + security=$(awk '/security updates/ {print $1}' /var/lib/update-notifier/updates-available) + else + total=$(apt list --upgradable 2>/dev/null | grep -c upgradable) + security=$(apt list --upgradable 2>/dev/null | grep -ci security) + fi + + if [ -n "$total" ] && [ "$total" -gt 0 ] 2>/dev/null; then + printf "${CYAN}%-15s${RESET} " "Updates:" + if [ -n "$security" ] && [ "$security" -gt 0 ] 2>/dev/null; then + printf "${YELLOW}%s packages (%s security)${RESET}\n" "$total" "$security" + else + printf "%s packages available\n" "$total" + fi + + # List upgradable packages (up to 5) and highlight security + mapfile -t upgradable_all < <(apt list --upgradable 2>/dev/null | tail -n +2) + upgradable_list=$(printf "%s\n" "${upgradable_all[@]}" | head -n5 | awk -F/ '{print $1}') + security_list=$(printf "%s\n" "${upgradable_all[@]}" | grep -i security | head -n5 | awk -F/ '{print $1}') + + [ -n "$upgradable_list" ] && \ + printf " ${DIM}Upgradable:${RESET} %s" "$(echo "$upgradable_list" | paste -sd ', ')" + [ "$total" -gt 5 ] && printf " ... (+%s more)\n" $((total - 5)) || printf "\n" + + [ -n "$security_list" ] && \ + printf " ${YELLOW}Security:${RESET} %s" "$(echo "$security_list" | paste -sd ', ')" + [ "$security" -gt 5 ] && printf " ... (+%s more)\n" $((security - 5)) || printf "\n" + fi + fi + + # --- Docker Info --- + if command -v docker &>/dev/null; then + mapfile -t docker_states < <(docker ps -a --format '{{.State}}' 2>/dev/null) + total=${#docker_states[@]} + if (( total > 0 )); then + running=$(printf "%s\n" "${docker_states[@]}" | grep -c '^running$') + printf "${CYAN}%-15s${RESET} ${GREEN}%s running${RESET} / %s total containers\n" "Docker:" "$running" "$total" + fi + fi + + printf "\n" +} + +# Check for available updates +checkupdates() { + if [ -x /usr/lib/update-notifier/apt-check ]; then + echo "Checking for updates..." + /usr/lib/update-notifier/apt-check --human-readable + elif command -v apt &>/dev/null; then + apt list --upgradable 2>/dev/null + else + echo "No package manager found" + return 1 + fi +} + +# --- Aliases --- +# Enable color support for common commands. +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + alias dir='dir --color=auto' + alias vdir='vdir --color=auto' + alias grep='grep --color=auto' + alias egrep='grep -E --color=auto' + alias fgrep='grep -F --color=auto' + alias diff='diff --color=auto' + alias ip='ip --color=auto' +fi + +# Standard ls aliases with human-readable sizes. +alias ll='ls -alFh' +alias la='ls -A' +alias l='ls -CF' +alias lt='ls -alFht' # Sort by modification time, newest first +alias ltr='ls -alFhtr' # Sort by modification time, oldest first +alias lS='ls -alFhS' # Sort by size, largest first + +# Safety aliases to prompt before overwriting. +alias rm='rm -i' +alias cp='cp -i' +alias mv='mv -i' +alias ln='ln -i' + +# Convenience & Navigation aliases. +alias ..='cd ..' +alias ...='cd ../..' +alias ....='cd ../../..' +alias .....='cd ../../../..' +alias -- -='cd -' # Go to previous directory +alias ~='cd ~' +alias h='history' +alias c='clear' +alias cls='clear' +alias reload='source ~/.bashrc && echo "Bashrc reloaded!"' + +# PATH printer as a function (portable, no echo -e) +unalias path 2>/dev/null +path() { + printf '%s\n' "${PATH//:/$'\n'}" +} + +# Enhanced directory listing. +alias lsd='ls -d */ 2>/dev/null' # List only directories +alias lsf='find . -maxdepth 1 -type f -printf "%f\n"' + +# System resource helpers. +alias df='df -h' +alias du='du -h' +alias free='free -h' +# psgrep as a function to accept patterns reliably +# Ensure no alias conflict before defining the function +unalias psgrep 2>/dev/null +psgrep() { + if [ $# -eq 0 ]; then + echo "Usage: psgrep " >&2 + return 1 + fi + # Build a pattern like '[n]ginx' to avoid matching the grep process itself + local pattern + local term="$1" + pattern="[${term:0:1}]${term:1}" + ps aux | grep -i "$pattern" +} +alias ports='ss -tuln' +alias listening='ss -tlnp' +alias meminfo='free -h -l -t' +alias psmem='ps auxf | sort -nr -k 4 | head -10' +alias pscpu='ps auxf | sort -nr -k 3 | head -10' +alias top10='ps aux --sort=-%mem | head -n 11' + +# Quick network info. +alias myip='curl -s ifconfig.me || curl -s icanhazip.com' # Alternatives: api.ipify.org, icanhazip.co +# Show local IP address(es), excluding loopback. +localip() { + ip -4 addr | awk '/inet/ {print $2}' | cut -d/ -f1 | grep -v '127.0.0.1' +} +alias netstat='ss' +alias ping='ping -c 5' +alias fastping='ping -c 100 -i 0.2' + +# Date and time helpers. +alias now='date +"%Y-%m-%d %H:%M:%S"' +alias nowdate='date +"%Y-%m-%d"' +alias timestamp='date +%s' + +# File operations. +alias count='find . -type f | wc -l' # Count files in current directory +alias cpv='rsync -ah --info=progress2' # Copy with progress +alias wget='wget -c' # Resume wget by default + +# Git shortcuts (if git is available). +if command -v git &>/dev/null; then + alias gs='git status' + alias ga='git add' + alias gc='git commit' + alias gp='git push' + alias gl='git log --oneline --graph --decorate' + alias gd='git diff' + alias gb='git branch' + alias gco='git checkout' +fi + +# --- Docker Shortcuts and Functions --- +if command -v docker &>/dev/null; then + # Core Docker aliases + alias d='docker' + alias dps='docker ps' + alias dpsa='docker ps -a' + alias dpsq='docker ps -q' + alias di='docker images' + alias dv='docker volume ls' + alias dn='docker network ls' + alias dex='docker exec -it' + alias dlog='docker logs -f' + alias dins='docker inspect' + alias drm='docker rm' + alias drmi='docker rmi' + alias dpull='docker pull' + + # Docker system management + alias dprune='docker system prune -f' + alias dprunea='docker system prune -af' + alias ddf='docker system df' + alias dvprune='docker volume prune -f' + alias diprune='docker image prune -af' + + # Docker stats + alias dstats='docker stats --no-stream' + alias dstatsa='docker stats' + dtop() { + docker stats --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}' + } + + # Safe stop all (shows command instead of executing) + alias dstopall='echo "To stop all containers, run: docker stop \$(docker ps -q)"' + + # Docker Compose v2 aliases (check if the compose plugin exists) + if docker compose version &>/dev/null; then + alias dc='docker compose' + alias dcup='docker compose up -d' + alias dcdown='docker compose down' + alias dclogs='docker compose logs -f' + alias dcps='docker compose ps' + alias dcex='docker compose exec' + alias dcbuild='docker compose build' + alias dcbn='docker compose build --no-cache' + alias dcrestart='docker compose restart' + alias dcrecreate='docker compose up -d --force-recreate' + alias dcpull='docker compose pull' + alias dcstop='docker compose stop' + alias dcstart='docker compose start' + alias dcconfig='docker compose config' + alias dcvalidate='docker compose config --quiet && echo "✓ docker-compose.yml is valid" || echo "✗ docker-compose.yml has errors"' + fi + +# --- Docker Functions --- + +# Enter container shell (bash or sh fallback) +dsh() { + if [ -z "$1" ]; then + echo "Usage: dsh " >&2 + return 1 + fi + docker exec -it "$1" bash 2>/dev/null || docker exec -it "$1" sh +} + +# Docker Compose enter shell (bash or sh fallback) +dcsh() { + if [ -z "$1" ]; then + echo "Usage: dcsh " >&2 + return 1 + fi + docker compose exec "$1" bash 2>/dev/null || docker compose exec "$1" sh +} + +# Follow logs for a specific container with tail +dfollow() { + if [ -z "$1" ]; then + echo "Usage: dfollow [lines]" >&2 + return 1 + fi + local lines="${2:-100}" + docker logs -f --tail "$lines" "$1" +} + +# Show container IP addresses +dip() { + if [ -z "$1" ]; then + docker ps -q | xargs -I {} docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' {} 2>/dev/null + else + docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$1" 2>/dev/null + fi +} + +# Show bind mounts for containers +dbinds() { + if [ -z "$1" ]; then + printf "\n\033[1;32mContainer Bind Mounts:\033[0m\n" + printf "═══════════════════════════════════════════════════════════════\n" + docker ps --format '{{.Names}}' | while IFS= read -r container; do + printf "\n\033[1;32m%s\033[0m:\n" "$container" + docker inspect "$container" --format '{{range .Mounts}}{{if eq .Type "bind"}} {{.Source}} → {{.Destination}}{{println}}{{end}}{{end}}' 2>/dev/null + done + printf "\n" + else + printf "\nBind mounts for %s:\n" "$1" + docker inspect "$1" --format '{{range .Mounts}}{{if eq .Type "bind"}} {{.Source}} → {{.Destination}}{{println}}{{end}}{{end}}' 2>/dev/null + fi +} + +# Show disk usage by containers (enable size reporting) +dsize() { + printf "\n%-40s %s\n" "Container" "Size" + printf "═══════════════════════════════════════════════════════════════\n" + docker ps -a --size --format '{{.Names}}\t{{.Size}}' | column -t + printf "\n" +} + +# Restart a compose service and follow logs +dcreload() { + if [ -z "$1" ]; then + echo "Usage: dcreload " >&2 + return 1 + fi + docker compose restart "$1" && docker compose logs -f "$1" +} + +# Update and restart a single compose service +dcupdate() { + if [ -z "$1" ]; then + echo "Usage: dcupdate " >&2 + return 1 + fi + docker compose pull "$1" && docker compose up -d "$1" && docker compose logs -f "$1" +} + +# Show Docker Compose services status with detailed info +dcstatus() { + printf "\n=== Docker Compose Services ===\n\n" + docker compose ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}' + printf "\n=== Resource Usage ===\n\n" + docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}' + printf "\n" +} + +# Watch Docker Compose logs for specific service with grep +dcgrep() { + if [ -z "$1" ] || [ -z "$2" ]; then + echo "Usage: dcgrep " >&2 + return 1 + fi + docker compose logs -f "$1" | grep --color=auto -i "$2" +} + +# Show environment variables for a container +denv() { + if [ -z "$1" ]; then + echo "Usage: denv " >&2 + return 1 + fi + docker inspect "$1" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | sort +} + +# Remove all stopped containers +drmall() { + # Using the modern, direct command + docker container prune -f +} + +fi + +# Systemd shortcuts. +if command -v systemctl &>/dev/null; then + alias sysstart='sudo systemctl start' + alias sysstop='sudo systemctl stop' + alias sysrestart='sudo systemctl restart' + alias sysstatus='sudo systemctl status' + alias sysenable='sudo systemctl enable' + alias sysdisable='sudo systemctl disable' + alias sysreload='sudo systemctl daemon-reload' +fi + +# Apt aliases for Debian/Ubuntu (only if apt is available). +if command -v apt &>/dev/null; then + alias aptup='sudo apt update && sudo apt upgrade' + alias aptin='sudo apt install' + alias aptrm='sudo apt remove' + alias aptsearch='apt search' + alias aptshow='apt show' + alias aptclean='sudo apt autoremove && sudo apt autoclean' + alias aptlist='apt list --installed' +fi + +# --- PATH Configuration --- +# Add user's local bin directories to PATH if they exist. +[ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH" +[ -d "$HOME/bin" ] && export PATH="$HOME/bin:$PATH" + +# --- Server-Specific Configuration --- +# Load hostname-specific configurations if they exist. +# This allows per-server customization without modifying the main bashrc. +if [ -f ~/.bashrc."$(hostname -s)" ]; then + # shellcheck disable=SC1090 + source ~/.bashrc."$(hostname -s)" +fi + +# --- Bash Completion & Personal Aliases --- +# Enable programmable completion features. +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + # shellcheck disable=SC1091 + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + # shellcheck disable=SC1091 + . /etc/bash_completion + fi +fi + +# Source personal aliases if the file exists. +if [ -f ~/.bash_aliases ]; then + # shellcheck disable=SC1090 + . ~/.bash_aliases +fi + +# Source local machine-specific settings that shouldn't be in version control. +if [ -f ~/.bashrc.local ]; then + # shellcheck disable=SC1090 + . ~/.bashrc.local +fi + +# --- Welcome Message for SSH Sessions --- +# Show system info and context on login for SSH sessions. +if [ -n "$SSH_CONNECTION" ]; then + # Use the existing sysinfo function for a full system overview. + sysinfo + + # Display previous login information (skip current session) + last_login=$(last -R "$USER" 2>/dev/null | sed -n '2p' | awk '{$1=""; print}' | xargs) + [ -n "$last_login" ] && printf "Last login: %s\n" "$last_login" + + # Show active sessions + printf "Active sessions: %s\n" "$(who | wc -l)" + printf -- "-----------------------------------------------------\n\n" +fi + +# --- Help System --- +# Display all custom functions and aliases with descriptions +bashhelp() { + local category="${1:-all}" + + case "$category" in + all|"") + cat << 'HELPTEXT' + +╔════════════════════════════════════════════════════════╗ +║ .bashrc - Quick Reference ║ +╚════════════════════════════════════════════════════════╝ + +Usage: bashhelp [category] +Categories: navigation, files, system, docker, git, network + +═══════════════════════════════════════════════════════════════════ +📁 NAVIGATION & DIRECTORY +═══════════════════════════════════════════════════════════════════ + .. Go up one directory + ... Go up two directories + .... Go up three directories + ..... Go up four directories + - Go to previous directory + ~ Go to home directory + + mkcd Create directory and cd into it + up Go up N directories (e.g., up 3) + path Display PATH variable (one per line) + +═══════════════════════════════════════════════════════════════════ +📄 FILE OPERATIONS +═══════════════════════════════════════════════════════════════════ + ll List all files with details (human-readable) + la List all files including hidden + l List files in column format + lt List by time, newest first + ltr List by time, oldest first + lS List by size, largest first + lsd List only directories + lsf List only files + + ff Find files by name (case-insensitive) + fd Find directories by name (case-insensitive) + ftext Search for text in files recursively + + extract Extract any archive (tar, zip, 7z, etc.) + targz Create tar.gz of directory + backup Create timestamped backup of file + + sizeof Get size of file or directory + duh [path] Disk usage sorted by size + count Count files in current directory + cpv Copy with progress bar (rsync) + +═══════════════════════════════════════════════════════════════════ +💻 SYSTEM & MONITORING +═══════════════════════════════════════════════════════════════════ + sysinfo Display comprehensive system information + checkupdates Check for available system updates + + psgrep Search for process by name + psmem Show top 10 processes by memory usage + pscpu Show top 10 processes by CPU usage + top10 Show top 10 memory-consuming processes + + ports Show all listening ports (TCP/UDP) + listening Show listening ports with process info + meminfo Display detailed memory information + + h Show command history + histop Show most used commands + reload Reload bashrc configuration + +═══════════════════════════════════════════════════════════════════ +🐳 DOCKER & DOCKER COMPOSE +═══════════════════════════════════════════════════════════════════ +Docker Commands: + d docker (shortcut) + dps List running containers + dpsa List all containers + di List images + dv List volumes + dn List networks + dex Execute interactive shell in container + dlog Follow container logs + + dsh Enter container shell (bash/sh) + dip [id] Show container IP addresses + dsize Show disk usage by containers + dbinds [id] Show bind mounts for containers + denv Show environment variables + dfollow Follow logs with tail (default 100 lines) + + dstats Container stats snapshot + dstatsa Container stats live + dtop Container stats formatted table + + dprune Prune system (remove unused data) + dprunea Prune all (including images) + dvprune Prune unused volumes + diprune Prune unused images + drmall Remove all stopped containers + +Docker Compose: + dc docker compose (shortcut) + dcup Start services in background + dcdown Stop and remove services + dclogs Follow compose logs + dcps List compose services + dcex Execute command in service + dcsh Enter service shell (bash/sh) + + dcbuild Build services + dcbn Build with no cache + dcrestart Restart services + dcrecreate Recreate services + dcpull Pull service images + dcstop Stop services + dcstart Start services + + dcstatus Show service status & resource usage + dcreload Restart service and follow logs + dcupdate Pull, restart service, follow logs + dcgrep Filter service logs + dcconfig Show resolved compose configuration + dcvalidate Validate compose file syntax + +═══════════════════════════════════════════════════════════════════ +🔀 GIT SHORTCUTS +═══════════════════════════════════════════════════════════════════ + gs git status + ga git add + gc git commit + gp git push + gl git log (graph view) + gd git diff + gb git branch + gco git checkout + +═══════════════════════════════════════════════════════════════════ +🌐 NETWORK +═══════════════════════════════════════════════════════════════════ + myip Show external IP address + localip Show local IP address(es) + ping Ping with 5 packets (default) + fastping Fast ping (100 packets, 0.2s interval) + netstat Show network connections (ss) + +═══════════════════════════════════════════════════════════════════ +⚙️ SYSTEM ADMINISTRATION +═══════════════════════════════════════════════════════════════════ +Systemd: + sysstart Start service + sysstop Stop service + sysrestart Restart service + sysstatus Show service status + sysenable Enable service + sysdisable Disable service + sysreload Reload systemd daemon + +APT (Debian/Ubuntu): + aptup Update and upgrade packages + aptin Install package + aptrm Remove package + aptsearch Search for packages + aptshow Show package information + aptclean Remove unused packages + aptlist List installed packages + +═══════════════════════════════════════════════════════════════════ +🕒 DATE & TIME +═══════════════════════════════════════════════════════════════════ + now Current date and time (YYYY-MM-DD HH:MM:SS) + nowdate Current date (YYYY-MM-DD) + timestamp Unix timestamp + +═══════════════════════════════════════════════════════════════════ +ℹ️ HELP & INFORMATION +═══════════════════════════════════════════════════════════════════ + bashhelp Show this help (all categories) + bashhelp navigation Show navigation commands only + bashhelp files Show file operation commands + bashhelp system Show system monitoring commands + bashhelp docker Show docker commands only + bashhelp git Show git shortcuts + bashhelp network Show network commands + +═══════════════════════════════════════════════════════════════════ + +💡 TIP: Most commands support --help or -h for more information + The prompt shows: ✗ for failed commands, git branch when in repo + +HELPTEXT + ;; + + navigation) + cat << 'HELPTEXT' + +═══ NAVIGATION & DIRECTORY COMMANDS ═══ + + .. Go up one directory + ... Go up two directories + .... Go up three directories + ..... Go up four directories + - Go to previous directory + ~ Go to home directory + + mkcd Create directory and cd into it + up Go up N directories + path Display PATH variable + +Examples: + mkcd ~/projects/newapp # Create and enter directory + up 3 # Go up 3 levels + cd - # Return to previous directory + +HELPTEXT + ;; + + files) + cat << 'HELPTEXT' + +═══ FILE OPERATION COMMANDS ═══ + +Listing: + ll, la, l, lt, ltr, lS, lsd, lsf + +Finding: + ff Find files by name + fd Find directories by name + ftext Search text in files + +Archives: + extract Extract any archive type + targz Create tar.gz archive + backup Create timestamped backup + +Size Info: + sizeof Get size of file/directory + duh [path] Disk usage sorted by size + count Count files in directory + cpv Copy with progress (rsync) + +Examples: + ff README # Find files named *README* + extract data.tar.gz + backup ~/.bashrc + +HELPTEXT + ;; + + system) + cat << 'HELPTEXT' + +═══ SYSTEM MONITORING COMMANDS ═══ + +Overview: + sysinfo Comprehensive system info + checkupdates Check for package updates + +Processes: + psgrep Search processes + psmem Top 10 by memory + pscpu Top 10 by CPU + top10 Top memory consumers + +Network: + ports Listening ports + listening Ports with process info + +Memory: + meminfo Detailed memory info + free Free memory (human-readable) + +History: + h Show history + histop Most used commands + reload Reload bashrc + +Examples: + psgrep nginx + psmem | grep docker + +HELPTEXT + ;; + + docker) + cat << 'HELPTEXT' + +═══ DOCKER COMMANDS ═══ + +Basic: + dps, dpsa, di, dv, dn, dex, dlog + +Management: + dsh Enter container shell + dip [id] Show IP addresses + dsize Show disk usage + dbinds [id] Show bind mounts + denv Show environment variables + dfollow Follow logs + +Stats & Cleanup: + dstats, dstatsa, dtop + dprune, dprunea, dvprune, diprune + drmall Remove stopped containers + +Docker Compose: + dcup, dcdown, dclogs, dcps, dcex, dcsh + dcbuild, dcrestart, dcrecreate + dcstatus Status & resource usage + dcreload Restart & follow logs + dcupdate Pull & update service + dcgrep

Filter logs + dcvalidate Validate compose file + +Examples: + dsh mycontainer + dcsh web bash + dcupdate nginx + dcgrep app "error" + +HELPTEXT + ;; + + git) + cat << 'HELPTEXT' + +═══ GIT SHORTCUTS ═══ + + gs git status + ga git add + gc git commit + gp git push + gl git log (graph) + gd git diff + gb git branch + gco git checkout + +Examples: + gs # Check status + ga . # Add all changes + gc -m "Update docs" # Commit + gp # Push to remote + +HELPTEXT + ;; + + network) + cat << 'HELPTEXT' + +═══ NETWORK COMMANDS ═══ + + myip Show external IP + localip Show local IP(s) + ports Show listening ports + listening Ports with process info + ping Ping (5 packets) + fastping Fast ping (100 packets) + netstat Network connections + +Examples: + myip # Get public IP + listening | grep 80 + ping google.com + +HELPTEXT + ;; + + *) + echo "Unknown category: $category" + echo "Available categories: navigation, files, system, docker, git, network" + echo "Use 'bashhelp' or 'bashhelp all' for complete reference" + return 1 + ;; + esac +} + +# Preserve Bash's builtin `help` while integrating bashhelp +help() { + case "${1:-}" in + ""|all|navigation|files|system|docker|git|network) + bashhelp "$@" + ;; + *) + command help "$@" 2>/dev/null || builtin help "$@" + ;; + esac +} + +# Shorter alias for bashhelp (not for help - that's a function now) +alias bh='bashhelp' + +# Quick command list (compact) +alias commands='compgen -A function -A alias | grep -v "^_" | sort | column' + + +# --- Performance Note --- +# This configuration is optimized for performance using built-in bash operations +# and minimizing external command calls. If startup feels slow, check: +# - ~/.bash_aliases and ~/.bashrc.local for expensive operations +# - Consider moving rarely-used functions to separate files +# - Use 'time bash -i -c exit' to measure startup time +EOF + then + print_error "Failed to write .bashrc content to temporary file $temp_source_bashrc." + log "Critical error: Failed to write bashrc content to $temp_source_bashrc." + rm -f "$temp_source_bashrc" 2>/dev/null + return 0 + fi + + log "Successfully created temporary .bashrc source at $temp_source_bashrc" + + if [[ -f "$BASHRC_PATH" ]] && ! grep -q "generated by /usr/sbin/adduser" "$BASHRC_PATH" 2>/dev/null; then + local BASHRC_BACKUP + BASHRC_BACKUP="$BASHRC_PATH.backup_$(date +%Y%m%d_%H%M%S)" + print_info "Backing up existing non-default .bashrc to $BASHRC_BACKUP" + cp "$BASHRC_PATH" "$BASHRC_BACKUP" + log "Backed up existing .bashrc to $BASHRC_BACKUP" + fi + + local temp_fallback_path="/tmp/custom_bashrc_for_${USERNAME}.txt" + + if ! tee "$BASHRC_PATH" < "$temp_source_bashrc" > /dev/null + then + print_error "Failed to automatically write custom .bashrc to $BASHRC_PATH." + log "Error writing custom .bashrc for $USERNAME to $BASHRC_PATH (likely permissions issue)." + + if cp "$temp_source_bashrc" "$temp_fallback_path"; then + chmod 644 "$temp_fallback_path" + print_warning "ACTION REQUIRED: The custom .bashrc content has been saved to:" + print_warning " ${temp_fallback_path}" + print_info "After setup, please manually copy it:" + print_info " sudo cp ${temp_fallback_path} ${BASHRC_PATH}" + print_info " sudo chown ${USERNAME}:${USERNAME} ${BASHRC_PATH}" + print_info " sudo chmod 644 ${BASHRC_PATH}" + log "Saved custom .bashrc content to $temp_fallback_path for manual installation." + keep_temp_source_on_error=true + else + print_error "Also failed to save custom .bashrc content to $temp_fallback_path." + log "Critical error: Failed both writing to $BASHRC_PATH and copying $temp_source_bashrc to $temp_fallback_path." + fi + else + if ! chown "$USERNAME:$USERNAME" "$BASHRC_PATH" || ! chmod 644 "$BASHRC_PATH"; then + print_warning "Failed to set correct ownership/permissions on $BASHRC_PATH." + log "Failed to chown/chmod $BASHRC_PATH" + print_warning "ACTION REQUIRED: Please manually set ownership/permissions:" + print_info " sudo chown ${USERNAME}:${USERNAME} ${BASHRC_PATH}" + print_info " sudo chmod 644 ${BASHRC_PATH}" + print_info " (Source content is in ${temp_source_bashrc})" + keep_temp_source_on_error=true + else + print_success "Custom .bashrc created for '$USERNAME'." + log "Custom .bashrc configuration completed for $USERNAME." + rm -f "$temp_fallback_path" 2>/dev/null + fi + fi + + if [[ "$keep_temp_source_on_error" == false ]]; then + rm -f "$temp_source_bashrc" 2>/dev/null + fi + + trap - INT TERM + + return 0 +} + # --- USER INTERACTION --- confirm() { @@ -1571,6 +2823,9 @@ setup_user() { fi fi + # Add custom .bashrc + configure_custom_bashrc "$USER_HOME" "$USERNAME" + print_info "Adding '$USERNAME' to sudo group..." if ! groups "$USERNAME" | grep -qw sudo; then if ! usermod -aG sudo "$USERNAME"; then @@ -2641,7 +3896,7 @@ setup_backup() { local ROOT_SSH_KEY="$ROOT_SSH_DIR/id_ed25519" local BACKUP_SCRIPT_PATH="/root/run_backup.sh" local EXCLUDE_FILE_PATH="/root/rsync_exclude.txt" - local CRON_MARKER="#-*- managed by setup_harden script -*-" + local CRON_MARKER="#-*- installed by du_setup script -*-" # --- Generate SSH Key for Root --- if [[ ! -f "$ROOT_SSH_KEY" ]]; then @@ -2885,7 +4140,7 @@ rsync_exit_code=$?; echo "$rsync_output" >> "$LOG_FILE" if [[ $rsync_exit_code -eq 0 ]]; then data_transferred=$(echo "$rsync_output" | grep 'Total transferred file size' | awk '{print $5}' | sed 's/,//g') human_readable=$(numfmt --to=iec-i --suffix=B --format="%.2f" "$data_transferred" 2>/dev/null || echo "0 B") - message="Backup completed successfully.\nData Transferred: ${human_readable}" + printf -v message "Backup completed successfully.\nData Transferred: %s" "${human_readable}" send_notification "SUCCESS" "$message" else message="rsync failed with exit code ${rsync_exit_code}. Check log for details." diff --git a/du_setup.sh.sha256 b/du_setup.sh.sha256 index b446a55..a14fc23 100644 --- a/du_setup.sh.sha256 +++ b/du_setup.sh.sha256 @@ -1 +1 @@ -dfc27413d157a29510f2f516cf594a9940942ec8622d292d94a3936b4490a2a6 du_setup.sh +e402db17ee1738ec96b2f4e2bcd1d1756df8b6a7e49de0953681a49f7f42fe18 du_setup.sh