From a68d8b638f5db8ae31467a83109c5676d5eb5cb8 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 30 Jan 2026 13:34:12 +0100 Subject: [PATCH] feat(p2p): Add MirrorBox auto-init, self-recovery, and ACL fixes MirrorBox Auto-Init: - Add blockchain-like gigogne P2P structure with peer zero (P0) genesis - Auto-create self-mesh on page load with configurable depth - Preserve MirrorBox peers during refresh cycles Self-Recovery System: - Add secubox-restore script for bootstrapping new OpenWrt boxes - Generate customized bootstrap.sh in Gitea backups - Support interactive and command-line restore modes ACL Fixes: - Add missing deploy/pull methods to luci-app-secubox-p2p ACL - Add luci.gitea and luci.secubox-p2p access to luci-app-secubox ACL - Fix null display issue in hub.js (changed to empty string) Backup Enhancements: - Fix syntax error in RPCD heredoc (openwrt_version line) - Add branch reference to Gitea API calls (main branch) - Include bootstrap.sh and secubox-restore in backup push Documentation: - Add comprehensive README.md for SecuBox P2P module Co-Authored-By: Claude Opus 4.5 --- .../resources/view/secubox-p2p/hub.js | 194 ++++++-- .../rpcd/acl.d/luci-app-secubox-p2p.json | 16 +- .../resources/view/secubox/wizard.js | 162 ++++++- .../share/rpcd/acl.d/luci-app-secubox.json | 21 + package/secubox/secubox-p2p/README.md | 342 ++++++++++++++ .../secubox-p2p/root/usr/bin/secubox-restore | 446 ++++++++++++++++++ .../root/usr/libexec/rpcd/luci.secubox-p2p | 209 +++++++- 7 files changed, 1320 insertions(+), 70 deletions(-) create mode 100644 package/secubox/secubox-p2p/README.md create mode 100644 package/secubox/secubox-p2p/root/usr/bin/secubox-restore diff --git a/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/hub.js b/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/hub.js index b783cd0c..4704062b 100644 --- a/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/hub.js +++ b/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/hub.js @@ -2,9 +2,24 @@ 'require view'; 'require ui'; 'require dom'; +'require rpc'; 'require secubox-p2p/api as P2PAPI'; 'require poll'; +// Gitea RPC for token generation +var callGiteaGenerateToken = rpc.declare({ + object: 'luci.gitea', + method: 'generate_token', + params: ['username', 'token_name', 'scopes'], + expect: {} +}); + +var callGiteaGetStatus = rpc.declare({ + object: 'luci.gitea', + method: 'get_status', + expect: {} +}); + return view.extend({ // State peers: [], @@ -76,12 +91,14 @@ return view.extend({ mode: 'gigogne', // gigogne = nested cycle, mono = single hop, full = all-to-all, ring = circular cycleDepth: 3, autoPropagate: true, - selfLoop: true + selfLoop: true, + autoSelfMesh: true // Auto-create peer zero MirrorBox on init }, - // Self-peer for testing + // Self-peer for MirrorBox mesh selfPeer: null, testMode: false, + mirrorBoxInit: false, // Track if MirrorBox is initialized // Mesh Backup Config meshBackupConfig: { @@ -191,6 +208,11 @@ return view.extend({ }).catch(function() {}); } + // Auto-init MirrorBox (peer zero) in load phase + if (self.distributionConfig.autoSelfMesh && !self.mirrorBoxInit) { + self.initMirrorBox(); + } + return {}; }); }, @@ -239,13 +261,23 @@ return view.extend({ refreshData: function() { var self = this; + + // Preserve MirrorBox peers before refresh + var mirrorBoxPeers = this.peers.filter(function(p) { + return p.isMirrorBox || p.isSelf || p.isGigogne; + }); + return Promise.all([ P2PAPI.getPeers(), P2PAPI.getServices(), P2PAPI.getSharedServices(), P2PAPI.healthCheck() ]).then(function(results) { - self.peers = results[0].peers || []; + // Merge API peers with preserved MirrorBox peers + var apiPeers = results[0].peers || []; + self.peers = mirrorBoxPeers.concat(apiPeers.filter(function(p) { + return !mirrorBoxPeers.some(function(m) { return m.id === p.id; }); + })); self.services = results[1].services || []; self.sharedServices = results[2].shared_services || []; self.health = results[3] || {}; @@ -407,7 +439,7 @@ return view.extend({ 'change': function(e) { self.distributionConfig.cycleDepth = parseInt(e.target.value); } }) ]), - this.testMode ? E('span', { 'class': 'test-badge' }, '๐Ÿงช TEST') : null + this.testMode ? E('span', { 'class': 'test-badge' }, '๐Ÿงช TEST') : '' ]) ]) ]); @@ -1310,7 +1342,62 @@ return view.extend({ ui.addNotification(null, E('p', 'WireGuard Mirror mode: ' + mode), 'info'); }, - // ==================== Self Peer & Distribution ==================== + // ==================== MirrorBox Auto-Init & Self Peer ==================== + initMirrorBox: function() { + var self = this; + + // Already initialized or no peers needed + if (this.mirrorBoxInit || !this.distributionConfig.autoSelfMesh) return; + + this.mirrorBoxInit = true; + + // Get hostname for peer zero naming + var hostname = (this.settings && this.settings.node_name) || 'secubox'; + + // Create peer zero - the genesis MirrorBox node + this.selfPeer = { + id: 'mirrorbox-0-' + Date.now(), + name: '๐Ÿ‘‘ ' + hostname + ' (P0)', + address: '127.0.0.1', + status: 'online', + isSelf: true, + isMirrorBox: true, + peerNumber: 0, + services: this.services.slice(), + wgMirror: true, + blockchainGenesis: true + }; + + this.peers.unshift(this.selfPeer); + this.testMode = true; + + // Create gigogne nested peers (blockchain-like structure) + var depth = this.distributionConfig.cycleDepth; + var prevId = this.selfPeer.id; + + for (var i = 1; i < depth; i++) { + var nestedPeer = { + id: 'gigogne-' + i + '-' + Date.now(), + name: '๐Ÿช† Gigogne L' + i, + address: '127.0.0.' + (i + 1), + status: 'online', + isSelf: true, + isGigogne: true, + isMirrorBox: true, + level: i, + peerNumber: i, + parentId: prevId, + services: this.services.slice(), + wgMirror: true, + blockchainPrev: prevId + }; + prevId = nestedPeer.id; + this.peers.push(nestedPeer); + } + + console.log('๐Ÿ”— MirrorBox initialized: peer zero + ' + (depth - 1) + ' gigogne levels'); + }, + addSelfPeer: function() { var self = this; this.testMode = true; @@ -1702,49 +1789,68 @@ return view.extend({ } }, - executeAutoCreate: function(serverUrl, repoName, repoDesc, token) { + executeAutoCreate: function(serverUrl, repoName, repoDesc, providedToken) { var self = this; ui.addNotification(null, E('p', '๐Ÿš€ Auto-creating mesh repository...'), 'info'); - // Step 1: Save Gitea config - P2PAPI.setGiteaConfig({ - server_url: serverUrl, - repo_name: repoName, - access_token: token, - enabled: 1, - auto_backup: 1, - backup_on_change: 1 - }).then(function() { - // Step 2: Create repository - ui.addNotification(null, E('p', '๐Ÿ“ฆ Creating repository: ' + repoName), 'info'); - return P2PAPI.createGiteaRepo(repoName, repoDesc, true, true); - }).then(function(result) { - if (result.success) { - // Update local state - self.giteaConfig.serverUrl = serverUrl; - self.giteaConfig.repoName = result.repo_name || repoName; - self.giteaConfig.repoOwner = result.owner || ''; - self.giteaConfig.enabled = true; - self.giteaConfig.hasToken = true; - self.giteaConfig.lastFetch = Date.now(); + // Step 1: Generate new token with full scopes via Gitea RPCD + ui.addNotification(null, E('p', '๐Ÿ”‘ Generating access token...'), 'info'); - ui.addNotification(null, E('p', 'โœ… Repository created: ' + self.giteaConfig.repoOwner + '/' + self.giteaConfig.repoName), 'success'); + callGiteaGenerateToken('gandalf', repoName + '-token', 'write:repository,write:user,read:user') + .then(function(result) { + var token = providedToken; + if (result && result.result && result.result.token) { + token = result.result.token; + ui.addNotification(null, E('p', 'โœ… Token generated successfully'), 'success'); + } else if (!token) { + throw new Error('Failed to generate token and no token provided'); + } - // Step 3: Push initial state - ui.addNotification(null, E('p', '๐Ÿ“ค Pushing initial mesh state...'), 'info'); - return P2PAPI.pushGiteaBackup('Initial SecuBox mesh configuration', {}); - } else { - throw new Error(result.error || 'Failed to create repository'); - } - }).then(function(pushResult) { - if (pushResult && pushResult.success) { - ui.addNotification(null, E('p', '๐ŸŽ‰ Mesh repository ready! ' + pushResult.files_pushed + ' files uploaded'), 'success'); - self.refreshGiteaCommits(); - } - }).catch(function(err) { - ui.addNotification(null, E('p', 'โŒ Auto-setup failed: ' + err.message), 'error'); - }); + // Step 2: Save Gitea config with new token + ui.addNotification(null, E('p', '๐Ÿ’พ Saving configuration...'), 'info'); + return P2PAPI.setGiteaConfig({ + server_url: serverUrl, + repo_name: repoName, + access_token: token, + enabled: 1, + auto_backup: 1, + backup_on_change: 1 + }); + }) + .then(function() { + // Step 3: Create repository + ui.addNotification(null, E('p', '๐Ÿ“ฆ Creating repository: ' + repoName), 'info'); + return P2PAPI.createGiteaRepo(repoName, repoDesc, true, true); + }) + .then(function(result) { + if (result.success) { + // Update local state + self.giteaConfig.serverUrl = serverUrl; + self.giteaConfig.repoName = result.repo_name || repoName; + self.giteaConfig.repoOwner = result.owner || ''; + self.giteaConfig.enabled = true; + self.giteaConfig.hasToken = true; + self.giteaConfig.lastFetch = Date.now(); + + ui.addNotification(null, E('p', 'โœ… Repository created: ' + self.giteaConfig.repoOwner + '/' + self.giteaConfig.repoName), 'success'); + + // Step 4: Push initial state + ui.addNotification(null, E('p', '๐Ÿ“ค Pushing initial mesh state...'), 'info'); + return P2PAPI.pushGiteaBackup('Initial SecuBox mesh configuration', {}); + } else { + throw new Error(result.error || 'Failed to create repository'); + } + }) + .then(function(pushResult) { + if (pushResult && pushResult.success) { + ui.addNotification(null, E('p', '๐ŸŽ‰ Mesh repository ready! ' + pushResult.files_pushed + ' files uploaded'), 'success'); + self.refreshGiteaCommits(); + } + }) + .catch(function(err) { + ui.addNotification(null, E('p', 'โŒ Auto-setup failed: ' + err.message), 'error'); + }); }, fetchGiteaCommits: function() { @@ -2340,7 +2446,7 @@ return view.extend({ E('div', { 'class': 'panel-title' }, [ E('span', {}, '๐Ÿ‘ฅ'), E('span', {}, 'Connected Peers'), - this.testMode ? E('span', { 'class': 'badge test' }, '๐Ÿงช TEST') : null + this.testMode ? E('span', { 'class': 'badge test' }, '๐Ÿงช TEST') : '' ]), E('button', { 'class': 'btn small', 'click': function() { self.discoverPeers(); } }, '๐Ÿ” Discover') ]), @@ -2377,7 +2483,7 @@ return view.extend({ E('div', { 'class': 'panel-actions' }, [ E('button', { 'class': 'btn', 'click': function() { self.showAddPeerModal(); } }, 'โž• Add Peer'), this.testMode ? - E('button', { 'class': 'btn', 'click': function() { self.removeSelfPeer(); } }, '๐Ÿ—‘๏ธ Clear Test') : null + E('button', { 'class': 'btn', 'click': function() { self.removeSelfPeer(); } }, '๐Ÿ—‘๏ธ Clear Test') : '' ]) ]); }, diff --git a/package/secubox/luci-app-secubox-p2p/root/usr/share/rpcd/acl.d/luci-app-secubox-p2p.json b/package/secubox/luci-app-secubox-p2p/root/usr/share/rpcd/acl.d/luci-app-secubox-p2p.json index ca05b12c..65c4c97d 100644 --- a/package/secubox/luci-app-secubox-p2p/root/usr/share/rpcd/acl.d/luci-app-secubox-p2p.json +++ b/package/secubox/luci-app-secubox-p2p/root/usr/share/rpcd/acl.d/luci-app-secubox-p2p.json @@ -17,7 +17,9 @@ "get_gitea_config", "list_gitea_repos", "get_gitea_commits", - "list_local_backups" + "list_local_backups", + "get_dns_bridge_config", + "get_wg_mirror_config" ], "uci": ["get", "state"] }, @@ -40,7 +42,17 @@ "push_gitea_backup", "pull_gitea_backup", "create_local_backup", - "restore_local_backup" + "restore_local_backup", + "deploy_registry", + "deploy_registry_entry", + "deploy_services", + "deploy_local_services", + "deploy_service", + "pull_mesh_services", + "pull_service", + "set_dns_bridge_config", + "set_wg_mirror_config", + "sync_wg_mirror" ], "uci": ["set", "delete", "commit", "apply"] }, diff --git a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/wizard.js b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/wizard.js index b6df16ec..64930079 100644 --- a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/wizard.js +++ b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/wizard.js @@ -1,11 +1,53 @@ 'use strict'; 'require view'; 'require ui'; +'require rpc'; 'require secubox/api as API'; 'require secubox-theme/theme as Theme'; 'require secubox/nav as SecuNav'; 'require secubox-portal/header as SbHeader'; +// P2P Mesh RPC declarations +var callGetGiteaConfig = rpc.declare({ + object: 'luci.secubox-p2p', + method: 'get_gitea_config', + expect: {} +}); + +var callSetGiteaConfig = rpc.declare({ + object: 'luci.secubox-p2p', + method: 'set_gitea_config', + params: ['config'], + expect: { success: false } +}); + +var callCreateGiteaRepo = rpc.declare({ + object: 'luci.secubox-p2p', + method: 'create_gitea_repo', + params: ['name', 'description', 'private', 'init_readme'], + expect: { success: false } +}); + +var callPushGiteaBackup = rpc.declare({ + object: 'luci.secubox-p2p', + method: 'push_gitea_backup', + params: ['message', 'components'], + expect: { success: false } +}); + +var callGiteaGenerateToken = rpc.declare({ + object: 'luci.gitea', + method: 'generate_token', + params: ['username', 'token_name', 'scopes'], + expect: {} +}); + +var callGiteaGetStatus = rpc.declare({ + object: 'luci.gitea', + method: 'get_status', + expect: {} +}); + // Load theme resources document.head.appendChild(E('link', { 'rel': 'stylesheet', @@ -37,7 +79,9 @@ return view.extend({ return Promise.all([ API.getFirstRunStatus(), API.listApps(), - API.listProfiles() + API.listProfiles(), + callGetGiteaConfig().catch(function() { return {}; }), + callGiteaGetStatus().catch(function() { return {}; }) ]); }, @@ -51,8 +95,12 @@ return view.extend({ // Filter to only show apps with wizards this.appList = allApps.filter(function(app) { return app.has_wizard === true; }); this.profileList = Array.isArray(payload[2]) ? payload[2] : (payload[2] && payload[2].profiles) || []; + this.p2pConfig = payload[3] || {}; + this.giteaStatus = payload[4] || {}; console.log('[SecuBox Wizard] Filtered appList (has_wizard only):', this.appList); console.log('[SecuBox Wizard] Parsed profileList:', this.profileList); + console.log('[SecuBox Wizard] P2P Config:', this.p2pConfig); + console.log('[SecuBox Wizard] Gitea Status:', this.giteaStatus); var container = E('div', { 'class': 'secubox-wizard-page' }, [ E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/core/variables.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), @@ -82,12 +130,16 @@ return view.extend({ }, renderFirstRunCard: function() { + var self = this; var data = this.firstRun || {}; + var p2pData = this.p2pConfig || {}; + var giteaData = this.giteaStatus || {}; var steps = [ { icon: '๐Ÿ”', label: _('Secure Admin Account'), description: _('Set a LuCI/root password to protect the router.'), complete: !!data.password_set, content: this.renderPasswordStep(data) }, { icon: '๐ŸŒ', label: _('Timezone & Locale'), description: _('Align system time with your region.'), complete: false, content: this.renderTimezoneStep(data) }, { icon: '๐Ÿ’พ', label: _('Storage Path'), description: _('Choose where SecuBox apps store data (USB/NAS recommended).'), complete: !!data.storage_ready, content: this.renderStorageStep(data) }, - { icon: '๐Ÿ›ก๏ธ', label: _('Network Mode'), description: _('Pick a default SecuBox network mode (router or DMZ).'), complete: false, content: this.renderModeStep(data) } + { icon: '๐Ÿ›ก๏ธ', label: _('Network Mode'), description: _('Pick a default SecuBox network mode (router or DMZ).'), complete: false, content: this.renderModeStep(data) }, + { icon: '๐ŸŒ', label: _('P2P Mesh Backup'), description: _('Auto-configure Gitea repository for mesh config versioning.'), complete: !!(p2pData.enabled && p2pData.repo_name), content: this.renderP2PMeshStep(p2pData, giteaData) } ]; return E('div', { 'class': 'sb-wizard-card' }, [ @@ -159,6 +211,112 @@ return view.extend({ ]); }, + renderP2PMeshStep: function(p2pData, giteaData) { + var self = this; + var giteaRunning = giteaData && giteaData.result && giteaData.result.running; + var repoConfigured = p2pData && p2pData.enabled && p2pData.repo_name; + + if (repoConfigured) { + return E('div', { 'class': 'sb-wizard-inline' }, [ + E('div', { 'class': 'sb-wizard-status ok' }, [ + _('Repository: '), + E('strong', {}, (p2pData.repo_owner || 'user') + '/' + p2pData.repo_name) + ]), + E('a', { + 'class': 'cbi-button cbi-button-action', + 'href': L.url('admin', 'secubox', 'p2p-hub') + }, _('Open P2P Hub')) + ]); + } + + if (!giteaRunning) { + return E('div', { 'class': 'sb-wizard-inline' }, [ + E('div', { 'class': 'sb-wizard-status warn' }, _('Gitea not running')), + E('a', { + 'class': 'cbi-button cbi-button-action', + 'href': L.url('admin', 'services', 'gitea') + }, _('Start Gitea')) + ]); + } + + // Gitea running but repo not configured - show auto-setup + return E('div', { 'class': 'sb-wizard-inline' }, [ + E('div', { 'class': 'sb-wizard-status warn' }, _('Not configured')), + E('button', { + 'class': 'cbi-button cbi-button-action', + 'id': 'btn-p2p-auto-setup', + 'click': function() { self.autoSetupP2PMesh(); } + }, _('๐Ÿš€ Auto Setup')) + ]); + }, + + autoSetupP2PMesh: function() { + var self = this; + var btn = document.getElementById('btn-p2p-auto-setup'); + if (btn) btn.disabled = true; + + ui.showModal(_('P2P Mesh Auto Setup'), [ + E('div', { 'class': 'spinning', 'style': 'margin: 20px auto;' }), + E('p', { 'id': 'p2p-setup-status', 'style': 'text-align: center;' }, _('Initializing...')) + ]); + + var updateStatus = function(msg) { + var el = document.getElementById('p2p-setup-status'); + if (el) el.textContent = msg; + }; + + var hostname = (this.firstRun && this.firstRun.hostname) || 'secubox'; + var repoName = hostname.toLowerCase().replace(/[^a-z0-9-]/g, '-') + '-p2p'; + + // Step 1: Generate token with proper scopes + updateStatus(_('Generating access token...')); + callGiteaGenerateToken('gandalf', repoName + '-token', 'write:repository,write:user,read:user') + .then(function(result) { + if (!result || !result.result || !result.result.token) { + throw new Error(_('Failed to generate token')); + } + var token = result.result.token; + + // Step 2: Save token to P2P config + updateStatus(_('Saving configuration...')); + return callSetGiteaConfig({ + server_url: 'http://localhost:3000', + repo_name: repoName, + access_token: token, + enabled: 1, + auto_backup: 1 + }).then(function() { return token; }); + }) + .then(function(token) { + // Step 3: Create repository + updateStatus(_('Creating repository: ') + repoName); + return callCreateGiteaRepo(repoName, 'SecuBox P2P Mesh configuration', true, true); + }) + .then(function(result) { + if (!result || !result.success) { + throw new Error(result && result.error ? result.error : _('Failed to create repository')); + } + + // Step 4: Push initial config + updateStatus(_('Pushing initial configuration...')); + return callPushGiteaBackup('Initial mesh configuration from wizard', {}); + }) + .then(function(result) { + ui.hideModal(); + if (result && result.success) { + ui.addNotification(null, E('p', {}, _('๐ŸŽ‰ P2P Mesh setup complete! Repository created and initial config pushed.')), 'info'); + setTimeout(function() { window.location.reload(); }, 1500); + } else { + throw new Error(_('Failed to push configuration')); + } + }) + .catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', {}, 'โŒ ' + (err.message || err)), 'error'); + if (btn) btn.disabled = false; + }); + }, + renderAppsCard: function() { var apps = this.appList || []; return E('div', { 'class': 'sb-wizard-card' }, [ diff --git a/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json b/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json index da694906..86450ddd 100644 --- a/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json +++ b/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json @@ -45,6 +45,15 @@ "p2p_get_peer_catalog", "p2p_get_shared_services" ], + "luci.gitea": [ + "get_status" + ], + "luci.secubox-p2p": [ + "get_gitea_config", + "list_gitea_repos", + "get_gitea_commits", + "list_local_backups" + ], "uci": [ "get", "state" @@ -90,6 +99,18 @@ "p2p_sync_catalog", "p2p_broadcast_command" ], + "luci.gitea": [ + "generate_token", + "create_repo" + ], + "luci.secubox-p2p": [ + "set_gitea_config", + "create_gitea_repo", + "push_gitea_backup", + "pull_gitea_backup", + "create_local_backup", + "restore_local_backup" + ], "uci": [ "set", "delete", diff --git a/package/secubox/secubox-p2p/README.md b/package/secubox/secubox-p2p/README.md new file mode 100644 index 00000000..490c1038 --- /dev/null +++ b/package/secubox/secubox-p2p/README.md @@ -0,0 +1,342 @@ +# SecuBox P2P Mesh Network + +Distributed peer-to-peer mesh networking for SecuBox appliances with integrated backup, recovery, and federation capabilities. + +## Overview + +SecuBox P2P enables multiple SecuBox appliances to form a distributed mesh network for: + +- **Service Discovery**: Automatically discover and connect to peer SecuBox nodes +- **Configuration Sync**: Share and synchronize configurations across the mesh +- **Distributed Backup**: Version-controlled backups via Gitea integration +- **Self-Recovery**: Bootstrap new appliances from existing backups +- **MaaS Federation**: Mesh-as-a-Service for distributed security infrastructure + +## Architecture + +``` + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Gitea Server โ”‚ + โ”‚ (Version Ctrl) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ” + โ”‚ SecuBox โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ SecuBox โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ SecuBox โ”‚ + โ”‚ Node A โ”‚ โ”‚ Node B โ”‚ โ”‚ Node C โ”‚ + โ”‚ (Leader)โ”‚ โ”‚ (Peer) โ”‚ โ”‚ (Peer) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + WireGuard WireGuard WireGuard + Tunnel Tunnel Tunnel +``` + +## Features + +### Mesh Networking + +| Feature | Description | +|---------|-------------| +| **Peer Discovery** | mDNS/DNS-SD based automatic peer discovery | +| **WireGuard VPN** | Encrypted mesh tunnels between nodes | +| **HAProxy LB** | Load balancing across mesh services | +| **DNS Integration** | Mesh-aware DNS resolution | + +### Backup & Recovery + +| Feature | Description | +|---------|-------------| +| **Gitea Integration** | Git-based versioned backups | +| **15 Component Types** | Comprehensive appliance backup | +| **Bootstrap Script** | One-command recovery for new boxes | +| **Historical Tracking** | Full audit trail of changes | + +### Topology Modes + +- **Full Mesh**: Every node connects to every other node +- **Star**: Central hub with spoke connections +- **Ring**: Circular topology with neighbor connections +- **Tree**: Hierarchical parent-child structure + +## Installation + +```bash +opkg update +opkg install secubox-p2p luci-app-secubox-p2p +``` + +## Configuration + +### UCI Configuration + +```bash +# /etc/config/secubox-p2p + +config p2p 'settings' + option enabled '1' + option node_name 'secubox-node' + option discovery_enabled '1' + option sync_interval '300' + +config gitea 'gitea' + option enabled '1' + option server_url 'http://localhost:3000' + option repo_owner 'admin' + option repo_name 'secubox-backup' + option access_token 'your-token-here' + option auto_backup '1' + option backup_interval '3600' +``` + +### Manual Configuration + +```bash +# Enable P2P mesh +uci set secubox-p2p.settings.enabled='1' +uci set secubox-p2p.settings.node_name='my-secubox' +uci commit secubox-p2p + +# Configure Gitea backup +uci set secubox-p2p.gitea.enabled='1' +uci set secubox-p2p.gitea.server_url='http://gitea.local:3000' +uci set secubox-p2p.gitea.repo_owner='admin' +uci set secubox-p2p.gitea.repo_name='secubox-backup' +uci set secubox-p2p.gitea.access_token='your-token' +uci commit secubox-p2p + +# Restart service +/etc/init.d/secubox-p2p restart +``` + +## Usage + +### Command Line + +```bash +# Peer management +secubox-p2p peers # List connected peers +secubox-p2p discover # Discover new peers +secubox-p2p add-peer # Add peer manually + +# Service management +secubox-p2p services # List local services +secubox-p2p shared-services # List mesh-shared services + +# Sync operations +secubox-p2p sync # Sync with all peers +``` + +### RPCD API + +All functions are available via ubus: + +```bash +# Peer operations +ubus call luci.secubox-p2p get_peers +ubus call luci.secubox-p2p discover '{"timeout":5}' +ubus call luci.secubox-p2p add_peer '{"address":"10.0.0.2","name":"peer1"}' + +# Gitea backup +ubus call luci.secubox-p2p push_gitea_backup '{"message":"Daily backup"}' +ubus call luci.secubox-p2p pull_gitea_backup '{"commit_sha":"abc123"}' +ubus call luci.secubox-p2p list_gitea_repos +ubus call luci.secubox-p2p get_gitea_commits '{"limit":10}' + +# Local backup +ubus call luci.secubox-p2p create_local_backup '{"name":"pre-upgrade"}' +ubus call luci.secubox-p2p list_local_backups +ubus call luci.secubox-p2p restore_local_backup '{"backup_id":"20260130-120000"}' +``` + +## Backup Components + +The backup system captures 15 component categories: + +| Component | Path | Description | +|-----------|------|-------------| +| `configs` | `/etc/config/` | UCI configuration files | +| `profiles` | `/usr/share/secubox/profiles/` | Deployment profiles | +| `presets` | `/etc/secubox/presets/` | Settings presets | +| `manifests` | `/etc/secubox/manifests/` | App manifests | +| `scripts` | `/usr/share/secubox/scripts/` | Custom scripts | +| `macros` | `/etc/secubox/macros/` | Automation macros | +| `workflows` | `/etc/secubox/workflows/` | CI/CD workflows | +| `packages` | - | Installed package list | +| `services` | - | Service states | +| `cron` | `/etc/crontabs/` | Scheduled tasks | +| `ssh` | `/etc/dropbear/` | SSH keys & config | +| `certificates` | `/etc/acme/`, `/etc/ssl/` | TLS certificates | +| `haproxy` | `/etc/haproxy/` | Load balancer config | +| `dns` | `/etc/dnsmasq.d/` | DNS configuration | +| `device` | - | Hardware/system info | + +## Self-Recovery + +### Quick Bootstrap + +Deploy SecuBox to a new OpenWrt box with one command: + +```bash +# From the Gitea repository +wget -qO- http://gitea.local:3000/user/repo/raw/branch/main/bootstrap.sh | sh + +# Or using curl +curl -sL http://gitea.local:3000/user/repo/raw/branch/main/bootstrap.sh | sh +``` + +### Manual Recovery + +```bash +# Interactive mode +secubox-restore -i + +# Direct restore +secubox-restore http://gitea.local:3000 admin secubox-backup [token] + +# Restore from specific branch +secubox-restore -b develop http://gitea.local:3000 admin secubox-backup +``` + +### Recovery Options + +``` +secubox-restore [options] [token] + +Options: + -i, --interactive Interactive mode with prompts + -b, --branch Git branch to restore from (default: main) + --include-network Also restore network/wireless/firewall configs + -h, --help Show help message +``` + +## LuCI Web Interface + +Access the P2P Hub at: **SecuBox > P2P Mesh > Hub** + +### Dashboard Features + +- **Globe Visualization**: Interactive mesh topology view +- **Status Indicators**: System, DNS, WireGuard, Load Balancer status +- **Peer Counters**: Connected peers, online nodes, shared services +- **Quick Actions**: Discover, Sync All, Add Peer, Self Peer + +### Gitea Integration Tab + +- **Repository Setup**: Configure Gitea server and credentials +- **Auto-Backup**: Enable scheduled backups +- **Commit History**: View backup history with restore options +- **Token Generation**: Create access tokens with proper scopes + +## Security + +### Authentication + +- Gitea tokens require specific scopes: + - `write:repository` - Push backups + - `read:user` - Verify identity + - `write:user` - Create tokens (for auto-setup) + +### Encryption + +- All mesh traffic encrypted via WireGuard +- Gitea communication over HTTPS (recommended) +- SSH keys backed up securely + +### Access Control + +- RPCD ACL controls API access +- Per-user Gitea permissions +- Network-level firewall rules + +## Troubleshooting + +### Common Issues + +**Peer discovery not working:** +```bash +# Check mDNS/avahi +/etc/init.d/avahi-daemon status + +# Verify firewall allows mDNS (port 5353/udp) +uci show firewall | grep mdns +``` + +**Gitea backup fails:** +```bash +# Test API connectivity +curl -s http://gitea:3000/api/v1/user \ + -H "Authorization: token YOUR_TOKEN" + +# Check token scopes +ubus call luci.secubox-p2p get_gitea_config +``` + +**WireGuard tunnel not establishing:** +```bash +# Check WireGuard status +wg show + +# Verify peer keys +uci show wireguard +``` + +### Logs + +```bash +# P2P service logs +logread | grep secubox-p2p + +# RPCD logs +logread | grep rpcd +``` + +## API Reference + +### Peer Management + +| Method | Parameters | Description | +|--------|------------|-------------| +| `get_peers` | - | List all peers | +| `add_peer` | `address`, `name` | Add new peer | +| `remove_peer` | `peer_id` | Remove peer | +| `discover` | `timeout` | Discover peers | + +### Gitea Operations + +| Method | Parameters | Description | +|--------|------------|-------------| +| `get_gitea_config` | - | Get Gitea settings | +| `set_gitea_config` | `config` | Update settings | +| `create_gitea_repo` | `name`, `description`, `private` | Create repository | +| `list_gitea_repos` | - | List repositories | +| `get_gitea_commits` | `limit` | Get commit history | +| `push_gitea_backup` | `message`, `components` | Push backup | +| `pull_gitea_backup` | `commit_sha` | Restore from commit | + +### Local Backup + +| Method | Parameters | Description | +|--------|------------|-------------| +| `create_local_backup` | `name`, `components` | Create backup | +| `list_local_backups` | - | List backups | +| `restore_local_backup` | `backup_id` | Restore backup | + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test on OpenWrt device +5. Submit a pull request + +## License + +GPL-2.0 - See LICENSE file for details. + +## Related Projects + +- [SecuBox Core](../secubox-core/) - Core SecuBox functionality +- [LuCI App SecuBox](../luci-app-secubox/) - Main dashboard +- [LuCI App SecuBox P2P](../luci-app-secubox-p2p/) - P2P web interface +- [SecuBox Gitea](../luci-app-gitea/) - Gitea container management diff --git a/package/secubox/secubox-p2p/root/usr/bin/secubox-restore b/package/secubox/secubox-p2p/root/usr/bin/secubox-restore new file mode 100644 index 00000000..ef9e2228 --- /dev/null +++ b/package/secubox/secubox-p2p/root/usr/bin/secubox-restore @@ -0,0 +1,446 @@ +#!/bin/sh +# +# SecuBox Self-Recovery Bootstrap Script +# Downloads and restores a SecuBox appliance configuration from Gitea +# +# Usage: +# curl -sL http://gitea-server/user/repo/raw/branch/master/bootstrap.sh | sh +# # OR +# wget -qO- http://gitea-server/user/repo/raw/branch/master/bootstrap.sh | sh +# # OR +# secubox-restore [access-token] +# + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[OK]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +print_banner() { + cat << 'EOF' + ____ ____ + / ___| ___ ___ _ | __ ) _____ __ + \___ \ / _ \/ __| | | | _ \ / _ \ \/ / + ___) | __/ (__| |_| | |_) | (_) > < + |____/ \___|\___|\__,_|____/ \___/_/\_\ + + Self-Recovery Bootstrap v1.0 +EOF +} + +# Check if we're on OpenWrt +check_openwrt() { + if [ ! -f /etc/openwrt_release ]; then + log_error "This script must be run on OpenWrt" + exit 1 + fi + . /etc/openwrt_release + log_info "Detected OpenWrt $DISTRIB_RELEASE on $(cat /tmp/sysinfo/model 2>/dev/null || echo 'unknown device')" +} + +# Install dependencies if missing +install_deps() { + log_info "Checking dependencies..." + + # Required packages + for pkg in curl git-http jsonfilter; do + if ! command -v "$pkg" >/dev/null 2>&1 && ! opkg list-installed | grep -q "^$pkg "; then + log_info "Installing $pkg..." + opkg update >/dev/null 2>&1 || true + opkg install "$pkg" 2>/dev/null || log_warn "Could not install $pkg" + fi + done + + log_success "Dependencies ready" +} + +# Fetch manifest from Gitea repository +fetch_manifest() { + local server_url="$1" + local repo_owner="$2" + local repo_name="$3" + local token="$4" + local branch="${5:-master}" + + log_info "Fetching backup manifest..." + + local manifest_url="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/manifest.json?ref=${branch}" + + if [ -n "$token" ]; then + response=$(curl -sL "$manifest_url" -H "Authorization: token $token") + else + response=$(curl -sL "$manifest_url") + fi + + # Check for error + if echo "$response" | jsonfilter -e '@.message' 2>/dev/null | grep -qi "not found"; then + log_error "Repository or manifest not found" + return 1 + fi + + # Decode base64 content + content=$(echo "$response" | jsonfilter -e '@.content' 2>/dev/null | base64 -d 2>/dev/null) + + if [ -z "$content" ]; then + log_error "Failed to fetch manifest" + return 1 + fi + + echo "$content" +} + +# Fetch and restore a file from Gitea +fetch_file() { + local server_url="$1" + local repo_owner="$2" + local repo_name="$3" + local file_path="$4" + local dest_path="$5" + local token="$6" + local branch="${7:-master}" + + local file_url="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/${file_path}?ref=${branch}" + + if [ -n "$token" ]; then + response=$(curl -sL "$file_url" -H "Authorization: token $token") + else + response=$(curl -sL "$file_url") + fi + + # Check if it's a file (has content) or directory + content=$(echo "$response" | jsonfilter -e '@.content' 2>/dev/null) + + if [ -n "$content" ]; then + # Single file - decode and save + mkdir -p "$(dirname "$dest_path")" + echo "$content" | base64 -d > "$dest_path" 2>/dev/null + return 0 + fi + + return 1 +} + +# Fetch directory contents recursively +fetch_directory() { + local server_url="$1" + local repo_owner="$2" + local repo_name="$3" + local dir_path="$4" + local dest_base="$5" + local token="$6" + local branch="${7:-master}" + + local dir_url="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/${dir_path}?ref=${branch}" + + if [ -n "$token" ]; then + response=$(curl -sL "$dir_url" -H "Authorization: token $token") + else + response=$(curl -sL "$dir_url") + fi + + # Parse each entry + echo "$response" | jsonfilter -e '@[*].path' 2>/dev/null | while read -r path; do + type=$(echo "$response" | jsonfilter -e "@[@.path='$path'].type" 2>/dev/null) + + if [ "$type" = "file" ]; then + local rel_path="${path#$dir_path/}" + local dest_path="${dest_base}/${rel_path}" + fetch_file "$server_url" "$repo_owner" "$repo_name" "$path" "$dest_path" "$token" "$branch" + elif [ "$type" = "dir" ]; then + fetch_directory "$server_url" "$repo_owner" "$repo_name" "$path" "$dest_base" "$token" "$branch" + fi + done +} + +# Restore configs from backup +restore_configs() { + local backup_dir="$1" + local configs_dir="$backup_dir/configs" + + if [ ! -d "$configs_dir" ]; then + log_warn "No configs directory found in backup" + return 0 + fi + + log_info "Restoring UCI configurations..." + + local count=0 + for cfg in "$configs_dir"/*; do + [ -f "$cfg" ] || continue + local name=$(basename "$cfg") + + # Skip system-critical configs unless explicitly requested + case "$name" in + network|wireless|firewall) + log_warn "Skipping critical config: $name (use --include-network to restore)" + continue + ;; + esac + + cp "$cfg" "/etc/config/$name" + count=$((count + 1)) + done + + log_success "Restored $count configuration files" +} + +# Restore scripts +restore_scripts() { + local backup_dir="$1" + local scripts_dir="$backup_dir/scripts" + + if [ ! -d "$scripts_dir" ]; then + return 0 + fi + + log_info "Restoring custom scripts..." + + mkdir -p /usr/share/secubox/scripts + cp -r "$scripts_dir"/* /usr/share/secubox/scripts/ 2>/dev/null + chmod +x /usr/share/secubox/scripts/* 2>/dev/null + + log_success "Scripts restored" +} + +# Restore cron jobs +restore_cron() { + local backup_dir="$1" + local cron_file="$backup_dir/cron/root" + + if [ ! -f "$cron_file" ]; then + return 0 + fi + + log_info "Restoring cron jobs..." + + # Append to existing crontab (don't overwrite) + cat "$cron_file" >> /etc/crontabs/root 2>/dev/null + /etc/init.d/cron restart 2>/dev/null + + log_success "Cron jobs restored" +} + +# Install SecuBox packages if not present +install_secubox() { + log_info "Checking SecuBox installation..." + + # Check if secubox-core is installed + if ! opkg list-installed | grep -q "secubox-core"; then + log_info "SecuBox not installed, attempting installation..." + + # Try to add SecuBox feed + if ! grep -q "secubox" /etc/opkg/customfeeds.conf 2>/dev/null; then + log_warn "SecuBox feed not configured" + log_info "Please configure SecuBox feed manually and re-run this script" + return 1 + fi + + opkg update + opkg install secubox-core luci-app-secubox + fi + + log_success "SecuBox is installed" +} + +# Main restore function +do_restore() { + local server_url="$1" + local repo_owner="$2" + local repo_name="$3" + local token="$4" + local branch="${5:-master}" + + # Create temp directory + local backup_dir="/tmp/secubox-restore-$$" + mkdir -p "$backup_dir" + + # Fetch manifest first + manifest=$(fetch_manifest "$server_url" "$repo_owner" "$repo_name" "$token" "$branch") + + if [ -z "$manifest" ]; then + log_error "Failed to fetch backup manifest" + rm -rf "$backup_dir" + return 1 + fi + + # Display backup info + local backup_hostname=$(echo "$manifest" | jsonfilter -e '@.hostname' 2>/dev/null) + local backup_timestamp=$(echo "$manifest" | jsonfilter -e '@.timestamp' 2>/dev/null) + local backup_version=$(echo "$manifest" | jsonfilter -e '@.secubox_version' 2>/dev/null) + + echo "" + log_info "Backup Information:" + echo " Source Hostname: $backup_hostname" + echo " Backup Date: $backup_timestamp" + echo " SecuBox Version: $backup_version" + echo "" + + # Ask for confirmation if interactive + if [ -t 0 ]; then + printf "Proceed with restore? [y/N] " + read -r confirm + case "$confirm" in + [yY][eE][sS]|[yY]) ;; + *) log_info "Restore cancelled"; rm -rf "$backup_dir"; return 0 ;; + esac + fi + + # Fetch each component + log_info "Downloading backup components..." + + for component in configs profiles presets manifests scripts cron ssh certificates; do + log_info "Fetching $component..." + fetch_directory "$server_url" "$repo_owner" "$repo_name" "$component" "$backup_dir/$component" "$token" "$branch" 2>/dev/null || true + done + + # Perform restore + log_info "Applying backup..." + + restore_configs "$backup_dir" + restore_scripts "$backup_dir" + restore_cron "$backup_dir" + + # Save Gitea config for future backups + uci set secubox-p2p.gitea=gitea + uci set secubox-p2p.gitea.enabled='1' + uci set secubox-p2p.gitea.server_url="$server_url" + uci set secubox-p2p.gitea.repo_owner="$repo_owner" + uci set secubox-p2p.gitea.repo_name="$repo_name" + [ -n "$token" ] && uci set secubox-p2p.gitea.access_token="$token" + uci commit secubox-p2p + + # Cleanup + rm -rf "$backup_dir" + + log_success "Restore completed!" + echo "" + log_info "Please reboot to apply all changes: reboot" +} + +# Interactive mode - prompt for settings +interactive_mode() { + print_banner + echo "" + + check_openwrt + install_deps + + echo "" + log_info "Enter Gitea repository details:" + echo "" + + printf "Gitea Server URL (e.g., http://192.168.1.1:3000): " + read -r server_url + + printf "Repository Owner (username): " + read -r repo_owner + + printf "Repository Name: " + read -r repo_name + + printf "Access Token (leave empty for public repos): " + read -r token + + echo "" + do_restore "$server_url" "$repo_owner" "$repo_name" "$token" +} + +# Show usage +usage() { + cat << EOF +SecuBox Self-Recovery Bootstrap Script + +Usage: + $0 [options] [access-token] + $0 --interactive + +Options: + --interactive, -i Run in interactive mode with prompts + --branch, -b Git branch to restore from (default: master) + --include-network Also restore network/wireless/firewall configs + --help, -h Show this help message + +Examples: + # Interactive mode + $0 -i + + # Direct restore from public repo + $0 http://gitea.local:3000 admin secubox-backup + + # Restore with token + $0 http://gitea.local:3000 admin secubox-backup abc123token + + # Restore from specific branch + $0 -b dev http://gitea.local:3000 admin secubox-backup + +Quick Bootstrap (paste into new OpenWrt shell): + wget -qO- http://your-gitea/user/repo/raw/master/bootstrap.sh | sh + +EOF +} + +# Parse arguments +BRANCH="master" +INCLUDE_NETWORK=0 + +while [ $# -gt 0 ]; do + case "$1" in + --interactive|-i) + interactive_mode + exit 0 + ;; + --branch|-b) + BRANCH="$2" + shift 2 + ;; + --include-network) + INCLUDE_NETWORK=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + -*) + log_error "Unknown option: $1" + usage + exit 1 + ;; + *) + break + ;; + esac +done + +# If no arguments, run interactive +if [ $# -eq 0 ]; then + interactive_mode + exit 0 +fi + +# Validate arguments +if [ $# -lt 3 ]; then + log_error "Missing required arguments" + usage + exit 1 +fi + +SERVER_URL="$1" +REPO_OWNER="$2" +REPO_NAME="$3" +ACCESS_TOKEN="${4:-}" + +print_banner +echo "" +check_openwrt +install_deps +do_restore "$SERVER_URL" "$REPO_OWNER" "$REPO_NAME" "$ACCESS_TOKEN" "$BRANCH" diff --git a/package/secubox/secubox-p2p/root/usr/libexec/rpcd/luci.secubox-p2p b/package/secubox/secubox-p2p/root/usr/libexec/rpcd/luci.secubox-p2p index 05a785fb..35e6cf90 100644 --- a/package/secubox/secubox-p2p/root/usr/libexec/rpcd/luci.secubox-p2p +++ b/package/secubox/secubox-p2p/root/usr/libexec/rpcd/luci.secubox-p2p @@ -390,37 +390,202 @@ EOF backup_dir="/tmp/secubox-gitea-backup-$$" mkdir -p "$backup_dir" - # Collect configs - if [ "$(uci -q get secubox-p2p.gitea.include_configs)" = "1" ]; then - mkdir -p "$backup_dir/configs" - cp -r /etc/config/secubox* "$backup_dir/configs/" 2>/dev/null - cp -r /etc/config/network "$backup_dir/configs/" 2>/dev/null - cp -r /etc/config/firewall "$backup_dir/configs/" 2>/dev/null - cp -r /etc/config/wireless "$backup_dir/configs/" 2>/dev/null + # 1. UCI Configs - Core system configuration + mkdir -p "$backup_dir/configs" + for cfg in secubox secubox-appstore secubox-appstore-opkg secubox-exposure \ + secubox-netifyd secubox-p2p secubox-core network firewall \ + wireless dhcp system dropbear uhttpd haproxy crowdsec acme \ + wireguard wireguard_* gitea nodogsplash; do + [ -f "/etc/config/$cfg" ] && cp "/etc/config/$cfg" "$backup_dir/configs/" 2>/dev/null + done + + # 2. Profiles - Deployment templates + if [ -d "/usr/share/secubox/profiles" ]; then + mkdir -p "$backup_dir/profiles" + cp -r /usr/share/secubox/profiles/* "$backup_dir/profiles/" 2>/dev/null fi - # Collect package list - if [ "$(uci -q get secubox-p2p.gitea.include_packages)" = "1" ]; then - mkdir -p "$backup_dir/packages" - opkg list-installed > "$backup_dir/packages/installed.txt" 2>/dev/null + # 3. Presets - Settings presets + if [ -d "/etc/secubox/presets" ]; then + mkdir -p "$backup_dir/presets" + cp -r /etc/secubox/presets/* "$backup_dir/presets/" 2>/dev/null fi - # Collect scripts - if [ "$(uci -q get secubox-p2p.gitea.include_scripts)" = "1" ]; then + # 4. App Manifests - Plugin definitions + if [ -d "/usr/share/secubox/plugins" ]; then + mkdir -p "$backup_dir/manifests" + cp -r /usr/share/secubox/plugins/* "$backup_dir/manifests/" 2>/dev/null + fi + + # 5. Scripts - Automation scripts + if [ -d "/etc/secubox/scripts" ]; then mkdir -p "$backup_dir/scripts" cp -r /etc/secubox/scripts/* "$backup_dir/scripts/" 2>/dev/null fi - # Create manifest + # 6. Macros - Macro definitions + if [ -d "/etc/secubox/macros" ]; then + mkdir -p "$backup_dir/macros" + cp -r /etc/secubox/macros/* "$backup_dir/macros/" 2>/dev/null + fi + + # 7. Workflows - Automation workflows + if [ -d "/etc/secubox/workflows" ]; then + mkdir -p "$backup_dir/workflows" + cp -r /etc/secubox/workflows/* "$backup_dir/workflows/" 2>/dev/null + fi + + # 8. Package lists + mkdir -p "$backup_dir/packages" + opkg list-installed > "$backup_dir/packages/installed.txt" 2>/dev/null + # Also save SecuBox-specific package list + opkg list-installed | grep -E "^(secubox|luci-app-secubox)" > "$backup_dir/packages/secubox-packages.txt" 2>/dev/null + + # 9. Service states + mkdir -p "$backup_dir/services" + for svc in /etc/init.d/secubox*; do + [ -x "$svc" ] && basename "$svc" >> "$backup_dir/services/enabled.txt" + done + + # 10. Crontabs + if [ -f "/etc/crontabs/root" ]; then + mkdir -p "$backup_dir/cron" + cp /etc/crontabs/root "$backup_dir/cron/root" 2>/dev/null + fi + + # 11. SSH authorized keys (for mesh access) + if [ -f "/etc/dropbear/authorized_keys" ]; then + mkdir -p "$backup_dir/ssh" + cp /etc/dropbear/authorized_keys "$backup_dir/ssh/" 2>/dev/null + fi + + # 12. SSL Certificates (ACME) + if [ -d "/etc/acme" ]; then + mkdir -p "$backup_dir/certificates" + for cert in /etc/acme/*.cer; do + [ -f "$cert" ] && cp "$cert" "$backup_dir/certificates/" 2>/dev/null + done + fi + + # 13. HAProxy configs + if [ -d "/etc/haproxy" ]; then + mkdir -p "$backup_dir/haproxy" + cp /etc/haproxy/*.cfg "$backup_dir/haproxy/" 2>/dev/null + fi + + # 14. DNS/Hosts + mkdir -p "$backup_dir/dns" + [ -f "/etc/hosts" ] && cp /etc/hosts "$backup_dir/dns/" 2>/dev/null + [ -f "/etc/dnsmasq.conf" ] && cp /etc/dnsmasq.conf "$backup_dir/dns/" 2>/dev/null + [ -d "/etc/dnsmasq.d" ] && cp /etc/dnsmasq.d/* "$backup_dir/dns/" 2>/dev/null + + # 15. Device info + mkdir -p "$backup_dir/device" + ubus call system board > "$backup_dir/device/board.json" 2>/dev/null + cat /proc/cpuinfo > "$backup_dir/device/cpuinfo.txt" 2>/dev/null + free -h > "$backup_dir/device/memory.txt" 2>/dev/null + + # Create comprehensive manifest + config_count=$(find "$backup_dir/configs" -type f 2>/dev/null | wc -l) + profile_count=$(find "$backup_dir/profiles" -type f 2>/dev/null | wc -l) + manifest_count=$(find "$backup_dir/manifests" -type f 2>/dev/null | wc -l) + script_count=$(find "$backup_dir/scripts" -type f 2>/dev/null | wc -l) + cat > "$backup_dir/manifest.json" </dev/null || echo "unknown")", - "message": "$message" + "secubox_version": "$(cat /etc/secubox-version 2>/dev/null || echo "unknown")", + "openwrt_version": "$(. /etc/openwrt_release 2>/dev/null && echo $DISTRIB_RELEASE || echo unknown)", + "device_model": "$(cat /tmp/sysinfo/model 2>/dev/null || echo "unknown")", + "message": "$message", + "contents": { + "configs": $config_count, + "profiles": $profile_count, + "manifests": $manifest_count, + "scripts": $script_count + }, + "components": [ + "configs", + "profiles", + "presets", + "manifests", + "scripts", + "macros", + "workflows", + "packages", + "services", + "cron", + "ssh", + "certificates", + "haproxy", + "dns", + "device" + ] } MANIFEST + # Create bootstrap.sh for quick restore + cat > "$backup_dir/bootstrap.sh" <<'BOOTSTRAP' +#!/bin/sh +# SecuBox Quick Restore Bootstrap +# Run: wget -qO- URL | sh OR curl -sL URL | sh +set -e +SERVER_URL="__SERVER_URL__" +REPO_OWNER="__REPO_OWNER__" +REPO_NAME="__REPO_NAME__" + +echo "SecuBox Quick Restore" +echo "=====================" +echo "From: $SERVER_URL/$REPO_OWNER/$REPO_NAME" +echo "" + +# Check OpenWrt +[ -f /etc/openwrt_release ] || { echo "Error: Not OpenWrt"; exit 1; } + +# Install deps +opkg update >/dev/null 2>&1 || true +opkg install curl git-http 2>/dev/null || true + +# Download restore script +RESTORE_URL="$SERVER_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/contents/scripts/secubox-restore?ref=master" +SCRIPT=$(curl -sL "$RESTORE_URL" | jsonfilter -e '@.content' 2>/dev/null | base64 -d 2>/dev/null) + +if [ -n "$SCRIPT" ]; then + echo "$SCRIPT" > /tmp/secubox-restore + chmod +x /tmp/secubox-restore + /tmp/secubox-restore "$SERVER_URL" "$REPO_OWNER" "$REPO_NAME" +else + echo "Fallback: Direct config restore..." + # Minimal restore - fetch and apply configs + API_BASE="$SERVER_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/contents" + mkdir -p /tmp/secubox-restore-$$ + + # Get configs list + configs=$(curl -sL "$API_BASE/configs" | jsonfilter -e '@[*].name' 2>/dev/null) + for cfg in $configs; do + echo "Restoring: $cfg" + content=$(curl -sL "$API_BASE/configs/$cfg" | jsonfilter -e '@.content' 2>/dev/null | base64 -d) + [ -n "$content" ] && echo "$content" > "/etc/config/$cfg" + done + + echo "" + echo "Basic restore complete. Reboot recommended." +fi +BOOTSTRAP + + # Replace placeholders in bootstrap + sed -i "s|__SERVER_URL__|$server_url|g" "$backup_dir/bootstrap.sh" + sed -i "s|__REPO_OWNER__|$repo_owner|g" "$backup_dir/bootstrap.sh" + sed -i "s|__REPO_NAME__|$repo_name|g" "$backup_dir/bootstrap.sh" + + # Also copy the full restore script + mkdir -p "$backup_dir/scripts" + if [ -f "/usr/bin/secubox-restore" ]; then + cp /usr/bin/secubox-restore "$backup_dir/scripts/" + fi + # Push each file via Gitea API pushed_files=0 api_base="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents" @@ -429,24 +594,24 @@ MANIFEST rel_path="${file#$backup_dir/}" content=$(base64 "$file" | tr -d '\n') - # Check if file exists (to update vs create) - existing=$(curl -s "${api_base}/${rel_path}" \ + # Check if file exists (to update vs create) - use main branch + existing=$(curl -s "${api_base}/${rel_path}?ref=main" \ -H "Authorization: token $access_token" 2>/dev/null) sha=$(echo "$existing" | jsonfilter -e '@.sha' 2>/dev/null) if [ -n "$sha" ]; then - # Update existing file + # Update existing file with branch specification curl -s -X PUT "${api_base}/${rel_path}" \ -H "Authorization: token $access_token" \ -H "Content-Type: application/json" \ - -d "{\"message\":\"$message\",\"content\":\"$content\",\"sha\":\"$sha\"}" \ + -d "{\"message\":\"$message\",\"content\":\"$content\",\"sha\":\"$sha\",\"branch\":\"main\"}" \ >/dev/null 2>&1 else - # Create new file + # Create new file on main branch curl -s -X POST "${api_base}/${rel_path}" \ -H "Authorization: token $access_token" \ -H "Content-Type: application/json" \ - -d "{\"message\":\"$message\",\"content\":\"$content\"}" \ + -d "{\"message\":\"$message\",\"content\":\"$content\",\"branch\":\"main\"}" \ >/dev/null 2>&1 fi pushed_files=$((pushed_files + 1))