Add optional dtop - Docker container monitoring TUI

This commit is contained in:
buildplan 2025-11-06 21:20:56 +00:00
parent 103abb8d1d
commit ff6bcc97ec

View File

@ -1,8 +1,10 @@
#!/bin/bash
# Debian and Ubuntu Server Hardening Interactive Script
# Version: 0.73 | 2025-10-22
# Version: 0.74 | 2025-11-06
# Changelog:
# - v0.74: Add optional dtop (https://github.com/amir20/dtop) after docker installation.
#. Update .bashrc
# - v0.73: Revised/improved logic in .bashrc for memory and system updates.
# - 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
@ -77,7 +79,7 @@
set -euo pipefail
# --- Update Configuration ---
CURRENT_VERSION="0.73"
CURRENT_VERSION="0.74"
SCRIPT_URL="https://raw.githubusercontent.com/buildplan/du_setup/refs/heads/main/du_setup.sh"
CHECKSUM_URL="${SCRIPT_URL}.sha256"
@ -228,7 +230,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.73 | 2025-10-22${NC}"
printf '%s\n' "${CYAN}║ v0.74 | 2025-11-06${NC}"
printf '%s\n' "${CYAN}║ ║${NC}"
printf '%s\n' "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}"
printf '\n'
@ -1238,16 +1240,15 @@ __bash_prompt_command() {
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
elif command -v nano &>/dev/null; then
export EDITOR=nano
export VISUAL=nano
else
export EDITOR=vi
export VISUAL=vi
fi
# --- Additional Environment Variables ---
@ -1265,7 +1266,7 @@ mkcd() {
# 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)"
local backup_file; backup_file="$1.backup-$(date +%Y%m%d-%H%M%S)"
cp "$1" "$backup_file"
echo "Backup created: $backup_file"
else
@ -1300,7 +1301,7 @@ extract() {
;;
*)
echo "'$1' cannot be extracted via extract()" >&2
return 1
return 1 # Add return 1 for consistency
;;
esac
else
@ -1334,6 +1335,9 @@ ftext() {
grep -rnw . -e "$1" 2>/dev/null
}
# Search history easily
hgrep() { history | grep -i --color=auto "$@"; }
# Create a tarball of a directory.
targz() {
if [ -d "$1" ]; then
@ -1357,7 +1361,36 @@ sizeof() {
# 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
history | awk -v ig="$HISTIGNORE" 'BEGIN{OFS="\t";gsub(/:/,"|",ig);ir="^("ig")($| )";sr="(^|\\s)\\./"}
{cmd=$4;for(i=5;i<=NF;i++)cmd=cmd" "$i}
(cmd==""||cmd~ir||cmd~sr){next}
{C[cmd]++;t++}
END{if(t>0)for(a in C)printf"%d\t%.2f%%\t%s\n",C[a],(C[a]/t*100),a}' |
sort -nr | head -n20 |
awk 'BEGIN{
FS="\t";
maxc=length("COUNT");
maxp=length("PERCENT");
}
{
data[NR]=$0;
len1=length($1);
len2=length($2);
if(len1>maxc)maxc=len1;
if(len2>maxp)maxp=len2;
}
END{
fmt=" %-4s %-*s %-*s %s\n";
printf fmt,"RANK",maxc,"COUNT",maxp,"PERCENT","COMMAND";
sep_c=sep_p="";
for(i=1;i<=maxc;i++)sep_c=sep_c"-";
for(i=1;i<=maxp;i++)sep_p=sep_p"-";
printf fmt,"----",maxc,sep_c,maxp,sep_p,"-------";
for(i=1;i<=NR;i++){
split(data[i],f,"\t");
printf fmt,i".",maxc,f[1],maxp,f[2],f[3]
}
}'
}
# Quick server info display
@ -1392,18 +1425,45 @@ sysinfo() {
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 Detection ---
local ip_addr public_ipv4 public_ipv6
# Try to get public IPv4 first
public_ipv4=$(curl -4 -s -m 2 --connect-timeout 1 https://checkip.amazonaws.com 2>/dev/null || \
curl -4 -s -m 2 --connect-timeout 1 https://ipconfig.io 2>/dev/null || \
curl -4 -s -m 2 --connect-timeout 1 https://api.ipify.org 2>/dev/null)
# If no IPv4, try IPv6
if [ -z "$public_ipv4" ]; then
public_ipv6=$(curl -6 -s -m 2 --connect-timeout 1 https://ipconfig.io 2>/dev/null || \
curl -6 -s -m 2 --connect-timeout 1 https://icanhazip.co 2>/dev/null || \
curl -6 -s -m 2 --connect-timeout 1 https://api64.ipify.org 2>/dev/null)
fi
# Get local/internal IP as fallback
for iface in eth0 ens3 enp0s3 enp0s6 wlan0 ens33 eno1; 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)
[ -z "$ip_addr" ] && ip_addr=$(ip -4 addr show scope global 2>/dev/null | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1)
# --- System Info ---
if [ -n "$ip_addr" ]; then
if [ -n "$public_ipv4" ]; then
# Show public IPv4 (preferred)
printf "${CYAN}%-15s${RESET} %s ${YELLOW}[%s]${RESET}" "Hostname:" "$(hostname)" "$public_ipv4"
# Show local IP if different from public
if [ -n "$ip_addr" ] && [ "$ip_addr" != "$public_ipv4" ]; then
printf " ${DIM}(local: %s)${RESET}\n" "$ip_addr"
else
printf "\n"
fi
elif [ -n "$public_ipv6" ]; then
# Show public IPv6 if no IPv4
printf "${CYAN}%-15s${RESET} %s ${YELLOW}[%s]${RESET}" "Hostname:" "$(hostname)" "$public_ipv6"
[ -n "$ip_addr" ] && printf " ${DIM}(local: %s)${RESET}\n" "$ip_addr" || printf "\n"
elif [ -n "$ip_addr" ]; then
# Show local IP only
printf "${CYAN}%-15s${RESET} %s ${YELLOW}[%s]${RESET}\n" "Hostname:" "$(hostname)" "$ip_addr"
else
# No IP detected
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')"
@ -1439,19 +1499,23 @@ sysinfo() {
security="${apt_check_output##*;}"
fi
fi
# Fallback if apt-check didn't provide values
if [ -z "$total" ] && [ -r /var/lib/update-notifier/updates-available ]; then
total=$(awk '/[0-9]+ (update|package)s? can be (updated|applied|installed)/ {print $1; exit}' /var/lib/update-notifier/updates-available 2>/dev/null)
security=$(awk '/[0-9]+ (update|package)s? .*security/ {print $1; exit}' /var/lib/update-notifier/updates-available 2>/dev/null)
fi
# Final fallback
if [ -z "$total" ]; then
total=$(apt list --upgradable 2>/dev/null | grep -c upgradable)
security=$(apt list --upgradable 2>/dev/null | grep -ci security)
fi
total="${total:-0}"
security="${security:-0}"
# Display updates if available
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
@ -1485,6 +1549,28 @@ sysinfo() {
fi
fi
# --- Tailscale Info (if installed and connected) ---
if command -v tailscale &>/dev/null; then
local ts_ipv4 ts_ipv6 ts_hostname
# Get Tailscale IPs
ts_ipv4=$(tailscale ip -4 2>/dev/null)
ts_ipv6=$(tailscale ip -6 2>/dev/null)
# Only show if connected
if [ -n "$ts_ipv4" ] || [ -n "$ts_ipv6" ]; then
# Get hostname from status (FIXED: use head -n1 to get only first line)
ts_hostname=$(tailscale status --self --peers=false 2>/dev/null | head -n1 | awk '{print $2}')
printf "${CYAN}%-15s${RESET} " "Tailscale:"
printf "${GREEN}Connected${RESET}"
[ -n "$ts_ipv4" ] && printf " - %s" "$ts_ipv4"
[ -n "$ts_hostname" ] && printf " ${DIM}(%s)${RESET}" "$ts_hostname"
printf "\n"
# Optional: Show IPv6 on second line if available
if [ -n "$ts_ipv6" ]; then
printf " ${DIM}IPv6: %s${RESET}\n" "$ts_ipv6"
fi
fi
fi
printf "\n"
}
@ -1501,6 +1587,47 @@ checkupdates() {
fi
}
# Disk space alert (warns if any partition > 80%)
diskcheck() {
df -h | awk '
NR > 1 {
usage = $5
gsub(/%/, "", usage)
if (usage > 80) {
printf "⚠️ %s\n", $0
found = 1
}
}
END {
if (!found) print "✓ All disks below 80%"
}
'
}
# Directory bookmarks
export MARKPATH=$HOME/.marks
[ -d "$MARKPATH" ] || mkdir -p "$MARKPATH"
mark() { ln -sfn "$(pwd)" "$MARKPATH/${1:-$(basename "$PWD")}"; }
jump() { cd -P "$MARKPATH/$1" 2>/dev/null || ls -l "$MARKPATH"; }
# Service status shortcut (cleaner output)
svc() { sudo systemctl status "$1" --no-pager -l | head -20; }
alias failed='systemctl --failed --no-pager'
# Show top 10 processes by CPU
topcpu() { ps aux --sort=-%cpu | head -11; }
# Show top 10 processes by memory
topmem() { ps aux --sort=-%mem | head -11; }
# Network connections summary
netsum() {
echo "=== Active Connections ==="
ss -s
echo -e "\n=== Listening Ports ==="
sudo ss -tulnp | grep LISTEN | awk '{print $5, $7}' | sort -u
}
# --- Aliases ---
# Enable color support for common commands.
if [ -x /usr/bin/dircolors ]; then
@ -1523,6 +1650,9 @@ 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
# Last command with sudo
alias please='sudo $(history -p !!)'
# Safety aliases to prompt before overwriting.
alias rm='rm -i'
alias cp='cp -i'
@ -1582,6 +1712,7 @@ alias myip='curl -s ifconfig.me || curl -s icanhazip.com' # Alternatives: api.ip
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'
@ -1635,12 +1766,14 @@ if command -v docker &>/dev/null; then
# Docker stats
alias dstats='docker stats --no-stream'
alias dstatsa='docker stats'
dtop() {
dst() {
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)"'
alias dstopa='echo "To stop all containers, run: docker stop \$(docker ps -q)"'
# Start all stopped containers
alias dstarta='docker start $(docker ps -aq)'
# Docker Compose v2 aliases (check if the compose plugin exists)
if docker compose version &>/dev/null; then
@ -1880,6 +2013,8 @@ Categories: navigation, files, system, docker, git, network
mkcd <dir> Create directory and cd into it
up <n> Go up N directories (e.g., up 3)
path Display PATH variable (one per line)
mark <name> Bookmark current directory
jump <name> Jump to a bookmarked directory
═══════════════════════════════════════════════════════════════════
📄 FILE OPERATIONS
@ -1911,18 +2046,22 @@ Categories: navigation, files, system, docker, git, network
═══════════════════════════════════════════════════════════════════
sysinfo Display comprehensive system information
checkupdates Check for available system updates
diskcheck Check for disk partitions over 80%
psgrep <pat> 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
topcpu Show top 10 processes by CPU
topmem Show top 10 processes by Memory
pscpu Show top 10 processes by CPU (tree view)
psmem Show top 10 processes by Memory (tree view)
ports Show all listening ports (TCP/UDP)
listening Show listening ports with process info
meminfo Display detailed memory information
h Show command history
hgrep <pat> Search command history
histop Show most used commands
c, cls Clear the screen
reload Reload bashrc configuration
═══════════════════════════════════════════════════════════════════
@ -1947,7 +2086,7 @@ Docker Commands:
dstats Container stats snapshot
dstatsa Container stats live
dtop Container stats formatted table
dst Container stats formatted table
dprune Prune system (remove unused data)
dprunea Prune all (including images)
@ -1996,6 +2135,8 @@ Docker Compose:
═══════════════════════════════════════════════════════════════════
myip Show external IP address
localip Show local IP address(es)
netsum Network connections summary
kssh SSH wrapper for kitty terminal
ping Ping with 5 packets (default)
fastping Fast ping (100 packets, 0.2s interval)
netstat Show network connections (ss)
@ -2004,6 +2145,8 @@ Docker Compose:
⚙️ SYSTEM ADMINISTRATION
═══════════════════════════════════════════════════════════════════
Systemd:
svc <srv> Show service status (brief)
failed List failed systemd services
sysstart <srv> Start service
sysstop <srv> Stop service
sysrestart <srv> Restart service
@ -2021,6 +2164,9 @@ APT (Debian/Ubuntu):
aptclean Remove unused packages
aptlist List installed packages
Sudo:
please Run last command with sudo
═══════════════════════════════════════════════════════════════════
🕒 DATE & TIME
═══════════════════════════════════════════════════════════════════
@ -2032,6 +2178,8 @@ APT (Debian/Ubuntu):
HELP & INFORMATION
═══════════════════════════════════════════════════════════════════
bashhelp Show this help (all categories)
bh Alias for bashhelp
commands List all custom functions and aliases
bashhelp navigation Show navigation commands only
bashhelp files Show file operation commands
bashhelp system Show system monitoring commands
@ -2042,7 +2190,7 @@ APT (Debian/Ubuntu):
═══════════════════════════════════════════════════════════════════
💡 TIP: Most commands support --help or -h for more information
The prompt shows: ✗ for failed commands, git branch when in repo
The prompt shows: ✗ for failed commands, (git branch) when in repo
HELPTEXT
;;
@ -2062,11 +2210,14 @@ HELPTEXT
mkcd <dir> Create directory and cd into it
up <n> Go up N directories
path Display PATH variable
mark <name> Bookmark current directory
jump <name> Jump to a bookmarked directory
Examples:
mkcd ~/projects/newapp # Create and enter directory
up 3 # Go up 3 levels
cd - # Return to previous directory
mark proj1 # Bookmark current dir as 'proj1'
jump proj1 # Jump back to 'proj1'
HELPTEXT
;;
@ -2111,12 +2262,14 @@ HELPTEXT
Overview:
sysinfo Comprehensive system info
checkupdates Check for package updates
diskcheck Check for disks > 80%
Processes:
psgrep <pat> Search processes
psmem Top 10 by memory
pscpu Top 10 by CPU
top10 Top memory consumers
topcpu Top 10 by CPU
topmem Top 10 by Memory
pscpu Top 10 by CPU (tree view)
psmem Top 10 by Memory (tree view)
Network:
ports Listening ports
@ -2126,9 +2279,11 @@ Memory:
meminfo Detailed memory info
free Free memory (human-readable)
History:
Shell:
h Show history
hgrep <pat> Search history
histop Most used commands
c, cls Clear screen
reload Reload bashrc
Examples:
@ -2155,7 +2310,7 @@ Management:
dfollow <id> Follow logs
Stats & Cleanup:
dstats, dstatsa, dtop
dstats, dstatsa, dst
dprune, dprunea, dvprune, diprune
drmall Remove stopped containers
@ -2165,7 +2320,7 @@ Docker Compose:
dcstatus Status & resource usage
dcreload <srv> Restart & follow logs
dcupdate <srv> Pull & update service
dcgrep <srv> <p> Filter logs
dcgrep <s> <p> Filter logs
dcvalidate Validate compose file
Examples:
@ -2207,11 +2362,13 @@ HELPTEXT
myip Show external IP
localip Show local IP(s)
netsum Network connection summary
kssh SSH wrapper for kitty
ports Show listening ports
listening Ports with process info
ping Ping (5 packets)
fastping Fast ping (100 packets)
netstat Network connections
netstat Network connections (ss)
Examples:
myip # Get public IP
@ -2231,6 +2388,7 @@ HELPTEXT
}
# Preserve Bash's builtin `help` while integrating bashhelp
# This wrapper routes custom help to bashhelp, bash builtins to builtin help
help() {
case "${1:-}" in
""|all|navigation|files|system|docker|git|network)
@ -3680,6 +3838,68 @@ EOF
fi
print_warning "NOTE: '$USERNAME' must log out and back in to use Docker without sudo."
log "Docker installation completed."
# Offer dtop installation
install_dtop_optional
}
install_dtop_optional() {
if sudo sh -c 'command -v dtop' >/dev/null 2>&1 || command -v dtop >/dev/null 2>&1; then
print_info "dtop is already installed."
return 0
fi
if ! confirm "Install 'dtop' (Docker container monitoring TUI)?"; then
print_info "Skipping dtop installation."
return 0
fi
print_info "Installing dtop for user '$USERNAME'..."
local DTOP_INSTALLER="/tmp/dtop-installer.sh"
if ! curl -fsSL "https://github.com/amir20/dtop/releases/latest/download/dtop-installer.sh" -o "$DTOP_INSTALLER"; then
print_warning "Failed to download dtop installer. Continuing setup..."
log "Failed to download dtop installer."
return 0
fi
chmod +x "$DTOP_INSTALLER"
trap 'rm -f "$DTOP_INSTALLER"' RETURN
local USER_HOME
USER_HOME=$(getent passwd "$USERNAME" | cut -d: -f6)
local USER_LOCAL_BIN="$USER_HOME/.local/bin"
if [[ ! -d "$USER_LOCAL_BIN" ]]; then
print_info "Creating $USER_LOCAL_BIN..."
if ! sudo -u "$USERNAME" mkdir -p "$USER_LOCAL_BIN"; then
print_warning "Failed to create $USER_LOCAL_BIN. Skipping dtop."
return 0
fi
fi
if sudo -u "$USERNAME" bash "$DTOP_INSTALLER" < /dev/null >> "$LOG_FILE" 2>&1; then
# Verify installation
if [[ -f "$USER_LOCAL_BIN/dtop" ]]; then
sudo -u "$USERNAME" chmod +x "$USER_LOCAL_BIN/dtop"
local BASHRC="$USER_HOME/.bashrc"
if [[ -f "$BASHRC" ]] && ! grep -q "\.local/bin" "$BASHRC"; then
print_info "Adding ~/.local/bin to PATH in $BASHRC..."
{
echo ''
echo '# Add local bin to PATH'
# shellcheck disable=SC2016
echo 'if [ -d "$HOME/.local/bin" ]; then PATH="$HOME/.local/bin:$PATH"; fi'
} >> "$BASHRC"
chown "$USERNAME:$USERNAME" "$BASHRC"
if grep -q "\.local/bin" "$BASHRC"; then
print_info "PATH configuration updated successfully."
else
print_warning "Failed to update PATH, but dtop is still installed."
fi
fi
print_success "dtop installed successfully to $USER_LOCAL_BIN."
log "dtop installed to $USER_LOCAL_BIN for user $USERNAME"
else
print_warning "dtop installer finished, but binary not found at $USER_LOCAL_BIN/dtop"
log "dtop binary missing after user installation attempt."
fi
else
print_warning "dtop installation script failed. Continuing setup..."
log "dtop installation script failed."
fi
}
install_tailscale() {