secubox-openwrt/package/secubox/secubox-app-config-vault/files/usr/sbin/configvaultctl
CyberMind-FR ec4aadbaa3 feat(config-vault): Add Configuration Vault system with Gitea sync
New packages:
- secubox-app-config-vault: Git-based config versioning CLI (configvaultctl)
- luci-app-config-vault: KISS-themed dashboard with status rings

Features:
- 9 configuration modules (users, network, services, security, etc.)
- Auto-commit and auto-push to private Gitea repository
- Export/import clone tarballs for device provisioning
- Commit history browser with restore capability

Also adds System Hardware Report to secubox-app-reporter:
- CPU/Memory/Disk/Temperature gauges with animations
- Environmental impact card (power/kWh/CO₂ estimates)
- Health recommendations based on system metrics
- Debug log viewer with severity highlighting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-13 12:49:33 +01:00

716 lines
17 KiB
Bash

#!/bin/sh
# SecuBox Configuration Vault - Versioned config backup with Gitea sync
# Supports cloning, deployment templates, and audit trail for certifications
. /lib/functions.sh
VAULT_PATH=""
GITEA_URL=""
GITEA_REPO=""
GITEA_BRANCH=""
GITEA_TOKEN=""
AUTO_COMMIT=""
AUTO_PUSH=""
# Module tracking
MODULES=""
# Load configuration
load_config() {
config_load config-vault
config_get VAULT_PATH global vault_path "/srv/config-vault"
config_get AUTO_COMMIT global auto_commit "1"
config_get AUTO_PUSH global auto_push "1"
config_get GITEA_URL gitea url ""
config_get GITEA_REPO gitea repo ""
config_get GITEA_BRANCH gitea branch "main"
# Get token from gitea config (shared)
GITEA_TOKEN=$(uci -q get gitea.main.api_token)
}
# Initialize vault repository
cmd_init() {
load_config
echo "Initializing Configuration Vault..."
# Create vault directory
mkdir -p "$VAULT_PATH"
cd "$VAULT_PATH" || exit 1
# Check if already initialized
if [ -d ".git" ]; then
echo "Vault already initialized at $VAULT_PATH"
return 0
fi
# Initialize git repo
git init
git config user.name "SecuBox Vault"
git config user.email "vault@secubox.local"
# Create directory structure for modules
config_load config-vault
config_foreach create_module_dir module
# Create README
cat > README.md << 'EOF'
# SecuBox Configuration Vault
Versioned configuration backups for SecuBox appliance.
## Structure
Each module has its own directory containing:
- `uci/` - UCI configuration exports (key=value format)
- `json/` - JSON exports for portability
- `flat/` - Flat file backups (certificates, keys, etc.)
## Modules
| Module | Description |
|--------|-------------|
| users | User Management & SSO |
| network | Network Configuration |
| services | Service Exposure & Distribution |
| security | Security & WAF |
| system | System Settings |
| containers | LXC Containers |
| reporter | Report Generator |
| dns | DNS & Domains |
| mesh | P2P Mesh Network |
## Usage
```bash
# Backup all modules
configvaultctl backup
# Backup specific module
configvaultctl backup users
# Restore from backup
configvaultctl restore users
# Clone to new device
configvaultctl export-clone > secubox-clone.tar.gz
# Push to Gitea
configvaultctl push
```
## Certification Compliance
All changes are versioned with timestamps and commit messages for audit trail.
EOF
# Create .gitignore
cat > .gitignore << 'EOF'
*.tmp
*.log
.DS_Store
EOF
# Initial commit
git add -A
git commit -m "Initialize SecuBox Configuration Vault
System: $(cat /etc/openwrt_release | grep DISTRIB_ID | cut -d= -f2 | tr -d "'")
Version: $(cat /etc/openwrt_release | grep DISTRIB_RELEASE | cut -d= -f2 | tr -d "'")
Hostname: $(uci -q get system.@system[0].hostname)
Date: $(date -Iseconds)"
# Setup remote if configured
if [ -n "$GITEA_URL" ] && [ -n "$GITEA_REPO" ]; then
local clone_url="${GITEA_URL}/${GITEA_REPO}.git"
git remote add origin "$clone_url" 2>/dev/null || git remote set-url origin "$clone_url"
echo "Remote configured: $clone_url"
fi
echo "Vault initialized at $VAULT_PATH"
}
# Create module directory structure
create_module_dir() {
local section="$1"
local enabled description
config_get enabled "$section" enabled "1"
[ "$enabled" = "1" ] || return
config_get description "$section" description "$section"
mkdir -p "$VAULT_PATH/$section/uci"
mkdir -p "$VAULT_PATH/$section/json"
mkdir -p "$VAULT_PATH/$section/flat"
# Create module manifest
cat > "$VAULT_PATH/$section/manifest.json" << EOF
{
"module": "$section",
"description": "$description",
"created": "$(date -Iseconds)",
"configs": []
}
EOF
}
# Backup a single UCI config
backup_uci_config() {
local config="$1"
local module="$2"
local uci_file="/etc/config/$config"
[ -f "$uci_file" ] || return 0
# UCI format backup
cp "$uci_file" "$VAULT_PATH/$module/uci/$config"
# JSON format backup
local json_out="$VAULT_PATH/$module/json/${config}.json"
uci export "$config" 2>/dev/null | uci_to_json > "$json_out"
}
# Convert UCI output to JSON (simplified)
uci_to_json() {
awk '
BEGIN {
print "{"
first_section = 1
}
/^package/ {
gsub(/'\''/, "", $2)
printf " \"package\": \"%s\",\n", $2
printf " \"sections\": [\n"
}
/^config/ {
if (!first_section) print " },"
first_section = 0
gsub(/'\''/, "", $2)
gsub(/'\''/, "", $3)
printf " {\n \"type\": \"%s\",\n \"name\": \"%s\",\n \"options\": {\n", $2, $3
first_opt = 1
}
/option|list/ {
if (!first_opt) print ","
first_opt = 0
gsub(/'\''/, "", $2)
gsub(/'\''/, "\"", $3)
# Handle multi-word values
$1 = ""; $2 = ""
gsub(/^ +/, "")
gsub(/'\''/, "")
printf " \"%s\": \"%s\"", $2, $0
}
END {
if (!first_section) {
print "\n }\n }"
}
print "\n ]\n}"
}
' 2>/dev/null || echo '{"error": "parse_failed"}'
}
# Backup module
backup_module() {
local module="$1"
local configs description
config_load config-vault
local enabled
config_get enabled "$module" enabled "1"
[ "$enabled" = "1" ] || return
config_get description "$module" description "$module"
echo "Backing up module: $module ($description)"
# Create directories
mkdir -p "$VAULT_PATH/$module/uci"
mkdir -p "$VAULT_PATH/$module/json"
mkdir -p "$VAULT_PATH/$module/flat"
# Get list of configs for this module
config_list_foreach "$module" config backup_config_item "$module"
# Update manifest
cat > "$VAULT_PATH/$module/manifest.json" << EOF
{
"module": "$module",
"description": "$description",
"backed_up": "$(date -Iseconds)",
"hostname": "$(uci -q get system.@system[0].hostname)"
}
EOF
}
backup_config_item() {
local config="$1"
local module="$2"
backup_uci_config "$config" "$module"
}
# Main backup command
cmd_backup() {
local target="$1"
load_config
cd "$VAULT_PATH" || { echo "Vault not initialized. Run: configvaultctl init"; exit 1; }
echo "Starting configuration backup..."
echo "Timestamp: $(date -Iseconds)"
echo ""
config_load config-vault
if [ -n "$target" ]; then
# Backup specific module
backup_module "$target"
else
# Backup all modules
config_foreach backup_module module
fi
# Backup additional flat files
backup_flat_files
# Auto-commit if enabled
if [ "$AUTO_COMMIT" = "1" ]; then
local changes=$(git status --porcelain | wc -l)
if [ "$changes" -gt 0 ]; then
git add -A
git commit -m "Config backup: $(date '+%Y-%m-%d %H:%M')
Modules: $(ls -d */ 2>/dev/null | tr -d '/' | tr '\n' ' ')
Changes: $changes files
Source: $(uci -q get system.@system[0].hostname)"
echo ""
echo "Changes committed: $changes files"
# Auto-push if enabled
if [ "$AUTO_PUSH" = "1" ] && [ -n "$GITEA_URL" ]; then
cmd_push
fi
else
echo "No changes detected."
fi
fi
}
# Backup important flat files
backup_flat_files() {
echo "Backing up flat files..."
# Users - export to JSON
if [ -x /usr/sbin/secubox-users ]; then
mkdir -p "$VAULT_PATH/users/flat"
/usr/sbin/secubox-users list --json > "$VAULT_PATH/users/flat/users.json" 2>/dev/null || true
fi
# SSH keys
mkdir -p "$VAULT_PATH/system/flat/ssh"
[ -f /etc/dropbear/authorized_keys ] && cp /etc/dropbear/authorized_keys "$VAULT_PATH/system/flat/ssh/" 2>/dev/null
# SSL certificates (public only)
mkdir -p "$VAULT_PATH/security/flat/certs"
for cert in /etc/ssl/certs/*.crt /etc/acme/*.cer; do
[ -f "$cert" ] && cp "$cert" "$VAULT_PATH/security/flat/certs/" 2>/dev/null
done
# HAProxy configs
mkdir -p "$VAULT_PATH/services/flat"
[ -f /etc/haproxy.cfg ] && cp /etc/haproxy.cfg "$VAULT_PATH/services/flat/" 2>/dev/null
# Container definitions
mkdir -p "$VAULT_PATH/containers/flat"
for cfg in /srv/lxc/*/config; do
[ -f "$cfg" ] && {
local name=$(dirname "$cfg" | xargs basename)
cp "$cfg" "$VAULT_PATH/containers/flat/${name}.config" 2>/dev/null
}
done
}
# Push to Gitea
cmd_push() {
load_config
cd "$VAULT_PATH" || exit 1
if [ -z "$GITEA_URL" ] || [ -z "$GITEA_TOKEN" ]; then
echo "Error: Gitea not configured"
return 1
fi
echo "Pushing to Gitea..."
# Configure credential helper for this push
local auth_url=$(echo "$GITEA_URL" | sed "s|://|://oauth2:${GITEA_TOKEN}@|")
git remote set-url origin "${auth_url}/${GITEA_REPO}.git"
git push -u origin "$GITEA_BRANCH" 2>&1
local result=$?
# Reset URL without token
git remote set-url origin "${GITEA_URL}/${GITEA_REPO}.git"
if [ $result -eq 0 ]; then
echo "Successfully pushed to Gitea"
else
echo "Push failed (code: $result)"
fi
return $result
}
# Pull from Gitea
cmd_pull() {
load_config
cd "$VAULT_PATH" || exit 1
echo "Pulling from Gitea..."
local auth_url=$(echo "$GITEA_URL" | sed "s|://|://oauth2:${GITEA_TOKEN}@|")
git remote set-url origin "${auth_url}/${GITEA_REPO}.git"
git pull origin "$GITEA_BRANCH" 2>&1
local result=$?
git remote set-url origin "${GITEA_URL}/${GITEA_REPO}.git"
return $result
}
# Restore module
cmd_restore() {
local module="$1"
load_config
cd "$VAULT_PATH" || exit 1
if [ -z "$module" ]; then
echo "Usage: configvaultctl restore <module>"
echo "Available modules:"
ls -d */ 2>/dev/null | tr -d '/'
return 1
fi
[ -d "$module" ] || { echo "Module not found: $module"; return 1; }
echo "Restoring module: $module"
echo "WARNING: This will overwrite current configurations!"
echo ""
# Restore UCI configs
for uci_file in "$module/uci/"*; do
[ -f "$uci_file" ] || continue
local config=$(basename "$uci_file")
echo " Restoring /etc/config/$config"
cp "$uci_file" "/etc/config/$config"
done
echo ""
echo "Restored. Run 'reload_config' or reboot to apply changes."
}
# Export clone package
cmd_export_clone() {
local output="${1:-/tmp/secubox-clone-$(date +%Y%m%d).tar.gz}"
load_config
# First do a backup
cmd_backup
cd "$VAULT_PATH" || exit 1
echo "Creating clone package: $output"
# Create clone manifest
cat > clone-manifest.json << EOF
{
"type": "secubox-clone",
"version": "1.0",
"created": "$(date -Iseconds)",
"source": {
"hostname": "$(uci -q get system.@system[0].hostname)",
"model": "$(cat /tmp/sysinfo/model 2>/dev/null || echo 'unknown')",
"version": "$(cat /etc/openwrt_release | grep DISTRIB_RELEASE | cut -d= -f2 | tr -d "'")"
},
"modules": [
$(ls -d */ 2>/dev/null | tr -d '/' | while read m; do echo " \"$m\","; done | sed '$ s/,$//')
]
}
EOF
# Create tarball
tar -czf "$output" -C "$VAULT_PATH" .
echo "Clone package created: $output"
echo "Size: $(ls -lh "$output" | awk '{print $5}')"
}
# Import clone package
cmd_import_clone() {
local archive="$1"
[ -f "$archive" ] || { echo "File not found: $archive"; return 1; }
load_config
echo "Importing clone package: $archive"
# Extract to vault
mkdir -p "$VAULT_PATH"
tar -xzf "$archive" -C "$VAULT_PATH"
# Show manifest
if [ -f "$VAULT_PATH/clone-manifest.json" ]; then
echo ""
echo "Clone source:"
jsonfilter -i "$VAULT_PATH/clone-manifest.json" -e '@.source.hostname' | xargs echo " Hostname:"
jsonfilter -i "$VAULT_PATH/clone-manifest.json" -e '@.source.version' | xargs echo " Version:"
fi
echo ""
echo "Import complete. Use 'configvaultctl restore <module>' to apply configs."
}
# Show status
cmd_status() {
load_config
echo "SecuBox Configuration Vault"
echo "==========================="
echo ""
echo "Vault Path: $VAULT_PATH"
echo "Auto-commit: $AUTO_COMMIT"
echo "Auto-push: $AUTO_PUSH"
echo ""
if [ -d "$VAULT_PATH/.git" ]; then
cd "$VAULT_PATH"
echo "Git Status:"
echo " Branch: $(git branch --show-current 2>/dev/null || echo 'unknown')"
echo " Remote: $(git remote get-url origin 2>/dev/null || echo 'not configured')"
echo " Last commit: $(git log -1 --format='%h %s' 2>/dev/null || echo 'none')"
echo " Changes: $(git status --porcelain 2>/dev/null | wc -l) uncommitted"
echo ""
echo "Modules:"
config_load config-vault
config_foreach show_module_status module
else
echo "Vault not initialized. Run: configvaultctl init"
fi
}
show_module_status() {
local section="$1"
local enabled description
config_get enabled "$section" enabled "1"
config_get description "$section" description "$section"
local status="disabled"
[ "$enabled" = "1" ] && status="enabled"
local files=0
[ -d "$VAULT_PATH/$section" ] && files=$(find "$VAULT_PATH/$section" -type f | wc -l)
printf " %-12s %-8s %3d files %s\n" "$section" "[$status]" "$files" "$description"
}
# Show history/changelog
cmd_history() {
local count="${1:-20}"
load_config
cd "$VAULT_PATH" || exit 1
echo "Configuration Change History"
echo "============================"
echo ""
git log --oneline -n "$count" --date=short --format="%h %ad %s"
}
# Show diff since last commit
cmd_diff() {
load_config
cd "$VAULT_PATH" || exit 1
git diff
}
# Track a LuCI config change (called by hook)
cmd_track() {
local config="$1"
local action="${2:-modified}"
local user="${3:-system}"
load_config
# Find which module this config belongs to
local module=""
config_load config-vault
find_module_for_config() {
local section="$1"
config_list_foreach "$section" config check_config_match "$config" "$section"
}
check_config_match() {
local cfg="$1"
local target="$2"
local mod="$3"
[ "$cfg" = "$target" ] && module="$mod"
}
config_foreach find_module_for_config module
[ -z "$module" ] && return 0 # Config not tracked
# Backup the changed config
backup_uci_config "$config" "$module"
# Commit the change
cd "$VAULT_PATH" || return 1
git add -A
git commit -m "LuCI change: $config ($action)
Module: $module
Config: $config
Action: $action
User: $user
Time: $(date -Iseconds)" 2>/dev/null
# Auto-push if enabled
[ "$AUTO_PUSH" = "1" ] && [ -n "$GITEA_URL" ] && cmd_push >/dev/null 2>&1 &
}
# List available modules
cmd_modules() {
load_config
echo "Configured Modules:"
echo ""
config_load config-vault
config_foreach list_module module
}
list_module() {
local section="$1"
local enabled description
config_get enabled "$section" enabled "1"
config_get description "$section" description ""
printf "%-12s " "$section"
[ "$enabled" = "1" ] && printf "[enabled] " || printf "[disabled]"
echo "$description"
# List configs
config_list_foreach "$section" config list_config_item
echo ""
}
list_config_item() {
local config="$1"
local exists=""
[ -f "/etc/config/$config" ] && exists="*" || exists=" "
echo " $exists $config"
}
# Usage
usage() {
cat << EOF
SecuBox Configuration Vault - Versioned config management
USAGE:
configvaultctl <command> [options]
COMMANDS:
init Initialize vault repository
backup [module] Backup configs (all or specific module)
restore <module> Restore module configs from vault
push Push changes to Gitea
pull Pull latest from Gitea
status Show vault status
history [n] Show last n config changes (default: 20)
diff Show uncommitted changes
modules List configured modules
track <config> Track a config change (used by hooks)
export-clone [file] Create deployment clone package
import-clone <file> Import clone package
EXAMPLES:
# Initialize and backup all
configvaultctl init
configvaultctl backup
# Backup specific module
configvaultctl backup users
# Create clone for new device
configvaultctl export-clone /tmp/secubox-v1.tar.gz
# Restore users on new device
configvaultctl import-clone /tmp/secubox-v1.tar.gz
configvaultctl restore users
AUDIT TRAIL:
All changes are versioned with git for certification compliance.
View history: configvaultctl history
EOF
}
# Main
case "$1" in
init)
cmd_init
;;
backup)
cmd_backup "$2"
;;
restore)
cmd_restore "$2"
;;
push)
cmd_push
;;
pull)
cmd_pull
;;
status)
cmd_status
;;
history)
cmd_history "$2"
;;
diff)
cmd_diff
;;
modules)
cmd_modules
;;
track)
cmd_track "$2" "$3" "$4"
;;
export-clone|export)
cmd_export_clone "$2"
;;
import-clone|import)
cmd_import_clone "$2"
;;
*)
usage
;;
esac