Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
f583e1466f | |||
9d893a97b6 | |||
aa52d5e9f6 | |||
623b7ee51f | |||
897e86ad60 | |||
ed78db20e2 | |||
bd00dfe02c | |||
55c040df82 | |||
e68654a022 | |||
89a5d23d2f | |||
f9aa1cfd2f | |||
e47f316d0a | |||
901127f784 | |||
dc4fd5afba |
43
changelog.md
43
changelog.md
@ -1,5 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-03-25 - 2.4.7 - fix(readme)
|
||||
Update installation instructions to combine download and execution into a single command for clarity
|
||||
|
||||
- Method 1 now uses a unified one-line command to download and run the install script
|
||||
|
||||
## 2025-03-25 - 2.4.6 - fix(installer)
|
||||
Improve installation instructions for interactive and non-interactive setups
|
||||
|
||||
- Changed install.sh to require explicit download of the install script and updated error messages for non-interactive modes
|
||||
- Updated readme.md to include three distinct installation methods with clear command examples
|
||||
|
||||
## 2025-03-25 - 2.4.5 - fix(install)
|
||||
Improve interactive terminal detection and update installation instructions
|
||||
|
||||
- Enhanced install.sh to better detect non-interactive environments and provide clearer guidance for both interactive and non-interactive installations
|
||||
- Updated README.md quick install instructions to recommend process substitution and clarify auto-yes usage
|
||||
|
||||
## 2025-03-25 - 2.4.4 - fix(install)
|
||||
Improve interactive mode detection and non-interactive installation handling in install.sh
|
||||
|
||||
- Detect and warn when running without a controlling terminal
|
||||
- Attempt to use /dev/tty for user input when possible
|
||||
- Update prompts and error messages for auto-installation of dependencies
|
||||
- Clarify installation instructions in readme for interactive and non-interactive modes
|
||||
|
||||
## 2025-03-25 - 2.4.3 - fix(readme)
|
||||
Update Quick Install command syntax in readme for auto-yes installation
|
||||
|
||||
- Changed installation command to use: curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -c "bash -s -- -y"
|
||||
|
||||
## 2025-03-25 - 2.4.2 - fix(daemon)
|
||||
Refactor shutdown initiation logic in daemon by moving the initiateShutdown and monitorDuringShutdown methods from the SNMP manager to the daemon, and update calls accordingly
|
||||
|
||||
- Moved initiateShutdown and monitorDuringShutdown to the daemon class for improved cohesion
|
||||
- Updated references in the daemon to call its own shutdown method instead of the SNMP manager
|
||||
- Removed redundant initiateShutdown method from the SNMP manager
|
||||
|
||||
## 2025-03-25 - 2.4.1 - fix(docs)
|
||||
Update readme with detailed legal and trademark guidance
|
||||
|
||||
- Clarified legal section by adding trademark and company information
|
||||
- Ensured users understand that licensing terms do not imply endorsement by the company
|
||||
|
||||
## 2025-03-25 - 2.4.0 - feat(installer)
|
||||
Add auto-yes flag to installer and update installation documentation
|
||||
|
||||
|
43
install.sh
43
install.sh
@ -50,11 +50,42 @@ fi
|
||||
|
||||
# Detect if script is being piped or run directly
|
||||
PIPED=0
|
||||
INTERACTIVE=1
|
||||
if [ ! -t 0 ]; then
|
||||
# Being piped, need to clone the repo
|
||||
PIPED=1
|
||||
fi
|
||||
|
||||
# Check if stdin is a terminal
|
||||
if [ ! -t 0 ] || [ ! -t 1 ]; then
|
||||
# Either stdin or stdout is not a terminal, check if -y was provided
|
||||
if [ $AUTO_YES -ne 1 ]; then
|
||||
echo "Script detected it's running in a non-interactive environment without -y flag."
|
||||
echo "Attempting to find a controlling terminal for interactive prompts..."
|
||||
# Try to use a controlling terminal for user input
|
||||
if [ -t 1 ]; then
|
||||
# Stdout is a terminal, use it
|
||||
exec < /dev/tty 2>/dev/null || INTERACTIVE=0
|
||||
else
|
||||
# Try to find controlling terminal
|
||||
exec < /dev/tty 2>/dev/null || INTERACTIVE=0
|
||||
fi
|
||||
|
||||
if [ $INTERACTIVE -eq 0 ]; then
|
||||
echo "ERROR: No controlling terminal available for interactive prompts."
|
||||
echo "For interactive installation (RECOMMENDED):"
|
||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh -o nupst-install.sh"
|
||||
echo " sudo bash nupst-install.sh"
|
||||
echo ""
|
||||
echo "For non-interactive installation with automatic dependency installation:"
|
||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y"
|
||||
exit 1
|
||||
else
|
||||
echo "Interactive terminal found, continuing with prompts..."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Helper function to detect OS type
|
||||
detect_os() {
|
||||
if [ -f /etc/os-release ]; then
|
||||
@ -141,8 +172,10 @@ if [ $PIPED -eq 1 ]; then
|
||||
if [ $AUTO_YES -eq 1 ]; then
|
||||
echo "Auto-installing git (-y flag provided)..."
|
||||
install_git
|
||||
else
|
||||
read -p "Would you like to install git now? (y/N): " install_git_prompt
|
||||
elif [ $INTERACTIVE -eq 1 ]; then
|
||||
# If interactive and no -y flag, ask the user
|
||||
echo "Would you like to install git now? (y/N): "
|
||||
read -r install_git_prompt
|
||||
|
||||
if [[ "$install_git_prompt" =~ ^[Yy]$ ]]; then
|
||||
install_git
|
||||
@ -152,6 +185,12 @@ if [ $PIPED -eq 1 ]; then
|
||||
echo " sudo bash install.sh -y"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Non-interactive mode without -y flag
|
||||
echo "Error: Git is required but not installed."
|
||||
echo "In non-interactive mode, use -y flag to auto-install dependencies:"
|
||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@serve.zone/nupst",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.7",
|
||||
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
|
33
readme.md
33
readme.md
@ -19,13 +19,21 @@ NUPST is a command-line tool that monitors SNMP-enabled UPS devices and initiate
|
||||
### Quick Install (One-line command)
|
||||
|
||||
```bash
|
||||
# Install directly without cloning the repository (requires root privileges)
|
||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
||||
# Method 1: Download and run (most reliable across all environments)
|
||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh -o nupst-install.sh && sudo bash nupst-install.sh && rm nupst-install.sh
|
||||
```
|
||||
|
||||
# Install with auto-yes for dependencies (will install git automatically if needed)
|
||||
```bash
|
||||
# Method 2: Pipe with automatic yes for dependencies (non-interactive)
|
||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y
|
||||
```
|
||||
|
||||
```bash
|
||||
# Method 3: Process substitution (only on systems that support /dev/fd/)
|
||||
# Note: This may fail on some systems with "No such file or directory" errors
|
||||
sudo bash <(curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh)
|
||||
```
|
||||
|
||||
### Direct from Git
|
||||
|
||||
```bash
|
||||
@ -252,6 +260,21 @@ NUPST was designed with security in mind:
|
||||
|
||||
The codebase is small, focused, and designed to be easily auditable. All code is open source and available for review.
|
||||
|
||||
## License
|
||||
## License and Legal Information
|
||||
|
||||
MIT
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/nupst',
|
||||
version: '2.4.0',
|
||||
version: '2.4.7',
|
||||
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
||||
}
|
||||
|
88
ts/daemon.ts
88
ts/daemon.ts
@ -1,7 +1,11 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { NupstSnmp, type ISnmpConfig } from './snmp.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Configuration interface for the daemon
|
||||
*/
|
||||
@ -269,7 +273,7 @@ export class NupstDaemon {
|
||||
if (status.batteryCapacity < this.config.thresholds.battery) {
|
||||
console.log('⚠️ WARNING: Battery capacity below threshold');
|
||||
console.log(`Current: ${status.batteryCapacity}% | Threshold: ${this.config.thresholds.battery}%`);
|
||||
await this.snmp.initiateShutdown('Battery capacity below threshold');
|
||||
await this.initiateShutdown('Battery capacity below threshold');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -277,10 +281,90 @@ export class NupstDaemon {
|
||||
if (status.batteryRuntime < this.config.thresholds.runtime) {
|
||||
console.log('⚠️ WARNING: Runtime below threshold');
|
||||
console.log(`Current: ${status.batteryRuntime} min | Threshold: ${this.config.thresholds.runtime} min`);
|
||||
await this.snmp.initiateShutdown('Runtime below threshold');
|
||||
await this.initiateShutdown('Runtime below threshold');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate system shutdown with UPS monitoring during shutdown
|
||||
* @param reason Reason for shutdown
|
||||
*/
|
||||
public async initiateShutdown(reason: string): Promise<void> {
|
||||
console.log(`Initiating system shutdown due to: ${reason}`);
|
||||
|
||||
// Set a longer delay for shutdown to allow VMs and services to close
|
||||
const shutdownDelayMinutes = 5;
|
||||
|
||||
try {
|
||||
// Execute shutdown command with delay to allow for VM graceful shutdown
|
||||
const { stdout } = await execAsync(`shutdown -h +${shutdownDelayMinutes} "UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes"`);
|
||||
console.log('Shutdown initiated:', stdout);
|
||||
console.log(`Allowing ${shutdownDelayMinutes} minutes for VMs to shut down safely`);
|
||||
|
||||
// Monitor UPS during shutdown and force immediate shutdown if battery gets too low
|
||||
console.log('Monitoring UPS during shutdown process...');
|
||||
await this.monitorDuringShutdown();
|
||||
} catch (error) {
|
||||
console.error('Failed to initiate shutdown:', error);
|
||||
// Try a different method if first one fails
|
||||
try {
|
||||
console.log('Trying alternative shutdown method...');
|
||||
await execAsync('poweroff --force');
|
||||
} catch (innerError) {
|
||||
console.error('All shutdown methods failed:', innerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor UPS during system shutdown
|
||||
* Force immediate shutdown if battery gets critically low
|
||||
*/
|
||||
private async monitorDuringShutdown(): Promise<void> {
|
||||
const EMERGENCY_RUNTIME_THRESHOLD = 5; // 5 minutes remaining is critical
|
||||
const CHECK_INTERVAL = 30000; // Check every 30 seconds during shutdown
|
||||
const MAX_MONITORING_TIME = 5 * 60 * 1000; // Max 5 minutes of monitoring
|
||||
const startTime = Date.now();
|
||||
|
||||
console.log(`Emergency shutdown threshold: ${EMERGENCY_RUNTIME_THRESHOLD} minutes remaining battery runtime`);
|
||||
|
||||
// Continue monitoring until max monitoring time is reached
|
||||
while (Date.now() - startTime < MAX_MONITORING_TIME) {
|
||||
try {
|
||||
console.log('Checking UPS status during shutdown...');
|
||||
const status = await this.snmp.getUpsStatus(this.config.snmp);
|
||||
|
||||
console.log(`Current battery: ${status.batteryCapacity}%, Runtime: ${status.batteryRuntime} minutes`);
|
||||
|
||||
// If battery runtime gets critically low, force immediate shutdown
|
||||
if (status.batteryRuntime < EMERGENCY_RUNTIME_THRESHOLD) {
|
||||
console.log('┌─ EMERGENCY SHUTDOWN ─────────────────────┐');
|
||||
console.log(`│ Battery runtime critically low: ${status.batteryRuntime} minutes`);
|
||||
console.log('│ Forcing immediate shutdown!');
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
|
||||
try {
|
||||
await execAsync('shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"');
|
||||
} catch (error) {
|
||||
console.error('Emergency shutdown failed, trying alternative method...');
|
||||
await execAsync('poweroff --force');
|
||||
}
|
||||
|
||||
// Stop monitoring after initiating emergency shutdown
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait before checking again
|
||||
await this.sleep(CHECK_INTERVAL);
|
||||
} catch (error) {
|
||||
console.error('Error monitoring UPS during shutdown:', error);
|
||||
await this.sleep(CHECK_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('UPS monitoring during shutdown completed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep for the specified milliseconds
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import * as dgram from 'dgram';
|
||||
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js';
|
||||
import { UpsOidSets } from './oid-sets.js';
|
||||
import { SnmpPacketCreator } from './packet-creator.js';
|
||||
import { SnmpPacketParser } from './packet-parser.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Class for SNMP communication with UPS devices
|
||||
* Main entry point for SNMP functionality
|
||||
@ -507,26 +503,5 @@ export class NupstSnmp {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate system shutdown
|
||||
* @param reason Reason for shutdown
|
||||
*/
|
||||
public async initiateShutdown(reason: string): Promise<void> {
|
||||
console.log(`Initiating system shutdown due to: ${reason}`);
|
||||
try {
|
||||
// Execute shutdown command with 5 minute delay to allow for VM graceful shutdown
|
||||
const { stdout } = await execAsync('shutdown -h +5 "UPS battery critical, shutting down in 5 minutes"');
|
||||
console.log('Shutdown initiated:', stdout);
|
||||
console.log('Allowing 5 minutes for VMs to shut down safely');
|
||||
} catch (error) {
|
||||
console.error('Failed to initiate shutdown:', error);
|
||||
// Try a different method if first one fails
|
||||
try {
|
||||
console.log('Trying alternative shutdown method...');
|
||||
await execAsync('poweroff --force');
|
||||
} catch (innerError) {
|
||||
console.error('All shutdown methods failed:', innerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
// initiateShutdown method has been moved to the NupstDaemon class
|
||||
}
|
Reference in New Issue
Block a user