feat(core): Add update checking and version logging across startup components

This commit is contained in:
Philipp Kunz 2025-03-25 09:27:44 +00:00
parent 32f85aa46f
commit 8ee21ea92b
6 changed files with 190 additions and 1 deletions

View File

@ -1,5 +1,13 @@
# Changelog # Changelog
## 2025-03-25 - 1.10.0 - feat(core)
Add update checking and version logging across startup components
- In daemon.ts, log version info on startup and check for updates in the background using npm registry response
- In nupst.ts, implement getVersion, checkForUpdates, getUpdateStatus, and compareVersions functions with update notifications
- Establish bidirectional reference between Nupst and NupstSnmp to support version logging
- Update systemd service status output to include version information
## 2025-03-25 - 1.9.0 - feat(cli) ## 2025-03-25 - 1.9.0 - feat(cli)
Add update command to CLI to update NUPST from repository and refresh the systemd service Add update command to CLI to update NUPST from repository and refresh the systemd service

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/nupst', name: '@serve.zone/nupst',
version: '1.9.0', version: '1.10.0',
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
} }

View File

@ -152,6 +152,21 @@ export class NupstDaemon {
await this.loadConfig(); await this.loadConfig();
this.logConfigLoaded(); this.logConfigLoaded();
// Log version information
this.snmp.getNupst().logVersionInfo(false); // Don't check for updates immediately on startup
// Check for updates in the background
this.snmp.getNupst().checkForUpdates().then(updateAvailable => {
if (updateAvailable) {
const updateStatus = this.snmp.getNupst().getUpdateStatus();
console.log('┌─ Update Available ───────────────────────┐');
console.log(`│ Current Version: ${updateStatus.currentVersion}`);
console.log(`│ Latest Version: ${updateStatus.latestVersion}`);
console.log('│ Run "sudo nupst update" to update');
console.log('└──────────────────────────────────────────┘');
}
}).catch(() => {}); // Ignore errors checking for updates
// Start UPS monitoring // Start UPS monitoring
this.isRunning = true; this.isRunning = true;
await this.monitor(); await this.monitor();

View File

@ -1,6 +1,9 @@
import { NupstSnmp } from './snmp.js'; import { NupstSnmp } from './snmp.js';
import { NupstDaemon } from './daemon.js'; import { NupstDaemon } from './daemon.js';
import { NupstSystemd } from './systemd.js'; import { NupstSystemd } from './systemd.js';
import { commitinfo } from './00_commitinfo_data.js';
import { spawn } from 'child_process';
import * as https from 'https';
/** /**
* Main Nupst class that coordinates all components * Main Nupst class that coordinates all components
@ -10,12 +13,15 @@ export class Nupst {
private readonly snmp: NupstSnmp; private readonly snmp: NupstSnmp;
private readonly daemon: NupstDaemon; private readonly daemon: NupstDaemon;
private readonly systemd: NupstSystemd; private readonly systemd: NupstSystemd;
private updateAvailable: boolean = false;
private latestVersion: string = '';
/** /**
* Create a new Nupst instance with all necessary components * Create a new Nupst instance with all necessary components
*/ */
constructor() { constructor() {
this.snmp = new NupstSnmp(); this.snmp = new NupstSnmp();
this.snmp.setNupst(this); // Set up bidirectional reference
this.daemon = new NupstDaemon(this.snmp); this.daemon = new NupstDaemon(this.snmp);
this.systemd = new NupstSystemd(this.daemon); this.systemd = new NupstSystemd(this.daemon);
} }
@ -40,4 +46,144 @@ export class Nupst {
public getSystemd(): NupstSystemd { public getSystemd(): NupstSystemd {
return this.systemd; return this.systemd;
} }
/**
* Get the current version of NUPST
* @returns The current version string
*/
public getVersion(): string {
return commitinfo.version;
}
/**
* Check if an update is available
* @returns Promise resolving to true if an update is available
*/
public async checkForUpdates(): Promise<boolean> {
try {
const latestVersion = await this.getLatestVersion();
const currentVersion = this.getVersion();
// Compare versions
this.updateAvailable = this.compareVersions(latestVersion, currentVersion) > 0;
this.latestVersion = latestVersion;
return this.updateAvailable;
} catch (error) {
console.error(`Error checking for updates: ${error.message}`);
return false;
}
}
/**
* Get update status information
* @returns Object with update status information
*/
public getUpdateStatus(): {
currentVersion: string,
latestVersion: string,
updateAvailable: boolean
} {
return {
currentVersion: this.getVersion(),
latestVersion: this.latestVersion || this.getVersion(),
updateAvailable: this.updateAvailable
};
}
/**
* Get the latest version from npm registry
* @returns Promise resolving to the latest version string
*/
private async getLatestVersion(): Promise<string> {
return new Promise<string>((resolve, reject) => {
const options = {
hostname: 'registry.npmjs.org',
path: '/@serve.zone/nupst',
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': `nupst/${this.getVersion()}`
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const response = JSON.parse(data);
if (response['dist-tags'] && response['dist-tags'].latest) {
resolve(response['dist-tags'].latest);
} else {
reject(new Error('Failed to parse version from npm registry response'));
}
} catch (error) {
reject(error);
}
});
});
req.on('error', (error) => {
reject(error);
});
req.end();
});
}
/**
* Compare two semantic version strings
* @param versionA First version
* @param versionB Second version
* @returns -1 if versionA < versionB, 0 if equal, 1 if versionA > versionB
*/
private compareVersions(versionA: string, versionB: string): number {
const partsA = versionA.split('.').map(part => parseInt(part, 10));
const partsB = versionB.split('.').map(part => parseInt(part, 10));
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const partA = i < partsA.length ? partsA[i] : 0;
const partB = i < partsB.length ? partsB[i] : 0;
if (partA > partB) return 1;
if (partA < partB) return -1;
}
return 0; // Versions are equal
}
/**
* Log the current version and update status
*/
public logVersionInfo(checkForUpdates: boolean = true): void {
const version = this.getVersion();
console.log('┌─ NUPST Version ────────────────────────┐');
console.log(`│ Current Version: ${version}`);
if (this.updateAvailable && this.latestVersion) {
console.log(`│ Update Available: ${this.latestVersion}`);
console.log('│ Run "sudo nupst update" to update');
} else if (checkForUpdates) {
console.log('│ Checking for updates...');
this.checkForUpdates().then(updateAvailable => {
if (updateAvailable) {
console.log(`│ Update Available: ${this.latestVersion}`);
console.log('│ Run "sudo nupst update" to update');
} else {
console.log('│ You are running the latest version');
}
console.log('└──────────────────────────────────────────┘');
}).catch(() => {
console.log('│ Could not check for updates');
console.log('└──────────────────────────────────────────┘');
});
} else {
console.log('└──────────────────────────────────────────┘');
}
}
} }

View File

@ -15,6 +15,8 @@ const execAsync = promisify(exec);
export class NupstSnmp { export class NupstSnmp {
// Active OID set // Active OID set
private activeOIDs: OIDSet; private activeOIDs: OIDSet;
// Reference to the parent Nupst instance
private nupst: any; // Type 'any' to avoid circular dependency
// Default SNMP configuration // Default SNMP configuration
private readonly DEFAULT_CONFIG: SnmpConfig = { private readonly DEFAULT_CONFIG: SnmpConfig = {
@ -43,6 +45,21 @@ export class NupstSnmp {
this.activeOIDs = UpsOidSets.getOidSet('cyberpower'); this.activeOIDs = UpsOidSets.getOidSet('cyberpower');
} }
/**
* Set reference to the main Nupst instance
* @param nupst Reference to the main Nupst instance
*/
public setNupst(nupst: any): void {
this.nupst = nupst;
}
/**
* Get reference to the main Nupst instance
*/
public getNupst(): any {
return this.nupst;
}
/** /**
* Set active OID set based on UPS model * Set active OID set based on UPS model
* @param config SNMP configuration * @param config SNMP configuration

View File

@ -129,6 +129,9 @@ WantedBy=multi-user.target
*/ */
public async getStatus(): Promise<void> { public async getStatus(): Promise<void> {
try { try {
// Display version information
this.daemon.getNupstSnmp().getNupst().logVersionInfo();
// Check if config exists first // Check if config exists first
try { try {
await this.checkConfigExists(); await this.checkConfigExists();