Mikrotik - Backup Script (To Git)¶
Problem: I have a handful of Mikrotik RouterOS based devices, and I would like a source-controlled versioning solution.
Solution: A simple bash script, with a cron job.
This post, provides a script, which allows automated backups of mikrotik configuration over ssh, from a linux host.
The Script¶
#!/bin/bash
cd "$(dirname "$0")" || exit 1
# MikroTik Configuration Backup Script
# This script connects to MikroTik routers/switches, exports configurations,
# and pushes them to a git repository
# Configuration variables
USERNAME="mikrotik-backup-user" # Default MikroTik username
SSH_PORT="22" # Default SSH port
SSH_KEY_PATH="" # Path to SSH private key (optional, uses default ~/.ssh/.. if empty)
# Logging
LOG_FILE="/var/log/mikrotik/backup.log"
LOG_RETENTION_DAYS=7 # Keep N days worth of logs
# SMTP Configuration for notifications
## Note- I never finished implementing AND testing SMTP.
SMTP_ENABLED=false # Enable/disable email notifications
SMTP_SERVER="relay.local.xtremeownage.com" # SMTP server address
SMTP_PORT="25" # SMTP server port (587 for TLS, 25 for plain)
SMTP_USERNAME="" # SMTP username (leave empty for no auth)
SMTP_PASSWORD="" # SMTP password (leave empty for no auth)
SMTP_FROM="mikrotik-backup@xtremeownage.com" # From email address
SMTP_TO="admin@yourdomain.com" # To email address
SMTP_USE_TLS=false # Use TLS/STARTTLS
SMTP_NOTIFY="always" # When to send emails: "always", "error", "success", "never"
# Export commands to run - first command keeps headers, subsequent commands have headers stripped
EXPORT_COMMANDS=(
"/export show-sensitive terse verbose"
"/user/export show-sensitive terse verbose"
"/system/scheduler/export show-sensitive terse verbose"
"/queue/export show-sensitive terse verbose"
)
# Array of MikroTik device IPs - Add your device IPs here
MIKROTIK_IPS=(
"10.100.0.100"
"10.100.0.200"
"10.100.0.15"
#"10.100.0.20"
"10.102.14.2"
# Add more IPs as needed
)
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging helper
log() {
local level=$1
local color=$2
local msg=$3
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${color}[${level}]${NC} $msg"
echo "[$timestamp] [$level] $msg" >> "$LOG_FILE"
}
print_status() { log "INFO" "$BLUE" "$1"; }
print_success() { log "SUCCESS" "$GREEN" "$1"; }
print_warning() { log "WARNING" "$YELLOW" "$1"; }
print_error() { log "ERROR" "$RED" "$1"; }
# Trap unexpected failures and log them directly to file
trap 'echo "[$(date "+%Y-%m-%d %H:%M:%S")] [ERROR] Unexpected failure on line $LINENO (exit code $?)" >> "$LOG_FILE"' ERR
# Function to check if required tools are installed
check_dependencies() {
if ! command -v git &> /dev/null; then
print_error "Git is not installed. Please install git and run the script again."
exit 1
fi
# Check for mail command if SMTP is enabled
if [ "$SMTP_ENABLED" = true ] && ! command -v mail &> /dev/null && ! command -v sendmail &> /dev/null; then
print_warning "No mail command found. Email notifications will be disabled."
print_warning "Install mailutils or postfix to enable email notifications."
SMTP_ENABLED=false
fi
}
# Function to setup git repository
setup_git_repo() {
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_error "This script must be run from within a git repository"
print_error "Initialize git repo first: git init && git remote add origin <your-repo-url>"
exit 1
fi
print_status "Pulling latest changes from remote..."
git pull 2>/dev/null || print_warning "Could not pull from remote (this is normal for first run)"
}
# Function to build SSH command with proper options
build_ssh_cmd() {
local ip=$1
local ssh_cmd="ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10"
# Add SSH key if specified
if [ -n "$SSH_KEY_PATH" ]; then
ssh_cmd="$ssh_cmd -i $SSH_KEY_PATH"
fi
# Add port and user@host
ssh_cmd="$ssh_cmd -p $SSH_PORT $USERNAME@$ip"
echo "$ssh_cmd"
}
# Function to get device identity (just use IP address)
get_device_identity() {
local ip=$1
echo $ip
}
# Function to export configuration from MikroTik device
export_config() {
local ip=$1
local device_name=$2
local device_dir="./$device_name"
local config_file="$device_dir/config.rsc"
local ssh_cmd
local temp_file
local first_command=true
print_status "Exporting configuration from $ip ($device_name)..."
# Create device directory if it doesn't exist
mkdir -p "$device_dir"
# Build SSH command with proper timeout for config export
ssh_cmd=$(build_ssh_cmd "$ip")
ssh_cmd=${ssh_cmd/ConnectTimeout=10/ConnectTimeout=30}
# Initialize the config file
> "$config_file"
# Run each export command
for cmd in "${EXPORT_COMMANDS[@]}"; do
temp_file=$(mktemp)
print_status "Running: $cmd"
$ssh_cmd "$cmd" > "$temp_file" 2>/dev/null
# Strip RouterOS auto-generated date/comment header
sed -i '/^# [0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\} .* by RouterOS /d' "$temp_file"
if [ $? -eq 0 ] && [ -s "$temp_file" ]; then
if [ "$first_command" = true ]; then
# First command: include everything (headers and content)
cat "$temp_file" >> "$config_file"
first_command=false
else
# Subsequent commands: skip comment lines at the beginning
awk '
BEGIN { in_header = 1 }
/^#/ {
if (in_header) next
else print
}
!/^#/ {
in_header = 0
print
}' "$temp_file" >> "$config_file"
fi
print_success "✓ $cmd completed"
else
print_warning "✗ $cmd failed or returned no data"
fi
rm -f "$temp_file"
done
# Check if we got any content
if [ -s "$config_file" ]; then
# Create device info file
cat > "$device_dir/device_info.txt" << EOF
Device IP: $ip
Device Name: $device_name
Last Backup: $(date)
SSH Port: $SSH_PORT
SSH Key: ${SSH_KEY_PATH:-"Default SSH key"}
Export Commands: ${#EXPORT_COMMANDS[@]} commands executed
EOF
print_success "All configurations exported successfully for $device_name"
return 0
else
print_error "Failed to export any configuration from $ip"
rm -f "$config_file" # Remove empty file
return 1
fi
}
# Function to commit and push to git
git_commit_and_push() {
print_status "Committing and pushing changes to git repository..."
# Add all changes
git add .
# Check if there are changes to commit
if git diff --staged --quiet; then
print_warning "No changes to commit"
return 0
fi
# Commit changes
local commit_message="Automated backup - $(date '+%Y-%m-%d %H:%M:%S')"
git commit -m "$commit_message"
if [ $? -eq 0 ]; then
# Push to remote repository - handle upstream branch setup
git push 2>/dev/null || git push --set-upstream origin $(git branch --show-current) || {
print_error "Failed to push to remote repository"
return 1
}
print_success "Changes pushed to remote repository successfully"
else
print_error "Failed to commit changes"
return 1
fi
}
# Function to cleanup old backups
cleanup_old_backups() {
local device_dir=$1
print_status "Cleaning up old backups in $device_dir..."
# Keep only the most recent backup files (excluding latest_config.rsc)
find "$device_dir" -name "config_*.rsc" -type f | sort | head -n -${BACKUP_RETENTION} | xargs -r rm
}
# Main function
main() {
# Rotate logs
if [ -f "$LOG_FILE" ]; then
ts=$(date '+%Y-%m-%d_%H-%M-%S')
mv "$LOG_FILE" "${LOG_FILE%.log}_$ts.log"
fi
touch "$LOG_FILE"
# Cleanup old logs
find "$(dirname "$LOG_FILE")" -maxdepth 1 -name "$(basename "${LOG_FILE%.log}")_*.log" -type f -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \;
print_status "Starting MikroTik configuration backup script..."
local start_time=$(date)
local failed_devices=()
local success_devices=()
# Check dependencies
check_dependencies
# Validate that we have devices to backup
if [ ${#MIKROTIK_IPS[@]} -eq 0 ]; then
print_error "No MikroTik IPs defined in the MIKROTIK_IPS array"
exit 1
fi
# Check SSH key if specified
if [ -n "$SSH_KEY_PATH" ] && [ ! -f "$SSH_KEY_PATH" ]; then
print_error "SSH key file not found: $SSH_KEY_PATH"
exit 1
fi
# Setup git repository
setup_git_repo
local success_count=0
local total_count=${#MIKROTIK_IPS[@]}
# Process each MikroTik device
for ip in "${MIKROTIK_IPS[@]}"; do
print_status "Processing device: $ip"
# Get device identity (just use IP)
device_name=$(get_device_identity "$ip")
print_status "Using device folder: $device_name"
# Export configuration
if export_config "$ip" "$device_name"; then
success_devices+=("$ip")
((success_count++))
else
failed_devices+=("$ip")
fi
echo "----------------------------------------"
done
# Commit and push if we had any successful exports
local git_success=false
if [ $success_count -gt 0 ]; then
if git_commit_and_push; then
git_success=true
fi
fi
local end_time=$(date)
print_status "Backup completed!"
print_status "Successfully backed up $success_count out of $total_count devices"
# Prepare email notification
local email_subject
local email_body
local email_status
if [ $success_count -eq $total_count ] && [ "$git_success" = true ]; then
# Complete success
email_status="success"
email_subject="✅ MikroTik Backup - All devices successful ($success_count/$total_count)"
email_body="MikroTik configuration backup completed successfully.
📊 BACKUP SUMMARY:
- Total devices: $total_count
- Successful: $success_count
- Failed: $((total_count - success_count))
- Git push: ✅ Success
🔧 SUCCESSFUL DEVICES:
$(printf '%s\n' "${success_devices[@]}")
⏰ TIMING:
- Started: $start_time
- Completed: $end_time"
else
# Partial or complete failure
email_status="error"
email_subject="❌ MikroTik Backup - Issues detected ($success_count/$total_count successful)"
email_body="MikroTik configuration backup completed with issues.
📊 BACKUP SUMMARY:
- Total devices: $total_count
- Successful: $success_count
- Failed: $((total_count - success_count))
- Git push: $([ "$git_success" = true ] && echo "✅ Success" || echo "❌ Failed")
✅ SUCCESSFUL DEVICES:
$(printf '%s\n' "${success_devices[@]}")
❌ FAILED DEVICES:
$(printf '%s\n' "${failed_devices[@]}")
⏰ TIMING:
- Started: $start_time
- Completed: $end_time
Please check the logs and device connectivity for failed devices."
fi
# Send email notification
send_email "$email_subject" "$email_body" "$email_status"
if [ $success_count -eq $total_count ] && [ "$git_success" = true ]; then
exit 0
else
exit 1
fi
}
# Script usage information
show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -u, --username USERNAME Set SSH username (default: admin)"
echo " -p, --port PORT Set SSH port (default: 22)"
echo " -k, --key PATH Path to SSH private key file"
echo ""
echo "Before running the script:"
echo "1. Place this script in the root of your git repository"
echo "2. Add your MikroTik device IPs to the MIKROTIK_IPS array in the script"
echo "3. Ensure SSH key authentication is set up on all MikroTik devices"
echo ""
echo "SSH Key Setup:"
echo "1. Generate SSH key: ssh-keygen -t rsa -b 2048 -f ~/.ssh/mikrotik_key"
echo "2. Copy public key to MikroTik: /user ssh-keys import public-key-file=mikrotik_key.pub user=admin"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-u|--username)
USERNAME="$2"
shift 2
;;
-p|--port)
SSH_PORT="$2"
shift 2
;;
-k|--key)
SSH_KEY_PATH="$2"
shift 2
;;
*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Run the main function
main
Schedule in Cron¶
To run the script automatically, I am using cron. The script below will schedule it to run at 2am everyday.
# Daily at 2:00 AM
(crontab -l 2>/dev/null; echo "0 2 * * * /path/to/backup.sh >> /var/log/mikrotik-backup.log 2>&1") | crontab -
Mikrotik Script - Create Backup User¶
Here is a simple mikrotik script to create the backup user.
# Create backup group with minimal read permissions
/user group add name=backup-group policy=read,ssh,api,test
# Create backup user
/user add name=mikrotik-backup-user group=backup-group disabled=no
# Add SSH public key directly (replace the key below with yours)
/user ssh-keys add key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDASM22nSot5CdMtn0Nmcu2ZhmC44U8hPUlW7j8NfFGSV6bkmHMC/emXDGFSWESlhjYmS054J75rheiFMLgp5MSbPC8Kbh9+pRzNae7/+HtMcwDds4x0iYD0uiyJSPWeVnHIxPZ8P8n6vHMxbIXmKcr5wd372oLLXWx31ErqqOlXr6aIRNfXM6ghz3fLffVhOHOIMYm6R24CSIhgdr1jTP0P7jjWXaFAp4aYHqBopqEnZ2MGU7eVo0yebACN63mZzYoc77kVW/bXZz9wXOid853OxuKOtGjKVmmhHUc/uvKz8rQygzM1Te2H1Un3LL89UpgrWgepdS6r8n7X0raWB8R5UZBHI7b0KIYb9234ioclQx3MEH8muh2pabU7YzwmM1vaK+uq8aYAO/RwriqAE89vnRZEvjlO3AKxJLyX6q6wYeBXBuN+s04+Xtck5MZKt/R5bliQbtO1VQlepYCGO5hfoMYPcXl+62+PRiH2XTQ/3lxEJCfZT4cf3Qo4IKgjw0= your-key-name" user=backup
# Verify the setup
/user print where name=backup
/user ssh-keys print where user=backup
Info
For those concerned about the above key being sensitive information, Its a randomly generated key. As well, its the public key, which is not sensitive.
The End Result? Backups.¶
This script has went through a few versions before I finally published this post.
I originally wrote this post in April, but, just now pushed it to here mid-way through October.
I only wanted a commit performed, when there were changes. I did not want my git repo spammed with empty commits. And- the script is able to deliver.
As, an example, a few days ago my ISP called, and let me know I needed to update my PPPoe credentials. The script was able to automatically capture the commit.
Overall, its a simple, stupid solution, which is fit for use, and fit for purpose. And... its been working well for half of the year so far.