Learner account management: - learner-add.sh: create accounts with SSH, plugin skeleton - learner-remove.sh: remove accounts with optional archive - plugin-skeleton template: starter maubot plugin Testing: - flake.nix: add checks output for pre-deploy validation - smoke-test.sh: post-deploy service verification Documentation: - learner-onboarding.md: VS Code Remote-SSH setup guide - learner-admin.md: account management procedures Skills: - code-review.md: multi-lens code review skill - orch, worklog: symlinks to shared skills
195 lines
4.7 KiB
Bash
Executable file
195 lines
4.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# learner-remove.sh - Remove a learner account
|
|
# Usage: learner-remove.sh <username> [--archive]
|
|
#
|
|
# 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/learners"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
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; }
|
|
|
|
usage() {
|
|
echo "Usage: $0 <username> [--archive]"
|
|
echo ""
|
|
echo "Arguments:"
|
|
echo " username - Learner's username to remove"
|
|
echo " --archive - Archive home directory instead of deleting"
|
|
echo ""
|
|
echo "Example:"
|
|
echo " $0 alice # Delete user and home directory"
|
|
echo " $0 alice --archive # Archive home directory before deleting"
|
|
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"
|
|
|
|
log_info "Removing maubot plugin symlinks for '$username'..."
|
|
|
|
# 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
|
|
rm "$symlink"
|
|
log_info "Removed symlink: $symlink"
|
|
((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
|
|
rm -f "$symlink"
|
|
log_info "Removed: $symlink"
|
|
((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
|
|
|
|
log_info "Archiving home directory..."
|
|
|
|
mkdir -p "$ARCHIVE_DIR"
|
|
local archive_name="${username}_$(date +%Y%m%d_%H%M%S).tar.gz"
|
|
local archive_path="$ARCHIVE_DIR/$archive_name"
|
|
|
|
tar -czf "$archive_path" -C /home "$username"
|
|
chmod 600 "$archive_path"
|
|
|
|
log_info "Archived to: $archive_path"
|
|
}
|
|
|
|
remove_user() {
|
|
local username="$1"
|
|
|
|
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"
|
|
}
|
|
|
|
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
|
|
;;
|
|
*)
|
|
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"
|
|
|
|
# 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 -p "Are you sure? (yes/no): " confirm
|
|
if [[ "$confirm" != "yes" ]]; then
|
|
log_info "Aborted"
|
|
exit 0
|
|
fi
|
|
|
|
remove_maubot_symlinks "$username"
|
|
|
|
if [[ "$archive" == true ]]; then
|
|
archive_home "$username"
|
|
fi
|
|
|
|
remove_user "$username"
|
|
|
|
log_info "Learner '$username' removed successfully"
|
|
}
|
|
|
|
main "$@"
|