feat: process env fixed and copy gql files

This commit is contained in:
Eli Bosley
2024-11-04 11:41:19 -05:00
parent 2f48ddf942
commit 72fcaca4f3
20 changed files with 186 additions and 59 deletions

View File

@@ -6,7 +6,6 @@ PATHS_DYNAMIX_CONFIG=./dev/dynamix/dynamix.cfg # Dynamix's config file
PATHS_MY_SERVERS_CONFIG=./dev/Unraid.net/myservers.cfg # My servers config file
PATHS_KEYFILE_BASE=./dev/Unraid.net # Keyfile location
PATHS_MACHINE_ID=./dev/data/machine-id
ENVIRONMENT="development"
NODE_ENV="development"
PORT="3001"

View File

@@ -1,5 +1,5 @@
[api]
version="THIS_WILL_BE_REPLACED_WHEN_BUILT"
version="3.11.0"
extraOrigins="https://google.com,https://test.com"
[local]
[notifier]

View File

@@ -1,5 +1,5 @@
[api]
version="THIS_WILL_BE_REPLACED_WHEN_BUILT"
version="3.11.0"
extraOrigins="https://google.com,https://test.com"
[local]
[notifier]
@@ -21,4 +21,4 @@ dynamicRemoteAccessType="DISABLED"
[upc]
apikey="unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810"
[connectionStatus]
minigraph="ERROR_RETRYING"
minigraph="PRE_INIT"

View File

@@ -2,8 +2,8 @@
"apps": [
{
"name": "unraid-api",
"script": "npm",
"args": "run dist/main.js",
"script": "node",
"args": "dist/main.js",
"log": "/var/log/unraid-api/unraid-api.log",
"exec_mode": "fork",
"ignore_watch": [

45
api/package-lock.json generated
View File

@@ -139,6 +139,7 @@
"typescript": "^5.4.5",
"typescript-eslint": "^8.10.0",
"vite-plugin-node": "^4.0.0",
"vite-plugin-static-copy": "^2.0.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.1.1",
"zx": "^7.2.3"
@@ -5520,10 +5521,11 @@
}
},
"node_modules/acorn": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -15649,6 +15651,25 @@
}
}
},
"node_modules/vite-plugin-static-copy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.0.0.tgz",
"integrity": "sha512-b/quFjTUa/RY9t3geIyeeT2GtWEoRI0GawYFFjys5iMLGgVP638NTGu0RoMjwmi8MoZZ3BQw4OQvb1GpVcXZDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.3",
"fast-glob": "^3.2.11",
"fs-extra": "^11.1.0",
"picocolors": "^1.0.0"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
"vite": "^5.0.0"
}
},
"node_modules/vite-tsconfig-paths": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz",
@@ -20378,9 +20399,9 @@
}
},
"acorn": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true
},
"acorn-jsx": {
@@ -27243,6 +27264,18 @@
"debug": "^4.3.2"
}
},
"vite-plugin-static-copy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.0.0.tgz",
"integrity": "sha512-b/quFjTUa/RY9t3geIyeeT2GtWEoRI0GawYFFjys5iMLGgVP638NTGu0RoMjwmi8MoZZ3BQw4OQvb1GpVcXZDA==",
"dev": true,
"requires": {
"chokidar": "^3.5.3",
"fast-glob": "^3.2.11",
"fs-extra": "^11.1.0",
"picocolors": "^1.0.0"
}
},
"vite-tsconfig-paths": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz",

View File

@@ -27,7 +27,7 @@
"start:plugin-verbose": "LOG_CONTEXT=true LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty LOG_LEVEL=trace unraid-api start --debug",
"run:tsx": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty LOG_LEVEL=trace DOTENV_CONFIG_PATH=./.env.development tsx -r dotenv/config src/cli.ts",
"watch:tsx": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty LOG_LEVEL=trace DOTENV_CONFIG_PATH=./.env.development tsx watch -r dotenv/config src/cli.ts",
"start:dev": "npm run watch:tsx -- start --debug",
"start:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/main.js",
"restart:dev": "npm run run:tsx -- restart --debug",
"stop:dev": "npm run run:tsx -- stop --debug",
"start:report": "npm run run:tsx -- report --debug",
@@ -175,6 +175,7 @@
"typescript": "^5.4.5",
"typescript-eslint": "^8.10.0",
"vite-plugin-node": "^4.0.0",
"vite-plugin-static-copy": "^2.0.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.1.1",
"zx": "^7.2.3"

View File

@@ -2,12 +2,12 @@ import { main } from '@app/cli/index';
import { internalLogger } from '@app/core/log';
try {
await main();
await main();
} catch (error) {
console.log(error);
internalLogger.error({
message: 'Failed to start unraid-api',
error,
});
process.exit(1);
}
console.log(error);
internalLogger.error({
message: 'Failed to start unraid-api',
error,
});
process.exit(1);
}

View File

@@ -12,25 +12,9 @@ export const main = async (...argv: string[]) => {
cliLogger.debug(env, 'Loading env file');
// Set envs
setEnv('LOG_TYPE', 'pretty');
cliLogger.debug({ paths: getters.paths() }, 'Starting CLI');
setEnv('DEBUG', mainOptions.debug ?? false);
setEnv('ENVIRONMENT', process.env.ENVIRONMENT ?? 'production');
setEnv('PORT', process.env.PORT ?? mainOptions.port ?? '9000');
setEnv('LOG_LEVEL', process.env.LOG_LEVEL ?? mainOptions['log-level'] ?? 'INFO');
if (!process.env.LOG_TRANSPORT) {
if (process.env.ENVIRONMENT === 'production' && !mainOptions.debug) {
setEnv('LOG_TRANSPORT', 'file');
setEnv('LOG_LEVEL', 'INFO');
} else if (!mainOptions.debug) {
// Staging Environment, backgrounded plugin
setEnv('LOG_TRANSPORT', 'file');
setEnv('LOG_LEVEL', 'TRACE');
} else {
cliLogger.debug('In Debug Mode - Log Level Defaulting to: stdout');
}
}
if (!command) {
// Run help command
@@ -53,7 +37,6 @@ export const main = async (...argv: string[]) => {
status: import('@app/cli/commands/status').then((pkg) => pkg.status),
report: import('@app/cli/commands/report').then((pkg) => pkg.report),
'validate-token': import('@app/cli/commands/validate-token').then((pkg) => pkg.validateToken),
};
// Unknown command

View File

@@ -46,11 +46,7 @@ export const KEEP_ALIVE_INTERVAL_MS = THREE_MINUTES_MS; // This is set to 45 sec
/**
* Graphql link.
*/
export const MOTHERSHIP_GRAPHQL_LINK =
process.env.MOTHERSHIP_GRAPHQL_LINK ??
(process.env.ENVIRONMENT === 'staging'
? 'https://staging.mothership.unraid.net/ws'
: 'https://mothership.unraid.net/ws');
export const JWKS_LOCAL_PAYLOAD: JSONWebKeySet = {
keys: [

View File

@@ -1,7 +1,7 @@
import { version } from 'package.json';
export const API_VERSION =
process.env.npm_package_version ?? version ?? 'PLEASE_RUN_PACKAGE_AS_NPM_SCRIPT';
process.env.npm_package_version ?? version ?? new Error('API_VERSION not set');
export const NODE_ENV = process.env.NODE_ENV as 'development' | 'test' | 'staging' | 'production';
export const environment = {
@@ -11,7 +11,7 @@ export const CHOKIDAR_USEPOLLING = process.env.CHOKIDAR_USEPOLLING === 'true';
export const IS_DOCKER = process.env.IS_DOCKER === 'true';
export const DEBUG = process.env.DEBUG === 'true';
export const INTROSPECTION = process.env.INTROSPECTION === 'true';
export const ENVIRONMENT = process.env.ENVIRONMENT as 'production' | 'staging' | 'development';
export const ENVIRONMENT = process.env.ENVIRONMENT as 'production' | 'staging' | 'development' ?? 'production';
export const GRAPHQL_INTROSPECTION = Boolean(INTROSPECTION ?? DEBUG ?? ENVIRONMENT !== 'production');
export const PORT = process.env.PORT ?? '/var/run/unraid-api.sock';
export const DRY_RUN = process.env.DRY_RUN === 'true';
@@ -25,5 +25,9 @@ export const LOG_LEVEL = process.env.LOG_LEVEL as
| 'INFO'
| 'WARN'
| 'ERROR'
| 'FATAL';
export const LOG_TRANSPORT = process.env.LOG_TRANSPORT as 'file' | 'stdout';
| 'FATAL' ?? process.env.ENVIRONMENT === 'production' ? 'INFO' : 'TRACE';
export const MOTHERSHIP_GRAPHQL_LINK =
process.env.MOTHERSHIP_GRAPHQL_LINK ??
(process.env.ENVIRONMENT === 'staging'
? 'https://staging.mothership.unraid.net/ws'
: 'https://mothership.unraid.net/ws');

View File

@@ -1,4 +1,4 @@
import { FIVE_DAYS_SECS, MOTHERSHIP_GRAPHQL_LINK, ONE_DAY_SECS } from '@app/consts';
import { FIVE_DAYS_SECS, ONE_DAY_SECS } from '@app/consts';
import { logger } from '@app/core/log';
import { checkDNS } from '@app/graphql/resolvers/query/cloud/check-dns';
import { checkMothershipAuthentication } from '@app/graphql/resolvers/query/cloud/check-mothership-authentication';
@@ -7,7 +7,7 @@ import { getCloudCache, getDnsCache } from '@app/store/getters';
import { setCloudCheck, setDNSCheck } from '@app/store/modules/cache';
import { got } from 'got';
import { type CloudResponse, MinigraphStatus } from '@app/graphql/generated/api/types';
import { API_VERSION } from '@app/environment';
import { API_VERSION, MOTHERSHIP_GRAPHQL_LINK } from '@app/environment';
const mothershipBaseUrl = new URL(MOTHERSHIP_GRAPHQL_LINK).origin;

View File

@@ -1,4 +1,4 @@
import { MOTHERSHIP_GRAPHQL_LINK } from '@app/consts';
import { MOTHERSHIP_GRAPHQL_LINK } from '@app/environment';
import { store } from '@app/store';
import { getDnsCache } from '@app/store/getters';
import { setDNSCheck } from '@app/store/modules/cache';

View File

@@ -0,0 +1,52 @@
import { logger } from '@app/core';
import { MOTHERSHIP_GRAPHQL_LINK } from '@app/environment';
import { got, HTTPError, TimeoutError } from 'got';
const createGotOptions = (apiVersion: string, apiKey: string) => ({
timeout: {
request: 5_000,
},
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'x-unraid-api-version': apiVersion,
'x-api-key': apiKey,
},
});
// Check if we're rate limited, etc.
export const checkMothershipAuthentication = async (apiVersion: string, apiKey: string) => {
const msURL = new URL(MOTHERSHIP_GRAPHQL_LINK);
const url = `https://${msURL.hostname}${msURL.pathname}`;
try {
const options = createGotOptions(apiVersion, apiKey);
// This will throw if there is a non 2XX/3XX code
await got.head(url, options);
} catch (error: unknown) {
// HTTP errors
if (error instanceof HTTPError) {
switch (error.response.statusCode) {
case 429: {
const retryAfter = error.response.headers['retry-after'];
throw new Error(retryAfter ? `${url} is rate limited for another ${retryAfter} seconds` : `${url} is rate limited`);
}
case 401:
throw new Error('Invalid credentials');
default:
throw new Error(`Failed to connect to ${url} with a "${error.response.statusCode}" HTTP error.`);
}
}
// Timeout error
if (error instanceof TimeoutError) throw new Error(`Timed-out while connecting to "${url}"`);
// Unknown error
logger.trace('Unknown Error', error);
// @TODO: Add in the cause when we move to a newer node version
// throw new Error('Unknown Error', { cause: error as Error });
throw new Error('Unknown Error');
}
};

View File

@@ -2,6 +2,7 @@ import { join } from 'path';
import { loadFilesSync } from '@graphql-tools/load-files';
import { mergeTypeDefs } from '@graphql-tools/merge';
console.log('Loading type definitions from ' + join(import.meta.dirname, './types'));
const files = loadFilesSync(join(import.meta.dirname, './types'), {
extensions: ['graphql'],
});

View File

@@ -1,6 +1,7 @@
import 'reflect-metadata';
import 'global-agent/bootstrap.js';
// Configure dotenv
import 'dotenv/config';
import http from 'http';
import https from 'https';
import CacheableLookup from 'cacheable-lookup';
@@ -28,7 +29,7 @@ import { type RawServerDefault } from 'fastify';
import { setupLogRotation } from '@app/core/logrotate/setup-logrotate';
import { WebSocket } from 'ws';
import exitHook from 'exit-hook';
import * as env from '@app/environment';
import * as envVars from '@app/environment';
let server: NestFastifyApplication<RawServerDefault> | null = null;
@@ -41,7 +42,8 @@ const unlinkUnixPort = () => {
try {
environment.IS_MAIN_PROCESS = true;
logger.debug('ENV %o', env);
logger.info('ENV %o', envVars);
logger.info('PATHS %o', store.getState().paths);
const cacheable = new CacheableLookup();

View File

@@ -1,4 +1,4 @@
import { FIVE_MINUTES_MS, MOTHERSHIP_GRAPHQL_LINK } from '@app/consts';
import { FIVE_MINUTES_MS } from '@app/consts';
import { minigraphLogger } from '@app/core/log';
import {
getMothershipConnectionParams,
@@ -14,7 +14,7 @@ import {
} from '@apollo/client/core/index.js';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions/index.js';
import { MinigraphStatus } from '@app/graphql/generated/api/types';
import { API_VERSION } from '@app/environment';
import { API_VERSION, MOTHERSHIP_GRAPHQL_LINK } from '@app/environment';
import {
receivedMothershipPing,
setMothershipTimeout,

View File

@@ -5,7 +5,7 @@ const initialState = {
core: import.meta.dirname,
'unraid-api-base': '/usr/local/unraid-api/' as const,
'unraid-data': resolvePath(
process.env.PATHS_UNRAID_DATA ?? ('/boot/config/plugins/dynamix.my.servers/data/' as const)
process['env'].PATHS_UNRAID_DATA ?? ('/boot/config/plugins/dynamix.my.servers/data/' as const)
),
'docker-autostart': '/var/lib/docker/unraid-autostart' as const,
'docker-socket': '/var/run/docker.sock' as const,

View File

@@ -0,0 +1,29 @@
import { getters, store } from '@app/store';
import { watch } from 'chokidar';
import { loadConfigFile, logoutUser } from '@app/store/modules/config';
import { logger } from '@app/core/log';
import { existsSync, writeFileSync } from 'fs';
import { CHOKIDAR_USEPOLLING, ENVIRONMENT } from '@app/environment';
export const setupConfigPathWatch = () => {
const myServersConfigPath = getters.paths()?.['myservers-config'];
if (myServersConfigPath) {
logger.info('Watch Setup on Config Path: %s', myServersConfigPath);
if (!existsSync(myServersConfigPath)) {
writeFileSync(myServersConfigPath, '', 'utf-8');
}
const watcher = watch(myServersConfigPath, {
persistent: true,
ignoreInitial: false,
usePolling: CHOKIDAR_USEPOLLING === true,
}).on('change', async () => {
await store.dispatch(loadConfigFile());
}).on('unlink', async () => {
watcher.close();
setupConfigPathWatch();
store.dispatch(logoutUser({ reason: 'Config File was Deleted'}))
});
} else {
logger.error('[FATAL] Failed to setup watch on My Servers Config (Could Not Read Config Path)');
}
};

View File

@@ -0,0 +1,16 @@
import { CHOKIDAR_USEPOLLING } from '@app/environment';
import { store } from '@app/store';
import { loadRegistrationKey } from '@app/store/modules/registration';
import { watch } from 'chokidar';
export const setupRegistrationKeyWatch = () => {
watch('/boot/config', {
persistent: true,
ignoreInitial: true,
ignored: (path: string) => !path.endsWith('.key'),
usePolling: CHOKIDAR_USEPOLLING === true,
}).on('all', async () => {
// Load updated key into store
await store.dispatch(loadRegistrationKey());
});
};

View File

@@ -2,11 +2,23 @@ import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';
import nodeExternals from 'rollup-plugin-node-externals';
import { viteCommonjs } from '@originjs/vite-plugin-commonjs';
import { viteStaticCopy } from 'vite-plugin-static-copy';
export default defineConfig(() => {
return {
plugins: [tsconfigPaths(), nodeExternals(), viteCommonjs()],
plugins: [
tsconfigPaths(),
nodeExternals(),
viteCommonjs(),
viteStaticCopy({
targets: [{ src: 'src/graphql/schema/types', dest: '' }],
}),
],
define: {
'process.env': 'process.env',
},
build: {
sourcemap: true,
outDir: 'dist',
rollupOptions: {
input: {
@@ -19,7 +31,7 @@ export default defineConfig(() => {
},
modulePreload: false,
minify: false,
target: 'esnext',
target: 'node20',
},
test: {
globals: true,
@@ -34,4 +46,3 @@ export default defineConfig(() => {
},
};
});