11import { Injectable , Logger } from '@nestjs/common' ;
2- import { exec } from 'child_process' ;
32import { readFile , writeFile } from 'fs/promises' ;
4- import { promisify } from 'util' ;
53
4+ import { execa , ExecaError } from 'execa' ;
65import { z } from 'zod' ;
76
87import { fileExistsSync } from '@app/core/utils/files/file-exists.js' ;
98import { UPSConfigInput } from '@app/unraid-api/graph/resolvers/ups/ups.inputs.js' ;
109
11- const execPromise = promisify ( exec ) ;
12-
1310const UPSSchema = z . object ( {
1411 MODEL : z . string ( ) . optional ( ) ,
1512 STATUS : z . string ( ) . optional ( ) ,
@@ -48,7 +45,10 @@ export class UPSService {
4845
4946 async getUPSData ( ) : Promise < UPSData > {
5047 try {
51- const { stdout } = await execPromise ( '/sbin/apcaccess 2>/dev/null' , { timeout : 10000 } ) ;
48+ const { stdout } = await execa ( '/sbin/apcaccess' , [ ] , {
49+ timeout : 10000 ,
50+ reject : false , // Handle errors manually
51+ } ) ;
5252 if ( ! stdout || stdout . trim ( ) . length === 0 ) {
5353 throw new Error ( 'No UPS data returned from apcaccess' ) ;
5454 }
@@ -71,7 +71,7 @@ export class UPSService {
7171
7272 // Stop the UPS service before making changes
7373 try {
74- await execPromise ( '/etc/rc.d/rc.apcupsd stop' , { timeout : 10000 } ) ;
74+ await execa ( '/etc/rc.d/rc.apcupsd' , [ ' stop'] , { timeout : 10000 } ) ;
7575 } catch ( error ) {
7676 this . logger . warn ( 'Failed to stop apcupsd service (may not be running):' , error ) ;
7777 }
@@ -122,21 +122,73 @@ export class UPSService {
122122 throw error ;
123123 }
124124
125+ // Validate KILLUPS and SERVICE values
126+ const validKillUpsValues = [ 'yes' , 'no' ] ;
127+ const validServiceValues = [ 'enable' , 'disable' ] ;
128+
129+ if ( config . KILLUPS && ! validKillUpsValues . includes ( config . KILLUPS ) ) {
130+ throw new Error ( `Invalid KILLUPS value: ${ config . KILLUPS } . Must be 'yes' or 'no'` ) ;
131+ }
132+
133+ if ( config . SERVICE && ! validServiceValues . includes ( config . SERVICE ) ) {
134+ throw new Error (
135+ `Invalid SERVICE value: ${ config . SERVICE } . Must be 'enable' or 'disable'`
136+ ) ;
137+ }
138+
125139 // Handle killpower configuration
126140 if ( config . KILLUPS === 'yes' && config . SERVICE === 'enable' ) {
127- await execPromise (
128- `! grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/sbin/poweroff:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:' /etc/rc.d/rc.6`
129- ) ;
141+ try {
142+ // First check if apccontrol is already in rc.6
143+ const { exitCode } = await execa ( 'grep' , [ '-q' , 'apccontrol' , '/etc/rc.d/rc.6' ] , {
144+ reject : false ,
145+ } ) ;
146+
147+ // If not found (exitCode !== 0), add it
148+ if ( exitCode !== 0 ) {
149+ await execa ( 'sed' , [
150+ '-i' ,
151+ '-e' ,
152+ 's:/sbin/poweroff:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:' ,
153+ '/etc/rc.d/rc.6' ,
154+ ] ) ;
155+ this . logger . debug ( 'Added killpower to rc.6' ) ;
156+ }
157+ } catch ( error ) {
158+ this . logger . error ( 'Failed to update rc.6 for killpower enable:' , error ) ;
159+ throw new Error (
160+ `Failed to enable killpower in /etc/rc.d/rc.6: ${ error instanceof Error ? error . message : String ( error ) } `
161+ ) ;
162+ }
130163 } else {
131- await execPromise (
132- `grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:/sbin/poweroff:' /etc/rc.d/rc.6`
133- ) ;
164+ try {
165+ // Check if apccontrol is in rc.6
166+ const { exitCode } = await execa ( 'grep' , [ '-q' , 'apccontrol' , '/etc/rc.d/rc.6' ] , {
167+ reject : false ,
168+ } ) ;
169+
170+ // If found (exitCode === 0), remove it
171+ if ( exitCode === 0 ) {
172+ await execa ( 'sed' , [
173+ '-i' ,
174+ '-e' ,
175+ 's:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:/sbin/poweroff:' ,
176+ '/etc/rc.d/rc.6' ,
177+ ] ) ;
178+ this . logger . debug ( 'Removed killpower from rc.6' ) ;
179+ }
180+ } catch ( error ) {
181+ this . logger . error ( 'Failed to update rc.6 for killpower disable:' , error ) ;
182+ throw new Error (
183+ `Failed to disable killpower in /etc/rc.d/rc.6: ${ error instanceof Error ? error . message : String ( error ) } `
184+ ) ;
185+ }
134186 }
135187
136188 // Start the service if enabled
137189 if ( config . SERVICE === 'enable' ) {
138190 try {
139- await execPromise ( '/etc/rc.d/rc.apcupsd start' , { timeout : 10000 } ) ;
191+ await execa ( '/etc/rc.d/rc.apcupsd' , [ ' start'] , { timeout : 10000 } ) ;
140192 this . logger . debug ( 'Successfully started apcupsd service' ) ;
141193 } catch ( error ) {
142194 this . logger . error ( 'Failed to start apcupsd service:' , error ) ;
0 commit comments