first commit

This commit is contained in:
kj
2026-07-14 11:51:45 -03:00
commit 0f2cfce7ad
6 changed files with 1178 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/backups/

152
README.md Normal file
View File

@@ -0,0 +1,152 @@
# ROCKNIX Backup & Restore
Backup and restore script for ROCKNIX emulator configurations. Allows syncing optimal settings between multiple consoles via SSH.
## Features
- **Specific backup**: Full backup including all device/user-specific settings
- **Generic backup**: Portable backup with device-specific settings filtered out
- **Selective restore**: Choose which components to restore via interactive dialog
- **Safety backup**: Automatically saves current settings before restoring
- **Device detection**: Automatically identifies the target ROCKNIX device
- **SSH support**: Password and key-based authentication
## Requirements
```bash
# Fedora/RHEL
sudo dnf install tar dialog rsync sshpass
# Debian/Ubuntu
sudo apt install tar dialog rsync sshpass
```
## Usage
### Backup
```bash
# Specific backup (includes all settings)
./backup-rocknix.sh
# Generic backup (portable between devices)
./backup-rocknix.sh --type generic
# Custom host and output directory
./backup-rocknix.sh --host myconsole.local --output /path/to/backups
```
### Restore
```bash
# Interactive restore (lists available backups)
./restore-rocknix.sh
# Restore specific backup file
./restore-rocknix.sh --backup backups/rocknix-config-specific-rg-cubexx-20260714-113010.tar.gz
# Restore all components without prompting
./restore-rocknix.sh --backup backup.tar.gz --all
```
## Options
### backup-rocknix.sh
| Option | Description | Default |
|-------------------|-------------------------|------------------|
| `--host HOST` | Console hostname/IP | `rgcubexx.local` |
| `--user USER` | SSH username | `root` |
| `--password PASS` | SSH password | (prompts) |
| `--output DIR` | Backup output directory | `backups/` |
| `--type TYPE` | `specific` or `generic` | `specific` |
| `--help` | Show help | |
### restore-rocknix.sh
| Option | Description | Default |
|-------------------|----------------------------|------------------|
| `--host HOST` | Target console hostname/IP | `rgcubexx.local` |
| `--user USER` | SSH username | `root` |
| `--password PASS` | SSH password | (prompts) |
| `--backup FILE` | Backup file to restore | (interactive) |
| `--all` | Restore all components | `false` |
| `--help` | Show help | |
## Components
The following components can be backed up and restored:
| Component | Description |
|--------------------------|--------------------------------------------|
| `retroarch-core-options` | RetroArch global core options |
| `retroarch-opt` | Per-core `.opt` files |
| `retroarch-rmp` | Button remaps `.rmp` files |
| `retroarch-config` | Per-core overrides (`config/`) |
| `shaders` | Shader packs and presets |
| `ppsspp` | PPSSPP (PSP) settings |
| `drastic` | DraStic (NDS) settings |
| `other-emu` | Flycast, DuckStation, Yabasanshiro configs |
## Backup Types
### Specific Backup
Includes all settings, including device-specific values like:
- Screen resolution and scaling
- Video/audio drivers
- Input mappings
- Account credentials (RetroAchievements, cloud sync)
Best for: Backing up the same device or identical hardware.
### Generic Backup
Filters out device-specific settings for portability:
- Screen dimensions (`video_fullscreen_x/y`)
- Scale factors (`menu_scale_factor`)
- Drivers (`video_driver`, `audio_driver`)
- Credentials and tokens
Best for: Sharing configs between different devices or users.
## Configuration
Edit `config.sh` to customize:
- `ROCKNIX_HOST`: Default console hostname
- `ROCKNIX_STORAGE`: Storage path on device (default: `/storage`)
- `BACKUP_DIR`: Local backup directory
- `SPECIFIC_FILES`: List of files to back up
- `GENERIC_EXCLUDES`: Files excluded from generic backups
- `GENERIC_SETTINGS_FILTER`: Settings patterns removed from generic backups
## Examples
```bash
# First-time setup: create a specific backup
./backup-rocknix.sh --host rgcubexx.local
# Clone settings to another device of the same model
./backup-rocknix.sh --type generic
./restore-rocknix.sh --host rgcubexx2.local --backup backups/rocknix-config-generic-*.tar.gz
# List available backups
./restore-rocknix.sh # Shows interactive menu
```
## File Structure
```
rocknix-backup/
├── backup-rocknix.sh # Backup script
├── restore-rocknix.sh # Restore script
├── lib-common.sh # Shared functions (SSH, rsync, logging)
├── config.sh # Default configuration
├── backups/ # Backup files (*.tar.gz)
└── PRE-ANALISIS-ROCKNIX.md # Technical analysis document
```
## License
MIT

203
backup-rocknix.sh Executable file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/env bash
# backup-rocknix.sh - Backup script for ROCKNIX configurations
#
# Usage:
# ./backup-rocknix.sh [OPTIONS]
#
# Options:
# --host HOST Console host (default: rgeubexx.local)
# --user USER SSH user (default: root)
# --output DIR Output directory for backups
# --type TYPE Backup type: specific or generic (default: specific)
# --help Show help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib-common.sh"
# Default values
BACKUP_TYPE="specific"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--host)
ROCKNIX_HOST="$2"
shift 2
;;
--user)
ROCKNIX_USER="$2"
shift 2
;;
--password)
ROCKNIX_PASSWORD="$2"
shift 2
;;
--output)
BACKUP_DIR="$2"
shift 2
;;
--type)
BACKUP_TYPE="$2"
shift 2
;;
--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --host HOST Console host (default: $ROCKNIX_HOST)"
echo " --user USER SSH user (default: $ROCKNIX_USER)"
echo " --password PASS SSH password"
echo " --output DIR Output directory"
echo " --type TYPE Backup type: specific, generic (default: specific)"
echo " --help Show this help"
exit 0
;;
*)
log ERROR "Unknown option: $1"
exit 1
;;
esac
done
# Verify valid type
if [[ "$BACKUP_TYPE" != "specific" && "$BACKUP_TYPE" != "generic" ]]; then
log ERROR "Invalid backup type: $BACKUP_TYPE (use: specific, generic)"
exit 1
fi
# Check dependencies
check_deps || exit 1
# Ask for password if not configured
ask_password || exit 1
# Verify SSH connection
ssh_check "$ROCKNIX_HOST" "$ROCKNIX_USER" || exit 1
# Detect device
DEVICE=$(detect_device "$ROCKNIX_HOST" "$ROCKNIX_USER" 2>/dev/null || echo "unknown")
DEVICE_SLUG=$(echo "$DEVICE" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
# Create backups directory
mkdir -p "$BACKUP_DIR"
# Create temporary directory
TMP_DIR=$(create_tmp_dir "rocknix-backup")
trap 'cleanup_tmp "$TMP_DIR"' EXIT
log INFO "Starting ${BACKUP_TYPE} backup..."
log INFO "Device: ${DEVICE}"
log INFO "Host: ${ROCKNIX_USER}@${ROCKNIX_HOST}"
# Select file list based on type
if [[ "$BACKUP_TYPE" == "specific" ]]; then
FILES_TO_BACKUP=("${SPECIFIC_FILES[@]}")
else
# For generic, exclude device-specific files
FILES_TO_BACKUP=()
for file in "${SPECIFIC_FILES[@]}"; do
skip=false
for exclude in "${GENERIC_EXCLUDES[@]}"; do
if [[ "$file" == "$exclude" ]]; then
skip=true
break
fi
done
if [[ "$skip" == "false" ]]; then
FILES_TO_BACKUP+=("$file")
fi
done
fi
# Download files
log INFO "Downloading files..."
DOWNLOADED=0
FAILED=0
for file in "${FILES_TO_BACKUP[@]}"; do
local_path="${TMP_DIR}/${file}"
remote_path="${file}"
# Create local directory
mkdir -p "$(dirname "$local_path")"
# Download
if [[ -d "${local_path}" ]] || [[ "$file" == */ ]]; then
# It's a directory
mkdir -p "$local_path"
if download_dir "$remote_path" "$local_path" "$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
DOWNLOADED=$((DOWNLOADED + 1))
log DEBUG "Downloaded: ${file}"
else
FAILED=$((FAILED + 1))
log WARN "Could not download: ${file}"
fi
else
# It's a file
if download_file "$remote_path" "$local_path" "$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
DOWNLOADED=$((DOWNLOADED + 1))
log DEBUG "Downloaded: ${file}"
else
FAILED=$((FAILED + 1))
log WARN "Could not download: ${file}"
fi
fi
done
log INFO "Downloaded: ${DOWNLOADED} files"
# For generic backup, filter device/user-specific settings
if [[ "$BACKUP_TYPE" == "generic" ]]; then
log INFO "Filtering device/user-specific settings..."
# Filter retroarch-core-options.cfg if it exists
if [[ -f "${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg" ]]; then
filter_specific_settings \
"${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg" \
"${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg.filtered"
mv "${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg.filtered" \
"${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg"
fi
# Filter overrides (.cfg files)
find "${TMP_DIR}/.config/retroarch/config/" -name "*.cfg" -type f 2>/dev/null | while read -r cfg_file; do
filter_specific_settings "$cfg_file" "${cfg_file}.filtered"
mv "${cfg_file}.filtered" "$cfg_file"
done
fi
# Create backup filename
TIMESTAMP=$(date '+%Y%m%d-%H%M%S')
BACKUP_NAME="rocknix-config-${BACKUP_TYPE}-${DEVICE_SLUG}-${TIMESTAMP}.tar.gz"
BACKUP_FILE="${BACKUP_DIR}/${BACKUP_NAME}"
# Compress
log INFO "Compressing backup..."
tar -czf "$BACKUP_FILE" -C "$TMP_DIR" .
# Verify
if [[ -f "$BACKUP_FILE" ]]; then
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
BACKUP_FILES=$(tar -tzf "$BACKUP_FILE" | wc -l)
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Backup completed successfully${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e " Type: ${CYAN}${BACKUP_TYPE}${NC}"
echo -e " Device: ${CYAN}${DEVICE}${NC}"
echo -e " File: ${CYAN}${BACKUP_NAME}${NC}"
echo -e " Location: ${CYAN}${BACKUP_DIR}${NC}"
echo -e " Size: ${CYAN}${BACKUP_SIZE}${NC}"
echo -e " Files: ${CYAN}${BACKUP_FILES}${NC}"
echo ""
echo -e " ${YELLOW}To restore, run:${NC}"
echo -e " ${CYAN}./restore-rocknix.sh --backup ${BACKUP_FILE}${NC}"
echo ""
else
log ERROR "Could not create backup file"
exit 1
fi

86
config.sh Executable file
View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# config.sh - Default configuration for ROCKNIX backup/restore
# ROCKNIX console host
ROCKNIX_HOST="${ROCKNIX_HOST:-rgcubexx.local}"
ROCKNIX_USER="${ROCKNIX_USER:-root}"
ROCKNIX_PASSWORD="${ROCKNIX_PASSWORD:-}"
ROCKNIX_STORAGE="${ROCKNIX_STORAGE:-/storage}"
# Local backup directory (resolved in lib-common.sh)
BACKUP_DIR="${BACKUP_DIR:-backups}"
# Files to back up (relative to /storage)
# Specific backup (includes everything)
SPECIFIC_FILES=(
# RetroArch global
".config/retroarch/retroarch.cfg"
".config/retroarch/retroarch-core-options.cfg"
".config/retroarch/retroarch32bit-append.cfg"
".config/retroarch/retroarch64bit-append.cfg"
# RetroArch .opt (per-core options at root level)
".config/retroarch/Flycast.opt"
".config/retroarch/Supafaust.opt"
".config/retroarch/ParaLLEl N64.opt"
# RetroArch .rmp (remaps at root level)
".config/retroarch/TATE-MAME 2003-Plus.rmp"
# RetroArch overrides (complete directory)
".config/retroarch/config/"
# Shaders
"shaders/"
# PPSSPP
".config/ppsspp/PSP/SYSTEM/ppsspp.ini"
".config/ppsspp/PSP/SYSTEM/controls.ini"
# DraStic
".config/drastic/config/"
# Other emulators
".config/game/configs/"
".config/flycast/emu.cfg"
".config/flycast/flycast.gptk"
".config/flycast/mappings/"
".config/duckstation/settings.ini"
".config/yabasanshiro/.config"
)
# Generic backup: exclude files with device/user-specific settings
GENERIC_EXCLUDES=(
".config/retroarch/retroarch.cfg"
".config/retroarch/retroarch32bit-append.cfg"
".config/retroarch/retroarch64bit-append.cfg"
)
# Settings patterns to filter from overrides in generic backup
GENERIC_SETTINGS_FILTER=(
"menu_scale_factor"
"menu_widget_scale_factor"
"video_fullscreen_x"
"video_fullscreen_y"
"video_windowed_position_width"
"video_windowed_position_height"
"video_refresh_rate"
"custom_viewport_width"
"custom_viewport_height"
"video_driver"
"audio_driver"
"input_driver"
"input_joypad_driver"
"cheevos_username"
"cheevos_password"
"cheevos_token"
"cheevos_custom_host"
"google_drive_refresh_token"
"webdav_password"
"webdav_username"
"kiosk_mode_password"
"content_show_settings_password"
"netplay_ip_address"
"netplay_ip_port"
"netplay_password"
)

343
lib-common.sh Executable file
View File

@@ -0,0 +1,343 @@
#!/usr/bin/env bash
# lib-common.sh - Shared functions for ROCKNIX backup/restore
set -euo pipefail
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Load configuration
source "$SCRIPT_DIR/config.sh"
# Resolve BACKUP_DIR relative to script
case "$BACKUP_DIR" in
/*) ;; # Absolute path, do nothing
*) BACKUP_DIR="${SCRIPT_DIR}/${BACKUP_DIR}" ;;
esac
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Logging
log() {
local level="$1"
shift
local msg="$*"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case "$level" in
INFO) echo -e "${GREEN}[${timestamp}] INFO:${NC} $msg" ;;
WARN) echo -e "${YELLOW}[${timestamp}] WARN:${NC} $msg" ;;
ERROR) echo -e "${RED}[${timestamp}] ERROR:${NC} $msg" >&2 ;;
DEBUG) echo -e "${BLUE}[${timestamp}] DEBUG:${NC} $msg" ;;
esac
}
# Check dependencies
check_deps() {
local missing=()
for dep in tar dialog rsync sshpass; do
if ! command -v "$dep" &>/dev/null; then
missing+=("$dep")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log ERROR "Missing dependencies: ${missing[*]}"
log INFO "Install with: sudo dnf install ${missing[*]}"
return 1
fi
return 0
}
# Ask for password if not configured
ask_password() {
if [[ -z "$ROCKNIX_PASSWORD" ]]; then
read -s -p "Password for ${ROCKNIX_USER}@${ROCKNIX_HOST}: " ROCKNIX_PASSWORD
echo ""
if [[ -z "$ROCKNIX_PASSWORD" ]]; then
log ERROR "Empty password"
return 1
fi
fi
export ROCKNIX_PASSWORD
}
# Build SSH connection string
ssh_target() {
local host="${1:-$ROCKNIX_HOST}"
local user="${2:-$ROCKNIX_USER}"
echo "${user}@${host}"
}
# Verify SSH connection
ssh_check() {
local host="${1:-$ROCKNIX_HOST}"
local user="${2:-$ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
log INFO "Checking connection to ${target}..."
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
if sshpass -p "$ROCKNIX_PASSWORD" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no "$target" "echo ok" &>/dev/null; then
log INFO "Connection successful"
return 0
fi
else
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$target" "echo ok" &>/dev/null; then
log INFO "Connection successful"
return 0
fi
fi
log ERROR "Could not connect to ${target}"
log INFO "Make sure SSH is enabled on the ROCKNIX console"
return 1
}
# Execute remote command
remote_exec() {
local cmd="$1"
local host="${2:-$ROCKNIX_HOST}"
local user="${3:-$ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
sshpass -p "$ROCKNIX_PASSWORD" ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$target" "$cmd" 2>&1
else
ssh -o ConnectTimeout=10 "$target" "$cmd" 2>&1
fi
}
# List remote files
remote_list() {
local remote_path="$1"
local host="${2:-$ROCKNIX_HOST}"
local user="${3:-$ROCKNIX_USER}"
remote_exec "ls -la ${ROCKNIX_STORAGE}/${remote_path}/ 2>/dev/null" "$host" "$user"
}
# Download entire directory via rsync
download_dir() {
local remote_path="$1"
local local_path="$2"
local host="${3:-$ROCKNIX_HOST}"
local user="${4:-$ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
mkdir -p "$local_path"
local rsync_opts=(-avz --timeout=30 -e "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10")
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
sshpass -p "$ROCKNIX_PASSWORD" rsync "${rsync_opts[@]}" \
"${target}:${ROCKNIX_STORAGE}/${remote_path}/" \
"${local_path}/" 2>&1
else
rsync "${rsync_opts[@]}" \
"${target}:${ROCKNIX_STORAGE}/${remote_path}/" \
"${local_path}/" 2>&1
fi
}
# Upload entire directory via rsync
upload_dir() {
local local_path="$1"
local remote_path="$2"
local host="${3:-$ROCKNIX_HOST}"
local user="${4:-$ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
local rsync_opts=(-avz --timeout=30 -e "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10")
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
sshpass -p "$ROCKNIX_PASSWORD" rsync "${rsync_opts[@]}" \
"${local_path}/" \
"${target}:${ROCKNIX_STORAGE}/${remote_path}/" 2>&1
else
rsync "${rsync_opts[@]}" \
"${local_path}/" \
"${target}:${ROCKNIX_STORAGE}/${remote_path}/" 2>&1
fi
}
# Download a single file
download_file() {
local remote_file="$1"
local local_file="$2"
local host="${3:-$ROCKNIX_HOST}"
local user="${4:-$ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
mkdir -p "$(dirname "$local_file")"
local rsync_opts=(-avz --timeout=30 -e "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10")
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
sshpass -p "$ROCKNIX_PASSWORD" rsync "${rsync_opts[@]}" \
"${target}:${ROCKNIX_STORAGE}/${remote_file}" \
"${local_file}" 2>&1
else
rsync "${rsync_opts[@]}" \
"${target}:${ROCKNIX_STORAGE}/${remote_file}" \
"${local_file}" 2>&1
fi
}
# Upload a single file
upload_file() {
local local_file="$1"
local remote_file="$2"
local host="${3:-$ROCKNIX_HOST}"
local user="${4:-$ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
local rsync_opts=(-avz --timeout=30 -e "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10")
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
sshpass -p "$ROCKNIX_PASSWORD" rsync "${rsync_opts[@]}" \
"${local_file}" \
"${target}:${ROCKNIX_STORAGE}/${remote_file}" 2>&1
else
rsync "${rsync_opts[@]}" \
"${local_file}" \
"${target}:${ROCKNIX_STORAGE}/${remote_file}" 2>&1
fi
}
# Direct backup via tar over SSH (no temporary files)
backup_via_ssh() {
local files=("$@")
local host="${ROCKNIX_HOST}"
local user="${ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
# Build remote tar command
local tar_args=""
for file in "${files[@]}"; do
tar_args="${tar_args} ${ROCKNIX_STORAGE}/${file}"
done
# Execute remote tar and compress locally
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
sshpass -p "$ROCKNIX_PASSWORD" ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$target" \
"tar czf - -C ${ROCKNIX_STORAGE} ${tar_args}" 2>/dev/null
else
ssh -o ConnectTimeout=10 "$target" \
"tar czf - -C ${ROCKNIX_STORAGE} ${tar_args}" 2>/dev/null
fi
}
# Detect ROCKNIX device
detect_device() {
local host="${1:-$ROCKNIX_HOST}"
local user="${2:-$ROCKNIX_USER}"
local target
target=$(ssh_target "$host" "$user")
log INFO "Detecting device..." >&2
local device_info
if [[ -n "$ROCKNIX_PASSWORD" ]]; then
device_info=$(sshpass -p "$ROCKNIX_PASSWORD" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no "$target" \
"cat ${ROCKNIX_STORAGE}/device_info_ROCKNIX_*.txt 2>/dev/null" 2>/dev/null || true)
else
device_info=$(ssh -o ConnectTimeout=5 "$target" \
"cat ${ROCKNIX_STORAGE}/device_info_ROCKNIX_*.txt 2>/dev/null" 2>/dev/null || true)
fi
if [[ -n "$device_info" ]]; then
local device_name
device_name=$(echo "$device_info" | grep "^DEVICE_NAME=" | cut -d'=' -f2 | tr -d '"')
local cfw_version
cfw_version=$(echo "$device_info" | grep "^CFW_VERSION=" | cut -d'=' -f2 | tr -d '"')
if [[ -n "$device_name" ]]; then
log INFO "Device: ${device_name} (ROCKNIX ${cfw_version})" >&2
echo "${device_name}"
return 0
fi
fi
log WARN "Could not detect device" >&2
echo "Unknown"
return 1
}
# Create temporary directory
create_tmp_dir() {
local prefix="${1:-rocknix-backup}"
local tmp_dir
tmp_dir=$(mktemp -d "/tmp/${prefix}-XXXXXX")
echo "$tmp_dir"
}
# Clean up temporary directory
cleanup_tmp() {
local tmp_dir="$1"
if [[ -d "$tmp_dir" ]]; then
rm -rf "$tmp_dir"
fi
}
# Get human-readable size
human_size() {
local bytes="$1"
if [[ $bytes -ge 1073741824 ]]; then
echo "$(echo "scale=1; $bytes/1073741824" | bc)G"
elif [[ $bytes -ge 1048576 ]]; then
echo "$(echo "scale=1; $bytes/1048576" | bc)M"
elif [[ $bytes -ge 1024 ]]; then
echo "$(echo "scale=1; $bytes/1024" | bc)K"
else
echo "${bytes}B"
fi
}
# Filter device-specific settings from file
filter_specific_settings() {
local input_file="$1"
local output_file="$2"
cp "$input_file" "$output_file"
for setting in "${GENERIC_SETTINGS_FILTER[@]}"; do
sed -i "/^${setting} = /d" "$output_file"
done
}
# Show contents of a backup file
show_backup_contents() {
local backup_file="$1"
if [[ ! -f "$backup_file" ]]; then
log ERROR "File not found: $backup_file"
return 1
fi
echo ""
log INFO "Backup contents:"
tar -tzf "$backup_file" | head -50
local total_files
total_files=$(tar -tzf "$backup_file" | wc -l)
local total_size
total_size=$(du -h "$backup_file" | cut -f1)
echo ""
echo -e "${CYAN}Total: ${total_files} archivos, ${total_size}${NC}"
}

393
restore-rocknix.sh Executable file
View File

@@ -0,0 +1,393 @@
#!/usr/bin/env bash
# restore-rocknix.sh - Restore script for ROCKNIX configurations
#
# Usage:
# ./restore-rocknix.sh [OPTIONS]
#
# Options:
# --host HOST Target console host
# --user USER SSH user (default: root)
# --backup FILE .tar.gz backup file to restore
# --all Restore all components without prompting
# --help Show help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib-common.sh"
# Default values
BACKUP_FILE=""
RESTORE_ALL=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--host)
ROCKNIX_HOST="$2"
shift 2
;;
--user)
ROCKNIX_USER="$2"
shift 2
;;
--password)
ROCKNIX_PASSWORD="$2"
shift 2
;;
--backup)
BACKUP_FILE="$2"
shift 2
;;
--all)
RESTORE_ALL=true
shift
;;
--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --host HOST Target console host"
echo " --user USER SSH user (default: $ROCKNIX_USER)"
echo " --password PASS SSH password"
echo " --backup FILE .tar.gz backup file to restore"
echo " --all Restore all components"
echo " --help Show this help"
exit 0
;;
*)
log ERROR "Unknown option: $1"
exit 1
;;
esac
done
# Check dependencies
check_deps || exit 1
# Ask for password if not configured
ask_password || exit 1
# If no backup specified, list available ones
if [[ -z "$BACKUP_FILE" ]]; then
log INFO "Searching for backups in ${BACKUP_DIR}..."
if [[ ! -d "$BACKUP_DIR" ]]; then
log ERROR "Backup directory not found: $BACKUP_DIR"
log INFO "Run first: ./backup-rocknix.sh"
exit 1
fi
BACKUPS=($(find "$BACKUP_DIR" -name "rocknix-config-*.tar.gz" -type f | sort -r))
if [[ ${#BACKUPS[@]} -eq 0 ]]; then
log ERROR "No backups found in ${BACKUP_DIR}"
log INFO "Run first: ./backup-rocknix.sh"
exit 1
fi
echo ""
echo -e "${CYAN}Available backups:${NC}"
echo ""
for i in "${!BACKUPS[@]}"; do
local backup="${BACKUPS[$i]}"
local name=$(basename "$backup")
local size=$(du -h "$backup" | cut -f1)
local date=$(stat -c %y "$backup" 2>/dev/null | cut -d'.' -f1)
echo -e " ${GREEN}$((i+1))${NC}) ${name}"
echo -e " Size: ${size} | Date: ${date}"
done
echo ""
read -p "Select backup (1-${#BACKUPS[@]}): " choice
if [[ "$choice" -lt 1 || "$choice" -gt ${#BACKUPS[@]} ]]; then
log ERROR "Invalid selection"
exit 1
fi
BACKUP_FILE="${BACKUPS[$((choice-1))]}"
fi
# Verify backup file exists
if [[ ! -f "$BACKUP_FILE" ]]; then
log ERROR "Backup file not found: $BACKUP_FILE"
exit 1
fi
# Show backup contents
show_backup_contents "$BACKUP_FILE"
# Verify connection with target console
echo ""
ssh_check "$ROCKNIX_HOST" "$ROCKNIX_USER" || exit 1
# Detect target device
DEVICE=$(detect_device "$ROCKNIX_HOST" "$ROCKNIX_USER" 2>/dev/null || echo "unknown")
log INFO "Target device: ${DEVICE}"
# Check backup type
BACKUP_NAME=$(basename "$BACKUP_FILE")
if [[ "$BACKUP_NAME" == *"generic"* ]]; then
log WARN "This is a GENERIC backup (without device-specific settings)"
log WARN "Make sure the target device is compatible"
echo ""
read -p "Continue? (y/N): " confirm
if [[ "$confirm" != "s" && "$confirm" != "S" ]]; then
log INFO "Restore cancelled"
exit 0
fi
fi
# Create temporary directory
TMP_DIR=$(create_tmp_dir "rocknix-restore")
trap 'cleanup_tmp "$TMP_DIR"' EXIT
# Extract backup
log INFO "Extracting backup..."
tar -xzf "$BACKUP_FILE" -C "$TMP_DIR"
# Select components to restore
if [[ "$RESTORE_ALL" == "false" ]]; then
echo ""
echo -e "${CYAN}Which components do you want to restore?${NC}"
echo ""
COMPONENTS=(
"retroarch-core-options:RetroArch core-options"
"retroarch-opt:RetroArch .opt (core options)"
"retroarch-rmp:RetroArch .rmp (remaps)"
"retroarch-config:RetroArch overrides (config/)"
"shaders:Shaders"
"ppsspp:PPSSPP"
"drastic:DraStic"
"other-emu:Other emulators"
)
SELECTION=()
for i in "${!COMPONENTS[@]}"; do
IFS=':' read -r key label <<< "${COMPONENTS[$i]}"
# Determine if there are files of this type
case "$key" in
retroarch-core-options)
[[ -f "${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg" ]] && available=true || available=false
;;
retroarch-opt)
find "${TMP_DIR}/.config/retroarch/" -maxdepth 1 -name "*.opt" -type f 2>/dev/null | grep -q . && available=true || available=false
;;
retroarch-rmp)
find "${TMP_DIR}/.config/retroarch/" -maxdepth 1 -name "*.rmp" -type f 2>/dev/null | grep -q . && available=true || available=false
;;
retroarch-config)
[[ -d "${TMP_DIR}/.config/retroarch/config/" ]] && available=true || available=false
;;
shaders)
[[ -d "${TMP_DIR}/shaders/" ]] && available=true || available=false
;;
ppsspp)
[[ -f "${TMP_DIR}/.config/ppsspp/PSP/SYSTEM/ppsspp.ini" ]] && available=true || available=false
;;
drastic)
[[ -d "${TMP_DIR}/.config/drastic/config/" ]] && available=true || available=false
;;
other-emu)
[[ -d "${TMP_DIR}/.config/game/" ]] || \
[[ -d "${TMP_DIR}/.config/flycast/" ]] || \
[[ -f "${TMP_DIR}/.config/duckstation/settings.ini" ]] || \
[[ -f "${TMP_DIR}/.config/yabasanshiro/.config" ]] && available=true || available=false
;;
esac
if [[ "$available" == "true" ]]; then
SELECTION+=("$key" "$label" "on")
else
SELECTION+=("$key" "$label" "off")
fi
done
CHOICES=$(dialog --checklist "Components to restore:" \
20 60 12 \
"${SELECTION[@]}" \
3>&1 1>&2 2>&3)
# If user cancels
if [[ $? -ne 0 ]]; then
log INFO "Restore cancelled"
exit 0
fi
else
# Restore all
CHOICES="retroarch-core-options retroarch-opt retroarch-rmp retroarch-config shaders ppsspp drastic other-emu"
fi
# Confirm restore
echo ""
echo -e "${YELLOW}Restore the following components on ${ROCKNIX_HOST}?${NC}"
for choice in $CHOICES; do
case "$choice" in
retroarch-core-options) echo " - RetroArch core-options" ;;
retroarch-opt) echo " - RetroArch .opt" ;;
retroarch-rmp) echo " - RetroArch .rmp" ;;
retroarch-config) echo " - RetroArch overrides" ;;
shaders) echo " - Shaders" ;;
ppsspp) echo " - PPSSPP" ;;
drastic) echo " - DraStic" ;;
other-emu) echo " - Other emulators" ;;
esac
done
echo ""
read -p "Confirm restore? (y/N): " confirm
if [[ "$confirm" != "s" && "$confirm" != "S" ]]; then
log INFO "Restore cancelled"
exit 0
fi
# Create safety backup on target console
log INFO "Creating safety backup on console..."
SAFETY_DIR="${TMP_DIR}/safety"
mkdir -p "$SAFETY_DIR"
# Download current files before overwriting
for choice in $CHOICES; do
case "$choice" in
retroarch-core-options)
download_file ".config/retroarch/retroarch-core-options.cfg" \
"${SAFETY_DIR}/retroarch-core-options.cfg" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" 2>/dev/null || true
;;
# Add others if needed
esac
done
log INFO "Safety backup created"
# Restore files
log INFO "Restoring files..."
RESTORED=0
FAILED=0
for choice in $CHOICES; do
log INFO "Restoring: ${choice}..."
case "$choice" in
retroarch-core-options)
if [[ -f "${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg" ]]; then
if upload_file "${TMP_DIR}/.config/retroarch/retroarch-core-options.cfg" \
".config/retroarch/retroarch-core-options.cfg" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
RESTORED=$((RESTORED + 1))
else
FAILED=$((FAILED + 1))
log WARN "Error restoring retroarch-core-options.cfg"
fi
fi
;;
retroarch-opt)
for opt_file in "${TMP_DIR}/.config/retroarch/"*.opt; do
if [[ -f "$opt_file" ]]; then
filename=$(basename "$opt_file")
if upload_file "$opt_file" \
".config/retroarch/${filename}" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
RESTORED=$((RESTORED + 1))
else
FAILED=$((FAILED + 1))
fi
fi
done
;;
retroarch-rmp)
for rmp_file in "${TMP_DIR}/.config/retroarch/"*.rmp; do
if [[ -f "$rmp_file" ]]; then
filename=$(basename "$rmp_file")
if upload_file "$rmp_file" \
".config/retroarch/${filename}" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
RESTORED=$((RESTORED + 1))
else
FAILED=$((FAILED + 1))
fi
fi
done
;;
retroarch-config)
if [[ -d "${TMP_DIR}/.config/retroarch/config/" ]]; then
if upload_dir "${TMP_DIR}/.config/retroarch/config/" \
".config/retroarch/config/" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
RESTORED=$((RESTORED + 1))
else
FAILED=$((FAILED + 1))
log WARN "Error restoring config/"
fi
fi
;;
shaders)
if [[ -d "${TMP_DIR}/shaders/" ]]; then
if upload_dir "${TMP_DIR}/shaders/" \
"shaders/" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
RESTORED=$((RESTORED + 1))
else
FAILED=$((FAILED + 1))
log WARN "Error restoring shaders/"
fi
fi
;;
ppsspp)
if [[ -f "${TMP_DIR}/.config/ppsspp/PSP/SYSTEM/ppsspp.ini" ]]; then
upload_file "${TMP_DIR}/.config/ppsspp/PSP/SYSTEM/ppsspp.ini" \
".config/ppsspp/PSP/SYSTEM/ppsspp.ini" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" 2>/dev/null && RESTORED=$((RESTORED + 1)) || FAILED=$((FAILED + 1))
fi
if [[ -f "${TMP_DIR}/.config/ppsspp/PSP/SYSTEM/controls.ini" ]]; then
upload_file "${TMP_DIR}/.config/ppsspp/PSP/SYSTEM/controls.ini" \
".config/ppsspp/PSP/SYSTEM/controls.ini" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" 2>/dev/null && RESTORED=$((RESTORED + 1)) || FAILED=$((FAILED + 1))
fi
;;
drastic)
if [[ -d "${TMP_DIR}/.config/drastic/config/" ]]; then
if upload_dir "${TMP_DIR}/.config/drastic/config/" \
".config/drastic/config/" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" &>/dev/null; then
RESTORED=$((RESTORED + 1))
else
FAILED=$((FAILED + 1))
log WARN "Error restoring drastic/config/"
fi
fi
;;
other-emu)
for emu_path in ".config/game/configs" ".config/flycast" ".config/duckstation" ".config/yabasanshiro"; do
if [[ -e "${TMP_DIR}/${emu_path}" ]]; then
upload_dir "${TMP_DIR}/${emu_path}/" "${emu_path}/" \
"$ROCKNIX_HOST" "$ROCKNIX_USER" 2>/dev/null && RESTORED=$((RESTORED + 1)) || FAILED=$((FAILED + 1))
fi
done
;;
esac
done
# Clean up temporary safety backup
cleanup_tmp "${SAFETY_DIR}" 2>/dev/null || true
# Summary
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Restore completed${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e " Device: ${CYAN}${DEVICE}${NC}"
echo -e " Restored: ${CYAN}${RESTORED}${NC}"
if [[ $FAILED -gt 0 ]]; then
echo -e " Failed: ${RED}${FAILED}${NC}"
fi
echo ""
echo -e " ${YELLOW}Note: Restart the console to apply changes${NC}"
echo ""