feat(core): Add update checking and version logging across startup components
This commit is contained in:
		| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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' | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								ts/daemon.ts
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								ts/daemon.ts
									
									
									
									
									
								
							| @@ -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(); | ||||||
|   | |||||||
							
								
								
									
										146
									
								
								ts/nupst.ts
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								ts/nupst.ts
									
									
									
									
									
								
							| @@ -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('└──────────────────────────────────────────┘'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user