mirror of
https://github.com/unraid/api.git
synced 2026-01-05 16:09:49 -06:00
198 lines
5.3 KiB
TypeScript
198 lines
5.3 KiB
TypeScript
/*!
|
|
* Copyright 2019-2020 Lime Technology Inc. All rights reserved.
|
|
* Written by: Alexis Tyler
|
|
*/
|
|
|
|
import os from 'os';
|
|
import am from 'am';
|
|
import * as Sentry from '@sentry/node';
|
|
import exitHook from 'async-exit-hook';
|
|
import getServerAddress from 'get-server-address';
|
|
import { core, states, coreLogger, log, apiManager, apiManagerLogger, logger } from './core';
|
|
import { server } from './server';
|
|
import { mothership } from './mothership/subscribe-to-servers';
|
|
import { startInternal, sockets } from './mothership';
|
|
import { sleep } from './core/utils';
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
const { version } = require('../package.json') as { version: string };
|
|
|
|
// Send errors to server if enabled
|
|
Sentry.init({
|
|
dsn: process.env.SENTRY_DSN,
|
|
tracesSampleRate: 1.0,
|
|
release: `unraid-api@${version}`,
|
|
environment: process.env.ENVIRONMENT ?? 'unknown',
|
|
serverName: os.hostname(),
|
|
enabled: Boolean(process.env.SENTRY_DSN)
|
|
});
|
|
|
|
// Set user's ID to their flashGuid
|
|
Sentry.setUser({
|
|
id: states.varState.data.flashGuid
|
|
});
|
|
|
|
// Boot app
|
|
am(async () => {
|
|
let lastKnownApiKey: string | undefined;
|
|
|
|
// Load core
|
|
await core.load();
|
|
|
|
// Try and load the HTTP server
|
|
coreLogger.debug('Starting HTTP server');
|
|
|
|
// Log only if the server actually binds to the port
|
|
server.server.on('listening', () => {
|
|
coreLogger.info('Server is up! %s', getServerAddress(server.server));
|
|
});
|
|
|
|
// It has it's first keys loaded
|
|
apiManager.on('ready', async () => {
|
|
try {
|
|
// Try to start server
|
|
await server.start().catch(error => {
|
|
log.error(error);
|
|
|
|
// On process exit
|
|
exitHook(async () => {
|
|
coreLogger.debug('Stopping HTTP server');
|
|
|
|
// Stop the server
|
|
server.stop();
|
|
});
|
|
});
|
|
} catch (error: unknown) {
|
|
coreLogger.error('Failed creating sockets on "ready" event with error %s.', (error as Error).message);
|
|
}
|
|
});
|
|
|
|
// If key is removed then disconnect our sockets
|
|
apiManager.on('expire', async name => {
|
|
try {
|
|
// Bail if this isn't our key
|
|
if (name !== 'my_servers') {
|
|
return;
|
|
}
|
|
|
|
log.debug('API key removed from cfg, closing connection to relay.');
|
|
|
|
// Disconnect internal
|
|
sockets.internal?.close();
|
|
|
|
// Disconnect relay
|
|
sockets.relay?.close();
|
|
} catch (error: unknown) {
|
|
apiManagerLogger.error('Failed updating sockets on "expire" event with error %s.', error);
|
|
}
|
|
});
|
|
|
|
let hasFirstKey = false;
|
|
|
|
// If the key changes try to (re)connect to Mothership
|
|
// The internal graphql check needs to be done
|
|
// first so it'll be up before relay connects
|
|
apiManager.on('replace', async (name, keyEntry) => {
|
|
try {
|
|
// Get key in string format
|
|
const newApiKey: string = keyEntry.key;
|
|
|
|
// Bail if this isn't our key
|
|
if (name !== 'my_servers') {
|
|
return;
|
|
}
|
|
|
|
// Ignore this key if it's the same as our current key.
|
|
if (newApiKey === lastKnownApiKey) {
|
|
apiManagerLogger.debug('API key has\'t changed');
|
|
return;
|
|
}
|
|
|
|
// Disconnect from relay
|
|
sockets.relay?.close();
|
|
coreLogger.debug('Disconnected from relay ws.');
|
|
|
|
// Disconnect from internal ws
|
|
sockets.internal?.close();
|
|
coreLogger.debug('Disconnected from internal ws.');
|
|
|
|
// Disconnect from mothership's subscription endpoint
|
|
mothership.close();
|
|
coreLogger.debug('Disconnected from mothership\'s subscription endpoint.');
|
|
|
|
// Since we no longer have a key and
|
|
// everything is disconnected we can bail
|
|
if (newApiKey === undefined) {
|
|
apiManagerLogger.debug('Cleared my_servers key.');
|
|
return;
|
|
}
|
|
|
|
// We've never had a key before so let's start the internal API connection
|
|
// That'll then start the relay connection to mothership
|
|
if (!hasFirstKey && lastKnownApiKey === undefined) {
|
|
coreLogger.debug('First time with a valid API key, waiting 5s to connect.');
|
|
// Wait 5s for the API to come up before connecting
|
|
setTimeout(() => {
|
|
coreLogger.debug('Connecting to internal for the first time.');
|
|
// Start the internal relay connection
|
|
// This will connect to relay once it's up
|
|
startInternal(newApiKey);
|
|
}, 5000);
|
|
|
|
// Mark this as being done
|
|
hasFirstKey = true;
|
|
|
|
// Record new key
|
|
apiManagerLogger.debug('Setting my_servers key. Key is "%s".', newApiKey);
|
|
lastKnownApiKey = newApiKey;
|
|
return;
|
|
}
|
|
|
|
// Record last known key
|
|
apiManagerLogger.debug('Replacing my_servers key. Last known key was "%s". New key is "%s".', lastKnownApiKey, newApiKey);
|
|
lastKnownApiKey = newApiKey;
|
|
|
|
// Stop internal connection
|
|
sockets.internal?.close();
|
|
|
|
// Let's reconnect everything
|
|
if (newApiKey) {
|
|
// Wait for internal to close
|
|
await sleep(1_000);
|
|
|
|
// Start internal as we do on a clean start
|
|
// This will take care of connecting to
|
|
// relay and the subscriptions
|
|
startInternal(newApiKey);
|
|
}
|
|
} catch (error: unknown) {
|
|
apiManagerLogger.error('Failed updating sockets on apiKey "replace" event with error %s.', error);
|
|
}
|
|
});
|
|
|
|
// Load nchan
|
|
core.loadNchan().catch(error => {
|
|
log.error(error);
|
|
});
|
|
}, async (error: NodeJS.ErrnoException) => {
|
|
// Log error to syslog
|
|
log.error(error);
|
|
|
|
// Send error to server for debugging
|
|
Sentry.captureException(error);
|
|
|
|
// Stop server
|
|
server.stop(async () => {
|
|
/**
|
|
* Flush messages to server before stopping.
|
|
*
|
|
* This may mean waiting up to 5s
|
|
* before the server actually stops.
|
|
*/
|
|
await Sentry.flush(5000);
|
|
|
|
// Kill application
|
|
process.exitCode = 1;
|
|
});
|
|
});
|