forked from snxraven/p2ns
Updates
This commit is contained in:
@@ -6,6 +6,7 @@ const { logError, logWarn, logDebug } = require('../../../infrastructure/logger'
|
|||||||
const { getAvailableIPsForSubnet } = require('../../../networking/virtual_interfaces');
|
const { getAvailableIPsForSubnet } = require('../../../networking/virtual_interfaces');
|
||||||
const { settingsMetadata, restartRequiredSettings, liveReloadableSettings, envWhitelist, applyLiveSettings } = require('../settings');
|
const { settingsMetadata, restartRequiredSettings, liveReloadableSettings, envWhitelist, applyLiveSettings } = require('../settings');
|
||||||
const { broadcast } = require('../websocket');
|
const { broadcast } = require('../websocket');
|
||||||
|
const { resetPluginStorage } = require('../../../../scripts/reset-plugin-storage');
|
||||||
|
|
||||||
async function handleSettingsRoutes(req, res) {
|
async function handleSettingsRoutes(req, res) {
|
||||||
const urlPath = req.urlPath || new URL(req.url, `https://${req.headers.host}`).pathname;
|
const urlPath = req.urlPath || new URL(req.url, `https://${req.headers.host}`).pathname;
|
||||||
@@ -548,6 +549,21 @@ async function handleSettingsRoutes(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean plugin HyperDB generated storage (same behavior as --clean)
|
||||||
|
let pluginResetSummary = null;
|
||||||
|
try {
|
||||||
|
pluginResetSummary = await resetPluginStorage({
|
||||||
|
logger: (level, message) => {
|
||||||
|
if (level === 'WARN') logWarn('Admin', message);
|
||||||
|
else if (level === 'ERROR') logError('Admin', message);
|
||||||
|
else logInfo('Admin', message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
logInfo('Admin', `Reset plugin spec/db directories for ${pluginResetSummary.resetCandidates} plugin(s)`);
|
||||||
|
} catch (pluginErr) {
|
||||||
|
logWarn('Admin', `Failed to reset plugin spec/db directories: ${pluginErr.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset state variables that depend on the storage
|
// Reset state variables that depend on the storage
|
||||||
state.dnsPass = null;
|
state.dnsPass = null;
|
||||||
state.consecutiveInviteFailures = 0; // Reset failure counter
|
state.consecutiveInviteFailures = 0; // Reset failure counter
|
||||||
@@ -560,8 +576,9 @@ async function handleSettingsRoutes(req, res) {
|
|||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({
|
res.end(JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'DNS Pass storage cleaned. Shutting down for reinitialization...',
|
message: 'DNS Pass and plugin storage cleaned. Shutting down for reinitialization...',
|
||||||
storageDir: storageDir,
|
storageDir: storageDir,
|
||||||
|
pluginResetSummary,
|
||||||
shutdownInitiated: true,
|
shutdownInitiated: true,
|
||||||
restartInstructions: 'Process will shut down. Restart manually with: sudo node p2ns.js' + (state.isMaster ? ' --master' : '')
|
restartInstructions: 'Process will shut down. Restart manually with: sudo node p2ns.js' + (state.isMaster ? ' --master' : '')
|
||||||
}));
|
}));
|
||||||
@@ -570,7 +587,7 @@ async function handleSettingsRoutes(req, res) {
|
|||||||
// Add a longer delay to ensure storage cleanup is complete and state is reset
|
// Add a longer delay to ensure storage cleanup is complete and state is reset
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
logInfo('Admin', 'Initiating graceful shutdown after DNS storage cleanup...');
|
logInfo('Admin', 'Initiating graceful shutdown after DNS storage cleanup...');
|
||||||
logInfo('Admin', '🔄 SYSTEM IS SHUTTING DOWN - Storage has been cleaned and will reinitialize on restart');
|
logInfo('Admin', '🔄 SYSTEM IS SHUTTING DOWN - DNS + plugin storage has been cleaned and will reinitialize on restart');
|
||||||
process.emit('SIGTERM');
|
process.emit('SIGTERM');
|
||||||
}, 3000); // Give more time for response to be sent and cleanup to complete
|
}, 3000); // Give more time for response to be sent and cleanup to complete
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -682,7 +682,7 @@ async function cleanDnsPassStorage() {
|
|||||||
console.warn('Could not fetch status for confirmation dialog:', err);
|
console.warn('Could not fetch status for confirmation dialog:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let warningMessage = '⚠️ WARNING: This will delete all DNS Pass data and immediately shut down the system for reinitialization.\n\n';
|
let warningMessage = '⚠️ WARNING: This will delete DNS Pass data plus plugin spec/db cache data and immediately shut down the system for reinitialization.\n\n';
|
||||||
|
|
||||||
if (statusInfo.peersCount > 0) {
|
if (statusInfo.peersCount > 0) {
|
||||||
warningMessage += `• ${statusInfo.peersCount} peers are currently connected - they will be disconnected\n`;
|
warningMessage += `• ${statusInfo.peersCount} peers are currently connected - they will be disconnected\n`;
|
||||||
@@ -715,7 +715,7 @@ async function cleanDnsPassStorage() {
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (window.showNotification) {
|
if (window.showNotification) {
|
||||||
window.showNotification('🧹 DNS Pass storage cleaned. System shutting down for reinitialization...', 'success');
|
window.showNotification('🧹 DNS + plugin storage cleaned. System shutting down for reinitialization...', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear any existing invite diagnostics results since the state has changed
|
// Clear any existing invite diagnostics results since the state has changed
|
||||||
@@ -734,7 +734,7 @@ async function cleanDnsPassStorage() {
|
|||||||
// Restore button
|
// Restore button
|
||||||
if (buttonEl) {
|
if (buttonEl) {
|
||||||
buttonEl.disabled = false;
|
buttonEl.disabled = false;
|
||||||
buttonEl.innerHTML = '🗑️ Clean DNS Storage';
|
buttonEl.innerHTML = '🗑️ Clean & Restart';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -782,7 +782,7 @@ async function showRestartRequiredMessage() {
|
|||||||
<div class="ml-3 flex-1">
|
<div class="ml-3 flex-1">
|
||||||
<h4 class="text-base font-semibold text-orange-200 mb-1">System Restart Required</h4>
|
<h4 class="text-base font-semibold text-orange-200 mb-1">System Restart Required</h4>
|
||||||
<p class="text-orange-100 text-sm mb-2">
|
<p class="text-orange-100 text-sm mb-2">
|
||||||
DNS Pass storage cleaned. Process must restart for reinitialization.
|
DNS + plugin storage cleaned. Process must restart for reinitialization.
|
||||||
</p>
|
</p>
|
||||||
<div class="bg-orange-800 rounded p-2 mb-2">
|
<div class="bg-orange-800 rounded p-2 mb-2">
|
||||||
<p class="text-xs text-orange-200 font-mono">
|
<p class="text-xs text-orange-200 font-mono">
|
||||||
@@ -820,7 +820,7 @@ function showShutdownMessage(result) {
|
|||||||
<div class="ml-3 flex-1">
|
<div class="ml-3 flex-1">
|
||||||
<h4 class="text-base font-semibold text-red-200 mb-1">System Shutting Down</h4>
|
<h4 class="text-base font-semibold text-red-200 mb-1">System Shutting Down</h4>
|
||||||
<p class="text-red-100 text-sm mb-2">
|
<p class="text-red-100 text-sm mb-2">
|
||||||
DNS Pass storage cleaned. Process shutting down for reinitialization.
|
DNS + plugin storage cleaned. Process shutting down for reinitialization.
|
||||||
</p>
|
</p>
|
||||||
<div class="bg-red-800 rounded p-2 mb-2">
|
<div class="bg-red-800 rounded p-2 mb-2">
|
||||||
<p class="text-xs text-red-200 font-mono">
|
<p class="text-xs text-red-200 font-mono">
|
||||||
|
|||||||
@@ -156,6 +156,21 @@ async function main() {
|
|||||||
logInfo('Main', 'Running as JOINER — needs a peer with --master or P2NS_MASTER=true to receive an invite');
|
logInfo('Main', 'Running as JOINER — needs a peer with --master or P2NS_MASTER=true to receive an invite');
|
||||||
}
|
}
|
||||||
logInfo('Main', `Clean storage flag detected: ${cleanStorage}`);
|
logInfo('Main', `Clean storage flag detected: ${cleanStorage}`);
|
||||||
|
async function runPluginStorageReset(reason) {
|
||||||
|
try {
|
||||||
|
const { resetPluginStorage } = require('./scripts/reset-plugin-storage');
|
||||||
|
const summary = await resetPluginStorage({
|
||||||
|
logger: (level, message) => {
|
||||||
|
const fn = level === 'WARN' ? logWarn : level === 'ERROR' ? logError : logInfo;
|
||||||
|
fn('Main', `[PluginStorageReset] ${message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
logInfo('Main', `Plugin spec/db reset complete for ${summary.resetCandidates} plugin(s) during ${reason}`);
|
||||||
|
} catch (err) {
|
||||||
|
logWarn('Main', `Plugin spec/db reset failed during ${reason}: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clean storage if requested
|
// Clean storage if requested
|
||||||
if (cleanStorage) {
|
if (cleanStorage) {
|
||||||
logInfo('Main', 'Cleaning storage...');
|
logInfo('Main', 'Cleaning storage...');
|
||||||
@@ -166,6 +181,10 @@ async function main() {
|
|||||||
logWarn('Main', `Storage clean error (may not exist): ${err.message}`);
|
logWarn('Main', `Storage clean error (may not exist): ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always reset plugin generated spec/db storage on startup.
|
||||||
|
// Data will re-sync from peers during normal bootstrapping.
|
||||||
|
await runPluginStorageReset(cleanStorage ? '--clean startup' : 'startup');
|
||||||
setupCache();
|
setupCache();
|
||||||
ca.installRootCA();
|
ca.installRootCA();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
const fs = require('fs').promises;
|
||||||
|
const fsSync = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function defaultLogger(level, message) {
|
||||||
|
const stamp = new Date().toISOString();
|
||||||
|
console.log(`${stamp} [PluginReset] [${level}] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pathExists(targetPath) {
|
||||||
|
try {
|
||||||
|
await fs.access(targetPath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeIfExists(targetPath, logger) {
|
||||||
|
const exists = await pathExists(targetPath);
|
||||||
|
if (!exists) return false;
|
||||||
|
try {
|
||||||
|
await fs.rm(targetPath, { recursive: true, force: true });
|
||||||
|
logger('INFO', `Removed: ${targetPath}`);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
if (err && (err.code === 'EACCES' || err.code === 'EPERM')) {
|
||||||
|
logger('WARN', `Permission denied removing ${targetPath} (${err.code})`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listPluginDirs(pluginSitesDir) {
|
||||||
|
if (!fsSync.existsSync(pluginSitesDir)) return [];
|
||||||
|
return fsSync
|
||||||
|
.readdirSync(pluginSitesDir, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isDirectory())
|
||||||
|
.map((entry) => path.join(pluginSitesDir, entry.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPluginConfig(pluginDir) {
|
||||||
|
const configPath = path.join(pluginDir, 'config.json');
|
||||||
|
if (!fsSync.existsSync(configPath)) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(fsSync.readFileSync(configPath, 'utf8'));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetPluginStorage(options = {}) {
|
||||||
|
const projectRoot = options.projectRoot || path.resolve(__dirname, '..');
|
||||||
|
const pluginSitesDir = path.join(projectRoot, 'plugin-sites');
|
||||||
|
const cachePluginSpecDir = path.join(projectRoot, 'cache', 'plugin-spec');
|
||||||
|
const logger = options.logger || defaultLogger;
|
||||||
|
|
||||||
|
const pluginDirs = listPluginDirs(pluginSitesDir);
|
||||||
|
const touched = [];
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
for (const pluginDir of pluginDirs) {
|
||||||
|
const config = readPluginConfig(pluginDir);
|
||||||
|
if (!config || !config.hyperdb) continue;
|
||||||
|
|
||||||
|
const pluginName = config.domain || path.basename(pluginDir);
|
||||||
|
const removed = {
|
||||||
|
plugin: pluginName,
|
||||||
|
specRemoved: false,
|
||||||
|
dbRemoved: false,
|
||||||
|
cacheSpecRemoved: false
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
removed.specRemoved = await removeIfExists(path.join(pluginDir, 'spec'), logger);
|
||||||
|
removed.dbRemoved = await removeIfExists(path.join(pluginDir, 'db'), logger);
|
||||||
|
removed.cacheSpecRemoved = await removeIfExists(path.join(cachePluginSpecDir, pluginName), logger);
|
||||||
|
} catch (err) {
|
||||||
|
warnings.push(`${pluginName}: ${err.message}`);
|
||||||
|
logger('WARN', `Failed to fully reset ${pluginName}: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
touched.push(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
const summary = {
|
||||||
|
inspectedPlugins: pluginDirs.length,
|
||||||
|
resetCandidates: touched.length,
|
||||||
|
warnings,
|
||||||
|
touched
|
||||||
|
};
|
||||||
|
|
||||||
|
logger(
|
||||||
|
'INFO',
|
||||||
|
`Plugin storage reset complete (inspected=${summary.inspectedPlugins}, candidates=${summary.resetCandidates})`
|
||||||
|
);
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
resetPluginStorage
|
||||||
|
};
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
resetPluginStorage().catch((err) => {
|
||||||
|
console.error(`${new Date().toISOString()} [PluginReset] [ERROR] ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user