diff --git a/AGENTS.md b/AGENTS.md index 3f94bec..cae1fbf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,6 +27,7 @@ Read these first: - `remove_user_interactive.sh` - `remove_user_guided.sh` - `remove_user_from_repos.sh` +- `scripts/` - `git-work-safety-tools/` - `github_access_scripts/` diff --git a/README.md b/README.md index 8df7a5f..99e473d 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ These are for scanning many repositories, checking dirty state, and performing s - Root `*.sh` files - Main Bash-based GitHub and maintenance utilities. +- `scripts/` + - Named operational scripts that are more self-contained than the older root-level helpers. - `git-work-safety-tools/` - Safer multi-repo git helpers. - `github_access_scripts/` diff --git a/docs/getting-started.md b/docs/getting-started.md index 8c2a460..5a6dcf0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -16,6 +16,7 @@ Start with: - `remove_user_interactive.sh` - `remove_user_guided.sh` - `remove_user_from_repos.sh` +- `scripts/ubuntu-vm-security-update.sh` for Ubuntu VM hardening and update automation ### If You Need Multi-Repo Git Helpers diff --git a/docs/repo-map.md b/docs/repo-map.md index 8e30af2..f255cbd 100644 --- a/docs/repo-map.md +++ b/docs/repo-map.md @@ -57,6 +57,14 @@ Key files: - `multi_repo_status.sh` - `multi_repo_interactive_fix.sh` +### `scripts/` + +Self-contained operational scripts that do not fit the older root-level naming pattern. + +Key files: + +- `ubuntu-vm-security-update.sh` + ### `github_access_scripts/` Focused GitHub access checks. diff --git a/scripts/ubuntu-vm-security-update.sh b/scripts/ubuntu-vm-security-update.sh index ebb9ed6..42eaca1 100644 --- a/scripts/ubuntu-vm-security-update.sh +++ b/scripts/ubuntu-vm-security-update.sh @@ -1,21 +1,23 @@ #!/usr/bin/env bash set -Eeuo pipefail -# vm-security-update.sh +# ubuntu-vm-security-update.sh # Robust Ubuntu VM security update + unattended-upgrades setup. # No Ubuntu Pro / paid features. # # Usage: -# sudo bash vm-security-update.sh -# sudo bash vm-security-update.sh --no-reboot -# sudo bash vm-security-update.sh --no-auto-reboot -# sudo bash vm-security-update.sh --dry-run +# sudo bash ubuntu-vm-security-update.sh +# sudo bash ubuntu-vm-security-update.sh --no-reboot +# sudo bash ubuntu-vm-security-update.sh --no-auto-reboot +# sudo bash ubuntu-vm-security-update.sh --dry-run LOG_FILE="/var/log/vm-security-update.log" AUTO_REBOOT="true" REBOOT_NOW="true" DRY_RUN="false" AUTO_REBOOT_TIME="04:00" +SSH_PORT_OVERRIDE="" +SCRIPT_NAME="$(basename "$0")" export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a @@ -31,18 +33,22 @@ die() { usage() { cat < "$tmp" + + if [[ -f "$target" ]] && cmp -s "$tmp" "$target"; then + log "No change needed for $target" + rm -f "$tmp" + return 0 + fi + + log "Writing $target" + + if [[ "$DRY_RUN" == "true" ]]; then + rm -f "$tmp" + return 0 + fi + + install -m "$mode" "$tmp" "$target" + rm -f "$tmp" +} + +have_systemd() { + command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]] +} + +ensure_service_enabled_and_restarted() { + local service="$1" + + if ! have_systemd; then + log "systemd not detected. Skipping enable/restart for $service" + return 0 + fi + + run systemctl enable "$service" + run systemctl restart "$service" +} + +detect_ssh_port() { + local detected_port="" + + if [[ -n "$SSH_PORT_OVERRIDE" ]]; then + echo "$SSH_PORT_OVERRIDE" + return 0 + fi + + if command -v sshd >/dev/null 2>&1; then + detected_port="$(sshd -T 2>/dev/null | awk '$1 == "port" {print $2; exit}')" + fi + + if [[ -z "$detected_port" ]] && [[ -f /etc/ssh/sshd_config ]]; then + detected_port="$(awk ' + $1 ~ /^[Pp]ort$/ && $2 ~ /^[0-9]+$/ {print $2} + ' /etc/ssh/sshd_config | tail -n 1)" + fi + + if [[ -z "$detected_port" ]]; then + detected_port="22" + fi + + echo "$detected_port" +} + +validate_time_hhmm() { + [[ "$1" =~ ^([01][0-9]|2[0-3]):[0-5][0-9]$ ]] +} + +while [[ $# -gt 0 ]]; do + case "$1" in --no-reboot) REBOOT_NOW="false" + shift ;; --no-auto-reboot) AUTO_REBOOT="false" + shift + ;; + --auto-reboot-time) + [[ $# -ge 2 ]] || die "--auto-reboot-time requires a value like 04:00" + validate_time_hhmm "$2" || die "Invalid --auto-reboot-time value: $2" + AUTO_REBOOT_TIME="$2" + shift 2 + ;; + --ssh-port) + [[ $# -ge 2 ]] || die "--ssh-port requires a numeric port" + [[ "$2" =~ ^[0-9]+$ ]] || die "Invalid --ssh-port value: $2" + (( "$2" >= 1 && "$2" <= 65535 )) || die "SSH port must be between 1 and 65535" + SSH_PORT_OVERRIDE="$2" + shift 2 ;; --dry-run) DRY_RUN="true" + shift ;; --help|-h) usage exit 0 ;; *) - die "Unknown argument: $arg" + die "Unknown argument: $1" ;; esac done @@ -86,7 +178,7 @@ done trap 'die "Script failed near line $LINENO. Check $LOG_FILE for details."' ERR if [[ "${EUID}" -ne 0 ]]; then - die "Please run as root: sudo bash $0" + die "Please run as root: sudo bash $SCRIPT_NAME" fi touch "$LOG_FILE" @@ -97,6 +189,7 @@ log "Starting Ubuntu VM security update setup" log "Dry run: $DRY_RUN" log "Auto reboot for future unattended updates: $AUTO_REBOOT" log "Reboot now if required: $REBOOT_NOW" +log "Automatic reboot time: $AUTO_REBOOT_TIME" log "============================================================" if [[ ! -f /etc/os-release ]]; then @@ -153,48 +246,48 @@ run apt-get full-upgrade -y log "Removing unused packages and cleaning apt cache" run apt-get autoremove --purge -y run apt-get autoclean +run apt-get clean log "Configuring unattended-upgrades daily security updates" -run_bash "cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF' -APT::Periodic::Update-Package-Lists \"1\"; -APT::Periodic::Download-Upgradeable-Packages \"1\"; -APT::Periodic::AutocleanInterval \"7\"; -APT::Periodic::Unattended-Upgrade \"1\"; -EOF" +safe_write_file /etc/apt/apt.conf.d/20auto-upgrades 0644 <<'EOF' +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "7"; +APT::Periodic::Unattended-Upgrade "1"; +EOF # Keep origins simple and Ubuntu-safe. This uses distro variables so it works across Ubuntu versions. -run_bash "cat > /etc/apt/apt.conf.d/51unattended-upgrades-custom <<'EOF' +safe_write_file /etc/apt/apt.conf.d/52bytelyst-unattended-upgrades 0644 < /etc/fail2ban/jail.d/sshd.local <<'EOF' +safe_write_file /etc/fail2ban/jail.d/sshd.local 0644 <<'EOF' [sshd] enabled = true port = ssh filter = sshd -backend = systemd +backend = auto maxretry = 5 findtime = 10m bantime = 1h -EOF" +EOF -run systemctl enable fail2ban -run systemctl restart fail2ban +ensure_service_enabled_and_restarted fail2ban + +if [[ "$DRY_RUN" == "false" ]] && have_systemd && command -v fail2ban-client >/dev/null 2>&1; then + fail2ban-client ping | tee -a "$LOG_FILE" || die "fail2ban-client ping failed" +fi log "Checking package integrity database availability" if [[ "$DRY_RUN" == "false" ]]; then - debsums_init_log="/tmp/debsums-init.log" if command -v debsums >/dev/null 2>&1; then log "debsums installed. You can later run: sudo debsums -s" fi