This commit is contained in:
Raven Scott
2026-05-27 19:59:45 -04:00
parent 229350e066
commit dbc6997219
8 changed files with 46 additions and 20 deletions
+3 -1
View File
@@ -103,7 +103,9 @@ TOPIC_SEED=p2ns-dns
# ============================================================================ # ============================================================================
# Master Node Configuration # Master Node Configuration
# ============================================================================ # ============================================================================
# These settings only apply when running with --master flag # Master mode: pass --master on the command line OR set P2NS_MASTER=true (for Docker/PM2).
# P2NS_MASTER=true
# These settings only apply in master mode
# Base interval in seconds for reconnection attempts when peers disconnect (default: 5) # Base interval in seconds for reconnection attempts when peers disconnect (default: 5)
# Master nodes will attempt to reconnect to disconnected peers with exponential backoff # Master nodes will attempt to reconnect to disconnected peers with exponential backoff
MASTER_RECONNECT_INTERVAL=5 MASTER_RECONNECT_INTERVAL=5
@@ -58,7 +58,7 @@ async function handleStatusRoutes(req, res) {
const query = url.parse(req.url, true).query; const query = url.parse(req.url, true).query;
const probeType = query.probe || 'liveness'; const probeType = query.probe || 'liveness';
const dnsHealthy = !!state.dnsPass && state.dnsPass.ready; const dnsHealthy = !!state.dnsPass && state.dnsPass.opened !== false;
const proxyHealthy = process.env.DISABLE_PROXY_SERVER !== 'true'; const proxyHealthy = process.env.DISABLE_PROXY_SERVER !== 'true';
const swarmHealthy = state.connectedPeers !== undefined; const swarmHealthy = state.connectedPeers !== undefined;
@@ -82,7 +82,7 @@ async function handleStatusRoutes(req, res) {
healthy: dnsHealthy, healthy: dnsHealthy,
initialized: !!state.dnsPass, initialized: !!state.dnsPass,
details: { details: {
passReady: !!state.dnsPass?.ready, passReady: state.dnsPass?.opened !== false,
domainsCount: state.domainToIPMap?.size || 0 domainsCount: state.domainToIPMap?.size || 0
} }
}, },
+1 -1
View File
@@ -214,7 +214,7 @@ function broadcastHealth() {
if (adminClients.size === 0) return; if (adminClients.size === 0) return;
try { try {
const dnsHealthy = !!state.dnsPass && state.dnsPass.ready; const dnsHealthy = !!state.dnsPass && state.dnsPass.opened !== false;
const proxyHealthy = process.env.DISABLE_PROXY_SERVER !== 'true'; const proxyHealthy = process.env.DISABLE_PROXY_SERVER !== 'true';
const swarmHealthy = state.connectedPeers !== undefined; const swarmHealthy = state.connectedPeers !== undefined;
const corestoreHealthy = state.dnsPass && state.dnsPass.base && state.dnsPass.base.writable !== undefined; const corestoreHealthy = state.dnsPass && state.dnsPass.base && state.dnsPass.base.writable !== undefined;
+2 -2
View File
@@ -57,7 +57,7 @@ async function handleStatusRoutes(req, res) {
const query = url.parse(req.url, true).query; const query = url.parse(req.url, true).query;
const probeType = query.probe || 'liveness'; const probeType = query.probe || 'liveness';
const dnsHealthy = !!state.dnsPass && state.dnsPass.ready; const dnsHealthy = !!state.dnsPass && state.dnsPass.opened !== false;
const proxyHealthy = process.env.DISABLE_PROXY_SERVER !== 'true'; const proxyHealthy = process.env.DISABLE_PROXY_SERVER !== 'true';
const swarmHealthy = state.connectedPeers !== undefined; const swarmHealthy = state.connectedPeers !== undefined;
@@ -81,7 +81,7 @@ async function handleStatusRoutes(req, res) {
healthy: dnsHealthy, healthy: dnsHealthy,
initialized: !!state.dnsPass, initialized: !!state.dnsPass,
details: { details: {
passReady: !!state.dnsPass?.ready, passReady: state.dnsPass?.opened !== false,
domainsCount: state.domainToIPMap?.size || 0 domainsCount: state.domainToIPMap?.size || 0
} }
}, },
+1 -1
View File
@@ -60,10 +60,10 @@ const countedTies = new Set(); // Set of tie keys: "domain:claimant"
function isDnsPassUsable(pass) { function isDnsPassUsable(pass) {
if (!pass) return false; if (!pass) return false;
if (pass.closed) return false; if (pass.closed) return false;
if (pass.opened === false) return false;
const base = pass.base; const base = pass.base;
if (!base) return false; if (!base) return false;
if (base.closed || base.closing) return false; if (base.closed || base.closing) return false;
if (!pass.ready) return false;
return true; return true;
} }
+14
View File
@@ -0,0 +1,14 @@
/**
* Resolve whether this process is the Autopass master (invite authority).
* Docker/PM2 often omit argv flags; P2NS_MASTER=true is supported as an alternative to --master.
* @returns {boolean}
*/
function resolveIsMaster() {
if (process.argv.includes('--master')) return true;
const v = process.env.P2NS_MASTER ?? process.env.MASTER;
if (v === undefined || v === '') return false;
const normalized = String(v).trim().toLowerCase();
return normalized === '1' || normalized === 'true' || normalized === 'yes';
}
module.exports = { resolveIsMaster };
+1 -1
View File
@@ -68,7 +68,7 @@ module.exports = {
holesailStartTimes: new Map(), // Track when each p2p-domain holesail connection was started holesailStartTimes: new Map(), // Track when each p2p-domain holesail connection was started
persistentConnections: new Set(), // Track which connections are persistent persistentConnections: new Set(), // Track which connections are persistent
localDnsRecords: [], localDnsRecords: [],
isMaster: process.argv.includes('--master'), isMaster: require('./master-mode').resolveIsMaster(),
sendRemovalRequest: null, sendRemovalRequest: null,
sendConsensusRequest: null, sendConsensusRequest: null,
domainsWithBoth: new Set(), domainsWithBoth: new Set(),
+22 -12
View File
@@ -142,14 +142,17 @@ async function main() {
logInfo('Main', 'Starting main function...'); logInfo('Main', 'Starting main function...');
// Check for flags // Check for flags
const isMaster = process.argv.includes('--master'); const { resolveIsMaster } = require('./includes/infrastructure/master-mode');
const isMaster = resolveIsMaster();
state.isMaster = isMaster; state.isMaster = isMaster;
const cleanStorage = process.argv.includes('--clean'); const cleanStorage = process.argv.includes('--clean');
logInfo('Main', `Master flag detected: ${isMaster}`); const masterViaArgv = process.argv.includes('--master');
const masterViaEnv = isMaster && !masterViaArgv;
logInfo('Main', `Master mode: ${isMaster} (argv --master: ${masterViaArgv}${masterViaEnv ? ', env P2NS_MASTER/MASTER: true' : ''})`);
if (isMaster) { if (isMaster) {
logInfo('Main', 'Running as MASTER — this node can create and send Autopass invites'); logInfo('Main', 'Running as MASTER — this node creates Autopass invites for joiners');
} else { } else {
logInfo('Main', 'Running as JOINER — requires a peer started with --master 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}`);
// Clean storage if requested // Clean storage if requested
@@ -630,20 +633,19 @@ async function main() {
10000, 10000,
'Autopass ready' 'Autopass ready'
); );
logDebug('Swarm', 'Paired Autopass ready'); logInfo('Swarm', 'Paired Autopass ready — dnsPass initialized');
// Use helper to ensure consistent state // Use helper to ensure consistent state
setDnsPass(newPass); setDnsPass(newPass);
logDebug('Swarm', `Core retrieved for joiner. Writable: ${core.writable}`); logInfo('Swarm', `Joiner core ready. Writable: ${core.writable}`);
// Sync initial data // Sync initial data
await core.update(); await core.update();
logDebug('Swarm', 'Synced initial data from master'); logDebug('Swarm', 'Synced initial data from master');
// Ensure dnsPass is fully ready before proceeding with dependent operations // Ensure dnsPass is fully ready before proceeding with dependent operations
const currentDnsPass = getDnsPass(); const currentDnsPass = getDnsPass();
if (currentDnsPass && !currentDnsPass.ready) { if (currentDnsPass && currentDnsPass.opened === false) {
logDebug('Swarm', 'Waiting for dnsPass to be fully ready...'); logDebug('Swarm', 'Waiting for dnsPass to finish opening...');
await currentDnsPass.ready(); await currentDnsPass.ready();
logDebug('Swarm', 'dnsPass is now fully ready');
} }
// Setup domains watcher for joiner // Setup domains watcher for joiner
@@ -817,7 +819,7 @@ async function main() {
// Ignore send errors - channel might be closed // Ignore send errors - channel might be closed
} }
} else { } else {
logDebug('Swarm', `Cannot provide invite to ${peerId} - also a joiner waiting for invite from master`); logInfo('Swarm', `Cannot provide invite to ${peerId} — this node is a joiner (no --master / P2NS_MASTER); peer needs the authoritative master`);
try { try {
channelManager.sendToPeer(CORE_DOMAIN, 'request', peerId, 'invite_unavailable:joiner'); channelManager.sendToPeer(CORE_DOMAIN, 'request', peerId, 'invite_unavailable:joiner');
} catch (err) { } catch (err) {
@@ -846,7 +848,10 @@ async function main() {
const inviteChannelInfo = channelManager.getChannelInfo(CORE_DOMAIN, 'invite'); const inviteChannelInfo = channelManager.getChannelInfo(CORE_DOMAIN, 'invite');
const invitePeerChannel = inviteChannelInfo?.peerChannels?.get(peerId); const invitePeerChannel = inviteChannelInfo?.peerChannels?.get(peerId);
if (invitePeerChannel) { if (invitePeerChannel) {
await channelManager.waitForBidirectionalOpen(invitePeerChannel, 8000); const opened = await channelManager.waitForBidirectionalOpen(invitePeerChannel, 3000);
if (!opened) {
logWarn('Swarm', `Invite channel not fully open for ${peerId}, sending invite anyway (protomux may queue)`);
}
} }
const inv = await createInvite(pass); const inv = await createInvite(pass);
const invWire = inviteToWire(inv); const invWire = inviteToWire(inv);
@@ -991,7 +996,7 @@ async function main() {
// Ensure dnsPass is fully ready before proceeding with dependent operations // Ensure dnsPass is fully ready before proceeding with dependent operations
const currentDnsPass = getDnsPass(); const currentDnsPass = getDnsPass();
if (currentDnsPass && !currentDnsPass.ready) { if (currentDnsPass && currentDnsPass.opened === false) {
logDebug('Swarm', 'Waiting for dnsPass to be fully ready...'); logDebug('Swarm', 'Waiting for dnsPass to be fully ready...');
await currentDnsPass.ready(); await currentDnsPass.ready();
logDebug('Swarm', 'dnsPass is now fully ready'); logDebug('Swarm', 'dnsPass is now fully ready');
@@ -1661,16 +1666,21 @@ async function main() {
if (!isDnsPassUsable(newPass)) { if (!isDnsPassUsable(newPass)) {
logError('Main', 'Autopass base core is not usable after ready(). Storage may be from Autopass 2 or corrupted.'); logError('Main', 'Autopass base core is not usable after ready(). Storage may be from Autopass 2 or corrupted.');
logError('Main', 'Stop the process and restart with: node p2ns.js --clean --master'); logError('Main', 'Stop the process and restart with: node p2ns.js --clean --master');
} else {
logInfo('Main', `Autopass dnsPass ready (writable: ${core.writable})`);
} }
logInfo('Main', `Core retrieved. Writable: ${core.writable}, Key: ${core.key.toString('hex')}`); logInfo('Main', `Core retrieved. Writable: ${core.writable}, Key: ${core.key.toString('hex')}`);
// Setup domains watcher for master // Setup domains watcher for master
setupDomainsWatcher(); setupDomainsWatcher();
setupListeners(); setupListeners();
} else if (!getDnsPass()) {
logInfo('Main', 'Joiner: dnsPass not loaded — will request an invite after peers connect');
} }
// Join the topic with both server and client modes for better local network discovery // Join the topic with both server and client modes for better local network discovery
swarm.join(topic, { server: true, client: true }); swarm.join(topic, { server: true, client: true });
logInfo('Main', 'Joined Hyperswarm topic (server: true, client: true)'); logInfo('Main', 'Joined Hyperswarm topic (server: true, client: true)');
logInfo('Main', `Network ready: role=${isMaster ? 'MASTER' : 'JOINER'}, dnsPass=${getDnsPass() ? 'initialized' : 'pending invite'}`);
// Set up periodic topic rejoin to ensure swarm stays connected // Set up periodic topic rejoin to ensure swarm stays connected
// This helps all nodes (master and joiner) maintain their connection to the DHT network // This helps all nodes (master and joiner) maintain their connection to the DHT network