This commit is contained in:
Raven Scott
2026-05-27 22:43:41 -04:00
parent 8b6789805c
commit 276eb4e015
4 changed files with 154 additions and 7 deletions
@@ -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">
+19
View File
@@ -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();
+111
View File
@@ -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);
});
}