Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ PATHS_RCLONE_SOCKET=./dev/rclone-socket
PATHS_LOG_BASE=./dev/log # Where we store logs
PATHS_LOGS_FILE=./dev/log/graphql-api.log
PATHS_CONNECT_STATUS_FILE_PATH=./dev/connectStatus.json # Connect plugin status file
PATHS_CONNECT_STATUS=./dev/states/connectStatus.json # Connect status file for development
ENVIRONMENT="development"
NODE_ENV="development"
PORT="3001"
PLAYGROUND=true
INTROSPECTION=true
MOTHERSHIP_GRAPHQL_LINK="http://authenticator:3000/graphql"
MOTHERSHIP_BASE_URL="http://localhost:8787"
NODE_TLS_REJECT_UNAUTHORIZED=0
BYPASS_PERMISSION_CHECKS=false
BYPASS_CORS_CHECKS=true
Expand Down
4 changes: 2 additions & 2 deletions api/dev/configs/api.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": "4.11.0",
"version": "4.12.0",
"extraOrigins": [],
"sandbox": false,
"ssoSubIds": [],
"plugins": []
"plugins": ["unraid-api-plugin-connect"]
}
9 changes: 8 additions & 1 deletion api/dev/configs/connect.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,12 @@
"username": "zspearmint",
"avatar": "https://via.placeholder.com/200",
"regWizTime": "1611175408732_0951-1653-3509-FBA155FA23C0",
"dynamicRemoteAccessType": "DISABLED"
"dynamicRemoteAccessType": "DISABLED",
"version": "4.4.1",
"extraOrigins": "https://google.com,https://test.com",
"sandbox": "yes",
"accesstoken": "",
"idtoken": "",
"refreshtoken": "",
"ssoSubIds": ""
}
11 changes: 11 additions & 0 deletions api/dev/keys/7789353b-40f4-4f3b-a230-b1f22909abff.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"createdAt": "2025-07-19T22:29:38.790Z",
"description": "Internal API Key Used By Unraid Connect to access your server resources for the connect.myunraid.net dashboard",
"id": "7789353b-40f4-4f3b-a230-b1f22909abff",
"key": "e6e0212193fa1cb468194dd5a4e41196305bc3b5e38532c2f86935bbde317bd0",
"name": "ConnectInternal",
"permissions": [],
"roles": [
"CONNECT"
]
}
11 changes: 0 additions & 11 deletions api/dev/keys/b5b4aa3d-8e40-4c92-bc40-d50182071886.json

This file was deleted.

7 changes: 7 additions & 0 deletions api/dev/states/connectStatus.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"connectionStatus": "PRE_INIT",
"error": null,
"lastPing": null,
"allowedOrigins": "",
"timestamp": 1753974976746
}
211 changes: 211 additions & 0 deletions api/generated-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,136 @@ type Settings implements Node {
api: ApiConfig!
}

type UPSBattery {
"""
Battery charge level as a percentage (0-100). Unit: percent (%). Example: 100 means battery is fully charged
"""
chargeLevel: Int!

"""
Estimated runtime remaining on battery power. Unit: seconds. Example: 3600 means 1 hour of runtime remaining
"""
estimatedRuntime: Int!

"""
Battery health status. Possible values: 'Good', 'Replace', 'Unknown'. Indicates if the battery needs replacement
"""
health: String!
}

type UPSPower {
"""
Input voltage from the wall outlet/mains power. Unit: volts (V). Example: 120.5 for typical US household voltage
"""
inputVoltage: Float!

"""
Output voltage being delivered to connected devices. Unit: volts (V). Example: 120.5 - should match input voltage when on mains power
"""
outputVoltage: Float!

"""
Current load on the UPS as a percentage of its capacity. Unit: percent (%). Example: 25 means UPS is loaded at 25% of its maximum capacity
"""
loadPercentage: Int!
}

type UPSDevice {
"""
Unique identifier for the UPS device. Usually based on the model name or a generated ID
"""
id: ID!

"""Display name for the UPS device. Can be customized by the user"""
name: String!

"""UPS model name/number. Example: 'APC Back-UPS Pro 1500'"""
model: String!

"""
Current operational status of the UPS. Common values: 'Online', 'On Battery', 'Low Battery', 'Replace Battery', 'Overload', 'Offline'. 'Online' means running on mains power, 'On Battery' means running on battery backup
"""
status: String!

"""Battery-related information"""
battery: UPSBattery!

"""Power-related information"""
power: UPSPower!
}

type UPSConfiguration {
"""
UPS service state. Values: 'enable' or 'disable'. Controls whether the UPS monitoring service is running
"""
service: String

"""
Type of cable connecting the UPS to the server. Common values: 'usb', 'smart', 'ether', 'custom'. Determines communication protocol
"""
upsCable: String

"""
Custom cable configuration string. Only used when upsCable is set to 'custom'. Format depends on specific UPS model
"""
customUpsCable: String

"""
UPS communication type. Common values: 'usb', 'net', 'snmp', 'dumb', 'pcnet', 'modbus'. Defines how the server communicates with the UPS
"""
upsType: String

"""
Device path or network address for UPS connection. Examples: '/dev/ttyUSB0' for USB, '192.168.1.100:3551' for network. Depends on upsType setting
"""
device: String

"""
Override UPS capacity for runtime calculations. Unit: volt-amperes (VA). Example: 1500 for a 1500VA UPS. Leave unset to use UPS-reported capacity
"""
overrideUpsCapacity: Int

"""
Battery level threshold for shutdown. Unit: percent (%). Example: 10 means shutdown when battery reaches 10%. System will shutdown when battery drops to this level
"""
batteryLevel: Int

"""
Runtime threshold for shutdown. Unit: minutes. Example: 5 means shutdown when 5 minutes runtime remaining. System will shutdown when estimated runtime drops below this
"""
minutes: Int

"""
Timeout for UPS communications. Unit: seconds. Example: 0 means no timeout. Time to wait for UPS response before considering it offline
"""
timeout: Int

"""
Kill UPS power after shutdown. Values: 'yes' or 'no'. If 'yes', tells UPS to cut power after system shutdown. Useful for ensuring complete power cycle
"""
killUps: String

"""
Network Information Server (NIS) IP address. Default: '0.0.0.0' (listen on all interfaces). IP address for apcupsd network information server
"""
nisIp: String

"""
Network server mode. Values: 'on' or 'off'. Enable to allow network clients to monitor this UPS
"""
netServer: String

"""
UPS name for network monitoring. Used to identify this UPS on the network. Example: 'SERVER_UPS'
"""
upsName: String

"""
Override UPS model name. Used for display purposes. Leave unset to use UPS-reported model
"""
modelName: String
}

type VmDomain implements Node {
"""The unique identifier for the vm (uuid)"""
id: PrefixedID!
Expand Down Expand Up @@ -1668,6 +1798,9 @@ type Query {
rclone: RCloneBackupSettings!
settings: Settings!
isSSOEnabled: Boolean!
upsDevices: [UPSDevice!]!
upsDeviceById(id: String!): UPSDevice
upsConfiguration: UPSConfiguration!

"""List all installed plugins with their metadata"""
plugins: [Plugin!]!
Expand Down Expand Up @@ -1707,6 +1840,7 @@ type Mutation {
"""Initiates a flash drive backup using a configured remote."""
initiateFlashBackup(input: InitiateFlashBackupInput!): FlashBackupStatus!
updateSettings(input: JSON!): UpdateSettingsResponse!
configureUps(config: UPSConfigInput!): Boolean!

"""
Add one or more plugins to the API. Returns false if restart was triggered automatically, true if manual restart is required.
Expand Down Expand Up @@ -1748,6 +1882,82 @@ input InitiateFlashBackupInput {
options: JSON
}

input UPSConfigInput {
"""Enable or disable the UPS monitoring service"""
service: UPSServiceState

"""Type of cable connecting the UPS to the server"""
upsCable: UPSCableType

"""
Custom cable configuration (only used when upsCable is CUSTOM). Format depends on specific UPS model
"""
customUpsCable: String

"""UPS communication protocol"""
upsType: UPSType

"""
Device path or network address for UPS connection. Examples: '/dev/ttyUSB0' for USB, '192.168.1.100:3551' for network
"""
device: String

"""
Override UPS capacity for runtime calculations. Unit: watts (W). Leave unset to use UPS-reported capacity
"""
overrideUpsCapacity: Int

"""
Battery level percentage to initiate shutdown. Unit: percent (%) - Valid range: 0-100
"""
batteryLevel: Int

"""Runtime left in minutes to initiate shutdown. Unit: minutes"""
minutes: Int

"""
Time on battery before shutdown. Unit: seconds. Set to 0 to disable timeout-based shutdown
"""
timeout: Int

"""
Turn off UPS power after system shutdown. Useful for ensuring complete power cycle
"""
killUps: UPSKillPower
}

"""Service state for UPS daemon"""
enum UPSServiceState {
ENABLE
DISABLE
}

"""UPS cable connection types"""
enum UPSCableType {
USB
SIMPLE
SMART
ETHER
CUSTOM
}

"""UPS communication protocols"""
enum UPSType {
USB
APCSMART
NET
SNMP
DUMB
PCNET
MODBUS
}

"""Kill UPS power after shutdown option"""
enum UPSKillPower {
YES
NO
}

input PluginManagementInput {
"""Array of plugin package names to add or remove"""
names: [String!]!
Expand Down Expand Up @@ -1833,6 +2043,7 @@ type Subscription {
serversSubscription: Server!
parityHistorySubscription: ParityCheck!
arraySubscription: UnraidArray!
upsUpdates: UPSDevice!
}

"""Available authentication action verbs"""
Expand Down
8 changes: 4 additions & 4 deletions api/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ export const LOG_LEVEL = process.env.LOG_LEVEL
? 'INFO'
: 'DEBUG';
export const SUPPRESS_LOGS = process.env.SUPPRESS_LOGS === 'true';
export const MOTHERSHIP_GRAPHQL_LINK = process.env.MOTHERSHIP_GRAPHQL_LINK
? process.env.MOTHERSHIP_GRAPHQL_LINK
export const MOTHERSHIP_BASE_URL = process.env.MOTHERSHIP_BASE_URL
? process.env.MOTHERSHIP_BASE_URL
: ENVIRONMENT === 'staging'
? 'https://staging.mothership.unraid.net/ws'
: 'https://mothership.unraid.net/ws';
? 'https://staging.mothership.unraid.net'
: 'https://mothership.unraid.net';

export const PM2_HOME = process.env.PM2_HOME ?? join(homedir(), '.pm2');
export const PM2_PATH = join(import.meta.dirname, '../../', 'node_modules', 'pm2', 'bin', 'pm2');
Expand Down
9 changes: 9 additions & 0 deletions api/src/unraid-api/unraid-file-modifier/file-modification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { applyPatch, createPatch, parsePatch, reversePatch } from 'diff';
import { coerce, compare, gte } from 'semver';

import { getUnraidVersion } from '@app/common/dashboard/get-unraid-version.js';
import { NODE_ENV } from '@app/environment.js';

export type ModificationEffect = 'nginx:reload';

Expand Down Expand Up @@ -218,6 +219,14 @@ export abstract class FileModification {
throw new Error('Invalid file modification configuration');
}

// Skip file modifications in development mode
if (NODE_ENV === 'development') {
return {
shouldApply: false,
reason: 'File modifications are disabled in development mode',
};
}

const fileExists = await access(this.filePath, constants.R_OK | constants.W_OK)
.then(() => true)
.catch(() => false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { ConfigService } from '@nestjs/config';

import type { ModificationEffect } from '@app/unraid-api/unraid-file-modifier/file-modification.js';
import { NODE_ENV } from '@app/environment.js';
import { FileModificationEffectService } from '@app/unraid-api/unraid-file-modifier/file-modification-effect.service.js';
import { FileModification } from '@app/unraid-api/unraid-file-modifier/file-modification.js';

Expand All @@ -29,6 +30,11 @@ export class UnraidFileModificationService
*/
async onModuleInit() {
try {
if (NODE_ENV === 'development') {
this.logger.log('Skipping file modifications in development mode');
return;
}

this.logger.log('Loading file modifications...');
const mods = await this.loadModifications();
await this.applyModifications(mods);
Expand Down
Loading
Loading