ops-jrz1/scripts/dev-remove.sh
Dan 812ffb9802 Add --dry-run flag to dev-remove.sh
Preview mode shows what would be removed without making changes.
Skips confirmation prompt and outputs cyan-colored dry-run messages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 19:40:21 -08:00

239 lines
6.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# dev-remove.sh - Remove a dev account
# Usage: dev-remove.sh <username> [--archive] [--dry-run]
#
# Removes:
# - Unix user account
# - Home directory (or archives if --archive flag)
# - Maubot plugin symlinks
set -euo pipefail
MAUBOT_PLUGINS_DIR="/var/lib/maubot/plugins"
ARCHIVE_DIR="/var/backups/devs"
DRY_RUN=false
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
log_dry() { echo -e "${CYAN}[DRY-RUN]${NC} $1"; }
usage() {
echo "Usage: $0 <username> [--archive] [--dry-run]"
echo ""
echo "Arguments:"
echo " username - Dev's username to remove"
echo " --archive - Archive home directory instead of deleting"
echo " --dry-run - Show what would be done without making changes"
echo ""
echo "Example:"
echo " $0 alice # Delete user and home directory"
echo " $0 alice --archive # Archive home directory before deleting"
echo " $0 alice --dry-run # Preview what would be removed"
exit 1
}
validate_username() {
local username="$1"
# Check if user exists
if ! id "$username" &>/dev/null; then
log_error "User '$username' does not exist"
exit 1
fi
# Safety check - don't delete system users
local uid
uid=$(id -u "$username")
if [[ $uid -lt 1000 ]]; then
log_error "Refusing to delete system user '$username' (UID $uid < 1000)"
exit 1
fi
# Safety check - don't delete important users
case "$username" in
root|dan|admin|maubot|postgres|nginx)
log_error "Refusing to delete protected user '$username'"
exit 1
;;
esac
}
remove_maubot_symlinks() {
local username="$1"
if [[ "$DRY_RUN" == true ]]; then
log_dry "Checking maubot plugin symlinks for '$username'..."
else
log_info "Removing maubot plugin symlinks for '$username'..."
fi
# Remove any symlinks that point to user's home directory
local count=0
for symlink in "$MAUBOT_PLUGINS_DIR"/*; do
if [[ -L "$symlink" ]]; then
local target
target=$(readlink "$symlink")
if [[ "$target" == "/home/$username/"* ]]; then
if [[ "$DRY_RUN" == true ]]; then
log_dry "Would remove symlink: $symlink -> $target"
else
rm "$symlink"
log_info "Removed symlink: $symlink"
fi
((count++)) || true
fi
fi
done
# Also remove any plugins named after user
for symlink in "$MAUBOT_PLUGINS_DIR/${username}-"*; do
if [[ -e "$symlink" || -L "$symlink" ]]; then
if [[ "$DRY_RUN" == true ]]; then
log_dry "Would remove: $symlink"
else
rm -f "$symlink"
log_info "Removed: $symlink"
fi
((count++)) || true
fi
done
if [[ $count -eq 0 ]]; then
log_info "No maubot symlinks found for '$username'"
fi
}
archive_home() {
local username="$1"
local home_dir="/home/$username"
if [[ ! -d "$home_dir" ]]; then
log_warn "Home directory does not exist: $home_dir"
return
fi
local archive_name
archive_name="${username}_$(date +%Y%m%d_%H%M%S).tar.gz"
local archive_path="$ARCHIVE_DIR/$archive_name"
if [[ "$DRY_RUN" == true ]]; then
log_dry "Would archive home directory to: $archive_path"
else
log_info "Archiving home directory..."
mkdir -p "$ARCHIVE_DIR"
tar -czf "$archive_path" -C /home "$username"
chmod 600 "$archive_path"
log_info "Archived to: $archive_path"
fi
}
remove_user() {
local username="$1"
if [[ "$DRY_RUN" == true ]]; then
log_dry "Would kill processes for user '$username'"
log_dry "Would remove user '$username' and home directory"
else
log_info "Removing user '$username'..."
# Kill any running processes
pkill -u "$username" 2>/dev/null || true
sleep 1
# Remove user and home directory
userdel -r "$username" 2>/dev/null || {
# If userdel -r fails, try without -r and manually remove home
userdel "$username" 2>/dev/null || true
rm -rf "/home/${username:?}"
}
log_info "User removed"
fi
}
main() {
if [[ $# -lt 1 ]]; then
usage
fi
local username="$1"
local archive=false
# Parse flags
shift
while [[ $# -gt 0 ]]; do
case "$1" in
--archive)
archive=true
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
# Must run as root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
validate_username "$username"
if [[ "$DRY_RUN" == true ]]; then
echo ""
log_dry "Preview of removing user '$username':"
if [[ "$archive" == true ]]; then
log_dry "Home directory would be archived to $ARCHIVE_DIR"
else
log_dry "Home directory would be PERMANENTLY DELETED"
fi
echo ""
else
# Confirm deletion
echo ""
log_warn "This will remove user '$username' and all their data!"
if [[ "$archive" == true ]]; then
echo "Home directory will be archived to $ARCHIVE_DIR"
else
echo "Home directory will be PERMANENTLY DELETED"
fi
echo ""
read -rp "Are you sure? (yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
log_info "Aborted"
exit 0
fi
fi
remove_maubot_symlinks "$username"
if [[ "$archive" == true ]]; then
archive_home "$username"
fi
remove_user "$username"
if [[ "$DRY_RUN" == true ]]; then
log_dry "Dry run complete - no changes made"
else
log_info "Dev '$username' removed successfully"
fi
}
main "$@"