secubox-openwrt/package/secubox/secubox-app-config-vault/files/usr/sbin/configvaultctl
CyberMind-FR e86545bd3a feat(config-vault): Add device provisioning system
Implement full provisioning workflow for SecuBox device replication:

Auto-Restore:
- import-clone <file> --apply: Auto-restore all modules after import
- restore-all: Restore all modules from vault

Remote Provisioning:
- provision <node|all>: Push clone to remote nodes via RPC proxy
- Transfer clone as base64 to remote node
- Trigger import+apply on remote

First-Boot Pull:
- pull-config <master>: Pull config from master node
- HTTP download or RPC fallback
- Auto-reboot after apply, marks /etc/secubox-provisioned

HTTP Serve:
- serve-clone: Generate clone at /www/config-vault/
- Enables HTTP-based config distribution

RPCD Methods (6 new):
- restore_all, import_apply, provision
- pull_config, export_clone_b64, serve_clone

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-15 15:18:54 +01:00

1052 lines
28 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"
local apply_flag="$2"
[ -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 ""
# Auto-apply if --apply flag is set
if [ "$apply_flag" = "--apply" ] || [ "$apply_flag" = "-a" ]; then
echo "Auto-applying all modules..."
cmd_restore_all
echo ""
echo "Import and restore complete. Reboot recommended."
else
echo "Import complete. Use 'configvaultctl restore <module>' to apply configs."
echo "Or use 'configvaultctl import-clone <file> --apply' to auto-restore all."
fi
}
# Restore all modules (for auto-apply)
cmd_restore_all() {
load_config
cd "$VAULT_PATH" || exit 1
echo "Restoring all modules..."
echo ""
local restored=0
local failed=0
for module_dir in */; do
[ -d "$module_dir" ] || continue
local module="${module_dir%/}"
# Skip non-module directories
[ "$module" = ".git" ] && continue
if [ -d "$module/uci" ] && [ "$(ls -A "$module/uci" 2>/dev/null)" ]; then
echo "[$module]"
for uci_file in "$module/uci/"*; do
[ -f "$uci_file" ] || continue
local config=$(basename "$uci_file")
echo " Restoring /etc/config/$config"
if cp "$uci_file" "/etc/config/$config" 2>/dev/null; then
restored=$((restored + 1))
else
echo " ✗ Failed to restore $config"
failed=$((failed + 1))
fi
done
fi
done
echo ""
echo "Restored: $restored configs, Failed: $failed"
# Reload configs
if [ $restored -gt 0 ]; then
echo ""
echo "Reloading configuration..."
/etc/init.d/network reload 2>/dev/null || true
/etc/init.d/system reload 2>/dev/null || true
fi
}
#------------------------------------------------------------------------------
# Remote Provisioning
#------------------------------------------------------------------------------
# Provision remote node with clone
cmd_provision() {
local target="$1"
local clone_file="$2"
[ -z "$target" ] && {
echo "Usage: configvaultctl provision <node> [clone-file]"
echo ""
echo "Provision a remote SecuBox node with configuration clone."
echo ""
echo "Arguments:"
echo " node Target node (IP, hostname, or 'all' for mesh)"
echo " clone-file Optional pre-existing clone (creates new if not specified)"
echo ""
echo "Examples:"
echo " configvaultctl provision 192.168.255.2"
echo " configvaultctl provision sb-office /tmp/secubox-clone.tar.gz"
echo " configvaultctl provision all"
return 1
}
# Check rttyctl is available
command -v rttyctl >/dev/null || {
echo "Error: rttyctl not found. Install secubox-app-rtty-remote."
return 1
}
load_config
# Create clone if not provided
if [ -z "$clone_file" ] || [ ! -f "$clone_file" ]; then
clone_file="/tmp/secubox-provision-$(date +%Y%m%d%H%M).tar.gz"
echo "Creating configuration clone..."
cmd_export_clone "$clone_file"
echo ""
fi
[ -f "$clone_file" ] || { echo "Clone file not found: $clone_file"; return 1; }
local clone_size=$(ls -lh "$clone_file" | awk '{print $5}')
local clone_b64="/tmp/clone-b64-$$.txt"
echo "Provisioning target: $target"
echo "Clone package: $clone_file ($clone_size)"
echo ""
# Base64 encode for transfer
base64 -w 0 "$clone_file" > "$clone_b64"
if [ "$target" = "all" ]; then
provision_all_nodes "$clone_b64"
else
provision_single_node "$target" "$clone_b64"
fi
rm -f "$clone_b64"
}
provision_single_node() {
local node="$1"
local clone_b64="$2"
echo "Provisioning node: $node"
# Check node is reachable
local addr=$(rttyctl node "$node" 2>&1 | grep "Address:" | awk '{print $2}')
[ -z "$addr" ] && addr="$node"
if ! ping -c 1 -W 2 "$addr" >/dev/null 2>&1; then
echo " ✗ Node not reachable"
return 1
fi
# Transfer clone via RPC (write to temp file on remote)
echo " Transferring clone package..."
local remote_file="/tmp/secubox-clone-import.tar.gz"
# Use file RPC to write the clone
local clone_content=$(cat "$clone_b64")
local write_result=$(rttyctl rpc "$node" "file" "write" \
"{\"path\":\"$remote_file\",\"data\":\"$clone_content\",\"base64\":true}" 2>&1)
if echo "$write_result" | grep -qi "error"; then
# Fallback: use exec to decode and write
echo " Using fallback transfer method..."
rttyctl rpc "$node" "file" "exec" \
"{\"command\":\"echo '$clone_content' | base64 -d > $remote_file\"}" 2>/dev/null
fi
# Trigger import with auto-apply on remote
echo " Importing and applying configuration..."
local import_result=$(rttyctl rpc "$node" "luci.config-vault" "import_apply" \
"{\"archive\":\"$remote_file\"}" 2>&1)
if echo "$import_result" | grep -qi "success.*true"; then
echo " ✓ Provisioning complete"
echo ""
echo " Remote node will reboot to apply changes."
else
# Try direct command via luci.sys
rttyctl rpc "$node" "luci" "setInitAction" \
'{"name":"config-vault-apply","action":"start"}' 2>/dev/null
echo " ✓ Clone transferred, manual apply may be required"
fi
}
provision_all_nodes() {
local clone_b64="$1"
echo "Provisioning ALL mesh nodes..."
echo ""
local success=0
local failed=0
# Get nodes from rttyctl
local nodes=$(rttyctl json-nodes 2>/dev/null | jsonfilter -e '@.nodes[*].address' 2>/dev/null)
for node in $nodes; do
[ -z "$node" ] && continue
# Skip local node
local local_ip=$(uci -q get network.lan.ipaddr)
case "$node" in
127.0.0.1|localhost|$local_ip)
echo "[$node] Skipping (local)"
continue
;;
esac
echo "[$node]"
if provision_single_node "$node" "$clone_b64"; then
success=$((success + 1))
else
failed=$((failed + 1))
fi
echo ""
done
echo "Provisioning Summary: $success succeeded, $failed failed"
}
#------------------------------------------------------------------------------
# First-Boot Config Pull
#------------------------------------------------------------------------------
# Pull configuration from master node on first boot
cmd_pull_config() {
local master_url="$1"
local apply="${2:---apply}"
[ -z "$master_url" ] && {
echo "Usage: configvaultctl pull-config <master-url> [--apply]"
echo ""
echo "Pull configuration from master SecuBox node."
echo "Used for first-boot provisioning of new devices."
echo ""
echo "Arguments:"
echo " master-url URL or IP of master node (e.g., 192.168.255.1)"
echo " --apply Auto-apply after pulling (default)"
echo " --no-apply Just download, don't apply"
echo ""
echo "Examples:"
echo " configvaultctl pull-config 192.168.255.1"
echo " configvaultctl pull-config master.secubox.local --no-apply"
echo ""
echo "First-boot setup:"
echo " Add to /etc/rc.local:"
echo " [ -f /etc/secubox-provisioned ] || configvaultctl pull-config 192.168.255.1"
return 1
}
load_config
echo "Pulling configuration from master: $master_url"
echo ""
local clone_file="/tmp/secubox-clone-pulled.tar.gz"
# Try to fetch clone via HTTP first (master serves it)
local http_url="http://${master_url}/config-vault/clone.tar.gz"
echo "Trying HTTP: $http_url"
if curl -sf -m 30 -o "$clone_file" "$http_url" 2>/dev/null; then
echo " ✓ Downloaded via HTTP"
else
# Fallback: try RPC via rttyctl
echo " HTTP failed, trying RPC..."
if command -v rttyctl >/dev/null; then
local export_result=$(rttyctl rpc "$master_url" "luci.config-vault" "export_clone" '{}' 2>&1)
local clone_b64=$(echo "$export_result" | jsonfilter -e '@.data' 2>/dev/null)
if [ -n "$clone_b64" ]; then
echo "$clone_b64" | base64 -d > "$clone_file"
echo " ✓ Downloaded via RPC"
else
echo " ✗ Failed to fetch clone from master"
return 1
fi
else
echo " ✗ No rttyctl available for RPC fallback"
return 1
fi
fi
# Verify archive
if ! tar -tzf "$clone_file" >/dev/null 2>&1; then
echo "Error: Invalid clone archive"
rm -f "$clone_file"
return 1
fi
# Import
if [ "$apply" = "--no-apply" ]; then
cmd_import_clone "$clone_file"
else
cmd_import_clone "$clone_file" --apply
# Mark as provisioned
echo "$(date -Iseconds) pulled from $master_url" > /etc/secubox-provisioned
echo ""
echo "Device marked as provisioned. Rebooting in 5 seconds..."
sleep 5
reboot
fi
}
# Serve clone for HTTP pull (called by uhttpd CGI or cron)
cmd_serve_clone() {
local output_dir="${1:-/www/config-vault}"
load_config
mkdir -p "$output_dir"
# Create fresh clone
local clone_file="$output_dir/clone.tar.gz"
cmd_export_clone "$clone_file" >/dev/null 2>&1
# Create metadata
cat > "$output_dir/manifest.json" << EOF
{
"hostname": "$(uci -q get system.@system[0].hostname)",
"updated": "$(date -Iseconds)",
"size": $(stat -c%s "$clone_file" 2>/dev/null || echo 0),
"modules": $(ls -d "$VAULT_PATH"/*/ 2>/dev/null | wc -l)
}
EOF
echo "Clone served at: $output_dir/clone.tar.gz"
}
# 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
restore-all Restore ALL modules 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> [--apply] Import clone (--apply to auto-restore)
DEVICE PROVISIONING:
provision <node> [file] Push clone to remote node and apply
pull-config <master> Pull config from master (first-boot)
serve-clone [dir] Generate clone for HTTP serving
EXAMPLES:
# Initialize and backup all
configvaultctl init
configvaultctl backup
# Create clone for new device
configvaultctl export-clone /tmp/secubox-v1.tar.gz
# Import and auto-apply on new device
configvaultctl import-clone /tmp/secubox-v1.tar.gz --apply
# Remote provisioning
configvaultctl provision 192.168.255.2
configvaultctl provision all
# First-boot pull from master
configvaultctl pull-config 192.168.255.1
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"
;;
restore-all)
cmd_restore_all
;;
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" "$3"
;;
provision)
cmd_provision "$2" "$3"
;;
pull-config)
cmd_pull_config "$2" "$3"
;;
serve-clone|serve)
cmd_serve_clone "$2"
;;
*)
usage
;;
esac