Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
2737fca294 | |||
896233914f | |||
5bb775b17d | |||
ae8219acf7 | |||
4ad383884c | |||
65a9d1c798 | |||
f583e1466f | |||
9d893a97b6 | |||
aa52d5e9f6 | |||
623b7ee51f | |||
897e86ad60 | |||
ed78db20e2 | |||
bd00dfe02c | |||
55c040df82 | |||
e68654a022 | |||
89a5d23d2f | |||
f9aa1cfd2f | |||
e47f316d0a | |||
901127f784 | |||
dc4fd5afba | |||
a7ced10f92 | |||
9b9e009523 | |||
1819b6827a | |||
bd5b85f6b0 | |||
c7db209da7 | |||
bbb8f4a22c | |||
ebc6f65fa9 | |||
0a459f9cd0 |
89
changelog.md
89
changelog.md
@ -1,5 +1,94 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.5.1 - fix(snmp)
|
||||||
|
Fix Eaton UPS support by updating power status OID and adjusting battery runtime conversion.
|
||||||
|
|
||||||
|
- Updated Eaton UPS power status OID to '1.3.6.1.4.1.534.1.4.4.0' to correctly detect online/battery status.
|
||||||
|
- Added conversion for Eaton UPS battery runtime from seconds to minutes in SNMP manager.
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.5.0 - feat(cli)
|
||||||
|
Automatically restart running NUPST service after configuration changes in interactive setup
|
||||||
|
|
||||||
|
- Added restartServiceIfRunning() to check and restart the service if it's active.
|
||||||
|
- Invoked the restart function post-setup to apply configuration changes immediately.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
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
|
||||||
|
|
||||||
|
- Enhance install.sh to parse -y/--yes and -h/--help options, automating git installation when auto-yes is provided
|
||||||
|
- Improve user prompts for dependency installation and provide clearer instructions
|
||||||
|
- Update readme.md to document new installer options and enhanced file system and service changes details
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.3.0 - feat(installer/cli)
|
||||||
|
Add OS detection and git auto-installation support to install.sh and improve service setup prompt in CLI
|
||||||
|
|
||||||
|
- Implemented helper functions in install.sh to detect OS type and automatically install git if missing
|
||||||
|
- Prompt user for git installation if not present before cloning the repository
|
||||||
|
- Enhanced CLI service setup flow to offer starting the NUPST service immediately after installation
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.2.0 - feat(cli)
|
||||||
|
Add 'config' command to display current configuration and update CLI help
|
||||||
|
|
||||||
|
- Introduce new 'config' command to show SNMP settings, thresholds, and configuration file location
|
||||||
|
- Update help text to include details for 'nupst config' command
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.1.0 - feat(cli)
|
||||||
|
Add uninstall command to CLI and update shutdown delay for graceful VM shutdown
|
||||||
|
|
||||||
|
- Implement uninstall command in ts/cli.ts that locates and executes uninstall.sh with user prompts
|
||||||
|
- Update uninstall.sh to support environment variables for configuration and repository removal
|
||||||
|
- Increase shutdown delay in ts/snmp/manager.ts from 1 minute to 5 minutes to allow VMs more time to shut down
|
||||||
|
|
||||||
## 2025-03-25 - 2.0.1 - fix(cli/systemd)
|
## 2025-03-25 - 2.0.1 - fix(cli/systemd)
|
||||||
Fix status command to pass debug flag and improve systemd status logging output
|
Fix status command to pass debug flag and improve systemd status logging output
|
||||||
|
|
||||||
|
213
install.sh
213
install.sh
@ -2,7 +2,45 @@
|
|||||||
|
|
||||||
# NUPST Installer Script
|
# NUPST Installer Script
|
||||||
# Downloads and installs NUPST globally on the system
|
# Downloads and installs NUPST globally on the system
|
||||||
# Can be used directly with curl: curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
# Can be used directly with curl:
|
||||||
|
# Without auto-installing dependencies:
|
||||||
|
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
||||||
|
# With auto-installing dependencies:
|
||||||
|
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# -y, --yes Automatically answer yes to all prompts
|
||||||
|
# -h, --help Show this help message
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
AUTO_YES=0
|
||||||
|
SHOW_HELP=0
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
-y|--yes)
|
||||||
|
AUTO_YES=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
SHOW_HELP=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $SHOW_HELP -eq 1 ]; then
|
||||||
|
echo "NUPST Installer Script"
|
||||||
|
echo "Usage: $0 [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -y, --yes Automatically answer yes to all prompts"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if running as root
|
# Check if running as root
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
@ -12,23 +50,149 @@ 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
|
||||||
|
detect_os() {
|
||||||
|
if [ -f /etc/os-release ]; then
|
||||||
|
. /etc/os-release
|
||||||
|
OS=$ID
|
||||||
|
elif type lsb_release >/dev/null 2>&1; then
|
||||||
|
OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
|
||||||
|
elif [ -f /etc/lsb-release ]; then
|
||||||
|
. /etc/lsb-release
|
||||||
|
OS=$DISTRIB_ID
|
||||||
|
elif [ -f /etc/debian_version ]; then
|
||||||
|
OS="debian"
|
||||||
|
elif [ -f /etc/redhat-release ]; then
|
||||||
|
if grep -q "CentOS" /etc/redhat-release; then
|
||||||
|
OS="centos"
|
||||||
|
elif grep -q "Fedora" /etc/redhat-release; then
|
||||||
|
OS="fedora"
|
||||||
|
else
|
||||||
|
OS="rhel"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
OS=$(uname -s)
|
||||||
|
fi
|
||||||
|
echo $OS
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to install git
|
||||||
|
install_git() {
|
||||||
|
OS=$(detect_os)
|
||||||
|
echo "Detected OS: $OS"
|
||||||
|
|
||||||
|
case "$OS" in
|
||||||
|
ubuntu|debian|pop|mint|elementary|kali|zorin)
|
||||||
|
echo "Installing git using apt..."
|
||||||
|
apt-get update && apt-get install -y git
|
||||||
|
;;
|
||||||
|
fedora|rhel|centos|almalinux|rocky)
|
||||||
|
echo "Installing git using dnf/yum..."
|
||||||
|
if command -v dnf &> /dev/null; then
|
||||||
|
dnf install -y git
|
||||||
|
else
|
||||||
|
yum install -y git
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
arch|manjaro|endeavouros|garuda)
|
||||||
|
echo "Installing git using pacman..."
|
||||||
|
pacman -Sy --noconfirm git
|
||||||
|
;;
|
||||||
|
opensuse*|suse|sles)
|
||||||
|
echo "Installing git using zypper..."
|
||||||
|
zypper install -y git
|
||||||
|
;;
|
||||||
|
alpine)
|
||||||
|
echo "Installing git using apk..."
|
||||||
|
apk add git
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported OS: $OS"
|
||||||
|
echo "Please install git manually and run the installer again."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check if git was installed successfully
|
||||||
|
if ! command -v git &> /dev/null; then
|
||||||
|
echo "Failed to install git. Please install git manually and run the installer again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Git installed successfully."
|
||||||
|
}
|
||||||
|
|
||||||
# Define installation directory
|
# Define installation directory
|
||||||
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. Please install git first."
|
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 [[ "$install_git_prompt" =~ ^[Yy]$ ]]; then
|
||||||
|
install_git
|
||||||
|
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
|
||||||
|
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
|
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
|
||||||
@ -71,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
|
||||||
|
21
license
Normal file
21
license
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Task Venture Capital GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "2.0.1",
|
"version": "2.5.1",
|
||||||
"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": {
|
||||||
|
81
readme.md
81
readme.md
@ -19,8 +19,19 @@ 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
|
||||||
|
```
|
||||||
|
|
||||||
|
```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
|
### Direct from Git
|
||||||
@ -33,20 +44,58 @@ cd nupst
|
|||||||
# Option 1: Quick install (requires root privileges)
|
# Option 1: Quick install (requires root privileges)
|
||||||
sudo ./install.sh
|
sudo ./install.sh
|
||||||
|
|
||||||
|
# Option 1a: Quick install with auto-yes for dependencies
|
||||||
|
sudo ./install.sh -y
|
||||||
|
|
||||||
# Option 2: Manual setup
|
# Option 2: Manual setup
|
||||||
./setup.sh
|
./setup.sh
|
||||||
sudo ln -s $(pwd)/bin/nupst /usr/local/bin/nupst
|
sudo ln -s $(pwd)/bin/nupst /usr/local/bin/nupst
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Installation Options
|
||||||
|
|
||||||
|
The installer script (`install.sh`) supports the following options:
|
||||||
|
|
||||||
|
```
|
||||||
|
-y, --yes Automatically answer yes to all prompts (like installing git)
|
||||||
|
-h, --help Show the help message
|
||||||
|
```
|
||||||
|
|
||||||
### From NPM
|
### From NPM
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g @serve.zone/nupst
|
npm install -g @serve.zone/nupst
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## System Changes
|
||||||
|
|
||||||
|
When installed, NUPST makes the following changes to your system:
|
||||||
|
|
||||||
|
### File System Changes
|
||||||
|
|
||||||
|
| Path | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `/opt/nupst/` | Main installation directory containing the NUPST files |
|
||||||
|
| `/etc/nupst/config.json` | Configuration file |
|
||||||
|
| `/usr/local/bin/nupst` | Symlink to the NUPST executable |
|
||||||
|
| `/etc/systemd/system/nupst.service` | Systemd service file (when enabled) |
|
||||||
|
|
||||||
|
### Service Changes
|
||||||
|
|
||||||
|
- Creates and enables a systemd service called `nupst.service` (when enabled with `nupst enable`)
|
||||||
|
- The service runs with root permissions to allow system shutdown capabilities
|
||||||
|
|
||||||
|
### Network Access
|
||||||
|
|
||||||
|
- NUPST only communicates with your UPS device via SNMP (default port 161)
|
||||||
|
- Brief connections to npmjs.org to check for updates
|
||||||
|
|
||||||
## Uninstallation
|
## Uninstallation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Using the CLI tool:
|
||||||
|
sudo nupst uninstall
|
||||||
|
|
||||||
# If installed from git repository:
|
# If installed from git repository:
|
||||||
cd /path/to/nupst
|
cd /path/to/nupst
|
||||||
sudo ./uninstall.sh
|
sudo ./uninstall.sh
|
||||||
@ -57,9 +106,10 @@ npm uninstall -g @serve.zone/nupst
|
|||||||
|
|
||||||
The uninstaller will:
|
The uninstaller will:
|
||||||
- Stop and disable the systemd service (if installed)
|
- Stop and disable the systemd service (if installed)
|
||||||
- Remove the systemd service file
|
- Remove the systemd service file from `/etc/systemd/system/nupst.service`
|
||||||
- Remove the symlink from /usr/local/bin
|
- Remove the symlink from `/usr/local/bin/nupst`
|
||||||
- Optionally remove configuration files from /etc/nupst
|
- Optionally remove configuration files from `/etc/nupst/`
|
||||||
|
- Remove the repository directory from `/opt/nupst/` (when using `nupst uninstall`)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -76,7 +126,9 @@ Usage:
|
|||||||
nupst status - Show status of the systemd service and UPS status
|
nupst status - Show status of the systemd service and UPS status
|
||||||
nupst setup - Run the interactive setup to configure SNMP settings
|
nupst setup - Run the interactive setup to configure SNMP settings
|
||||||
nupst test - Test the current configuration by connecting to the UPS
|
nupst test - Test the current configuration by connecting to the UPS
|
||||||
|
nupst config - Display the current configuration
|
||||||
nupst update - Update NUPST from repository and refresh systemd service (requires root)
|
nupst update - Update NUPST from repository and refresh systemd service (requires root)
|
||||||
|
nupst uninstall - Completely uninstall NUPST from the system (requires root)
|
||||||
nupst help - Show this help message
|
nupst help - Show this help message
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -208,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.
|
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 = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/nupst',
|
name: '@serve.zone/nupst',
|
||||||
version: '2.0.1',
|
version: '2.5.1',
|
||||||
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
||||||
}
|
}
|
||||||
|
254
ts/cli.ts
254
ts/cli.ts
@ -1,4 +1,7 @@
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { Nupst } from './nupst.js';
|
import { Nupst } from './nupst.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,6 +97,14 @@ export class NupstCli {
|
|||||||
case 'update':
|
case 'update':
|
||||||
await this.update();
|
await this.update();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'uninstall':
|
||||||
|
await this.uninstall();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'config':
|
||||||
|
await this.showConfig();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'help':
|
case 'help':
|
||||||
default:
|
default:
|
||||||
@ -365,7 +376,9 @@ Usage:
|
|||||||
nupst status - Show status of the systemd service and UPS status
|
nupst status - Show status of the systemd service and UPS status
|
||||||
nupst setup - Run the interactive setup to configure SNMP settings
|
nupst setup - Run the interactive setup to configure SNMP settings
|
||||||
nupst test - Test the current configuration by connecting to the UPS
|
nupst test - Test the current configuration by connecting to the UPS
|
||||||
|
nupst config - Display the current configuration
|
||||||
nupst update - Update NUPST from repository and refresh systemd service (requires root)
|
nupst update - Update NUPST from repository and refresh systemd service (requires root)
|
||||||
|
nupst uninstall - Completely uninstall NUPST from the system (requires root)
|
||||||
nupst help - Show this help message
|
nupst help - Show this help message
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -520,6 +533,9 @@ Options:
|
|||||||
// Test the connection if requested
|
// Test the connection if requested
|
||||||
await this.optionallyTestConnection(config, prompt);
|
await this.optionallyTestConnection(config, prompt);
|
||||||
|
|
||||||
|
// Check if service is running and restart it if needed
|
||||||
|
await this.restartServiceIfRunning();
|
||||||
|
|
||||||
console.log('\nSetup complete!');
|
console.log('\nSetup complete!');
|
||||||
await this.optionallyEnableService(prompt);
|
await this.optionallyEnableService(prompt);
|
||||||
}
|
}
|
||||||
@ -821,7 +837,45 @@ Options:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optionally enable systemd service
|
* Check if the systemd service is running and restart it if it is
|
||||||
|
* This is useful after configuration changes
|
||||||
|
*/
|
||||||
|
private async restartServiceIfRunning(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Check if the service is active
|
||||||
|
const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
// Service is running, restart it
|
||||||
|
console.log('┌─ Service Update ─────────────────────────┐');
|
||||||
|
console.log('│ Configuration has changed.');
|
||||||
|
console.log('│ Restarting NUPST service to apply changes...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process.getuid && process.getuid() === 0) {
|
||||||
|
// We have root access, restart directly
|
||||||
|
execSync('systemctl restart nupst.service');
|
||||||
|
console.log('│ Service restarted successfully.');
|
||||||
|
} else {
|
||||||
|
// No root access, show instructions
|
||||||
|
console.log('│ Please restart the service with:');
|
||||||
|
console.log('│ sudo systemctl restart nupst.service');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`│ Error restarting service: ${error.message}`);
|
||||||
|
console.log('│ You may need to restart the service manually:');
|
||||||
|
console.log('│ sudo systemctl restart nupst.service');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('└──────────────────────────────────────────┘');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors checking service status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally enable and start systemd service
|
||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
*/
|
*/
|
||||||
private async optionallyEnableService(prompt: (question: string) => Promise<string>): Promise<void> {
|
private async optionallyEnableService(prompt: (question: string) => Promise<string>): Promise<void> {
|
||||||
@ -830,9 +884,203 @@ Options:
|
|||||||
} else {
|
} else {
|
||||||
const setupService = await prompt('Would you like to enable NUPST as a system service? (y/N): ');
|
const setupService = await prompt('Would you like to enable NUPST as a system service? (y/N): ');
|
||||||
if (setupService.toLowerCase() === 'y') {
|
if (setupService.toLowerCase() === 'y') {
|
||||||
await this.nupst.getSystemd().install();
|
try {
|
||||||
console.log('Service installed. Use "nupst start" to start the service.');
|
await this.nupst.getSystemd().install();
|
||||||
|
console.log('Service installed and enabled to start on boot.');
|
||||||
|
|
||||||
|
// Ask if the user wants to start the service now
|
||||||
|
const startService = await prompt('Would you like to start the NUPST service now? (Y/n): ');
|
||||||
|
if (startService.toLowerCase() !== 'n') {
|
||||||
|
await this.nupst.getSystemd().start();
|
||||||
|
console.log('NUPST service started successfully.');
|
||||||
|
} else {
|
||||||
|
console.log('Service not started. Use "nupst start" to start the service manually.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to setup service: ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the current configuration
|
||||||
|
*/
|
||||||
|
private async showConfig(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Try to load configuration
|
||||||
|
try {
|
||||||
|
await this.nupst.getDaemon().loadConfig();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('┌─ Configuration Error ─────────────────────┐');
|
||||||
|
console.error('│ No configuration found.');
|
||||||
|
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
||||||
|
console.error('└──────────────────────────────────────────┘');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current configuration
|
||||||
|
const config = this.nupst.getDaemon().getConfig();
|
||||||
|
|
||||||
|
console.log('┌─ NUPST Configuration ──────────────────────┐');
|
||||||
|
|
||||||
|
// SNMP Settings
|
||||||
|
console.log('│ SNMP Settings:');
|
||||||
|
console.log(`│ Host: ${config.snmp.host}`);
|
||||||
|
console.log(`│ Port: ${config.snmp.port}`);
|
||||||
|
console.log(`│ Version: ${config.snmp.version}`);
|
||||||
|
console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
||||||
|
|
||||||
|
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
||||||
|
console.log(`│ Community: ${config.snmp.community}`);
|
||||||
|
} else if (config.snmp.version === 3) {
|
||||||
|
console.log(`│ Security Level: ${config.snmp.securityLevel}`);
|
||||||
|
console.log(`│ Username: ${config.snmp.username}`);
|
||||||
|
|
||||||
|
// Show auth and privacy details based on security level
|
||||||
|
if (config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv') {
|
||||||
|
console.log(`│ Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.snmp.securityLevel === 'authPriv') {
|
||||||
|
console.log(`│ Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show timeout value
|
||||||
|
console.log(`│ Timeout: ${config.snmp.timeout / 1000} seconds`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show OIDs if custom model is selected
|
||||||
|
if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) {
|
||||||
|
console.log('│ Custom OIDs:');
|
||||||
|
console.log(`│ Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`);
|
||||||
|
console.log(`│ Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`);
|
||||||
|
console.log(`│ Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thresholds
|
||||||
|
console.log('│ Thresholds:');
|
||||||
|
console.log(`│ Battery: ${config.thresholds.battery}%`);
|
||||||
|
console.log(`│ Runtime: ${config.thresholds.runtime} minutes`);
|
||||||
|
console.log(`│ Check Interval: ${config.checkInterval / 1000} seconds`);
|
||||||
|
|
||||||
|
// Configuration file location
|
||||||
|
console.log('│');
|
||||||
|
console.log('│ Configuration File Location:');
|
||||||
|
console.log('│ /etc/nupst/config.json');
|
||||||
|
|
||||||
|
console.log('└──────────────────────────────────────────┘');
|
||||||
|
|
||||||
|
// Show service status
|
||||||
|
try {
|
||||||
|
const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
||||||
|
const isEnabled = execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled';
|
||||||
|
|
||||||
|
console.log('┌─ Service Status ─────────────────────────┐');
|
||||||
|
console.log(`│ Service Active: ${isActive ? 'Yes' : 'No'}`);
|
||||||
|
console.log(`│ Service Enabled: ${isEnabled ? 'Yes' : 'No'}`);
|
||||||
|
console.log('└──────────────────────────────────────────┘');
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors checking service status
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to display configuration: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely uninstall NUPST from the system
|
||||||
|
*/
|
||||||
|
private async uninstall(): Promise<void> {
|
||||||
|
// Check if running as root
|
||||||
|
this.checkRootAccess('This command must be run as root.');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Import readline module for user input
|
||||||
|
const readline = await import('readline');
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to prompt for input
|
||||||
|
const prompt = (question: string): Promise<string> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer: string) => {
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('\nNUPST Uninstaller');
|
||||||
|
console.log('===============');
|
||||||
|
console.log('This will completely remove NUPST from your system.\n');
|
||||||
|
|
||||||
|
// Ask about removing configuration
|
||||||
|
const removeConfig = await prompt('Do you want to remove the NUPST configuration files? (y/N): ');
|
||||||
|
|
||||||
|
// Find the uninstall.sh script location
|
||||||
|
let uninstallScriptPath: string;
|
||||||
|
|
||||||
|
// Try to determine script location based on executable path
|
||||||
|
try {
|
||||||
|
// For ESM, we can use import.meta.url, but since we might be in CJS
|
||||||
|
// we'll use a more reliable approach based on process.argv[1]
|
||||||
|
const binPath = process.argv[1];
|
||||||
|
const modulePath = dirname(dirname(binPath));
|
||||||
|
uninstallScriptPath = join(modulePath, 'uninstall.sh');
|
||||||
|
|
||||||
|
// Check if the script exists
|
||||||
|
await fs.access(uninstallScriptPath);
|
||||||
|
} catch (error) {
|
||||||
|
// If we can't find it in the expected location, try common installation paths
|
||||||
|
const commonPaths = [
|
||||||
|
'/opt/nupst/uninstall.sh',
|
||||||
|
join(process.cwd(), 'uninstall.sh')
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const path of commonPaths) {
|
||||||
|
try {
|
||||||
|
await fs.access(path);
|
||||||
|
uninstallScriptPath = path;
|
||||||
|
break;
|
||||||
|
} catch {
|
||||||
|
// Continue to next path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uninstallScriptPath) {
|
||||||
|
console.error('Could not locate uninstall.sh script. Aborting uninstall.');
|
||||||
|
rl.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close readline before executing script
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
// Execute uninstall.sh with the appropriate option
|
||||||
|
console.log(`\nRunning uninstaller from ${uninstallScriptPath}...`);
|
||||||
|
|
||||||
|
// Pass the configuration removal option as an environment variable
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
REMOVE_CONFIG: removeConfig.toLowerCase() === 'y' ? 'yes' : 'no',
|
||||||
|
REMOVE_REPO: 'yes', // Always remove repo as requested
|
||||||
|
NUPST_CLI_CALL: 'true' // Flag to indicate this is being called from CLI
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the uninstall script with sudo
|
||||||
|
execSync(`sudo bash ${uninstallScriptPath}`, {
|
||||||
|
env,
|
||||||
|
stdio: 'inherit' // Show output in the terminal
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Uninstall failed: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
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
|
||||||
@ -352,6 +348,14 @@ export class NupstSnmp {
|
|||||||
} else if (powerStatusValue === 3) {
|
} else if (powerStatusValue === 3) {
|
||||||
powerStatus = 'onBattery';
|
powerStatus = 'onBattery';
|
||||||
}
|
}
|
||||||
|
} else if (config.upsModel === 'eaton') {
|
||||||
|
// Eaton UPS: xupsOutputSource values
|
||||||
|
// 3=normal/mains, 5=battery, etc.
|
||||||
|
if (powerStatusValue === 3) {
|
||||||
|
powerStatus = 'online';
|
||||||
|
} else if (powerStatusValue === 5) {
|
||||||
|
powerStatus = 'onBattery';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default interpretation for other UPS models
|
// Default interpretation for other UPS models
|
||||||
if (powerStatusValue === 1) {
|
if (powerStatusValue === 1) {
|
||||||
@ -361,14 +365,21 @@ export class NupstSnmp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert TimeTicks to minutes for CyberPower runtime (value is in 1/100 seconds)
|
// Convert to minutes for UPS models with different time units
|
||||||
let processedRuntime = batteryRuntime;
|
let processedRuntime = batteryRuntime;
|
||||||
|
|
||||||
if (config.upsModel === 'cyberpower' && batteryRuntime > 0) {
|
if (config.upsModel === 'cyberpower' && batteryRuntime > 0) {
|
||||||
// TimeTicks is in 1/100 seconds, convert to minutes
|
// CyberPower: TimeTicks is in 1/100 seconds, convert to minutes
|
||||||
processedRuntime = Math.floor(batteryRuntime / 6000); // 6000 ticks = 1 minute
|
processedRuntime = Math.floor(batteryRuntime / 6000); // 6000 ticks = 1 minute
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.log(`Converting CyberPower runtime from ${batteryRuntime} ticks to ${processedRuntime} minutes`);
|
console.log(`Converting CyberPower runtime from ${batteryRuntime} ticks to ${processedRuntime} minutes`);
|
||||||
}
|
}
|
||||||
|
} else if (config.upsModel === 'eaton' && batteryRuntime > 0) {
|
||||||
|
// Eaton: Runtime is in seconds, convert to minutes
|
||||||
|
processedRuntime = Math.floor(batteryRuntime / 60);
|
||||||
|
if (this.debug) {
|
||||||
|
console.log(`Converting Eaton runtime from ${batteryRuntime} seconds to ${processedRuntime} minutes`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
@ -507,25 +518,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
|
|
||||||
const { stdout } = await execAsync('shutdown -h +1 "UPS battery critical, shutting down in 1 minute"');
|
|
||||||
console.log('Shutdown initiated:', stdout);
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -25,9 +25,9 @@ export class UpsOidSets {
|
|||||||
|
|
||||||
// Eaton OIDs
|
// Eaton OIDs
|
||||||
eaton: {
|
eaton: {
|
||||||
POWER_STATUS: '1.3.6.1.4.1.534.1.1.2.0', // Power status
|
POWER_STATUS: '1.3.6.1.4.1.534.1.4.4.0', // xupsOutputSource (3=normal/mains, 5=battery)
|
||||||
BATTERY_CAPACITY: '1.3.6.1.4.1.534.1.2.4.0', // Battery capacity in percentage
|
BATTERY_CAPACITY: '1.3.6.1.4.1.534.1.2.4.0', // xupsBatCapacity (percentage)
|
||||||
BATTERY_RUNTIME: '1.3.6.1.4.1.534.1.2.1.0', // Remaining runtime in minutes
|
BATTERY_RUNTIME: '1.3.6.1.4.1.534.1.2.1.0', // xupsBatTimeRemaining (seconds)
|
||||||
},
|
},
|
||||||
|
|
||||||
// TrippLite OIDs
|
// TrippLite OIDs
|
||||||
|
65
uninstall.sh
65
uninstall.sh
@ -5,13 +5,22 @@
|
|||||||
|
|
||||||
# Check if running as root
|
# Check if running as root
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
echo "Please run as root (sudo ./uninstall.sh)"
|
echo "Please run as root (sudo nupst uninstall or sudo ./uninstall.sh)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# This script can be called directly or through the CLI
|
||||||
|
# When called through the CLI, environment variables are set
|
||||||
|
# REMOVE_CONFIG=yes|no - whether to remove configuration files
|
||||||
|
# REMOVE_REPO=yes|no - whether to remove the repository
|
||||||
|
|
||||||
|
# If not set through CLI, use defaults
|
||||||
|
REMOVE_CONFIG=${REMOVE_CONFIG:-"no"}
|
||||||
|
REMOVE_REPO=${REMOVE_REPO:-"no"}
|
||||||
|
|
||||||
echo "NUPST Uninstaller"
|
echo "NUPST Uninstaller"
|
||||||
echo "================="
|
echo "================="
|
||||||
echo "This script will completely remove NUPST from your system."
|
echo "This will completely remove NUPST from your system."
|
||||||
|
|
||||||
# Find the directory where this script is located
|
# Find the directory where this script is located
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
@ -37,20 +46,52 @@ if [ -L "/usr/local/bin/nupst" ]; then
|
|||||||
rm -f /usr/local/bin/nupst
|
rm -f /usr/local/bin/nupst
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 3: Ask about removing configuration
|
# Step 3: Remove configuration if requested
|
||||||
read -p "Do you want to remove the NUPST configuration files? (y/N) " -n 1 -r
|
if [ "$REMOVE_CONFIG" = "yes" ]; then
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Removing configuration files..."
|
echo "Removing configuration files..."
|
||||||
rm -rf /etc/nupst
|
rm -rf /etc/nupst
|
||||||
|
else
|
||||||
|
# If not called through CLI, ask user
|
||||||
|
if [ -z "$NUPST_CLI_CALL" ]; then
|
||||||
|
read -p "Do you want to remove the NUPST configuration files? (y/N) " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Removing configuration files..."
|
||||||
|
rm -rf /etc/nupst
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 4: Check if this was a git installation
|
# Step 4: Remove repository if requested
|
||||||
if [ -d "$SCRIPT_DIR/.git" ]; then
|
if [ "$REMOVE_REPO" = "yes" ]; then
|
||||||
echo
|
if [ -d "$SCRIPT_DIR/.git" ]; then
|
||||||
echo "This appears to be a git installation. The local repository will remain intact."
|
echo "Removing NUPST repository directory..."
|
||||||
echo "If you wish to completely remove it, you can delete the directory:"
|
|
||||||
echo " rm -rf $SCRIPT_DIR"
|
# Get parent directory to remove it after the script exits
|
||||||
|
PARENT_DIR=$(dirname "$SCRIPT_DIR")
|
||||||
|
REPO_NAME=$(basename "$SCRIPT_DIR")
|
||||||
|
|
||||||
|
# Create a temporary cleanup script
|
||||||
|
CLEANUP_SCRIPT=$(mktemp)
|
||||||
|
echo "#!/bin/bash" > "$CLEANUP_SCRIPT"
|
||||||
|
echo "sleep 1" >> "$CLEANUP_SCRIPT"
|
||||||
|
echo "rm -rf \"$SCRIPT_DIR\"" >> "$CLEANUP_SCRIPT"
|
||||||
|
echo "echo \"NUPST repository has been removed.\"" >> "$CLEANUP_SCRIPT"
|
||||||
|
chmod +x "$CLEANUP_SCRIPT"
|
||||||
|
|
||||||
|
# Run the cleanup script in the background
|
||||||
|
nohup "$CLEANUP_SCRIPT" > /dev/null 2>&1 &
|
||||||
|
|
||||||
|
echo "NUPST repository will be removed after uninstaller exits."
|
||||||
|
else
|
||||||
|
echo "No git repository found."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# If not requested, just display info
|
||||||
|
if [ -d "$SCRIPT_DIR/.git" ]; then
|
||||||
|
echo
|
||||||
|
echo "NUPST repository at $SCRIPT_DIR will remain intact."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for npm global installation
|
# Check for npm global installation
|
||||||
|
Reference in New Issue
Block a user