Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
f750299780 | |||
ca1039408d | |||
df3e0b9424 | |||
c8e5960abd | |||
7304a62357 | |||
a5a88e53ba | |||
73bc271c59 | |||
1e98181e71 | |||
eb5a8185ae | |||
ef3d3f3fa3 | |||
34e6e850ad | |||
992a776fd2 | |||
3e15a2d52f | |||
d1a3576d31 | |||
1ca05e879b |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "4.3.0",
|
"version": "5.0.3",
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run --allow-all mod.ts",
|
"dev": "deno run --allow-all mod.ts",
|
||||||
|
91
install.sh
91
install.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# NUPST Installer Script (v4.0+)
|
# NUPST Installer Script (v5.0+)
|
||||||
# Downloads and installs pre-compiled NUPST binary from Gitea releases
|
# Downloads and installs pre-compiled NUPST binary from Gitea releases
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# 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 | sudo bash
|
||||||
#
|
#
|
||||||
# With version specification:
|
# With version specification:
|
||||||
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0
|
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v5.0.0
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# -h, --help Show this help message
|
# -h, --help Show this help message
|
||||||
@@ -48,14 +48,14 @@ while [[ $# -gt 0 ]]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ $SHOW_HELP -eq 1 ]; then
|
if [ $SHOW_HELP -eq 1 ]; then
|
||||||
echo "NUPST Installer Script (v4.0+)"
|
echo "NUPST Installer Script (v5.0+)"
|
||||||
echo "Downloads and installs pre-compiled NUPST binary"
|
echo "Downloads and installs pre-compiled NUPST binary"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage: $0 [options]"
|
echo "Usage: $0 [options]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " -h, --help Show this help message"
|
echo " -h, --help Show this help message"
|
||||||
echo " --version VERSION Install specific version (e.g., v4.0.0)"
|
echo " --version VERSION Install specific version (e.g., v5.0.0)"
|
||||||
echo " --install-dir DIR Installation directory (default: /opt/nupst)"
|
echo " --install-dir DIR Installation directory (default: /opt/nupst)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
@@ -63,7 +63,7 @@ if [ $SHOW_HELP -eq 1 ]; then
|
|||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash"
|
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash"
|
||||||
echo ""
|
echo ""
|
||||||
echo " # Install specific version"
|
echo " # Install specific version"
|
||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0"
|
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v5.0.0"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ get_latest_version() {
|
|||||||
|
|
||||||
# Main installation process
|
# Main installation process
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo " NUPST Installation Script (v4.0+)"
|
echo " NUPST Installation Script (v5.0+)"
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -169,51 +169,26 @@ DOWNLOAD_URL="${GITEA_BASE_URL}/${GITEA_REPO}/releases/download/${VERSION}/${BIN
|
|||||||
echo "Download URL: $DOWNLOAD_URL"
|
echo "Download URL: $DOWNLOAD_URL"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check if installation directory exists
|
# Check if service is running and stop it
|
||||||
SERVICE_WAS_RUNNING=0
|
SERVICE_WAS_RUNNING=0
|
||||||
OLD_NODE_INSTALL=0
|
if systemctl is-enabled --quiet nupst 2>/dev/null || systemctl is-active --quiet nupst 2>/dev/null; then
|
||||||
|
SERVICE_WAS_RUNNING=1
|
||||||
if [ -d "$INSTALL_DIR" ]; then
|
if systemctl is-active --quiet nupst 2>/dev/null; then
|
||||||
# Check if this is an old Node.js-based installation
|
echo "Stopping NUPST service..."
|
||||||
if [ -f "$INSTALL_DIR/package.json" ] || [ -d "$INSTALL_DIR/node_modules" ]; then
|
systemctl stop nupst
|
||||||
OLD_NODE_INSTALL=1
|
|
||||||
echo "Detected old Node.js-based NUPST installation (v3.x or earlier)"
|
|
||||||
echo "This installer will migrate to the new Deno-based binary version (v4.0+)"
|
|
||||||
echo ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Updating existing installation at $INSTALL_DIR..."
|
|
||||||
|
|
||||||
# Check if service exists (enabled or running) and stop it if active
|
|
||||||
if systemctl is-enabled --quiet nupst 2>/dev/null || systemctl is-active --quiet nupst 2>/dev/null; then
|
|
||||||
SERVICE_WAS_RUNNING=1
|
|
||||||
if systemctl is-active --quiet nupst 2>/dev/null; then
|
|
||||||
echo "Stopping NUPST service..."
|
|
||||||
systemctl stop nupst
|
|
||||||
else
|
|
||||||
echo "Service is installed but not currently running (will be updated)..."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up old Node.js installation files
|
|
||||||
if [ $OLD_NODE_INSTALL -eq 1 ]; then
|
|
||||||
echo "Cleaning up old Node.js installation files..."
|
|
||||||
rm -rf "$INSTALL_DIR/node_modules" 2>/dev/null || true
|
|
||||||
rm -rf "$INSTALL_DIR/vendor" 2>/dev/null || true
|
|
||||||
rm -rf "$INSTALL_DIR/dist_ts" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/package.json" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/package-lock.json" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/pnpm-lock.yaml" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/tsconfig.json" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/setup.sh" 2>/dev/null || true
|
|
||||||
rm -rf "$INSTALL_DIR/bin" 2>/dev/null || true
|
|
||||||
echo "Old installation files removed."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Creating installation directory: $INSTALL_DIR"
|
|
||||||
mkdir -p "$INSTALL_DIR"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Clean installation directory - ensure only binary exists
|
||||||
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
|
echo "Cleaning installation directory: $INSTALL_DIR"
|
||||||
|
rm -rf "$INSTALL_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create fresh installation directory
|
||||||
|
echo "Creating installation directory: $INSTALL_DIR"
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
# Download binary
|
# Download binary
|
||||||
echo "Downloading NUPST binary..."
|
echo "Downloading NUPST binary..."
|
||||||
TEMP_FILE="$INSTALL_DIR/nupst.download"
|
TEMP_FILE="$INSTALL_DIR/nupst.download"
|
||||||
@@ -260,14 +235,6 @@ echo "Symlink created: $BIN_DIR/nupst -> $BINARY_PATH"
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Update systemd service file if migrating from v3
|
|
||||||
if [ $SERVICE_WAS_RUNNING -eq 1 ] && [ $OLD_NODE_INSTALL -eq 1 ]; then
|
|
||||||
echo "Updating systemd service file for v4..."
|
|
||||||
$BINARY_PATH service enable > /dev/null 2>&1
|
|
||||||
echo "Service file updated."
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Restart service if it was running before update
|
# Restart service if it was running before update
|
||||||
if [ $SERVICE_WAS_RUNNING -eq 1 ]; then
|
if [ $SERVICE_WAS_RUNNING -eq 1 ]; then
|
||||||
echo "Restarting NUPST service..."
|
echo "Restarting NUPST service..."
|
||||||
@@ -280,20 +247,6 @@ echo "================================================"
|
|||||||
echo " NUPST Installation Complete!"
|
echo " NUPST Installation Complete!"
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ $OLD_NODE_INSTALL -eq 1 ]; then
|
|
||||||
echo "Migration from v3.x to v4.0 successful!"
|
|
||||||
echo ""
|
|
||||||
echo "What changed:"
|
|
||||||
echo " • Node.js runtime removed (now a self-contained binary)"
|
|
||||||
echo " • Faster startup and lower memory usage"
|
|
||||||
echo " • CLI commands now use subcommand structure"
|
|
||||||
echo " (old commands still work with deprecation warnings)"
|
|
||||||
echo ""
|
|
||||||
echo "See readme for migration details: https://code.foss.global/serve.zone/nupst#migration-from-v3x"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Installation details:"
|
echo "Installation details:"
|
||||||
echo " Binary location: $BINARY_PATH"
|
echo " Binary location: $BINARY_PATH"
|
||||||
echo " Symlink location: $BIN_DIR/nupst"
|
echo " Symlink location: $BIN_DIR/nupst"
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
|
import process from 'node:process';
|
||||||
import { exec } from 'node:child_process';
|
import { exec } from 'node:child_process';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import { Action, type IActionConfig, type IActionContext } from './base-action.ts';
|
import { Action, type IActionConfig, type IActionContext } from './base-action.ts';
|
||||||
|
103
ts/cli.ts
103
ts/cli.ts
@@ -127,8 +127,7 @@ export class NupstCli {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove':
|
case 'remove':
|
||||||
case 'rm': // Alias
|
case 'rm': {
|
||||||
case 'delete': { // Backward compatibility
|
|
||||||
const upsIdToRemove = subcommandArgs[0];
|
const upsIdToRemove = subcommandArgs[0];
|
||||||
if (!upsIdToRemove) {
|
if (!upsIdToRemove) {
|
||||||
logger.error('UPS ID is required for remove command');
|
logger.error('UPS ID is required for remove command');
|
||||||
@@ -172,8 +171,7 @@ export class NupstCli {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove':
|
case 'remove':
|
||||||
case 'rm': // Alias
|
case 'rm': {
|
||||||
case 'delete': { // Backward compatibility
|
|
||||||
const groupIdToRemove = subcommandArgs[0];
|
const groupIdToRemove = subcommandArgs[0];
|
||||||
if (!groupIdToRemove) {
|
if (!groupIdToRemove) {
|
||||||
logger.error('Group ID is required for remove command');
|
logger.error('Group ID is required for remove command');
|
||||||
@@ -206,8 +204,7 @@ export class NupstCli {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove':
|
case 'remove':
|
||||||
case 'rm': // Alias
|
case 'rm': {
|
||||||
case 'delete': { // Backward compatibility
|
|
||||||
const upsId = subcommandArgs[0];
|
const upsId = subcommandArgs[0];
|
||||||
const actionIndex = subcommandArgs[1];
|
const actionIndex = subcommandArgs[1];
|
||||||
await actionHandler.remove(upsId, actionIndex);
|
await actionHandler.remove(upsId, actionIndex);
|
||||||
@@ -242,72 +239,8 @@ export class NupstCli {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle top-level commands and backward compatibility
|
// Handle top-level commands
|
||||||
switch (command) {
|
switch (command) {
|
||||||
// Backward compatibility - old UPS commands
|
|
||||||
case 'add':
|
|
||||||
logger.log("Note: 'nupst add' is deprecated. Use 'nupst ups add' instead.");
|
|
||||||
await upsHandler.add();
|
|
||||||
break;
|
|
||||||
case 'edit':
|
|
||||||
logger.log("Note: 'nupst edit' is deprecated. Use 'nupst ups edit' instead.");
|
|
||||||
await upsHandler.edit(commandArgs[0]);
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
logger.log("Note: 'nupst delete' is deprecated. Use 'nupst ups remove' instead.");
|
|
||||||
if (!commandArgs[0]) {
|
|
||||||
logger.error('UPS ID is required for delete command');
|
|
||||||
this.showHelp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await upsHandler.remove(commandArgs[0]);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
logger.log("Note: 'nupst list' is deprecated. Use 'nupst ups list' instead.");
|
|
||||||
await upsHandler.list();
|
|
||||||
break;
|
|
||||||
case 'test':
|
|
||||||
logger.log("Note: 'nupst test' is deprecated. Use 'nupst ups test' instead.");
|
|
||||||
await upsHandler.test(debugMode);
|
|
||||||
break;
|
|
||||||
case 'setup':
|
|
||||||
logger.log("Note: 'nupst setup' is deprecated. Use 'nupst ups edit' instead.");
|
|
||||||
await upsHandler.edit(undefined);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Backward compatibility - old service commands
|
|
||||||
case 'enable':
|
|
||||||
logger.log("Note: 'nupst enable' is deprecated. Use 'nupst service enable' instead.");
|
|
||||||
await serviceHandler.enable();
|
|
||||||
break;
|
|
||||||
case 'disable':
|
|
||||||
logger.log("Note: 'nupst disable' is deprecated. Use 'nupst service disable' instead.");
|
|
||||||
await serviceHandler.disable();
|
|
||||||
break;
|
|
||||||
case 'start':
|
|
||||||
logger.log("Note: 'nupst start' is deprecated. Use 'nupst service start' instead.");
|
|
||||||
await serviceHandler.start();
|
|
||||||
break;
|
|
||||||
case 'stop':
|
|
||||||
logger.log("Note: 'nupst stop' is deprecated. Use 'nupst service stop' instead.");
|
|
||||||
await serviceHandler.stop();
|
|
||||||
break;
|
|
||||||
case 'status':
|
|
||||||
logger.log("Note: 'nupst status' is deprecated. Use 'nupst service status' instead.");
|
|
||||||
await serviceHandler.status();
|
|
||||||
break;
|
|
||||||
case 'logs':
|
|
||||||
logger.log("Note: 'nupst logs' is deprecated. Use 'nupst service logs' instead.");
|
|
||||||
await serviceHandler.logs();
|
|
||||||
break;
|
|
||||||
case 'daemon-start':
|
|
||||||
logger.log(
|
|
||||||
"Note: 'nupst daemon-start' is deprecated. Use 'nupst service start-daemon' instead.",
|
|
||||||
);
|
|
||||||
await serviceHandler.daemonStart(debugMode);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Top-level commands (no changes)
|
|
||||||
case 'update':
|
case 'update':
|
||||||
await serviceHandler.update();
|
await serviceHandler.update();
|
||||||
break;
|
break;
|
||||||
@@ -571,9 +504,9 @@ export class NupstCli {
|
|||||||
|
|
||||||
// Action subcommands
|
// Action subcommands
|
||||||
logger.log(theme.info('Action Subcommands:'));
|
logger.log(theme.info('Action Subcommands:'));
|
||||||
this.printCommand('nupst action add <ups-id>', 'Add a new action to a UPS');
|
this.printCommand('nupst action add <target-id>', 'Add a new action to a UPS or group');
|
||||||
this.printCommand('nupst action remove <ups-id> <index>', 'Remove an action by index');
|
this.printCommand('nupst action remove <target-id> <index>', 'Remove an action by index');
|
||||||
this.printCommand('nupst action list [ups-id]', 'List all actions (optionally for specific UPS)');
|
this.printCommand('nupst action list [target-id]', 'List all actions (optionally for specific target)');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
@@ -589,11 +522,6 @@ export class NupstCli {
|
|||||||
logger.dim(' nupst group list # Show all configured groups');
|
logger.dim(' nupst group list # Show all configured groups');
|
||||||
logger.dim(' nupst config # Display current configuration');
|
logger.dim(' nupst config # Display current configuration');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
// Note about deprecated commands
|
|
||||||
logger.warn('Note: Old command format (e.g., \'nupst add\') still works but is deprecated.');
|
|
||||||
logger.dim(' Use the new format (e.g., \'nupst ups add\') going forward.');
|
|
||||||
console.log('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -691,18 +619,19 @@ Usage:
|
|||||||
nupst action <subcommand> [arguments]
|
nupst action <subcommand> [arguments]
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
add <ups-id> - Add a new action to a UPS interactively
|
add <ups-id|group-id> - Add a new action to a UPS or group interactively
|
||||||
remove <ups-id> <index> - Remove an action by index (alias: rm, delete)
|
remove <ups-id|group-id> <index> - Remove an action by index (alias: rm)
|
||||||
list [ups-id] - List all actions (optionally for specific UPS) (alias: ls)
|
list [ups-id|group-id] - List all actions (optionally for specific target) (alias: ls)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--debug, -d - Enable debug mode for detailed logging
|
--debug, -d - Enable debug mode for detailed logging
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
nupst action list - List actions for all UPS devices
|
nupst action list - List actions for all UPS devices and groups
|
||||||
nupst action list default - List actions for UPS with ID 'default'
|
nupst action list default - List actions for UPS or group with ID 'default'
|
||||||
nupst action add default - Add a new action to UPS 'default'
|
nupst action add default - Add a new action to UPS or group 'default'
|
||||||
nupst action remove default 0 - Remove action at index 0 from UPS 'default'
|
nupst action remove default 0 - Remove action at index 0 from UPS or group 'default'
|
||||||
|
nupst action add dc-rack-1 - Add a new action to group 'dc-rack-1'
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import { Nupst } from '../nupst.ts';
|
|||||||
import { logger, type ITableColumn } from '../logger.ts';
|
import { logger, type ITableColumn } from '../logger.ts';
|
||||||
import { theme, symbols } from '../colors.ts';
|
import { theme, symbols } from '../colors.ts';
|
||||||
import type { IActionConfig } from '../actions/base-action.ts';
|
import type { IActionConfig } from '../actions/base-action.ts';
|
||||||
import type { IUpsConfig } from '../daemon.ts';
|
import type { IUpsConfig, IGroupConfig } from '../daemon.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for handling action-related CLI commands
|
* Class for handling action-related CLI commands
|
||||||
@@ -21,30 +21,42 @@ export class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new action to a UPS
|
* Add a new action to a UPS or group
|
||||||
*/
|
*/
|
||||||
public async add(upsId?: string): Promise<void> {
|
public async add(targetId?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (!upsId) {
|
if (!targetId) {
|
||||||
logger.error('UPS ID is required');
|
logger.error('Target ID is required');
|
||||||
logger.log(` ${theme.dim('Usage:')} ${theme.command('nupst action add <ups-id>')}`);
|
logger.log(
|
||||||
|
` ${theme.dim('Usage:')} ${theme.command('nupst action add <ups-id|group-id>')}`,
|
||||||
|
);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.log(` ${theme.dim('List UPS devices:')} ${theme.command('nupst ups list')}`);
|
logger.log(` ${theme.dim('List UPS devices:')} ${theme.command('nupst ups list')}`);
|
||||||
|
logger.log(` ${theme.dim('List groups:')} ${theme.command('nupst group list')}`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await this.nupst.getDaemon().loadConfig();
|
const config = await this.nupst.getDaemon().loadConfig();
|
||||||
const ups = config.upsDevices.find((u) => u.id === upsId);
|
|
||||||
|
|
||||||
if (!ups) {
|
// Check if it's a UPS
|
||||||
logger.error(`UPS with ID '${upsId}' not found`);
|
const ups = config.upsDevices.find((u) => u.id === targetId);
|
||||||
|
// Check if it's a group
|
||||||
|
const group = config.groups?.find((g) => g.id === targetId);
|
||||||
|
|
||||||
|
if (!ups && !group) {
|
||||||
|
logger.error(`UPS or Group with ID '${targetId}' not found`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.log(` ${theme.dim('List available UPS devices:')} ${theme.command('nupst ups list')}`);
|
logger.log(` ${theme.dim('List available UPS devices:')} ${theme.command('nupst ups list')}`);
|
||||||
|
logger.log(` ${theme.dim('List available groups:')} ${theme.command('nupst group list')}`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const target = ups || group;
|
||||||
|
const targetType = ups ? 'UPS' : 'Group';
|
||||||
|
const targetName = ups ? ups.name : group!.name;
|
||||||
|
|
||||||
const readline = await import('node:readline');
|
const readline = await import('node:readline');
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
@@ -61,7 +73,7 @@ export class ActionHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.info(`Add Action to ${theme.highlight(ups.name)}`);
|
logger.info(`Add Action to ${targetType} ${theme.highlight(targetName)}`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
|
|
||||||
// Action type (currently only shutdown is supported)
|
// Action type (currently only shutdown is supported)
|
||||||
@@ -130,37 +142,38 @@ export class ActionHandler {
|
|||||||
shutdownDelay,
|
shutdownDelay,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add to UPS
|
// Add to target (UPS or group)
|
||||||
if (!ups.actions) {
|
if (!target!.actions) {
|
||||||
ups.actions = [];
|
target!.actions = [];
|
||||||
}
|
}
|
||||||
ups.actions.push(newAction);
|
target!.actions.push(newAction);
|
||||||
|
|
||||||
await this.nupst.getDaemon().saveConfig(config);
|
await this.nupst.getDaemon().saveConfig(config);
|
||||||
|
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.success(`Action added to ${ups.name}`);
|
logger.success(`Action added to ${targetType} ${targetName}`);
|
||||||
logger.log('');
|
logger.log(` ${theme.dim('Changes saved and will be applied automatically')}`);
|
||||||
logger.log(` ${theme.dim('Restart service to apply changes:')} ${theme.command('nupst service restart')}`);
|
|
||||||
logger.log('');
|
logger.log('');
|
||||||
} finally {
|
} finally {
|
||||||
rl.close();
|
rl.close();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to add action: ${error instanceof Error ? error.message : String(error)}`);
|
logger.error(
|
||||||
|
`Failed to add action: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an action from a UPS
|
* Remove an action from a UPS or group
|
||||||
*/
|
*/
|
||||||
public async remove(upsId?: string, actionIndexStr?: string): Promise<void> {
|
public async remove(targetId?: string, actionIndexStr?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (!upsId || !actionIndexStr) {
|
if (!targetId || !actionIndexStr) {
|
||||||
logger.error('UPS ID and action index are required');
|
logger.error('Target ID and action index are required');
|
||||||
logger.log(
|
logger.log(
|
||||||
` ${theme.dim('Usage:')} ${theme.command('nupst action remove <ups-id> <action-index>')}`,
|
` ${theme.dim('Usage:')} ${theme.command('nupst action remove <ups-id|group-id> <action-index>')}`,
|
||||||
);
|
);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.log(` ${theme.dim('List actions:')} ${theme.command('nupst action list')}`);
|
logger.log(` ${theme.dim('List actions:')} ${theme.command('nupst action list')}`);
|
||||||
@@ -175,47 +188,57 @@ export class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config = await this.nupst.getDaemon().loadConfig();
|
const config = await this.nupst.getDaemon().loadConfig();
|
||||||
const ups = config.upsDevices.find((u) => u.id === upsId);
|
|
||||||
|
|
||||||
if (!ups) {
|
// Check if it's a UPS
|
||||||
logger.error(`UPS with ID '${upsId}' not found`);
|
const ups = config.upsDevices.find((u) => u.id === targetId);
|
||||||
|
// Check if it's a group
|
||||||
|
const group = config.groups?.find((g) => g.id === targetId);
|
||||||
|
|
||||||
|
if (!ups && !group) {
|
||||||
|
logger.error(`UPS or Group with ID '${targetId}' not found`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.log(` ${theme.dim('List available UPS devices:')} ${theme.command('nupst ups list')}`);
|
logger.log(` ${theme.dim('List available UPS devices:')} ${theme.command('nupst ups list')}`);
|
||||||
|
logger.log(` ${theme.dim('List available groups:')} ${theme.command('nupst group list')}`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ups.actions || ups.actions.length === 0) {
|
const target = ups || group;
|
||||||
logger.error(`No actions configured for UPS '${ups.name}'`);
|
const targetType = ups ? 'UPS' : 'Group';
|
||||||
|
const targetName = ups ? ups.name : group!.name;
|
||||||
|
|
||||||
|
if (!target!.actions || target!.actions.length === 0) {
|
||||||
|
logger.error(`No actions configured for ${targetType} '${targetName}'`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionIndex >= ups.actions.length) {
|
if (actionIndex >= target!.actions.length) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Invalid action index. UPS '${ups.name}' has ${ups.actions.length} action(s) (index 0-${ups.actions.length - 1})`,
|
`Invalid action index. ${targetType} '${targetName}' has ${target!.actions.length} action(s) (index 0-${target!.actions.length - 1})`,
|
||||||
);
|
);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.log(` ${theme.dim('List actions:')} ${theme.command(`nupst action list ${upsId}`)}`);
|
logger.log(
|
||||||
|
` ${theme.dim('List actions:')} ${theme.command(`nupst action list ${targetId}`)}`,
|
||||||
|
);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const removedAction = ups.actions[actionIndex];
|
const removedAction = target!.actions[actionIndex];
|
||||||
ups.actions.splice(actionIndex, 1);
|
target!.actions.splice(actionIndex, 1);
|
||||||
|
|
||||||
await this.nupst.getDaemon().saveConfig(config);
|
await this.nupst.getDaemon().saveConfig(config);
|
||||||
|
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.success(`Action removed from ${ups.name}`);
|
logger.success(`Action removed from ${targetType} ${targetName}`);
|
||||||
logger.log(` ${theme.dim('Type:')} ${removedAction.type}`);
|
logger.log(` ${theme.dim('Type:')} ${removedAction.type}`);
|
||||||
if (removedAction.thresholds) {
|
if (removedAction.thresholds) {
|
||||||
logger.log(
|
logger.log(
|
||||||
` ${theme.dim('Thresholds:')} Battery: ${removedAction.thresholds.battery}%, Runtime: ${removedAction.thresholds.runtime}min`,
|
` ${theme.dim('Thresholds:')} Battery: ${removedAction.thresholds.battery}%, Runtime: ${removedAction.thresholds.runtime}min`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
logger.log('');
|
logger.log(` ${theme.dim('Changes saved and will be applied automatically')}`);
|
||||||
logger.log(` ${theme.dim('Restart service to apply changes:')} ${theme.command('nupst service restart')}`);
|
|
||||||
logger.log('');
|
logger.log('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -226,43 +249,61 @@ export class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all actions for a specific UPS or all UPS devices
|
* List all actions for a specific UPS/group or all devices
|
||||||
*/
|
*/
|
||||||
public async list(upsId?: string): Promise<void> {
|
public async list(targetId?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const config = await this.nupst.getDaemon().loadConfig();
|
const config = await this.nupst.getDaemon().loadConfig();
|
||||||
|
|
||||||
if (upsId) {
|
if (targetId) {
|
||||||
// List actions for specific UPS
|
// List actions for specific UPS or group
|
||||||
const ups = config.upsDevices.find((u) => u.id === upsId);
|
const ups = config.upsDevices.find((u) => u.id === targetId);
|
||||||
|
const group = config.groups?.find((g) => g.id === targetId);
|
||||||
|
|
||||||
if (!ups) {
|
if (!ups && !group) {
|
||||||
logger.error(`UPS with ID '${upsId}' not found`);
|
logger.error(`UPS or Group with ID '${targetId}' not found`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.log(` ${theme.dim('List available UPS devices:')} ${theme.command('nupst ups list')}`);
|
logger.log(` ${theme.dim('List available UPS devices:')} ${theme.command('nupst ups list')}`);
|
||||||
|
logger.log(` ${theme.dim('List available groups:')} ${theme.command('nupst group list')}`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.displayUpsActions(ups);
|
if (ups) {
|
||||||
|
this.displayTargetActions(ups, 'UPS');
|
||||||
|
} else {
|
||||||
|
this.displayTargetActions(group!, 'Group');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// List actions for all UPS devices
|
// List actions for all UPS devices and groups
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.info('Actions for All UPS Devices');
|
logger.info('Actions for All UPS Devices and Groups');
|
||||||
logger.log('');
|
logger.log('');
|
||||||
|
|
||||||
let hasAnyActions = false;
|
let hasAnyActions = false;
|
||||||
|
|
||||||
|
// Display UPS actions
|
||||||
for (const ups of config.upsDevices) {
|
for (const ups of config.upsDevices) {
|
||||||
if (ups.actions && ups.actions.length > 0) {
|
if (ups.actions && ups.actions.length > 0) {
|
||||||
hasAnyActions = true;
|
hasAnyActions = true;
|
||||||
this.displayUpsActions(ups);
|
this.displayTargetActions(ups, 'UPS');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display Group actions
|
||||||
|
for (const group of config.groups || []) {
|
||||||
|
if (group.actions && group.actions.length > 0) {
|
||||||
|
hasAnyActions = true;
|
||||||
|
this.displayTargetActions(group, 'Group');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasAnyActions) {
|
if (!hasAnyActions) {
|
||||||
logger.log(` ${theme.dim('No actions configured')}`);
|
logger.log(` ${theme.dim('No actions configured')}`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
logger.log(` ${theme.dim('Add an action:')} ${theme.command('nupst action add <ups-id>')}`);
|
logger.log(
|
||||||
|
` ${theme.dim('Add an action:')} ${theme.command('nupst action add <ups-id|group-id>')}`,
|
||||||
|
);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,13 +316,18 @@ export class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display actions for a single UPS
|
* Display actions for a single UPS or Group
|
||||||
*/
|
*/
|
||||||
private displayUpsActions(ups: IUpsConfig): void {
|
private displayTargetActions(
|
||||||
logger.log(`${symbols.info} ${theme.highlight(ups.name)} ${theme.dim(`(${ups.id})`)}`);
|
target: IUpsConfig | IGroupConfig,
|
||||||
|
targetType: 'UPS' | 'Group',
|
||||||
|
): void {
|
||||||
|
logger.log(
|
||||||
|
`${symbols.info} ${targetType} ${theme.highlight(target.name)} ${theme.dim(`(${target.id})`)}`,
|
||||||
|
);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
|
|
||||||
if (!ups.actions || ups.actions.length === 0) {
|
if (!target.actions || target.actions.length === 0) {
|
||||||
logger.log(` ${theme.dim('No actions configured')}`);
|
logger.log(` ${theme.dim('No actions configured')}`);
|
||||||
logger.log('');
|
logger.log('');
|
||||||
return;
|
return;
|
||||||
@@ -296,7 +342,7 @@ export class ActionHandler {
|
|||||||
{ header: 'Delay', key: 'delay', align: 'right' },
|
{ header: 'Delay', key: 'delay', align: 'right' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const rows = ups.actions.map((action, index) => ({
|
const rows = target.actions.map((action, index) => ({
|
||||||
index: theme.dim(index.toString()),
|
index: theme.dim(index.toString()),
|
||||||
type: theme.highlight(action.type),
|
type: theme.highlight(action.type),
|
||||||
battery: action.thresholds ? `${action.thresholds.battery}%` : theme.dim('N/A'),
|
battery: action.thresholds ? `${action.thresholds.battery}%` : theme.dim('N/A'),
|
||||||
|
@@ -277,6 +277,9 @@ WantedBy=multi-user.target
|
|||||||
for (const ups of config.upsDevices) {
|
for (const ups of config.upsDevices) {
|
||||||
await this.displaySingleUpsStatus(ups, snmp);
|
await this.displaySingleUpsStatus(ups, snmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display groups after UPS devices
|
||||||
|
this.displayGroupsStatus();
|
||||||
} else if (config.snmp) {
|
} else if (config.snmp) {
|
||||||
// Legacy single UPS configuration (v1/v2 format)
|
// Legacy single UPS configuration (v1/v2 format)
|
||||||
logger.info('UPS Devices (1):');
|
logger.info('UPS Devices (1):');
|
||||||
@@ -365,6 +368,27 @@ WantedBy=multi-user.target
|
|||||||
logger.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`);
|
logger.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display actions if any
|
||||||
|
if (ups.actions && ups.actions.length > 0) {
|
||||||
|
for (const action of ups.actions) {
|
||||||
|
let actionDesc = `${action.type}`;
|
||||||
|
if (action.thresholds) {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyThresholds'}: battery<${action.thresholds.battery}%, runtime<${action.thresholds.runtime}min`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
} else {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyPowerChanges'}`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
}
|
||||||
|
logger.log(` ${theme.dim('Action:')} ${theme.info(actionDesc)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.log('');
|
logger.log('');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -376,6 +400,69 @@ WantedBy=multi-user.target
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display status of all groups
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private displayGroupsStatus(): void {
|
||||||
|
const config = this.daemon.getConfig();
|
||||||
|
|
||||||
|
if (!config.groups || config.groups.length === 0) {
|
||||||
|
return; // No groups to display
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('');
|
||||||
|
logger.info(`Groups (${config.groups.length}):`);
|
||||||
|
|
||||||
|
for (const group of config.groups) {
|
||||||
|
// Display group name and mode
|
||||||
|
const modeColor = group.mode === 'redundant' ? theme.success : theme.warning;
|
||||||
|
logger.log(
|
||||||
|
` ${symbols.info} ${theme.highlight(group.name)} ${theme.dim(`(${modeColor(group.mode)})`)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Display description if present
|
||||||
|
if (group.description) {
|
||||||
|
logger.log(` ${theme.dim(group.description)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display UPS devices in this group
|
||||||
|
const upsInGroup = config.upsDevices.filter((ups) =>
|
||||||
|
ups.groups && ups.groups.includes(group.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (upsInGroup.length > 0) {
|
||||||
|
const upsNames = upsInGroup.map((ups) => ups.name).join(', ');
|
||||||
|
logger.log(` ${theme.dim(`UPS Devices (${upsInGroup.length}):`)} ${upsNames}`);
|
||||||
|
} else {
|
||||||
|
logger.log(` ${theme.dim('UPS Devices: None')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display actions if any
|
||||||
|
if (group.actions && group.actions.length > 0) {
|
||||||
|
for (const action of group.actions) {
|
||||||
|
let actionDesc = `${action.type}`;
|
||||||
|
if (action.thresholds) {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyThresholds'}: battery<${action.thresholds.battery}%, runtime<${action.thresholds.runtime}min`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
} else {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyPowerChanges'}`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
}
|
||||||
|
logger.log(` ${theme.dim('Action:')} ${theme.info(actionDesc)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable and uninstall the systemd service
|
* Disable and uninstall the systemd service
|
||||||
* @throws Error if disabling fails
|
* @throws Error if disabling fails
|
||||||
|
Reference in New Issue
Block a user