#!/bin/sh # # SecuBox Self-Recovery Bootstrap Script # Downloads and restores a SecuBox appliance configuration from Gitea # # Usage: # curl -sL http://gitea-server/user/repo/raw/branch/master/bootstrap.sh | sh # # OR # wget -qO- http://gitea-server/user/repo/raw/branch/master/bootstrap.sh | sh # # OR # secubox-restore [access-token] # set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } print_banner() { cat << 'EOF' ____ ____ / ___| ___ ___ _ | __ ) _____ __ \___ \ / _ \/ __| | | | _ \ / _ \ \/ / ___) | __/ (__| |_| | |_) | (_) > < |____/ \___|\___|\__,_|____/ \___/_/\_\ Self-Recovery Bootstrap v1.0 EOF } # Check if we're on OpenWrt check_openwrt() { if [ ! -f /etc/openwrt_release ]; then log_error "This script must be run on OpenWrt" exit 1 fi . /etc/openwrt_release log_info "Detected OpenWrt $DISTRIB_RELEASE on $(cat /tmp/sysinfo/model 2>/dev/null || echo 'unknown device')" } # Install dependencies if missing install_deps() { log_info "Checking dependencies..." # Required packages for pkg in curl git-http jsonfilter; do if ! command -v "$pkg" >/dev/null 2>&1 && ! opkg list-installed | grep -q "^$pkg "; then log_info "Installing $pkg..." opkg update >/dev/null 2>&1 || true opkg install "$pkg" 2>/dev/null || log_warn "Could not install $pkg" fi done log_success "Dependencies ready" } # Fetch manifest from Gitea repository fetch_manifest() { local server_url="$1" local repo_owner="$2" local repo_name="$3" local token="$4" local branch="${5:-master}" log_info "Fetching backup manifest..." local manifest_url="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/manifest.json?ref=${branch}" if [ -n "$token" ]; then response=$(curl -sL "$manifest_url" -H "Authorization: token $token") else response=$(curl -sL "$manifest_url") fi # Check for error if echo "$response" | jsonfilter -e '@.message' 2>/dev/null | grep -qi "not found"; then log_error "Repository or manifest not found" return 1 fi # Decode base64 content content=$(echo "$response" | jsonfilter -e '@.content' 2>/dev/null | base64 -d 2>/dev/null) if [ -z "$content" ]; then log_error "Failed to fetch manifest" return 1 fi echo "$content" } # Fetch and restore a file from Gitea fetch_file() { local server_url="$1" local repo_owner="$2" local repo_name="$3" local file_path="$4" local dest_path="$5" local token="$6" local branch="${7:-master}" local file_url="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/${file_path}?ref=${branch}" if [ -n "$token" ]; then response=$(curl -sL "$file_url" -H "Authorization: token $token") else response=$(curl -sL "$file_url") fi # Check if it's a file (has content) or directory content=$(echo "$response" | jsonfilter -e '@.content' 2>/dev/null) if [ -n "$content" ]; then # Single file - decode and save mkdir -p "$(dirname "$dest_path")" echo "$content" | base64 -d > "$dest_path" 2>/dev/null return 0 fi return 1 } # Fetch directory contents recursively fetch_directory() { local server_url="$1" local repo_owner="$2" local repo_name="$3" local dir_path="$4" local dest_base="$5" local token="$6" local branch="${7:-master}" local dir_url="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/${dir_path}?ref=${branch}" if [ -n "$token" ]; then response=$(curl -sL "$dir_url" -H "Authorization: token $token") else response=$(curl -sL "$dir_url") fi # Parse each entry echo "$response" | jsonfilter -e '@[*].path' 2>/dev/null | while read -r path; do type=$(echo "$response" | jsonfilter -e "@[@.path='$path'].type" 2>/dev/null) if [ "$type" = "file" ]; then local rel_path="${path#$dir_path/}" local dest_path="${dest_base}/${rel_path}" fetch_file "$server_url" "$repo_owner" "$repo_name" "$path" "$dest_path" "$token" "$branch" elif [ "$type" = "dir" ]; then fetch_directory "$server_url" "$repo_owner" "$repo_name" "$path" "$dest_base" "$token" "$branch" fi done } # Restore configs from backup restore_configs() { local backup_dir="$1" local configs_dir="$backup_dir/configs" if [ ! -d "$configs_dir" ]; then log_warn "No configs directory found in backup" return 0 fi log_info "Restoring UCI configurations..." local count=0 for cfg in "$configs_dir"/*; do [ -f "$cfg" ] || continue local name=$(basename "$cfg") # Skip system-critical configs unless explicitly requested case "$name" in network|wireless|firewall) log_warn "Skipping critical config: $name (use --include-network to restore)" continue ;; esac cp "$cfg" "/etc/config/$name" count=$((count + 1)) done log_success "Restored $count configuration files" } # Restore scripts restore_scripts() { local backup_dir="$1" local scripts_dir="$backup_dir/scripts" if [ ! -d "$scripts_dir" ]; then return 0 fi log_info "Restoring custom scripts..." mkdir -p /usr/share/secubox/scripts cp -r "$scripts_dir"/* /usr/share/secubox/scripts/ 2>/dev/null chmod +x /usr/share/secubox/scripts/* 2>/dev/null log_success "Scripts restored" } # Restore cron jobs restore_cron() { local backup_dir="$1" local cron_file="$backup_dir/cron/root" if [ ! -f "$cron_file" ]; then return 0 fi log_info "Restoring cron jobs..." # Append to existing crontab (don't overwrite) cat "$cron_file" >> /etc/crontabs/root 2>/dev/null /etc/init.d/cron restart 2>/dev/null log_success "Cron jobs restored" } # Install SecuBox packages if not present install_secubox() { log_info "Checking SecuBox installation..." # Check if secubox-core is installed if ! opkg list-installed | grep -q "secubox-core"; then log_info "SecuBox not installed, attempting installation..." # Try to add SecuBox feed if ! grep -q "secubox" /etc/opkg/customfeeds.conf 2>/dev/null; then log_warn "SecuBox feed not configured" log_info "Please configure SecuBox feed manually and re-run this script" return 1 fi opkg update opkg install secubox-core luci-app-secubox fi log_success "SecuBox is installed" } # Main restore function do_restore() { local server_url="$1" local repo_owner="$2" local repo_name="$3" local token="$4" local branch="${5:-master}" # Create temp directory local backup_dir="/tmp/secubox-restore-$$" mkdir -p "$backup_dir" # Fetch manifest first manifest=$(fetch_manifest "$server_url" "$repo_owner" "$repo_name" "$token" "$branch") if [ -z "$manifest" ]; then log_error "Failed to fetch backup manifest" rm -rf "$backup_dir" return 1 fi # Display backup info local backup_hostname=$(echo "$manifest" | jsonfilter -e '@.hostname' 2>/dev/null) local backup_timestamp=$(echo "$manifest" | jsonfilter -e '@.timestamp' 2>/dev/null) local backup_version=$(echo "$manifest" | jsonfilter -e '@.secubox_version' 2>/dev/null) echo "" log_info "Backup Information:" echo " Source Hostname: $backup_hostname" echo " Backup Date: $backup_timestamp" echo " SecuBox Version: $backup_version" echo "" # Ask for confirmation if interactive if [ -t 0 ]; then printf "Proceed with restore? [y/N] " read -r confirm case "$confirm" in [yY][eE][sS]|[yY]) ;; *) log_info "Restore cancelled"; rm -rf "$backup_dir"; return 0 ;; esac fi # Fetch each component log_info "Downloading backup components..." for component in configs profiles presets manifests scripts cron ssh certificates; do log_info "Fetching $component..." fetch_directory "$server_url" "$repo_owner" "$repo_name" "$component" "$backup_dir/$component" "$token" "$branch" 2>/dev/null || true done # Perform restore log_info "Applying backup..." restore_configs "$backup_dir" restore_scripts "$backup_dir" restore_cron "$backup_dir" # Save Gitea config for future backups uci set secubox-p2p.gitea=gitea uci set secubox-p2p.gitea.enabled='1' uci set secubox-p2p.gitea.server_url="$server_url" uci set secubox-p2p.gitea.repo_owner="$repo_owner" uci set secubox-p2p.gitea.repo_name="$repo_name" [ -n "$token" ] && uci set secubox-p2p.gitea.access_token="$token" uci commit secubox-p2p # Cleanup rm -rf "$backup_dir" log_success "Restore completed!" echo "" log_info "Please reboot to apply all changes: reboot" } # Interactive mode - prompt for settings interactive_mode() { print_banner echo "" check_openwrt install_deps echo "" log_info "Enter Gitea repository details:" echo "" printf "Gitea Server URL (e.g., http://192.168.1.1:3000): " read -r server_url printf "Repository Owner (username): " read -r repo_owner printf "Repository Name: " read -r repo_name printf "Access Token (leave empty for public repos): " read -r token echo "" do_restore "$server_url" "$repo_owner" "$repo_name" "$token" } # Show usage usage() { cat << EOF SecuBox Self-Recovery Bootstrap Script Usage: $0 [options] [access-token] $0 --interactive Options: --interactive, -i Run in interactive mode with prompts --branch, -b Git branch to restore from (default: master) --include-network Also restore network/wireless/firewall configs --help, -h Show this help message Examples: # Interactive mode $0 -i # Direct restore from public repo $0 http://gitea.local:3000 admin secubox-backup # Restore with token $0 http://gitea.local:3000 admin secubox-backup abc123token # Restore from specific branch $0 -b dev http://gitea.local:3000 admin secubox-backup Quick Bootstrap (paste into new OpenWrt shell): wget -qO- http://your-gitea/user/repo/raw/master/bootstrap.sh | sh EOF } # Parse arguments BRANCH="master" INCLUDE_NETWORK=0 while [ $# -gt 0 ]; do case "$1" in --interactive|-i) interactive_mode exit 0 ;; --branch|-b) BRANCH="$2" shift 2 ;; --include-network) INCLUDE_NETWORK=1 shift ;; --help|-h) usage exit 0 ;; -*) log_error "Unknown option: $1" usage exit 1 ;; *) break ;; esac done # If no arguments, run interactive if [ $# -eq 0 ]; then interactive_mode exit 0 fi # Validate arguments if [ $# -lt 3 ]; then log_error "Missing required arguments" usage exit 1 fi SERVER_URL="$1" REPO_OWNER="$2" REPO_NAME="$3" ACCESS_TOKEN="${4:-}" print_banner echo "" check_openwrt install_deps do_restore "$SERVER_URL" "$REPO_OWNER" "$REPO_NAME" "$ACCESS_TOKEN" "$BRANCH"