From c8afcb43d22f577bbe8815aab7dd105c0f2b0ac3 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 14:04:00 +0100 Subject: [PATCH 1/9] add state config loading logic --- instance/src/lib/config.ts | 35 +++++++++++++++++++++++++---------- instance/src/lib/paths.ts | 4 ++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/instance/src/lib/config.ts b/instance/src/lib/config.ts index 166b584..00bb2c2 100644 --- a/instance/src/lib/config.ts +++ b/instance/src/lib/config.ts @@ -18,16 +18,14 @@ export function parse_container_config(json: string) { name: z.string(), version: z.number(), redirects: z.array( - z - .tuple([ - z.number(), - z.number(), - ]) - .or(z.tuple([ - z.number(), - z.number(), - z.literal("tcp").or(z.literal("udp")), - ])), + z.tuple([ + z.number(), + z.number(), + ]).or(z.tuple([ + z.number(), + z.number(), + z.literal("tcp").or(z.literal("udp")), + ])), ), }).parse(JSON.parse(json)); } @@ -57,3 +55,20 @@ export async function load_all_container_configs() { const results = await Promise.all(names.map((name) => load_container_config(name))); return results.filter((item) => item != null) as ContainerConfig[]; } + +export type StateConfig = ReturnType; +export function new_config(app_key: string, app_secret: string, consumer_key: string) { + return { app_key, app_secret, consumer_key }; +} + +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; + } +} diff --git a/instance/src/lib/paths.ts b/instance/src/lib/paths.ts index 622a905..3fa1aa9 100644 --- a/instance/src/lib/paths.ts +++ b/instance/src/lib/paths.ts @@ -34,3 +34,7 @@ export function base_paths(name: string) { const module = base + "/module.ts"; return { base, module }; } + +export function state_config_path() { + return instance_root_path() + "/local/config.json"; +} From 6f92727bb3ee934aaeace4feb09b7ba6f7f5158b Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 20:15:35 +0100 Subject: [PATCH 2/9] add nginx wrapper and refactor --- deno.lock | 343 ++++++++++++++++++++++++++++++++++++- instance/control.ts | 9 +- instance/daemon.ts | 10 +- instance/src/lib.ts | 57 +++--- instance/src/lib/config.ts | 138 +++++++++------ instance/src/lib/create.ts | 6 +- instance/src/lib/nginx.ts | 42 +++++ instance/src/lib/nspawn.ts | 11 +- instance/src/lib/utils.ts | 6 + 9 files changed, 530 insertions(+), 92 deletions(-) create mode 100644 instance/src/lib/nginx.ts diff --git a/deno.lock b/deno.lock index 8f12033..1c8cebf 100644 --- a/deno.lock +++ b/deno.lock @@ -1,10 +1,338 @@ { "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": { "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/bytes/concat.ts": "d3d420badeb4f26a0f2f3ce343725f937326aff1b4634b25341b12b27353aac4", "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/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", "https://deno.land/std@0.208.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", @@ -108,6 +436,19 @@ "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/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" } } diff --git a/instance/control.ts b/instance/control.ts index 0942da3..88197f4 100755 --- a/instance/control.ts +++ b/instance/control.ts @@ -1,9 +1,10 @@ #!/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 { load_all_container_configs, new_container_config, save_container_config } from "./src/lib/config.ts"; +import { ContainerConfig } from "./src/lib/config.ts"; import { load_base, new_container_context } from "./src/lib/create.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"; const log = log_from("control"); @@ -78,16 +79,16 @@ Commands: export async function create(name: string, base_name: string) { log("loading base", base_name); const base = await load_base(base_name); - const known_containers = await load_all_container_configs(); + const known_containers = await async_collect(ContainerConfig.load_all()); const paths = container_paths(name); await Deno.mkdir(paths.base); - const configuration = new_container_config(name); + const configuration = ContainerConfig.new_default(name); log("creating the container", name, "at", paths.base); const context = new_container_context(paths.root, configuration, known_containers); await Deno.mkdir(paths.root); await run("sudo", "chown", "root:root", paths.root); await base.build(context); - await save_container_config(configuration); + await configuration.save(); } diff --git a/instance/daemon.ts b/instance/daemon.ts index 5c89e8a..53364eb 100755 --- a/instance/daemon.ts +++ b/instance/daemon.ts @@ -1,7 +1,7 @@ #!/bin/env -S deno run -A --unstable -import { daemon_listen, Runner, start_runner } from "./src/lib.ts"; -import { load_container_config } from "./src/lib/config.ts"; +import { daemon_listen, Runner } from "./src/lib.ts"; +import { ContainerConfig } from "./src/lib/config.ts"; import { socket_path, state_path } from "./src/lib/paths.ts"; import { log_from, sleep } from "./src/lib/utils.ts"; @@ -93,10 +93,12 @@ async function create_state() { async enable(name: string) { if (self.enabled.has(name)) throw new Error("Container already enabled"); log("loading container", name); - const config = await load_container_config(name); // TODO + const config = await ContainerConfig.load(name); // TODO if (config === null) throw new Error("can't read config"); log("starting container", name); - self.enabled.set(name, await start_runner(config)); + const runner = new Runner(config); + runner.start(); + self.enabled.set(name, runner); log("container", name, "started"); }, async disable(name: string) { diff --git a/instance/src/lib.ts b/instance/src/lib.ts index c63c2e3..44ec2f2 100644 --- a/instance/src/lib.ts +++ b/instance/src/lib.ts @@ -5,6 +5,7 @@ import { lines, log_from, loop_process, run } from "./lib/utils.ts"; import { ContainerConfig } from "./lib/config.ts"; import { container_paths } from "./lib/paths.ts"; import { container_command } from "./lib/nspawn.ts"; +import { LoopProcess } from "./lib/utils.ts"; const log = log_from("lib"); @@ -66,28 +67,36 @@ export async function daemon_send(sock_path: string, command: Cmd) { return await toText(request.readable); } -export type Runner = Awaited>; -export function start_runner(config: ContainerConfig) { - const { name } = config; - const paths = container_paths(name); - const command = container_command(name, paths.root, { - boot: true, - veth: true, - ports: config.redirects, - cmd_opts: { - stdin: "null", - stdout: "null", - }, - syscall_filter: ["add_key", "keyctl", "bpf"], - }); - const container_loop = loop_process(command, { - on_start: () => log("container", name, "started"), - on_stop: () => log("container", name, "stopped"), - }); - return { - name, - config, - container_loop, - stop: () => container_loop.kill(), - }; +export class Runner { + name; + config; + loop: LoopProcess | null; + + constructor(config: ContainerConfig) { + this.name = config.name; + this.config = config; + this.loop = null; + } + + start() { + const paths = container_paths(name); + const command = container_command(name, paths.root, { + boot: true, + veth: true, + redirections: this.config.redirections, + cmd_opts: { + stdin: "null", + stdout: "null", + }, + syscall_filter: ["add_key", "keyctl", "bpf"], + }); + this.loop = loop_process(command, { + on_start: () => log("container", name, "started"), + on_stop: () => log("container", name, "stopped"), + }); + } + + async stop() { + await this.loop?.kill(); + } } diff --git a/instance/src/lib/config.ts b/instance/src/lib/config.ts index 00bb2c2..4afce83 100644 --- a/instance/src/lib/config.ts +++ b/instance/src/lib/config.ts @@ -1,61 +1,9 @@ import { container_paths, containers_path, state_config_path } from "./paths.ts"; -import { exists, log_from } from "./utils.ts"; +import { log_from } from "./utils.ts"; import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts"; const log = log_from("config"); -export function new_container_config(name: string): ContainerConfig { - return { - name, - version: 0, - redirects: [] as [number, number][], - }; -} - -export type ContainerConfig = ReturnType; -export function parse_container_config(json: string) { - return z.object({ - name: z.string(), - version: z.number(), - redirects: z.array( - z.tuple([ - z.number(), - z.number(), - ]).or(z.tuple([ - z.number(), - z.number(), - z.literal("tcp").or(z.literal("udp")), - ])), - ), - }).parse(JSON.parse(json)); -} - -export async function load_container_config(name: string) { - 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[]; -} - export type StateConfig = ReturnType; export function new_config(app_key: string, app_secret: string, consumer_key: string) { return { app_key, app_secret, consumer_key }; @@ -72,3 +20,87 @@ export async function load_state_config() { 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); + } + + public *used_host_ports() { + for (const redir of this.redirections) { + 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({ + name: z.string(), + version: z.number(), + redirections: z.array(redirection), + }).parse(input); +} + +type SerializedContainerConfig = ReturnType; +export type ContainerConfigRedirection = SerializedContainerConfig["redirections"][0]; diff --git a/instance/src/lib/create.ts b/instance/src/lib/create.ts index 52c78ca..6c3c30b 100644 --- a/instance/src/lib/create.ts +++ b/instance/src/lib/create.ts @@ -37,11 +37,11 @@ export function new_container_context(root_dir: string, config: ContainerConfig, }, redirect: (from: number, to: number) => { log("redirects", from, "to", to); - config.redirects.push([from, to]); + config.redirections.push({ kind: "port", from, to }); }, available_port: (target: number) => { - const used_by_known_containers = known_containers.map((c) => c.redirects[0]).flat(); - const used_by_self = config.redirects.map((r) => r[0]); + const used_by_known_containers = known_containers.map((c) => [...c.used_host_ports()]).flat(); + const used_by_self = config.used_host_ports(); const used = [...used_by_known_containers, ...used_by_self]; let result = target; while (used.includes(result)) result += 1; diff --git a/instance/src/lib/nginx.ts b/instance/src/lib/nginx.ts new file mode 100644 index 0000000..c2c3044 --- /dev/null +++ b/instance/src/lib/nginx.ts @@ -0,0 +1,42 @@ +import { run } from "./utils.ts"; +import * as path from "https://deno.land/std@0.214.0/path/mod.ts"; + +class NginxController { + proxy_target_url; + enabled_conf_dir; + + constructor(proxy_target_url: string, enabled_conf_dir: string) { + this.proxy_target_url = proxy_target_url; + this.enabled_conf_dir = enabled_conf_dir; + } + + public async add_proxy(url: string, port: number, conf_dir: string) { + const conf_file_content = ` +server { + listen 80; + listen [::]:80; + server_name ${url}; + location / { + proxy_pass http://${this.proxy_target_url}:${port}; + } +} +`; + const conf_file_path = path.join(conf_dir, url + ".conf"); + const enabled_conf_file_path = path.join(this.enabled_conf_dir, url + ".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(); + } + + public async remove_proxy(url: string, conf_dir: string) { + const conf_file_path = path.join(conf_dir, url + ".conf"); + const enabled_conf_file_path = path.join(this.enabled_conf_dir, url + ".conf"); + await Deno.remove(enabled_conf_file_path); + await Deno.remove(conf_file_path); + await this.reload(); + } + + private async reload() { + await run("systemctl", "restart", "nginx"); + } +} diff --git a/instance/src/lib/nspawn.ts b/instance/src/lib/nspawn.ts index 491f486..12b76ca 100644 --- a/instance/src/lib/nspawn.ts +++ b/instance/src/lib/nspawn.ts @@ -1,13 +1,14 @@ // wrapper import { log_from, sleep } from "./utils.ts"; +import { ContainerConfigRedirection } from "./config.ts"; const log = log_from("nspawn"); export function container_command(name: string, directory: string, opts?: { veth?: boolean; boot?: boolean; - ports?: ([number, number] | [number, number, "tcp" | "udp"])[]; + redirections?: ContainerConfigRedirection[]; cmd_opts?: Deno.CommandOptions; syscall_filter?: string[]; }) { @@ -17,8 +18,12 @@ export function container_command(name: string, directory: string, opts?: { ]; if (opts?.veth ?? false) args.push("--network-veth"); if (opts?.boot ?? false) args.push("--boot"); - for (const [from, to, proto] of opts?.ports ?? []) { - args.push(proto === undefined ? `--port=${from}:${to}` : `--port=${proto}:${from}:${to}`); + for (const redir of opts?.redirections ?? []) { + if (redir.kind === "http") args.push(`--port=${redir.port}`); + 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}`); const command = new Deno.Command("systemd-nspawn", { ...opts?.cmd_opts, args }); diff --git a/instance/src/lib/utils.ts b/instance/src/lib/utils.ts index a770923..a941ba0 100644 --- a/instance/src/lib/utils.ts +++ b/instance/src/lib/utils.ts @@ -93,3 +93,9 @@ export function log_from(...prefixes: string[]) { console.log(prefix, ...args); }; } + +export async function async_collect(gen: AsyncIterable) { + const result = [] as T[]; + for await (const item of gen) result.push(item); + return result; +} From a20745f38dcdc410a965534a973b0bc1abe43f26 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 21:01:39 +0100 Subject: [PATCH 3/9] refactor daemon state --- instance/daemon.ts | 114 +++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 50 deletions(-) diff --git a/instance/daemon.ts b/instance/daemon.ts index 53364eb..ce14c0b 100755 --- a/instance/daemon.ts +++ b/instance/daemon.ts @@ -9,21 +9,11 @@ const log = log_from("daemon"); if (import.meta.main) await main(); async function main() { - const state = await create_state(); + const state = await State.load(); const server = await daemon_listen(socket_path()); log("listening to", socket_path()); - 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); - } + const finish = termination_function(state, server.server); Deno.addSignalListener("SIGINT", finish); for await (const { cmd, respond } of server) { @@ -87,43 +77,67 @@ async function main() { } } -async function create_state() { - const self = { - enabled: new Map(), - async enable(name: string) { - if (self.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); - runner.start(); - self.enabled.set(name, runner); - 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 { - log("trying to recover state from", state_path()); - const loaded = JSON.parse(await Deno.readTextFile(state_path())); - for (const name of loaded.enabled ?? []) { - await self.enable(name); - } - log("successfully loaded from", state_path()); - } catch (error) { - log("experienced failure", error); +class State { + enabled; + + constructor() { + this.enabled = new Map(); + } + + static async load() { + const result = new State(); + try { + log("trying to recover state from", state_path()); + const loaded = JSON.parse(await Deno.readTextFile(state_path())); + for (const name of loaded.enabled ?? []) { + await result.enable(name); + } + log("successfully loaded from", state_path()); + } catch (error) { + log("experienced failure", error); + } + return result; + } + + 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); + 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); } - return self; +} + +function termination_function(state: State, server: Deno.Listener) { + 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); + }; } From 686ed543ab8e9275bc965a610d48472caa7324fb Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 21:02:50 +0100 Subject: [PATCH 4/9] fix main call --- instance/daemon.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/instance/daemon.ts b/instance/daemon.ts index ce14c0b..655d8c9 100755 --- a/instance/daemon.ts +++ b/instance/daemon.ts @@ -7,7 +7,6 @@ import { log_from, sleep } from "./src/lib/utils.ts"; const log = log_from("daemon"); -if (import.meta.main) await main(); async function main() { const state = await State.load(); const server = await daemon_listen(socket_path()); @@ -141,3 +140,5 @@ function termination_function(state: State, server: Deno.Listener) { Deno.exit(0); }; } + +if (import.meta.main) await main(); From 466239d0e6c15fd9b371d581d92cb55b87f364cb Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 23:15:07 +0100 Subject: [PATCH 5/9] enable nginx config management --- instance/daemon.ts | 5 ++++- instance/src/lib.ts | 28 +++++++++++++++++++++++++--- instance/src/lib/nginx.ts | 39 ++++++++++++++++++++++++--------------- instance/src/lib/paths.ts | 3 ++- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/instance/daemon.ts b/instance/daemon.ts index 655d8c9..35d3353 100755 --- a/instance/daemon.ts +++ b/instance/daemon.ts @@ -2,6 +2,7 @@ import { daemon_listen, Runner } from "./src/lib.ts"; import { ContainerConfig } from "./src/lib/config.ts"; +import { NginxController } from "./src/lib/nginx.ts"; import { socket_path, state_path } from "./src/lib/paths.ts"; import { log_from, sleep } from "./src/lib/utils.ts"; @@ -78,9 +79,11 @@ async function main() { class State { enabled; + nginx; constructor() { this.enabled = new Map(); + this.nginx = new NginxController("barnulf.net", "/etc/nginx/sites-enabled"); } static async load() { @@ -104,7 +107,7 @@ class State { 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); + const runner = new Runner(config, this.nginx); runner.start(); this.enabled.set(name, runner); log("container", name, "started"); diff --git a/instance/src/lib.ts b/instance/src/lib.ts index 44ec2f2..9d9d876 100644 --- a/instance/src/lib.ts +++ b/instance/src/lib.ts @@ -6,6 +6,7 @@ import { ContainerConfig } from "./lib/config.ts"; import { container_paths } from "./lib/paths.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"); @@ -70,15 +71,22 @@ export async function daemon_send(sock_path: string, command: Cmd) { export class Runner { name; config; - loop: LoopProcess | null; + nginx; + loop; - constructor(config: ContainerConfig) { + constructor(config: ContainerConfig, nginx: NginxController) { this.name = config.name; this.config = config; - this.loop = null; + this.nginx = nginx; + this.loop = null as LoopProcess | null; } start() { + this.update_proxies(); + this.start_process(); + } + + private start_process() { const paths = container_paths(name); const command = container_command(name, paths.root, { boot: true, @@ -99,4 +107,18 @@ export class Runner { async stop() { await this.loop?.kill(); } + + async update_proxies() { + const paths = container_paths(name); + await Deno.mkdir(paths.sites, { recursive: true }); + const sites = new Set(); + 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); + } + } } diff --git a/instance/src/lib/nginx.ts b/instance/src/lib/nginx.ts index c2c3044..64c3dc3 100644 --- a/instance/src/lib/nginx.ts +++ b/instance/src/lib/nginx.ts @@ -1,41 +1,50 @@ import { run } from "./utils.ts"; import * as path from "https://deno.land/std@0.214.0/path/mod.ts"; -class NginxController { - proxy_target_url; - enabled_conf_dir; +export class NginxController { + private proxy_target_domain; + private enabled_conf_dir; - constructor(proxy_target_url: string, enabled_conf_dir: string) { - this.proxy_target_url = proxy_target_url; + 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(url: string, port: number, conf_dir: string) { + public async add_proxy(domain: string, port: number, conf_dir: string) { const conf_file_content = ` server { listen 80; listen [::]:80; - server_name ${url}; + server_name ${domain}; location / { - proxy_pass http://${this.proxy_target_url}:${port}; + proxy_pass http://${this.proxy_target_domain}:${port}; } } `; - const conf_file_path = path.join(conf_dir, url + ".conf"); - const enabled_conf_file_path = path.join(this.enabled_conf_dir, url + ".conf"); + 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(url: string, conf_dir: string) { - const conf_file_path = path.join(conf_dir, url + ".conf"); - const enabled_conf_file_path = path.join(this.enabled_conf_dir, url + ".conf"); - await Deno.remove(enabled_conf_file_path); - await Deno.remove(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"); } diff --git a/instance/src/lib/paths.ts b/instance/src/lib/paths.ts index 3fa1aa9..dc3d3fc 100644 --- a/instance/src/lib/paths.ts +++ b/instance/src/lib/paths.ts @@ -18,7 +18,8 @@ export function container_paths(name: string) { const base = containers_path() + "/" + name; const configuration = base + "/config.json"; const root = base + "/root"; - return { base, configuration, root }; + const sites = base + "/sites"; + return { base, configuration, root, sites }; } export function state_path() { From 8aac0b8bde8231d5714ee7bc9b23a2c7fcf118ea Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 23:29:39 +0100 Subject: [PATCH 6/9] fix looping errors --- instance/src/lib/utils.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/instance/src/lib/utils.ts b/instance/src/lib/utils.ts index a941ba0..78f6f18 100644 --- a/instance/src/lib/utils.ts +++ b/instance/src/lib/utils.ts @@ -53,17 +53,20 @@ export function loop_process( on_start: opts?.on_start ?? 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">(); + kill_sig.receive().then(() => { + control.do_continue = false; + control.child_process?.kill(); + }); async function launch() { - while (true) { + while (control.do_continue) { await events.on_start(); - const child_process = command.spawn(); - const result = await Promise.any([kill_sig.receive(), child_process.output()]); - if (result === "kill") { - await events.on_stop(); - child_process.kill(); - break; - } + control.child_process = command.spawn(); + await control.child_process.output(); await events.on_stop(); await sleep(opts?.delay ?? 500); } From 4acf870e7b6a0ab1ee3499a02cfe67d62bb7ffca Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 23:31:41 +0100 Subject: [PATCH 7/9] fix looping errors 2 --- instance/src/lib/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/instance/src/lib/utils.ts b/instance/src/lib/utils.ts index 78f6f18..c46c003 100644 --- a/instance/src/lib/utils.ts +++ b/instance/src/lib/utils.ts @@ -60,7 +60,9 @@ export function loop_process( const kill_sig = channel<"kill">(); kill_sig.receive().then(() => { control.do_continue = false; - control.child_process?.kill(); + try { + control.child_process?.kill(); + } catch (_) { /* isok */ } }); async function launch() { while (control.do_continue) { From 752efeece8dde2cad5b0da7797bad0546fc25a68 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 3 Feb 2024 23:39:18 +0100 Subject: [PATCH 8/9] fix container name not set --- instance/src/lib.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/instance/src/lib.ts b/instance/src/lib.ts index 9d9d876..7e172c4 100644 --- a/instance/src/lib.ts +++ b/instance/src/lib.ts @@ -87,8 +87,8 @@ export class Runner { } private start_process() { - const paths = container_paths(name); - const command = container_command(name, paths.root, { + const paths = container_paths(this.name); + const command = container_command(this.name, paths.root, { boot: true, veth: true, redirections: this.config.redirections, @@ -99,8 +99,8 @@ export class Runner { syscall_filter: ["add_key", "keyctl", "bpf"], }); this.loop = loop_process(command, { - on_start: () => log("container", name, "started"), - on_stop: () => log("container", name, "stopped"), + on_start: () => log("container", this.name, "started"), + on_stop: () => log("container", this.name, "stopped"), }); } @@ -109,7 +109,7 @@ export class Runner { } async update_proxies() { - const paths = container_paths(name); + const paths = container_paths(this.name); await Deno.mkdir(paths.sites, { recursive: true }); const sites = new Set(); for (const redir of this.config.redirections) { From 7f1fe0b9370523ba366b4a0885e8c3b83b8c279a Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sun, 4 Feb 2024 00:35:39 +0100 Subject: [PATCH 9/9] fix config formatting --- instance/src/lib/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instance/src/lib/config.ts b/instance/src/lib/config.ts index 4afce83..57ec985 100644 --- a/instance/src/lib/config.ts +++ b/instance/src/lib/config.ts @@ -66,7 +66,7 @@ export class ContainerConfig { name: this.name, redirections: this.redirections, }; - return JSON.stringify(raw); + return JSON.stringify(raw, null, 4); } public *used_host_ports() {