feat(p2p): Add backend Gitea and backup support via secubox-core RPCD
Add complete backend implementation for Gitea integration and local backups with admin permissions: RPCD Methods (luci.secubox-p2p): - get_gitea_config / set_gitea_config - Gitea server configuration - create_gitea_repo - Create new Gitea repository via API - list_gitea_repos - List user's Gitea repositories - get_gitea_commits - Fetch commit history - push_gitea_backup - Push config/packages/scripts to Gitea - pull_gitea_backup - Restore from Gitea commit - create_local_backup - Create local backup snapshot - list_local_backups - List available local backups - restore_local_backup - Restore from local backup UCI Config (secubox-p2p): - gitea section: server_url, repo_name, access_token, auto_backup options - backup section: backup_dir, max_backups, auto_cleanup Frontend (hub.js): - Updated createGiteaRepo() to use backend API - Updated backup functions to use backend storage - Added refreshGiteaCommits() for real API calls - Load function now fetches Gitea config and backup list Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
18a5f441ba
commit
5287a7b807
@ -213,6 +213,75 @@ var callSyncWGMirror = rpc.declare({
|
|||||||
expect: { success: false, synced_peers: 0 }
|
expect: { success: false, synced_peers: 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Gitea Integration
|
||||||
|
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 callListGiteaRepos = rpc.declare({
|
||||||
|
object: 'luci.secubox-p2p',
|
||||||
|
method: 'list_gitea_repos',
|
||||||
|
expect: { success: false, repos: [] }
|
||||||
|
});
|
||||||
|
|
||||||
|
var callGetGiteaCommits = rpc.declare({
|
||||||
|
object: 'luci.secubox-p2p',
|
||||||
|
method: 'get_gitea_commits',
|
||||||
|
params: ['limit'],
|
||||||
|
expect: { success: false, commits: [] }
|
||||||
|
});
|
||||||
|
|
||||||
|
var callPushGiteaBackup = rpc.declare({
|
||||||
|
object: 'luci.secubox-p2p',
|
||||||
|
method: 'push_gitea_backup',
|
||||||
|
params: ['message', 'components'],
|
||||||
|
expect: { success: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
var callPullGiteaBackup = rpc.declare({
|
||||||
|
object: 'luci.secubox-p2p',
|
||||||
|
method: 'pull_gitea_backup',
|
||||||
|
params: ['commit_sha'],
|
||||||
|
expect: { success: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Local Backup
|
||||||
|
var callCreateLocalBackup = rpc.declare({
|
||||||
|
object: 'luci.secubox-p2p',
|
||||||
|
method: 'create_local_backup',
|
||||||
|
params: ['name', 'components'],
|
||||||
|
expect: { success: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
var callListLocalBackups = rpc.declare({
|
||||||
|
object: 'luci.secubox-p2p',
|
||||||
|
method: 'list_local_backups',
|
||||||
|
expect: { success: false, backups: [] }
|
||||||
|
});
|
||||||
|
|
||||||
|
var callRestoreLocalBackup = rpc.declare({
|
||||||
|
object: 'luci.secubox-p2p',
|
||||||
|
method: 'restore_local_backup',
|
||||||
|
params: ['backup_id'],
|
||||||
|
expect: { success: false }
|
||||||
|
});
|
||||||
|
|
||||||
return baseclass.extend({
|
return baseclass.extend({
|
||||||
// Peers
|
// Peers
|
||||||
getPeers: function() { return callGetPeers(); },
|
getPeers: function() { return callGetPeers(); },
|
||||||
@ -269,5 +338,21 @@ return baseclass.extend({
|
|||||||
// WireGuard Mirror
|
// WireGuard Mirror
|
||||||
getWGMirrorConfig: function() { return callGetWGMirrorConfig(); },
|
getWGMirrorConfig: function() { return callGetWGMirrorConfig(); },
|
||||||
setWGMirrorConfig: function(config) { return callSetWGMirrorConfig(config); },
|
setWGMirrorConfig: function(config) { return callSetWGMirrorConfig(config); },
|
||||||
syncWGMirror: function() { return callSyncWGMirror(); }
|
syncWGMirror: function() { return callSyncWGMirror(); },
|
||||||
|
|
||||||
|
// Gitea Integration
|
||||||
|
getGiteaConfig: function() { return callGetGiteaConfig(); },
|
||||||
|
setGiteaConfig: function(config) { return callSetGiteaConfig(config); },
|
||||||
|
createGiteaRepo: function(name, description, isPrivate, initReadme) {
|
||||||
|
return callCreateGiteaRepo(name, description, isPrivate, initReadme);
|
||||||
|
},
|
||||||
|
listGiteaRepos: function() { return callListGiteaRepos(); },
|
||||||
|
getGiteaCommits: function(limit) { return callGetGiteaCommits(limit || 20); },
|
||||||
|
pushGiteaBackup: function(message, components) { return callPushGiteaBackup(message, components); },
|
||||||
|
pullGiteaBackup: function(commitSha) { return callPullGiteaBackup(commitSha); },
|
||||||
|
|
||||||
|
// Local Backup
|
||||||
|
createLocalBackup: function(name, components) { return callCreateLocalBackup(name, components); },
|
||||||
|
listLocalBackups: function() { return callListLocalBackups(); },
|
||||||
|
restoreLocalBackup: function(backupId) { return callRestoreLocalBackup(backupId); }
|
||||||
});
|
});
|
||||||
|
|||||||
@ -144,7 +144,9 @@ return view.extend({
|
|||||||
P2PAPI.getWireGuardConfig().catch(function() { return {}; }),
|
P2PAPI.getWireGuardConfig().catch(function() { return {}; }),
|
||||||
P2PAPI.getHAProxyConfig().catch(function() { return {}; }),
|
P2PAPI.getHAProxyConfig().catch(function() { return {}; }),
|
||||||
P2PAPI.getRegistry().catch(function() { return {}; }),
|
P2PAPI.getRegistry().catch(function() { return {}; }),
|
||||||
P2PAPI.healthCheck().catch(function() { return {}; })
|
P2PAPI.healthCheck().catch(function() { return {}; }),
|
||||||
|
P2PAPI.getGiteaConfig().catch(function() { return {}; }),
|
||||||
|
P2PAPI.listLocalBackups().catch(function() { return { backups: [] }; })
|
||||||
]).then(function(results) {
|
]).then(function(results) {
|
||||||
self.peers = results[0].peers || [];
|
self.peers = results[0].peers || [];
|
||||||
self.settings = results[1] || {};
|
self.settings = results[1] || {};
|
||||||
@ -156,11 +158,39 @@ return view.extend({
|
|||||||
self.registry = results[7] || {};
|
self.registry = results[7] || {};
|
||||||
self.health = results[8] || {};
|
self.health = results[8] || {};
|
||||||
|
|
||||||
|
// Populate Gitea config from backend
|
||||||
|
var giteaCfg = results[9] || {};
|
||||||
|
if (giteaCfg.server_url) self.giteaConfig.serverUrl = giteaCfg.server_url;
|
||||||
|
if (giteaCfg.repo_name) self.giteaConfig.repoName = giteaCfg.repo_name;
|
||||||
|
if (giteaCfg.repo_owner) self.giteaConfig.repoOwner = giteaCfg.repo_owner;
|
||||||
|
if (giteaCfg.enabled) self.giteaConfig.enabled = !!giteaCfg.enabled;
|
||||||
|
if (giteaCfg.has_token) self.giteaConfig.hasToken = giteaCfg.has_token;
|
||||||
|
|
||||||
|
// Populate local backups
|
||||||
|
var backupList = results[10] || {};
|
||||||
|
if (backupList.backups) self.meshBackupConfig.snapshots = backupList.backups;
|
||||||
|
|
||||||
// Populate hubRegistry from API
|
// Populate hubRegistry from API
|
||||||
if (self.registry.base_url) self.hubRegistry.baseUrl = self.registry.base_url;
|
if (self.registry.base_url) self.hubRegistry.baseUrl = self.registry.base_url;
|
||||||
if (self.registry.cache_enabled !== undefined) self.hubRegistry.cacheEnabled = self.registry.cache_enabled;
|
if (self.registry.cache_enabled !== undefined) self.hubRegistry.cacheEnabled = self.registry.cache_enabled;
|
||||||
if (self.registry.cache_ttl) self.hubRegistry.cacheTTL = self.registry.cache_ttl;
|
if (self.registry.cache_ttl) self.hubRegistry.cacheTTL = self.registry.cache_ttl;
|
||||||
|
|
||||||
|
// If Gitea is configured, fetch commits
|
||||||
|
if (self.giteaConfig.enabled && self.giteaConfig.serverUrl) {
|
||||||
|
P2PAPI.getGiteaCommits(20).then(function(result) {
|
||||||
|
if (result.success && result.commits) {
|
||||||
|
self.giteaConfig.commits = result.commits.map(function(c) {
|
||||||
|
return {
|
||||||
|
sha: c.sha,
|
||||||
|
message: c.commit ? c.commit.message : c.message,
|
||||||
|
date: c.commit ? new Date(c.commit.author.date).getTime() : Date.now()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
self.giteaConfig.lastFetch = Date.now();
|
||||||
|
}
|
||||||
|
}).catch(function() {});
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -1428,19 +1458,34 @@ return view.extend({
|
|||||||
|
|
||||||
createMeshBackup: function() {
|
createMeshBackup: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var snapshot = {
|
var backupName = 'backup-' + new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
|
||||||
id: 'snap-' + Date.now(),
|
|
||||||
timestamp: Date.now(),
|
ui.addNotification(null, E('p', '📸 Creating backup...'), 'info');
|
||||||
targets: this.meshBackupConfig.targets.slice(),
|
|
||||||
peers: this.peers.length,
|
P2PAPI.createLocalBackup(backupName, {
|
||||||
services: this.services.length
|
configs: true,
|
||||||
};
|
packages: true,
|
||||||
this.meshBackupConfig.snapshots.unshift(snapshot);
|
scripts: true
|
||||||
if (this.meshBackupConfig.snapshots.length > this.meshBackupConfig.maxSnapshots) {
|
}).then(function(result) {
|
||||||
this.meshBackupConfig.snapshots.pop();
|
if (result.success) {
|
||||||
}
|
// Update local snapshots list
|
||||||
this.meshBackupConfig.lastBackup = Date.now();
|
self.meshBackupConfig.snapshots.unshift({
|
||||||
ui.addNotification(null, E('p', '📸 Mesh backup created: ' + snapshot.id), 'info');
|
id: result.backup_id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
size: result.size,
|
||||||
|
path: result.path
|
||||||
|
});
|
||||||
|
if (self.meshBackupConfig.snapshots.length > self.meshBackupConfig.maxSnapshots) {
|
||||||
|
self.meshBackupConfig.snapshots.pop();
|
||||||
|
}
|
||||||
|
self.meshBackupConfig.lastBackup = Date.now();
|
||||||
|
ui.addNotification(null, E('p', '✅ Backup created: ' + result.backup_id + ' (' + result.size + ')'), 'success');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', '❌ Backup failed: ' + (result.error || 'Unknown error')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', '❌ Backup error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
showBackupHistoryModal: function() {
|
showBackupHistoryModal: function() {
|
||||||
@ -1479,7 +1524,21 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
restoreBackup: function(snapId) {
|
restoreBackup: function(snapId) {
|
||||||
|
var self = this;
|
||||||
ui.addNotification(null, E('p', '♻️ Restoring backup ' + snapId + '...'), 'info');
|
ui.addNotification(null, E('p', '♻️ Restoring backup ' + snapId + '...'), 'info');
|
||||||
|
|
||||||
|
P2PAPI.restoreLocalBackup(snapId).then(function(result) {
|
||||||
|
if (result.success) {
|
||||||
|
ui.addNotification(null, E('p', '✅ Restored ' + result.files_restored + ' files from ' + snapId), 'success');
|
||||||
|
if (result.pre_restore_backup) {
|
||||||
|
ui.addNotification(null, E('p', '💾 Pre-restore backup saved: ' + result.pre_restore_backup), 'info');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', '❌ Restore failed: ' + (result.error || 'Unknown error')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', '❌ Restore error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteBackup: function(snapId) {
|
deleteBackup: function(snapId) {
|
||||||
@ -1548,29 +1607,72 @@ return view.extend({
|
|||||||
|
|
||||||
fetchGiteaCommits: function() {
|
fetchGiteaCommits: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (!this.giteaConfig.serverUrl) {
|
if (!this.giteaConfig.enabled) {
|
||||||
ui.addNotification(null, E('p', 'Configure Gitea server first'), 'warning');
|
ui.addNotification(null, E('p', 'Configure Gitea server first'), 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ui.addNotification(null, E('p', '🔄 Fetching commits from Gitea...'), 'info');
|
ui.addNotification(null, E('p', '🔄 Fetching commits from Gitea...'), 'info');
|
||||||
// Simulate fetch
|
|
||||||
setTimeout(function() {
|
P2PAPI.getGiteaCommits(20).then(function(result) {
|
||||||
self.giteaConfig.commits = [
|
if (result.success && result.commits) {
|
||||||
{ sha: 'abc1234', message: 'feat(p2p): Add mesh backup', date: Date.now() - 3600000 },
|
self.giteaConfig.commits = result.commits.map(function(c) {
|
||||||
{ sha: 'def5678', message: 'fix(dns): Bridge sync issue', date: Date.now() - 7200000 },
|
return {
|
||||||
{ sha: 'ghi9012', message: 'chore: Update dependencies', date: Date.now() - 86400000 }
|
sha: c.sha,
|
||||||
];
|
message: c.commit ? c.commit.message : c.message,
|
||||||
self.giteaConfig.lastFetch = Date.now();
|
date: c.commit ? new Date(c.commit.author.date).getTime() : Date.now()
|
||||||
ui.addNotification(null, E('p', '✅ Fetched ' + self.giteaConfig.commits.length + ' commits'), 'success');
|
};
|
||||||
}, 1000);
|
});
|
||||||
|
self.giteaConfig.lastFetch = Date.now();
|
||||||
|
ui.addNotification(null, E('p', '✅ Fetched ' + self.giteaConfig.commits.length + ' commits'), 'success');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', '⚠️ ' + (result.error || 'Failed to fetch commits')), 'warning');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', '❌ Error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
pushToGitea: function() {
|
pushToGitea: function() {
|
||||||
if (!this.giteaConfig.serverUrl) {
|
var self = this;
|
||||||
|
if (!this.giteaConfig.enabled) {
|
||||||
ui.addNotification(null, E('p', 'Configure Gitea server first'), 'warning');
|
ui.addNotification(null, E('p', 'Configure Gitea server first'), 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var commitMsg = 'SecuBox backup ' + new Date().toISOString().substring(0, 19).replace('T', ' ');
|
||||||
ui.addNotification(null, E('p', '📤 Pushing config to Gitea...'), 'info');
|
ui.addNotification(null, E('p', '📤 Pushing config to Gitea...'), 'info');
|
||||||
|
|
||||||
|
P2PAPI.pushGiteaBackup(commitMsg, {}).then(function(result) {
|
||||||
|
if (result.success) {
|
||||||
|
ui.addNotification(null, E('p', '✅ Pushed ' + result.files_pushed + ' files to Gitea'), 'success');
|
||||||
|
// Refresh commits
|
||||||
|
self.refreshGiteaCommits();
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', '❌ Push failed: ' + (result.error || 'Unknown error')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', '❌ Error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
pullFromGitea: function(commitSha) {
|
||||||
|
var self = this;
|
||||||
|
if (!this.giteaConfig.enabled) {
|
||||||
|
ui.addNotification(null, E('p', 'Configure Gitea server first'), 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.addNotification(null, E('p', '📥 Pulling from Gitea' + (commitSha ? ' (commit ' + commitSha.substring(0, 7) + ')' : '') + '...'), 'info');
|
||||||
|
|
||||||
|
P2PAPI.pullGiteaBackup(commitSha || '').then(function(result) {
|
||||||
|
if (result.success) {
|
||||||
|
ui.addNotification(null, E('p', '✅ Restored ' + result.files_restored + ' files from Gitea'), 'success');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', '❌ Pull failed: ' + (result.error || 'Unknown error')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', '❌ Error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
createGiteaRepo: function() {
|
createGiteaRepo: function() {
|
||||||
@ -1624,47 +1726,61 @@ return view.extend({
|
|||||||
E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() {
|
E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() {
|
||||||
var serverUrl = document.getElementById('create-gitea-url').value;
|
var serverUrl = document.getElementById('create-gitea-url').value;
|
||||||
var repoName = document.getElementById('create-repo-name').value;
|
var repoName = document.getElementById('create-repo-name').value;
|
||||||
|
var repoDesc = document.getElementById('create-repo-desc').value;
|
||||||
var token = document.getElementById('create-gitea-token').value;
|
var token = document.getElementById('create-gitea-token').value;
|
||||||
|
var isPrivate = document.getElementById('create-private').checked;
|
||||||
|
var initReadme = document.getElementById('create-init').checked;
|
||||||
var pushState = document.getElementById('create-push-state').checked;
|
var pushState = document.getElementById('create-push-state').checked;
|
||||||
|
|
||||||
if (!serverUrl || !repoName) {
|
if (!serverUrl || !repoName || !token) {
|
||||||
ui.addNotification(null, E('p', 'Server URL and repo name required'), 'warning');
|
ui.addNotification(null, E('p', 'Server URL, repo name and access token required'), 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save config
|
|
||||||
self.giteaConfig.serverUrl = serverUrl;
|
|
||||||
self.giteaConfig.repoName = repoName;
|
|
||||||
self.giteaConfig.token = token;
|
|
||||||
self.giteaConfig.repoOwner = 'secubox';
|
|
||||||
self.giteaConfig.enabled = true;
|
|
||||||
|
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
ui.addNotification(null, E('p', '➕ Creating repository ' + repoName + '...'), 'info');
|
ui.addNotification(null, E('p', '➕ Creating repository ' + repoName + '...'), 'info');
|
||||||
|
|
||||||
// Simulate repo creation
|
// First save the Gitea config to backend
|
||||||
setTimeout(function() {
|
P2PAPI.setGiteaConfig({
|
||||||
// Add initial commit
|
server_url: serverUrl,
|
||||||
self.giteaConfig.commits = [{
|
repo_name: repoName,
|
||||||
sha: 'init' + Date.now().toString(16).substring(0, 4),
|
access_token: token,
|
||||||
message: 'Initial commit: SecuBox P2P Hub config',
|
enabled: 1
|
||||||
date: Date.now()
|
}).then(function() {
|
||||||
}];
|
// Now create the repository via backend
|
||||||
self.giteaConfig.lastFetch = Date.now();
|
return P2PAPI.createGiteaRepo(repoName, repoDesc, isPrivate, initReadme);
|
||||||
|
}).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.lastFetch = Date.now();
|
||||||
|
|
||||||
ui.addNotification(null, E('p', '✅ Repository created: ' + repoName), 'success');
|
ui.addNotification(null, E('p', '✅ Repository created: ' + repoName), 'success');
|
||||||
|
|
||||||
if (pushState) {
|
// Push current state if requested
|
||||||
setTimeout(function() {
|
if (pushState) {
|
||||||
self.giteaConfig.commits.unshift({
|
ui.addNotification(null, E('p', '📤 Pushing current state...'), 'info');
|
||||||
sha: 'state' + Date.now().toString(16).substring(0, 4),
|
P2PAPI.pushGiteaBackup('Initial SecuBox mesh state', {}).then(function(pushResult) {
|
||||||
message: 'feat: Add current mesh state and config',
|
if (pushResult.success) {
|
||||||
date: Date.now()
|
ui.addNotification(null, E('p', '📤 Current state pushed (' + pushResult.files_pushed + ' files)'), 'success');
|
||||||
|
// Refresh commits
|
||||||
|
self.refreshGiteaCommits();
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', 'Push failed: ' + (pushResult.error || 'Unknown error')), 'error');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ui.addNotification(null, E('p', '📤 Current state pushed to ' + repoName), 'success');
|
} else {
|
||||||
}, 1000);
|
self.refreshGiteaCommits();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', 'Failed to create repo: ' + (result.error || 'Unknown error')), 'error');
|
||||||
}
|
}
|
||||||
}, 1500);
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
} }, '➕ Create Repository')
|
} }, '➕ Create Repository')
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
@ -1707,28 +1823,90 @@ return view.extend({
|
|||||||
E('div', { 'class': 'modal-actions' }, [
|
E('div', { 'class': 'modal-actions' }, [
|
||||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, 'Cancel'),
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, 'Cancel'),
|
||||||
E('button', { 'class': 'cbi-button', 'click': function() {
|
E('button', { 'class': 'cbi-button', 'click': function() {
|
||||||
self.giteaConfig.serverUrl = document.getElementById('gitea-url').value;
|
var serverUrl = document.getElementById('gitea-url').value;
|
||||||
self.giteaConfig.repoOwner = document.getElementById('gitea-owner').value;
|
var repoOwner = document.getElementById('gitea-owner').value;
|
||||||
self.giteaConfig.repoName = document.getElementById('gitea-repo').value;
|
var repoName = document.getElementById('gitea-repo').value;
|
||||||
self.giteaConfig.branch = document.getElementById('gitea-branch').value || 'main';
|
var token = document.getElementById('gitea-token').value;
|
||||||
self.giteaConfig.token = document.getElementById('gitea-token').value;
|
|
||||||
ui.hideModal();
|
// Test connection via backend
|
||||||
ui.addNotification(null, E('p', 'Gitea configuration saved'), 'info');
|
ui.addNotification(null, E('p', '🔄 Testing connection...'), 'info');
|
||||||
if (self.giteaConfig.serverUrl) self.fetchGiteaCommits();
|
P2PAPI.setGiteaConfig({
|
||||||
|
server_url: serverUrl,
|
||||||
|
repo_owner: repoOwner,
|
||||||
|
repo_name: repoName,
|
||||||
|
access_token: token
|
||||||
|
}).then(function() {
|
||||||
|
return P2PAPI.getGiteaCommits(5);
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.success) {
|
||||||
|
self.giteaConfig.serverUrl = serverUrl;
|
||||||
|
self.giteaConfig.repoOwner = repoOwner;
|
||||||
|
self.giteaConfig.repoName = repoName;
|
||||||
|
self.giteaConfig.token = token;
|
||||||
|
self.giteaConfig.enabled = true;
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null, E('p', '✅ Connection successful! ' + result.commits.length + ' commits found'), 'success');
|
||||||
|
self.refreshGiteaCommits();
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', '⚠️ ' + (result.error || 'Connection failed')), 'warning');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', '❌ Error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
} }, 'Test Connection'),
|
} }, 'Test Connection'),
|
||||||
E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() {
|
E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() {
|
||||||
self.giteaConfig.serverUrl = document.getElementById('gitea-url').value;
|
var serverUrl = document.getElementById('gitea-url').value;
|
||||||
self.giteaConfig.repoOwner = document.getElementById('gitea-owner').value;
|
var repoOwner = document.getElementById('gitea-owner').value;
|
||||||
self.giteaConfig.repoName = document.getElementById('gitea-repo').value;
|
var repoName = document.getElementById('gitea-repo').value;
|
||||||
self.giteaConfig.branch = document.getElementById('gitea-branch').value || 'main';
|
var branch = document.getElementById('gitea-branch').value || 'main';
|
||||||
self.giteaConfig.token = document.getElementById('gitea-token').value;
|
var token = document.getElementById('gitea-token').value;
|
||||||
ui.hideModal();
|
|
||||||
ui.addNotification(null, E('p', 'Gitea configuration saved'), 'info');
|
// Save config via backend
|
||||||
|
P2PAPI.setGiteaConfig({
|
||||||
|
server_url: serverUrl,
|
||||||
|
repo_owner: repoOwner,
|
||||||
|
repo_name: repoName,
|
||||||
|
access_token: token,
|
||||||
|
enabled: 1
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.success) {
|
||||||
|
self.giteaConfig.serverUrl = serverUrl;
|
||||||
|
self.giteaConfig.repoOwner = repoOwner;
|
||||||
|
self.giteaConfig.repoName = repoName;
|
||||||
|
self.giteaConfig.branch = branch;
|
||||||
|
self.giteaConfig.token = token;
|
||||||
|
self.giteaConfig.enabled = true;
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null, E('p', '✅ Gitea configuration saved'), 'success');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', '❌ Failed to save config'), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', '❌ Error: ' + err.message), 'error');
|
||||||
|
});
|
||||||
} }, 'Save')
|
} }, 'Save')
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refreshGiteaCommits: function() {
|
||||||
|
var self = this;
|
||||||
|
P2PAPI.getGiteaCommits(20).then(function(result) {
|
||||||
|
if (result.success && result.commits) {
|
||||||
|
self.giteaConfig.commits = result.commits.map(function(c) {
|
||||||
|
return {
|
||||||
|
sha: c.sha,
|
||||||
|
message: c.commit ? c.commit.message : c.message,
|
||||||
|
date: c.commit ? new Date(c.commit.author.date).getTime() : Date.now()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
self.giteaConfig.lastFetch = Date.now();
|
||||||
|
}
|
||||||
|
}).catch(function() {
|
||||||
|
// Silent fail for background refresh
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== Component Sources Actions ====================
|
// ==================== Component Sources Actions ====================
|
||||||
toggleComponentSource: function(key, enabled) {
|
toggleComponentSource: function(key, enabled) {
|
||||||
this.componentSources[key].enabled = enabled;
|
this.componentSources[key].enabled = enabled;
|
||||||
|
|||||||
@ -34,3 +34,22 @@ config maas 'maas'
|
|||||||
option enabled '0'
|
option enabled '0'
|
||||||
option auto_register '1'
|
option auto_register '1'
|
||||||
option sync_interval '60'
|
option sync_interval '60'
|
||||||
|
|
||||||
|
config gitea 'gitea'
|
||||||
|
option enabled '0'
|
||||||
|
option server_url ''
|
||||||
|
option repo_name 'secubox-backup'
|
||||||
|
option repo_owner ''
|
||||||
|
option access_token ''
|
||||||
|
option auto_backup '0'
|
||||||
|
option backup_interval '3600'
|
||||||
|
option backup_on_change '1'
|
||||||
|
option include_configs '1'
|
||||||
|
option include_packages '1'
|
||||||
|
option include_scripts '1'
|
||||||
|
|
||||||
|
config backup 'backup'
|
||||||
|
option enabled '1'
|
||||||
|
option backup_dir '/etc/secubox/backups'
|
||||||
|
option max_backups '10'
|
||||||
|
option auto_cleanup '1'
|
||||||
|
|||||||
@ -27,7 +27,17 @@ case "$1" in
|
|||||||
"set_haproxy_config": { "config": "object" },
|
"set_haproxy_config": { "config": "object" },
|
||||||
"get_registry": {},
|
"get_registry": {},
|
||||||
"register_url": { "short_url": "string", "target_url": "string" },
|
"register_url": { "short_url": "string", "target_url": "string" },
|
||||||
"health_check": {}
|
"health_check": {},
|
||||||
|
"get_gitea_config": {},
|
||||||
|
"set_gitea_config": { "config": "object" },
|
||||||
|
"create_gitea_repo": { "name": "string", "description": "string", "private": true, "init_readme": true },
|
||||||
|
"list_gitea_repos": {},
|
||||||
|
"get_gitea_commits": { "limit": 20 },
|
||||||
|
"push_gitea_backup": { "message": "string", "components": "object" },
|
||||||
|
"pull_gitea_backup": { "commit_sha": "string" },
|
||||||
|
"create_local_backup": { "name": "string", "components": "object" },
|
||||||
|
"list_local_backups": {},
|
||||||
|
"restore_local_backup": { "backup_id": "string" }
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
;;
|
;;
|
||||||
@ -219,6 +229,389 @@ EOF
|
|||||||
EOF
|
EOF
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
get_gitea_config)
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"enabled": $(uci -q get secubox-p2p.gitea.enabled || echo 0),
|
||||||
|
"server_url": "$(uci -q get secubox-p2p.gitea.server_url || echo "")",
|
||||||
|
"repo_name": "$(uci -q get secubox-p2p.gitea.repo_name || echo "secubox-backup")",
|
||||||
|
"repo_owner": "$(uci -q get secubox-p2p.gitea.repo_owner || echo "")",
|
||||||
|
"auto_backup": $(uci -q get secubox-p2p.gitea.auto_backup || echo 0),
|
||||||
|
"backup_interval": $(uci -q get secubox-p2p.gitea.backup_interval || echo 3600),
|
||||||
|
"backup_on_change": $(uci -q get secubox-p2p.gitea.backup_on_change || echo 1),
|
||||||
|
"include_configs": $(uci -q get secubox-p2p.gitea.include_configs || echo 1),
|
||||||
|
"include_packages": $(uci -q get secubox-p2p.gitea.include_packages || echo 1),
|
||||||
|
"include_scripts": $(uci -q get secubox-p2p.gitea.include_scripts || echo 1),
|
||||||
|
"has_token": $([ -n "$(uci -q get secubox-p2p.gitea.access_token)" ] && echo "true" || echo "false")
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
|
||||||
|
set_gitea_config)
|
||||||
|
read input
|
||||||
|
server_url=$(echo "$input" | jsonfilter -e '@.config.server_url' 2>/dev/null)
|
||||||
|
repo_name=$(echo "$input" | jsonfilter -e '@.config.repo_name' 2>/dev/null)
|
||||||
|
repo_owner=$(echo "$input" | jsonfilter -e '@.config.repo_owner' 2>/dev/null)
|
||||||
|
access_token=$(echo "$input" | jsonfilter -e '@.config.access_token' 2>/dev/null)
|
||||||
|
enabled=$(echo "$input" | jsonfilter -e '@.config.enabled' 2>/dev/null)
|
||||||
|
auto_backup=$(echo "$input" | jsonfilter -e '@.config.auto_backup' 2>/dev/null)
|
||||||
|
backup_interval=$(echo "$input" | jsonfilter -e '@.config.backup_interval' 2>/dev/null)
|
||||||
|
|
||||||
|
[ -n "$server_url" ] && uci set secubox-p2p.gitea.server_url="$server_url"
|
||||||
|
[ -n "$repo_name" ] && uci set secubox-p2p.gitea.repo_name="$repo_name"
|
||||||
|
[ -n "$repo_owner" ] && uci set secubox-p2p.gitea.repo_owner="$repo_owner"
|
||||||
|
[ -n "$access_token" ] && uci set secubox-p2p.gitea.access_token="$access_token"
|
||||||
|
[ -n "$enabled" ] && uci set secubox-p2p.gitea.enabled="$enabled"
|
||||||
|
[ -n "$auto_backup" ] && uci set secubox-p2p.gitea.auto_backup="$auto_backup"
|
||||||
|
[ -n "$backup_interval" ] && uci set secubox-p2p.gitea.backup_interval="$backup_interval"
|
||||||
|
uci commit secubox-p2p
|
||||||
|
|
||||||
|
echo '{"success":true}'
|
||||||
|
;;
|
||||||
|
|
||||||
|
create_gitea_repo)
|
||||||
|
read input
|
||||||
|
repo_name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
|
||||||
|
description=$(echo "$input" | jsonfilter -e '@.description' 2>/dev/null)
|
||||||
|
is_private=$(echo "$input" | jsonfilter -e '@.private' 2>/dev/null)
|
||||||
|
init_readme=$(echo "$input" | jsonfilter -e '@.init_readme' 2>/dev/null)
|
||||||
|
|
||||||
|
server_url=$(uci -q get secubox-p2p.gitea.server_url)
|
||||||
|
access_token=$(uci -q get secubox-p2p.gitea.access_token)
|
||||||
|
|
||||||
|
if [ -z "$server_url" ] || [ -z "$access_token" ]; then
|
||||||
|
echo '{"success":false,"error":"Gitea server URL and access token required"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$repo_name" ]; then
|
||||||
|
echo '{"success":false,"error":"Repository name required"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create repo via Gitea API
|
||||||
|
api_url="${server_url}/api/v1/user/repos"
|
||||||
|
[ "$is_private" = "true" ] && private_val="true" || private_val="false"
|
||||||
|
[ "$init_readme" = "true" ] && readme_val="true" || readme_val="false"
|
||||||
|
|
||||||
|
response=$(curl -s -X POST "$api_url" \
|
||||||
|
-H "Authorization: token $access_token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"$repo_name\",\"description\":\"$description\",\"private\":$private_val,\"auto_init\":$readme_val}" \
|
||||||
|
2>/dev/null)
|
||||||
|
|
||||||
|
if echo "$response" | jsonfilter -e '@.id' >/dev/null 2>&1; then
|
||||||
|
clone_url=$(echo "$response" | jsonfilter -e '@.clone_url' 2>/dev/null)
|
||||||
|
html_url=$(echo "$response" | jsonfilter -e '@.html_url' 2>/dev/null)
|
||||||
|
owner=$(echo "$response" | jsonfilter -e '@.owner.login' 2>/dev/null)
|
||||||
|
|
||||||
|
# Save repo config
|
||||||
|
uci set secubox-p2p.gitea.repo_name="$repo_name"
|
||||||
|
uci set secubox-p2p.gitea.repo_owner="$owner"
|
||||||
|
uci set secubox-p2p.gitea.enabled=1
|
||||||
|
uci commit secubox-p2p
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"repo_name": "$repo_name",
|
||||||
|
"clone_url": "$clone_url",
|
||||||
|
"html_url": "$html_url",
|
||||||
|
"owner": "$owner"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
error_msg=$(echo "$response" | jsonfilter -e '@.message' 2>/dev/null || echo "Failed to create repository")
|
||||||
|
echo "{\"success\":false,\"error\":\"$error_msg\"}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
list_gitea_repos)
|
||||||
|
server_url=$(uci -q get secubox-p2p.gitea.server_url)
|
||||||
|
access_token=$(uci -q get secubox-p2p.gitea.access_token)
|
||||||
|
|
||||||
|
if [ -z "$server_url" ] || [ -z "$access_token" ]; then
|
||||||
|
echo '{"success":false,"repos":[],"error":"Gitea not configured"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
response=$(curl -s "${server_url}/api/v1/user/repos" \
|
||||||
|
-H "Authorization: token $access_token" \
|
||||||
|
2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
echo "{\"success\":true,\"repos\":$response}"
|
||||||
|
else
|
||||||
|
echo '{"success":false,"repos":[],"error":"Failed to fetch repositories"}'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
get_gitea_commits)
|
||||||
|
read input
|
||||||
|
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null || echo "20")
|
||||||
|
|
||||||
|
server_url=$(uci -q get secubox-p2p.gitea.server_url)
|
||||||
|
access_token=$(uci -q get secubox-p2p.gitea.access_token)
|
||||||
|
repo_owner=$(uci -q get secubox-p2p.gitea.repo_owner)
|
||||||
|
repo_name=$(uci -q get secubox-p2p.gitea.repo_name)
|
||||||
|
|
||||||
|
if [ -z "$server_url" ] || [ -z "$access_token" ] || [ -z "$repo_owner" ] || [ -z "$repo_name" ]; then
|
||||||
|
echo '{"success":false,"commits":[],"error":"Gitea repository not configured"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
response=$(curl -s "${server_url}/api/v1/repos/${repo_owner}/${repo_name}/commits?limit=${limit}" \
|
||||||
|
-H "Authorization: token $access_token" \
|
||||||
|
2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ] && echo "$response" | jsonfilter -e '@[0].sha' >/dev/null 2>&1; then
|
||||||
|
echo "{\"success\":true,\"commits\":$response}"
|
||||||
|
else
|
||||||
|
echo '{"success":false,"commits":[],"error":"Failed to fetch commits or repository empty"}'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
push_gitea_backup)
|
||||||
|
read input
|
||||||
|
message=$(echo "$input" | jsonfilter -e '@.message' 2>/dev/null || echo "SecuBox backup $(date +%Y%m%d-%H%M%S)")
|
||||||
|
components=$(echo "$input" | jsonfilter -e '@.components' 2>/dev/null)
|
||||||
|
|
||||||
|
server_url=$(uci -q get secubox-p2p.gitea.server_url)
|
||||||
|
access_token=$(uci -q get secubox-p2p.gitea.access_token)
|
||||||
|
repo_owner=$(uci -q get secubox-p2p.gitea.repo_owner)
|
||||||
|
repo_name=$(uci -q get secubox-p2p.gitea.repo_name)
|
||||||
|
|
||||||
|
if [ -z "$server_url" ] || [ -z "$access_token" ] || [ -z "$repo_owner" ] || [ -z "$repo_name" ]; then
|
||||||
|
echo '{"success":false,"error":"Gitea repository not configured"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create backup directory
|
||||||
|
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
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect scripts
|
||||||
|
if [ "$(uci -q get secubox-p2p.gitea.include_scripts)" = "1" ]; then
|
||||||
|
mkdir -p "$backup_dir/scripts"
|
||||||
|
cp -r /etc/secubox/scripts/* "$backup_dir/scripts/" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create manifest
|
||||||
|
cat > "$backup_dir/manifest.json" <<MANIFEST
|
||||||
|
{
|
||||||
|
"timestamp": "$(date -Iseconds)",
|
||||||
|
"hostname": "$(uci -q get system.@system[0].hostname || echo "secubox")",
|
||||||
|
"version": "$(cat /etc/secubox-version 2>/dev/null || echo "unknown")",
|
||||||
|
"message": "$message"
|
||||||
|
}
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# Push each file via Gitea API
|
||||||
|
pushed_files=0
|
||||||
|
api_base="${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents"
|
||||||
|
|
||||||
|
for file in $(find "$backup_dir" -type f); do
|
||||||
|
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}" \
|
||||||
|
-H "Authorization: token $access_token" 2>/dev/null)
|
||||||
|
sha=$(echo "$existing" | jsonfilter -e '@.sha' 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$sha" ]; then
|
||||||
|
# Update existing file
|
||||||
|
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\"}" \
|
||||||
|
>/dev/null 2>&1
|
||||||
|
else
|
||||||
|
# Create new file
|
||||||
|
curl -s -X POST "${api_base}/${rel_path}" \
|
||||||
|
-H "Authorization: token $access_token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"message\":\"$message\",\"content\":\"$content\"}" \
|
||||||
|
>/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
pushed_files=$((pushed_files + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf "$backup_dir"
|
||||||
|
|
||||||
|
echo "{\"success\":true,\"files_pushed\":$pushed_files,\"message\":\"$message\"}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
pull_gitea_backup)
|
||||||
|
read input
|
||||||
|
commit_sha=$(echo "$input" | jsonfilter -e '@.commit_sha' 2>/dev/null)
|
||||||
|
|
||||||
|
server_url=$(uci -q get secubox-p2p.gitea.server_url)
|
||||||
|
access_token=$(uci -q get secubox-p2p.gitea.access_token)
|
||||||
|
repo_owner=$(uci -q get secubox-p2p.gitea.repo_owner)
|
||||||
|
repo_name=$(uci -q get secubox-p2p.gitea.repo_name)
|
||||||
|
|
||||||
|
if [ -z "$server_url" ] || [ -z "$access_token" ] || [ -z "$repo_owner" ] || [ -z "$repo_name" ]; then
|
||||||
|
echo '{"success":false,"error":"Gitea repository not configured"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get file tree at commit
|
||||||
|
ref_param=""
|
||||||
|
[ -n "$commit_sha" ] && ref_param="?ref=$commit_sha"
|
||||||
|
|
||||||
|
tree=$(curl -s "${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents${ref_param}" \
|
||||||
|
-H "Authorization: token $access_token" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$tree" ]; then
|
||||||
|
echo '{"success":false,"error":"Failed to fetch repository contents"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create restore directory
|
||||||
|
restore_dir="/tmp/secubox-restore-$$"
|
||||||
|
mkdir -p "$restore_dir"
|
||||||
|
restored_files=0
|
||||||
|
|
||||||
|
# Download configs directory
|
||||||
|
configs=$(curl -s "${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/configs${ref_param}" \
|
||||||
|
-H "Authorization: token $access_token" 2>/dev/null)
|
||||||
|
|
||||||
|
if echo "$configs" | jsonfilter -e '@[0].name' >/dev/null 2>&1; then
|
||||||
|
mkdir -p "$restore_dir/configs"
|
||||||
|
for file_info in $(echo "$configs" | jsonfilter -e '@[*].name'); do
|
||||||
|
file_content=$(curl -s "${server_url}/api/v1/repos/${repo_owner}/${repo_name}/contents/configs/${file_info}${ref_param}" \
|
||||||
|
-H "Authorization: token $access_token" 2>/dev/null)
|
||||||
|
content_b64=$(echo "$file_content" | jsonfilter -e '@.content' 2>/dev/null)
|
||||||
|
if [ -n "$content_b64" ]; then
|
||||||
|
echo "$content_b64" | base64 -d > "$restore_dir/configs/$file_info"
|
||||||
|
restored_files=$((restored_files + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Apply configs (with backup)
|
||||||
|
if [ -d "$restore_dir/configs" ]; then
|
||||||
|
cp -r /etc/config /etc/config.bak.$(date +%Y%m%d%H%M%S) 2>/dev/null
|
||||||
|
for cfg in "$restore_dir"/configs/secubox*; do
|
||||||
|
[ -f "$cfg" ] && cp "$cfg" /etc/config/ 2>/dev/null
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf "$restore_dir"
|
||||||
|
|
||||||
|
echo "{\"success\":true,\"files_restored\":$restored_files,\"commit\":\"${commit_sha:-HEAD}\"}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
create_local_backup)
|
||||||
|
read input
|
||||||
|
backup_name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null || echo "backup-$(date +%Y%m%d-%H%M%S)")
|
||||||
|
|
||||||
|
backup_base=$(uci -q get secubox-p2p.backup.backup_dir || echo "/etc/secubox/backups")
|
||||||
|
mkdir -p "$backup_base"
|
||||||
|
|
||||||
|
backup_dir="$backup_base/$backup_name"
|
||||||
|
mkdir -p "$backup_dir"
|
||||||
|
|
||||||
|
# Backup configs
|
||||||
|
mkdir -p "$backup_dir/configs"
|
||||||
|
cp -r /etc/config/secubox* "$backup_dir/configs/" 2>/dev/null
|
||||||
|
cp /etc/config/network "$backup_dir/configs/" 2>/dev/null
|
||||||
|
cp /etc/config/firewall "$backup_dir/configs/" 2>/dev/null
|
||||||
|
cp /etc/config/wireless "$backup_dir/configs/" 2>/dev/null
|
||||||
|
|
||||||
|
# Backup package list
|
||||||
|
opkg list-installed > "$backup_dir/packages.txt" 2>/dev/null
|
||||||
|
|
||||||
|
# Create manifest
|
||||||
|
cat > "$backup_dir/manifest.json" <<MANIFEST
|
||||||
|
{
|
||||||
|
"name": "$backup_name",
|
||||||
|
"timestamp": "$(date -Iseconds)",
|
||||||
|
"hostname": "$(uci -q get system.@system[0].hostname || echo "secubox")",
|
||||||
|
"version": "$(cat /etc/secubox-version 2>/dev/null || echo "unknown")"
|
||||||
|
}
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# Cleanup old backups
|
||||||
|
max_backups=$(uci -q get secubox-p2p.backup.max_backups || echo 10)
|
||||||
|
if [ "$(uci -q get secubox-p2p.backup.auto_cleanup)" = "1" ]; then
|
||||||
|
ls -1dt "$backup_base"/*/ 2>/dev/null | tail -n +$((max_backups + 1)) | xargs rm -rf 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup_size=$(du -sh "$backup_dir" 2>/dev/null | cut -f1)
|
||||||
|
echo "{\"success\":true,\"backup_id\":\"$backup_name\",\"path\":\"$backup_dir\",\"size\":\"$backup_size\"}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
list_local_backups)
|
||||||
|
backup_base=$(uci -q get secubox-p2p.backup.backup_dir || echo "/etc/secubox/backups")
|
||||||
|
mkdir -p "$backup_base"
|
||||||
|
|
||||||
|
echo '{"success":true,"backups":['
|
||||||
|
first=1
|
||||||
|
for dir in "$backup_base"/*/; do
|
||||||
|
[ -d "$dir" ] || continue
|
||||||
|
backup_id=$(basename "$dir")
|
||||||
|
if [ -f "$dir/manifest.json" ]; then
|
||||||
|
timestamp=$(jsonfilter -i "$dir/manifest.json" -e '@.timestamp' 2>/dev/null || echo "")
|
||||||
|
hostname=$(jsonfilter -i "$dir/manifest.json" -e '@.hostname' 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
timestamp=""
|
||||||
|
hostname=""
|
||||||
|
fi
|
||||||
|
size=$(du -sh "$dir" 2>/dev/null | cut -f1)
|
||||||
|
[ $first -eq 1 ] || echo ","
|
||||||
|
first=0
|
||||||
|
echo "{\"id\":\"$backup_id\",\"timestamp\":\"$timestamp\",\"hostname\":\"$hostname\",\"size\":\"$size\"}"
|
||||||
|
done
|
||||||
|
echo ']}'
|
||||||
|
;;
|
||||||
|
|
||||||
|
restore_local_backup)
|
||||||
|
read input
|
||||||
|
backup_id=$(echo "$input" | jsonfilter -e '@.backup_id' 2>/dev/null)
|
||||||
|
|
||||||
|
backup_base=$(uci -q get secubox-p2p.backup.backup_dir || echo "/etc/secubox/backups")
|
||||||
|
backup_dir="$backup_base/$backup_id"
|
||||||
|
|
||||||
|
if [ ! -d "$backup_dir" ]; then
|
||||||
|
echo '{"success":false,"error":"Backup not found"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create pre-restore backup
|
||||||
|
pre_restore="$backup_base/pre-restore-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
mkdir -p "$pre_restore/configs"
|
||||||
|
cp -r /etc/config/secubox* "$pre_restore/configs/" 2>/dev/null
|
||||||
|
|
||||||
|
# Restore configs
|
||||||
|
restored=0
|
||||||
|
if [ -d "$backup_dir/configs" ]; then
|
||||||
|
for cfg in "$backup_dir"/configs/*; do
|
||||||
|
[ -f "$cfg" ] && cp "$cfg" /etc/config/ 2>/dev/null && restored=$((restored + 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "{\"success\":true,\"files_restored\":$restored,\"pre_restore_backup\":\"$pre_restore\"}"
|
||||||
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
echo '{"error":"Unknown method"}'
|
echo '{"error":"Unknown method"}'
|
||||||
;;
|
;;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user