Added rsync backup option

This commit is contained in:
Ali
2025-06-28 11:43:10 +01:00
parent f0ea7ae8df
commit 8658dac58c
2 changed files with 466 additions and 66 deletions

119
README.md
View File

@@ -1,15 +1,13 @@
# Debian & Ubuntu Server Setup & Hardening Script # Debian & Ubuntu Server Setup & Hardening Script
**Version:** 3.13 **Version:** 4.1
**Last Updated:** 2025-06-27 **Last Updated:** 2025-06-28
**Compatible With:** **Compatible With:**
- Debian 12 - Debian 12
- Ubuntu 22.04, 24.04, 24.10 - Ubuntu 22.04, 24.04, 24.10 (24.10 experimental)
* * *
## Overview ## Overview
@@ -17,8 +15,6 @@ This script automates the initial setup and security hardening of a fresh Debian
It runs interactively, guiding the user through critical choices while automating the tedious but essential steps of securing a new server. It runs interactively, guiding the user through critical choices while automating the tedious but essential steps of securing a new server.
* * *
## Features ## Features
- **Secure User Management:** Creates a new administrator user with `sudo` privileges and disables the root account's SSH access. - **Secure User Management:** Creates a new administrator user with `sudo` privileges and disables the root account's SSH access.
@@ -27,6 +23,7 @@ It runs interactively, guiding the user through critical choices while automatin
- **Intrusion Prevention:** Installs and configures **Fail2Ban** to automatically block IPs that show malicious signs, such as repeated password failures. - **Intrusion Prevention:** Installs and configures **Fail2Ban** to automatically block IPs that show malicious signs, such as repeated password failures.
- **Automated Security Updates:** Configures `unattended-upgrades` to automatically install new security patches. - **Automated Security Updates:** Configures `unattended-upgrades` to automatically install new security patches.
- **System Stability:** Sets up NTP time synchronization with `chrony` and can configure a swap file for systems with low RAM. - **System Stability:** Sets up NTP time synchronization with `chrony` and can configure a swap file for systems with low RAM.
- **Remote rsync Backups:** Configures a root cron job for `rsync` backups to any SSH-accessible server (e.g., Hetzner Storage Box, NAS, or custom server), with SSH key automation, cron scheduling, ntfy/Discord notifications, and customizable exclude file.
- **Safety First:** Automatically backs up all critical configuration files before modification, with simple restoration instructions. - **Safety First:** Automatically backs up all critical configuration files before modification, with simple restoration instructions.
- **Optional Software:** Provides optional, interactive installation for: - **Optional Software:** Provides optional, interactive installation for:
- Docker & Docker Compose - Docker & Docker Compose
@@ -34,8 +31,6 @@ It runs interactively, guiding the user through critical choices while automatin
- **Comprehensive Logging:** All actions are logged to `/var/log/setup_harden_debian_ubuntu_*.log`. - **Comprehensive Logging:** All actions are logged to `/var/log/setup_harden_debian_ubuntu_*.log`.
- **Automation-Friendly:** Includes a `--quiet` mode to suppress non-essential output for use in automated provisioning workflows. - **Automation-Friendly:** Includes a `--quiet` mode to suppress non-essential output for use in automated provisioning workflows.
* * *
## Installation & Usage ## Installation & Usage
### Prerequisites ### Prerequisites
@@ -43,43 +38,45 @@ It runs interactively, guiding the user through critical choices while automatin
- A fresh installation of a compatible OS. - A fresh installation of a compatible OS.
- Root or `sudo` privileges. - Root or `sudo` privileges.
- Internet access for downloading packages. - Internet access for downloading packages.
- For remote backups: An SSH-accessible server (e.g., Hetzner Storage Box or custom server) with credentials or SSH key access.
### 1\. Download the Script ### 1. Download the Script
``` ```bash
wget https://raw.githubusercontent.com/buildplan/setup_harden_server/refs/heads/main/setup_harden_debian_ubuntu.sh wget https://raw.githubusercontent.com/buildplan/setup_harden_server/refs/heads/main/setup_harden_debian_ubuntu.sh
chmod +x setup_harden_debian_ubuntu.sh chmod +x setup_harden_debian_ubuntu.sh
``` ```
### 2\. Run the Script Interactively ### 2. Run the Script Interactively
It is highly recommended to run the script interactively the first time. It is highly recommended to run the script interactively the first time.
``` ```bash
sudo ./setup_harden_debian_ubuntu.sh sudo ./setup_harden_debian_ubuntu.sh
``` ```
### 3\. Run in Quiet Mode (for automation - not recmmended) ### 3. Run in Quiet Mode (for automation - not recommended)
``` ```bash
sudo ./setup_harden_debian_ubuntu.sh --quiet sudo ./setup_harden_debian_ubuntu.sh --quiet
``` ```
> :warning: **Critical Safety Check:** The script will pause and require you to test your new SSH connection from a separate terminal before it proceeds to disable old access methods. **Do not skip this step!** > **Warning:** The script will pause and require you to test your new SSH connection from a separate terminal before it proceeds to disable old access methods. **Do not skip this step!**
> >
> *Make sure to check VPS providers firewall, you will have to open your selected custom SSH port there.* > *Make sure to check your VPS provider's firewall; you will have to open your selected custom SSH port there.*
>
* * * > *For remote backups, ensure the backup server's SSH port is open and accessible.*
## What It Does in Detail ## What It Does in Detail
| Task | Description | | Task | Description |
| --- | --- | | --- | --- |
| **System Checks** | Verifies OS compatibility, root privileges, and internet connectivity. | | **System Checks** | Verifies OS compatibility, root privileges, and internet connectivity. |
| **Package Management** | Updates all packages and installs essential tools (`ufw`, `fail2ban`, `chrony`, etc.). | | **Package Management** | Updates all packages and installs essential tools (`ufw`, `fail2ban`, `chrony`, `rsync`, etc.). |
| **Admin User Creation** | Creates a new `sudo` user with a password and/or a provided SSH public key. | | **Admin User Creation** | Creates a new `sudo` user with a password and/or a provided SSH public key. |
| **SSH Hardening** | Disables root login, enforces key-based auth, and sets a custom port. | | **SSH Hardening** | Disables root login, enforces key-based auth, and sets a custom port. |
| **Firewall Setup** | Configures UFW to deny incoming traffic by default and allow specific ports. | | **Firewall Setup** | Configures UFW to deny incoming traffic by default and allow specific ports. |
| **Remote Backup Setup** | (Optional) Configures `rsync` backups to a user-specified SSH server (e.g., `user@host:port`), including root SSH key generation, cron job scheduling, ntfy/Discord notifications, and an exclude file with defaults (e.g., `*~`, `*.tmp`). |
| **System Backups** | Creates timestamped backups of configs in `/root/` before modification. | | **System Backups** | Creates timestamped backups of configs in `/root/` before modification. |
| **Swap File Setup** | (Optional) Creates a swap file with a user-selected size. | | **Swap File Setup** | (Optional) Creates a swap file with a user-selected size. |
| **Timezone & Locales** | (Optional) Interactive configuration for timezone and system locales. | | **Timezone & Locales** | (Optional) Interactive configuration for timezone and system locales. |
@@ -87,31 +84,43 @@ sudo ./setup_harden_debian_ubuntu.sh --quiet
| **Tailscale Install** | (Optional) Installs the Tailscale client. | | **Tailscale Install** | (Optional) Installs the Tailscale client. |
| **Final Cleanup** | Removes unused packages and reloads system daemons. | | **Final Cleanup** | Removes unused packages and reloads system daemons. |
* * *
## Logs & Backups ## Logs & Backups
- **Log Files:** `/var/log/setup_harden_debian_ubuntu_*.log` - **Log Files:** `/var/log/setup_harden_debian_ubuntu_*.log`
- **Backup Logs:** `/var/log/backup_*.log` (for remote backup operations)
- **Configuration Backups:** `/root/setup_harden_backup_*` - **Configuration Backups:** `/root/setup_harden_backup_*`
* * * ## Post-Reboot Verification Steps
After rebooting, verify the setup with the following commands:
- **SSH Access**: `ssh -p <custom_port> <username>@<server_ip>`
- **Firewall Rules**: `sudo ufw status verbose`
- **Time Synchronization**: `chronyc tracking`
- **Fail2Ban Status**: `sudo fail2ban-client status sshd`
- **Swap Status**: `sudo swapon --show && free -h`
- **Hostname**: `hostnamectl`
- **Docker Status** (if installed): `docker ps`
- **Tailscale Status** (if installed): `tailscale status`
- **Remote Backup** (if configured):
- Verify SSH key: `cat /root/.ssh/id_ed25519.pub`
- Copy key to backup server (if not done during setup): `ssh-copy-id -p <backup_port> -s <backup_user@backup_host>`
- Test backup: `sudo /root/backup.sh`
- Check backup logs: `sudo less /var/log/backup_*.log`
## Tested On ## Tested On
- Debian 12 - Debian 12
- Ubuntu 24.04 and 24.10 - Ubuntu 22.04, 24.04, 24.10 (experimental)
- Cloud providers (DigitalOcean, Oracle Cloud, Hetzner, Netcup) and local VMs. - Cloud providers (DigitalOcean, Oracle Cloud, Hetzner, Netcup) and local VMs, including Hetzner Storage Box for backups.
* * * ## Important Notes
## :exclamation: Important Notes
- **Run this on a fresh system.** While idempotent, the script is designed for initial provisioning. - **Run this on a fresh system.** While idempotent, the script is designed for initial provisioning.
- **A system reboot is required** after the script completes to ensure all changes, especially to the kernel and services, are applied cleanly. - **A system reboot is required** after the script completes to ensure all changes, especially to the kernel and services, are applied cleanly.
- Always test the script in a non-production environment (like a staging VM) before deploying to a live server. - Always test the script in a non-production environment (like a staging VM) before deploying to a live server.
- Ensure you have out-of-band console access to your server in case you accidentally lock yourself out. - Ensure you have out-of-band console access to your server in case you accidentally lock yourself out.
- For remote backups, ensure the root SSH key is copied to the backup server (`ssh-copy-id -p <backup_port> -s <backup_user@backup_host>`) to enable automated backups.
* * *
## Troubleshooting ## Troubleshooting
@@ -121,14 +130,14 @@ If you are locked out of SSH, use your provider's web console to perform the fol
1. **Remove the hardened configuration:** 1. **Remove the hardened configuration:**
``` ```bash
# This file overrides the main config, so it must be removed. # This file overrides the main config, so it must be removed.
rm /etc/ssh/sshd_config.d/99-hardening.conf rm /etc/ssh/sshd_config.d/99-hardening.conf
``` ```
2. **Restore the original `sshd_config` file:** 2. **Restore the original `sshd_config` file:**
``` ```bash
# Find the latest backup directory # Find the latest backup directory
LATEST_BACKUP=$(ls -td /root/setup_harden_backup_* | head -1) LATEST_BACKUP=$(ls -td /root/setup_harden_backup_* | head -1)
@@ -137,13 +146,53 @@ If you are locked out of SSH, use your provider's web console to perform the fol
``` ```
3. **Restart the SSH service:** 3. **Restart the SSH service:**
```
```bash
systemctl restart ssh systemctl restart ssh
``` ```
You should now be able to log in using the original port (usually 22) and credentials. You should now be able to log in using the original port (usually 22) and credentials.
--- ### Backup Issues
## [MIT](https://github.com/buildplan/setup_harden_server/blob/main/LICENSE "LICENCE") License If backups fail, check the following:
1. **Verify SSH Key Setup**:
- Ensure the root SSH key is copied to the backup server:
```bash
ssh-copy-id -p <backup_port> -s <backup_user@backup_host>
```
- Test SSH connectivity:
```bash
ssh -p <backup_port> <backup_user@backup_host> exit
```
2. **Check Backup Logs**:
- Review logs for errors:
```bash
sudo less /var/log/backup_*.log
```
3. **Test Backup Manually**:
- Run the backup script to identify issues:
```bash
sudo /root/backup.sh
```
4. **Verify Cron Job**:
- Check the cron schedule:
```bash
sudo crontab -l
```
- Ensure the schedule is valid (e.g., `0 3 * * *` for daily at 3 AM).
5. **Network Issues**:
- Verify the backup servers SSH port is open:
```bash
nc -zv <backup_host> <backup_port>
```
- Check your VPS providers firewall for outbound access to the backup servers port.
## [MIT](https://github.com/buildplan/setup_harden_server/blob/main/LICENSE "LICENSE") License
This script is open-source and provided "as is" without warranty. Use at your own risk. This script is open-source and provided "as is" without warranty. Use at your own risk.

View File

@@ -1,9 +1,12 @@
#!/bin/bash #!/bin/bash
# Debian 12 and Ubuntu Server Hardening Interactive Script # Debian 12 and Ubuntu Server Hardening Interactive Script
# Version: 3.13 | 2025-06-27 # Version: 4.1 | 2025-06-28
# Compatible with: Debian 12 (Bookworm), Ubuntu 20.04 LTS, 22.04 LTS, 24.04 LTS. 24.10 (experimental) # Changelog:
# Tested on Debian 12, Ubuntu 24.04 and 24.10 at DigitalOcean, Oracle Cloud, Netcup, Hetzner and local VMs # - v4.1: Generalized backup configuration to support any rsync-compatible SSH destination, renamed setup_hetzner_backup to setup_backup.
# - v4.0: Added Hetzner Storage Box backup configuration with root SSH key automation, cron job scheduling, ntfy/Discord notifications, and exclude file defaults.
# - v4.0: Enhanced generate_summary to include backup details (script path, cron schedule, notifications).
# - v4.0: Tested on Debian 12, Ubuntu 20.04, 22.04, 24.04, and 24.10 (experimental) at DigitalOcean, Oracle Cloud, Netcup, Hetzner, and local VMs.
# #
# Description: # Description:
# This script provisions and hardens a fresh Debian 12 or Ubuntu server with essential security # This script provisions and hardens a fresh Debian 12 or Ubuntu server with essential security
@@ -80,7 +83,7 @@ print_header() {
echo -e "${CYAN}╔═════════════════════════════════════════════════════════════════╗${NC}" echo -e "${CYAN}╔═════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ ║${NC}" echo -e "${CYAN}║ ║${NC}"
echo -e "${CYAN}║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC}" echo -e "${CYAN}║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC}"
echo -e "${CYAN}║ v3.13 | 2025-06-27${NC}" echo -e "${CYAN}║ v4.1 | 2025-06-28 ${NC}"
echo -e "${CYAN}║ ║${NC}" echo -e "${CYAN}║ ║${NC}"
echo -e "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}" echo -e "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}"
echo echo
@@ -335,8 +338,8 @@ install_packages() {
print_info "Installing essential packages..." print_info "Installing essential packages..."
if ! apt-get install -y -qq \ if ! apt-get install -y -qq \
ufw fail2ban unattended-upgrades chrony \ ufw fail2ban unattended-upgrades chrony \
rsync wget vim htop iotop nethogs ncdu tree \ rsync wget vim htop iotop nethogs netcat-traditional ncdu \
rsyslog cron jq gawk coreutils perl skopeo git \ tree rsyslog cron jq gawk coreutils perl skopeo git \
openssh-client openssh-server; then openssh-client openssh-server; then
print_error "Failed to install one or more essential packages." print_error "Failed to install one or more essential packages."
exit 1 exit 1
@@ -770,6 +773,317 @@ configure_firewall() {
log "Firewall configuration completed." log "Firewall configuration completed."
} }
setup_backup() {
print_section "Backup Configuration (rsync over SSH)"
if ! confirm "Configure rsync-based backups to a remote SSH server?"; then
print_info "Skipping backup configuration."
return 0
fi
print_warning "The backup cron job will run as root to ensure access to all files in /home/$USERNAME."
print_info "This requires copying the root SSH key to the remote server after script completion."
# Generate SSH key for root (if not exists)
local ROOT_SSH_DIR="/root/.ssh"
local ROOT_SSH_KEY="$ROOT_SSH_DIR/id_ed25519"
if [[ ! -f "$ROOT_SSH_KEY" ]]; then
print_info "Generating SSH key for root..."
mkdir -p "$ROOT_SSH_DIR"
chmod 700 "$ROOT_SSH_DIR"
ssh-keygen -t ed25519 -f "$ROOT_SSH_KEY" -N "" -q
chown -R root:root "$ROOT_SSH_DIR"
print_success "Root SSH key generated."
else
print_info "Root SSH key already exists at $ROOT_SSH_KEY."
fi
# Ask for backup destination details
read -rp "$(echo -e "${CYAN}Enter backup destination (e.g., user@host or u45555-sub4@u45555.your-storagebox.de): ${NC}")" BACKUP_DEST
read -rp "$(echo -e "${CYAN}Enter SSH port for backup destination [22]: ${NC}")" BACKUP_PORT
read -rp "$(echo -e "${CYAN}Enter remote backup path (e.g., /home/myvps_backup/): ${NC}")" REMOTE_BACKUP_DIR
BACKUP_DEST=${BACKUP_DEST:-user@host}
BACKUP_PORT=${BACKUP_PORT:-22}
REMOTE_BACKUP_DIR=${REMOTE_BACKUP_DIR:-/home/backup/}
if [[ ! "$BACKUP_DEST" =~ ^[a-zA-Z0-9_-]+@[a-zA-Z0-9.-]+$ ]]; then
print_error "Invalid backup destination format. Expected user@host."
exit 1
fi
if ! validate_port "$BACKUP_PORT"; then
print_error "Invalid SSH port. Must be between 1024 and 65535."
exit 1
fi
if [[ ! "$REMOTE_BACKUP_DIR" =~ ^/[^[:space:]]*/$ ]]; then
print_error "Invalid remote backup path. Must start and end with '/' and contain no spaces."
exit 1
fi
# Optional SSH key copy attempt
if confirm "Attempt to copy SSH key to the backup destination now? (Requires password)"; then
if ssh-copy-id -p "$BACKUP_PORT" -s "$BACKUP_DEST" 2>/dev/null; then
print_success "SSH key copied successfully."
else
print_warning "SSH key copy failed. You must manually copy the key later."
fi
fi
# Display SSH key copy instructions
print_warning "ACTION REQUIRED: If not already done, copy the root SSH key to the backup destination to enable backups."
echo -e "${YELLOW}Root public key:${NC}"
cat "$ROOT_SSH_KEY.pub"
echo -e "${CYAN}Run this command on your local machine or another terminal:${NC}"
echo -e " ssh-copy-id -p $BACKUP_PORT -s $BACKUP_DEST"
print_info "You can copy the SSH key later, but the backup cron job will fail until this is done."
# Optional SSH connection test
if confirm "Test SSH connection to the backup destination (optional)?"; then
print_info "Testing connection (timeout: 5 seconds)..."
DEST_HOST=$(echo "$BACKUP_DEST" | cut -d'@' -f2)
if ssh -p "$BACKUP_PORT" -o BatchMode=yes -o ConnectTimeout=5 "$BACKUP_DEST" exit 2>/dev/null; then
print_success "SSH connection successful!"
else
print_error "SSH connection failed."
print_info "Verify the following:"
print_info " 1. The key was copied: ${YELLOW}ssh-copy-id -p $BACKUP_PORT -s $BACKUP_DEST${NC}"
if command -v nc >/dev/null 2>&1; then
print_info " 2. Port $BACKUP_PORT is open: ${YELLOW}nc -zv $DEST_HOST $BACKUP_PORT${NC}"
else
print_info " 2. Port $BACKUP_PORT is open: ${YELLOW}Contact your network admin or try 'telnet $DEST_HOST $BACKUP_PORT'${NC}"
fi
fi
fi
# Create exclude file
local EXCLUDE_FILE="/root/backup_exclude.txt"
print_info "Creating rsync exclude file at $EXCLUDE_FILE..."
cat > "$EXCLUDE_FILE" <<EOF
.ansible/
.bash_history
.bash_logout
.bashrc
.cache/
.cloud-locale-test.skip
.config/
.lesshst
.docker/
.local/
.profile
.selected_editor
.ssh/
.sudo_as_admin_successful
.wget-hsts
.wg-easy/
*~
*.tmp
EOF
if confirm "Add additional files/directories to exclude from backup?"; then
read -rp "$(echo -e "${CYAN}Enter directories/files to exclude (space-separated, e.g., .cache/ .log): ${NC}")" EXCLUDE_ITEMS
for item in $EXCLUDE_ITEMS; do
echo "$item" >> "$EXCLUDE_FILE"
done
fi
chmod 600 "$EXCLUDE_FILE"
print_success "Rsync exclude file created."
# Ask for cron schedule
print_info "Configuring cron schedule for backups..."
read -rp "$(echo -e "${CYAN}Enter cron schedule (e.g., '0 3 * * *' for daily at 3 AM): ${NC}")" CRON_SCHEDULE
CRON_SCHEDULE=${CRON_SCHEDULE:-0 3 * * *}
if ! echo "$CRON_SCHEDULE" | grep -qE '^((\*|[0-9,-]+(/[0-9]+)?)\s*){5}$'; then
print_error "Invalid cron expression. Using default daily at 3 AM."
CRON_SCHEDULE="0 3 * * *"
fi
# Ask for notification preference
local NOTIFICATION_SETUP="none" NTFY_URL NTFY_TOPIC NTFY_TOKEN DISCORD_WEBHOOK
if confirm "Enable backup notifications?"; then
echo -e "${CYAN}Choose notification method:${NC}"
echo -e " 1) ntfy"
echo -e " 2) Discord"
echo -e " 3) None"
read -rp "$(echo -e "${CYAN}Enter choice (1-3): ${NC}")" NOTIFICATION_CHOICE
case "$NOTIFICATION_CHOICE" in
1)
read -rp "$(echo -e "${CYAN}Enter ntfy URL (e.g., https://ntfy.sh): ${NC}")" NTFY_URL
read -rp "$(echo -e "${CYAN}Enter ntfy topic: ${NC}")" NTFY_TOPIC
read -rp "$(echo -e "${CYAN}Enter ntfy token (optional, press Enter to skip): ${NC}")" NTFY_TOKEN
NTFY_URL=${NTFY_URL:-https://ntfy.sh}
NTFY_TOPIC=${NTFY_TOPIC:-vps-backups}
NOTIFICATION_SETUP="ntfy"
;;
2)
read -rp "$(echo -e "${CYAN}Enter Discord webhook URL: ${NC}")" DISCORD_WEBHOOK
if [[ ! "$DISCORD_WEBHOOK" =~ ^https://discord.com/api/webhooks/ ]]; then
print_error "Invalid Discord webhook URL."
exit 1
fi
NOTIFICATION_SETUP="discord"
;;
*) NOTIFICATION_SETUP="none" ;;
esac
fi
# Create backup script
local BACKUP_SCRIPT="/root/backup.sh"
print_info "Creating backup script at $BACKUP_SCRIPT..."
cat > "$BACKUP_SCRIPT" <<EOF
#!/bin/bash
# rsync backup script for remote SSH server
# Generated by setup_harden_debian_ubuntu.sh on \$(date '+%Y-%m-%d %H:%M:%S')
set -Euo pipefail
umask 077
# Configuration
RSYNC_CMD="\$(command -v rsync)"
CURL_CMD="\$(command -v curl)"
HOSTNAME_CMD="\$(command -v hostname)"
DATE_CMD="\$(command -v date)"
STAT_CMD="\$(command -v stat)"
MV_CMD="\$(command -v mv)"
TOUCH_CMD="\$(command -v touch)"
NC_CMD="\$(command -v nc)"
AWK_CMD="\$(command -v awk)"
NUMFMT_CMD="\$(command -v numfmt)"
GREP_CMD="\$(command -v grep)"
LOCAL_DIR="/home/$USERNAME/"
REMOTE_DIR="$REMOTE_BACKUP_DIR"
BACKUP_DEST="$BACKUP_DEST"
EXCLUDE_FILE="$EXCLUDE_FILE"
SSH_PORT="$BACKUP_PORT"
LOG_FILE="/var/log/backup_\$(date +%Y%m%d_%H%M%S).log"
MAX_LOG_SIZE=10485760 # 10 MB
NOTIFICATION_SETUP="$NOTIFICATION_SETUP"
EOF
if [[ "$NOTIFICATION_SETUP" == "ntfy" ]]; then
cat >> "$BACKUP_SCRIPT" <<EOF
NTFY_URL="$NTFY_URL/$NTFY_TOPIC"
NTFY_TOKEN="$NTFY_TOKEN"
EOF
elif [[ "$NOTIFICATION_SETUP" == "discord" ]]; then
cat >> "$BACKUP_SCRIPT" <<EOF
DISCORD_WEBHOOK="$DISCORD_WEBHOOK"
EOF
fi
cat >> "$BACKUP_SCRIPT" <<'EOF'
# Notification function
send_notification() {
local title="$1"
local message="$2"
local priority="${3:-default}"
local color=65280 # Green for success
if [[ "$title" == *"FAILED"* ]]; then
color=16711680 # Red for failure
fi
EOF
if [[ "$NOTIFICATION_SETUP" == "ntfy" ]]; then
cat >> "$BACKUP_SCRIPT" <<EOF
"$CURL_CMD" -s ${NTFY_TOKEN:+-u :"$NTFY_TOKEN"} \
-H "Title: $title" \
-H "Priority: $priority" \
-d "$message" \
"$NTFY_URL" > /dev/null 2>> "$LOG_FILE"
EOF
elif [[ "$NOTIFICATION_SETUP" == "discord" ]]; then
cat >> "$BACKUP_SCRIPT" <<EOF
"$CURL_CMD" -s -H "Content-Type: application/json" \
-d "{\"embeds\": [{\"title\": \"$title\", \"description\": \"$message\", \"color\": $color}]}" \
"$DISCORD_WEBHOOK" > /dev/null 2>> "$LOG_FILE"
EOF
else
cat >> "$BACKUP_SCRIPT" <<'EOF'
: # No notifications configured
EOF
fi
cat >> "$BACKUP_SCRIPT" <<'EOF'
}
# Format backup stats
format_backup_stats() {
local stats_line
stats_line=$("$GREP_CMD" 'Total transferred file size' "$LOG_FILE" | tail -n 1)
if [ -n "$stats_line" ]; then
local bytes
bytes=$(echo "$stats_line" | "$AWK_CMD" '{gsub(/,/, ""); print $5}')
if [[ "$bytes" =~ ^[0-9]+$ && "$bytes" -gt 0 ]]; then
local human_readable
human_readable=$("$NUMFMT_CMD" --to=iec-i --suffix=B --format="%.2f" "$bytes")
printf "Data Transferred: $human_readable"
else
printf "Data Transferred: 0 B (No changes)"
fi
else
printf "See log for statistics."
fi
}
# Dependency check
for cmd in "$RSYNC_CMD" "$CURL_CMD" "$NC_CMD" "$AWK_CMD" "$NUMFMT_CMD" "$GREP_CMD" "$HOSTNAME_CMD" "$DATE_CMD" "$STAT_CMD" "$MV_CMD" "$TOUCH_CMD"; do
if ! command -v "$cmd" &>/dev/null; then
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] FATAL: Required command not found at '$cmd'" >> "$LOG_FILE"
send_notification "❌ Backup FAILED: $("$HOSTNAME_CMD")" "Required command not found at '$cmd'" "high"
exit 10
fi
done
# Pre-flight checks
if [[ ! -f "$EXCLUDE_FILE" ]]; then
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] FATAL: Exclude file not found at $EXCLUDE_FILE" >> "$LOG_FILE"
send_notification "❌ Backup FAILED: $("$HOSTNAME_CMD")" "Exclude file not found at $EXCLUDE_FILE" "high"
exit 3
fi
if [[ "$LOCAL_DIR" != */ ]]; then
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] FATAL: LOCAL_DIR must end with a trailing slash" >> "$LOG_FILE"
send_notification "❌ Backup FAILED: $("$HOSTNAME_CMD")" "LOCAL_DIR must end with a trailing slash" "high"
exit 2
fi
# Log rotation
if [ -f "$LOG_FILE" ] && [ "$("$STAT_CMD" -c%s "$LOG_FILE")" -gt "$MAX_LOG_SIZE" ]; then
"$MV_CMD" "$LOG_FILE" "${LOG_FILE}.$("$DATE_CMD" +%Y%m%d_%H%M%S)"
"$TOUCH_CMD" "$LOG_FILE"
fi
# Network connectivity check
DEST_HOST=$(echo "$BACKUP_DEST" | cut -d'@' -f2)
if ! "$NC_CMD" -z -w 5 "$DEST_HOST" "$SSH_PORT"; then
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] FATAL: Cannot reach $DEST_HOST on port $SSH_PORT" >> "$LOG_FILE"
send_notification "❌ Backup FAILED: $("$HOSTNAME_CMD")" "Cannot reach $DEST_HOST on port $SSH_PORT" "high"
exit 4
fi
# Rsync backup
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] Starting rsync backup for $("$HOSTNAME_CMD")" >> "$LOG_FILE"
if LC_ALL=C "$RSYNC_CMD" -avz --stats --delete --partial --timeout=60 --exclude-from="$EXCLUDE_FILE" -e "ssh -p $SSH_PORT" "$LOCAL_DIR" "$BACKUP_DEST:$REMOTE_DIR" >> "$LOG_FILE" 2>&1; then
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] SUCCESS: rsync completed successfully." >> "$LOG_FILE"
BACKUP_STATS=$(format_backup_stats)
send_notification "✅ Backup SUCCESS: $("$HOSTNAME_CMD")" "rsync backup completed successfully.\n\n$BACKUP_STATS"
else
EXIT_CODE=$?
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] FAILED: rsync exited with status code: $EXIT_CODE" >> "$LOG_FILE"
send_notification "❌ Backup FAILED: $("$HOSTNAME_CMD")" "rsync failed with exit code $EXIT_CODE. Check log: $LOG_FILE" "high"
exit $EXIT_CODE
fi
echo "[$("$DATE_CMD" '+%Y-%m-%d %H:%M:%S')] Run Finished" >> "$LOG_FILE"
EOF
chmod 700 "$BACKUP_SCRIPT"
print_success "Backup script created at $BACKUP_SCRIPT."
# Add to crontab
print_info "Adding cron job for root..."
local CRON_FILE=$(mktemp)
crontab -u root -l > "$CRON_FILE" 2>/dev/null || true
if grep -q "$BACKUP_SCRIPT" "$CRON_FILE"; then
print_info "Cron job for $BACKUP_SCRIPT already exists. Updating schedule..."
sed -i "\|$BACKUP_SCRIPT|d" "$CRON_FILE"
fi
echo "$CRON_SCHEDULE $BACKUP_SCRIPT" >> "$CRON_FILE"
crontab -u root "$CRON_FILE"
rm -f "$CRON_FILE"
print_success "Cron job added for root: $CRON_SCHEDULE $BACKUP_SCRIPT"
log "Backup configuration completed."
}
configure_fail2ban() { configure_fail2ban() {
print_section "Fail2Ban Configuration" print_section "Fail2Ban Configuration"
@@ -1119,6 +1433,13 @@ generate_summary() {
print_error "Service docker is NOT active." print_error "Service docker is NOT active."
fi fi
fi fi
if command -v tailscale >/dev/null 2>&1; then
if systemctl is-active --quiet tailscaled; then
print_success "Service tailscaled is active."
else
print_error "Service tailscaled is NOT active."
fi
fi
echo -e "\n${GREEN}Server setup and hardening script has finished successfully.${NC}" echo -e "\n${GREEN}Server setup and hardening script has finished successfully.${NC}"
echo echo
echo -e "${YELLOW}Configuration Summary:${NC}" echo -e "${YELLOW}Configuration Summary:${NC}"
@@ -1126,6 +1447,27 @@ generate_summary() {
echo -e " Hostname: $SERVER_NAME" echo -e " Hostname: $SERVER_NAME"
echo -e " SSH Port: $SSH_PORT" echo -e " SSH Port: $SSH_PORT"
echo -e " Server IP: $SERVER_IP" echo -e " Server IP: $SERVER_IP"
if [[ -f /root/backup.sh ]]; then
local CRON_SCHEDULE=$(crontab -u root -l 2>/dev/null | grep -F "/root/backup.sh" | awk '{print $1, $2, $3, $4, $5}' || echo "Not configured")
local NOTIFICATION_STATUS="None"
local BACKUP_DEST=$(grep "^BACKUP_DEST=" /root/backup.sh | cut -d'"' -f2 || echo "Unknown")
local BACKUP_PORT=$(grep "^SSH_PORT=" /root/backup.sh | cut -d'"' -f2 || echo "Unknown")
local REMOTE_BACKUP_DIR=$(grep "^REMOTE_DIR=" /root/backup.sh | cut -d'"' -f2 || echo "Unknown")
if grep -q "NTFY_URL" /root/backup.sh; then
NOTIFICATION_STATUS="ntfy"
elif grep -q "DISCORD_WEBHOOK" /root/backup.sh; then
NOTIFICATION_STATUS="Discord"
fi
echo -e " Remote Backup: Enabled"
echo -e " - Backup Script: /root/backup.sh"
echo -e " - Destination: $BACKUP_DEST"
echo -e " - SSH Port: $BACKUP_PORT"
echo -e " - Remote Path: $REMOTE_BACKUP_DIR"
echo -e " - Cron Schedule: $CRON_SCHEDULE"
echo -e " - Notifications: $NOTIFICATION_STATUS"
else
echo -e " Remote Backup: Not configured"
fi
echo echo
echo -e "${PURPLE}Log File: ${LOG_FILE}${NC}" echo -e "${PURPLE}Log File: ${LOG_FILE}${NC}"
echo -e "${PURPLE}Backups: ${BACKUP_DIR}${NC}" echo -e "${PURPLE}Backups: ${BACKUP_DIR}${NC}"
@@ -1143,7 +1485,15 @@ generate_summary() {
if command -v tailscale >/dev/null 2>&1; then if command -v tailscale >/dev/null 2>&1; then
echo -e " - Tailscale status: tailscale status" echo -e " - Tailscale status: tailscale status"
fi fi
print_warning "\nA reboot is required to apply all changes cleanly." if [[ -f /root/backup.sh ]]; then
echo -e " - Remote Backup:"
echo -e " - Verify SSH key: cat /root/.ssh/id_ed25519.pub"
echo -e " - Copy key if needed: ssh-copy-id -p $BACKUP_PORT -s $BACKUP_DEST"
echo -e " - Test backup: sudo /root/backup.sh"
echo -e " - Check logs: sudo less /var/log/backup_*.log"
fi
print_warning "\nACTION REQUIRED: If remote backup is enabled, ensure the root SSH key is copied to the destination server."
print_warning "A reboot is required to apply all changes cleanly."
if [[ $VERBOSE == true ]]; then if [[ $VERBOSE == true ]]; then
if confirm "Reboot now?" "y"; then if confirm "Reboot now?" "y"; then
print_info "Rebooting now..." print_info "Rebooting now..."
@@ -1186,6 +1536,7 @@ main() {
configure_time_sync configure_time_sync
install_docker install_docker
install_tailscale install_tailscale
setup_backup
configure_swap configure_swap
final_cleanup final_cleanup
generate_summary generate_summary