From cdfb3c772b8d4897a3bd59eb28d196c4653d4519 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 28 Jan 2025 10:09:29 -0500 Subject: [PATCH] feat: async hypervisor and FIXED vm listing --- api/scripts/deploy-dev.sh | 12 +++---- api/src/cli.ts | 27 ++++++++++----- api/src/core/modules/vms/get-domains.ts | 5 +-- api/src/environment.ts | 2 +- .../graphql/schema/types/vms/domain.graphql | 34 +++++++++++++++++++ api/src/unraid-api/cli/restart.command.ts | 2 ++ api/src/unraid-api/cli/start.command.ts | 2 +- .../resolvers/display/display.resolver.ts | 2 +- .../graph/resolvers/flash/flash.resolver.ts | 2 +- .../graph/resolvers/me/me.resolver.ts | 2 +- .../graph/resolvers/online/online.resolver.ts | 2 +- .../graph/resolvers/owner/owner.resolver.ts | 2 +- .../registration/registration.resolver.ts | 2 +- .../resolvers/servers/server.resolver.ts | 2 +- .../graph/resolvers/vars/vars.resolver.ts | 2 +- .../graph/resolvers/vms/vms.resolver.ts | 26 +++++++------- 16 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 api/src/graphql/schema/types/vms/domain.graphql diff --git a/api/scripts/deploy-dev.sh b/api/scripts/deploy-dev.sh index 08fcb8a49..04f5e6b5e 100755 --- a/api/scripts/deploy-dev.sh +++ b/api/scripts/deploy-dev.sh @@ -34,6 +34,7 @@ if [ ! -d "$source_directory" ]; then fi fi +# Change ownership on copy # Replace the value inside the rsync command with the user's input rsync_command="rsync -avz -e ssh $source_directory root@${server_name}:/usr/local/unraid-api" @@ -44,14 +45,11 @@ echo "$rsync_command" eval "$rsync_command" exit_code=$? -# Run unraid-api restart on remote host -dev=${DEV:-true} +# Chown the directory +ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api" -if [ "$dev" = true ]; then - ssh root@"${server_name}" "INTROSPECTION=true unraid-api restart" -else - ssh root@"${server_name}" "unraid-api restart" -fi +# Run unraid-api restart on remote host +ssh root@"${server_name}" "INTROSPECTION=true LOG_LEVEL=trace unraid-api restart" # Play built-in sound based on the operating system if [[ "$OSTYPE" == "darwin"* ]]; then diff --git a/api/src/cli.ts b/api/src/cli.ts index ac1013a2b..323c6369e 100644 --- a/api/src/cli.ts +++ b/api/src/cli.ts @@ -5,25 +5,36 @@ import '@app/dotenv'; import { execa } from 'execa'; import { CommandFactory } from 'nest-commander'; -import { cliLogger, internalLogger } from '@app/core/log'; +import { internalLogger, logger } from '@app/core/log'; +import { LOG_LEVEL } from '@app/environment'; import { CliModule } from '@app/unraid-api/cli/cli.module'; +import { LogService } from '@app/unraid-api/cli/log.service'; + +const getUnraidApiLocation = async () => { + try { + const shellToUse = await execa('which unraid-api'); + if (shellToUse.code !== 0) { + throw new Error('unraid-api not found'); + } + return shellToUse.stdout.trim(); + } finally { + return '/usr/bin/unraid-api'; + } +}; try { - const shellToUse = await execa('which unraid-api') - .then((res) => res.toString().trim()) - .catch((_) => '/usr/local/bin/unraid-api'); await CommandFactory.run(CliModule, { cliName: 'unraid-api', - logger: false, // new LogService(), - enable this to see nest initialization issues + logger: LOG_LEVEL === 'TRACE' && new LogService(), // - enable this to see nest initialization issues completion: { - fig: true, + fig: false, cmd: 'completion-script', - nativeShell: { executablePath: shellToUse }, + nativeShell: { executablePath: await getUnraidApiLocation() }, }, }); process.exit(0); } catch (error) { - cliLogger.error('ERROR:', error); + logger.error('ERROR:', error); internalLogger.error({ message: 'Failed to start unraid-api', error, diff --git a/api/src/core/modules/vms/get-domains.ts b/api/src/core/modules/vms/get-domains.ts index 5a831d86b..e18b7a4db 100644 --- a/api/src/core/modules/vms/get-domains.ts +++ b/api/src/core/modules/vms/get-domains.ts @@ -18,9 +18,10 @@ const states = { * Get vm domains. */ export const getDomains = async () => { - const { ConnectListAllDomainsFlags } = await import('@vmngr/libvirt'); - const { UnraidHypervisor } = await import('@app/core/utils/vms/get-hypervisor'); try { + const { ConnectListAllDomainsFlags } = await import('@vmngr/libvirt'); + const { UnraidHypervisor } = await import('@app/core/utils/vms/get-hypervisor'); + const hypervisor = await UnraidHypervisor.getInstance().getHypervisor(); if (!hypervisor) { throw new GraphQLError('VMs Disabled'); diff --git a/api/src/environment.ts b/api/src/environment.ts index 0624b8331..a66ee8d41 100644 --- a/api/src/environment.ts +++ b/api/src/environment.ts @@ -19,7 +19,7 @@ export const BYPASS_PERMISSION_CHECKS = process.env.BYPASS_PERMISSION_CHECKS === export const BYPASS_CORS_CHECKS = process.env.BYPASS_CORS_CHECKS === 'true'; export const LOG_CORS = process.env.LOG_CORS === 'true'; export const LOG_TYPE = (process.env.LOG_TYPE as 'pretty' | 'raw') ?? 'pretty'; -export const LOG_LEVEL = process.env.LOG_LEVEL as +export const LOG_LEVEL = process.env.LOG_LEVEL?.toUpperCase() as | 'TRACE' | 'DEBUG' | 'INFO' diff --git a/api/src/graphql/schema/types/vms/domain.graphql b/api/src/graphql/schema/types/vms/domain.graphql new file mode 100644 index 000000000..1787d0dd6 --- /dev/null +++ b/api/src/graphql/schema/types/vms/domain.graphql @@ -0,0 +1,34 @@ +type Query { + """Virtual machines""" + vms: Vms +} + +type Vms { + id: ID! + domain: [VmDomain!] +} + +type Subscription { + vms: Vms +} + +# https://libvirt.org/manpages/virsh.html#list +enum VmState { + NOSTATE + RUNNING + IDLE + PAUSED + SHUTDOWN + SHUTOFF + CRASHED + PMSUSPENDED +} + +"""A virtual machine""" +type VmDomain { + uuid: ID! + """A friendly name for the vm""" + name: String + """Current domain vm state""" + state: VmState! +} diff --git a/api/src/unraid-api/cli/restart.command.ts b/api/src/unraid-api/cli/restart.command.ts index 538e4dc2c..fc264f40e 100644 --- a/api/src/unraid-api/cli/restart.command.ts +++ b/api/src/unraid-api/cli/restart.command.ts @@ -12,11 +12,13 @@ export class RestartCommand extends CommandRunner { async run(_): Promise { try { + this.logger.info('Restarting the Unraid API'); const { stderr, stdout } = await execa(PM2_PATH, [ 'restart', ECOSYSTEM_PATH, '--update-env', ]); + this.logger.info('Unraid API restarted'); if (stderr) { this.logger.error(stderr); process.exit(1); diff --git a/api/src/unraid-api/cli/start.command.ts b/api/src/unraid-api/cli/start.command.ts index 6be24fd28..037f6ed20 100644 --- a/api/src/unraid-api/cli/start.command.ts +++ b/api/src/unraid-api/cli/start.command.ts @@ -3,7 +3,7 @@ import { Command, CommandRunner, Option } from 'nest-commander'; import { ECOSYSTEM_PATH, PM2_PATH } from '@app/consts'; import { levels, type LogLevel } from '@app/core/log'; -import type { LogService } from '@app/unraid-api/cli/log.service'; +import { LogService } from '@app/unraid-api/cli/log.service'; interface StartCommandOptions { 'log-level'?: string; diff --git a/api/src/unraid-api/graph/resolvers/display/display.resolver.ts b/api/src/unraid-api/graph/resolvers/display/display.resolver.ts index 1b5e42ee6..168af4eac 100644 --- a/api/src/unraid-api/graph/resolvers/display/display.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/display/display.resolver.ts @@ -58,7 +58,7 @@ const states = { }, }; -@Resolver() +@Resolver('Display') export class DisplayResolver { @Query() @UsePermissions({ diff --git a/api/src/unraid-api/graph/resolvers/flash/flash.resolver.ts b/api/src/unraid-api/graph/resolvers/flash/flash.resolver.ts index ce60f08a9..2330e15ba 100644 --- a/api/src/unraid-api/graph/resolvers/flash/flash.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/flash/flash.resolver.ts @@ -5,7 +5,7 @@ import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz'; import { Resource } from '@app/graphql/generated/api/types'; import { getters } from '@app/store/index'; -@Resolver() +@Resolver('Flash') export class FlashResolver { @Query() @UsePermissions({ diff --git a/api/src/unraid-api/graph/resolvers/me/me.resolver.ts b/api/src/unraid-api/graph/resolvers/me/me.resolver.ts index 12979c6ad..3108188e8 100644 --- a/api/src/unraid-api/graph/resolvers/me/me.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/me/me.resolver.ts @@ -6,7 +6,7 @@ import type { UserAccount } from '@app/graphql/generated/api/types'; import { Me, Resource } from '@app/graphql/generated/api/types'; import { GraphqlUser } from '@app/unraid-api/auth/user.decorator'; -@Resolver() +@Resolver('Me') export class MeResolver { constructor() {} diff --git a/api/src/unraid-api/graph/resolvers/online/online.resolver.ts b/api/src/unraid-api/graph/resolvers/online/online.resolver.ts index 1f8bdb474..1d14d3979 100644 --- a/api/src/unraid-api/graph/resolvers/online/online.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/online/online.resolver.ts @@ -4,7 +4,7 @@ import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz'; import { Resource } from '@app/graphql/generated/api/types'; -@Resolver() +@Resolver('Online') export class OnlineResolver { @Query() @UsePermissions({ diff --git a/api/src/unraid-api/graph/resolvers/owner/owner.resolver.ts b/api/src/unraid-api/graph/resolvers/owner/owner.resolver.ts index 2a8981abd..320284a78 100644 --- a/api/src/unraid-api/graph/resolvers/owner/owner.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/owner/owner.resolver.ts @@ -6,7 +6,7 @@ import { createSubscription, PUBSUB_CHANNEL } from '@app/core/pubsub'; import { Resource } from '@app/graphql/generated/api/types'; import { getters } from '@app/store/index'; -@Resolver() +@Resolver('Owner') export class OwnerResolver { @Query() @UsePermissions({ diff --git a/api/src/unraid-api/graph/resolvers/registration/registration.resolver.ts b/api/src/unraid-api/graph/resolvers/registration/registration.resolver.ts index 389ff52cc..e80862acd 100644 --- a/api/src/unraid-api/graph/resolvers/registration/registration.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/registration/registration.resolver.ts @@ -9,7 +9,7 @@ import { registrationType, Resource } from '@app/graphql/generated/api/types'; import { getters } from '@app/store/index'; import { FileLoadStatus } from '@app/store/types'; -@Resolver() +@Resolver('Registration') export class RegistrationResolver { @Query() @UsePermissions({ diff --git a/api/src/unraid-api/graph/resolvers/servers/server.resolver.ts b/api/src/unraid-api/graph/resolvers/servers/server.resolver.ts index 0fcac2a93..18ce7324f 100644 --- a/api/src/unraid-api/graph/resolvers/servers/server.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/servers/server.resolver.ts @@ -7,7 +7,7 @@ import { Resource } from '@app/graphql/generated/api/types'; import { type Server } from '@app/graphql/generated/client/graphql'; import { getLocalServer } from '@app/graphql/schema/utils'; -@Resolver() +@Resolver('Server') export class ServerResolver { @Query() @UsePermissions({ diff --git a/api/src/unraid-api/graph/resolvers/vars/vars.resolver.ts b/api/src/unraid-api/graph/resolvers/vars/vars.resolver.ts index 93b17d2ec..f688e099f 100644 --- a/api/src/unraid-api/graph/resolvers/vars/vars.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/vars/vars.resolver.ts @@ -5,7 +5,7 @@ import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz'; import { Resource } from '@app/graphql/generated/api/types'; import { getters } from '@app/store/index'; -@Resolver() +@Resolver('Vars') export class VarsResolver { @Query() @UsePermissions({ diff --git a/api/src/unraid-api/graph/resolvers/vms/vms.resolver.ts b/api/src/unraid-api/graph/resolvers/vms/vms.resolver.ts index be266510a..adac84b00 100644 --- a/api/src/unraid-api/graph/resolvers/vms/vms.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/vms/vms.resolver.ts @@ -1,11 +1,10 @@ -import { Query, Resolver } from '@nestjs/graphql'; +import { Query, ResolveField, Resolver } from '@nestjs/graphql'; import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz'; -import { getDomains } from '@app/core/modules/vms/get-domains'; -import { Resource } from '@app/graphql/generated/api/types'; +import { Resource, type VmDomain } from '@app/graphql/generated/api/types'; -@Resolver() +@Resolver('Vms') export class VmsResolver { @Query() @UsePermissions({ @@ -14,17 +13,16 @@ export class VmsResolver { possession: AuthPossession.ANY, }) public async vms() { - return {}; + console.log('Resolving Domains'); + return { + id: 'vms', + }; } - @Resolver('domain') - @Query() - @UsePermissions({ - action: AuthActionVerb.READ, - resource: 'vms/domain', - possession: AuthPossession.ANY, - }) - public async domain() { - return getDomains(); + @ResolveField('domain') + public async domain(): Promise> { + const { getDomains } = await import('@app/core/modules/vms/get-domains'); + const domains = await getDomains(); + return domains; } }