secubox-openwrt/package/secubox/luci-app-config-vault/root/usr/libexec/rpcd/luci.config-vault
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

424 lines
11 KiB
Bash

#!/bin/sh
# RPCD backend for Configuration Vault
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
VAULT_CTL="/usr/sbin/configvaultctl"
handle_status() {
local enabled vault_path auto_commit auto_push gitea_url gitea_repo
json_init
config_load config-vault
config_get enabled global enabled "0"
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 ""
json_add_boolean enabled "$enabled"
json_add_string vault_path "$vault_path"
json_add_boolean auto_commit "$auto_commit"
json_add_boolean auto_push "$auto_push"
json_add_string gitea_url "$gitea_url"
json_add_string gitea_repo "$gitea_repo"
if [ -d "$vault_path/.git" ]; then
cd "$vault_path"
json_add_boolean initialized 1
json_add_string branch "$(git branch --show-current 2>/dev/null)"
json_add_string last_commit "$(git log -1 --format='%h' 2>/dev/null)"
json_add_string last_commit_date "$(git log -1 --format='%ci' 2>/dev/null)"
json_add_string last_commit_msg "$(git log -1 --format='%s' 2>/dev/null)"
json_add_int uncommitted "$(git status --porcelain 2>/dev/null | wc -l)"
json_add_int total_commits "$(git rev-list --count HEAD 2>/dev/null || echo 0)"
else
json_add_boolean initialized 0
fi
json_dump
}
add_config_json() {
local cfg="$1"
json_add_object
json_add_string name "$cfg"
[ -f "/etc/config/$cfg" ] && json_add_boolean exists 1 || json_add_boolean exists 0
json_close_object
}
list_module_json() {
local section="$1"
local enabled description files last_backup
config_get enabled "$section" enabled "1"
config_get description "$section" description ""
json_add_object
json_add_string name "$section"
json_add_string description "$description"
json_add_boolean enabled "$enabled"
files=0
[ -d "$VAULT_PATH/$section" ] && files=$(find "$VAULT_PATH/$section" -type f 2>/dev/null | wc -l)
json_add_int files "$files"
last_backup=""
[ -f "$VAULT_PATH/$section/manifest.json" ] && {
last_backup=$(jsonfilter -i "$VAULT_PATH/$section/manifest.json" -e '@.backed_up' 2>/dev/null)
}
json_add_string last_backup "$last_backup"
json_add_array configs
config_list_foreach "$section" config add_config_json
json_close_array
json_close_object
}
handle_modules() {
json_init
json_add_array modules
config_load config-vault
config_get VAULT_PATH global vault_path "/srv/config-vault"
export VAULT_PATH
config_foreach list_module_json module
json_close_array
json_dump
}
handle_history() {
local count vault_path
read -r input
json_load "$input"
json_get_var count count
[ -z "$count" ] && count=20
json_init
json_add_array commits
config_load config-vault
config_get vault_path global vault_path "/srv/config-vault"
if [ -d "$vault_path/.git" ]; then
cd "$vault_path"
git log --format='%H|%h|%ci|%s' -n "$count" 2>/dev/null | while IFS='|' read hash short date msg; do
json_add_object
json_add_string hash "$hash"
json_add_string short "$short"
json_add_string date "$date"
json_add_string message "$msg"
json_close_object
done
fi
json_close_array
json_dump
}
handle_diff() {
local vault_path diff_output
config_load config-vault
config_get vault_path global vault_path "/srv/config-vault"
json_init
if [ -d "$vault_path/.git" ]; then
cd "$vault_path"
diff_output=$(git diff 2>/dev/null | head -200)
json_add_string diff "$diff_output"
json_add_int changed_files "$(git status --porcelain 2>/dev/null | wc -l)"
else
json_add_string diff ""
json_add_int changed_files 0
fi
json_dump
}
handle_backup() {
local module output rc
read -r input
json_load "$input"
json_get_var module module
json_init
if [ -n "$module" ]; then
output=$($VAULT_CTL backup "$module" 2>&1)
else
output=$($VAULT_CTL backup 2>&1)
fi
rc=$?
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
json_add_string output "$output"
json_dump
}
handle_restore() {
local module output rc
read -r input
json_load "$input"
json_get_var module module
json_init
if [ -z "$module" ]; then
json_add_boolean success 0
json_add_string error "Module name required"
else
output=$($VAULT_CTL restore "$module" 2>&1)
rc=$?
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
json_add_string output "$output"
fi
json_dump
}
handle_push() {
local output rc
json_init
output=$($VAULT_CTL push 2>&1)
rc=$?
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
json_add_string output "$output"
json_dump
}
handle_pull() {
local output rc
json_init
output=$($VAULT_CTL pull 2>&1)
rc=$?
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
json_add_string output "$output"
json_dump
}
handle_init() {
local output rc
json_init
output=$($VAULT_CTL init 2>&1)
rc=$?
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
json_add_string output "$output"
json_dump
}
handle_export_clone() {
local path output rc
read -r input
json_load "$input"
json_get_var path path
[ -z "$path" ] && path="/tmp/secubox-clone-$(date +%Y%m%d).tar.gz"
json_init
output=$($VAULT_CTL export-clone "$path" 2>&1)
rc=$?
[ $rc -eq 0 ] && json_add_boolean success 1 || json_add_boolean success 0
json_add_string output "$output"
json_add_string path "$path"
if [ -f "$path" ]; then
json_add_int size "$(stat -c%s "$path" 2>/dev/null || echo 0)"
fi
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)
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 ;;
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