Files
pulse/server/configLoader.js
T

255 lines
10 KiB
JavaScript

const { URL } = require('url');
// Align placeholder values with install script and .env.example
const placeholderValues = [
// Hostname parts - case-insensitive matching might be better if OS env vars differ.
// For now, direct case-sensitive include check.
'your-proxmox-ip-or-hostname',
'proxmox_host', // Substring for https://proxmox_host:8006 or similar
'YOUR_PBS_IP_OR_HOSTNAME', // For PBS host
// Token ID parts - these are more specific to example/guidance values
'user@pam!your-token-name', // Matches common PVE example format
'user@pbs!your-token-name', // Matches common PBS example format
'your-api-token-id', // Generic part often seen in examples
'user@pam!tokenid', // From original install script comment
'user@pbs!tokenid', // PBS variant of install script comment
// Secret parts
'your-token-secret-uuid', // Common PVE secret example
'your-pbs-token-secret-uuid', // Common PBS secret example
'YOUR_API_SECRET_HERE', // From original install script comment
'secret-uuid', // Specific value used in config.test.js
'your-api-token-uuid', // Specific value used in config.test.js for PROXMOX_TOKEN_SECRET
'your-port' // Specific value used in config.test.js for PROXMOX_PORT
];
// Error class for configuration issues
class ConfigurationError extends Error {
constructor(message) {
super(message);
this.name = 'ConfigurationError';
}
}
// Function to load PBS configuration
function loadPbsConfig(index = null) {
const suffix = index ? `_${index}` : '';
const hostVar = `PBS_HOST${suffix}`;
const tokenIdVar = `PBS_TOKEN_ID${suffix}`;
const tokenSecretVar = `PBS_TOKEN_SECRET${suffix}`;
const nodeNameVar = `PBS_NODE_NAME${suffix}`;
const portVar = `PBS_PORT${suffix}`;
const selfSignedVar = `PBS_ALLOW_SELF_SIGNED_CERTS${suffix}`;
const pbsHostUrl = process.env[hostVar];
if (!pbsHostUrl) {
return false; // No more PBS configs if PBS_HOST is missing
}
let pbsHostname = pbsHostUrl;
try {
const parsedUrl = new URL(pbsHostUrl);
pbsHostname = parsedUrl.hostname;
} catch (e) {
// console.warn(`WARN: Could not parse PBS_HOST URL "${pbsHostUrl}". Using full value as fallback name.`);
}
const pbsTokenId = process.env[tokenIdVar];
const pbsTokenSecret = process.env[tokenSecretVar];
let config = null;
let idPrefix = index ? `pbs_endpoint_${index}` : 'pbs_primary';
if (pbsTokenId && pbsTokenSecret) {
const pbsPlaceholders = placeholderValues.filter(p =>
pbsHostUrl.includes(p) || pbsTokenId.includes(p) || pbsTokenSecret.includes(p)
);
if (pbsPlaceholders.length > 0) {
console.warn(`WARN: Skipping PBS configuration ${index || 'primary'} (Token). Placeholder values detected for: ${pbsPlaceholders.join(', ')}`);
} else {
config = {
id: `${idPrefix}_token`,
authMethod: 'token',
name: process.env[nodeNameVar] || pbsHostname,
host: pbsHostUrl,
port: process.env[portVar] || '8007',
tokenId: pbsTokenId,
tokenSecret: pbsTokenSecret,
nodeName: process.env[nodeNameVar], // Keep nodeName field
allowSelfSignedCerts: process.env[selfSignedVar] !== 'false',
enabled: true
};
console.log(`INFO: Found PBS configuration ${index || 'primary'} with ID: ${config.id}, name: ${config.name}, host: ${config.host}`);
}
} else {
console.warn(`WARN: Partial PBS configuration found for ${hostVar}. Please set (${tokenIdVar} + ${tokenSecretVar}) along with ${hostVar}.`);
}
if (config) {
return { found: true, config: config }; // Return config if found
}
// Return found:true if host was set, but config was invalid/partial
return { found: !!pbsHostUrl, config: null };
}
// Main function to load all configurations
function loadConfiguration() {
// Only load .env file if not in test environment
if (process.env.NODE_ENV !== 'test') {
require('dotenv').config();
}
let isConfigPlaceholder = false; // Add this flag
// --- Proxmox Primary Endpoint Validation ---
const primaryRequiredEnvVars = [
'PROXMOX_HOST',
'PROXMOX_TOKEN_ID',
'PROXMOX_TOKEN_SECRET'
];
let missingVars = [];
let placeholderVars = [];
primaryRequiredEnvVars.forEach(varName => {
const value = process.env[varName];
if (!value) {
missingVars.push(varName);
} else if (placeholderValues.some(placeholder => value.includes(placeholder) || placeholder.includes(value))) {
placeholderVars.push(varName);
}
});
// Throw error only if required vars are MISSING
if (missingVars.length > 0) {
let errorMessages = ['--- Configuration Error (Primary Endpoint) ---'];
errorMessages.push(`Missing required environment variables: ${missingVars.join(', ')}.`);
errorMessages.push('Please ensure valid Proxmox connection details are provided.');
errorMessages.push('Refer to server/.env.example for the required variable names and format.');
throw new ConfigurationError(errorMessages.join('\n'));
}
// Set the flag if placeholders were detected (but don't throw error)
if (placeholderVars.length > 0) {
isConfigPlaceholder = true;
// Ensure token ID placeholder is included if missing
if (process.env.PROXMOX_TOKEN_ID && !placeholderVars.includes('PROXMOX_TOKEN_ID')) {
const hostIdx = placeholderVars.indexOf('PROXMOX_HOST');
if (hostIdx !== -1) placeholderVars.splice(hostIdx + 1, 0, 'PROXMOX_TOKEN_ID');
else placeholderVars.push('PROXMOX_TOKEN_ID');
}
console.warn(`WARN: Primary Proxmox environment variables seem to contain placeholder values: ${placeholderVars.join(', ')}. Pulse may not function correctly until configured.`);
}
// --- Load All Proxmox Endpoint Configurations ---
const endpoints = [];
function createProxmoxEndpointConfig(idPrefix, index, hostEnv, portEnv, tokenIdEnv, tokenSecretEnv, enabledEnv, selfSignedEnv, nodeNameEnv) {
const host = process.env[hostEnv];
const tokenId = process.env[tokenIdEnv];
const tokenSecret = process.env[tokenSecretEnv];
const nodeName = process.env[nodeNameEnv];
// Basic validation for additional endpoints (primary is validated earlier)
if (index !== null && (!tokenId || !tokenSecret)) {
// console.warn(`WARN: Skipping endpoint ${index || idPrefix} (Host: ${host}). Missing token ID or secret.`);
return null;
}
if (index !== null && placeholderValues.some(p => host.includes(p) || tokenId.includes(p) || tokenSecret.includes(p))) {
// console.warn(`WARN: Skipping endpoint ${index || idPrefix} (Host: ${host}). Environment variables seem to contain placeholder values.`);
return null;
}
return {
id: index ? `${idPrefix}_${index}` : idPrefix,
name: nodeName || host,
host: host,
port: process.env[portEnv] || '8006',
tokenId: tokenId,
tokenSecret: tokenSecret,
enabled: process.env[enabledEnv] !== 'false',
allowSelfSignedCerts: process.env[selfSignedEnv] !== 'false',
};
}
// Load primary endpoint (index null for helper)
const primaryEndpoint = createProxmoxEndpointConfig(
'primary',
null, // No index for primary
'PROXMOX_HOST',
'PROXMOX_PORT',
'PROXMOX_TOKEN_ID',
'PROXMOX_TOKEN_SECRET',
'PROXMOX_ENABLED',
'PROXMOX_ALLOW_SELF_SIGNED_CERTS',
'PROXMOX_NODE_NAME'
);
if (primaryEndpoint) { // Should always exist due to earlier checks, but good practice
endpoints.push(primaryEndpoint);
}
// Load additional Proxmox endpoints
let i = 2;
while (process.env[`PROXMOX_HOST_${i}`]) {
const additionalEndpoint = createProxmoxEndpointConfig(
'endpoint',
i,
`PROXMOX_HOST_${i}`,
`PROXMOX_PORT_${i}`,
`PROXMOX_TOKEN_ID_${i}`,
`PROXMOX_TOKEN_SECRET_${i}`,
`PROXMOX_ENABLED_${i}`,
`PROXMOX_ALLOW_SELF_SIGNED_CERTS_${i}`,
`PROXMOX_NODE_NAME_${i}`
);
if (additionalEndpoint) {
endpoints.push(additionalEndpoint);
}
i++;
}
if (endpoints.length > 1) {
// console.log(`INFO: Loaded configuration for ${endpoints.length} Proxmox endpoints.`);
}
// --- Load All PBS Configurations ---
const pbsConfigs = [];
// Load primary PBS config
const primaryPbsResult = loadPbsConfig();
/* istanbul ignore else */ // Ignore else path - tested by 'should not add primary PBS config if host is set but tokens are missing'
if (primaryPbsResult.config) {
pbsConfigs.push(primaryPbsResult.config);
}
// Load additional PBS configs
let pbsIndex = 2;
let pbsResult = loadPbsConfig(pbsIndex);
while (pbsResult.found) { // Continue as long as a PBS_HOST_n was found
if (pbsResult.config) {
pbsConfigs.push(pbsResult.config);
}
pbsIndex++;
pbsResult = loadPbsConfig(pbsIndex);
}
if (pbsConfigs.length > 0) {
// console.log(`INFO: Loaded configuration for ${pbsConfigs.length} PBS instances.`);
} else {
// console.log("INFO: No PBS instances configured.");
}
// --- Final Validation ---
const enabledEndpoints = endpoints.filter(e => e.enabled);
if (enabledEndpoints.length === 0 && pbsConfigs.length === 0) {
// Throw error instead of exiting
throw new ConfigurationError('\n--- Configuration Error ---\nNo enabled Proxmox VE or PBS endpoints could be configured. Please check your .env file and environment variables.\n');
}
// console.log('INFO: Configuration loaded successfully.');
// Return the flag along with endpoints and pbsConfigs
return { endpoints, pbsConfigs, isConfigPlaceholder };
}
module.exports = { loadConfiguration, ConfigurationError }; // Export the function and error class