fix: PXE boot debugging — bisect root cause, syslog logging, serial console #3
434
bastion/pnpm-lock.yaml
generated
434
bastion/pnpm-lock.yaml
generated
@@ -13,16 +13,16 @@ importers:
|
||||
version: 22.19.15
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^8.57.1
|
||||
version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)
|
||||
version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.57.1
|
||||
version: 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||
version: 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint:
|
||||
specifier: ^10.0.3
|
||||
version: 10.0.3
|
||||
version: 10.0.3(jiti@2.6.1)
|
||||
eslint-config-prettier:
|
||||
specifier: ^10.1.8
|
||||
version: 10.1.8(eslint@10.0.3)
|
||||
version: 10.1.8(eslint@10.0.3(jiti@2.6.1))
|
||||
rimraf:
|
||||
specifier: ^6.0.0
|
||||
version: 6.1.3
|
||||
@@ -34,7 +34,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(@types/node@22.19.15)(tsx@4.21.0)
|
||||
version: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0)
|
||||
|
||||
src/bastion:
|
||||
dependencies:
|
||||
@@ -74,6 +74,40 @@ importers:
|
||||
specifier: ^22.10.0
|
||||
version: 22.19.15
|
||||
|
||||
src/labd:
|
||||
dependencies:
|
||||
'@fastify/websocket':
|
||||
specifier: ^11.0.2
|
||||
version: 11.2.0
|
||||
'@lab/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
'@prisma/client':
|
||||
specifier: ^6.9.0
|
||||
version: 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)
|
||||
fastify:
|
||||
specifier: ^5.3.3
|
||||
version: 5.8.2
|
||||
winston:
|
||||
specifier: ^3.17.0
|
||||
version: 3.19.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.14.1
|
||||
version: 22.19.15
|
||||
prisma:
|
||||
specifier: ^6.9.0
|
||||
version: 6.19.2(typescript@5.9.3)
|
||||
rimraf:
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3
|
||||
tsx:
|
||||
specifier: ^4.21.0
|
||||
version: 4.21.0
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
|
||||
src/shared: {}
|
||||
|
||||
packages:
|
||||
@@ -298,6 +332,9 @@ packages:
|
||||
'@fastify/static@8.3.0':
|
||||
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
|
||||
|
||||
'@fastify/websocket@11.2.0':
|
||||
resolution: {integrity: sha512-3HrDPbAG1CzUCqnslgJxppvzaAZffieOVbLp1DAy1huCSynUWPifSvfdEDUR8HlJLp3sp1A36uOM2tJogADS8w==}
|
||||
|
||||
'@humanfs/core@0.19.1':
|
||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@@ -328,6 +365,36 @@ packages:
|
||||
'@pinojs/redact@0.4.0':
|
||||
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||
|
||||
'@prisma/client@6.19.2':
|
||||
resolution: {integrity: sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==}
|
||||
engines: {node: '>=18.18'}
|
||||
peerDependencies:
|
||||
prisma: '*'
|
||||
typescript: '>=5.1.0'
|
||||
peerDependenciesMeta:
|
||||
prisma:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@prisma/config@6.19.2':
|
||||
resolution: {integrity: sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==}
|
||||
|
||||
'@prisma/debug@6.19.2':
|
||||
resolution: {integrity: sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==}
|
||||
|
||||
'@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7':
|
||||
resolution: {integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==}
|
||||
|
||||
'@prisma/engines@6.19.2':
|
||||
resolution: {integrity: sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==}
|
||||
|
||||
'@prisma/fetch-engine@6.19.2':
|
||||
resolution: {integrity: sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==}
|
||||
|
||||
'@prisma/get-platform@6.19.2':
|
||||
resolution: {integrity: sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
|
||||
cpu: [arm]
|
||||
@@ -463,6 +530,9 @@ packages:
|
||||
'@so-ric/colorspace@1.1.6':
|
||||
resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
@@ -621,6 +691,14 @@ packages:
|
||||
resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
c12@3.1.0:
|
||||
resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
|
||||
peerDependencies:
|
||||
magicast: ^0.3.5
|
||||
peerDependenciesMeta:
|
||||
magicast:
|
||||
optional: true
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -633,6 +711,16 @@ packages:
|
||||
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
citty@0.1.6:
|
||||
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
|
||||
|
||||
citty@0.2.1:
|
||||
resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==}
|
||||
|
||||
color-convert@3.1.3:
|
||||
resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==}
|
||||
engines: {node: '>=14.6'}
|
||||
@@ -653,6 +741,13 @@ packages:
|
||||
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
confbox@0.2.4:
|
||||
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
|
||||
|
||||
consola@3.4.2:
|
||||
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
|
||||
engines: {node: ^14.18.0 || >=16.10.0}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -681,6 +776,13 @@ packages:
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
deepmerge-ts@7.1.5:
|
||||
resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -689,9 +791,29 @@ packages:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
destr@2.0.5:
|
||||
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
|
||||
|
||||
dotenv@16.6.1:
|
||||
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
duplexify@4.1.3:
|
||||
resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
|
||||
|
||||
effect@3.18.4:
|
||||
resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==}
|
||||
|
||||
empathic@2.0.0:
|
||||
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
enabled@2.0.0:
|
||||
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
@@ -766,6 +888,13 @@ packages:
|
||||
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
exsolve@1.0.8:
|
||||
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
|
||||
|
||||
fast-check@3.23.2:
|
||||
resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
fast-decode-uri-component@1.0.1:
|
||||
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
|
||||
|
||||
@@ -850,6 +979,10 @@ packages:
|
||||
get-tsconfig@4.13.6:
|
||||
resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
|
||||
|
||||
giget@2.0.0:
|
||||
resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==}
|
||||
hasBin: true
|
||||
|
||||
glob-parent@6.0.2:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -922,6 +1055,10 @@ packages:
|
||||
resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
js-tokens@9.0.1:
|
||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||
|
||||
@@ -995,14 +1132,28 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-fetch-native@1.6.7:
|
||||
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
|
||||
|
||||
npm-run-path@6.0.0:
|
||||
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
nypm@0.6.5:
|
||||
resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
ohash@2.0.11:
|
||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||
|
||||
on-exit-leak-free@2.1.2:
|
||||
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
one-time@1.0.0:
|
||||
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
||||
|
||||
@@ -1048,6 +1199,9 @@ packages:
|
||||
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
||||
engines: {node: '>= 14.16'}
|
||||
|
||||
perfect-debounce@1.0.0:
|
||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -1065,6 +1219,9 @@ packages:
|
||||
resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==}
|
||||
hasBin: true
|
||||
|
||||
pkg-types@2.3.0:
|
||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||
|
||||
postcss@8.5.8:
|
||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -1077,6 +1234,16 @@ packages:
|
||||
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
prisma@6.19.2:
|
||||
resolution: {integrity: sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==}
|
||||
engines: {node: '>=18.18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '>=5.1.0'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
process-warning@4.0.1:
|
||||
resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
|
||||
|
||||
@@ -1087,13 +1254,23 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
pure-rand@6.1.0:
|
||||
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
|
||||
|
||||
quick-format-unescaped@4.0.4:
|
||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||
|
||||
rc9@2.1.2:
|
||||
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
real-require@0.2.0:
|
||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
@@ -1190,6 +1367,9 @@ packages:
|
||||
std-env@3.10.0:
|
||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||
|
||||
stream-shift@1.0.3:
|
||||
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
@@ -1213,6 +1393,10 @@ packages:
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyexec@1.0.4:
|
||||
resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1369,6 +1553,21 @@ packages:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
ws@8.19.0:
|
||||
resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1465,9 +1664,9 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.27.4':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.3)':
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.3(jiti@2.6.1))':
|
||||
dependencies:
|
||||
eslint: 10.0.3
|
||||
eslint: 10.0.3(jiti@2.6.1)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/regexpp@4.12.2': {}
|
||||
@@ -1537,6 +1736,15 @@ snapshots:
|
||||
fastq: 1.20.1
|
||||
glob: 11.1.0
|
||||
|
||||
'@fastify/websocket@11.2.0':
|
||||
dependencies:
|
||||
duplexify: 4.1.3
|
||||
fastify-plugin: 5.1.0
|
||||
ws: 8.19.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
'@humanfs/node@0.16.7':
|
||||
@@ -1556,6 +1764,41 @@ snapshots:
|
||||
|
||||
'@pinojs/redact@0.4.0': {}
|
||||
|
||||
'@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)':
|
||||
optionalDependencies:
|
||||
prisma: 6.19.2(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
'@prisma/config@6.19.2':
|
||||
dependencies:
|
||||
c12: 3.1.0
|
||||
deepmerge-ts: 7.1.5
|
||||
effect: 3.18.4
|
||||
empathic: 2.0.0
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
'@prisma/debug@6.19.2': {}
|
||||
|
||||
'@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {}
|
||||
|
||||
'@prisma/engines@6.19.2':
|
||||
dependencies:
|
||||
'@prisma/debug': 6.19.2
|
||||
'@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7
|
||||
'@prisma/fetch-engine': 6.19.2
|
||||
'@prisma/get-platform': 6.19.2
|
||||
|
||||
'@prisma/fetch-engine@6.19.2':
|
||||
dependencies:
|
||||
'@prisma/debug': 6.19.2
|
||||
'@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7
|
||||
'@prisma/get-platform': 6.19.2
|
||||
|
||||
'@prisma/get-platform@6.19.2':
|
||||
dependencies:
|
||||
'@prisma/debug': 6.19.2
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
optional: true
|
||||
|
||||
@@ -1640,6 +1883,8 @@ snapshots:
|
||||
color: 5.0.3
|
||||
text-hex: 1.0.0
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
@@ -1659,15 +1904,15 @@ snapshots:
|
||||
|
||||
'@types/triple-beam@1.3.5': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)':
|
||||
'@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.57.1
|
||||
'@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||
'@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.57.1
|
||||
eslint: 10.0.3
|
||||
eslint: 10.0.3(jiti@2.6.1)
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||
@@ -1675,14 +1920,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.57.1(eslint@10.0.3)(typescript@5.9.3)':
|
||||
'@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.57.1
|
||||
'@typescript-eslint/types': 8.57.1
|
||||
'@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.57.1
|
||||
debug: 4.4.3
|
||||
eslint: 10.0.3
|
||||
eslint: 10.0.3(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -1705,13 +1950,13 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.57.1(eslint@10.0.3)(typescript@5.9.3)':
|
||||
'@typescript-eslint/type-utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.57.1
|
||||
'@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
|
||||
debug: 4.4.3
|
||||
eslint: 10.0.3
|
||||
eslint: 10.0.3(jiti@2.6.1)
|
||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -1734,13 +1979,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.57.1(eslint@10.0.3)(typescript@5.9.3)':
|
||||
'@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
|
||||
'@typescript-eslint/scope-manager': 8.57.1
|
||||
'@typescript-eslint/types': 8.57.1
|
||||
'@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
|
||||
eslint: 10.0.3
|
||||
eslint: 10.0.3(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -1758,13 +2003,13 @@ snapshots:
|
||||
chai: 5.3.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.15)(tsx@4.21.0))':
|
||||
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.3.1(@types/node@22.19.15)(tsx@4.21.0)
|
||||
vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
@@ -1835,6 +2080,21 @@ snapshots:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
c12@3.1.0:
|
||||
dependencies:
|
||||
chokidar: 4.0.3
|
||||
confbox: 0.2.4
|
||||
defu: 6.1.4
|
||||
dotenv: 16.6.1
|
||||
exsolve: 1.0.8
|
||||
giget: 2.0.0
|
||||
jiti: 2.6.1
|
||||
ohash: 2.0.11
|
||||
pathe: 2.0.3
|
||||
perfect-debounce: 1.0.0
|
||||
pkg-types: 2.3.0
|
||||
rc9: 2.1.2
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
chai@5.3.3:
|
||||
@@ -1847,6 +2107,16 @@ snapshots:
|
||||
|
||||
check-error@2.1.3: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
citty@0.1.6:
|
||||
dependencies:
|
||||
consola: 3.4.2
|
||||
|
||||
citty@0.2.1: {}
|
||||
|
||||
color-convert@3.1.3:
|
||||
dependencies:
|
||||
color-name: 2.1.0
|
||||
@@ -1864,6 +2134,10 @@ snapshots:
|
||||
|
||||
commander@13.1.0: {}
|
||||
|
||||
confbox@0.2.4: {}
|
||||
|
||||
consola@3.4.2: {}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@@ -1884,12 +2158,38 @@ snapshots:
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
deepmerge-ts@7.1.5: {}
|
||||
|
||||
defu@6.1.4: {}
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
destr@2.0.5: {}
|
||||
|
||||
dotenv@16.6.1: {}
|
||||
|
||||
duplexify@4.1.3:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
stream-shift: 1.0.3
|
||||
|
||||
effect@3.18.4:
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
fast-check: 3.23.2
|
||||
|
||||
empathic@2.0.0: {}
|
||||
|
||||
enabled@2.0.0: {}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
||||
es-module-lexer@1.7.0: {}
|
||||
|
||||
esbuild@0.27.4:
|
||||
@@ -1925,9 +2225,9 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-config-prettier@10.1.8(eslint@10.0.3):
|
||||
eslint-config-prettier@10.1.8(eslint@10.0.3(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 10.0.3
|
||||
eslint: 10.0.3(jiti@2.6.1)
|
||||
|
||||
eslint-scope@9.1.2:
|
||||
dependencies:
|
||||
@@ -1940,9 +2240,9 @@ snapshots:
|
||||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@10.0.3:
|
||||
eslint@10.0.3(jiti@2.6.1):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.23.3
|
||||
'@eslint/config-helpers': 0.5.3
|
||||
@@ -1972,6 +2272,8 @@ snapshots:
|
||||
minimatch: 10.2.4
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
optionalDependencies:
|
||||
jiti: 2.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -2014,6 +2316,12 @@ snapshots:
|
||||
|
||||
expect-type@1.3.0: {}
|
||||
|
||||
exsolve@1.0.8: {}
|
||||
|
||||
fast-check@3.23.2:
|
||||
dependencies:
|
||||
pure-rand: 6.1.0
|
||||
|
||||
fast-decode-uri-component@1.0.1: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
@@ -2112,6 +2420,15 @@ snapshots:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
giget@2.0.0:
|
||||
dependencies:
|
||||
citty: 0.1.6
|
||||
consola: 3.4.2
|
||||
defu: 6.1.4
|
||||
node-fetch-native: 1.6.7
|
||||
nypm: 0.6.5
|
||||
pathe: 2.0.3
|
||||
|
||||
glob-parent@6.0.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -2171,6 +2488,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 9.0.0
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
||||
json-buffer@3.0.1: {}
|
||||
@@ -2237,13 +2556,27 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
npm-run-path@6.0.0:
|
||||
dependencies:
|
||||
path-key: 4.0.0
|
||||
unicorn-magic: 0.3.0
|
||||
|
||||
nypm@0.6.5:
|
||||
dependencies:
|
||||
citty: 0.2.1
|
||||
pathe: 2.0.3
|
||||
tinyexec: 1.0.4
|
||||
|
||||
ohash@2.0.11: {}
|
||||
|
||||
on-exit-leak-free@2.1.2: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
one-time@1.0.0:
|
||||
dependencies:
|
||||
fn.name: 1.1.0
|
||||
@@ -2284,6 +2617,8 @@ snapshots:
|
||||
|
||||
pathval@2.0.1: {}
|
||||
|
||||
perfect-debounce@1.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
@@ -2308,6 +2643,12 @@ snapshots:
|
||||
sonic-boom: 4.2.1
|
||||
thread-stream: 4.0.0
|
||||
|
||||
pkg-types@2.3.0:
|
||||
dependencies:
|
||||
confbox: 0.2.4
|
||||
exsolve: 1.0.8
|
||||
pathe: 2.0.3
|
||||
|
||||
postcss@8.5.8:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
@@ -2320,20 +2661,38 @@ snapshots:
|
||||
dependencies:
|
||||
parse-ms: 4.0.0
|
||||
|
||||
prisma@6.19.2(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@prisma/config': 6.19.2
|
||||
'@prisma/engines': 6.19.2
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
process-warning@4.0.1: {}
|
||||
|
||||
process-warning@5.0.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
pure-rand@6.1.0: {}
|
||||
|
||||
quick-format-unescaped@4.0.4: {}
|
||||
|
||||
rc9@2.1.2:
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
@@ -2424,6 +2783,8 @@ snapshots:
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
stream-shift@1.0.3: {}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@@ -2444,6 +2805,8 @@ snapshots:
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyexec@1.0.4: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -2488,13 +2851,13 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vite-node@3.2.4(@types/node@22.19.15)(tsx@4.21.0):
|
||||
vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 7.3.1(@types/node@22.19.15)(tsx@4.21.0)
|
||||
vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -2509,7 +2872,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite@7.3.1(@types/node@22.19.15)(tsx@4.21.0):
|
||||
vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0):
|
||||
dependencies:
|
||||
esbuild: 0.27.4
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -2520,13 +2883,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 22.19.15
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
tsx: 4.21.0
|
||||
|
||||
vitest@3.2.4(@types/node@22.19.15)(tsx@4.21.0):
|
||||
vitest@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.15)(tsx@4.21.0))
|
||||
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@@ -2544,8 +2908,8 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 7.3.1(@types/node@22.19.15)(tsx@4.21.0)
|
||||
vite-node: 3.2.4(@types/node@22.19.15)(tsx@4.21.0)
|
||||
vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0)
|
||||
vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(tsx@4.21.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 22.19.15
|
||||
@@ -2594,6 +2958,10 @@ snapshots:
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.19.0: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
yoctocolors@2.1.2: {}
|
||||
|
||||
36
bastion/src/labd/package.json
Normal file
36
bastion/src/labd/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@lab/labd",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/main.js",
|
||||
"types": "./dist/main.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/main.js",
|
||||
"types": "./dist/main.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"clean": "rimraf dist",
|
||||
"dev": "tsx src/main.ts",
|
||||
"db:push": "prisma db push",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:generate": "prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lab/shared": "workspace:*",
|
||||
"@prisma/client": "^6.9.0",
|
||||
"fastify": "^5.3.3",
|
||||
"@fastify/websocket": "^11.0.2",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.1",
|
||||
"prisma": "^6.9.0",
|
||||
"rimraf": "^6.1.3",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
145
bastion/src/labd/prisma/schema.prisma
Normal file
145
bastion/src/labd/prisma/schema.prisma
Normal file
@@ -0,0 +1,145 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "cockroachdb"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Server {
|
||||
id String @id @default(uuid())
|
||||
hostname String @unique
|
||||
mac String? @unique
|
||||
cloud String @default("baremetal")
|
||||
environment String @default("default")
|
||||
role String @default("worker")
|
||||
labels Json @default("{}")
|
||||
ip String?
|
||||
agentVersion String?
|
||||
status String @default("unknown") // unknown, online, offline, provisioning
|
||||
lastHeartbeat DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
agent Agent?
|
||||
auditLogs AuditLog[]
|
||||
}
|
||||
|
||||
model Agent {
|
||||
id String @id @default(uuid())
|
||||
serverId String @unique
|
||||
server Server @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
||||
certificatePem String?
|
||||
enrolledAt DateTime @default(now())
|
||||
lastSeen DateTime?
|
||||
|
||||
@@index([serverId])
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
displayName String?
|
||||
certFingerprint String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
roleBindings UserRole[]
|
||||
auditLogs AuditLog[]
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
permissions Permission[]
|
||||
userBindings UserRole[]
|
||||
}
|
||||
|
||||
model Permission {
|
||||
id String @id @default(uuid())
|
||||
roleId String
|
||||
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
||||
type String @default("allow") // allow or deny
|
||||
action String // read, exec, apply, destroy, manage, admin, kubectl, *
|
||||
cloud String @default("*")
|
||||
environment String @default("*")
|
||||
server String @default("*")
|
||||
|
||||
@@index([roleId])
|
||||
}
|
||||
|
||||
model UserRole {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
roleId String
|
||||
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, roleId])
|
||||
@@index([userId])
|
||||
@@index([roleId])
|
||||
}
|
||||
|
||||
model JoinToken {
|
||||
id String @id @default(uuid())
|
||||
token String @unique
|
||||
type String @default("one-time") // one-time or reusable
|
||||
label String?
|
||||
usedBy String? // server hostname that used it
|
||||
usedAt DateTime?
|
||||
revokedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime?
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(uuid())
|
||||
userId String?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
serverId String?
|
||||
server Server? @relation(fields: [serverId], references: [id])
|
||||
sessionId String?
|
||||
action String // exec, kubectl, apply, login, rbac-denied, etc.
|
||||
resourceType String? // server, cluster, role, app, etc.
|
||||
resourceName String?
|
||||
args String? // sanitized command args
|
||||
result String @default("success") // success, denied, error
|
||||
durationMs Int?
|
||||
sourceIp String?
|
||||
timestamp DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
@@index([serverId])
|
||||
@@index([sessionId])
|
||||
@@index([timestamp])
|
||||
@@index([action])
|
||||
}
|
||||
|
||||
model PulumiRun {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
stackName String
|
||||
action String // up, preview, destroy
|
||||
status String @default("pending") // pending, running, succeeded, failed
|
||||
output String?
|
||||
startedAt DateTime @default(now())
|
||||
completedAt DateTime?
|
||||
|
||||
@@index([userId])
|
||||
@@index([stackName])
|
||||
}
|
||||
|
||||
model Cluster {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
cloud String @default("baremetal")
|
||||
environment String @default("default")
|
||||
kubeconfigEnc String? // encrypted kubeconfig
|
||||
labels Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
19
bastion/src/labd/src/config.ts
Normal file
19
bastion/src/labd/src/config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// Configuration from environment variables with sensible defaults.
|
||||
|
||||
export interface LabdConfig {
|
||||
port: number;
|
||||
host: string;
|
||||
databaseUrl: string;
|
||||
caDir: string;
|
||||
logLevel: string;
|
||||
}
|
||||
|
||||
export function loadConfig(overrides: Partial<LabdConfig> = {}): LabdConfig {
|
||||
return {
|
||||
port: overrides.port ?? parseInt(process.env["LABD_PORT"] ?? "3100", 10),
|
||||
host: overrides.host ?? process.env["LABD_HOST"] ?? "0.0.0.0",
|
||||
databaseUrl: overrides.databaseUrl ?? process.env["DATABASE_URL"] ?? "",
|
||||
caDir: overrides.caDir ?? process.env["CA_DIR"] ?? "/etc/labd/ca",
|
||||
logLevel: overrides.logLevel ?? process.env["LABD_LOG_LEVEL"] ?? "info",
|
||||
};
|
||||
}
|
||||
91
bastion/src/labd/src/main.ts
Normal file
91
bastion/src/labd/src/main.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
// Entry point for the lab master daemon (labd).
|
||||
// Initializes Prisma, starts Fastify with WebSocket support, registers routes.
|
||||
|
||||
import { loadConfig } from "./config.js";
|
||||
import { createApp } from "./server.js";
|
||||
import { logger } from "./services/logger.js";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const config = loadConfig();
|
||||
|
||||
// Initialize Prisma client (wrapped in try/catch for when DB isn't available)
|
||||
let db;
|
||||
try {
|
||||
const { PrismaClient } = await import("@prisma/client");
|
||||
const prisma = new PrismaClient({
|
||||
datasources: config.databaseUrl
|
||||
? { db: { url: config.databaseUrl } }
|
||||
: undefined,
|
||||
});
|
||||
await prisma.$connect();
|
||||
logger.info("Database connected");
|
||||
db = prisma;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.warn(`Database not available: ${message}`);
|
||||
logger.warn("Running without database -- some features will be unavailable");
|
||||
|
||||
// Create a stub db client that returns errors for all operations
|
||||
db = {
|
||||
$queryRaw: async () => {
|
||||
throw new Error("Database not connected");
|
||||
},
|
||||
server: {
|
||||
findMany: async () => {
|
||||
throw new Error("Database not connected");
|
||||
},
|
||||
findUnique: async () => {
|
||||
throw new Error("Database not connected");
|
||||
},
|
||||
},
|
||||
joinToken: {
|
||||
findUnique: async () => {
|
||||
throw new Error("Database not connected");
|
||||
},
|
||||
findMany: async () => {
|
||||
throw new Error("Database not connected");
|
||||
},
|
||||
create: async () => {
|
||||
throw new Error("Database not connected");
|
||||
},
|
||||
update: async () => {
|
||||
throw new Error("Database not connected");
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Create Fastify app
|
||||
const { app } = createApp(config, db);
|
||||
|
||||
// Start server
|
||||
try {
|
||||
await app.listen({ port: config.port, host: config.host });
|
||||
logger.info(`labd listening on ${config.host}:${config.port}`);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to start server: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
const shutdown = async (): Promise<void> => {
|
||||
logger.info("Shutting down...");
|
||||
await app.close();
|
||||
if (db !== null && "$disconnect" in db) {
|
||||
await (db as { $disconnect: () => Promise<void> }).$disconnect();
|
||||
}
|
||||
logger.info("Goodbye");
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => void shutdown());
|
||||
process.on("SIGTERM", () => void shutdown());
|
||||
|
||||
// Keep process alive
|
||||
await new Promise(() => {});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("Failed to start labd:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
33
bastion/src/labd/src/middleware/auth.ts
Normal file
33
bastion/src/labd/src/middleware/auth.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// Placeholder mTLS auth middleware.
|
||||
// Extracts client certificate info from the request and resolves user/agent identity.
|
||||
|
||||
import type { FastifyRequest, FastifyReply } from "fastify";
|
||||
import { logger } from "../services/logger.js";
|
||||
|
||||
declare module "fastify" {
|
||||
interface FastifyRequest {
|
||||
clientCertFingerprint?: string;
|
||||
authenticatedUser?: string;
|
||||
authenticatedAgent?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function createMtlsAuthMiddleware(): (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => Promise<void> {
|
||||
return async function mtlsAuthMiddleware(
|
||||
request: FastifyRequest,
|
||||
_reply: FastifyReply,
|
||||
): Promise<void> {
|
||||
// TODO: Extract client certificate from TLS connection
|
||||
// const cert = (request.raw.socket as TLSSocket).getPeerCertificate();
|
||||
// For now, this is a no-op placeholder
|
||||
|
||||
const certHeader = request.headers["x-client-cert-fingerprint"];
|
||||
if (typeof certHeader === "string" && certHeader.length > 0) {
|
||||
request.clientCertFingerprint = certHeader;
|
||||
logger.info(`mTLS: client cert fingerprint=${certHeader.slice(0, 16)}...`);
|
||||
}
|
||||
};
|
||||
}
|
||||
163
bastion/src/labd/src/routes/auth.ts
Normal file
163
bastion/src/labd/src/routes/auth.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
// Authentication and token management routes.
|
||||
// POST /api/auth/enroll — agent enrollment (token + CSR -> signed cert)
|
||||
// POST /api/tokens — create join token
|
||||
// GET /api/tokens — list tokens
|
||||
// DELETE /api/tokens/:id — revoke token
|
||||
|
||||
import { randomBytes } from "node:crypto";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import type { DbClient } from "../server.js";
|
||||
import { logger } from "../services/logger.js";
|
||||
|
||||
export function registerAuthRoutes(app: FastifyInstance, db: DbClient): void {
|
||||
// Agent enrollment: validate join token, accept CSR, return signed cert
|
||||
app.post<{
|
||||
Body: {
|
||||
token?: string;
|
||||
hostname?: string;
|
||||
csr?: string;
|
||||
};
|
||||
}>("/api/auth/enroll", async (request, reply) => {
|
||||
const { token, hostname, csr } = request.body ?? {};
|
||||
|
||||
if (token === undefined || token === "") {
|
||||
return reply.code(400).send({ error: "token is required" });
|
||||
}
|
||||
if (hostname === undefined || hostname === "") {
|
||||
return reply.code(400).send({ error: "hostname is required" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Validate token
|
||||
const joinToken = await db.joinToken.findUnique({
|
||||
where: { token },
|
||||
}) as { id: string; type: string; usedBy: string | null; revokedAt: Date | null; expiresAt: Date | null } | null;
|
||||
|
||||
if (joinToken === null) {
|
||||
return reply.code(401).send({ error: "Invalid join token" });
|
||||
}
|
||||
if (joinToken.revokedAt !== null) {
|
||||
return reply.code(401).send({ error: "Token has been revoked" });
|
||||
}
|
||||
if (joinToken.expiresAt !== null && joinToken.expiresAt < new Date()) {
|
||||
return reply.code(401).send({ error: "Token has expired" });
|
||||
}
|
||||
if (joinToken.type === "one-time" && joinToken.usedBy !== null) {
|
||||
return reply.code(401).send({ error: "Token has already been used" });
|
||||
}
|
||||
|
||||
// Mark token as used
|
||||
await db.joinToken.update({
|
||||
where: { id: joinToken.id },
|
||||
data: {
|
||||
usedBy: hostname,
|
||||
usedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(`AGENT ENROLLED: ${hostname} (token=${joinToken.id.slice(0, 8)}...)`);
|
||||
|
||||
// TODO: Sign CSR with CA and return certificate
|
||||
// For now, return a placeholder acknowledging enrollment
|
||||
return reply.send({
|
||||
status: "enrolled",
|
||||
hostname,
|
||||
message: "Agent enrolled successfully",
|
||||
certificatePem: null, // TODO: implement CA signing
|
||||
csr: csr !== undefined ? "received" : "not provided",
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return reply.code(500).send({ error: "Enrollment failed", detail: message });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new join token
|
||||
app.post<{
|
||||
Body: {
|
||||
type?: string;
|
||||
label?: string;
|
||||
expiresInHours?: number;
|
||||
};
|
||||
}>("/api/tokens", async (request, reply) => {
|
||||
const { type, label, expiresInHours } = request.body ?? {};
|
||||
|
||||
const tokenType = type ?? "one-time";
|
||||
if (tokenType !== "one-time" && tokenType !== "reusable") {
|
||||
return reply.code(400).send({ error: "type must be 'one-time' or 'reusable'" });
|
||||
}
|
||||
|
||||
const tokenValue = randomBytes(32).toString("hex");
|
||||
const expiresAt = expiresInHours !== undefined
|
||||
? new Date(Date.now() + expiresInHours * 60 * 60 * 1000)
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const created = await db.joinToken.create({
|
||||
data: {
|
||||
token: tokenValue,
|
||||
type: tokenType,
|
||||
label: label ?? null,
|
||||
expiresAt: expiresAt ?? null,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(`TOKEN CREATED: ${(created as { id: string }).id} type=${tokenType} label=${label ?? "(none)"}`);
|
||||
|
||||
return reply.code(201).send({
|
||||
id: (created as { id: string }).id,
|
||||
token: tokenValue,
|
||||
type: tokenType,
|
||||
label: label ?? null,
|
||||
expiresAt: expiresAt?.toISOString() ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return reply.code(500).send({ error: "Failed to create token", detail: message });
|
||||
}
|
||||
});
|
||||
|
||||
// List tokens
|
||||
app.get("/api/tokens", async (_request, reply) => {
|
||||
try {
|
||||
const tokens = await db.joinToken.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
label: true,
|
||||
usedBy: true,
|
||||
usedAt: true,
|
||||
revokedAt: true,
|
||||
createdAt: true,
|
||||
expiresAt: true,
|
||||
// Intentionally omit token value for security
|
||||
},
|
||||
});
|
||||
return reply.send(tokens);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return reply.code(500).send({ error: "Failed to list tokens", detail: message });
|
||||
}
|
||||
});
|
||||
|
||||
// Revoke a token
|
||||
app.delete<{
|
||||
Params: { id: string };
|
||||
}>("/api/tokens/:id", async (request, reply) => {
|
||||
const { id } = request.params;
|
||||
|
||||
try {
|
||||
await db.joinToken.update({
|
||||
where: { id },
|
||||
data: { revokedAt: new Date() },
|
||||
});
|
||||
|
||||
logger.info(`TOKEN REVOKED: ${id}`);
|
||||
return reply.send({ status: "revoked", id });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return reply.code(500).send({ error: "Failed to revoke token", detail: message });
|
||||
}
|
||||
});
|
||||
}
|
||||
28
bastion/src/labd/src/routes/health.ts
Normal file
28
bastion/src/labd/src/routes/health.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// Health check routes.
|
||||
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import type { DbClient } from "../server.js";
|
||||
|
||||
export function registerHealthRoutes(app: FastifyInstance, db: DbClient): void {
|
||||
app.get("/healthz", async (_request, reply) => {
|
||||
let dbOk = false;
|
||||
try {
|
||||
await db.$queryRaw`SELECT 1`;
|
||||
dbOk = true;
|
||||
} catch {
|
||||
// DB not reachable
|
||||
}
|
||||
|
||||
const status = dbOk ? "healthy" : "degraded";
|
||||
const statusCode = dbOk ? 200 : 503;
|
||||
|
||||
return reply.code(statusCode).send({
|
||||
status,
|
||||
uptime: process.uptime(),
|
||||
timestamp: new Date().toISOString(),
|
||||
checks: {
|
||||
database: dbOk ? "ok" : "error",
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
64
bastion/src/labd/src/routes/servers.ts
Normal file
64
bastion/src/labd/src/routes/servers.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// Server management routes.
|
||||
// GET /api/servers — list servers with optional filters (cloud, environment, label)
|
||||
// GET /api/servers/:id — get server details
|
||||
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import type { DbClient } from "../server.js";
|
||||
|
||||
export function registerServerRoutes(app: FastifyInstance, db: DbClient): void {
|
||||
// List servers with optional filters
|
||||
app.get<{
|
||||
Querystring: {
|
||||
cloud?: string;
|
||||
environment?: string;
|
||||
status?: string;
|
||||
};
|
||||
}>("/api/servers", async (request, reply) => {
|
||||
const { cloud, environment, status } = request.query;
|
||||
|
||||
const where: Record<string, unknown> = {};
|
||||
if (cloud !== undefined && cloud !== "") {
|
||||
where["cloud"] = cloud;
|
||||
}
|
||||
if (environment !== undefined && environment !== "") {
|
||||
where["environment"] = environment;
|
||||
}
|
||||
if (status !== undefined && status !== "") {
|
||||
where["status"] = status;
|
||||
}
|
||||
|
||||
try {
|
||||
const servers = await db.server.findMany({
|
||||
where: Object.keys(where).length > 0 ? where : undefined,
|
||||
orderBy: { hostname: "asc" },
|
||||
});
|
||||
return reply.send(servers);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return reply.code(500).send({ error: "Failed to list servers", detail: message });
|
||||
}
|
||||
});
|
||||
|
||||
// Get server details by ID
|
||||
app.get<{
|
||||
Params: { id: string };
|
||||
}>("/api/servers/:id", async (request, reply) => {
|
||||
const { id } = request.params;
|
||||
|
||||
try {
|
||||
const server = await db.server.findUnique({
|
||||
where: { id },
|
||||
include: { agent: true },
|
||||
});
|
||||
|
||||
if (server === null) {
|
||||
return reply.code(404).send({ error: "Server not found", id });
|
||||
}
|
||||
|
||||
return reply.send(server);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return reply.code(500).send({ error: "Failed to get server", detail: message });
|
||||
}
|
||||
});
|
||||
}
|
||||
63
bastion/src/labd/src/server.ts
Normal file
63
bastion/src/labd/src/server.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
// Fastify application setup with all routes registered.
|
||||
|
||||
import Fastify from "fastify";
|
||||
import websocket from "@fastify/websocket";
|
||||
import type { LabdConfig } from "./config.js";
|
||||
import { logger } from "./services/logger.js";
|
||||
import { registerHealthRoutes } from "./routes/health.js";
|
||||
import { registerServerRoutes } from "./routes/servers.js";
|
||||
import { registerAuthRoutes } from "./routes/auth.js";
|
||||
|
||||
export interface DbClient {
|
||||
$queryRaw: (query: TemplateStringsArray) => Promise<unknown>;
|
||||
server: {
|
||||
findMany: (args?: unknown) => Promise<unknown[]>;
|
||||
findUnique: (args: unknown) => Promise<unknown>;
|
||||
};
|
||||
joinToken: {
|
||||
findUnique: (args: unknown) => Promise<unknown>;
|
||||
findMany: (args?: unknown) => Promise<unknown[]>;
|
||||
create: (args: unknown) => Promise<unknown>;
|
||||
update: (args: unknown) => Promise<unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
export function createApp(_config: LabdConfig, db: DbClient): {
|
||||
app: ReturnType<typeof Fastify>;
|
||||
} {
|
||||
const app = Fastify({
|
||||
logger: false, // We use winston instead
|
||||
});
|
||||
|
||||
// Register WebSocket support
|
||||
void app.register(websocket);
|
||||
|
||||
// Register route handlers
|
||||
registerHealthRoutes(app, db);
|
||||
registerServerRoutes(app, db);
|
||||
registerAuthRoutes(app, db);
|
||||
|
||||
// WebSocket handler for agent connections
|
||||
app.register(async (fastify) => {
|
||||
fastify.get("/ws/agent", { websocket: true }, (socket, _request) => {
|
||||
logger.info("Agent WebSocket connection established");
|
||||
|
||||
socket.on("message", (message: Buffer) => {
|
||||
const data = message.toString();
|
||||
logger.info(`Agent message: ${data}`);
|
||||
// TODO: Handle agent heartbeat, command relay, etc.
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
logger.info("Agent WebSocket connection closed");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Log all requests
|
||||
app.addHook("onRequest", async (request) => {
|
||||
logger.info(`HTTP: ${request.ip} ${request.method} ${request.url}`);
|
||||
});
|
||||
|
||||
return { app };
|
||||
}
|
||||
17
bastion/src/labd/src/services/logger.ts
Normal file
17
bastion/src/labd/src/services/logger.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// Winston logger instance shared across the labd application.
|
||||
|
||||
import winston from "winston";
|
||||
|
||||
export const logger = winston.createLogger({
|
||||
level: process.env["LABD_LOG_LEVEL"] ?? "info",
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: "HH:mm:ss" }),
|
||||
winston.format.printf(({ timestamp, level, message }) => {
|
||||
const prefix = level === "error" ? "\x1b[31m[labd]\x1b[0m"
|
||||
: level === "warn" ? "\x1b[33m[labd]\x1b[0m"
|
||||
: "\x1b[36m[labd]\x1b[0m";
|
||||
return `${prefix} ${timestamp as string} ${message as string}`;
|
||||
}),
|
||||
),
|
||||
transports: [new winston.transports.Console()],
|
||||
});
|
||||
65
bastion/src/labd/tests/health.test.ts
Normal file
65
bastion/src/labd/tests/health.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import Fastify from "fastify";
|
||||
import { registerHealthRoutes } from "../src/routes/health.js";
|
||||
import type { DbClient } from "../src/server.js";
|
||||
|
||||
function createMockDb(overrides: Partial<DbClient> = {}): DbClient {
|
||||
return {
|
||||
$queryRaw: vi.fn().mockResolvedValue([{ "?column?": 1 }]),
|
||||
server: {
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
findUnique: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
joinToken: {
|
||||
findUnique: vi.fn().mockResolvedValue(null),
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
create: vi.fn().mockResolvedValue({ id: "test-id" }),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("Health endpoint", () => {
|
||||
it("returns healthy when database is reachable", async () => {
|
||||
const app = Fastify({ logger: false });
|
||||
const db = createMockDb();
|
||||
|
||||
registerHealthRoutes(app, db);
|
||||
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/healthz",
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const body = JSON.parse(response.body);
|
||||
expect(body.status).toBe("healthy");
|
||||
expect(body.checks.database).toBe("ok");
|
||||
expect(body.uptime).toBeTypeOf("number");
|
||||
expect(body.timestamp).toBeTypeOf("string");
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("returns degraded when database is unreachable", async () => {
|
||||
const app = Fastify({ logger: false });
|
||||
const db = createMockDb({
|
||||
$queryRaw: vi.fn().mockRejectedValue(new Error("Connection refused")),
|
||||
});
|
||||
|
||||
registerHealthRoutes(app, db);
|
||||
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/healthz",
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(503);
|
||||
const body = JSON.parse(response.body);
|
||||
expect(body.status).toBe("degraded");
|
||||
expect(body.checks.database).toBe("error");
|
||||
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
12
bastion/src/labd/tsconfig.json
Normal file
12
bastion/src/labd/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [
|
||||
{ "path": "../shared" }
|
||||
]
|
||||
}
|
||||
8
bastion/src/labd/vitest.config.ts
Normal file
8
bastion/src/labd/vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { defineProject } from 'vitest/config';
|
||||
|
||||
export default defineProject({
|
||||
test: {
|
||||
name: 'labd',
|
||||
include: ['tests/**/*.test.ts'],
|
||||
},
|
||||
});
|
||||
@@ -31,3 +31,6 @@ DHCP_RANGE_END=
|
||||
|
||||
# Path to SSH keys directory on host (mounted read-only)
|
||||
SSH_KEY_PATH=~/.ssh
|
||||
|
||||
# CockroachDB connection (used by labd)
|
||||
DATABASE_URL=postgresql://root@localhost:26257/labctl?sslmode=disable
|
||||
|
||||
@@ -15,6 +15,18 @@ services:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
|
||||
cockroachdb:
|
||||
image: cockroachdb/cockroach:latest-v24.3
|
||||
command: start-single-node --insecure --store=type=mem,size=256MiB
|
||||
ports:
|
||||
- "26257:26257"
|
||||
- "8081:8080"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health?ready=1"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
bastion-state:
|
||||
bastion-tftp:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"references": [
|
||||
{ "path": "src/shared" },
|
||||
{ "path": "src/bastion" },
|
||||
{ "path": "src/cli" }
|
||||
{ "path": "src/cli" },
|
||||
{ "path": "src/labd" }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user