feat: almost working

This commit is contained in:
Eli Bosley
2024-10-18 12:50:55 -04:00
parent 9d3397a687
commit 0c79995107
26 changed files with 1008 additions and 239 deletions
+659 -17
View File
@@ -63,7 +63,7 @@
"ini": "^4.1.2",
"ip": "^2.0.1",
"jose": "^5.3.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"multi-ini": "^2.2.0",
"mustache": "^4.2.0",
"nanobus": "^4.5.0",
@@ -73,7 +73,6 @@
"node-cache": "^5.1.2",
"node-window-polyfill": "^1.0.2",
"openid-client": "^5.6.5",
"p-iteration": "^1.1.8",
"p-retry": "^4.6.2",
"passport-custom": "^1.1.1",
"passport-http-header-strategy": "^1.1.0",
@@ -87,6 +86,7 @@
"stoppable": "^1.1.0",
"systeminformation": "^5.22.9",
"ts-command-line-args": "^2.5.1",
"tsx": "^4.19.1",
"uuid": "^10.0.0",
"ws": "^8.17.0",
"wtfnode": "^0.9.2",
@@ -120,6 +120,7 @@
"@types/graphql-fields": "^1.3.9",
"@types/graphql-type-uuid": "^0.2.6",
"@types/ini": "^4.1.0",
"@types/ip": "^1.1.3",
"@types/lodash": "^4.17.1",
"@types/mustache": "^4.2.5",
"@types/node": "^20.12.12",
@@ -2023,6 +2024,21 @@
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.19.11",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz",
@@ -5325,6 +5341,15 @@
"integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==",
"dev": true
},
"node_modules/@types/ip": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz",
"integrity": "sha512-64waoJgkXFTYnCYDUWgSATJ/dXEBanVkaP5d4Sbk7P6U7cTTMhxVyROTckc6JKdwCrgnAjZMn0k3177aQxtDEA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.5",
"dev": true,
@@ -10883,9 +10908,12 @@
}
},
"node_modules/get-tsconfig": {
"version": "4.5.0",
"dev": true,
"license": "MIT",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
"integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
@@ -12719,6 +12747,11 @@
"version": "4.17.21",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"license": "MIT"
@@ -14241,13 +14274,6 @@
"node": ">=8"
}
},
"node_modules/p-iteration": {
"version": "1.1.8",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"dev": true,
@@ -15853,6 +15879,14 @@
"node": ">=8"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/response-iterator": {
"version": "0.2.6",
"license": "MIT",
@@ -17926,6 +17960,407 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/tsx": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz",
"integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==",
"dependencies": {
"esbuild": "~0.23.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
"cpu": [
"loong64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
"cpu": [
"mips64el"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/esbuild": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.23.1",
"@esbuild/android-arm": "0.23.1",
"@esbuild/android-arm64": "0.23.1",
"@esbuild/android-x64": "0.23.1",
"@esbuild/darwin-arm64": "0.23.1",
"@esbuild/darwin-x64": "0.23.1",
"@esbuild/freebsd-arm64": "0.23.1",
"@esbuild/freebsd-x64": "0.23.1",
"@esbuild/linux-arm": "0.23.1",
"@esbuild/linux-arm64": "0.23.1",
"@esbuild/linux-ia32": "0.23.1",
"@esbuild/linux-loong64": "0.23.1",
"@esbuild/linux-mips64el": "0.23.1",
"@esbuild/linux-ppc64": "0.23.1",
"@esbuild/linux-riscv64": "0.23.1",
"@esbuild/linux-s390x": "0.23.1",
"@esbuild/linux-x64": "0.23.1",
"@esbuild/netbsd-x64": "0.23.1",
"@esbuild/openbsd-arm64": "0.23.1",
"@esbuild/openbsd-x64": "0.23.1",
"@esbuild/sunos-x64": "0.23.1",
"@esbuild/win32-arm64": "0.23.1",
"@esbuild/win32-ia32": "0.23.1",
"@esbuild/win32-x64": "0.23.1"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"license": "Apache-2.0",
@@ -20736,6 +21171,12 @@
"dev": true,
"optional": true
},
"@esbuild/openbsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.19.11",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz",
@@ -23051,6 +23492,15 @@
"integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==",
"dev": true
},
"@types/ip": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz",
"integrity": "sha512-64waoJgkXFTYnCYDUWgSATJ/dXEBanVkaP5d4Sbk7P6U7cTTMhxVyROTckc6JKdwCrgnAjZMn0k3177aQxtDEA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/js-yaml": {
"version": "4.0.5",
"dev": true
@@ -26842,8 +27292,12 @@
}
},
"get-tsconfig": {
"version": "4.5.0",
"dev": true
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
"integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
"requires": {
"resolve-pkg-maps": "^1.0.0"
}
},
"getpass": {
"version": "0.1.7",
@@ -28047,6 +28501,11 @@
"lodash": {
"version": "4.17.21"
},
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash.camelcase": {
"version": "4.3.0"
},
@@ -29060,9 +29519,6 @@
"version": "3.0.0",
"dev": true
},
"p-iteration": {
"version": "1.1.8"
},
"p-limit": {
"version": "3.1.0",
"dev": true,
@@ -30113,6 +30569,11 @@
"global-dirs": "^0.1.1"
}
},
"resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="
},
"response-iterator": {
"version": "0.2.6"
},
@@ -31505,6 +31966,187 @@
}
}
},
"tsx": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz",
"integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==",
"requires": {
"esbuild": "~0.23.0",
"fsevents": "~2.3.3",
"get-tsconfig": "^4.7.5"
},
"dependencies": {
"@esbuild/aix-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
"optional": true
},
"@esbuild/android-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
"optional": true
},
"@esbuild/android-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
"optional": true
},
"esbuild": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
"requires": {
"@esbuild/aix-ppc64": "0.23.1",
"@esbuild/android-arm": "0.23.1",
"@esbuild/android-arm64": "0.23.1",
"@esbuild/android-x64": "0.23.1",
"@esbuild/darwin-arm64": "0.23.1",
"@esbuild/darwin-x64": "0.23.1",
"@esbuild/freebsd-arm64": "0.23.1",
"@esbuild/freebsd-x64": "0.23.1",
"@esbuild/linux-arm": "0.23.1",
"@esbuild/linux-arm64": "0.23.1",
"@esbuild/linux-ia32": "0.23.1",
"@esbuild/linux-loong64": "0.23.1",
"@esbuild/linux-mips64el": "0.23.1",
"@esbuild/linux-ppc64": "0.23.1",
"@esbuild/linux-riscv64": "0.23.1",
"@esbuild/linux-s390x": "0.23.1",
"@esbuild/linux-x64": "0.23.1",
"@esbuild/netbsd-x64": "0.23.1",
"@esbuild/openbsd-arm64": "0.23.1",
"@esbuild/openbsd-x64": "0.23.1",
"@esbuild/sunos-x64": "0.23.1",
"@esbuild/win32-arm64": "0.23.1",
"@esbuild/win32-ia32": "0.23.1",
"@esbuild/win32-x64": "0.23.1"
}
}
}
},
"tunnel-agent": {
"version": "0.6.0",
"requires": {
+4 -2
View File
@@ -43,6 +43,7 @@
"install:unraid": "./scripts/install-in-unraid.sh",
"start:plugin": "INTROSPECTION=true LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty LOG_LEVEL=trace unraid-api start --debug",
"start:plugin-verbose": "LOG_CONTEXT=true LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty LOG_LEVEL=trace unraid-api start --debug",
"start:tsx": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty LOG_LEVEL=trace DOTENV_CONFIG_PATH=./.env.staging tsx -r dotenv/config src/cli.ts start --debug",
"start:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development tsup --config ./tsup.config.ts --watch --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs start --debug'",
"restart:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development tsup --config ./tsup.config.ts --watch --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs restart --debug'",
"stop:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development tsup --config ./tsup.config.ts --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs stop --debug'",
@@ -114,7 +115,7 @@
"ini": "^4.1.2",
"ip": "^2.0.1",
"jose": "^5.3.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"multi-ini": "^2.2.0",
"mustache": "^4.2.0",
"nanobus": "^4.5.0",
@@ -124,7 +125,6 @@
"node-cache": "^5.1.2",
"node-window-polyfill": "^1.0.2",
"openid-client": "^5.6.5",
"p-iteration": "^1.1.8",
"p-retry": "^4.6.2",
"passport-custom": "^1.1.1",
"passport-http-header-strategy": "^1.1.0",
@@ -138,6 +138,7 @@
"stoppable": "^1.1.0",
"systeminformation": "^5.22.9",
"ts-command-line-args": "^2.5.1",
"tsx": "^4.19.1",
"uuid": "^10.0.0",
"ws": "^8.17.0",
"wtfnode": "^0.9.2",
@@ -168,6 +169,7 @@
"@types/graphql-fields": "^1.3.9",
"@types/graphql-type-uuid": "^0.2.6",
"@types/ini": "^4.1.0",
"@types/ip": "^1.1.3",
"@types/lodash": "^4.17.1",
"@types/mustache": "^4.2.5",
"@types/node": "^20.12.12",
@@ -0,0 +1,78 @@
import { expect, test, vi } from 'vitest';
import { stop } from '@app/cli/commands/stop';
import { spawn } from 'node:child_process';
import { cliLogger } from '@app/core/log';
import { sleep } from '@app/core/utils/misc/sleep';
import { getAllUnraidApiPids } from '@app/cli/get-unraid-api-pid';
import path from 'node:path';
const spawnUnraidApiTestDaemon = (difficulty: 'easy' | 'hard') => {
const pathToJsFile = difficulty === 'easy' ? '../../setup/child-process-easy-to-kill.js' : '../../setup/child-process-hard-to-kill.js';
// Spawn child
console.log('Spawning child process at', path.join(import.meta.dirname, pathToJsFile));
const child = spawn('node', [path.join(import.meta.dirname, pathToJsFile)], {
// In the parent set the tracking environment variable
env: Object.assign(process.env, { _DAEMONIZE_PROCESS: '1' }),
stdio: 'ignore',
detached: true,
});
// Convert process into daemon
child.unref();
cliLogger.debug('Daemonized successfully!');
};
test('It stops successfully (easy)', async () => {
spawnUnraidApiTestDaemon('easy');
spawnUnraidApiTestDaemon('easy');
await sleep(100);
const { cliLogger } = await import('@app/core/log');
const loggerSpy = vi.spyOn(cliLogger, 'info');
const pids = await getAllUnraidApiPids();
expect(pids.length).toBe(2);
await stop();
const pids2 = await getAllUnraidApiPids();
expect(pids2.length).toBe(0);
expect(loggerSpy).toHaveBeenNthCalledWith(1,
'Stopping %s unraid-api process(es)...',
2,
);
});
test('It stops successfully (easy and hard)', async () => {
spawnUnraidApiTestDaemon('easy');
spawnUnraidApiTestDaemon('easy');
spawnUnraidApiTestDaemon('hard');
spawnUnraidApiTestDaemon('hard');
await sleep(100);
const { cliLogger } = await import('@app/core/log');
const loggerSpy = vi.spyOn(cliLogger, 'info');
const pids = await getAllUnraidApiPids();
expect(pids.length).toBe(4);
await stop();
const pids2 = await getAllUnraidApiPids();
expect(pids2.length).toBe(0);
expect(loggerSpy).toHaveBeenNthCalledWith(1,
'Stopping %s unraid-api process(es)...',
4,
);
expect(loggerSpy).toHaveBeenNthCalledWith(2,
'Stopping %s unraid-api process(es)...',
2,
);
expect(loggerSpy).toHaveBeenNthCalledWith(3,
'Stopping %s unraid-api process(es)...',
2,
);
expect(loggerSpy).toHaveBeenNthCalledWith(4, 'Process did not exit cleanly, forcing shutdown', expect.any(Error));
}, 15_000);
@@ -3,7 +3,7 @@ import 'reflect-metadata';
import { test, expect } from 'vitest';
import { getWriteableConfig } from '@app/core/utils/files/config-file-normalizer';
import { initialState } from '@app/store/modules/config';
import { cloneDeep } from 'lodash';
import { cloneDeep } from 'lodash-es';
test('it creates a FLASH config with NO OPTIONAL values', () => {
const basicConfig = initialState;
+62 -62
View File
@@ -3,7 +3,7 @@ import { addExitCallback } from 'catch-exit';
import { cliLogger } from '@app/core/log';
import { mainOptions } from '@app/cli/options';
import { logToSyslog } from '@app/cli/log-to-syslog';
import { getters } from '@app/store';
import { getters } from '@app/store/index';
import { getAllUnraidApiPids } from '@app/cli/get-unraid-api-pid';
import { API_VERSION } from '@app/environment';
@@ -11,78 +11,78 @@ import { API_VERSION } from '@app/environment';
* Start a new API process.
*/
export const start = async () => {
// Set process title
// Set process title
process.title = 'unraid-api';
const runningProcesses = await getAllUnraidApiPids();
if (runningProcesses.length > 0) {
cliLogger.info('unraid-api is Already Running!');
cliLogger.info('Run "unraid-api restart" to stop all running processes and restart');
process.exit(1);
}
process.title = 'unraid-api';
const runningProcesses = await getAllUnraidApiPids();
if (runningProcesses.length > 0) {
cliLogger.info('unraid-api is Already Running!');
cliLogger.info('Run "unraid-api restart" to stop all running processes and restart');
process.exit(1);
}
// Start API
cliLogger.info('Starting unraid-api@v%s', API_VERSION);
// Start API
cliLogger.info('Starting unraid-api@v%s', API_VERSION);
// If we're in debug mode or we're NOT
// in debug but ARE in the child process
if (mainOptions.debug || process.env._DAEMONIZE_PROCESS) {
// Log when the API exits
addExitCallback((signal, exitCode, error) => {
if (exitCode === 0 || exitCode === 130 || signal === 'SIGTERM') {
logToSyslog('👋 Farewell. UNRAID API shutting down!');
return;
}
// If we're in debug mode or we're NOT
console.log(mainOptions);
// in debug but ARE in the child process
if (mainOptions.debug || process.env._DAEMONIZE_PROCESS) {
// Log when the API exits
addExitCallback((signal, exitCode, error) => {
if (exitCode === 0 || exitCode === 130 || signal === 'SIGTERM') {
logToSyslog('👋 Farewell. UNRAID API shutting down!');
return;
}
// Log when the API crashes
if (signal === 'uncaughtException' && error) {
logToSyslog(`⚠️ Caught exception: ${error.message}`);
}
// Log when the API crashes
if (signal === 'uncaughtException' && error) {
logToSyslog(`⚠️ Caught exception: ${error.message}`);
}
// Log when we crash
if (exitCode) {
logToSyslog(`⚠️ UNRAID API crashed with exit code ${exitCode}`);
return;
}
// Log when we crash
if (exitCode) {
logToSyslog(`⚠️ UNRAID API crashed with exit code ${exitCode}`);
return;
}
logToSyslog('🛑 UNRAID API crashed without an exit code?');
});
logToSyslog('🛑 UNRAID API crashed without an exit code?');
});
logToSyslog('✔️ UNRAID API started successfully!');
}
logToSyslog('✔️ UNRAID API started successfully!');
}
// Load bundled index file
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../../index');
console.log(mainOptions);
if (!mainOptions.debug) {
if ('_DAEMONIZE_PROCESS' in process.env) {
// In the child, clean up the tracking environment variable
delete process.env._DAEMONIZE_PROCESS;
} else {
cliLogger.debug('Daemonizing process. %s %o', process.execPath, process.argv);
await import ('@app/index.ts');
if (!mainOptions.debug) {
if ('_DAEMONIZE_PROCESS' in process.env) {
// In the child, clean up the tracking environment variable
delete process.env._DAEMONIZE_PROCESS;
} else {
cliLogger.debug('Daemonizing process. %s %o', process.execPath, process.argv);
// Spawn child
// First arg is path (inside PKG), second arg is restart, stop, etc, rest is args to main argument
const [path, , ...rest] = process.argv.slice(1);
const replacedCommand = [path, 'start', ...rest];
const child = spawn(process.execPath, replacedCommand, {
// In the parent set the tracking environment variable
env: Object.assign(process.env, { _DAEMONIZE_PROCESS: '1' }),
// The process MUST have it's cwd set to the
// path where it resides within the Nexe VFS
cwd: getters.paths()['unraid-api-base'],
stdio: 'ignore',
detached: true,
});
// Spawn child
// First arg is path (inside PKG), second arg is restart, stop, etc, rest is args to main argument
const [path, , ...rest] = process.argv.slice(1);
const replacedCommand = [path, 'start', ...rest];
const child = spawn(process.execPath, replacedCommand, {
// In the parent set the tracking environment variable
env: Object.assign(process.env, { _DAEMONIZE_PROCESS: '1' }),
// The process MUST have it's cwd set to the
// path where it resides within the Nexe VFS
cwd: getters.paths()['unraid-api-base'],
stdio: 'ignore',
detached: true,
});
// Convert process into daemon
child.unref();
// Convert process into daemon
child.unref();
cliLogger.debug('Daemonized successfully!');
cliLogger.debug('Daemonized successfully!');
// Exit cleanly
process.exit(0);
}
}
// Exit cleanly
process.exit(0);
}
}
};
+1 -1
View File
@@ -1,5 +1,5 @@
import { getters, type RootState, store } from '@app/store';
import { uniq } from 'lodash';
import uniq from 'lodash/uniq';
import {
getServerIps,
getUrlForField,
+3 -3
View File
@@ -4,7 +4,6 @@ import {
blockDevices,
diskLayout,
} from 'systeminformation';
import { map as asyncMap } from 'p-iteration';
import {
type Disk,
DiskInterfaceType,
@@ -91,8 +90,9 @@ export const getDisks = async (
const partitions = await blockDevices().then((devices) =>
devices.filter((device) => device.type === 'part')
);
const disks = await asyncMap(await diskLayout(), async (disk) =>
parseDisk(disk, partitions)
const diskLayoutData = await diskLayout();
const disks = await Promise.all(
diskLayoutData.map((disk) => parseDisk(disk, partitions))
);
return disks;
@@ -9,7 +9,7 @@ import type {
MyServersConfig,
MyServersConfigMemory,
} from '@app/types/my-servers-config';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
export type ConfigType = 'flash' | 'memory';
type ConfigObject<T> = T extends 'flash'
+18 -11
View File
@@ -40,16 +40,23 @@ export const phpLoader = async (options: Options) => {
encodeParameters(body),
];
return execa('php', options_, { cwd: __dirname })
.then(({ stdout }) => {
// Missing php file
if (stdout.includes(`Warning: include(${file}): failed to open stream: No such file or directory in ${path.join(__dirname, '/wrapper.php')}`)) {
throw new FileMissingError(file);
}
return execa('php', options_, { cwd: import.meta.dirname })
.then(({ stdout }) => {
// Missing php file
if (
stdout.includes(
`Warning: include(${file}): failed to open stream: No such file or directory in ${path.join(
import.meta.dirname,
'/wrapper.php'
)}`
)
) {
throw new FileMissingError(file);
}
return stdout;
})
.catch(error => {
throw new PhpError(error);
});
return stdout;
})
.catch((error) => {
throw new PhpError(error);
});
};
+9 -6
View File
@@ -1,5 +1,4 @@
import { execa } from 'execa';
import { map as asyncMap } from 'p-iteration';
import { sync as commandExistsSync } from 'command-exists';
interface Device {
@@ -13,9 +12,9 @@ interface Device {
* @param devices Devices to be checked.
* @returns Processed devices.
*/
export const filterDevices = async (devices: Device[]): Promise<Device[]> => asyncMap(devices, async (device: Device) => {
export const filterDevices = async (devices: Device[]): Promise<Device[]> => {
// Don't run if we don't have the udevadm command available
if (!commandExistsSync('udevadm')) return device;
if (!commandExistsSync('udevadm')) return devices;
const networkDeviceIds = await execa('udevadm', 'info -q path -p /sys/class/net/eth0'.split(' '))
.then(({ stdout }) => {
@@ -25,7 +24,11 @@ export const filterDevices = async (devices: Device[]): Promise<Device[]> => asy
.catch(() => []);
const allowed = new Set(networkDeviceIds);
device.allowed = allowed.has(device.id);
return device;
});
const processedDevices = devices.map((device: Device) => {
device.allowed = allowed.has(device.id);
return device;
});
return processedDevices;
};
+1 -1
View File
@@ -3,7 +3,7 @@ import {
HttpLink,
InMemoryCache,
split,
} from '@apollo/client/core/core.cjs';
} from '@apollo/client/core';
import { onError } from '@apollo/client/link/error';
import { getInternalApiAddress } from '@app/consts';
import WebSocket from 'ws';
@@ -0,0 +1,65 @@
/*!
* Copyright 2022 Lime Technology Inc. All rights reserved.
* Written by: Alexis Tyler
*/
import { MOTHERSHIP_GRAPHQL_LINK } from '@app/consts';
import { store } from '@app/store';
import { getDnsCache } from '@app/store/getters';
import { setDNSCheck } from '@app/store/modules/cache';
import { lookup as lookupDNS, resolve as resolveDNS } from 'dns';
import ip from 'ip';
import { promisify } from 'util';
const msHostname = new URL(MOTHERSHIP_GRAPHQL_LINK).host;
/**
* Check if the local and network resolvers are able to see mothership
*
* See: https://nodejs.org/docs/latest/api/dns.html#dns_implementation_considerations
*/
export const checkDNS = async (hostname = msHostname): Promise<{ cloudIp: string }> => {
const dnsCachedResuslt = getDnsCache();
if (dnsCachedResuslt) {
if (dnsCachedResuslt.cloudIp) {
return { cloudIp: dnsCachedResuslt.cloudIp };
}
if (dnsCachedResuslt.error) {
throw dnsCachedResuslt.error;
}
}
let local: string | null = null;
let network: string | null = null;
try {
// Check the local resolver like "ping" does
// Check the DNS server the server has set - does a DNS query on the network
const [localRes, networkRes] = await Promise.all([
promisify(lookupDNS)(hostname).then(({ address }) => address),
promisify(resolveDNS)(hostname).then(([address]) => address),
]);
local = localRes;
network = networkRes;
// The user's server and the DNS server they're using are returning different results
if (!local.includes(network)) throw new Error(`Local and network resolvers showing different IP for "${hostname}". [local="${local ?? 'NOT FOUND'}"] [network="${network ?? 'NOT FOUND'}"]`);
// The user likely has a PI-hole or something similar running.
if (ip.isPrivate(local)) throw new Error(`"${hostname}" is being resolved to a private IP. [IP=${local ?? 'NOT FOUND'}]`);
} catch (error: unknown) {
if (!(error instanceof Error)) {
throw error;
}
store.dispatch(setDNSCheck({ cloudIp: null, error }));
}
if (typeof local === 'string' || typeof network === 'string') {
const validIp: string = local ?? network ?? '';
store.dispatch(setDNSCheck({ cloudIp: validIp, error: null }));
return { cloudIp: validIp };
}
return { cloudIp: '' };
};
+48 -87
View File
@@ -1,11 +1,4 @@
import {
cpu,
cpuFlags,
mem,
memLayout,
osInfo,
versions,
} from 'systeminformation';
import { cpu, cpuFlags, mem, memLayout, osInfo, versions } from 'systeminformation';
import { docker } from '@app/core/utils/clients/docker';
import {
type InfoApps,
@@ -30,7 +23,6 @@ import { AppError } from '@app/core/errors/app-error';
import { cleanStdout } from '@app/core/utils/misc/clean-stdout';
import { execaCommandSync, execa } from 'execa';
import { pathExists } from 'path-exists';
import { filter as asyncFilter } from 'p-iteration';
import { isSymlink } from 'path-type';
import type { PciDevice } from '@app/core/types';
import { vmRegExps } from '@app/core/utils/vms/domain/vm-regexps';
@@ -63,8 +55,7 @@ export const generateOs = async (): Promise<InfoOs> => {
};
export const generateCpu = async (): Promise<InfoCpu> => {
const { cores, physicalCores, speedMin, speedMax, stepping, ...rest } =
await cpu();
const { cores, physicalCores, speedMin, speedMax, stepping, ...rest } = await cpu();
const flags = await cpuFlags()
.then((flags) => flags.split(' '))
.catch(() => []);
@@ -118,9 +109,9 @@ export const generateVersions = async (): Promise<Versions> => {
};
export const generateMemory = async (): Promise<InfoMemory> => {
const layout = await memLayout().then((dims) =>
dims.map((dim) => dim as MemoryLayout)
).catch(() => []);
const layout = await memLayout()
.then((dims) => dims.map((dim) => dim as MemoryLayout))
.catch(() => []);
const info = await mem();
let max = info.total;
@@ -136,14 +127,10 @@ export const generateMemory = async (): Promise<InfoMemory> => {
throw error;
});
const lines = memoryInfo.split('\n');
const header = lines.find((line) =>
line.startsWith('Physical Memory Array')
);
const header = lines.find((line) => line.startsWith('Physical Memory Array'));
if (header) {
const start = lines.indexOf(header);
const nextHeaders = lines
.slice(start, -1)
.find((line) => line.startsWith('Handle '));
const nextHeaders = lines.slice(start, -1).find((line) => line.startsWith('Handle '));
if (nextHeaders) {
const end = lines.indexOf(nextHeaders);
@@ -151,9 +138,7 @@ export const generateMemory = async (): Promise<InfoMemory> => {
max = toBytes(
fields
?.find((line) =>
line.trim().startsWith('Maximum Capacity')
)
?.find((line) => line.trim().startsWith('Maximum Capacity'))
?.trim()
?.split(': ')[1] ?? '0'
);
@@ -215,11 +200,13 @@ export const generateDevices = async (): Promise<Devices> => {
const basePath = '/sys/bus/pci/devices/0000:';
// Remove devices with no IOMMU support
const filteredDevices = await asyncFilter(
devices,
async (device: Readonly<PciDevice>) =>
pathExists(`${basePath}${device.id}/iommu_group/`)
);
const filteredDevices = await Promise.all(
devices.map((device: Readonly<PciDevice>) =>
pathExists(`${basePath}${device.id}/iommu_group/`).then((exists) =>
exists ? device : null
)
)
).then((devices) => devices.filter((device) => device !== null));
/**
* Run device cleanup
@@ -230,36 +217,29 @@ export const generateDevices = async (): Promise<Devices> => {
* - Add whether kernel-bound driver exists
* - Cleanup device vendor/product names
*/
const processedDevices = await filterDevices(filteredDevices).then(
async (devices) =>
Promise.all(
devices
// @ts-expect-error - Device is not PciDevice
.map((device) => addDeviceClass(device))
.map(async (device) => {
// Attempt to get the current kernel-bound driver for this pci device
await isSymlink(
`${basePath}${device.id}/driver`
).then((symlink) => {
if (symlink) {
// $strLink = @readlink('/sys/bus/pci/devices/0000:'.$arrMatch['id']. '/driver');
// if (!empty($strLink)) {
// $strDriver = basename($strLink);
// }
}
});
const processedDevices = await filterDevices(filteredDevices).then(async (devices) =>
Promise.all(
devices
// @ts-expect-error - Device is not PciDevice
.map((device) => addDeviceClass(device))
.map(async (device) => {
// Attempt to get the current kernel-bound driver for this pci device
await isSymlink(`${basePath}${device.id}/driver`).then((symlink) => {
if (symlink) {
// $strLink = @readlink('/sys/bus/pci/devices/0000:'.$arrMatch['id']. '/driver');
// if (!empty($strLink)) {
// $strDriver = basename($strLink);
// }
}
});
// Clean up the vendor and product name
device.vendorname = sanitizeVendor(
device.vendorname
);
device.productname = sanitizeProduct(
device.productname
);
// Clean up the vendor and product name
device.vendorname = sanitizeVendor(device.vendorname);
device.productname = sanitizeProduct(device.productname);
return device;
})
)
return device;
})
)
);
return processedDevices;
@@ -298,10 +278,7 @@ export const generateDevices = async (): Promise<Devices> => {
const getSystemUSBDevices = async () => {
try {
// Get a list of all usb hubs so we can filter the allowed/disallowed
const usbHubs = await execa(
'cat /sys/bus/usb/drivers/hub/*/modalias',
{ shell: true }
)
const usbHubs = await execa('cat /sys/bus/usb/drivers/hub/*/modalias', { shell: true })
.then(({ stdout }) =>
stdout.split('\n').map((line) => {
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
@@ -318,8 +295,7 @@ export const generateDevices = async (): Promise<Devices> => {
emhttp.var.flashGuid !== device.guid;
// Remove usb hubs
const filterUsbHubs = (device: Readonly<PciDevice>): boolean =>
!usbHubs.includes(device.id);
const filterUsbHubs = (device: Readonly<PciDevice>): boolean => !usbHubs.includes(device.id);
// Clean up the name
const sanitizeVendorName = (device: Readonly<PciDevice>) => {
@@ -330,9 +306,7 @@ export const generateDevices = async (): Promise<Devices> => {
};
};
const parseDeviceLine = (
line: Readonly<string>
): { value: string; string: string } => {
const parseDeviceLine = (line: Readonly<string>): { value: string; string: string } => {
const emptyLine = { value: '', string: '' };
// If the line is blank return nothing
@@ -361,31 +335,19 @@ export const generateDevices = async (): Promise<Devices> => {
const modifiedDevice: PciDevice = {
...device,
};
const info = execaCommandSync(
`lsusb -d ${device.id} -v`
).stdout.split('\n');
const info = execaCommandSync(`lsusb -d ${device.id} -v`).stdout.split('\n');
const deviceName = device.name.trim();
const iSerial = parseDeviceLine(
info.filter((line) => line.includes('iSerial'))[0]
);
const iProduct = parseDeviceLine(
info.filter((line) => line.includes('iProduct'))[0]
);
const iSerial = parseDeviceLine(info.filter((line) => line.includes('iSerial'))[0]);
const iProduct = parseDeviceLine(info.filter((line) => line.includes('iProduct'))[0]);
const iManufacturer = parseDeviceLine(
info.filter((line) => line.includes('iManufacturer'))[0]
);
const idProduct = parseDeviceLine(
info.filter((line) => line.includes('idProduct'))[0]
);
const idVendor = parseDeviceLine(
info.filter((line) => line.includes('idVendor'))[0]
);
const serial = `${iSerial.string
const idProduct = parseDeviceLine(info.filter((line) => line.includes('idProduct'))[0]);
const idVendor = parseDeviceLine(info.filter((line) => line.includes('idVendor'))[0]);
const serial = `${iSerial.string.slice(8).slice(0, 4)}-${iSerial.string
.slice(8)
.slice(0, 4)}-${iSerial.string.slice(8).slice(4)}`;
const guid = `${idVendor.value.slice(
2
)}-${idProduct.value.slice(2)}-${serial}`;
.slice(4)}`;
const guid = `${idVendor.value.slice(2)}-${idProduct.value.slice(2)}-${serial}`;
modifiedDevice.serial = iSerial.string;
modifiedDevice.product = iProduct.string;
@@ -394,8 +356,7 @@ export const generateDevices = async (): Promise<Devices> => {
// Set name if missing
if (deviceName === '') {
modifiedDevice.name =
`${iProduct.string} ${iManufacturer.string}`.trim();
modifiedDevice.name = `${iProduct.string} ${iManufacturer.string}`.trim();
}
// Name still blank? Replace using fallback default
+3 -1
View File
@@ -2,6 +2,8 @@ import { join } from 'path';
import { loadFilesSync } from '@graphql-tools/load-files';
import { mergeTypeDefs } from '@graphql-tools/merge';
const files = loadFilesSync(join(__dirname, '../src/graphql/schema/types'), { extensions: ['graphql'] });
const files = loadFilesSync(join(import.meta.dirname, '../src/graphql/schema/types'), {
extensions: ['graphql'],
});
export const typeDefs = mergeTypeDefs(files);
+39 -2
View File
@@ -1,9 +1,9 @@
import { hasSubscribedToChannel } from '@app/ws';
import { type User } from '@app/core/types/states/user';
import { AppError } from '@app/core/errors/app-error';
import { ensurePermission } from '@app/core/utils/permissions/ensure-permission';
import { pubsub } from '@app/core/pubsub';
import { store } from '@app/store';
import { graphqlLogger } from '@app/core/log';
import {
ServerStatus,
type Server,
@@ -15,6 +15,43 @@ export interface Context {
websocketId: string;
}
type Subscription = {
total: number;
channels: string[];
};
const subscriptions: Record<string, Subscription> = {};
/**
* Return current ws connection count.
*/
export const getWsConnectionCount = () => Object.values(subscriptions).filter(subscription => subscription.total >= 1).length;
/**
* Return current ws connection count in channel.
*/
export const getWsConnectionCountInChannel = (channel: string) => Object.values(subscriptions).filter(subscription => subscription.channels.includes(channel)).length;
export const hasSubscribedToChannel = (id: string, channel: string) => {
graphqlLogger.debug('Subscribing to %s', channel);
// Setup initial object
if (subscriptions[id] === undefined) {
subscriptions[id] = {
total: 1,
channels: [channel],
};
return;
}
subscriptions[id].total++;
subscriptions[id].channels.push(channel);
};
/**
* Create a pubsub subscription.
* @param channel The pubsub channel to subscribe to.
@@ -33,7 +70,7 @@ export const createSubscription = (channel: string, resource?: string) => ({
possession: 'any',
});
hasSubscribedToChannel(context.websocketId, channel);
hasSubscribedToChannel(context.websocketId, channel);
return pubsub.asyncIterator(channel);
},
});
+3 -1
View File
@@ -28,6 +28,7 @@ import { bootstrapNestServer } from '@app/unraid-api/main';
import { type NestFastifyApplication } from '@nestjs/platform-fastify';
import { type RawServerDefault } from 'fastify';
import { setupLogRotation } from '@app/core/logrotate/setup-logrotate';
import { WebSocket } from 'ws';
import * as env from '@app/environment';
let server: NestFastifyApplication<RawServerDefault>;
@@ -46,7 +47,8 @@ void am(
const cacheable = new CacheableLookup();
Object.assign(global, { WebSocket: require('ws') });
Object.assign(global, { WebSocket });
// Ensure all DNS lookups are cached for their TTL
cacheable.install(http.globalAgent);
cacheable.install(https.globalAgent);
@@ -5,7 +5,7 @@ import { startAppListening } from '@app/store/listeners/listener-middleware';
import { loadSingleStateFile } from '@app/store/modules/emhttp';
import { StateFileKey } from '@app/store/types';
import { isAnyOf } from '@reduxjs/toolkit';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
export const enableArrayEventListener = () =>
startAppListening({
+1 -1
View File
@@ -1,6 +1,6 @@
import { startAppListening } from '@app/store/listeners/listener-middleware';
import { getDiff } from 'json-difference';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { logger } from '@app/core/log';
import {
type ConfigType,
+1 -1
View File
@@ -22,7 +22,7 @@ import { getWriteableConfig } from '@app/core/utils/files/config-file-normalizer
import { writeFileSync } from 'fs';
import { safelySerializeObjectToIni } from '@app/core/utils/files/safe-ini-serializer';
import { PUBSUB_CHANNEL, pubsub } from '@app/core/pubsub';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { setupRemoteAccessThunk } from '@app/store/actions/setup-remote-access';
export type SliceState = {
+1 -1
View File
@@ -2,7 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
import { join, resolve as resolvePath } from 'path';
const initialState = {
core: __dirname,
core: import.meta.dirname,
'unraid-api-base': '/usr/local/bin/unraid-api/' as const,
'unraid-data': resolvePath(
process.env.PATHS_UNRAID_DATA ?? ('/boot/config/plugins/dynamix.my.servers/data/' as const)
+1 -1
View File
@@ -6,7 +6,7 @@ import { syncInfoApps } from '@app/store/sync/info-apps-sync';
import { setupConfigPathWatch } from '@app/store/watch/config-watch';
import { NODE_ENV } from '@app/environment';
import { writeFileSync } from 'fs';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { join } from 'path';
export const startStoreSync = async () => {
+1 -1
View File
@@ -2,7 +2,7 @@ import { logger } from '@app/core/log';
import { PUBSUB_CHANNEL, pubsub } from '@app/core/pubsub';
import { store } from '@app/store';
import { DaemonConnectionStatus, type StoreSubscriptionHandler } from '@app/store/types';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
type InfoAppsEvent = {
info: {
+1 -1
View File
@@ -4,7 +4,7 @@ import { updateDockerState } from '@app/store/modules/docker';
import { getDockerContainers } from '@app/core/modules/index';
import { docker } from '@app/core/utils/index';
import DockerEE from 'docker-event-emitter';
import { debounce } from 'lodash';
import { debounce } from 'lodash-es';
const updateContainerCache = async () => {
try {
+3
View File
@@ -1,3 +1,6 @@
import path from 'path';
import { fileURLToPath } from 'url';
export function notNull<T>(value: T): value is NonNullable<T> {
return value !== null;
}
-34
View File
@@ -1,34 +0,0 @@
import { graphqlLogger } from '@app/core/log';
type Subscription = {
total: number;
channels: string[];
};
const subscriptions: Record<string, Subscription> = {};
/**
* Return current ws connection count.
*/
export const getWsConnectionCount = () => Object.values(subscriptions).filter(subscription => subscription.total >= 1).length;
/**
* Return current ws connection count in channel.
*/
export const getWsConnectionCountInChannel = (channel: string) => Object.values(subscriptions).filter(subscription => subscription.channels.includes(channel)).length;
export const hasSubscribedToChannel = (id: string, channel: string) => {
graphqlLogger.debug('Subscribing to %s', channel);
// Setup initial object
if (subscriptions[id] === undefined) {
subscriptions[id] = {
total: 1,
channels: [channel],
};
return;
}
subscriptions[id].total++;
subscriptions[id].channels.push(channel);
};
+3 -2
View File
@@ -14,11 +14,12 @@
"paths": {
"@app/*": [
"./src/*"
]
],
},
"skipLibCheck": true,
"target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"allowJs": false, /* Allow javascript files to be compiled. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */