Compare commits
No commits in common. "43feec227c05c2cc5a6261a874d62c4c6e2c866e" and "ad003c4ae053f2a31a09c6608aaa76d752602b32" have entirely different histories.
43feec227c
...
ad003c4ae0
10 changed files with 142 additions and 654 deletions
343
deno.lock
generated
343
deno.lock
generated
|
@ -1,338 +1,10 @@
|
||||||
{
|
{
|
||||||
"version": "3",
|
"version": "3",
|
||||||
"packages": {
|
|
||||||
"specifiers": {
|
|
||||||
"npm:@deepkit/type@1.0.1-alpha.97": "npm:@deepkit/type@1.0.1-alpha.97_@deepkit+core@1.0.1-alpha.108",
|
|
||||||
"npm:@effect/schema@0.36.5": "npm:@effect/schema@0.36.5_@effect+data@0.18.7_@effect+io@0.40.3__@effect+data@0.18.7",
|
|
||||||
"npm:@ovh-api/api": "npm:@ovh-api/api@4.1.3",
|
|
||||||
"npm:@ovh-api/domain": "npm:@ovh-api/domain@4.0.3",
|
|
||||||
"npm:@sinclair/typebox@0.31.17": "npm:@sinclair/typebox@0.31.17",
|
|
||||||
"npm:ajv@8.12.0": "npm:ajv@8.12.0",
|
|
||||||
"npm:arktype@1.0.21-alpha": "npm:arktype@1.0.21-alpha",
|
|
||||||
"npm:io-ts@2.2.20": "npm:io-ts@2.2.20_fp-ts@2.16.1",
|
|
||||||
"npm:joi@17.10.2": "npm:joi@17.10.2",
|
|
||||||
"npm:ovh-es": "npm:ovh-es@1.7.0",
|
|
||||||
"npm:ow@0.28.2": "npm:ow@0.28.2",
|
|
||||||
"npm:runtypes@6.7.0": "npm:runtypes@6.7.0",
|
|
||||||
"npm:superstruct@1.0.3": "npm:superstruct@1.0.3",
|
|
||||||
"npm:yup@1.3.2": "npm:yup@1.3.2"
|
|
||||||
},
|
|
||||||
"npm": {
|
|
||||||
"@deepkit/core@1.0.1-alpha.108": {
|
|
||||||
"integrity": "sha512-4nN9qL7OSG8kAvhI4aNVGSADgLK7ZPetISfTq7LGSWZJGnT4hGi+/Dxd2bW3KYrUZ4NRP+e/N3IUaQBkSAJ6Pw==",
|
|
||||||
"dependencies": {
|
|
||||||
"dot-prop": "dot-prop@5.3.0",
|
|
||||||
"to-fast-properties": "to-fast-properties@3.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@deepkit/type-spec@1.0.1-alpha.108": {
|
|
||||||
"integrity": "sha512-OIJsZ0nZKybaKrzrY7WpkIlDXm88wE8N5G5cu+FL3snZx/drzF5FxmbRP2omLGRRDb68+o/vTni26sUbBtWOBw==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@deepkit/type@1.0.1-alpha.97_@deepkit+core@1.0.1-alpha.108": {
|
|
||||||
"integrity": "sha512-fnMrhMVYANyZ8zrKEj7KnJMTl2WhXKza9dh9s0Mtgcowd73YSJyEdvFkBKcabmHbvlbHFkAHv8KAvYifvWbGvw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@deepkit/core": "@deepkit/core@1.0.1-alpha.108",
|
|
||||||
"@deepkit/type-spec": "@deepkit/type-spec@1.0.1-alpha.108",
|
|
||||||
"@types/uuid": "@types/uuid@8.3.4",
|
|
||||||
"buffer": "buffer@5.7.1",
|
|
||||||
"uuid": "uuid@8.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@effect/data@0.18.7": {
|
|
||||||
"integrity": "sha512-r7C9VjavmG85wb5BOvcbbKGioPJCoBAFsJVOUNWlxZBRIU+l68ENpsyzCkWsU7dP4teAztjBZ89O3hVhBX0Huw==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@effect/io@0.40.3_@effect+data@0.18.7": {
|
|
||||||
"integrity": "sha512-bnHBFq1XzXn94x2w5FcXZz3MbY7N3YrhiI2CrncJr3WcGRDc2rCYIpPupw0JQGFcJsoopTj1BNfBuBR5s4/RyA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@effect/data": "@effect/data@0.18.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@effect/schema@0.36.5_@effect+data@0.18.7_@effect+io@0.40.3__@effect+data@0.18.7": {
|
|
||||||
"integrity": "sha512-E8KZ17DqZJl7E/eEVTkxb2NEv6vYhab79HugHi10krd2Gm3vOJvWs+nRgtPhI+0dvkXVxzCcY8bLfkND2F0rBg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@effect/data": "@effect/data@0.18.7",
|
|
||||||
"@effect/io": "@effect/io@0.40.3_@effect+data@0.18.7",
|
|
||||||
"fast-check": "fast-check@3.14.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@hapi/hoek@9.3.0": {
|
|
||||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@hapi/topo@5.1.0": {
|
|
||||||
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@hapi/hoek": "@hapi/hoek@9.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@ovh-api/api@4.1.3": {
|
|
||||||
"integrity": "sha512-gRnlqLEolPj2thBHt7tlM0z1tPNc86PSmOy3jkmOr46hAY+eHI/mGlTInIAJknc9iDSqZ9eIVzUiNngtjysYAw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@ovh-api/common": "@ovh-api/common@4.0.2",
|
|
||||||
"open": "open@8.4.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@ovh-api/common@4.0.2": {
|
|
||||||
"integrity": "sha512-9PKJjtq7l2mP1t6Ta+cEYZKF3UkHtR1P6f5xvH+M78Hgj7HMwX2vxByuf5FkipOeb3Elfq8ASwZxSiP5+wMP7g==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@ovh-api/domain@4.0.3": {
|
|
||||||
"integrity": "sha512-ARUCOicVekcXmXAUV03aFqXGoHpYow3jyvdqq8e7k3ftXtvkoR1UQXX4rWGXdWgxW7rA7ooDWcU+vGm/jYQqgQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@ovh-api/common": "@ovh-api/common@4.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@sideway/address@4.1.4": {
|
|
||||||
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@hapi/hoek": "@hapi/hoek@9.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@sideway/formula@3.0.1": {
|
|
||||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@sideway/pinpoint@2.0.0": {
|
|
||||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@sinclair/typebox@0.31.17": {
|
|
||||||
"integrity": "sha512-GVYVHHOGINx+DT2DwjXoCQO0mRpztYKyb3d48tucuqhjhHpQYGp7Xx7dhhQGzILx/beuBrzfITMC7/5X7fw+UA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@sindresorhus/is@4.6.0": {
|
|
||||||
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"@types/uuid@8.3.4": {
|
|
||||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"ajv@8.12.0": {
|
|
||||||
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "fast-deep-equal@3.1.3",
|
|
||||||
"json-schema-traverse": "json-schema-traverse@1.0.0",
|
|
||||||
"require-from-string": "require-from-string@2.0.2",
|
|
||||||
"uri-js": "uri-js@4.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"arktype@1.0.21-alpha": {
|
|
||||||
"integrity": "sha512-XQBoepICSrwDXYlh7gqcrKWEbWTSebToIoTimi9CxVLLlxNWt/YXNrKrSITr/T9jke+TIag1tUcIum/90EQNpg==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"base64-js@1.5.1": {
|
|
||||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"buffer@5.7.1": {
|
|
||||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"base64-js": "base64-js@1.5.1",
|
|
||||||
"ieee754": "ieee754@1.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"callsites@3.1.0": {
|
|
||||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"debug@2.6.9": {
|
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "ms@2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"define-lazy-prop@2.0.0": {
|
|
||||||
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"dot-prop@5.3.0": {
|
|
||||||
"integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"is-obj": "is-obj@2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dot-prop@6.0.1": {
|
|
||||||
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
|
|
||||||
"dependencies": {
|
|
||||||
"is-obj": "is-obj@2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fast-check@3.14.0": {
|
|
||||||
"integrity": "sha512-9Z0zqASzDNjXBox/ileV/fd+4P+V/f3o4shM6QawvcdLFh8yjPG4h5BrHUZ8yzY6amKGDTAmRMyb/JZqe+dCgw==",
|
|
||||||
"dependencies": {
|
|
||||||
"pure-rand": "pure-rand@6.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fast-deep-equal@3.1.3": {
|
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"fp-ts@2.16.1": {
|
|
||||||
"integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"ieee754@1.2.1": {
|
|
||||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"io-ts@2.2.20_fp-ts@2.16.1": {
|
|
||||||
"integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==",
|
|
||||||
"dependencies": {
|
|
||||||
"fp-ts": "fp-ts@2.16.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-docker@2.2.1": {
|
|
||||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"is-obj@2.0.0": {
|
|
||||||
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"is-wsl@2.2.0": {
|
|
||||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
|
||||||
"dependencies": {
|
|
||||||
"is-docker": "is-docker@2.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"joi@17.10.2": {
|
|
||||||
"integrity": "sha512-hcVhjBxRNW/is3nNLdGLIjkgXetkeGc2wyhydhz8KumG23Aerk4HPjU5zaPAMRqXQFc0xNqXTC7+zQjxr0GlKA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@hapi/hoek": "@hapi/hoek@9.3.0",
|
|
||||||
"@hapi/topo": "@hapi/topo@5.1.0",
|
|
||||||
"@sideway/address": "@sideway/address@4.1.4",
|
|
||||||
"@sideway/formula": "@sideway/formula@3.0.1",
|
|
||||||
"@sideway/pinpoint": "@sideway/pinpoint@2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json-schema-traverse@1.0.0": {
|
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"lodash.isequal@4.5.0": {
|
|
||||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"mout@1.2.4": {
|
|
||||||
"integrity": "sha512-mZb9uOruMWgn/fw28DG4/yE3Kehfk1zKCLhuDU2O3vlKdnBBr4XaOCqVTflJ5aODavGUPqFHZgrFX3NJVuxGhQ==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"ms@2.0.0": {
|
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"nyks@6.10.0": {
|
|
||||||
"integrity": "sha512-Y65Kzo+A9b+BzsGBSfpjdm1a496sH564gHZ1anw8yNA+Ki9u2KC4EJuDQCSTbNliE1g76YsxAs6SWKXhZDsbJA==",
|
|
||||||
"dependencies": {
|
|
||||||
"mout": "mout@1.2.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"open@8.4.2": {
|
|
||||||
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"define-lazy-prop": "define-lazy-prop@2.0.0",
|
|
||||||
"is-docker": "is-docker@2.2.1",
|
|
||||||
"is-wsl": "is-wsl@2.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ovh-es@1.7.0": {
|
|
||||||
"integrity": "sha512-om96fYG7JpCfDyegVynDpkmIixMsqPkDb8V3Mz7i+7sC9NPxNwe9icRrBzexcDvMstJH0t7QnesRHDPuA2hMwg==",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "debug@2.6.9",
|
|
||||||
"mout": "mout@1.2.4",
|
|
||||||
"nyks": "nyks@6.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ow@0.28.2": {
|
|
||||||
"integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@sindresorhus/is": "@sindresorhus/is@4.6.0",
|
|
||||||
"callsites": "callsites@3.1.0",
|
|
||||||
"dot-prop": "dot-prop@6.0.1",
|
|
||||||
"lodash.isequal": "lodash.isequal@4.5.0",
|
|
||||||
"vali-date": "vali-date@1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"property-expr@2.0.6": {
|
|
||||||
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"punycode@2.3.0": {
|
|
||||||
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"pure-rand@6.0.4": {
|
|
||||||
"integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"require-from-string@2.0.2": {
|
|
||||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"runtypes@6.7.0": {
|
|
||||||
"integrity": "sha512-3TLdfFX8YHNFOhwHrSJza6uxVBmBrEjnNQlNXvXCdItS0Pdskfg5vVXUTWIN+Y23QR09jWpSl99UHkA83m4uWA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"superstruct@1.0.3": {
|
|
||||||
"integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"tiny-case@1.0.3": {
|
|
||||||
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"to-fast-properties@3.0.1": {
|
|
||||||
"integrity": "sha512-/wtNi1tW1F3nf0OL6AqVxGw9Tr1ET70InMhJuVxPwFdGqparF0nQ4UWGLf2DsoI2bFDtthlBnALncZpUzOnsUw==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"toposort@2.0.2": {
|
|
||||||
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"type-fest@2.19.0": {
|
|
||||||
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"uri-js@4.4.1": {
|
|
||||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
|
||||||
"dependencies": {
|
|
||||||
"punycode": "punycode@2.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uuid@8.3.2": {
|
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"vali-date@1.0.0": {
|
|
||||||
"integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==",
|
|
||||||
"dependencies": {}
|
|
||||||
},
|
|
||||||
"yup@1.3.2": {
|
|
||||||
"integrity": "sha512-6KCM971iQtJ+/KUaHdrhVr2LDkfhBtFPRnsG1P8F4q3uUVQ2RfEM9xekpha9aA4GXWJevjM10eDcPQ1FfWlmaQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"property-expr": "property-expr@2.0.6",
|
|
||||||
"tiny-case": "tiny-case@1.0.3",
|
|
||||||
"toposort": "toposort@2.0.2",
|
|
||||||
"type-fest": "type-fest@2.19.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"redirects": {
|
|
||||||
"https://deno.land/x/typeschema/mod.ts": "https://deno.land/x/typeschema@v0.12.1/mod.ts",
|
|
||||||
"https://deno.land/x/validasaur/mod.ts": "https://deno.land/x/validasaur@v0.15.0/mod.ts",
|
|
||||||
"https://deno.land/x/zod/mod.ts": "https://deno.land/x/zod@v3.22.4/mod.ts"
|
|
||||||
},
|
|
||||||
"remote": {
|
"remote": {
|
||||||
"https://deno.land/std@0.208.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
|
"https://deno.land/std@0.208.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
|
||||||
"https://deno.land/std@0.208.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
|
"https://deno.land/std@0.208.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
|
||||||
"https://deno.land/std@0.208.0/bytes/concat.ts": "d3d420badeb4f26a0f2f3ce343725f937326aff1b4634b25341b12b27353aac4",
|
"https://deno.land/std@0.208.0/bytes/concat.ts": "d3d420badeb4f26a0f2f3ce343725f937326aff1b4634b25341b12b27353aac4",
|
||||||
"https://deno.land/std@0.208.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219",
|
"https://deno.land/std@0.208.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219",
|
||||||
"https://deno.land/std@0.208.0/encoding/_util.ts": "f368920189c4fe6592ab2e93bd7ded8f3065b84f95cd3e036a4a10a75649dcba",
|
|
||||||
"https://deno.land/std@0.208.0/encoding/hex.ts": "a384101d02cd87036c708f540825feb5b073c8527a00d635e48b2e69c992a5f9",
|
|
||||||
"https://deno.land/std@0.208.0/io/buffer.ts": "11acaeae3dc0e491a335d82915e09e9f8afd53ecb82562515e5951e78f69b076",
|
"https://deno.land/std@0.208.0/io/buffer.ts": "11acaeae3dc0e491a335d82915e09e9f8afd53ecb82562515e5951e78f69b076",
|
||||||
"https://deno.land/std@0.208.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946",
|
"https://deno.land/std@0.208.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946",
|
||||||
"https://deno.land/std@0.208.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a",
|
"https://deno.land/std@0.208.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a",
|
||||||
|
@ -436,19 +108,6 @@
|
||||||
"https://deno.land/std@0.208.0/streams/writable_stream_from_writer.ts": "057bdfc6c08e34a8a86e2d61efe7354a8a0976260cd1437de8b7e64bcefaa9b9",
|
"https://deno.land/std@0.208.0/streams/writable_stream_from_writer.ts": "057bdfc6c08e34a8a86e2d61efe7354a8a0976260cd1437de8b7e64bcefaa9b9",
|
||||||
"https://deno.land/std@0.208.0/streams/write_all.ts": "8fc9525c16eb60eb851274d4693ecd35c2177620239fbdfa8f5aacb32702f2cb",
|
"https://deno.land/std@0.208.0/streams/write_all.ts": "8fc9525c16eb60eb851274d4693ecd35c2177620239fbdfa8f5aacb32702f2cb",
|
||||||
"https://deno.land/std@0.208.0/streams/writer_from_stream_writer.ts": "71d7a26f2a2b955794a7318856ad64d9eade36467d930b40698d3d5bfb0ce3b4",
|
"https://deno.land/std@0.208.0/streams/writer_from_stream_writer.ts": "71d7a26f2a2b955794a7318856ad64d9eade36467d930b40698d3d5bfb0ce3b4",
|
||||||
"https://deno.land/std@0.208.0/streams/zip_readable_streams.ts": "721c5bce8862c8225e60995b2d61c7b1b1d5b5178b60d303a01f453d5c26bb62",
|
"https://deno.land/std@0.208.0/streams/zip_readable_streams.ts": "721c5bce8862c8225e60995b2d61c7b1b1d5b5178b60d303a01f453d5c26bb62"
|
||||||
"https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4",
|
|
||||||
"https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#!/bin/env -S deno run -A --unstable
|
#!/bin/env -S deno run -A --unstable
|
||||||
|
|
||||||
import { daemon_send, new_cmd_disable, new_cmd_enable, new_cmd_status, new_cmd_stop } from "./src/lib.ts";
|
import { daemon_send, new_cmd_disable, new_cmd_enable, new_cmd_status, new_cmd_stop } from "./src/lib.ts";
|
||||||
import { ContainerConfig } from "./src/lib/config.ts";
|
import { load_all_container_configs, new_container_config, save_container_config } from "./src/lib/config.ts";
|
||||||
import { load_base, new_container_context } from "./src/lib/create.ts";
|
import { load_base, new_container_context } from "./src/lib/create.ts";
|
||||||
import { container_paths, socket_path } from "./src/lib/paths.ts";
|
import { container_paths, socket_path } from "./src/lib/paths.ts";
|
||||||
import { async_collect } from "./src/lib/utils.ts";
|
|
||||||
import { log_from, run } from "./src/lib/utils.ts";
|
import { log_from, run } from "./src/lib/utils.ts";
|
||||||
|
|
||||||
const log = log_from("control");
|
const log = log_from("control");
|
||||||
|
@ -79,16 +78,16 @@ Commands:
|
||||||
export async function create(name: string, base_name: string) {
|
export async function create(name: string, base_name: string) {
|
||||||
log("loading base", base_name);
|
log("loading base", base_name);
|
||||||
const base = await load_base(base_name);
|
const base = await load_base(base_name);
|
||||||
const known_containers = await async_collect(ContainerConfig.load_all());
|
const known_containers = await load_all_container_configs();
|
||||||
|
|
||||||
const paths = container_paths(name);
|
const paths = container_paths(name);
|
||||||
await Deno.mkdir(paths.base);
|
await Deno.mkdir(paths.base);
|
||||||
const configuration = ContainerConfig.new_default(name);
|
const configuration = new_container_config(name);
|
||||||
|
|
||||||
log("creating the container", name, "at", paths.base);
|
log("creating the container", name, "at", paths.base);
|
||||||
const context = new_container_context(paths.root, configuration, known_containers);
|
const context = new_container_context(paths.root, configuration, known_containers);
|
||||||
await Deno.mkdir(paths.root);
|
await Deno.mkdir(paths.root);
|
||||||
await run("sudo", "chown", "root:root", paths.root);
|
await run("sudo", "chown", "root:root", paths.root);
|
||||||
await base.build(context);
|
await base.build(context);
|
||||||
await configuration.save();
|
await save_container_config(configuration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
#!/bin/env -S deno run -A --unstable
|
#!/bin/env -S deno run -A --unstable
|
||||||
|
|
||||||
import { daemon_listen, Runner } from "./src/lib.ts";
|
import { daemon_listen, Runner, start_runner } from "./src/lib.ts";
|
||||||
import { ContainerConfig } from "./src/lib/config.ts";
|
import { load_container_config } from "./src/lib/config.ts";
|
||||||
import { NginxController } from "./src/lib/nginx.ts";
|
|
||||||
import { socket_path, state_path } from "./src/lib/paths.ts";
|
import { socket_path, state_path } from "./src/lib/paths.ts";
|
||||||
import { log_from, sleep } from "./src/lib/utils.ts";
|
import { log_from, sleep } from "./src/lib/utils.ts";
|
||||||
|
|
||||||
const log = log_from("daemon");
|
const log = log_from("daemon");
|
||||||
|
|
||||||
|
if (import.meta.main) await main();
|
||||||
async function main() {
|
async function main() {
|
||||||
const state = await State.load();
|
const state = await create_state();
|
||||||
const server = await daemon_listen(socket_path());
|
const server = await daemon_listen(socket_path());
|
||||||
log("listening to", socket_path());
|
log("listening to", socket_path());
|
||||||
|
|
||||||
const finish = termination_function(state, server.server);
|
async function finish() {
|
||||||
|
log("stopping");
|
||||||
|
server.server.close();
|
||||||
|
for (const runner of state.enabled.values()) {
|
||||||
|
try {
|
||||||
|
await runner.stop();
|
||||||
|
} catch (_) { /* on s'en fou */ }
|
||||||
|
}
|
||||||
|
await Deno.remove(socket_path());
|
||||||
|
Deno.exit(0);
|
||||||
|
}
|
||||||
Deno.addSignalListener("SIGINT", finish);
|
Deno.addSignalListener("SIGINT", finish);
|
||||||
|
|
||||||
for await (const { cmd, respond } of server) {
|
for await (const { cmd, respond } of server) {
|
||||||
|
@ -77,71 +87,41 @@ async function main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class State {
|
async function create_state() {
|
||||||
enabled;
|
const self = {
|
||||||
nginx;
|
enabled: new Map<string, Runner>(),
|
||||||
|
async enable(name: string) {
|
||||||
constructor() {
|
if (self.enabled.has(name)) throw new Error("Container already enabled");
|
||||||
this.enabled = new Map<string, Runner>();
|
log("loading container", name);
|
||||||
this.nginx = new NginxController("barnulf.net", "/etc/nginx/sites-enabled");
|
const config = await load_container_config(name); // TODO
|
||||||
}
|
if (config === null) throw new Error("can't read config");
|
||||||
|
log("starting container", name);
|
||||||
static async load() {
|
self.enabled.set(name, await start_runner(config));
|
||||||
const result = new State();
|
log("container", name, "started");
|
||||||
|
},
|
||||||
|
async disable(name: string) {
|
||||||
|
const container = self.enabled.get(name);
|
||||||
|
if (container === undefined) throw new Error("Container not found");
|
||||||
|
await container.stop();
|
||||||
|
self.enabled.delete(name);
|
||||||
|
},
|
||||||
|
list() {
|
||||||
|
return Array.from(self.enabled.keys());
|
||||||
|
},
|
||||||
|
async save() {
|
||||||
|
const content = JSON.stringify({ enabled: self.list() });
|
||||||
|
await Deno.writeTextFile(state_path(), content);
|
||||||
|
},
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
log("trying to recover state from", state_path());
|
log("trying to recover state from", state_path());
|
||||||
const loaded = JSON.parse(await Deno.readTextFile(state_path()));
|
const loaded = JSON.parse(await Deno.readTextFile(state_path()));
|
||||||
for (const name of loaded.enabled ?? []) {
|
for (const name of loaded.enabled ?? []) {
|
||||||
await result.enable(name);
|
await self.enable(name);
|
||||||
}
|
}
|
||||||
log("successfully loaded from", state_path());
|
log("successfully loaded from", state_path());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log("experienced failure", error);
|
log("experienced failure", error);
|
||||||
}
|
}
|
||||||
return result;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
async enable(name: string) {
|
|
||||||
if (this.enabled.has(name)) throw new Error("Container already enabled");
|
|
||||||
log("loading container", name);
|
|
||||||
const config = await ContainerConfig.load(name); // TODO
|
|
||||||
if (config === null) throw new Error("can't read config");
|
|
||||||
log("starting container", name);
|
|
||||||
const runner = new Runner(config, this.nginx);
|
|
||||||
runner.start();
|
|
||||||
this.enabled.set(name, runner);
|
|
||||||
log("container", name, "started");
|
|
||||||
}
|
|
||||||
|
|
||||||
async disable(name: string) {
|
|
||||||
const container = this.enabled.get(name);
|
|
||||||
if (container === undefined) throw new Error("Container not found");
|
|
||||||
await container.stop();
|
|
||||||
this.enabled.delete(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
list() {
|
|
||||||
return Array.from(this.enabled.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
async save() {
|
|
||||||
const content = JSON.stringify({ enabled: this.list() });
|
|
||||||
await Deno.writeTextFile(state_path(), content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function termination_function(state: State, server: Deno.Listener<Deno.Conn>) {
|
|
||||||
return async function finish() {
|
|
||||||
log("stopping");
|
|
||||||
server.close();
|
|
||||||
for (const runner of state.enabled.values()) {
|
|
||||||
try {
|
|
||||||
await runner.stop();
|
|
||||||
} catch (_) { /* on s'en fou */ }
|
|
||||||
}
|
|
||||||
await Deno.remove(socket_path());
|
|
||||||
Deno.exit(0);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.main) await main();
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { lines, log_from, loop_process, run } from "./lib/utils.ts";
|
||||||
import { ContainerConfig } from "./lib/config.ts";
|
import { ContainerConfig } from "./lib/config.ts";
|
||||||
import { container_paths } from "./lib/paths.ts";
|
import { container_paths } from "./lib/paths.ts";
|
||||||
import { container_command } from "./lib/nspawn.ts";
|
import { container_command } from "./lib/nspawn.ts";
|
||||||
import { LoopProcess } from "./lib/utils.ts";
|
|
||||||
import { NginxController } from "./lib/nginx.ts";
|
|
||||||
|
|
||||||
const log = log_from("lib");
|
const log = log_from("lib");
|
||||||
|
|
||||||
|
@ -68,57 +66,28 @@ export async function daemon_send(sock_path: string, command: Cmd) {
|
||||||
return await toText(request.readable);
|
return await toText(request.readable);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Runner {
|
export type Runner = Awaited<ReturnType<typeof start_runner>>;
|
||||||
name;
|
export function start_runner(config: ContainerConfig) {
|
||||||
config;
|
const { name } = config;
|
||||||
nginx;
|
const paths = container_paths(name);
|
||||||
loop;
|
const command = container_command(name, paths.root, {
|
||||||
|
|
||||||
constructor(config: ContainerConfig, nginx: NginxController) {
|
|
||||||
this.name = config.name;
|
|
||||||
this.config = config;
|
|
||||||
this.nginx = nginx;
|
|
||||||
this.loop = null as LoopProcess | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.update_proxies();
|
|
||||||
this.start_process();
|
|
||||||
}
|
|
||||||
|
|
||||||
private start_process() {
|
|
||||||
const paths = container_paths(this.name);
|
|
||||||
const command = container_command(this.name, paths.root, {
|
|
||||||
boot: true,
|
boot: true,
|
||||||
veth: true,
|
veth: true,
|
||||||
redirections: this.config.redirections,
|
ports: config.redirects,
|
||||||
cmd_opts: {
|
cmd_opts: {
|
||||||
stdin: "null",
|
stdin: "null",
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
},
|
},
|
||||||
syscall_filter: ["add_key", "keyctl", "bpf"],
|
syscall_filter: ["add_key", "keyctl", "bpf"],
|
||||||
});
|
});
|
||||||
this.loop = loop_process(command, {
|
const container_loop = loop_process(command, {
|
||||||
on_start: () => log("container", this.name, "started"),
|
on_start: () => log("container", name, "started"),
|
||||||
on_stop: () => log("container", this.name, "stopped"),
|
on_stop: () => log("container", name, "stopped"),
|
||||||
});
|
});
|
||||||
}
|
return {
|
||||||
|
name,
|
||||||
async stop() {
|
config,
|
||||||
await this.loop?.kill();
|
container_loop,
|
||||||
}
|
stop: () => container_loop.kill(),
|
||||||
|
};
|
||||||
async update_proxies() {
|
|
||||||
const paths = container_paths(this.name);
|
|
||||||
await Deno.mkdir(paths.sites, { recursive: true });
|
|
||||||
const sites = new Set<string>();
|
|
||||||
for (const redir of this.config.redirections) {
|
|
||||||
if (redir.kind !== "http") continue;
|
|
||||||
await this.nginx.add_proxy(redir.domain, redir.port, paths.sites);
|
|
||||||
sites.add(redir.domain);
|
|
||||||
}
|
|
||||||
for await (const domains of this.nginx.read_all_in_dir(paths.sites)) {
|
|
||||||
if (!sites.has(domains)) this.nginx.remove_proxy(domains, paths.sites);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,106 +1,59 @@
|
||||||
import { container_paths, containers_path, state_config_path } from "./paths.ts";
|
import { container_paths, containers_path, state_config_path } from "./paths.ts";
|
||||||
import { log_from } from "./utils.ts";
|
import { exists, log_from } from "./utils.ts";
|
||||||
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
|
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
|
||||||
|
|
||||||
const log = log_from("config");
|
const log = log_from("config");
|
||||||
|
|
||||||
export type StateConfig = ReturnType<typeof new_config>;
|
export function new_container_config(name: string): ContainerConfig {
|
||||||
export function new_config(app_key: string, app_secret: string, consumer_key: string) {
|
return {
|
||||||
return { app_key, app_secret, consumer_key };
|
name,
|
||||||
}
|
version: 0,
|
||||||
|
redirects: [] as [number, number][],
|
||||||
export async function load_state_config() {
|
|
||||||
try {
|
|
||||||
const content = await Deno.readTextFile(state_config_path());
|
|
||||||
return JSON.parse(content) as StateConfig;
|
|
||||||
} catch (_) {
|
|
||||||
const result = new_config("APP_KEY", "APP_SECRET", "CONSUMER_KEY");
|
|
||||||
const content = JSON.stringify(result);
|
|
||||||
await Deno.writeTextFile(state_config_path(), content);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTAINER_CONFIG_VERSION = 1;
|
|
||||||
|
|
||||||
export class ContainerConfig {
|
|
||||||
name;
|
|
||||||
redirections;
|
|
||||||
|
|
||||||
constructor(name: string, redirections: ContainerConfigRedirection[]) {
|
|
||||||
this.name = name;
|
|
||||||
this.redirections = redirections;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static new_default(name: string) {
|
|
||||||
return new ContainerConfig(name, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async load(name: string) {
|
|
||||||
const path = container_paths(name).configuration;
|
|
||||||
const content = await Deno.readTextFile(path);
|
|
||||||
return ContainerConfig.deserialize(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async save() {
|
|
||||||
const path = container_paths(this.name).configuration;
|
|
||||||
await Deno.writeTextFile(path, this.serialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async *load_all() {
|
|
||||||
for await (const { isDirectory, name } of Deno.readDir(containers_path())) {
|
|
||||||
if (!isDirectory) continue;
|
|
||||||
yield await ContainerConfig.load(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static deserialize(raw: string) {
|
|
||||||
const unknown = JSON.parse(raw);
|
|
||||||
const parsed = parse(unknown);
|
|
||||||
return new ContainerConfig(parsed.name, parsed.redirections);
|
|
||||||
}
|
|
||||||
|
|
||||||
public serialize() {
|
|
||||||
const raw: SerializedContainerConfig = {
|
|
||||||
version: CONTAINER_CONFIG_VERSION,
|
|
||||||
name: this.name,
|
|
||||||
redirections: this.redirections,
|
|
||||||
};
|
};
|
||||||
return JSON.stringify(raw, null, 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public *used_host_ports() {
|
export type ContainerConfig = ReturnType<typeof parse_container_config>;
|
||||||
for (const redir of this.redirections) {
|
export function parse_container_config(json: string) {
|
||||||
if (redir.kind === "http") yield redir.port;
|
|
||||||
if (redir.kind === "port") yield redir.from;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse(input: unknown) {
|
|
||||||
const redir_port = z.object({
|
|
||||||
kind: z.literal("port"),
|
|
||||||
from: z.number(),
|
|
||||||
to: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const redir_http = z.object({
|
|
||||||
kind: z.literal("http"),
|
|
||||||
tls: z.boolean(),
|
|
||||||
port: z.number(),
|
|
||||||
domain: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO : redir DNS SRV
|
|
||||||
|
|
||||||
const redirection = redir_http.or(redir_port);
|
|
||||||
|
|
||||||
return z.object({
|
return z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
version: z.number(),
|
version: z.number(),
|
||||||
redirections: z.array(redirection),
|
redirects: z.array(
|
||||||
}).parse(input);
|
z
|
||||||
|
.tuple([
|
||||||
|
z.number(),
|
||||||
|
z.number(),
|
||||||
|
])
|
||||||
|
.or(z.tuple([
|
||||||
|
z.number(),
|
||||||
|
z.number(),
|
||||||
|
z.literal("tcp").or(z.literal("udp")),
|
||||||
|
])),
|
||||||
|
),
|
||||||
|
}).parse(JSON.parse(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
type SerializedContainerConfig = ReturnType<typeof parse>;
|
export async function load_container_config(name: string) {
|
||||||
export type ContainerConfigRedirection = SerializedContainerConfig["redirections"][0];
|
const config_path = container_paths(name).configuration;
|
||||||
|
log("loading config for", name);
|
||||||
|
if (!exists(config_path)) return null;
|
||||||
|
const content = await Deno.readTextFile(config_path);
|
||||||
|
const read = parse_container_config(content);
|
||||||
|
const default_ = new_container_config(name);
|
||||||
|
if (read.version < default_.version) throw new Error("read conf version is outdated");
|
||||||
|
return { ...default_, ...read } as ContainerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function save_container_config(config: ContainerConfig) {
|
||||||
|
log("saving config of", config.name);
|
||||||
|
const config_path = container_paths(config.name).configuration;
|
||||||
|
const serialized = JSON.stringify(config, null, 4);
|
||||||
|
await Deno.writeTextFile(config_path, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function load_all_container_configs() {
|
||||||
|
const names = Array.from(Deno.readDirSync(containers_path()))
|
||||||
|
.filter((e) => e.isDirectory)
|
||||||
|
.map((e) => e.name);
|
||||||
|
const results = await Promise.all(names.map((name) => load_container_config(name)));
|
||||||
|
return results.filter((item) => item != null) as ContainerConfig[];
|
||||||
|
}
|
||||||
|
|
|
@ -37,11 +37,11 @@ export function new_container_context(root_dir: string, config: ContainerConfig,
|
||||||
},
|
},
|
||||||
redirect: (from: number, to: number) => {
|
redirect: (from: number, to: number) => {
|
||||||
log("redirects", from, "to", to);
|
log("redirects", from, "to", to);
|
||||||
config.redirections.push({ kind: "port", from, to });
|
config.redirects.push([from, to]);
|
||||||
},
|
},
|
||||||
available_port: (target: number) => {
|
available_port: (target: number) => {
|
||||||
const used_by_known_containers = known_containers.map((c) => [...c.used_host_ports()]).flat();
|
const used_by_known_containers = known_containers.map((c) => c.redirects[0]).flat();
|
||||||
const used_by_self = config.used_host_ports();
|
const used_by_self = config.redirects.map((r) => r[0]);
|
||||||
const used = [...used_by_known_containers, ...used_by_self];
|
const used = [...used_by_known_containers, ...used_by_self];
|
||||||
let result = target;
|
let result = target;
|
||||||
while (used.includes(result)) result += 1;
|
while (used.includes(result)) result += 1;
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { run } from "./utils.ts";
|
|
||||||
import * as path from "https://deno.land/std@0.214.0/path/mod.ts";
|
|
||||||
|
|
||||||
export class NginxController {
|
|
||||||
private proxy_target_domain;
|
|
||||||
private enabled_conf_dir;
|
|
||||||
|
|
||||||
constructor(proxy_target_domain: string, enabled_conf_dir: string) {
|
|
||||||
this.proxy_target_domain = proxy_target_domain;
|
|
||||||
this.enabled_conf_dir = enabled_conf_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async add_proxy(domain: string, port: number, conf_dir: string) {
|
|
||||||
const conf_file_content = `
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name ${domain};
|
|
||||||
location / {
|
|
||||||
proxy_pass http://${this.proxy_target_domain}:${port};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const conf_file_path = path.join(conf_dir, domain + ".conf");
|
|
||||||
const enabled_conf_file_path = path.join(this.enabled_conf_dir, domain + ".conf");
|
|
||||||
await Deno.writeTextFile(conf_file_path, conf_file_content);
|
|
||||||
await run("ln", "-s", await Deno.realPath(conf_file_path), enabled_conf_file_path);
|
|
||||||
await this.reload();
|
|
||||||
return conf_file_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async remove_proxy(domain: string, conf_dir: string) {
|
|
||||||
const conf_file_path = path.join(conf_dir, domain + ".conf");
|
|
||||||
const enabled_conf_file_path = path.join(this.enabled_conf_dir, domain + ".conf");
|
|
||||||
await Deno.remove(enabled_conf_file_path, { recursive: true });
|
|
||||||
await Deno.remove(conf_file_path, { recursive: true });
|
|
||||||
await this.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async *read_all_in_dir(conf_dir: string) {
|
|
||||||
for await (const { name } of Deno.readDir(conf_dir)) {
|
|
||||||
const [domain, rest] = name.split(".conf");
|
|
||||||
if (rest !== "") continue;
|
|
||||||
yield domain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async reload() {
|
|
||||||
await run("systemctl", "restart", "nginx");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,13 @@
|
||||||
// wrapper
|
// wrapper
|
||||||
|
|
||||||
import { log_from, sleep } from "./utils.ts";
|
import { log_from, sleep } from "./utils.ts";
|
||||||
import { ContainerConfigRedirection } from "./config.ts";
|
|
||||||
|
|
||||||
const log = log_from("nspawn");
|
const log = log_from("nspawn");
|
||||||
|
|
||||||
export function container_command(name: string, directory: string, opts?: {
|
export function container_command(name: string, directory: string, opts?: {
|
||||||
veth?: boolean;
|
veth?: boolean;
|
||||||
boot?: boolean;
|
boot?: boolean;
|
||||||
redirections?: ContainerConfigRedirection[];
|
ports?: ([number, number] | [number, number, "tcp" | "udp"])[];
|
||||||
cmd_opts?: Deno.CommandOptions;
|
cmd_opts?: Deno.CommandOptions;
|
||||||
syscall_filter?: string[];
|
syscall_filter?: string[];
|
||||||
}) {
|
}) {
|
||||||
|
@ -18,12 +17,8 @@ export function container_command(name: string, directory: string, opts?: {
|
||||||
];
|
];
|
||||||
if (opts?.veth ?? false) args.push("--network-veth");
|
if (opts?.veth ?? false) args.push("--network-veth");
|
||||||
if (opts?.boot ?? false) args.push("--boot");
|
if (opts?.boot ?? false) args.push("--boot");
|
||||||
for (const redir of opts?.redirections ?? []) {
|
for (const [from, to, proto] of opts?.ports ?? []) {
|
||||||
if (redir.kind === "http") args.push(`--port=${redir.port}`);
|
args.push(proto === undefined ? `--port=${from}:${to}` : `--port=${proto}:${from}:${to}`);
|
||||||
if (redir.kind === "port") {
|
|
||||||
args.push(`--port=tcp:${redir.from}:${redir.to}`);
|
|
||||||
args.push(`--port=udp:${redir.from}:${redir.to}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (const call of opts?.syscall_filter ?? []) args.push(`--system-call-filter=${call}`);
|
for (const call of opts?.syscall_filter ?? []) args.push(`--system-call-filter=${call}`);
|
||||||
const command = new Deno.Command("systemd-nspawn", { ...opts?.cmd_opts, args });
|
const command = new Deno.Command("systemd-nspawn", { ...opts?.cmd_opts, args });
|
||||||
|
|
|
@ -18,8 +18,7 @@ export function container_paths(name: string) {
|
||||||
const base = containers_path() + "/" + name;
|
const base = containers_path() + "/" + name;
|
||||||
const configuration = base + "/config.json";
|
const configuration = base + "/config.json";
|
||||||
const root = base + "/root";
|
const root = base + "/root";
|
||||||
const sites = base + "/sites";
|
return { base, configuration, root };
|
||||||
return { base, configuration, root, sites };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function state_path() {
|
export function state_path() {
|
||||||
|
@ -35,7 +34,3 @@ export function base_paths(name: string) {
|
||||||
const module = base + "/module.ts";
|
const module = base + "/module.ts";
|
||||||
return { base, module };
|
return { base, module };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function state_config_path() {
|
|
||||||
return instance_root_path() + "/local/config.json";
|
|
||||||
}
|
|
||||||
|
|
|
@ -53,22 +53,17 @@ export function loop_process(
|
||||||
on_start: opts?.on_start ?? async_noop,
|
on_start: opts?.on_start ?? async_noop,
|
||||||
on_stop: opts?.on_stop ?? async_noop,
|
on_stop: opts?.on_stop ?? async_noop,
|
||||||
};
|
};
|
||||||
const control = {
|
|
||||||
do_continue: true,
|
|
||||||
child_process: null as null | Deno.ChildProcess,
|
|
||||||
};
|
|
||||||
const kill_sig = channel<"kill">();
|
const kill_sig = channel<"kill">();
|
||||||
kill_sig.receive().then(() => {
|
|
||||||
control.do_continue = false;
|
|
||||||
try {
|
|
||||||
control.child_process?.kill();
|
|
||||||
} catch (_) { /* isok */ }
|
|
||||||
});
|
|
||||||
async function launch() {
|
async function launch() {
|
||||||
while (control.do_continue) {
|
while (true) {
|
||||||
await events.on_start();
|
await events.on_start();
|
||||||
control.child_process = command.spawn();
|
const child_process = command.spawn();
|
||||||
await control.child_process.output();
|
const result = await Promise.any([kill_sig.receive(), child_process.output()]);
|
||||||
|
if (result === "kill") {
|
||||||
|
await events.on_stop();
|
||||||
|
child_process.kill();
|
||||||
|
break;
|
||||||
|
}
|
||||||
await events.on_stop();
|
await events.on_stop();
|
||||||
await sleep(opts?.delay ?? 500);
|
await sleep(opts?.delay ?? 500);
|
||||||
}
|
}
|
||||||
|
@ -98,9 +93,3 @@ export function log_from(...prefixes: string[]) {
|
||||||
console.log(prefix, ...args);
|
console.log(prefix, ...args);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function async_collect<T>(gen: AsyncIterable<T>) {
|
|
||||||
const result = [] as T[];
|
|
||||||
for await (const item of gen) result.push(item);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue