mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
feat: async hypervisor and FIXED vm listing
This commit is contained in:
@@ -34,6 +34,7 @@ if [ ! -d "$source_directory" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Change ownership on copy
|
||||||
# Replace the value inside the rsync command with the user's input
|
# 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"
|
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"
|
eval "$rsync_command"
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
|
|
||||||
# Run unraid-api restart on remote host
|
# Chown the directory
|
||||||
dev=${DEV:-true}
|
ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api"
|
||||||
|
|
||||||
if [ "$dev" = true ]; then
|
# Run unraid-api restart on remote host
|
||||||
ssh root@"${server_name}" "INTROSPECTION=true unraid-api restart"
|
ssh root@"${server_name}" "INTROSPECTION=true LOG_LEVEL=trace unraid-api restart"
|
||||||
else
|
|
||||||
ssh root@"${server_name}" "unraid-api restart"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Play built-in sound based on the operating system
|
# Play built-in sound based on the operating system
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
|||||||
@@ -5,25 +5,36 @@ import '@app/dotenv';
|
|||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
import { CommandFactory } from 'nest-commander';
|
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 { 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 {
|
try {
|
||||||
const shellToUse = await execa('which unraid-api')
|
|
||||||
.then((res) => res.toString().trim())
|
|
||||||
.catch((_) => '/usr/local/bin/unraid-api');
|
|
||||||
await CommandFactory.run(CliModule, {
|
await CommandFactory.run(CliModule, {
|
||||||
cliName: 'unraid-api',
|
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: {
|
completion: {
|
||||||
fig: true,
|
fig: false,
|
||||||
cmd: 'completion-script',
|
cmd: 'completion-script',
|
||||||
nativeShell: { executablePath: shellToUse },
|
nativeShell: { executablePath: await getUnraidApiLocation() },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cliLogger.error('ERROR:', error);
|
logger.error('ERROR:', error);
|
||||||
internalLogger.error({
|
internalLogger.error({
|
||||||
message: 'Failed to start unraid-api',
|
message: 'Failed to start unraid-api',
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ const states = {
|
|||||||
* Get vm domains.
|
* Get vm domains.
|
||||||
*/
|
*/
|
||||||
export const getDomains = async () => {
|
export const getDomains = async () => {
|
||||||
const { ConnectListAllDomainsFlags } = await import('@vmngr/libvirt');
|
|
||||||
const { UnraidHypervisor } = await import('@app/core/utils/vms/get-hypervisor');
|
|
||||||
try {
|
try {
|
||||||
|
const { ConnectListAllDomainsFlags } = await import('@vmngr/libvirt');
|
||||||
|
const { UnraidHypervisor } = await import('@app/core/utils/vms/get-hypervisor');
|
||||||
|
|
||||||
const hypervisor = await UnraidHypervisor.getInstance().getHypervisor();
|
const hypervisor = await UnraidHypervisor.getInstance().getHypervisor();
|
||||||
if (!hypervisor) {
|
if (!hypervisor) {
|
||||||
throw new GraphQLError('VMs Disabled');
|
throw new GraphQLError('VMs Disabled');
|
||||||
|
|||||||
@@ -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 BYPASS_CORS_CHECKS = process.env.BYPASS_CORS_CHECKS === 'true';
|
||||||
export const LOG_CORS = process.env.LOG_CORS === '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_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'
|
| 'TRACE'
|
||||||
| 'DEBUG'
|
| 'DEBUG'
|
||||||
| 'INFO'
|
| 'INFO'
|
||||||
|
|||||||
34
api/src/graphql/schema/types/vms/domain.graphql
Normal file
34
api/src/graphql/schema/types/vms/domain.graphql
Normal 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!
|
||||||
|
}
|
||||||
@@ -12,11 +12,13 @@ export class RestartCommand extends CommandRunner {
|
|||||||
|
|
||||||
async run(_): Promise<void> {
|
async run(_): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
this.logger.info('Restarting the Unraid API');
|
||||||
const { stderr, stdout } = await execa(PM2_PATH, [
|
const { stderr, stdout } = await execa(PM2_PATH, [
|
||||||
'restart',
|
'restart',
|
||||||
ECOSYSTEM_PATH,
|
ECOSYSTEM_PATH,
|
||||||
'--update-env',
|
'--update-env',
|
||||||
]);
|
]);
|
||||||
|
this.logger.info('Unraid API restarted');
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
this.logger.error(stderr);
|
this.logger.error(stderr);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Command, CommandRunner, Option } from 'nest-commander';
|
|||||||
|
|
||||||
import { ECOSYSTEM_PATH, PM2_PATH } from '@app/consts';
|
import { ECOSYSTEM_PATH, PM2_PATH } from '@app/consts';
|
||||||
import { levels, type LogLevel } from '@app/core/log';
|
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 {
|
interface StartCommandOptions {
|
||||||
'log-level'?: string;
|
'log-level'?: string;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const states = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Display')
|
||||||
export class DisplayResolver {
|
export class DisplayResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz';
|
|||||||
import { Resource } from '@app/graphql/generated/api/types';
|
import { Resource } from '@app/graphql/generated/api/types';
|
||||||
import { getters } from '@app/store/index';
|
import { getters } from '@app/store/index';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Flash')
|
||||||
export class FlashResolver {
|
export class FlashResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { UserAccount } from '@app/graphql/generated/api/types';
|
|||||||
import { Me, Resource } from '@app/graphql/generated/api/types';
|
import { Me, Resource } from '@app/graphql/generated/api/types';
|
||||||
import { GraphqlUser } from '@app/unraid-api/auth/user.decorator';
|
import { GraphqlUser } from '@app/unraid-api/auth/user.decorator';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Me')
|
||||||
export class MeResolver {
|
export class MeResolver {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz';
|
|||||||
|
|
||||||
import { Resource } from '@app/graphql/generated/api/types';
|
import { Resource } from '@app/graphql/generated/api/types';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Online')
|
||||||
export class OnlineResolver {
|
export class OnlineResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { createSubscription, PUBSUB_CHANNEL } from '@app/core/pubsub';
|
|||||||
import { Resource } from '@app/graphql/generated/api/types';
|
import { Resource } from '@app/graphql/generated/api/types';
|
||||||
import { getters } from '@app/store/index';
|
import { getters } from '@app/store/index';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Owner')
|
||||||
export class OwnerResolver {
|
export class OwnerResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { registrationType, Resource } from '@app/graphql/generated/api/types';
|
|||||||
import { getters } from '@app/store/index';
|
import { getters } from '@app/store/index';
|
||||||
import { FileLoadStatus } from '@app/store/types';
|
import { FileLoadStatus } from '@app/store/types';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Registration')
|
||||||
export class RegistrationResolver {
|
export class RegistrationResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Resource } from '@app/graphql/generated/api/types';
|
|||||||
import { type Server } from '@app/graphql/generated/client/graphql';
|
import { type Server } from '@app/graphql/generated/client/graphql';
|
||||||
import { getLocalServer } from '@app/graphql/schema/utils';
|
import { getLocalServer } from '@app/graphql/schema/utils';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Server')
|
||||||
export class ServerResolver {
|
export class ServerResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz';
|
|||||||
import { Resource } from '@app/graphql/generated/api/types';
|
import { Resource } from '@app/graphql/generated/api/types';
|
||||||
import { getters } from '@app/store/index';
|
import { getters } from '@app/store/index';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Vars')
|
||||||
export class VarsResolver {
|
export class VarsResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
|
|||||||
@@ -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 { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz';
|
||||||
|
|
||||||
import { getDomains } from '@app/core/modules/vms/get-domains';
|
import { Resource, type VmDomain } from '@app/graphql/generated/api/types';
|
||||||
import { Resource } from '@app/graphql/generated/api/types';
|
|
||||||
|
|
||||||
@Resolver()
|
@Resolver('Vms')
|
||||||
export class VmsResolver {
|
export class VmsResolver {
|
||||||
@Query()
|
@Query()
|
||||||
@UsePermissions({
|
@UsePermissions({
|
||||||
@@ -14,17 +13,16 @@ export class VmsResolver {
|
|||||||
possession: AuthPossession.ANY,
|
possession: AuthPossession.ANY,
|
||||||
})
|
})
|
||||||
public async vms() {
|
public async vms() {
|
||||||
return {};
|
console.log('Resolving Domains');
|
||||||
|
return {
|
||||||
|
id: 'vms',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resolver('domain')
|
@ResolveField('domain')
|
||||||
@Query()
|
public async domain(): Promise<Array<VmDomain>> {
|
||||||
@UsePermissions({
|
const { getDomains } = await import('@app/core/modules/vms/get-domains');
|
||||||
action: AuthActionVerb.READ,
|
const domains = await getDomains();
|
||||||
resource: 'vms/domain',
|
return domains;
|
||||||
possession: AuthPossession.ANY,
|
|
||||||
})
|
|
||||||
public async domain() {
|
|
||||||
return getDomains();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user