Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
936f86c346 | |||
7ff1a7da36 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "4.0.5",
|
"version": "4.0.7",
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run --allow-all mod.ts",
|
"dev": "deno run --allow-all mod.ts",
|
||||||
|
@@ -129,81 +129,32 @@ export class ServiceHandler {
|
|||||||
try {
|
try {
|
||||||
// Check if running as root
|
// Check if running as root
|
||||||
this.checkRootAccess(
|
this.checkRootAccess(
|
||||||
'This command must be run as root to update NUPST and refresh the systemd service.',
|
'This command must be run as root to update NUPST.',
|
||||||
);
|
);
|
||||||
|
|
||||||
const boxWidth = 45;
|
console.log('');
|
||||||
logger.logBoxTitle('NUPST Update Process', boxWidth);
|
logger.info('Updating NUPST to latest version...');
|
||||||
logger.logBoxLine('Updating NUPST from repository...');
|
console.log('');
|
||||||
|
|
||||||
// Determine the installation directory (assuming it's either /opt/nupst or the current directory)
|
|
||||||
const { existsSync } = await import('fs');
|
|
||||||
let installDir = '/opt/nupst';
|
|
||||||
|
|
||||||
if (!existsSync(installDir)) {
|
|
||||||
// If not installed in /opt/nupst, use the current directory
|
|
||||||
const { dirname } = await import('path');
|
|
||||||
installDir = dirname(dirname(process.argv[1])); // Go up two levels from the executable
|
|
||||||
logger.logBoxLine(`Using local installation directory: ${installDir}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Update the repository
|
// Download and run the install script
|
||||||
logger.logBoxLine('Pulling latest changes from git repository...');
|
// This handles everything: download binary, stop service, replace, restart
|
||||||
execSync(`cd ${installDir} && git fetch origin && git reset --hard origin/main`, {
|
const installUrl = 'https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh';
|
||||||
stdio: 'pipe',
|
|
||||||
|
logger.dim('Downloading install script...');
|
||||||
|
execSync(`curl -sSL ${installUrl} | bash`, {
|
||||||
|
stdio: 'inherit', // Show install script output to user
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Run the install.sh script
|
console.log('');
|
||||||
logger.logBoxLine('Running install.sh to update NUPST...');
|
logger.success('Update completed successfully!');
|
||||||
execSync(`cd ${installDir} && bash ./install.sh`, { stdio: 'pipe' });
|
logger.dim('Run "nupst service status" to verify the update.');
|
||||||
|
console.log('');
|
||||||
// 3. Run the setup.sh script with force flag to update Node.js and dependencies
|
|
||||||
logger.logBoxLine('Running setup.sh to update Node.js and dependencies...');
|
|
||||||
execSync(`cd ${installDir} && bash ./setup.sh --force`, { stdio: 'pipe' });
|
|
||||||
|
|
||||||
// 4. Refresh the systemd service
|
|
||||||
logger.logBoxLine('Refreshing systemd service...');
|
|
||||||
|
|
||||||
// First check if service exists
|
|
||||||
let serviceExists = false;
|
|
||||||
try {
|
|
||||||
const output = execSync('systemctl list-unit-files | grep nupst.service').toString();
|
|
||||||
serviceExists = output.includes('nupst.service');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If grep fails (service not found), serviceExists remains false
|
console.log('');
|
||||||
serviceExists = false;
|
logger.error('Update failed');
|
||||||
}
|
logger.dim(`${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
console.log('');
|
||||||
if (serviceExists) {
|
|
||||||
// Stop the service if it's running
|
|
||||||
const isRunning =
|
|
||||||
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
|
||||||
if (isRunning) {
|
|
||||||
logger.logBoxLine('Stopping nupst service...');
|
|
||||||
execSync('systemctl stop nupst.service');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reinstall the service
|
|
||||||
logger.logBoxLine('Reinstalling systemd service...');
|
|
||||||
await this.nupst.getSystemd().install();
|
|
||||||
|
|
||||||
// Restart the service if it was running
|
|
||||||
if (isRunning) {
|
|
||||||
logger.logBoxLine('Restarting nupst service...');
|
|
||||||
execSync('systemctl start nupst.service');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.logBoxLine('Systemd service not installed, skipping service refresh.');
|
|
||||||
logger.logBoxLine('Run "nupst enable" to install the service.');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.logBoxLine('Update completed successfully!');
|
|
||||||
logger.logBoxEnd();
|
|
||||||
} catch (error) {
|
|
||||||
logger.logBoxLine('Error during update process:');
|
|
||||||
logger.logBoxLine(`${error instanceof Error ? error.message : String(error)}`);
|
|
||||||
logger.logBoxEnd();
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
148
ts/systemd.ts
148
ts/systemd.ts
@@ -3,6 +3,7 @@ import { promises as fs } from 'node:fs';
|
|||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
import { NupstDaemon } from './daemon.ts';
|
import { NupstDaemon } from './daemon.ts';
|
||||||
import { logger } from './logger.ts';
|
import { logger } from './logger.ts';
|
||||||
|
import { theme, symbols, getBatteryColor, getRuntimeColor, formatPowerStatus } from './colors.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for managing systemd service
|
* Class for managing systemd service
|
||||||
@@ -171,18 +172,50 @@ WantedBy=multi-user.target
|
|||||||
private displayServiceStatus(): void {
|
private displayServiceStatus(): void {
|
||||||
try {
|
try {
|
||||||
const serviceStatus = execSync('systemctl status nupst.service').toString();
|
const serviceStatus = execSync('systemctl status nupst.service').toString();
|
||||||
const boxWidth = 45;
|
const lines = serviceStatus.split('\n');
|
||||||
logger.logBoxTitle('Service Status', boxWidth);
|
|
||||||
// Process each line of the status output
|
// Parse key information from systemctl output
|
||||||
serviceStatus.split('\n').forEach((line) => {
|
let isActive = false;
|
||||||
logger.logBoxLine(line);
|
let pid = '';
|
||||||
});
|
let memory = '';
|
||||||
logger.logBoxEnd();
|
let cpu = '';
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.includes('Active:')) {
|
||||||
|
isActive = line.includes('active (running)');
|
||||||
|
} else if (line.includes('Main PID:')) {
|
||||||
|
const match = line.match(/Main PID:\s+(\d+)/);
|
||||||
|
if (match) pid = match[1];
|
||||||
|
} else if (line.includes('Memory:')) {
|
||||||
|
const match = line.match(/Memory:\s+([\d.]+[A-Z])/);
|
||||||
|
if (match) memory = match[1];
|
||||||
|
} else if (line.includes('CPU:')) {
|
||||||
|
const match = line.match(/CPU:\s+([\d.]+(?:ms|s))/);
|
||||||
|
if (match) cpu = match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display beautiful status
|
||||||
|
console.log('');
|
||||||
|
if (isActive) {
|
||||||
|
console.log(`${symbols.running} ${theme.success('Service:')} ${theme.statusActive('active (running)')}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('inactive')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid || memory || cpu) {
|
||||||
|
const details = [];
|
||||||
|
if (pid) details.push(`PID: ${theme.dim(pid)}`);
|
||||||
|
if (memory) details.push(`Memory: ${theme.dim(memory)}`);
|
||||||
|
if (cpu) details.push(`CPU: ${theme.dim(cpu)}`);
|
||||||
|
console.log(` ${details.join(' ')}`);
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const boxWidth = 45;
|
console.log('');
|
||||||
logger.logBoxTitle('Service Status', boxWidth);
|
console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('not installed')}`);
|
||||||
logger.logBoxLine('Service is not running');
|
console.log('');
|
||||||
logger.logBoxEnd();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +232,7 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
// Check if we have the new multi-UPS config format
|
// Check if we have the new multi-UPS config format
|
||||||
if (config.upsDevices && Array.isArray(config.upsDevices) && config.upsDevices.length > 0) {
|
if (config.upsDevices && Array.isArray(config.upsDevices) && config.upsDevices.length > 0) {
|
||||||
logger.log(`Found ${config.upsDevices.length} UPS device(s) in configuration`);
|
console.log(theme.info(`UPS Devices (${config.upsDevices.length}):`));
|
||||||
|
|
||||||
// Show status for each UPS
|
// Show status for each UPS
|
||||||
for (const ups of config.upsDevices) {
|
for (const ups of config.upsDevices) {
|
||||||
@@ -207,6 +240,7 @@ WantedBy=multi-user.target
|
|||||||
}
|
}
|
||||||
} else if (config.snmp) {
|
} else if (config.snmp) {
|
||||||
// Legacy single UPS configuration
|
// Legacy single UPS configuration
|
||||||
|
console.log(theme.info('UPS Devices (1):'));
|
||||||
const legacyUps = {
|
const legacyUps = {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
name: 'Default UPS',
|
name: 'Default UPS',
|
||||||
@@ -217,15 +251,16 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
await this.displaySingleUpsStatus(legacyUps, snmp);
|
await this.displaySingleUpsStatus(legacyUps, snmp);
|
||||||
} else {
|
} else {
|
||||||
logger.error('No UPS devices found in configuration');
|
console.log('');
|
||||||
|
console.log(`${symbols.warning} ${theme.warning('No UPS devices configured')}`);
|
||||||
|
console.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to add a device')}`);
|
||||||
|
console.log('');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const boxWidth = 45;
|
console.log('');
|
||||||
logger.logBoxTitle('UPS Status', boxWidth);
|
console.log(`${symbols.error} ${theme.error('Failed to retrieve UPS status')}`);
|
||||||
logger.logBoxLine(
|
console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`);
|
||||||
`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`,
|
console.log('');
|
||||||
);
|
|
||||||
logger.logBoxEnd();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,24 +270,6 @@ WantedBy=multi-user.target
|
|||||||
* @param snmp SNMP manager
|
* @param snmp SNMP manager
|
||||||
*/
|
*/
|
||||||
private async displaySingleUpsStatus(ups: any, snmp: any): Promise<void> {
|
private async displaySingleUpsStatus(ups: any, snmp: any): Promise<void> {
|
||||||
const boxWidth = 45;
|
|
||||||
logger.logBoxTitle(`Connecting to UPS: ${ups.name}`, boxWidth);
|
|
||||||
logger.logBoxLine(`ID: ${ups.id}`);
|
|
||||||
logger.logBoxLine(`Host: ${ups.snmp.host}:${ups.snmp.port}`);
|
|
||||||
logger.logBoxLine(`UPS Model: ${ups.snmp.upsModel || 'cyberpower'}`);
|
|
||||||
|
|
||||||
if (ups.groups && ups.groups.length > 0) {
|
|
||||||
// Get group names if available
|
|
||||||
const config = this.daemon.getConfig();
|
|
||||||
const groupNames = ups.groups.map((groupId: string) => {
|
|
||||||
const group = config.groups?.find((g: { id: string }) => g.id === groupId);
|
|
||||||
return group ? group.name : groupId;
|
|
||||||
});
|
|
||||||
logger.logBoxLine(`Groups: ${groupNames.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.logBoxEnd();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a test config with a short timeout
|
// Create a test config with a short timeout
|
||||||
const testConfig = {
|
const testConfig = {
|
||||||
@@ -262,32 +279,43 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
const status = await snmp.getUpsStatus(testConfig);
|
const status = await snmp.getUpsStatus(testConfig);
|
||||||
|
|
||||||
logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth);
|
// Determine status symbol based on power status
|
||||||
logger.logBoxLine(`Power Status: ${status.powerStatus}`);
|
let statusSymbol = symbols.unknown;
|
||||||
logger.logBoxLine(`Battery Capacity: ${status.batteryCapacity}%`);
|
if (status.powerStatus === 'online') {
|
||||||
logger.logBoxLine(`Runtime Remaining: ${status.batteryRuntime} minutes`);
|
statusSymbol = symbols.running;
|
||||||
|
} else if (status.powerStatus === 'onBattery') {
|
||||||
|
statusSymbol = symbols.warning;
|
||||||
|
}
|
||||||
|
|
||||||
// Show threshold status
|
// Display UPS name and power status
|
||||||
logger.logBoxLine('');
|
console.log(` ${statusSymbol} ${theme.highlight(ups.name)} - ${formatPowerStatus(status.powerStatus)}`);
|
||||||
logger.logBoxLine('Thresholds:');
|
|
||||||
logger.logBoxLine(
|
// Display battery with color coding
|
||||||
` Battery: ${status.batteryCapacity}% / ${ups.thresholds.battery}% ${
|
const batteryColor = getBatteryColor(status.batteryCapacity);
|
||||||
status.batteryCapacity < ups.thresholds.battery ? '⚠️' : '✓'
|
const batterySymbol = status.batteryCapacity >= ups.thresholds.battery ? symbols.success : symbols.warning;
|
||||||
}`,
|
console.log(` Battery: ${batteryColor(status.batteryCapacity + '%')} ${batterySymbol} Runtime: ${getRuntimeColor(status.batteryRuntime)(status.batteryRuntime + ' min')}`);
|
||||||
);
|
|
||||||
logger.logBoxLine(
|
// Display host info
|
||||||
` Runtime: ${status.batteryRuntime} min / ${ups.thresholds.runtime} min ${
|
console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`);
|
||||||
status.batteryRuntime < ups.thresholds.runtime ? '⚠️' : '✓'
|
|
||||||
}`,
|
// Display groups if any
|
||||||
);
|
if (ups.groups && ups.groups.length > 0) {
|
||||||
|
const config = this.daemon.getConfig();
|
||||||
|
const groupNames = ups.groups.map((groupId: string) => {
|
||||||
|
const group = config.groups?.find((g: { id: string }) => g.id === groupId);
|
||||||
|
return group ? group.name : groupId;
|
||||||
|
});
|
||||||
|
console.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
logger.logBoxEnd();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth);
|
// Display error for this UPS
|
||||||
logger.logBoxLine(
|
console.log(` ${symbols.error} ${theme.highlight(ups.name)} - ${theme.error('Connection failed')}`);
|
||||||
`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`,
|
console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`);
|
||||||
);
|
console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`);
|
||||||
logger.logBoxEnd();
|
console.log('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user