reorg
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
const { logError, logWarn } = require('./logger');
|
||||
const { parseMinutesToMs, parseSecondsToMs } = require('./utils');
|
||||
|
||||
/**
|
||||
* Validates and normalizes configuration from environment variables
|
||||
* @returns {object} - Validated configuration object
|
||||
*/
|
||||
function validateConfig() {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const config = {};
|
||||
|
||||
// Storage directory
|
||||
config.STORAGE_DIR = process.env.STORAGE_DIR || './my-storage';
|
||||
|
||||
// Domains file
|
||||
config.DOMAINS_FILE = process.env.DOMAINS_FILE || './cache/domains.json';
|
||||
|
||||
// Local DNS file
|
||||
config.LOCAL_DNS_FILE = process.env.LOCAL_DNS_FILE || 'cache/local_dns.json';
|
||||
|
||||
// Holesail files
|
||||
config.HOLESAIL_SERVERS_FILE = process.env.HOLESAIL_SERVERS_FILE || './cache/holesail_servers.json';
|
||||
config.HOLESAIL_CLIENTS_FILE = process.env.HOLESAIL_CLIENTS_FILE || './cache/holesail_clients.json';
|
||||
|
||||
// Selector cache file
|
||||
config.SELECTOR_CACHE_FILE = process.env.SELECTOR_CACHE_FILE || './cache/selector_cache.json';
|
||||
|
||||
// Topic seed
|
||||
config.TOPIC_SEED = process.env.TOPIC_SEED || 'p2ns-dns';
|
||||
|
||||
// Certificates directory
|
||||
config.CERTS_DIR = process.env.CERTS_DIR || './certs';
|
||||
|
||||
// Internal port
|
||||
const internalPort = parseInt(process.env.INTERNAL_PORT || '8080', 10);
|
||||
if (isNaN(internalPort) || internalPort < 1 || internalPort > 65535) {
|
||||
errors.push(`INTERNAL_PORT must be between 1 and 65535, got: ${process.env.INTERNAL_PORT}`);
|
||||
} else {
|
||||
config.INTERNAL_PORT = internalPort;
|
||||
}
|
||||
|
||||
// Subnet base (legacy, for backward compatibility)
|
||||
config.SUBNET_BASE = process.env.SUBNET_BASE || '192.168.3';
|
||||
|
||||
// Initial IP index (legacy, for backward compatibility)
|
||||
const initialIPIndex = parseInt(process.env.INITIAL_IP_INDEX || '2', 10);
|
||||
if (isNaN(initialIPIndex) || initialIPIndex < 1 || initialIPIndex > 254) {
|
||||
warnings.push(`INITIAL_IP_INDEX should be between 1 and 254, got: ${process.env.INITIAL_IP_INDEX}`);
|
||||
} else {
|
||||
config.INITIAL_IP_INDEX = initialIPIndex;
|
||||
}
|
||||
|
||||
// Subnets configuration (multiple subnets support)
|
||||
let subnets = [];
|
||||
if (process.env.SUBNETS) {
|
||||
try {
|
||||
subnets = JSON.parse(process.env.SUBNETS);
|
||||
if (!Array.isArray(subnets)) {
|
||||
errors.push('SUBNETS must be a JSON array');
|
||||
} else {
|
||||
// Validate each subnet entry
|
||||
subnets.forEach((subnet, index) => {
|
||||
if (!subnet || typeof subnet !== 'object') {
|
||||
errors.push(`SUBNETS[${index}]: must be an object`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate base IP (should be network address)
|
||||
if (!subnet.base || typeof subnet.base !== 'string') {
|
||||
errors.push(`SUBNETS[${index}]: base is required and must be a string`);
|
||||
} else if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(subnet.base)) {
|
||||
errors.push(`SUBNETS[${index}]: base must be a valid IPv4 address (got: ${subnet.base})`);
|
||||
}
|
||||
|
||||
// Validate CIDR (1-32)
|
||||
const cidr = parseInt(subnet.cidr, 10);
|
||||
if (isNaN(cidr) || cidr < 1 || cidr > 32) {
|
||||
errors.push(`SUBNETS[${index}]: cidr must be between 1 and 32 (got: ${subnet.cidr})`);
|
||||
} else {
|
||||
subnet.cidr = cidr;
|
||||
}
|
||||
|
||||
// Validate startIndex (1-254, or up to subnet size)
|
||||
const startIndex = parseInt(subnet.startIndex || config.INITIAL_IP_INDEX, 10);
|
||||
const maxIPs = Math.pow(2, 32 - subnet.cidr) - 2; // Subtract network and broadcast
|
||||
if (isNaN(startIndex) || startIndex < 1 || startIndex > Math.min(254, maxIPs)) {
|
||||
warnings.push(`SUBNETS[${index}]: startIndex should be between 1 and ${Math.min(254, maxIPs)} (got: ${startIndex})`);
|
||||
} else {
|
||||
subnet.startIndex = startIndex;
|
||||
}
|
||||
|
||||
// Name is optional, default to "Subnet N"
|
||||
if (!subnet.name || typeof subnet.name !== 'string') {
|
||||
subnet.name = `Subnet ${index + 1}`;
|
||||
}
|
||||
});
|
||||
|
||||
if (subnets.length === 0) {
|
||||
warnings.push('SUBNETS array is empty, will fall back to SUBNET_BASE');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
errors.push(`Failed to parse SUBNETS JSON: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// If no subnets configured, create default from SUBNET_BASE for backward compatibility
|
||||
if (subnets.length === 0) {
|
||||
const baseParts = config.SUBNET_BASE.split('.');
|
||||
if (baseParts.length === 3) {
|
||||
subnets = [{
|
||||
base: `${config.SUBNET_BASE}.0`,
|
||||
cidr: 24,
|
||||
startIndex: config.INITIAL_IP_INDEX,
|
||||
name: 'Default Subnet'
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
config.SUBNETS = subnets;
|
||||
|
||||
// Public DNS server(s) - supports comma-separated list
|
||||
const publicDnsServerEnv = process.env.PUBLIC_DNS_SERVER || '1.1.1.1';
|
||||
config.PUBLIC_DNS_SERVER = publicDnsServerEnv;
|
||||
|
||||
// Parse and validate comma-separated list
|
||||
const dnsServers = publicDnsServerEnv.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
||||
const invalidServers = [];
|
||||
dnsServers.forEach((server, index) => {
|
||||
if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(server)) {
|
||||
invalidServers.push(`server ${index + 1} (${server})`);
|
||||
}
|
||||
});
|
||||
|
||||
if (invalidServers.length > 0) {
|
||||
warnings.push(`PUBLIC_DNS_SERVER contains invalid IP addresses: ${invalidServers.join(', ')}`);
|
||||
}
|
||||
|
||||
if (dnsServers.length === 0) {
|
||||
warnings.push('PUBLIC_DNS_SERVER is empty, using default 1.1.1.1');
|
||||
config.PUBLIC_DNS_SERVER = '1.1.1.1';
|
||||
}
|
||||
|
||||
// Holesail timeout (in minutes, converted to milliseconds)
|
||||
try {
|
||||
config.HOLESAIL_TIMEOUT = parseMinutesToMs(process.env.HOLESAIL_TIMEOUT || '5');
|
||||
if (config.HOLESAIL_TIMEOUT < 0) {
|
||||
warnings.push(`HOLESAIL_TIMEOUT should be a positive number, got: ${process.env.HOLESAIL_TIMEOUT}`);
|
||||
}
|
||||
} catch (err) {
|
||||
warnings.push(`HOLESAIL_TIMEOUT parse error: ${err.message}, using default 5 minutes`);
|
||||
config.HOLESAIL_TIMEOUT = parseMinutesToMs('5');
|
||||
}
|
||||
|
||||
// Full persistence
|
||||
config.FULL_PERSISTENCE = process.env.FULL_PERSISTENCE === 'true';
|
||||
|
||||
// Port check timeout (in seconds, converted to milliseconds)
|
||||
try {
|
||||
config.PORT_CHECK_TIMEOUT = parseSecondsToMs(process.env.PORT_CHECK_TIMEOUT || '2');
|
||||
if (config.PORT_CHECK_TIMEOUT < 0) {
|
||||
warnings.push(`PORT_CHECK_TIMEOUT should be a positive number, got: ${process.env.PORT_CHECK_TIMEOUT}`);
|
||||
}
|
||||
} catch (err) {
|
||||
warnings.push(`PORT_CHECK_TIMEOUT parse error: ${err.message}, using default 2 seconds`);
|
||||
config.PORT_CHECK_TIMEOUT = parseSecondsToMs('2');
|
||||
}
|
||||
|
||||
// Log level
|
||||
const logLevel = parseInt(process.env.LOG_LEVEL || '0', 10);
|
||||
if (isNaN(logLevel) || logLevel < 0 || logLevel > 3) {
|
||||
warnings.push(`LOG_LEVEL should be between 0 and 3, got: ${process.env.LOG_LEVEL}`);
|
||||
} else {
|
||||
config.LOG_LEVEL = logLevel;
|
||||
}
|
||||
|
||||
// DNS port
|
||||
const dnsPort = parseInt(process.env.DNS_PORT || '53', 10);
|
||||
if (isNaN(dnsPort) || dnsPort < 1 || dnsPort > 65535) {
|
||||
errors.push(`DNS_PORT must be between 1 and 65535, got: ${process.env.DNS_PORT}`);
|
||||
} else {
|
||||
config.DNS_PORT = dnsPort;
|
||||
}
|
||||
|
||||
// HTTPS port
|
||||
const httpsPort = parseInt(process.env.HTTPS_PORT || '443', 10);
|
||||
if (isNaN(httpsPort) || httpsPort < 1 || httpsPort > 65535) {
|
||||
errors.push(`HTTPS_PORT must be between 1 and 65535, got: ${process.env.HTTPS_PORT}`);
|
||||
} else {
|
||||
config.HTTPS_PORT = httpsPort;
|
||||
}
|
||||
|
||||
// HTTP port
|
||||
const httpPort = parseInt(process.env.HTTP_PORT || '80', 10);
|
||||
if (isNaN(httpPort) || httpPort < 1 || httpPort > 65535) {
|
||||
errors.push(`HTTP_PORT must be between 1 and 65535, got: ${process.env.HTTP_PORT}`);
|
||||
} else {
|
||||
config.HTTP_PORT = httpPort;
|
||||
}
|
||||
|
||||
// Feature flags
|
||||
config.DISABLE_DNS_SERVER = process.env.DISABLE_DNS_SERVER === 'true';
|
||||
config.DISABLE_PROXY_SERVER = process.env.DISABLE_PROXY_SERVER === 'true';
|
||||
config.DISABLE_VIRTUAL_INTERFACES = process.env.DISABLE_VIRTUAL_INTERFACES === 'true';
|
||||
config.ALLOW_ANY_WRITER_INVITES = process.env.ALLOW_ANY_WRITER_INVITES === 'true';
|
||||
|
||||
// Consensus configuration
|
||||
const consensusQuorumThreshold = parseFloat(process.env.CONSENSUS_QUORUM_THRESHOLD || '0.5');
|
||||
if (isNaN(consensusQuorumThreshold) || consensusQuorumThreshold < 0 || consensusQuorumThreshold > 1) {
|
||||
warnings.push(`CONSENSUS_QUORUM_THRESHOLD should be between 0 and 1, got: ${process.env.CONSENSUS_QUORUM_THRESHOLD}`);
|
||||
} else {
|
||||
config.CONSENSUS_QUORUM_THRESHOLD = consensusQuorumThreshold;
|
||||
}
|
||||
|
||||
const consensusMinVotes = parseInt(process.env.CONSENSUS_MIN_VOTES || '2', 10);
|
||||
if (isNaN(consensusMinVotes) || consensusMinVotes < 1) {
|
||||
warnings.push(`CONSENSUS_MIN_VOTES should be at least 1, got: ${process.env.CONSENSUS_MIN_VOTES}`);
|
||||
} else {
|
||||
config.CONSENSUS_MIN_VOTES = consensusMinVotes;
|
||||
}
|
||||
|
||||
const consensusTieBreaker = process.env.CONSENSUS_TIE_BREAKER || 'timestamp';
|
||||
if (!['timestamp', 'claimant_age', 'lexicographic'].includes(consensusTieBreaker)) {
|
||||
warnings.push(`CONSENSUS_TIE_BREAKER should be one of: timestamp, claimant_age, lexicographic. Got: ${consensusTieBreaker}`);
|
||||
} else {
|
||||
config.CONSENSUS_TIE_BREAKER = consensusTieBreaker;
|
||||
}
|
||||
|
||||
config.CONSENSUS_VOTE_VALIDATION = process.env.CONSENSUS_VOTE_VALIDATION !== 'false';
|
||||
config.CONSENSUS_IMMEDIATE_UPDATE = process.env.CONSENSUS_IMMEDIATE_UPDATE !== 'false';
|
||||
|
||||
// Log errors and warnings
|
||||
if (errors.length > 0) {
|
||||
errors.forEach(error => logError('Config', error));
|
||||
throw new Error(`Configuration validation failed: ${errors.join('; ')}`);
|
||||
}
|
||||
|
||||
if (warnings.length > 0) {
|
||||
warnings.forEach(warning => logWarn('Config', warning));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = { validateConfig };
|
||||
|
||||
Reference in New Issue
Block a user