mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 13:59:40 +00:00
Migration Data Saver v1.0.0: - migration-export.sh: SSH export from SecuBox-OpenWrt - migration-import.sh: Import with transformations + rollback - migration-transform.py: UCI → TOML/netplan/nftables converter Supported modules: network, firewall, wireguard, crowdsec, dhcp, haproxy, nginx, certs, content, vhosts, users, state Features: - UCIParser for OpenWrt config format - NetworkTransformer: UCI → netplan YAML - FirewallTransformer: UCI → nftables rules - DHCPTransformer: UCI → dnsmasq.conf - AES-256 archive encryption - SHA256 checksums - Pre-import rollback snapshots (4R pattern) Also adds plan for Eye Remote Recovery Boot System (kwboot, mvebu64boot, Tow-Boot UEFI integration for MOCHAbin/ESPRESSObin recovery). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
449 lines
14 KiB
Bash
Executable File
449 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ══════════════════════════════════════════════════════════════════
|
|
# scripts/migration-export.sh — Export SecuBox-OpenWrt → Migration Archive
|
|
#
|
|
# SecuBox-DEB :: Migration Data Saver
|
|
# CyberMind — https://cybermind.fr
|
|
# Author: Gérald Kerma <gandalf@gk2.net>
|
|
#
|
|
# Usage:
|
|
# bash scripts/migration-export.sh -h 192.168.255.1 -o /tmp/migration.tar.gz
|
|
# bash scripts/migration-export.sh -h 192.168.255.1 -i ~/.ssh/secubox-openwrt -o /tmp/migration.tar.gz
|
|
# bash scripts/migration-export.sh -h 192.168.255.1 --encrypt -o /tmp/migration.tar.gz.enc
|
|
# ══════════════════════════════════════════════════════════════════
|
|
set -euo pipefail
|
|
|
|
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
readonly REPO="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
readonly VERSION="1.0.0"
|
|
readonly TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
|
|
|
# ── Colors ──
|
|
CYAN='\033[0;36m'; GREEN='\033[0;32m'; GOLD='\033[0;33m'
|
|
RED='\033[0;31m'; PURPLE='\033[0;35m'; NC='\033[0m'
|
|
|
|
log() { echo -e "${CYAN}[export]${NC} $*"; }
|
|
ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
|
|
warn() { echo -e "${GOLD}[ WARN ]${NC} $*"; }
|
|
err() { echo -e "${RED}[ FAIL ]${NC} $*" >&2; exit 1; }
|
|
section() { echo -e "\n${PURPLE}══════════════════════════════════════════════════════════════════${NC}"; echo -e "${PURPLE} $*${NC}"; echo -e "${PURPLE}══════════════════════════════════════════════════════════════════${NC}"; }
|
|
|
|
# ── Default values ──
|
|
HOST=""
|
|
SSH_KEY=""
|
|
OUTPUT=""
|
|
ENCRYPT=0
|
|
PASSPHRASE=""
|
|
SSH_PORT=22
|
|
WORKDIR=""
|
|
MODULES="all"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
SecuBox Migration Export — Version $VERSION
|
|
|
|
Export configurations and data from SecuBox-OpenWrt to migration archive.
|
|
|
|
Usage:
|
|
$(basename "$0") -h HOST [-i SSH_KEY] [-o OUTPUT] [OPTIONS]
|
|
|
|
Required:
|
|
-h, --host HOST OpenWrt host (IP or hostname)
|
|
-o, --output FILE Output archive path (default: ./secubox-migration-TIMESTAMP.tar.gz)
|
|
|
|
Options:
|
|
-i, --identity FILE SSH private key file
|
|
-p, --port PORT SSH port (default: 22)
|
|
-e, --encrypt Encrypt archive with AES-256
|
|
--passphrase PASS Encryption passphrase (prompts if not provided)
|
|
-m, --modules LIST Comma-separated module list (default: all)
|
|
Available: network,firewall,wireguard,crowdsec,dhcp,
|
|
haproxy,nginx,certs,content,vhosts,users
|
|
--help Show this help
|
|
|
|
Examples:
|
|
# Basic export
|
|
$(basename "$0") -h 192.168.255.1 -o /tmp/migration.tar.gz
|
|
|
|
# With SSH key
|
|
$(basename "$0") -h 192.168.255.1 -i ~/.ssh/secubox-openwrt -o /tmp/migration.tar.gz
|
|
|
|
# Encrypted archive
|
|
$(basename "$0") -h 192.168.255.1 -e --passphrase "secret" -o /tmp/migration.tar.gz.enc
|
|
|
|
# Specific modules only
|
|
$(basename "$0") -h 192.168.255.1 -m wireguard,crowdsec,certs -o /tmp/migration.tar.gz
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# ── Parse arguments ──
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h|--host) HOST="$2"; shift 2 ;;
|
|
-i|--identity) SSH_KEY="$2"; shift 2 ;;
|
|
-o|--output) OUTPUT="$2"; shift 2 ;;
|
|
-p|--port) SSH_PORT="$2"; shift 2 ;;
|
|
-e|--encrypt) ENCRYPT=1; shift ;;
|
|
--passphrase) PASSPHRASE="$2"; shift 2 ;;
|
|
-m|--modules) MODULES="$2"; shift 2 ;;
|
|
--help) usage ;;
|
|
*) err "Unknown option: $1" ;;
|
|
esac
|
|
done
|
|
|
|
# ── Validate required arguments ──
|
|
[[ -z "$HOST" ]] && err "Host required. Use -h/--host"
|
|
[[ -z "$OUTPUT" ]] && OUTPUT="./secubox-migration-${TIMESTAMP}.tar.gz"
|
|
|
|
# ── Build SSH command ──
|
|
SSH_CMD="ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p $SSH_PORT"
|
|
SCP_CMD="scp -o StrictHostKeyChecking=no -P $SSH_PORT"
|
|
[[ -n "$SSH_KEY" ]] && {
|
|
[[ -f "$SSH_KEY" ]] || err "SSH key not found: $SSH_KEY"
|
|
SSH_CMD="$SSH_CMD -i $SSH_KEY"
|
|
SCP_CMD="$SCP_CMD -i $SSH_KEY"
|
|
}
|
|
|
|
ssh_run() { $SSH_CMD "root@$HOST" "$@"; }
|
|
scp_get() { $SCP_CMD "root@$HOST:$1" "$2"; }
|
|
|
|
# ── Test SSH connection ──
|
|
section "Testing SSH connection to $HOST"
|
|
ssh_run "echo 'Connection OK'" 2>/dev/null || err "Cannot connect to $HOST via SSH"
|
|
ok "SSH connection established"
|
|
|
|
# ── Create work directory ──
|
|
WORKDIR=$(mktemp -d -t secubox-migration-XXXXXX)
|
|
trap 'rm -rf "$WORKDIR"' EXIT
|
|
|
|
mkdir -p "$WORKDIR"/{configs,secrets,content,state}
|
|
|
|
# ── Detect OpenWrt version ──
|
|
section "Detecting SecuBox-OpenWrt version"
|
|
OWRT_VERSION=$(ssh_run "cat /etc/openwrt_release 2>/dev/null | grep DISTRIB_RELEASE | cut -d= -f2 | tr -d \"'\"" || echo "unknown")
|
|
SECUBOX_VERSION=$(ssh_run "cat /etc/secubox-version 2>/dev/null || echo 'unknown'")
|
|
log "OpenWrt: $OWRT_VERSION"
|
|
log "SecuBox: $SECUBOX_VERSION"
|
|
|
|
# ── Create manifest ──
|
|
cat > "$WORKDIR/manifest.json" <<EOF
|
|
{
|
|
"version": "$VERSION",
|
|
"timestamp": "$TIMESTAMP",
|
|
"source": {
|
|
"host": "$HOST",
|
|
"type": "openwrt",
|
|
"openwrt_version": "$OWRT_VERSION",
|
|
"secubox_version": "$SECUBOX_VERSION"
|
|
},
|
|
"modules": [],
|
|
"checksums": {}
|
|
}
|
|
EOF
|
|
|
|
# ── Module export functions ──
|
|
|
|
export_network() {
|
|
section "Exporting: Network Configuration (UCI)"
|
|
local dst="$WORKDIR/configs/network"
|
|
mkdir -p "$dst"
|
|
|
|
# UCI network config
|
|
ssh_run "cat /etc/config/network" > "$dst/network.uci" 2>/dev/null || warn "No /etc/config/network"
|
|
|
|
# Interfaces info
|
|
ssh_run "ip addr show" > "$dst/interfaces.txt" 2>/dev/null || true
|
|
ssh_run "ip route show" > "$dst/routes.txt" 2>/dev/null || true
|
|
|
|
ok "Network config exported"
|
|
}
|
|
|
|
export_firewall() {
|
|
section "Exporting: Firewall Rules (UCI)"
|
|
local dst="$WORKDIR/configs/firewall"
|
|
mkdir -p "$dst"
|
|
|
|
# UCI firewall config
|
|
ssh_run "cat /etc/config/firewall" > "$dst/firewall.uci" 2>/dev/null || warn "No /etc/config/firewall"
|
|
|
|
# Current iptables rules (for reference)
|
|
ssh_run "iptables-save 2>/dev/null" > "$dst/iptables.rules" || true
|
|
ssh_run "ip6tables-save 2>/dev/null" > "$dst/ip6tables.rules" || true
|
|
|
|
# Custom nftables if present
|
|
ssh_run "cat /etc/nftables.conf 2>/dev/null" > "$dst/nftables.conf" || true
|
|
|
|
ok "Firewall rules exported"
|
|
}
|
|
|
|
export_wireguard() {
|
|
section "Exporting: WireGuard Configuration"
|
|
local dst="$WORKDIR/configs/wireguard"
|
|
local secrets_dst="$WORKDIR/secrets/wireguard"
|
|
mkdir -p "$dst" "$secrets_dst"
|
|
|
|
# UCI wireguard config
|
|
ssh_run "cat /etc/config/wireguard 2>/dev/null" > "$dst/wireguard.uci" || true
|
|
|
|
# WireGuard configs (contains private keys - goes to secrets)
|
|
ssh_run "ls /etc/wireguard/*.conf 2>/dev/null" | while read -r f; do
|
|
local name=$(basename "$f")
|
|
ssh_run "cat $f" > "$secrets_dst/$name"
|
|
log " Exported: $name (secrets)"
|
|
done || warn "No WireGuard configs found"
|
|
|
|
# Interface status
|
|
ssh_run "wg show all 2>/dev/null" > "$dst/wg-status.txt" || true
|
|
|
|
ok "WireGuard exported"
|
|
}
|
|
|
|
export_crowdsec() {
|
|
section "Exporting: CrowdSec Configuration"
|
|
local dst="$WORKDIR/configs/crowdsec"
|
|
mkdir -p "$dst"
|
|
|
|
# Main config
|
|
ssh_run "cat /etc/crowdsec/config.yaml 2>/dev/null" > "$dst/config.yaml" || warn "No CrowdSec config"
|
|
|
|
# Acquis configs
|
|
ssh_run "tar -czf - /etc/crowdsec/acquis.d 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=2 || true
|
|
|
|
# Local parsers/scenarios
|
|
for subdir in parsers scenarios postoverflows; do
|
|
ssh_run "tar -czf - /etc/crowdsec/$subdir 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=2 || true
|
|
done
|
|
|
|
# Local API creds (secrets)
|
|
mkdir -p "$WORKDIR/secrets/crowdsec"
|
|
ssh_run "cat /etc/crowdsec/local_api_credentials.yaml 2>/dev/null" > "$WORKDIR/secrets/crowdsec/local_api_credentials.yaml" || true
|
|
|
|
ok "CrowdSec exported"
|
|
}
|
|
|
|
export_dhcp() {
|
|
section "Exporting: DHCP/DNS Configuration (UCI)"
|
|
local dst="$WORKDIR/configs/dhcp"
|
|
mkdir -p "$dst"
|
|
|
|
# UCI dhcp config
|
|
ssh_run "cat /etc/config/dhcp" > "$dst/dhcp.uci" 2>/dev/null || warn "No /etc/config/dhcp"
|
|
|
|
# Static leases
|
|
ssh_run "cat /tmp/dhcp.leases 2>/dev/null" > "$dst/leases.txt" || true
|
|
|
|
# Custom dnsmasq config
|
|
ssh_run "cat /etc/dnsmasq.conf 2>/dev/null" > "$dst/dnsmasq.conf" || true
|
|
ssh_run "ls /etc/dnsmasq.d/*.conf 2>/dev/null | xargs cat" > "$dst/dnsmasq.d.conf" || true
|
|
|
|
ok "DHCP/DNS exported"
|
|
}
|
|
|
|
export_haproxy() {
|
|
section "Exporting: HAProxy Configuration"
|
|
local dst="$WORKDIR/configs/haproxy"
|
|
mkdir -p "$dst"
|
|
|
|
# Main config
|
|
ssh_run "cat /etc/haproxy/haproxy.cfg 2>/dev/null" > "$dst/haproxy.cfg" || warn "No HAProxy config"
|
|
|
|
# Additional configs
|
|
ssh_run "tar -czf - /etc/haproxy/conf.d 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=2 || true
|
|
|
|
ok "HAProxy exported"
|
|
}
|
|
|
|
export_nginx() {
|
|
section "Exporting: Nginx Configuration"
|
|
local dst="$WORKDIR/configs/nginx"
|
|
mkdir -p "$dst"
|
|
|
|
# Main config
|
|
ssh_run "cat /etc/nginx/nginx.conf 2>/dev/null" > "$dst/nginx.conf" || true
|
|
|
|
# Sites
|
|
ssh_run "tar -czf - /etc/nginx/sites-enabled 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=2 || true
|
|
ssh_run "tar -czf - /etc/nginx/sites-available 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=2 || true
|
|
|
|
# Conf.d
|
|
ssh_run "tar -czf - /etc/nginx/conf.d 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=2 || true
|
|
|
|
ok "Nginx exported"
|
|
}
|
|
|
|
export_certs() {
|
|
section "Exporting: SSL Certificates"
|
|
local secrets_dst="$WORKDIR/secrets/certs"
|
|
mkdir -p "$secrets_dst"
|
|
|
|
# Let's Encrypt certificates
|
|
ssh_run "tar -czf - /etc/letsencrypt 2>/dev/null" | tar -xzf - -C "$secrets_dst" --strip-components=1 || true
|
|
|
|
# Custom SSL certs
|
|
ssh_run "ls /etc/ssl/private/*.key 2>/dev/null" | while read -r f; do
|
|
local name=$(basename "$f")
|
|
ssh_run "cat $f" > "$secrets_dst/$name"
|
|
done || true
|
|
|
|
ssh_run "ls /etc/ssl/certs/*.pem 2>/dev/null" | while read -r f; do
|
|
local name=$(basename "$f")
|
|
ssh_run "cat $f" > "$secrets_dst/$name"
|
|
done || true
|
|
|
|
ok "Certificates exported"
|
|
}
|
|
|
|
export_content() {
|
|
section "Exporting: Web Content"
|
|
local dst="$WORKDIR/content"
|
|
|
|
# /srv/www
|
|
ssh_run "tar -czf - /srv/www 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=1 || true
|
|
|
|
# /var/www
|
|
ssh_run "tar -czf - /var/www 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=1 || true
|
|
|
|
# Calculate size
|
|
local size=$(du -sh "$dst" 2>/dev/null | cut -f1)
|
|
ok "Web content exported ($size)"
|
|
}
|
|
|
|
export_vhosts() {
|
|
section "Exporting: Virtual Hosts (UCI)"
|
|
local dst="$WORKDIR/configs/vhosts"
|
|
mkdir -p "$dst"
|
|
|
|
# UCI vhost config
|
|
ssh_run "cat /etc/config/vhost 2>/dev/null" > "$dst/vhost.uci" || warn "No /etc/config/vhost"
|
|
|
|
# SecuBox vhost configs
|
|
ssh_run "tar -czf - /etc/secubox/vhosts 2>/dev/null" | tar -xzf - -C "$dst" --strip-components=3 || true
|
|
|
|
ok "Virtual hosts exported"
|
|
}
|
|
|
|
export_users() {
|
|
section "Exporting: User Accounts"
|
|
local dst="$WORKDIR/configs/users"
|
|
local secrets_dst="$WORKDIR/secrets/users"
|
|
mkdir -p "$dst" "$secrets_dst"
|
|
|
|
# Filter system users - only export secubox-related users
|
|
ssh_run "grep -E '^(root|secubox|admin)' /etc/passwd 2>/dev/null" > "$dst/passwd.filtered" || true
|
|
ssh_run "grep -E '^(root|secubox|admin)' /etc/shadow 2>/dev/null" > "$secrets_dst/shadow.filtered" || true
|
|
|
|
# SSH authorized keys
|
|
ssh_run "cat /root/.ssh/authorized_keys 2>/dev/null" > "$secrets_dst/authorized_keys" || true
|
|
|
|
# SecuBox auth config
|
|
ssh_run "cat /etc/secubox/auth.toml 2>/dev/null" > "$dst/auth.toml" || true
|
|
|
|
ok "Users exported"
|
|
}
|
|
|
|
export_state() {
|
|
section "Exporting: Service State Data"
|
|
local dst="$WORKDIR/state"
|
|
|
|
# CrowdSec decisions DB
|
|
mkdir -p "$dst/crowdsec"
|
|
ssh_run "cat /var/lib/crowdsec/data/crowdsec.db 2>/dev/null" > "$dst/crowdsec/crowdsec.db" || true
|
|
|
|
# SecuBox state
|
|
mkdir -p "$dst/secubox"
|
|
ssh_run "tar -czf - /var/lib/secubox 2>/dev/null" | tar -xzf - -C "$dst/secubox" --strip-components=3 || true
|
|
|
|
ok "State data exported"
|
|
}
|
|
|
|
# ── Module selection ──
|
|
declare -A MODULE_FUNCS=(
|
|
[network]=export_network
|
|
[firewall]=export_firewall
|
|
[wireguard]=export_wireguard
|
|
[crowdsec]=export_crowdsec
|
|
[dhcp]=export_dhcp
|
|
[haproxy]=export_haproxy
|
|
[nginx]=export_nginx
|
|
[certs]=export_certs
|
|
[content]=export_content
|
|
[vhosts]=export_vhosts
|
|
[users]=export_users
|
|
[state]=export_state
|
|
)
|
|
|
|
ALL_MODULES="network,firewall,wireguard,crowdsec,dhcp,haproxy,nginx,certs,content,vhosts,users,state"
|
|
|
|
if [[ "$MODULES" == "all" ]]; then
|
|
MODULES="$ALL_MODULES"
|
|
fi
|
|
|
|
# ── Execute exports ──
|
|
section "Starting Migration Export"
|
|
log "Target: $HOST"
|
|
log "Modules: $MODULES"
|
|
log "Output: $OUTPUT"
|
|
|
|
IFS=',' read -ra MOD_ARRAY <<< "$MODULES"
|
|
EXPORTED_MODULES=()
|
|
|
|
for mod in "${MOD_ARRAY[@]}"; do
|
|
mod=$(echo "$mod" | tr -d ' ')
|
|
if [[ -n "${MODULE_FUNCS[$mod]:-}" ]]; then
|
|
${MODULE_FUNCS[$mod]}
|
|
EXPORTED_MODULES+=("$mod")
|
|
else
|
|
warn "Unknown module: $mod"
|
|
fi
|
|
done
|
|
|
|
# ── Update manifest with exported modules ──
|
|
section "Creating Archive"
|
|
|
|
# Add modules to manifest
|
|
MODULES_JSON=$(printf '"%s",' "${EXPORTED_MODULES[@]}" | sed 's/,$//')
|
|
sed -i "s/\"modules\": \[\]/\"modules\": [$MODULES_JSON]/" "$WORKDIR/manifest.json"
|
|
|
|
# Generate checksums
|
|
log "Generating checksums..."
|
|
(cd "$WORKDIR" && find . -type f ! -name 'manifest.json' -exec sha256sum {} \; > checksums.sha256)
|
|
|
|
# ── Create archive ──
|
|
log "Creating tar archive..."
|
|
ARCHIVE_TMP="${OUTPUT%.enc}"
|
|
tar -czf "$ARCHIVE_TMP" -C "$WORKDIR" .
|
|
|
|
# ── Encrypt if requested ──
|
|
if [[ $ENCRYPT -eq 1 ]]; then
|
|
section "Encrypting Archive"
|
|
|
|
if [[ -z "$PASSPHRASE" ]]; then
|
|
read -sp "Enter encryption passphrase: " PASSPHRASE
|
|
echo
|
|
read -sp "Confirm passphrase: " PASSPHRASE2
|
|
echo
|
|
[[ "$PASSPHRASE" != "$PASSPHRASE2" ]] && err "Passphrases do not match"
|
|
fi
|
|
|
|
openssl enc -aes-256-cbc -salt -pbkdf2 -in "$ARCHIVE_TMP" -out "${OUTPUT}" -pass pass:"$PASSPHRASE"
|
|
rm -f "$ARCHIVE_TMP"
|
|
ok "Archive encrypted: $OUTPUT"
|
|
else
|
|
[[ "$ARCHIVE_TMP" != "$OUTPUT" ]] && mv "$ARCHIVE_TMP" "$OUTPUT"
|
|
fi
|
|
|
|
# ── Summary ──
|
|
section "Export Complete"
|
|
ARCHIVE_SIZE=$(du -h "$OUTPUT" | cut -f1)
|
|
log "Archive: $OUTPUT ($ARCHIVE_SIZE)"
|
|
log "Modules exported: ${EXPORTED_MODULES[*]}"
|
|
log "Encrypted: $([ $ENCRYPT -eq 1 ] && echo 'Yes (AES-256)' || echo 'No')"
|
|
echo
|
|
ok "Migration export completed successfully"
|
|
echo
|
|
log "To import on SecuBox-DEB target:"
|
|
log " bash scripts/migration-import.sh -f $OUTPUT --dry-run"
|
|
log " bash scripts/migration-import.sh -f $OUTPUT"
|