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>
This commit is contained in:
parent
ac7912e0a1
commit
e86545bd3a
@ -5244,3 +5244,12 @@ git checkout HEAD -- index.html
|
||||
- 4 new RPCD methods: install_remote, install_mesh, deploy_ttyd, install_status
|
||||
- ACL permissions updated for remote installation write actions
|
||||
- Use case: Deploy ttyd web terminal to all SecuBox nodes for browser-based SSH
|
||||
|
||||
- **Device Provisioning System (Complete)**
|
||||
- Auto-Restore: `import-clone <file> --apply` - auto-restores all modules after import
|
||||
- Remote Provisioning: `provision <node|all>` - pushes clone to remote nodes via RPC
|
||||
- First-Boot Pull: `pull-config <master>` - pulls config from master on new device
|
||||
- HTTP Serve: `serve-clone` - generates clone at /www/config-vault/ for HTTP download
|
||||
- CLI commands: restore-all, provision, pull-config, serve-clone
|
||||
- 6 new RPCD methods: restore_all, import_apply, provision, pull_config, export_clone_b64, serve_clone
|
||||
- Use case: Zero-touch provisioning of new SecuBox devices from master configuration
|
||||
|
||||
@ -10,6 +10,16 @@ _Last updated: 2026-03-16 (DPI LAN Passive Analysis)_
|
||||
|
||||
### 2026-03-16
|
||||
|
||||
- **Device Provisioning System (Complete)**
|
||||
- **Auto-Restore**: `configvaultctl import-clone <file> --apply` auto-restores all modules
|
||||
- **Remote Provisioning**: `configvaultctl provision <node|all>` pushes clone to remote nodes
|
||||
- **First-Boot Pull**: `configvaultctl pull-config <master>` pulls config on new device boot
|
||||
- **HTTP Serve**: `configvaultctl serve-clone` generates clone for HTTP download
|
||||
- New CLI commands: restore-all, provision, pull-config, serve-clone
|
||||
- 6 new RPCD methods: restore_all, import_apply, provision, pull_config, export_clone_b64, serve_clone
|
||||
- ACL permissions updated for provisioning actions
|
||||
- Use case: Clone master SecuBox config to new devices automatically
|
||||
|
||||
- **Remote ttyd Deployment for Mesh Nodes (Complete)**
|
||||
- CLI commands: `rttyctl install`, `rttyctl install-status`, `rttyctl deploy-ttyd`
|
||||
- Installs packages on remote mesh nodes via RPC proxy
|
||||
@ -635,7 +645,7 @@ _Last updated: 2026-03-16 (DPI LAN Passive Analysis)_
|
||||
|
||||
### v1.0 Release Prep
|
||||
|
||||
1. **Device Provisioning** - Use Config Vault export-clone for SecuBox replication
|
||||
1. **LuCI Provisioning Dashboard** - Add provisioning UI to Config Vault dashboard (optional)
|
||||
2. **LuCI Remote Install Button** - Add "Deploy ttyd" action to Remote Control dashboard (optional)
|
||||
|
||||
### v1.1+ Extended Mesh
|
||||
|
||||
@ -243,22 +243,181 @@ handle_export_clone() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Provisioning Methods
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Import and auto-apply clone (for remote provisioning)
|
||||
handle_import_apply() {
|
||||
local archive output rc
|
||||
|
||||
read -r input
|
||||
json_load "$input"
|
||||
json_get_var archive archive
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$archive" ]; then
|
||||
json_add_boolean success 0
|
||||
json_add_string error "Archive path required"
|
||||
elif [ ! -f "$archive" ]; then
|
||||
json_add_boolean success 0
|
||||
json_add_string error "Archive not found: $archive"
|
||||
else
|
||||
output=$($VAULT_CTL import-clone "$archive" --apply 2>&1)
|
||||
rc=$?
|
||||
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
|
||||
json_add_string output "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Provision remote node
|
||||
handle_provision() {
|
||||
local target clone_file output rc
|
||||
|
||||
read -r input
|
||||
json_load "$input"
|
||||
json_get_var target target
|
||||
json_get_var clone_file clone_file
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$target" ]; then
|
||||
json_add_boolean success 0
|
||||
json_add_string error "Target node required"
|
||||
else
|
||||
output=$($VAULT_CTL provision "$target" "$clone_file" 2>&1)
|
||||
rc=$?
|
||||
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
|
||||
json_add_string output "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Pull config from master (first-boot)
|
||||
handle_pull_config() {
|
||||
local master apply output rc
|
||||
|
||||
read -r input
|
||||
json_load "$input"
|
||||
json_get_var master master
|
||||
json_get_var apply apply
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$master" ]; then
|
||||
json_add_boolean success 0
|
||||
json_add_string error "Master URL required"
|
||||
else
|
||||
[ "$apply" = "false" ] && apply="--no-apply" || apply="--apply"
|
||||
output=$($VAULT_CTL pull-config "$master" "$apply" 2>&1)
|
||||
rc=$?
|
||||
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
|
||||
json_add_string output "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Restore all modules
|
||||
handle_restore_all() {
|
||||
local output rc
|
||||
|
||||
json_init
|
||||
output=$($VAULT_CTL restore-all 2>&1)
|
||||
rc=$?
|
||||
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
|
||||
json_add_string output "$output"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Export clone as base64 (for RPC transfer)
|
||||
handle_export_clone_b64() {
|
||||
local path
|
||||
|
||||
path="/tmp/secubox-clone-rpc-$$.tar.gz"
|
||||
$VAULT_CTL export-clone "$path" >/dev/null 2>&1
|
||||
|
||||
json_init
|
||||
|
||||
if [ -f "$path" ]; then
|
||||
json_add_boolean success 1
|
||||
json_add_int size "$(stat -c%s "$path" 2>/dev/null || echo 0)"
|
||||
# Stream base64 to avoid memory issues
|
||||
local b64_data=$(base64 -w 0 "$path")
|
||||
json_add_string data "$b64_data"
|
||||
rm -f "$path"
|
||||
else
|
||||
json_add_boolean success 0
|
||||
json_add_string error "Failed to create clone"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Serve clone via HTTP (update /www/config-vault/)
|
||||
handle_serve_clone() {
|
||||
local output_dir output rc
|
||||
|
||||
read -r input
|
||||
json_load "$input"
|
||||
json_get_var output_dir output_dir
|
||||
[ -z "$output_dir" ] && output_dir="/www/config-vault"
|
||||
|
||||
json_init
|
||||
output=$($VAULT_CTL serve-clone "$output_dir" 2>&1)
|
||||
rc=$?
|
||||
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
|
||||
json_add_string output "$output"
|
||||
json_add_string url "/config-vault/clone.tar.gz"
|
||||
json_dump
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"status":{},"modules":{},"history":{"count":"int"},"diff":{},"backup":{"module":"str"},"restore":{"module":"str"},"push":{},"pull":{},"init":{},"export_clone":{"path":"str"}}'
|
||||
cat << 'EOF'
|
||||
{
|
||||
"status": {},
|
||||
"modules": {},
|
||||
"history": {"count": 20},
|
||||
"diff": {},
|
||||
"backup": {"module": "str"},
|
||||
"restore": {"module": "str"},
|
||||
"restore_all": {},
|
||||
"push": {},
|
||||
"pull": {},
|
||||
"init": {},
|
||||
"export_clone": {"path": "str"},
|
||||
"export_clone_b64": {},
|
||||
"import_apply": {"archive": "str"},
|
||||
"provision": {"target": "str", "clone_file": "str"},
|
||||
"pull_config": {"master": "str", "apply": true},
|
||||
"serve_clone": {"output_dir": "str"}
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
status) handle_status ;;
|
||||
modules) handle_modules ;;
|
||||
history) handle_history ;;
|
||||
diff) handle_diff ;;
|
||||
backup) handle_backup ;;
|
||||
restore) handle_restore ;;
|
||||
push) handle_push ;;
|
||||
pull) handle_pull ;;
|
||||
init) handle_init ;;
|
||||
export_clone) handle_export_clone ;;
|
||||
status) handle_status ;;
|
||||
modules) handle_modules ;;
|
||||
history) handle_history ;;
|
||||
diff) handle_diff ;;
|
||||
backup) handle_backup ;;
|
||||
restore) handle_restore ;;
|
||||
restore_all) handle_restore_all ;;
|
||||
push) handle_push ;;
|
||||
pull) handle_pull ;;
|
||||
init) handle_init ;;
|
||||
export_clone) handle_export_clone ;;
|
||||
export_clone_b64) handle_export_clone_b64 ;;
|
||||
import_apply) handle_import_apply ;;
|
||||
provision) handle_provision ;;
|
||||
pull_config) handle_pull_config ;;
|
||||
serve_clone) handle_serve_clone ;;
|
||||
*) echo '{"error":"Unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -3,13 +3,31 @@
|
||||
"description": "Configuration Vault Management",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.config-vault": ["status", "modules", "history", "diff"]
|
||||
"luci.config-vault": [
|
||||
"status",
|
||||
"modules",
|
||||
"history",
|
||||
"diff",
|
||||
"export_clone_b64"
|
||||
]
|
||||
},
|
||||
"uci": ["config-vault"]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.config-vault": ["backup", "restore", "push", "pull", "init", "export_clone"]
|
||||
"luci.config-vault": [
|
||||
"backup",
|
||||
"restore",
|
||||
"restore_all",
|
||||
"push",
|
||||
"pull",
|
||||
"init",
|
||||
"export_clone",
|
||||
"import_apply",
|
||||
"provision",
|
||||
"pull_config",
|
||||
"serve_clone"
|
||||
]
|
||||
},
|
||||
"uci": ["config-vault"]
|
||||
}
|
||||
|
||||
@ -457,6 +457,7 @@ EOF
|
||||
# Import clone package
|
||||
cmd_import_clone() {
|
||||
local archive="$1"
|
||||
local apply_flag="$2"
|
||||
|
||||
[ -f "$archive" ] || { echo "File not found: $archive"; return 1; }
|
||||
|
||||
@ -477,7 +478,321 @@ cmd_import_clone() {
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Import complete. Use 'configvaultctl restore <module>' to apply configs."
|
||||
|
||||
# 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
|
||||
@ -637,33 +952,42 @@ 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
|
||||
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
|
||||
|
||||
# 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
|
||||
# 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.
|
||||
@ -682,6 +1006,9 @@ case "$1" in
|
||||
restore)
|
||||
cmd_restore "$2"
|
||||
;;
|
||||
restore-all)
|
||||
cmd_restore_all
|
||||
;;
|
||||
push)
|
||||
cmd_push
|
||||
;;
|
||||
@ -707,7 +1034,16 @@ case "$1" in
|
||||
cmd_export_clone "$2"
|
||||
;;
|
||||
import-clone|import)
|
||||
cmd_import_clone "$2"
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user