Compare commits
14 Commits
v2.4.1
...
4ad383884c
Author | SHA1 | Date | |
---|---|---|---|
4ad383884c | |||
65a9d1c798 | |||
f583e1466f | |||
9d893a97b6 | |||
aa52d5e9f6 | |||
623b7ee51f | |||
897e86ad60 | |||
ed78db20e2 | |||
bd00dfe02c | |||
55c040df82 | |||
e68654a022 | |||
89a5d23d2f | |||
f9aa1cfd2f | |||
e47f316d0a |
44
changelog.md
44
changelog.md
@ -1,5 +1,49 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.4.8 - fix(installer)
|
||||||
|
Improve Git dependency handling and repository cloning in install.sh
|
||||||
|
|
||||||
|
- Add explicit check for git installation and prompt the user interactively if git is missing.
|
||||||
|
- Auto-install git when '-y' flag is provided in non-interactive mode.
|
||||||
|
- Ensure proper cloning of the repository when running the installer outside the repo.
|
||||||
|
|
||||||
|
## 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)
|
## 2025-03-25 - 2.4.1 - fix(docs)
|
||||||
Update readme with detailed legal and trademark guidance
|
Update readme with detailed legal and trademark guidance
|
||||||
|
|
||||||
|
110
install.sh
110
install.sh
@ -50,11 +50,42 @@ fi
|
|||||||
|
|
||||||
# Detect if script is being piped or run directly
|
# Detect if script is being piped or run directly
|
||||||
PIPED=0
|
PIPED=0
|
||||||
|
INTERACTIVE=1
|
||||||
if [ ! -t 0 ]; then
|
if [ ! -t 0 ]; then
|
||||||
# Being piped, need to clone the repo
|
# Being piped, need to clone the repo
|
||||||
PIPED=1
|
PIPED=1
|
||||||
fi
|
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
|
# Helper function to detect OS type
|
||||||
detect_os() {
|
detect_os() {
|
||||||
if [ -f /etc/os-release ]; then
|
if [ -f /etc/os-release ]; then
|
||||||
@ -131,29 +162,37 @@ install_git() {
|
|||||||
INSTALL_DIR="/opt/nupst"
|
INSTALL_DIR="/opt/nupst"
|
||||||
REPO_URL="https://code.foss.global/serve.zone/nupst.git"
|
REPO_URL="https://code.foss.global/serve.zone/nupst.git"
|
||||||
|
|
||||||
if [ $PIPED -eq 1 ]; then
|
# Check if git is installed - needed for both piped and direct execution
|
||||||
echo "Installing NUPST from remote repository..."
|
if ! command -v git &> /dev/null; then
|
||||||
|
echo "Git is required but not installed."
|
||||||
|
|
||||||
# Check if git is installed
|
if [ $AUTO_YES -eq 1 ]; then
|
||||||
if ! command -v git &> /dev/null; then
|
echo "Auto-installing git (-y flag provided)..."
|
||||||
echo "Git is required but not installed."
|
install_git
|
||||||
|
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 [ $AUTO_YES -eq 1 ]; then
|
if [[ "$install_git_prompt" =~ ^[Yy]$ ]]; then
|
||||||
echo "Auto-installing git (-y flag provided)..."
|
|
||||||
install_git
|
install_git
|
||||||
else
|
else
|
||||||
read -p "Would you like to install git now? (y/N): " install_git_prompt
|
echo "Git installation skipped. Please install git manually and run the installer again."
|
||||||
|
echo "Alternatively, you can run the installer with -y flag to automatically install git:"
|
||||||
if [[ "$install_git_prompt" =~ ^[Yy]$ ]]; then
|
echo " sudo bash install.sh -y"
|
||||||
install_git
|
exit 1
|
||||||
else
|
|
||||||
echo "Git installation skipped. Please install git manually and run the installer again."
|
|
||||||
echo "Alternatively, you can run the installer with -y flag to automatically install git:"
|
|
||||||
echo " sudo bash install.sh -y"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $PIPED -eq 1 ]; then
|
||||||
|
echo "Installing NUPST from remote repository..."
|
||||||
|
|
||||||
# Check if installation directory exists
|
# Check if installation directory exists
|
||||||
if [ -d "$INSTALL_DIR" ] && [ -d "$INSTALL_DIR/.git" ]; then
|
if [ -d "$INSTALL_DIR" ] && [ -d "$INSTALL_DIR/.git" ]; then
|
||||||
@ -196,12 +235,47 @@ if [ $PIPED -eq 1 ]; then
|
|||||||
# Set script directory to the cloned repo
|
# Set script directory to the cloned repo
|
||||||
SCRIPT_DIR="$INSTALL_DIR"
|
SCRIPT_DIR="$INSTALL_DIR"
|
||||||
else
|
else
|
||||||
# Running directly from within the repo
|
# Running directly from within the repo or downloaded script
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
|
|
||||||
|
# When running from a downloaded script in a different location
|
||||||
|
# we need to clone the repository first
|
||||||
|
if [ ! -f "$SCRIPT_DIR/setup.sh" ]; then
|
||||||
|
echo "Running installer from downloaded script outside repository."
|
||||||
|
echo "Will clone the repository to $INSTALL_DIR..."
|
||||||
|
|
||||||
|
# Create installation directory if needed
|
||||||
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
|
echo "Removing previous installation at $INSTALL_DIR..."
|
||||||
|
rm -rf "$INSTALL_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
|
# Clone the repository
|
||||||
|
echo "Cloning NUPST repository to $INSTALL_DIR..."
|
||||||
|
git clone --depth 1 $REPO_URL "$INSTALL_DIR"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Failed to clone repository. Please check your internet connection."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update script directory to use the cloned repo
|
||||||
|
SCRIPT_DIR="$INSTALL_DIR"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run setup script
|
# Run setup script
|
||||||
echo "Running setup script..."
|
echo "Running setup script..."
|
||||||
|
if [ ! -f "$SCRIPT_DIR/setup.sh" ]; then
|
||||||
|
echo "ERROR: Setup script not found at $SCRIPT_DIR/setup.sh"
|
||||||
|
echo "Current directory: $(pwd)"
|
||||||
|
echo "Script directory: $SCRIPT_DIR"
|
||||||
|
ls -la "$SCRIPT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
bash "$SCRIPT_DIR/setup.sh"
|
bash "$SCRIPT_DIR/setup.sh"
|
||||||
|
|
||||||
# Install globally
|
# Install globally
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "2.4.1",
|
"version": "2.4.8",
|
||||||
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
14
readme.md
14
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)
|
### Quick Install (One-line command)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install directly without cloning the repository (requires root privileges)
|
# Method 1: Download and run (most reliable across all environments)
|
||||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
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
|
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
|
### Direct from Git
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/nupst',
|
name: '@serve.zone/nupst',
|
||||||
version: '2.4.1',
|
version: '2.4.8',
|
||||||
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
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 fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { promisify } from 'util';
|
||||||
import { NupstSnmp, type ISnmpConfig } from './snmp.js';
|
import { NupstSnmp, type ISnmpConfig } from './snmp.js';
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration interface for the daemon
|
* Configuration interface for the daemon
|
||||||
*/
|
*/
|
||||||
@ -269,7 +273,7 @@ export class NupstDaemon {
|
|||||||
if (status.batteryCapacity < this.config.thresholds.battery) {
|
if (status.batteryCapacity < this.config.thresholds.battery) {
|
||||||
console.log('⚠️ WARNING: Battery capacity below threshold');
|
console.log('⚠️ WARNING: Battery capacity below threshold');
|
||||||
console.log(`Current: ${status.batteryCapacity}% | Threshold: ${this.config.thresholds.battery}%`);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,10 +281,90 @@ export class NupstDaemon {
|
|||||||
if (status.batteryRuntime < this.config.thresholds.runtime) {
|
if (status.batteryRuntime < this.config.thresholds.runtime) {
|
||||||
console.log('⚠️ WARNING: Runtime below threshold');
|
console.log('⚠️ WARNING: Runtime below threshold');
|
||||||
console.log(`Current: ${status.batteryRuntime} min | Threshold: ${this.config.thresholds.runtime} min`);
|
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;
|
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
|
* Sleep for the specified milliseconds
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
import { exec } from 'child_process';
|
|
||||||
import { promisify } from 'util';
|
|
||||||
import * as dgram from 'dgram';
|
import * as dgram from 'dgram';
|
||||||
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js';
|
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js';
|
||||||
import { UpsOidSets } from './oid-sets.js';
|
import { UpsOidSets } from './oid-sets.js';
|
||||||
import { SnmpPacketCreator } from './packet-creator.js';
|
import { SnmpPacketCreator } from './packet-creator.js';
|
||||||
import { SnmpPacketParser } from './packet-parser.js';
|
import { SnmpPacketParser } from './packet-parser.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for SNMP communication with UPS devices
|
* Class for SNMP communication with UPS devices
|
||||||
* Main entry point for SNMP functionality
|
* Main entry point for SNMP functionality
|
||||||
@ -507,26 +503,5 @@ export class NupstSnmp {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// initiateShutdown method has been moved to the NupstDaemon class
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user