feat: async hypervisor and FIXED vm listing

This commit is contained in:
Eli Bosley
2025-01-28 10:09:29 -05:00
parent f1e53831c8
commit cdfb3c772b
16 changed files with 85 additions and 41 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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');

View File

@@ -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'

View File

@@ -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!
}

View File

@@ -12,11 +12,13 @@ export class RestartCommand extends CommandRunner {
async run(_): Promise<void> {
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);

View File

@@ -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;

View File

@@ -58,7 +58,7 @@ const states = {
},
};
@Resolver()
@Resolver('Display')
export class DisplayResolver {
@Query()
@UsePermissions({

View File

@@ -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({

View File

@@ -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() {}

View File

@@ -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({

View File

@@ -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({

View File

@@ -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({

View File

@@ -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({

View File

@@ -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({

View File

@@ -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<Array<VmDomain>> {
const { getDomains } = await import('@app/core/modules/vms/get-domains');
const domains = await getDomains();
return domains;
}
}