diff --git a/container-runtime/src/Adapters/HostSystemStartOs.ts b/container-runtime/src/Adapters/HostSystemStartOs.ts index e4177044e..007f783c0 100644 --- a/container-runtime/src/Adapters/HostSystemStartOs.ts +++ b/container-runtime/src/Adapters/HostSystemStartOs.ts @@ -38,7 +38,10 @@ export class HostSystemStartOs implements Effects { constructor(readonly callbackHolder: CallbackHolder) {} id = 0 - rpcRound(method: string, params: unknown) { + rpcRound( + method: K, + params: unknown, + ) { const id = this.id++ const client = net.createConnection({ path: SOCKET_PATH }, () => { client.write( @@ -74,7 +77,7 @@ export class HostSystemStartOs implements Effects { console.error("Debug: " + res.error.data.debug) } } - reject(new Error(message)) + reject(new Error(`${message}@${method}`)) } else if (testRpcResult(res)) { resolve(res.result) } else { @@ -91,13 +94,7 @@ export class HostSystemStartOs implements Effects { }) }) } - started = - // @ts-ignore - this.method !== MAIN - ? null - : () => { - return this.rpcRound("started", null) - } + bind(...[options]: Parameters) { return this.rpcRound("bind", options) as ReturnType } @@ -131,9 +128,9 @@ export class HostSystemStartOs implements Effects { T.Effects["exportAction"] > } - exportServiceInterface( - ...[options]: Parameters - ) { + exportServiceInterface: Effects["exportServiceInterface"] = ( + ...[options]: Parameters + ) => { return this.rpcRound("exportServiceInterface", options) as ReturnType< T.Effects["exportServiceInterface"] > @@ -158,31 +155,24 @@ export class HostSystemStartOs implements Effects { T.Effects["getContainerIp"] > } - getHostnames: any = (...[allOptions]: any[]) => { + getHostInfo: Effects["getHostInfo"] = (...[allOptions]: any[]) => { const options = { ...allOptions, callback: this.callbackHolder.addCallback(allOptions.callback), } - return this.rpcRound("getHostnames", options) as ReturnType< - T.Effects["getHostnames"] - > + return this.rpcRound("getHostInfo", options) as ReturnType< + T.Effects["getHostInfo"] + > as any } - getInterface(...[options]: Parameters) { - return this.rpcRound("getInterface", { + getServiceInterface( + ...[options]: Parameters + ) { + return this.rpcRound("getServiceInterface", { ...options, callback: this.callbackHolder.addCallback(options.callback), - }) as ReturnType - } - getIPHostname(...[]: Parameters) { - return this.rpcRound("getIPHostname", null) as ReturnType< - T.Effects["getIPHostname"] - > - } - getLocalHostname(...[]: Parameters) { - return this.rpcRound("getLocalHostname", null) as ReturnType< - T.Effects["getLocalHostname"] - > + }) as ReturnType } + getPrimaryUrl(...[options]: Parameters) { return this.rpcRound("getPrimaryUrl", { ...options, @@ -196,14 +186,6 @@ export class HostSystemStartOs implements Effects { T.Effects["getServicePortForward"] > } - getServiceTorHostname( - ...[interfaceId, packageId]: Parameters - ) { - return this.rpcRound("getServiceTorHostname", { - interfaceId, - packageId, - }) as ReturnType - } getSslCertificate( ...[packageId, algorithm]: Parameters ) { @@ -223,11 +205,13 @@ export class HostSystemStartOs implements Effects { callback: this.callbackHolder.addCallback(options.callback), }) as ReturnType } - listInterface(...[options]: Parameters) { - return this.rpcRound("listInterface", { + listServiceInterfaces( + ...[options]: Parameters + ) { + return this.rpcRound("listServiceInterfaces", { ...options, callback: this.callbackHolder.addCallback(options.callback), - }) as ReturnType + }) as ReturnType } mount(...[options]: Parameters) { return this.rpcRound("mount", options) as ReturnType @@ -304,17 +288,4 @@ export class HostSystemStartOs implements Effects { T.Effects["store"]["set"] >, } - - /** - * So, this is created - * @param options - * @returns - */ - embassyGetInterface(options: { - target: "tor-key" | "tor-address" | "lan-address" - packageId: string - interface: string - }) { - return this.rpcRound("embassyGetInterface", options) as Promise - } } diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts index c9cbe9fef..815a5538e 100644 --- a/container-runtime/src/Adapters/RpcListener.ts +++ b/container-runtime/src/Adapters/RpcListener.ts @@ -11,6 +11,7 @@ import { matches, any, shape, + anyOf, } from "ts-matches" import { types as T } from "@start9labs/start-sdk" @@ -24,16 +25,28 @@ import { HostSystem } from "../Interfaces/HostSystem" import { jsonPath } from "../Models/JsonPath" import { System } from "../Interfaces/System" type MaybePromise = T | Promise -type SocketResponse = { jsonrpc: "2.0"; id: IdType } & ( - | { result: unknown } - | { - error: { - code: number - message: string - data: { details: string; debug?: string } - } - } +export const matchRpcResult = anyOf( + object({ result: any }), + object({ + error: object( + { + code: number, + message: string, + data: object( + { + details: string, + debug: any, + }, + ["details", "debug"], + ), + }, + ["data"], + ), + }), ) +export type RpcResult = typeof matchRpcResult._TYPE +type SocketResponse = { jsonrpc: "2.0"; id: IdType } & RpcResult + const SOCKET_PARENT = "/media/startos/rpc" const SOCKET_PATH = "/media/startos/rpc/service.sock" const jsonrpc = "2.0" as const @@ -186,23 +199,11 @@ export class RpcListener { input: params.input, timeout: params.timeout, }) - .then((result) => - "ok" in result - ? { - jsonrpc, - id, - result: result.ok === undefined ? null : result.ok, - } - : { - jsonrpc, - id, - error: { - code: result.err.code, - message: "Package Root Error", - data: { details: result.err.message }, - }, - }, - ) + .then((result) => ({ + jsonrpc, + id, + ...result, + })) .catch((error) => ({ jsonrpc, id, diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts index 9cbda69dd..3129ce45d 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -33,8 +33,11 @@ export class DockerProcedureContainer { await overlay.mount({ type: "assets", id: mount }, mounts[mount]) } else if (volumeMount.type === "certificate") { volumeMount - const certChain = await effects.getSslCertificate() - const key = await effects.getSslKey() + const certChain = await effects.getSslCertificate( + null, + volumeMount["interface-id"], + ) + const key = await effects.getSslKey(null, volumeMount["interface-id"]) await fs.writeFile( `${path}/${volumeMount["interface-id"]}.cert.pem`, certChain.join("\n"), diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index ca1a69d2e..d96f826d7 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -28,6 +28,9 @@ import { import { HostSystemStartOs } from "../../HostSystemStartOs" import { JsonPath, unNestPath } from "../../../Models/JsonPath" import { HostSystem } from "../../../Interfaces/HostSystem" +import { RpcResult, matchRpcResult } from "../../RpcListener" +import { ServiceInterface } from "../../../../../sdk/dist/cjs/lib/types" +import { createUtils } from "../../../../../sdk/dist/cjs/lib/util" type Optional = A | undefined | null function todo(): never { @@ -68,7 +71,7 @@ export class SystemForEmbassy implements System { input: unknown timeout?: number | undefined }, - ): Promise { + ): Promise { return this._execute(effects, options) .then((x) => matches(x) @@ -76,16 +79,14 @@ export class SystemForEmbassy implements System { object({ result: any, }), - (x) => ({ - ok: x.result, - }), + (x) => x, ) .when( object({ error: string, }), (x) => ({ - err: { + error: { code: 0, message: x.error, }, @@ -96,20 +97,34 @@ export class SystemForEmbassy implements System { "error-code": tuple(number, string), }), ({ "error-code": [code, message] }) => ({ - err: { + error: { code, message, }, }), ) - .defaultTo({ ok: x }), + .defaultTo({ result: x }), ) - .catch((error) => ({ - err: { - code: 0, - message: "" + error, - }, - })) + .catch((error: unknown) => { + if (error instanceof Error) + return { + error: { + code: 0, + message: error.name, + data: { + details: error.message, + debug: `${error?.cause ?? "[noCause]"}:${error?.stack ?? "[noStack]"}`, + }, + }, + } + if (matchRpcResult.test(error)) return error + return { + error: { + code: 0, + message: String(error), + }, + } + }) } async exit(effects: HostSystemStartOs): Promise { if (this.currentRunning) await this.currentRunning.clean() @@ -157,6 +172,7 @@ export class SystemForEmbassy implements System { return this.dependenciesAutoconfig(effects, procedures[2], input) } } + throw new Error(`Could not find the path for ${options.procedure}`) } private async init( effects: HostSystemStartOs, @@ -864,6 +880,7 @@ async function updateConfig( ) { if (!dictionary([string, unknown]).test(spec)) return if (!dictionary([string, unknown]).test(mutConfigValue)) return + const utils = createUtils(effects) for (const key in spec) { const specValue = spec[key] @@ -890,11 +907,18 @@ async function updateConfig( mutConfigValue[key] = configValue } if (matchPointerPackage.test(specValue)) { - mutConfigValue[key] = await effects.embassyGetInterface({ - target: specValue.target, - packageId: specValue["package-id"], - interface: specValue["interface"], - }) + const filled = await utils.serviceInterface + .get({ + packageId: specValue["package-id"], + id: specValue.interface, + }) + .once() + if (specValue.target === "tor-key") + throw new Error("This service uses an unsupported target TorKey") + mutConfigValue[key] = + specValue.target === "lan-address" + ? filled.addressInfo.localHostnames[0] + : filled.addressInfo.onionHostnames[0] } } } diff --git a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts index 9d2dcd4b8..95afb5fb4 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -3,6 +3,7 @@ import { unNestPath } from "../../Models/JsonPath" import { string } from "ts-matches" import { HostSystemStartOs } from "../HostSystemStartOs" import { Effects } from "../../Models/Effects" +import { RpcResult } from "../RpcListener" const LOCATION = "/usr/lib/startos/package/startos" export class SystemForStartOs implements System { private onTerm: (() => Promise) | undefined @@ -30,8 +31,8 @@ export class SystemForStartOs implements System { input: unknown timeout?: number | undefined }, - ): Promise { - return { ok: await this._execute(effects, options) } + ): Promise { + return { result: await this._execute(effects, options) } } async _execute( effects: Effects, diff --git a/container-runtime/src/Interfaces/System.ts b/container-runtime/src/Interfaces/System.ts index 7dcde3c52..86b2aa492 100644 --- a/container-runtime/src/Interfaces/System.ts +++ b/container-runtime/src/Interfaces/System.ts @@ -1,6 +1,7 @@ import { types as T } from "@start9labs/start-sdk" import { JsonPath } from "../Models/JsonPath" import { HostSystemStartOs } from "../Adapters/HostSystemStartOs" +import { RpcResult } from "../Adapters/RpcListener" export type ExecuteResult = | { ok: unknown } | { err: { code: number; message: string } } @@ -17,7 +18,7 @@ export interface System { input: unknown timeout?: number }, - ): Promise + ): Promise // sandbox( // effects: Effects, // options: { diff --git a/container-runtime/src/Models/CallbackHolder.ts b/container-runtime/src/Models/CallbackHolder.ts index 3aa4392ce..b562e8dd0 100644 --- a/container-runtime/src/Models/CallbackHolder.ts +++ b/container-runtime/src/Models/CallbackHolder.ts @@ -7,7 +7,9 @@ export class CallbackHolder { return this.root + (this.inc++).toString(36) } addCallback(callback: Function) { - return this.callbacks.set(this.newId(), callback) + const id = this.newId() + this.callbacks.set(id, callback) + return id } callCallback(index: string, args: any[]): Promise { const callback = this.callbacks.get(index) diff --git a/core/Cargo.lock b/core/Cargo.lock index ec677308d..95b7f3ca9 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -32,9 +32,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.12", "once_cell", @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ "cfg-if", "getrandom 0.2.12", @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -218,7 +218,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -229,7 +229,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -487,9 +487,9 @@ dependencies = [ [[package]] name = "bitmaps" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" [[package]] name = "bitvec" @@ -575,9 +575,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" [[package]] name = "byteorder" @@ -608,9 +608,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", @@ -686,16 +686,16 @@ dependencies = [ "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap 1.9.3", - "strsim", + "strsim 0.10.0", "termcolor", "textwrap", ] [[package]] name = "clap" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", @@ -703,26 +703,26 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", - "clap_lex 0.6.0", - "strsim", + "clap_lex 0.7.0", + "strsim 0.11.0", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -736,9 +736,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "color-eyre" @@ -879,17 +879,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - [[package]] name = "cookie" version = "0.17.0" @@ -911,23 +900,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "cookie_store" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" -dependencies = [ - "cookie 0.16.2", - "idna 0.2.3", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - [[package]] name = "cookie_store" version = "0.20.0" @@ -987,9 +959,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -1133,9 +1105,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -1156,14 +1128,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] name = "darling" -version = "0.20.3" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "c376d08ea6aa96aafe61237c7200d1241cb177b7d3a542d791f2d118e9cbb955" dependencies = [ "darling_core", "darling_macro", @@ -1171,27 +1143,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "33043dcd19068b8192064c704b3f83eb464f91f1ff527b44a4e2b08d9cdb8855" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", - "syn 2.0.48", + "strsim 0.10.0", + "syn 2.0.49", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" dependencies = [ "darling_core", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -1359,11 +1331,11 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519 2.2.3", "rand_core 0.6.4", "serde", @@ -1375,9 +1347,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" dependencies = [ "serde", ] @@ -1452,7 +1424,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -1490,9 +1462,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "eyre" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -1525,9 +1497,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" [[package]] name = "filetime" @@ -1686,7 +1658,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -1793,7 +1765,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.11", - "indexmap 2.1.0", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -1812,7 +1784,7 @@ dependencies = [ "futures-sink", "futures-util", "http 1.0.0", - "indexmap 2.1.0", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -1847,7 +1819,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", ] [[package]] @@ -1856,7 +1828,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "allocator-api2", ] @@ -1919,9 +1891,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "hex" @@ -2116,12 +2088,11 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", - "futures-channel", "futures-util", "http 1.0.0", "http-body 1.0.0", @@ -2129,14 +2100,13 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2161,17 +2131,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.3.0" @@ -2275,9 +2234,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -2286,9 +2245,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -2347,12 +2306,12 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.4", - "rustix", + "hermit-abi 0.3.6", + "libc", "windows-sys 0.52.0", ] @@ -2395,9 +2354,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -2414,7 +2373,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb52eeac20f256459e909bd4a03bb8c4fab6a1fdbb8ed52d00f644152df48ece" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", "dyn-clone", "hifijson", "indexmap 1.9.3", @@ -2466,9 +2425,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -2572,9 +2531,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -2635,12 +2594,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.7.3" @@ -2708,9 +2661,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -2733,7 +2686,7 @@ version = "0.1.0" dependencies = [ "base64 0.21.7", "color-eyre", - "ed25519-dalek 2.1.0", + "ed25519-dalek 2.1.1", "emver", "ipnet", "lazy_static", @@ -2890,28 +2843,33 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" dependencies = [ "num-traits", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -2932,9 +2890,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -2946,7 +2904,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.4", + "hermit-abi 0.3.6", "libc", ] @@ -2968,7 +2926,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -3034,7 +2992,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -3045,9 +3003,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.2.1+3.2.0" +version = "300.2.3+3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" dependencies = [ "cc", ] @@ -3223,7 +3181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.1.0", + "indexmap 2.2.3", ] [[package]] @@ -3258,7 +3216,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -3296,9 +3254,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" @@ -3359,7 +3317,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.21.0", + "toml_edit 0.21.1", ] [[package]] @@ -3422,7 +3380,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -3647,14 +3605,14 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64 0.21.7", "bytes", - "cookie 0.16.2", - "cookie_store 0.16.2", + "cookie 0.17.0", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -3671,9 +3629,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -3695,7 +3655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba529055ea150e42e4eb9c11dcd380a41025ad4d594b0cb4904ef28b037e1061" dependencies = [ "bytes", - "cookie_store 0.20.0", + "cookie_store", "reqwest", "url", ] @@ -3712,16 +3672,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom 0.2.12", "libc", "spin 0.9.8", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3760,17 +3721,17 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.2.3" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#8d714d09a327249f16f77a8f5a160a2b7cfbf380" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#c89e0abdb15dd3bed9adb5339cf0b61a96f32b50" dependencies = [ "async-stream", "async-trait", "axum 0.7.4", - "clap 4.4.18", + "clap 4.5.1", "futures", "http 1.0.0", "http-body-util", "imbl-value", - "itertools 0.12.0", + "itertools 0.12.1", "lazy_format", "lazy_static", "openssl", @@ -3800,7 +3761,7 @@ dependencies = [ [[package]] name = "rpc-toolkit-macro" version = "0.2.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#8d714d09a327249f16f77a8f5a160a2b7cfbf380" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#c89e0abdb15dd3bed9adb5339cf0b61a96f32b50" dependencies = [ "proc-macro2", "rpc-toolkit-macro-internals 0.2.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits)", @@ -3821,9 +3782,9 @@ dependencies = [ [[package]] name = "rpc-toolkit-macro-internals" version = "0.2.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#8d714d09a327249f16f77a8f5a160a2b7cfbf380" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#c89e0abdb15dd3bed9adb5339cf0b61a96f32b50" dependencies = [ - "itertools 0.12.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 1.0.109", @@ -3888,9 +3849,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags 2.4.2", "errno", @@ -3919,7 +3880,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.1", + "rustls-webpki 0.102.2", "subtle", "zeroize", ] @@ -3935,9 +3896,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" [[package]] name = "rustls-webpki" @@ -3951,9 +3912,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.1" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ "ring", "rustls-pki-types", @@ -3980,9 +3941,9 @@ dependencies = [ [[package]] name = "rustyline-async" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca4447465ceb8c01c253cc81660b242547c58e4a59c85b13294a6e70de8b9e" +checksum = "8b6eb06391513b2184f0a5405c11a4a0a5302e8be442f4c5c35267187c2b37d5" dependencies = [ "crossterm", "futures-channel", @@ -4115,16 +4076,16 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] name = "serde_json" -version = "1.0.112" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.3", "itoa", "ryu", "serde", @@ -4163,16 +4124,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.5.1" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5c9fdb6b00a489875b22efd4b78fe2b363b72265cc5f6eb2e2b9ee270e6140c" +checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" dependencies = [ "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.1.0", + "indexmap 2.2.3", "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -4180,23 +4142,23 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.5.1" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff351eb4b33600a2e138dfa0b10b65a238ea8ff8fb2387c422c5022a3e8298" +checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] name = "serde_yaml" -version = "0.9.30" +version = "0.9.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.3", "itoa", "ryu", "serde", @@ -4384,7 +4346,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools 0.12.0", + "itertools 0.12.1", "nom", "unicode_categories", ] @@ -4408,7 +4370,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "atoi", "byteorder", "bytes", @@ -4425,7 +4387,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.1.0", + "indexmap 2.2.3", "log", "memchr", "once_cell", @@ -4615,8 +4577,8 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "strsim", - "syn 2.0.48", + "strsim 0.10.0", + "syn 2.0.49", "unicode-width", ] @@ -4647,7 +4609,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01f8f4ea73476c0aa5d5e6a75ce1e8634e2c3f82005ef3bbed21547ac57f2bf7" dependencies = [ - "ed25519-dalek 2.1.0", + "ed25519-dalek 2.1.1", "p256", "p384", "p521", @@ -4680,18 +4642,18 @@ dependencies = [ "bytes", "chrono", "ciborium", - "clap 4.4.18", + "clap 4.5.1", "color-eyre", "console", "console-subscriber", "cookie 0.18.0", - "cookie_store 0.20.0", + "cookie_store", "current_platform", "digest 0.10.7", "divrem", "ed25519 2.2.3", "ed25519-dalek 1.0.1", - "ed25519-dalek 2.1.0", + "ed25519-dalek 2.1.1", "emver", "fd-lock-rs", "futures", @@ -4703,13 +4665,13 @@ dependencies = [ "imbl", "imbl-value", "include_dir", - "indexmap 2.1.0", + "indexmap 2.2.3", "indicatif", "integer-encoding", "ipnet", "iprange", "isocountry", - "itertools 0.12.0", + "itertools 0.12.1", "jaq-core", "jaq-std", "josekit", @@ -4766,7 +4728,7 @@ dependencies = [ "tokio-tar", "tokio-tungstenite", "tokio-util", - "toml 0.8.8", + "toml 0.8.10", "torut", "tracing", "tracing-error", @@ -4824,6 +4786,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "subtle" version = "2.5.0" @@ -4843,9 +4811,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", @@ -4898,13 +4866,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", "windows-sys 0.52.0", ] @@ -4931,9 +4898,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thingbuf" @@ -4947,22 +4914,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -4988,12 +4955,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -5008,10 +4976,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -5041,9 +5010,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -5077,7 +5046,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5181,14 +5150,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.22.6", ] [[package]] @@ -5206,24 +5175,35 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.3", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.3", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +dependencies = [ + "indexmap 2.2.3", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.1", ] [[package]] @@ -5325,7 +5305,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5400,9 +5380,9 @@ dependencies = [ [[package]] name = "treediff" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +checksum = "4d127780145176e2b5d16611cc25a900150e86e9fd79d3bde6ff3a37359c9cb5" dependencies = [ "serde_json", ] @@ -5497,7 +5477,7 @@ checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5544,9 +5524,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" @@ -5667,9 +5647,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5677,24 +5657,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -5704,9 +5684,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5714,28 +5694,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "wasm-streams" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" dependencies = [ "futures-util", "js-sys", @@ -5746,9 +5726,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -5756,9 +5736,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "whoami" @@ -5940,9 +5920,18 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.35" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1931d78a9c73861da0134f453bb1f790ce49b2e30eba8410b4b79bac72b46a2d" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d90f4e0f530c4c69f62b80d839e9ef3855edc9cba471a160c4d692deed62b401" dependencies = [ "memchr", ] @@ -6004,7 +5993,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f355ab62ebe30b758c1f4ab096a306722c4b7dbfb9d8c07d18c70d71a945588" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "hashbrown 0.13.2", "lazy_static", "serde", @@ -6027,7 +6016,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -6047,5 +6036,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] diff --git a/core/build-prod.sh b/core/build-prod.sh index 0588384dc..8b6184942 100755 --- a/core/build-prod.sh +++ b/core/build-prod.sh @@ -22,7 +22,7 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then RUSTFLAGS="--cfg tokio_unstable" fi -alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' +alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' set +e fail= diff --git a/core/models/src/id/interface.rs b/core/models/src/id/host.rs similarity index 74% rename from core/models/src/id/interface.rs rename to core/models/src/id/host.rs index b9b32dd4a..91abd56e7 100644 --- a/core/models/src/id/interface.rs +++ b/core/models/src/id/host.rs @@ -6,48 +6,48 @@ use serde::{Deserialize, Deserializer, Serialize}; use crate::{Id, InvalidId}; #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] -pub struct InterfaceId(Id); -impl FromStr for InterfaceId { +pub struct HostId(Id); +impl FromStr for HostId { type Err = InvalidId; fn from_str(s: &str) -> Result { Ok(Self(Id::try_from(s.to_owned())?)) } } -impl From for InterfaceId { +impl From for HostId { fn from(id: Id) -> Self { Self(id) } } -impl std::fmt::Display for InterfaceId { +impl std::fmt::Display for HostId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", &self.0) } } -impl std::ops::Deref for InterfaceId { +impl std::ops::Deref for HostId { type Target = str; fn deref(&self) -> &Self::Target { &*self.0 } } -impl AsRef for InterfaceId { +impl AsRef for HostId { fn as_ref(&self) -> &str { self.0.as_ref() } } -impl<'de> Deserialize<'de> for InterfaceId { +impl<'de> Deserialize<'de> for HostId { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - Ok(InterfaceId(Deserialize::deserialize(deserializer)?)) + Ok(HostId(Deserialize::deserialize(deserializer)?)) } } -impl AsRef for InterfaceId { +impl AsRef for HostId { fn as_ref(&self) -> &Path { self.0.as_ref().as_ref() } } -impl<'q> sqlx::Encode<'q, sqlx::Postgres> for InterfaceId { +impl<'q> sqlx::Encode<'q, sqlx::Postgres> for HostId { fn encode_by_ref( &self, buf: &mut >::ArgumentBuffer, @@ -55,7 +55,7 @@ impl<'q> sqlx::Encode<'q, sqlx::Postgres> for InterfaceId { <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) } } -impl sqlx::Type for InterfaceId { +impl sqlx::Type for HostId { fn type_info() -> sqlx::postgres::PgTypeInfo { <&str as sqlx::Type>::type_info() } diff --git a/core/models/src/id/mod.rs b/core/models/src/id/mod.rs index ac32ceb22..068955336 100644 --- a/core/models/src/id/mod.rs +++ b/core/models/src/id/mod.rs @@ -7,8 +7,8 @@ use yasi::InternedString; mod action; mod address; mod health_check; +mod host; mod image; -mod interface; mod invalid_id; mod package; mod volume; @@ -16,8 +16,8 @@ mod volume; pub use action::ActionId; pub use address::AddressId; pub use health_check::HealthCheckId; +pub use host::HostId; pub use image::ImageId; -pub use interface::InterfaceId; pub use invalid_id::InvalidId; pub use package::{PackageId, SYSTEM_PACKAGE_ID}; pub use volume::VolumeId; diff --git a/core/startos/src/auth.rs b/core/startos/src/auth.rs index cdf2a4591..891f390bc 100644 --- a/core/startos/src/auth.rs +++ b/core/startos/src/auth.rs @@ -437,7 +437,8 @@ pub async fn reset_password_impl( let account_password = &account.password; ctx.db .mutate(|d| { - d.as_server_info_mut() + d.as_public_mut() + .as_server_info_mut() .as_password_hash_mut() .ser(account_password) }) diff --git a/core/startos/src/backup/backup_bulk.rs b/core/startos/src/backup/backup_bulk.rs index 5c68753c7..4660ab4bc 100644 --- a/core/startos/src/backup/backup_bulk.rs +++ b/core/startos/src/backup/backup_bulk.rs @@ -141,7 +141,8 @@ pub async fn backup_all( } ctx.db .mutate(|v| { - v.as_server_info_mut() + v.as_public_mut() + .as_server_info_mut() .as_status_info_mut() .as_backup_progress_mut() .ser(&None) @@ -159,6 +160,7 @@ async fn assure_backing_up( ) -> Result<(), Error> { db.mutate(|v| { let backing_up = v + .as_public_mut() .as_server_info_mut() .as_status_info_mut() .as_backup_progress_mut(); @@ -221,7 +223,7 @@ async fn perform_backup( ) })?; - let ui = ctx.db.peek().await.into_ui().de()?; + let ui = ctx.db.peek().await.into_public().into_ui().de()?; let mut os_backup_file = AtomicFile::new(backup_guard.path().join("os-backup.cbor"), None::) @@ -261,7 +263,12 @@ async fn perform_backup( backup_guard.save_and_unmount().await?; ctx.db - .mutate(|v| v.as_server_info_mut().as_last_backup_mut().ser(×tamp)) + .mutate(|v| { + v.as_public_mut() + .as_server_info_mut() + .as_last_backup_mut() + .ser(×tamp) + }) .await?; Ok(backup_report) diff --git a/core/startos/src/backup/mod.rs b/core/startos/src/backup/mod.rs index d1fd57898..de2dfbf7d 100644 --- a/core/startos/src/backup/mod.rs +++ b/core/startos/src/backup/mod.rs @@ -1,13 +1,12 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; -use models::PackageId; +use models::{HostId, PackageId}; use reqwest::Url; use rpc_toolkit::{from_fn_async, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use crate::context::CliContext; -use crate::net::interface::InterfaceId; #[allow(unused_imports)] use crate::prelude::*; use crate::util::serde::{Base32, Base64}; @@ -50,8 +49,8 @@ pub fn backup() -> ParentHandler { struct BackupMetadata { pub timestamp: DateTime, #[serde(default)] - pub network_keys: BTreeMap>, + pub network_keys: BTreeMap>, #[serde(default)] - pub tor_keys: BTreeMap>, // DEPRECATED + pub tor_keys: BTreeMap>, // DEPRECATED pub marketplace_url: Option, } diff --git a/core/startos/src/config/mod.rs b/core/startos/src/config/mod.rs index 220e388c9..522e5c1a7 100644 --- a/core/startos/src/config/mod.rs +++ b/core/startos/src/config/mod.rs @@ -1,10 +1,9 @@ -use std::collections::BTreeMap; use std::sync::Arc; use std::time::Duration; use clap::Parser; use color_eyre::eyre::eyre; -use indexmap::IndexSet; +use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use models::{ErrorKind, OptionExt, PackageId}; use patch_db::value::InternedString; @@ -18,15 +17,15 @@ use crate::context::{CliContext, RpcContext}; use crate::prelude::*; use crate::util::serde::{HandlerExtSerde, StdinDeserializable}; +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ConfigSpec(pub IndexMap); + pub mod action; -pub mod spec; pub mod util; -pub use spec::{ConfigSpec, Defaultable}; use util::NumRange; use self::action::ConfigRes; -use self::spec::ValueSpecPointer; pub type Config = patch_db::value::InOMap; pub trait TypeOf { @@ -53,8 +52,6 @@ pub enum ConfigurationError { NoMatch(#[from] NoMatchWithPath), #[error("System Error: {0}")] SystemError(Error), - #[error("Permission Denied: {0}")] - PermissionDenied(ValueSpecPointer), } impl From for Error { fn from(err: ConfigurationError) -> Self { @@ -122,8 +119,6 @@ pub enum MatchError { PropertyMatchesUnionTag(InternedString, String), #[error("Name of Property {0:?} Conflicts With Map Tag Name")] PropertyNameMatchesMapTag(String), - #[error("Pointer Is Invalid: {0}")] - InvalidPointer(spec::ValueSpecPointer), #[error("Object Key Is Invalid: {0}")] InvalidKey(String), #[error("Value In List Is Not Unique")] @@ -178,65 +173,19 @@ pub struct SetParams { // )] #[instrument(skip_all)] pub fn set() -> ParentHandler { - ParentHandler::new() - .root_handler( - from_fn_async(set_impl) - .with_metadata("sync_db", Value::Bool(true)) - .with_inherited(|set_params, id| (id, set_params)) - .no_display() - .with_remote_cli::(), - ) - .subcommand( - "dry", - from_fn_async(set_dry) - .with_inherited(|set_params, id| (id, set_params)) - .with_display_serializable() - .with_remote_cli::(), - ) -} - -pub async fn set_dry( - ctx: RpcContext, - _: Empty, - ( - id, - SetParams { - timeout, - config: StdinDeserializable(config), - }, - ): (PackageId, SetParams), -) -> Result, Error> { - let breakages = BTreeMap::new(); - let overrides = Default::default(); - - let configure_context = ConfigureContext { - breakages, - timeout: timeout.map(|t| *t), - config, - dry_run: true, - overrides, - }; - ctx.services - .get(&id) - .await - .as_ref() - .ok_or_else(|| { - Error::new( - eyre!("There is no manager running for {id}"), - ErrorKind::Unknown, - ) - })? - .configure(configure_context) - .await + ParentHandler::new().root_handler( + from_fn_async(set_impl) + .with_metadata("sync_db", Value::Bool(true)) + .with_inherited(|set_params, id| (id, set_params)) + .no_display() + .with_remote_cli::(), + ) } #[derive(Default)] pub struct ConfigureContext { - pub breakages: BTreeMap, pub timeout: Option, pub config: Option, - pub overrides: BTreeMap, - pub dry_run: bool, } #[instrument(skip_all)] @@ -251,15 +200,9 @@ pub async fn set_impl( }, ): (PackageId, SetParams), ) -> Result<(), Error> { - let breakages = BTreeMap::new(); - let overrides = Default::default(); - let configure_context = ConfigureContext { - breakages, timeout: timeout.map(|t| *t), config, - dry_run: false, - overrides, }; ctx.services .get(&id) diff --git a/core/startos/src/config/spec.rs b/core/startos/src/config/spec.rs deleted file mode 100644 index ec2667bfb..000000000 --- a/core/startos/src/config/spec.rs +++ /dev/null @@ -1,2015 +0,0 @@ -use std::borrow::Cow; -use std::collections::{BTreeMap, BTreeSet}; -use std::fmt; -use std::fmt::Debug; -use std::hash::{Hash, Hasher}; -use std::iter::FromIterator; -use std::ops::RangeBounds; -use std::sync::Arc; -use std::time::Duration; - -use async_trait::async_trait; -use imbl::Vector; -use imbl_value::InternedString; -use indexmap::{IndexMap, IndexSet}; -use itertools::Itertools; -use jsonpath_lib::Compiled as CompiledJsonPath; -use models::ProcedureName; -use patch_db::value::{Number, Value}; -use rand::{CryptoRng, Rng}; -use regex::Regex; -use serde::de::{MapAccess, Visitor}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use sqlx::PgPool; - -use super::util::{self, CharSet, NumRange, UniqueBy, STATIC_NULL}; -use super::{Config, MatchError, NoMatchWithPath, TimeoutError, TypeOf}; -use crate::config::action::ConfigRes; -use crate::config::ConfigurationError; -use crate::context::RpcContext; -use crate::net::interface::InterfaceId; -use crate::net::keys::Key; -use crate::prelude::*; -use crate::s9pk::manifest::{Manifest, PackageId}; - -// Config Value Specifications -#[async_trait] -pub trait ValueSpec { - // This function defines whether the value supplied in the argument is - // consistent with the spec in &self - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath>; - // This function checks whether the value spec is consistent with itself, - // since not all inVariant can be checked by the type - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath>; - // update is to fill in values for environment pointers recursively - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError>; - // returns all pointers that are live in the provided config - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath>; - // requires returns whether the app id is the target of a pointer within it - fn requires(&self, id: &PackageId, value: &Value) -> bool; - // defines if 2 values of this type are equal for the purpose of uniqueness - fn eq(&self, lhs: &Value, rhs: &Value) -> bool; -} - -// Config Value Default Generation -// -// This behavior is defined by two independent traits as well as a third that -// represents a conjunction of those two traits: -// -// DefaultableWith - defines an associated type describing the information it -// needs to be able to generate a default value, as well as a function for -// extracting relevant pieces of that information and using it to actually -// generate the default value -// -// HasDefaultSpec - only purpose is to summon the default spec for the type -// -// Defaultable - this is a redundant trait that may replace 'DefaultableWith' -// and 'HasDefaultSpec'. -pub trait DefaultableWith { - type DefaultSpec: Sync; - type Error: std::error::Error; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - rng: &mut R, - timeout: &Option, - ) -> Result; -} -pub trait HasDefaultSpec: DefaultableWith { - fn default_spec(&self) -> &Self::DefaultSpec; -} - -pub trait Defaultable { - type Error; - - fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result; -} -impl Defaultable for T -where - T: HasDefaultSpec + DefaultableWith + Sync, - E: std::error::Error, -{ - type Error = E; - - fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result { - self.gen_with(self.default_spec(), rng, timeout) - } -} - -// WithDefault - trivial wrapper that pairs a 'DefaultableWith' type with a -// default spec -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WithDefault { - #[serde(flatten)] - pub inner: T, - pub default: T::DefaultSpec, -} -impl DefaultableWith for WithDefault -where - T: DefaultableWith + Sync + Send, - T::DefaultSpec: Send, -{ - type DefaultSpec = T::DefaultSpec; - type Error = T::Error; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - rng: &mut R, - timeout: &Option, - ) -> Result { - self.inner.gen_with(spec, rng, timeout) - } -} -impl HasDefaultSpec for WithDefault -where - T: DefaultableWith + Sync + Send, - T::DefaultSpec: Send, -{ - fn default_spec(&self) -> &Self::DefaultSpec { - &self.default - } -} -#[async_trait] -impl ValueSpec for WithDefault -where - T: ValueSpec + DefaultableWith + Send + Sync, - Self: Send + Sync, -{ - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - self.inner.matches(value) - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - self.inner.validate(manifest) - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - self.inner - .update(ctx, manifest, config_overrides, value) - .await - } - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { - self.inner.pointers(value) - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - self.inner.requires(id, value) - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - self.inner.eq(lhs, rhs) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WithNullable { - #[serde(flatten)] - pub inner: T, - pub nullable: bool, -} -#[async_trait] -impl ValueSpec for WithNullable -where - T: ValueSpec + Send + Sync, - Self: Send + Sync, -{ - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match (self.nullable, value) { - (true, &Value::Null) => Ok(()), - _ => self.inner.matches(value), - } - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - self.inner.validate(manifest) - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - self.inner - .update(ctx, manifest, config_overrides, value) - .await - } - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { - self.inner.pointers(value) - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - self.inner.requires(id, value) - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - self.inner.eq(lhs, rhs) - } -} - -impl DefaultableWith for WithNullable -where - T: DefaultableWith + Sync + Send, -{ - type DefaultSpec = T::DefaultSpec; - type Error = T::Error; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - rng: &mut R, - timeout: &Option, - ) -> Result { - self.inner.gen_with(spec, rng, timeout) - } -} - -impl Defaultable for WithNullable -where - T: Defaultable + Sync + Send, -{ - type Error = T::Error; - - fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result { - self.inner.gen(rng, timeout) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct WithDescription { - #[serde(flatten)] - pub inner: T, - pub description: Option, - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub warning: Option, -} -#[async_trait] -impl ValueSpec for WithDescription -where - T: ValueSpec + Sync + Send, - Self: Sync + Send, -{ - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - self.inner.matches(value) - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - self.inner.validate(manifest) - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - self.inner - .update(ctx, manifest, config_overrides, value) - .await - } - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { - self.inner.pointers(value) - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - self.inner.requires(id, value) - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - self.inner.eq(lhs, rhs) - } -} - -impl DefaultableWith for WithDescription -where - T: DefaultableWith + Sync + Send, -{ - type DefaultSpec = T::DefaultSpec; - type Error = T::Error; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - rng: &mut R, - timeout: &Option, - ) -> Result { - self.inner.gen_with(spec, rng, timeout) - } -} - -impl Defaultable for WithDescription -where - T: Defaultable + Sync + Send, -{ - type Error = T::Error; - - fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result { - self.inner.gen(rng, timeout) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -#[serde(tag = "type")] -pub enum ValueSpecAny { - Boolean(WithDescription>), - Enum(WithDescription>), - List(ValueSpecList), - Number(WithDescription>>), - Object(WithDescription), - String(WithDescription>>), - Union(WithDescription>), - Pointer(WithDescription), -} -impl ValueSpecAny { - pub fn name(&self) -> &'_ str { - match self { - ValueSpecAny::Boolean(b) => b.name.as_str(), - ValueSpecAny::Enum(e) => e.name.as_str(), - ValueSpecAny::List(l) => match l { - ValueSpecList::Enum(e) => e.name.as_str(), - ValueSpecList::Number(n) => n.name.as_str(), - ValueSpecList::Object(o) => o.name.as_str(), - ValueSpecList::String(s) => s.name.as_str(), - ValueSpecList::Union(u) => u.name.as_str(), - }, - ValueSpecAny::Number(n) => n.name.as_str(), - ValueSpecAny::Object(o) => o.name.as_str(), - ValueSpecAny::Pointer(p) => p.name.as_str(), - ValueSpecAny::String(s) => s.name.as_str(), - ValueSpecAny::Union(u) => u.name.as_str(), - } - } -} -#[async_trait] -impl ValueSpec for ValueSpecAny { - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match self { - ValueSpecAny::Boolean(a) => a.matches(value), - ValueSpecAny::Enum(a) => a.matches(value), - ValueSpecAny::List(a) => a.matches(value), - ValueSpecAny::Number(a) => a.matches(value), - ValueSpecAny::Object(a) => a.matches(value), - ValueSpecAny::String(a) => a.matches(value), - ValueSpecAny::Union(a) => a.matches(value), - ValueSpecAny::Pointer(a) => a.matches(value), - } - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - match self { - ValueSpecAny::Boolean(a) => a.validate(manifest), - ValueSpecAny::Enum(a) => a.validate(manifest), - ValueSpecAny::List(a) => a.validate(manifest), - ValueSpecAny::Number(a) => a.validate(manifest), - ValueSpecAny::Object(a) => a.validate(manifest), - ValueSpecAny::String(a) => a.validate(manifest), - ValueSpecAny::Union(a) => a.validate(manifest), - ValueSpecAny::Pointer(a) => a.validate(manifest), - } - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - match self { - ValueSpecAny::Boolean(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecAny::Enum(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecAny::List(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecAny::Number(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecAny::Object(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecAny::String(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecAny::Union(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecAny::Pointer(a) => a.update(ctx, manifest, config_overrides, value).await, - } - } - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { - match self { - ValueSpecAny::Boolean(a) => a.pointers(value), - ValueSpecAny::Enum(a) => a.pointers(value), - ValueSpecAny::List(a) => a.pointers(value), - ValueSpecAny::Number(a) => a.pointers(value), - ValueSpecAny::Object(a) => a.pointers(value), - ValueSpecAny::String(a) => a.pointers(value), - ValueSpecAny::Union(a) => a.pointers(value), - ValueSpecAny::Pointer(a) => a.pointers(value), - } - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - match self { - ValueSpecAny::Boolean(a) => a.requires(id, value), - ValueSpecAny::Enum(a) => a.requires(id, value), - ValueSpecAny::List(a) => a.requires(id, value), - ValueSpecAny::Number(a) => a.requires(id, value), - ValueSpecAny::Object(a) => a.requires(id, value), - ValueSpecAny::String(a) => a.requires(id, value), - ValueSpecAny::Union(a) => a.requires(id, value), - ValueSpecAny::Pointer(a) => a.requires(id, value), - } - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match self { - ValueSpecAny::Boolean(a) => a.eq(lhs, rhs), - ValueSpecAny::Enum(a) => a.eq(lhs, rhs), - ValueSpecAny::List(a) => a.eq(lhs, rhs), - ValueSpecAny::Number(a) => a.eq(lhs, rhs), - ValueSpecAny::Object(a) => a.eq(lhs, rhs), - ValueSpecAny::String(a) => a.eq(lhs, rhs), - ValueSpecAny::Union(a) => a.eq(lhs, rhs), - ValueSpecAny::Pointer(a) => a.eq(lhs, rhs), - } - } -} -impl Defaultable for ValueSpecAny { - type Error = ConfigurationError; - - fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result { - match self { - ValueSpecAny::Boolean(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), - ValueSpecAny::Enum(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), - ValueSpecAny::List(a) => a.gen(rng, timeout), - ValueSpecAny::Number(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), - ValueSpecAny::Object(a) => a.gen(rng, timeout), - ValueSpecAny::String(a) => a.gen(rng, timeout).map_err(ConfigurationError::from), - ValueSpecAny::Union(a) => a.gen(rng, timeout), - ValueSpecAny::Pointer(a) => a.gen(rng, timeout), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ValueSpecBoolean {} -#[async_trait] -impl ValueSpec for ValueSpecBoolean { - fn matches(&self, val: &Value) -> Result<(), NoMatchWithPath> { - match val { - Value::Bool(_) => Ok(()), - Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), - a => Err(NoMatchWithPath::new(MatchError::InvalidType( - "boolean", - a.type_of(), - ))), - } - } - fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { - Ok(()) - } - async fn update( - &self, - _ctx: &RpcContext, - _manifest: &Manifest, - _config_overrides: &BTreeMap, - _value: &mut Value, - ) -> Result<(), ConfigurationError> { - Ok(()) - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - Ok(BTreeSet::new()) - } - fn requires(&self, _id: &PackageId, _value: &Value) -> bool { - false - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - (Value::Bool(lhs), Value::Bool(rhs)) => lhs == rhs, - _ => false, - } - } -} -impl DefaultableWith for ValueSpecBoolean { - type DefaultSpec = bool; - type Error = crate::util::Never; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - _rng: &mut R, - _timeout: &Option, - ) -> Result { - Ok(Value::Bool(*spec)) - } -} - -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ValueSpecEnum { - pub values: IndexSet, - pub value_names: BTreeMap, -} -impl<'de> serde::de::Deserialize<'de> for ValueSpecEnum { - fn deserialize>(deserializer: D) -> Result { - #[derive(Deserialize)] - #[serde(rename_all = "kebab-case")] - pub struct _ValueSpecEnum { - pub values: IndexSet, - #[serde(default)] - pub value_names: BTreeMap, - } - - let mut r#enum = _ValueSpecEnum::deserialize(deserializer)?; - for name in &r#enum.values { - if !r#enum.value_names.contains_key(name) { - r#enum.value_names.insert(name.clone(), name.clone()); - } - } - Ok(ValueSpecEnum { - values: r#enum.values, - value_names: r#enum.value_names, - }) - } -} -#[async_trait] -impl ValueSpec for ValueSpecEnum { - fn matches(&self, val: &Value) -> Result<(), NoMatchWithPath> { - match val { - Value::String(b) => { - if self.values.contains(&**b) { - Ok(()) - } else { - Err(NoMatchWithPath::new(MatchError::Enum( - b.clone(), - self.values.clone(), - ))) - } - } - Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), - a => Err(NoMatchWithPath::new(MatchError::InvalidType( - "string", - a.type_of(), - ))), - } - } - fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { - Ok(()) - } - async fn update( - &self, - _ctx: &RpcContext, - _manifest: &Manifest, - _config_overrides: &BTreeMap, - _value: &mut Value, - ) -> Result<(), ConfigurationError> { - Ok(()) - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - Ok(BTreeSet::new()) - } - fn requires(&self, _id: &PackageId, _value: &Value) -> bool { - false - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - (Value::String(lhs), Value::String(rhs)) => lhs == rhs, - _ => false, - } - } -} -impl DefaultableWith for ValueSpecEnum { - type DefaultSpec = Arc; - type Error = crate::util::Never; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - _rng: &mut R, - _timeout: &Option, - ) -> Result { - Ok(Value::String(spec.clone())) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ListSpec { - pub spec: T, - pub range: NumRange, -} -#[async_trait] -impl ValueSpec for ListSpec -where - T: ValueSpec + Sync + Send, - Self: Sync + Send, -{ - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match value { - Value::Array(l) => { - if !self.range.contains(&l.len()) { - Err(NoMatchWithPath { - path: Vec::new(), - error: MatchError::LengthMismatch(self.range.clone(), l.len()), - }) - } else { - l.iter() - .enumerate() - .map(|(i, v)| { - self.spec - .matches(v) - .map_err(|e| e.prepend(InternedString::from_display(&i)))?; - if l.iter() - .enumerate() - .any(|(i2, v2)| i != i2 && self.spec.eq(v, v2)) - { - Err(NoMatchWithPath::new(MatchError::ListUniquenessViolation) - .prepend(InternedString::from_display(&i))) - } else { - Ok(()) - } - }) - .collect() - } - } - Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), - a => Err(NoMatchWithPath::new(MatchError::InvalidType( - "list", - a.type_of(), - ))), - } - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - self.spec.validate(manifest) - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - if let Value::Array(ref mut ls) = value { - for (i, val) in ls.iter_mut().enumerate() { - match self.spec.update(ctx, manifest, config_overrides, val).await { - Err(ConfigurationError::NoMatch(e)) => Err(ConfigurationError::NoMatch( - e.prepend(InternedString::from_display(&i)), - )), - a => a, - }?; - } - Ok(()) - } else { - Err(ConfigurationError::NoMatch(NoMatchWithPath::new( - MatchError::InvalidType("list", value.type_of()), - ))) - } - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - Ok(BTreeSet::new()) - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - if let Value::Array(ref ls) = value { - ls.into_iter().any(|v| self.spec.requires(id, v)) - } else { - false - } - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - (Value::Array(lhs), Value::Array(rhs)) => { - lhs.iter().zip_longest(rhs.iter()).all(|zip| match zip { - itertools::EitherOrBoth::Both(lhs, rhs) => lhs == rhs, - _ => false, - }) - } - _ => false, - } - } -} - -impl DefaultableWith for ListSpec -where - T: DefaultableWith + Sync + Send, -{ - type DefaultSpec = Vec; - type Error = T::Error; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - rng: &mut R, - timeout: &Option, - ) -> Result { - let mut res = Vector::new(); - for spec_member in spec.iter() { - res.push_back(self.spec.gen_with(spec_member, rng, timeout)?); - } - Ok(Value::Array(res)) - } -} - -unsafe impl Sync for ValueSpecObject {} // TODO: remove -unsafe impl Send for ValueSpecObject {} // TODO: remove -unsafe impl Sync for ValueSpecUnion {} // TODO: remove -unsafe impl Send for ValueSpecUnion {} // TODO: remove - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -#[serde(tag = "subtype")] -pub enum ValueSpecList { - Enum(WithDescription>>), - Number(WithDescription>>), - Object(WithDescription>>), - String(WithDescription>>), - Union(WithDescription>>>), -} -#[async_trait] -impl ValueSpec for ValueSpecList { - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match self { - ValueSpecList::Enum(a) => a.matches(value), - ValueSpecList::Number(a) => a.matches(value), - ValueSpecList::Object(a) => a.matches(value), - ValueSpecList::String(a) => a.matches(value), - ValueSpecList::Union(a) => a.matches(value), - } - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - match self { - ValueSpecList::Enum(a) => a.validate(manifest), - ValueSpecList::Number(a) => a.validate(manifest), - ValueSpecList::Object(a) => a.validate(manifest), - ValueSpecList::String(a) => a.validate(manifest), - ValueSpecList::Union(a) => a.validate(manifest), - } - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - match self { - ValueSpecList::Enum(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecList::Number(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecList::Object(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecList::String(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecList::Union(a) => a.update(ctx, manifest, config_overrides, value).await, - } - } - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { - match self { - ValueSpecList::Enum(a) => a.pointers(value), - ValueSpecList::Number(a) => a.pointers(value), - ValueSpecList::Object(a) => a.pointers(value), - ValueSpecList::String(a) => a.pointers(value), - ValueSpecList::Union(a) => a.pointers(value), - } - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - match self { - ValueSpecList::Enum(a) => a.requires(id, value), - ValueSpecList::Number(a) => a.requires(id, value), - ValueSpecList::Object(a) => a.requires(id, value), - ValueSpecList::String(a) => a.requires(id, value), - ValueSpecList::Union(a) => a.requires(id, value), - } - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match self { - ValueSpecList::Enum(a) => a.eq(lhs, rhs), - ValueSpecList::Number(a) => a.eq(lhs, rhs), - ValueSpecList::Object(a) => a.eq(lhs, rhs), - ValueSpecList::String(a) => a.eq(lhs, rhs), - ValueSpecList::Union(a) => a.eq(lhs, rhs), - } - } -} - -impl Defaultable for ValueSpecList { - type Error = ConfigurationError; - - fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result { - match self { - ValueSpecList::Enum(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), - ValueSpecList::Number(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), - ValueSpecList::Object(a) => { - let mut ret = match a.gen(rng, timeout).unwrap() { - Value::Array(l) => l, - a => { - return Err(ConfigurationError::NoMatch(NoMatchWithPath::new( - MatchError::InvalidType("list", a.type_of()), - ))) - } - }; - while !( - a.inner.inner.range.start_bound(), - std::ops::Bound::Unbounded, - ) - .contains(&ret.len()) - { - ret.push_back( - a.inner - .inner - .spec - .gen(rng, timeout) - .map_err(ConfigurationError::from)?, - ); - } - Ok(Value::Array(ret)) - } - ValueSpecList::String(a) => a.gen(rng, timeout).map_err(ConfigurationError::from), - ValueSpecList::Union(a) => a.gen(rng, timeout).map_err(ConfigurationError::from), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ValueSpecNumber { - range: Option>, - #[serde(default)] - integral: bool, - #[serde(skip_serializing_if = "Option::is_none")] - units: Option, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub placeholder: Option, -} -#[async_trait] -impl ValueSpec for ValueSpecNumber { - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match value { - Value::Number(n) => { - let n = n.as_f64().unwrap(); - if self.integral && n.floor() != n { - return Err(NoMatchWithPath::new(MatchError::NonIntegral(n))); - } - if let Some(range) = &self.range { - if !range.contains(&n) { - return Err(NoMatchWithPath::new(MatchError::OutOfRange( - range.clone(), - n, - ))); - } - } - Ok(()) - } - Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), - a => Err(NoMatchWithPath::new(MatchError::InvalidType( - "object", - a.type_of(), - ))), - } - } - fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { - Ok(()) - } - async fn update( - &self, - _ctx: &RpcContext, - _manifest: &Manifest, - _config_overrides: &BTreeMap, - _value: &mut Value, - ) -> Result<(), ConfigurationError> { - Ok(()) - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - Ok(BTreeSet::new()) - } - fn requires(&self, _id: &PackageId, _value: &Value) -> bool { - false - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - (Value::Number(lhs), Value::Number(rhs)) => lhs == rhs, - _ => false, - } - } -} -impl DefaultableWith for ValueSpecNumber { - type DefaultSpec = Option; - type Error = crate::util::Never; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - _rng: &mut R, - _timeout: &Option, - ) -> Result { - Ok(spec - .clone() - .map(|s| Value::Number(s)) - .unwrap_or(Value::Null)) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct ValueSpecObject { - pub spec: ConfigSpec, - pub display_as: Option, - #[serde(default)] - pub unique_by: UniqueBy, -} -#[async_trait] -impl ValueSpec for ValueSpecObject { - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match value { - Value::Object(o) => self.spec.matches(o), - Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), - a => Err(NoMatchWithPath::new(MatchError::InvalidType( - "object", - a.type_of(), - ))), - } - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - self.spec.validate(manifest) - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - if let Value::Object(o) = value { - self.spec.update(ctx, manifest, config_overrides, o).await - } else { - Err(ConfigurationError::NoMatch(NoMatchWithPath::new( - MatchError::InvalidType("object", value.type_of()), - ))) - } - } - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { - if let Value::Object(o) = value { - self.spec.pointers(o) - } else { - Err(NoMatchWithPath::new(MatchError::InvalidType( - "object", - value.type_of(), - ))) - } - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - if let Value::Object(o) = value { - self.spec.requires(id, o) - } else { - false - } - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - (Value::Object(lhs), Value::Object(rhs)) => self.unique_by.eq(lhs, rhs), - _ => false, - } - } -} -impl DefaultableWith for ValueSpecObject { - type DefaultSpec = Config; - type Error = crate::util::Never; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - _rng: &mut R, - _timeout: &Option, - ) -> Result { - Ok(Value::Object(spec.clone())) - } -} -impl Defaultable for ValueSpecObject { - type Error = ConfigurationError; - - fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result { - self.spec.gen(rng, timeout).map(Value::Object) - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct ConfigSpec(pub IndexMap); -impl ConfigSpec { - pub fn matches(&self, value: &Config) -> Result<(), NoMatchWithPath> { - for (key, val) in self.0.iter() { - if let Some(v) = value.get(&**key) { - val.matches(v).map_err(|e| e.prepend(key.clone()))?; - } else { - val.matches(&Value::Null) - .map_err(|e| e.prepend(key.clone()))?; - } - } - Ok(()) - } - - pub fn gen( - &self, - rng: &mut R, - timeout: &Option, - ) -> Result { - let mut res = Config::new(); - for (key, val) in self.0.iter() { - res.insert(key.clone(), val.gen(rng, timeout)?); - } - Ok(res) - } - - pub fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - for (name, val) in &self.0 { - val.validate(manifest) - .map_err(|e| e.prepend(name.clone()))?; - } - Ok(()) - } - - pub async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - cfg: &mut Config, - ) -> Result<(), ConfigurationError> { - for (k, vs) in self.0.iter() { - match cfg.get_mut(k) { - None => { - let mut v = Value::Null; - vs.update(ctx, manifest, config_overrides, &mut v).await?; - cfg.insert(k.clone(), v); - } - Some(v) => match vs.update(ctx, manifest, config_overrides, v).await { - Err(ConfigurationError::NoMatch(e)) => { - Err(ConfigurationError::NoMatch(e.prepend(k.clone()))) - } - a => a, - }?, - }; - } - Ok(()) - } - - pub fn pointers(&self, cfg: &Config) -> Result, NoMatchWithPath> { - cfg.iter() - .filter_map(|(k, v)| self.0.get(k).map(|vs| (k, vs.pointers(v)))) - .fold(Ok(BTreeSet::::new()), |acc, v| { - match (acc, v) { - // propagate existing errors - (Err(e), _) => Err(e), - // create new error case - (Ok(_), (k, Err(e))) => Err(e.prepend(k.clone())), - // combine sets - (Ok(s0), (_, Ok(s1))) => Ok(BTreeSet::from_iter(s0.union(&s1).cloned())), - } - }) - } - - pub fn requires(&self, id: &PackageId, cfg: &Config) -> bool { - self.0 - .iter() - .any(|(k, v)| v.requires(id, cfg.get(k).unwrap_or(&STATIC_NULL))) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct Pattern { - #[serde(with = "util::serde_regex")] - pub pattern: Regex, - pub pattern_description: String, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ValueSpecString { - #[serde(flatten)] - pub pattern: Option, - pub textarea: bool, - pub copyable: bool, - pub masked: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub placeholder: Option, -} -impl<'de> Deserialize<'de> for ValueSpecString { - fn deserialize>(deserializer: D) -> Result { - struct ValueSpecStringVisitor; - impl<'de> Visitor<'de> for ValueSpecStringVisitor { - type Value = ValueSpecString; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct ValueSpecString") - } - fn visit_map>(self, mut map: V) -> Result { - let mut pattern = None; - let mut pattern_description = None; - let mut textarea = false; - let mut copyable = false; - let mut masked = false; - let mut placeholder = None; - while let Some::(key) = map.next_key()? { - if &key == "pattern" { - if pattern.is_some() { - return Err(serde::de::Error::duplicate_field("pattern")); - } else { - pattern = Some( - Regex::new(&map.next_value::()?) - .map_err(serde::de::Error::custom)?, - ); - } - } else if &key == "pattern-description" { - if pattern_description.is_some() { - return Err(serde::de::Error::duplicate_field("pattern-description")); - } else { - pattern_description = Some(map.next_value()?); - } - } else if &key == "textarea" { - textarea = map.next_value()?; - } else if &key == "copyable" { - copyable = map.next_value()?; - } else if &key == "masked" { - masked = map.next_value()?; - } else if &key == "placeholder" { - if placeholder.is_some() { - return Err(serde::de::Error::duplicate_field("placeholder")); - } else { - placeholder = Some(map.next_value()?); - } - } - } - let regex = match (pattern, pattern_description) { - (None, None) => None, - (Some(p), Some(d)) => Some(Pattern { - pattern: p, - pattern_description: d, - }), - (Some(_), None) => { - return Err(serde::de::Error::missing_field("pattern-description")); - } - (None, Some(_)) => { - return Err(serde::de::Error::missing_field("pattern")); - } - }; - Ok(ValueSpecString { - pattern: regex, - textarea, - copyable, - masked, - placeholder, - }) - } - } - const FIELDS: &[&str] = &[ - "pattern", - "pattern-description", - "textarea", - "copyable", - "masked", - "placeholder", - ]; - deserializer.deserialize_struct("ValueSpecString", FIELDS, ValueSpecStringVisitor) - } -} -#[async_trait] -impl ValueSpec for ValueSpecString { - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match value { - Value::String(s) => { - if let Some(pattern) = &self.pattern { - if pattern.pattern.is_match(s) { - Ok(()) - } else { - Err(NoMatchWithPath::new(MatchError::Pattern( - s.clone(), - pattern.pattern.clone(), - ))) - } - } else { - Ok(()) - } - } - Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), - a => Err(NoMatchWithPath::new(MatchError::InvalidType( - "string", - a.type_of(), - ))), - } - } - fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { - Ok(()) - } - async fn update( - &self, - _ctx: &RpcContext, - _manifest: &Manifest, - _config_overrides: &BTreeMap, - _value: &mut Value, - ) -> Result<(), ConfigurationError> { - Ok(()) - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - Ok(BTreeSet::new()) - } - fn requires(&self, _id: &PackageId, _value: &Value) -> bool { - false - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - (Value::String(lhs), Value::String(rhs)) => lhs == rhs, - _ => false, - } - } -} -impl DefaultableWith for ValueSpecString { - type DefaultSpec = Option; - type Error = TimeoutError; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - rng: &mut R, - timeout: &Option, - ) -> Result { - if let Some(spec) = spec { - let now = timeout.as_ref().map(|_| std::time::Instant::now()); - loop { - let candidate = spec.gen(rng); - match (spec, &self.pattern) { - (DefaultString::Entropy(_), Some(pattern)) - if !pattern.pattern.is_match(&candidate) => {} - _ => { - return Ok(Value::String(candidate)); - } - } - if let (Some(now), Some(timeout)) = (now, timeout) { - if &now.elapsed() > timeout { - return Err(TimeoutError); - } - } else { - return Ok(Value::String(candidate)); - } - } - } else { - Ok(Value::Null) - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum DefaultString { - Literal(String), - Entropy(Entropy), -} -impl DefaultString { - pub fn gen(&self, rng: &mut R) -> Arc { - Arc::new(match self { - DefaultString::Literal(s) => s.clone(), - DefaultString::Entropy(e) => e.gen(rng), - }) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Entropy { - pub charset: Option, - pub len: usize, -} -impl Entropy { - pub fn gen(&self, rng: &mut R) -> String { - let len = self.len; - let set = self - .charset - .as_ref() - .map(|cs| Cow::Borrowed(cs)) - .unwrap_or_else(|| Cow::Owned(Default::default())); - std::iter::repeat_with(|| set.gen(rng)).take(len).collect() - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct UnionTag { - pub id: InternedString, - pub name: String, - pub description: Option, - pub variant_names: BTreeMap, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ValueSpecUnion { - pub tag: UnionTag, - pub variants: BTreeMap, - pub display_as: Option, - pub unique_by: UniqueBy, -} - -impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion { - fn deserialize>(deserializer: D) -> Result { - #[derive(Deserialize)] - #[serde(rename_all = "kebab-case")] - #[serde(untagged)] - pub enum _UnionTag { - Old(InternedString), - New(UnionTag), - } - #[derive(Deserialize)] - #[serde(rename_all = "kebab-case")] - pub struct _ValueSpecUnion { - pub variants: BTreeMap, - pub tag: _UnionTag, - pub display_as: Option, - #[serde(default)] - pub unique_by: UniqueBy, - } - - let u = _ValueSpecUnion::deserialize(deserializer)?; - Ok(ValueSpecUnion { - tag: match u.tag { - _UnionTag::Old(id) => UnionTag { - id: id.clone(), - name: id.to_string(), - description: None, - variant_names: u - .variants - .keys() - .map(|k| (k.to_owned(), k.to_owned())) - .collect(), - }, - _UnionTag::New(UnionTag { - id, - name, - description, - mut variant_names, - }) => UnionTag { - id, - name, - description, - variant_names: { - let mut iter = u.variants.keys(); - while variant_names.len() < u.variants.len() { - if let Some(variant) = iter.next() { - variant_names.insert(variant.to_owned(), variant.to_owned()); - } else { - break; - } - } - variant_names - }, - }, - }, - variants: u.variants, - display_as: u.display_as, - unique_by: u.unique_by, - }) - } -} - -#[async_trait] -impl ValueSpec for ValueSpecUnion { - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match value { - Value::Object(o) => { - if let Some(Value::String(ref tag)) = o.get(&*self.tag.id) { - if let Some(obj_spec) = self.variants.get(&**tag) { - let mut without_tag = o.clone(); - without_tag.remove(&*self.tag.id); - obj_spec.matches(&without_tag) - } else { - Err(NoMatchWithPath::new(MatchError::Union( - tag.clone(), - self.variants.keys().cloned().collect(), - ))) - } - } else { - Err(NoMatchWithPath::new(MatchError::MissingTag( - self.tag.id.clone(), - ))) - } - } - Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), - a => Err(NoMatchWithPath::new(MatchError::InvalidType( - "object", - a.type_of(), - ))), - } - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - for (name, variant) in &self.variants { - if variant.0.get(&*self.tag.id).is_some() { - return Err(NoMatchWithPath::new(MatchError::PropertyMatchesUnionTag( - self.tag.id.clone(), - name.clone(), - ))); - } - variant.validate(manifest)?; - } - Ok(()) - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - if let Value::Object(o) = value { - match o.get(&*self.tag.id) { - None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( - MatchError::MissingTag(self.tag.id.clone()), - ))), - Some(Value::String(tag)) => match self.variants.get(&**tag) { - None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( - MatchError::Union(tag.clone(), self.variants.keys().cloned().collect()), - ))), - Some(spec) => spec.update(ctx, manifest, config_overrides, o).await, - }, - Some(other) => Err(ConfigurationError::NoMatch( - NoMatchWithPath::new(MatchError::InvalidType("string", other.type_of())) - .prepend(self.tag.id.clone()), - )), - } - } else { - Err(ConfigurationError::NoMatch(NoMatchWithPath::new( - MatchError::InvalidType("object", value.type_of()), - ))) - } - } - fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { - if let Value::Object(o) = value { - match o.get(&*self.tag.id) { - None => Err(NoMatchWithPath::new(MatchError::MissingTag( - self.tag.id.clone(), - ))), - Some(Value::String(tag)) => match self.variants.get(&**tag) { - None => Err(NoMatchWithPath::new(MatchError::Union( - tag.clone(), - self.variants.keys().cloned().collect(), - ))), - Some(spec) => spec.pointers(o), - }, - Some(other) => Err(NoMatchWithPath::new(MatchError::InvalidType( - "string", - other.type_of(), - )) - .prepend(self.tag.id.clone())), - } - } else { - Err(NoMatchWithPath::new(MatchError::InvalidType( - "object", - value.type_of(), - ))) - } - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - if let Value::Object(o) = value { - match o.get(&*self.tag.id) { - Some(Value::String(tag)) => match self.variants.get(&**tag) { - None => false, - Some(spec) => spec.requires(id, o), - }, - _ => false, - } - } else { - false - } - } - fn eq(&self, lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - (Value::Object(lhs), Value::Object(rhs)) => self.unique_by.eq(lhs, rhs), - _ => false, - } - } -} -impl DefaultableWith for ValueSpecUnion { - type DefaultSpec = Arc; - type Error = ConfigurationError; - - fn gen_with( - &self, - spec: &Self::DefaultSpec, - rng: &mut R, - timeout: &Option, - ) -> Result { - let variant = if let Some(v) = self.variants.get(&**spec) { - v - } else { - return Err(ConfigurationError::NoMatch(NoMatchWithPath::new( - MatchError::Union(spec.clone(), self.variants.keys().cloned().collect()), - ))); - }; - let cfg_res = variant.gen(rng, timeout)?; - - let mut tagged_cfg = Config::new(); - tagged_cfg.insert(self.tag.id.clone(), Value::String(spec.clone())); - tagged_cfg.extend(cfg_res.into_iter()); - - Ok(Value::Object(tagged_cfg)) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(tag = "subtype")] -#[serde(rename_all = "kebab-case")] -pub enum ValueSpecPointer { - Package(PackagePointerSpec), - System(SystemPointerSpec), -} -impl fmt::Display for ValueSpecPointer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ValueSpecPointer::Package(p) => write!(f, "{}", p), - ValueSpecPointer::System(p) => write!(f, "{}", p), - } - } -} -impl Defaultable for ValueSpecPointer { - type Error = ConfigurationError; - fn gen( - &self, - _rng: &mut R, - _timeout: &Option, - ) -> Result { - Ok(Value::Null) - } -} -#[async_trait] -impl ValueSpec for ValueSpecPointer { - fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { - match self { - ValueSpecPointer::Package(a) => a.matches(value), - ValueSpecPointer::System(a) => a.matches(value), - } - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - match self { - ValueSpecPointer::Package(a) => a.validate(manifest), - ValueSpecPointer::System(a) => a.validate(manifest), - } - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - match self { - ValueSpecPointer::Package(a) => a.update(ctx, manifest, config_overrides, value).await, - ValueSpecPointer::System(a) => a.update(ctx, manifest, config_overrides, value).await, - } - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - let mut pointers = BTreeSet::new(); - pointers.insert(self.clone()); - Ok(pointers) - } - fn requires(&self, id: &PackageId, value: &Value) -> bool { - match self { - ValueSpecPointer::Package(a) => a.requires(id, value), - ValueSpecPointer::System(a) => a.requires(id, value), - } - } - fn eq(&self, _lhs: &Value, _rhs: &Value) -> bool { - false - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(tag = "target")] -#[serde(rename_all = "kebab-case")] -pub enum PackagePointerSpec { - TorKey(TorKeyPointer), - TorAddress(TorAddressPointer), - LanAddress(LanAddressPointer), - Config(ConfigPointer), -} -impl PackagePointerSpec { - pub fn package_id(&self) -> &PackageId { - match self { - PackagePointerSpec::TorKey(TorKeyPointer { package_id, .. }) => package_id, - PackagePointerSpec::TorAddress(TorAddressPointer { package_id, .. }) => package_id, - PackagePointerSpec::LanAddress(LanAddressPointer { package_id, .. }) => package_id, - PackagePointerSpec::Config(ConfigPointer { package_id, .. }) => package_id, - } - } - async fn deref( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - ) -> Result { - match &self { - PackagePointerSpec::TorKey(key) => key.deref(&manifest.id, &ctx.secret_store).await, - PackagePointerSpec::TorAddress(tor) => tor.deref(ctx).await, - PackagePointerSpec::LanAddress(lan) => lan.deref(ctx).await, - PackagePointerSpec::Config(cfg) => cfg.deref(ctx, config_overrides).await, - } - } -} -impl fmt::Display for PackagePointerSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - PackagePointerSpec::TorKey(key) => write!(f, "{}", key), - PackagePointerSpec::TorAddress(tor) => write!(f, "{}", tor), - PackagePointerSpec::LanAddress(lan) => write!(f, "{}", lan), - PackagePointerSpec::Config(cfg) => write!(f, "{}", cfg), - } - } -} -impl Defaultable for PackagePointerSpec { - type Error = ConfigurationError; - fn gen( - &self, - _rng: &mut R, - _timeout: &Option, - ) -> Result { - Ok(Value::Null) - } -} -#[async_trait] -impl ValueSpec for PackagePointerSpec { - fn matches(&self, _value: &Value) -> Result<(), NoMatchWithPath> { - Ok(()) - } - fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { - if &manifest.id != self.package_id() - && !manifest.dependencies.0.contains_key(self.package_id()) - { - return Err(NoMatchWithPath::new(MatchError::InvalidPointer( - ValueSpecPointer::Package(self.clone()), - ))); - } - match self { - _ => Ok(()), - } - } - async fn update( - &self, - ctx: &RpcContext, - manifest: &Manifest, - config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - *value = self.deref(ctx, manifest, config_overrides).await?; - Ok(()) - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - let mut pointers = BTreeSet::new(); - pointers.insert(ValueSpecPointer::Package(self.clone())); - Ok(pointers) - } - fn requires(&self, id: &PackageId, _value: &Value) -> bool { - self.package_id() == id - } - fn eq(&self, _lhs: &Value, _rhs: &Value) -> bool { - false - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct TorAddressPointer { - pub package_id: PackageId, - interface: InterfaceId, -} -impl TorAddressPointer { - async fn deref(&self, ctx: &RpcContext) -> Result { - let addr = ctx - .db - .peek() - .await - .as_package_data() - .as_idx(&self.package_id) - .and_then(|pde| pde.as_installed()) - .and_then(|i| i.as_interface_addresses().as_idx(&self.interface)) - .and_then(|a| a.as_tor_address().de().transpose()) - .transpose() - .map_err(|e| ConfigurationError::SystemError(e))?; - Ok(addr.map(Arc::new).map(Value::String).unwrap_or(Value::Null)) - } -} -impl fmt::Display for TorAddressPointer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TorAddressPointer { - package_id, - interface, - } => write!(f, "{}: tor-address: {}", package_id, interface), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct LanAddressPointer { - pub package_id: PackageId, - interface: InterfaceId, -} -impl fmt::Display for LanAddressPointer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let LanAddressPointer { - package_id, - interface, - } = self; - write!(f, "{}: lan-address: {}", package_id, interface) - } -} -impl LanAddressPointer { - async fn deref(&self, ctx: &RpcContext) -> Result { - let addr = ctx - .db - .peek() - .await - .as_package_data() - .as_idx(&self.package_id) - .and_then(|pde| pde.as_installed()) - .and_then(|i| i.as_interface_addresses().as_idx(&self.interface)) - .and_then(|a| a.as_lan_address().de().transpose()) - .transpose() - .map_err(|e| ConfigurationError::SystemError(e))?; - Ok(addr - .to_owned() - .map(Arc::new) - .map(Value::String) - .unwrap_or(Value::Null)) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct ConfigPointer { - package_id: PackageId, - selector: Arc, - multi: bool, -} -impl ConfigPointer { - pub fn select(&self, val: &Value) -> Value { - self.selector.select(self.multi, val) - } - async fn deref( - &self, - ctx: &RpcContext, - config_overrides: &BTreeMap, - ) -> Result { - if let Some(cfg) = config_overrides.get(&self.package_id) { - Ok(self.select(&Value::Object(cfg.clone()))) - } else { - let id = &self.package_id; - let version = ctx - .db - .peek() - .await - .as_package_data() - .as_idx(id) - .and_then(|pde| pde.as_installed()) - .map(|i| i.as_manifest().as_version().de()) - .transpose() - .map_err(ConfigurationError::SystemError)?; - if let Some(version) = version { - let cfg_res = ctx - .services - .get(&id) - .await - .as_ref() - .or_not_found(lazy_format!("Manager for {id}@{version}")) - .map_err(|e| ConfigurationError::SystemError(e))? - .get_config() - .await - .map_err(ConfigurationError::SystemError)?; - if let Some(cfg) = cfg_res.config { - Ok(self.select(&Value::Object(cfg))) - } else { - Ok(Value::Null) - } - } else { - Ok(Value::Null) - } - } - } -} -impl fmt::Display for ConfigPointer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let ConfigPointer { - package_id, - selector, - .. - } = self; - write!(f, "{}: config: {}", package_id, selector) - } -} - -#[derive(Clone, Debug)] -pub struct ConfigSelector { - src: String, - compiled: CompiledJsonPath, -} -impl ConfigSelector { - fn select(&self, multi: bool, val: &Value) -> Value { - let selected = self.compiled.select(&val).ok().unwrap_or_else(Vector::new); - if multi { - Value::Array(selected.into_iter().cloned().collect()) - } else { - selected.get(0).map(|v| (*v).clone()).unwrap_or(Value::Null) - } - } -} -impl fmt::Display for ConfigSelector { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.src) - } -} -impl Serialize for ConfigSelector { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.src) - } -} -impl<'de> Deserialize<'de> for ConfigSelector { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let src: String = Deserialize::deserialize(deserializer)?; - let compiled = CompiledJsonPath::compile(&src).map_err(serde::de::Error::custom)?; - Ok(Self { src, compiled }) - } -} -impl PartialEq for ConfigSelector { - fn eq(&self, other: &ConfigSelector) -> bool { - self.src == other.src - } -} -impl Eq for ConfigSelector {} -impl PartialOrd for ConfigSelector { - fn partial_cmp(&self, other: &Self) -> Option { - self.src.partial_cmp(&other.src) - } -} -impl Ord for ConfigSelector { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.src.cmp(&other.src) - } -} -impl Hash for ConfigSelector { - fn hash(&self, state: &mut H) { - self.src.hash(state) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct TorKeyPointer { - package_id: PackageId, - interface: InterfaceId, -} -impl TorKeyPointer { - async fn deref( - &self, - source_package: &PackageId, - secrets: &PgPool, - ) -> Result { - if &self.package_id != source_package { - return Err(ConfigurationError::PermissionDenied( - ValueSpecPointer::Package(PackagePointerSpec::TorKey(self.clone())), - )); - } - let key = Key::for_interface( - secrets - .acquire() - .await - .map_err(|e| ConfigurationError::SystemError(e.into()))? - .as_mut(), - Some((self.package_id.clone(), self.interface.clone())), - ) - .await - .map_err(ConfigurationError::SystemError)?; - Ok(Value::String(Arc::new(base32::encode( - base32::Alphabet::RFC4648 { padding: false }, - &key.tor_key().as_bytes(), - )))) - } -} -impl fmt::Display for TorKeyPointer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}: tor-key: {}", self.package_id, self.interface) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -#[serde(tag = "target")] -pub enum SystemPointerSpec {} -impl fmt::Display for SystemPointerSpec { - fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { - // write!(f, "SYSTEM: {}", match *self {}) - Ok(()) - } -} -impl SystemPointerSpec { - async fn deref(&self, _ctx: &RpcContext) -> Result { - #[allow(unreachable_code)] - Ok(match *self {}) - } -} -impl Defaultable for SystemPointerSpec { - type Error = ConfigurationError; - fn gen( - &self, - _rng: &mut R, - _timeout: &Option, - ) -> Result { - Ok(Value::Null) - } -} -#[async_trait] -impl ValueSpec for SystemPointerSpec { - fn matches(&self, _value: &Value) -> Result<(), NoMatchWithPath> { - Ok(()) - } - fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { - Ok(()) - } - async fn update( - &self, - ctx: &RpcContext, - _manifest: &Manifest, - _config_overrides: &BTreeMap, - value: &mut Value, - ) -> Result<(), ConfigurationError> { - *value = self.deref(ctx).await?; - Ok(()) - } - fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { - let mut pointers = BTreeSet::new(); - pointers.insert(ValueSpecPointer::System(self.clone())); - #[allow(unreachable_code)] - Ok(pointers) - } - fn requires(&self, _id: &PackageId, _value: &Value) -> bool { - false - } - fn eq(&self, _lhs: &Value, _rhs: &Value) -> bool { - false - } -} - -#[test] -fn invalid_regex_produces_error() { - assert!( - serde_yaml::from_reader::<_, ConfigSpec>(std::io::Cursor::new(include_bytes!( - "../../test/config-spec/lnd-invalid-regex.yaml" - ))) - .is_err() - ) -} - -#[test] -fn missing_pattern_description_produces_error() { - assert!( - serde_yaml::from_reader::<_, ConfigSpec>(std::io::Cursor::new(include_bytes!( - "../../test/config-spec/lnd-missing-pattern-description.yaml" - ))) - .is_err() - ) -} - -#[test] -fn missing_pattern_produces_error() { - assert!( - serde_yaml::from_reader::<_, ConfigSpec>(std::io::Cursor::new(include_bytes!( - "../../test/config-spec/lnd-missing-pattern.yaml" - ))) - .is_err() - ) -} - -#[test] -fn regex_control() { - let spec = serde_yaml::from_reader::<_, ConfigSpec>(std::io::Cursor::new(include_bytes!( - "../../test/config-spec/lnd-correct.yaml" - ))) - .unwrap(); - println!("{}", serde_json::to_string_pretty(&spec).unwrap()); -} diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index df2747089..905132071 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -116,15 +116,27 @@ impl RpcContext { let devices = lshw().await?; let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024; - if !db.peek().await.as_server_info().as_ntp_synced().de()? { + if !db + .peek() + .await + .as_public() + .as_server_info() + .as_ntp_synced() + .de()? + { let db = db.clone(); tokio::spawn(async move { while !check_time_is_synchronized().await.unwrap() { tokio::time::sleep(Duration::from_secs(30)).await; } - db.mutate(|v| v.as_server_info_mut().as_ntp_synced_mut().ser(&true)) - .await - .unwrap() + db.mutate(|v| { + v.as_public_mut() + .as_server_info_mut() + .as_ntp_synced_mut() + .ser(&true) + }) + .await + .unwrap() }); } @@ -208,12 +220,15 @@ impl RpcContext { self.db .mutate(|f| { let mut current_dependents = f + .as_public_mut() .as_package_data() .keys()? .into_iter() .map(|k| (k.clone(), BTreeMap::new())) .collect::>(); - for (package_id, package) in f.as_package_data_mut().as_entries_mut()? { + for (package_id, package) in + f.as_public_mut().as_package_data_mut().as_entries_mut()? + { for (k, v) in package .as_installed_mut() .into_iter() @@ -228,6 +243,7 @@ impl RpcContext { } for (package_id, current_dependents) in current_dependents { if let Some(deps) = f + .as_public_mut() .as_package_data_mut() .as_idx_mut(&package_id) .and_then(|pde| pde.expect_as_installed_mut().ok()) @@ -235,6 +251,7 @@ impl RpcContext { { deps.ser(&CurrentDependents(current_dependents))?; } else if let Some(deps) = f + .as_public_mut() .as_package_data_mut() .as_idx_mut(&package_id) .and_then(|pde| pde.expect_as_removing_mut().ok()) @@ -252,7 +269,7 @@ impl RpcContext { let mut all_dependency_config_errs = BTreeMap::new(); let peek = self.db.peek().await; - for (package_id, package) in peek.as_package_data().as_entries()?.into_iter() { + for (package_id, package) in peek.as_public().as_package_data().as_entries()?.into_iter() { let package = package.clone(); if let Some(current_dependencies) = package .as_installed() @@ -276,6 +293,7 @@ impl RpcContext { .mutate(|v| { for (package_id, errs) in all_dependency_config_errs { if let Some(config_errors) = v + .as_public_mut() .as_package_data_mut() .as_idx_mut(&package_id) .and_then(|pde| pde.as_installed_mut()) diff --git a/core/startos/src/db/mod.rs b/core/startos/src/db/mod.rs index 77b2dfef2..bf09c0ff2 100644 --- a/core/startos/src/db/mod.rs +++ b/core/startos/src/db/mod.rs @@ -11,7 +11,7 @@ use clap::Parser; use futures::{FutureExt, StreamExt}; use http::header::COOKIE; use http::HeaderMap; -use patch_db::json_ptr::JsonPointer; +use patch_db::json_ptr::{JsonPointer, ROOT}; use patch_db::{Dump, Revision}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{command, from_fn_async, CallRemote, HandlerExt, ParentHandler}; @@ -25,13 +25,17 @@ use crate::middleware::auth::{HasValidSession, HashSessionToken}; use crate::prelude::*; use crate::util::serde::{apply_expr, HandlerExtSerde}; +lazy_static::lazy_static! { + static ref PUBLIC: JsonPointer = "/public".parse().unwrap(); +} + #[instrument(skip_all)] async fn ws_handler( ctx: RpcContext, session: Option<(HasValidSession, HashSessionToken)>, mut stream: WebSocket, ) -> Result<(), Error> { - let (dump, sub) = ctx.db.dump_and_sub().await; + let (dump, sub) = ctx.db.dump_and_sub(PUBLIC.clone()).await; if let Some((session, token)) = session { let kill = subscribe_to_session_kill(&ctx, token).await; @@ -181,7 +185,7 @@ pub enum RevisionsRes { #[instrument(skip_all)] async fn cli_dump(ctx: CliContext, DumpParams { path }: DumpParams) -> Result { let dump = if let Some(path) = path { - PatchDb::open(path).await?.dump().await + PatchDb::open(path).await?.dump(&ROOT).await } else { from_value::(ctx.call_remote("db.dump", imbl_value::json!({})).await?)? }; @@ -201,7 +205,7 @@ pub struct DumpParams { // display(display_serializable) // )] pub async fn dump(ctx: RpcContext, _: DumpParams) -> Result { - Ok(ctx.db.dump().await) + Ok(ctx.db.dump(&*PUBLIC).await) } #[instrument(skip_all)] diff --git a/core/startos/src/db/model.rs b/core/startos/src/db/model.rs index 2f4d33ffa..571573a54 100644 --- a/core/startos/src/db/model.rs +++ b/core/startos/src/db/model.rs @@ -7,7 +7,7 @@ use imbl_value::InternedString; use ipnet::{Ipv4Net, Ipv6Net}; use isocountry::CountryCode; use itertools::Itertools; -use models::{DataUrl, HealthCheckId, InterfaceId, PackageId}; +use models::{DataUrl, HealthCheckId, HostId, PackageId}; use openssl::hash::MessageDigest; use patch_db::json_ptr::JsonPointer; use patch_db::{HasModel, Value}; @@ -16,7 +16,6 @@ use serde::{Deserialize, Serialize}; use ssh_key::public::Ed25519PublicKey; use crate::account::AccountInfo; -use crate::config::spec::PackagePointerSpec; use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; use crate::prelude::*; use crate::progress::FullProgress; @@ -30,72 +29,85 @@ use crate::{ARCH, PLATFORM}; #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] #[model = "Model"] -// #[macro_debug] pub struct Database { - pub server_info: ServerInfo, - pub package_data: AllPackageData, - pub ui: Value, + pub public: Public, + pub private: (), // TODO } impl Database { pub fn init(account: &AccountInfo) -> Self { let lan_address = account.hostname.lan_address().parse().unwrap(); Database { - server_info: ServerInfo { - arch: get_arch(), - platform: get_platform(), - id: account.server_id.clone(), - version: Current::new().semver().into(), - hostname: account.hostname.no_dot_host_name(), - last_backup: None, - last_wifi_region: None, - eos_version_compat: Current::new().compat().clone(), - lan_address, - tor_address: format!("https://{}", account.key.tor_address()) - .parse() - .unwrap(), - ip_info: BTreeMap::new(), - status_info: ServerStatus { - backup_progress: None, - updated: false, - update_progress: None, - shutting_down: false, - restarting: false, - }, - wifi: WifiInfo { - ssids: Vec::new(), - connected: None, - selected: None, - }, - unread_notification_count: 0, - connection_addresses: ConnectionAddresses { - tor: Vec::new(), - clearnet: Vec::new(), - }, - password_hash: account.password.clone(), - pubkey: ssh_key::PublicKey::from(Ed25519PublicKey::from(&account.key.ssh_key())) + public: Public { + server_info: ServerInfo { + arch: get_arch(), + platform: get_platform(), + id: account.server_id.clone(), + version: Current::new().semver().into(), + hostname: account.hostname.no_dot_host_name(), + last_backup: None, + last_wifi_region: None, + eos_version_compat: Current::new().compat().clone(), + lan_address, + tor_address: format!("https://{}", account.key.tor_address()) + .parse() + .unwrap(), + ip_info: BTreeMap::new(), + status_info: ServerStatus { + backup_progress: None, + updated: false, + update_progress: None, + shutting_down: false, + restarting: false, + }, + wifi: WifiInfo { + ssids: Vec::new(), + connected: None, + selected: None, + }, + unread_notification_count: 0, + connection_addresses: ConnectionAddresses { + tor: Vec::new(), + clearnet: Vec::new(), + }, + password_hash: account.password.clone(), + pubkey: ssh_key::PublicKey::from(Ed25519PublicKey::from( + &account.key.ssh_key(), + )) .to_openssh() .unwrap(), - ca_fingerprint: account - .root_ca_cert - .digest(MessageDigest::sha256()) - .unwrap() - .iter() - .map(|x| format!("{x:X}")) - .join(":"), - ntp_synced: false, - zram: true, - governor: None, + ca_fingerprint: account + .root_ca_cert + .digest(MessageDigest::sha256()) + .unwrap() + .iter() + .map(|x| format!("{x:X}")) + .join(":"), + ntp_synced: false, + zram: true, + governor: None, + }, + package_data: AllPackageData::default(), + ui: serde_json::from_str(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../web/patchdb-ui-seed.json" + ))) + .unwrap(), }, - package_data: AllPackageData::default(), - ui: serde_json::from_str(include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../web/patchdb-ui-seed.json" - ))) - .unwrap(), + private: (), // TODO } } } +#[derive(Debug, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +// #[macro_debug] +pub struct Public { + pub server_info: ServerInfo, + pub package_data: AllPackageData, + pub ui: Value, +} + pub type DatabaseModel = Model; fn get_arch() -> InternedString { @@ -532,14 +544,13 @@ pub struct StaticDependencyInfo { #[model = "Model"] pub struct CurrentDependencyInfo { #[serde(default)] - pub pointers: BTreeSet, pub health_checks: BTreeSet, } #[derive(Debug, Default, Deserialize, Serialize)] -pub struct InterfaceAddressMap(pub BTreeMap); +pub struct InterfaceAddressMap(pub BTreeMap); impl Map for InterfaceAddressMap { - type Key = InterfaceId; + type Key = HostId; type Value = InterfaceAddresses; } diff --git a/core/startos/src/db/prelude.rs b/core/startos/src/db/prelude.rs index 15e511d53..14f5e21eb 100644 --- a/core/startos/src/db/prelude.rs +++ b/core/startos/src/db/prelude.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::panic::UnwindSafe; pub use imbl_value::Value; +use patch_db::json_ptr::ROOT; use patch_db::value::InternedString; pub use patch_db::{HasModel, PatchDb}; use serde::de::DeserializeOwned; @@ -42,7 +43,7 @@ pub trait PatchDbExt { #[async_trait::async_trait] impl PatchDbExt for PatchDb { async fn peek(&self) -> DatabaseModel { - DatabaseModel::from(self.dump().await.value) + DatabaseModel::from(self.dump(&ROOT).await.value) } async fn mutate( &self, diff --git a/core/startos/src/dependencies.rs b/core/startos/src/dependencies.rs index d6b297e13..6ebe7afed 100644 --- a/core/startos/src/dependencies.rs +++ b/core/startos/src/dependencies.rs @@ -8,7 +8,6 @@ use rpc_toolkit::{command, from_fn_async, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; -use crate::config::spec::PackagePointerSpec; use crate::config::{Config, ConfigSpec, ConfigureContext}; use crate::context::{CliContext, RpcContext}; use crate::db::model::{CurrentDependencies, Database}; @@ -66,19 +65,11 @@ pub struct ConfigureParams { dependency_id: PackageId, } pub fn configure() -> ParentHandler { - ParentHandler::new() - .root_handler( - from_fn_async(configure_impl) - .with_inherited(|params, _| params) - .no_cli(), - ) - .subcommand( - "dry", - from_fn_async(configure_dry) - .with_inherited(|params, _| params) - .with_display_serializable() - .with_remote_cli::(), - ) + ParentHandler::new().root_handler( + from_fn_async(configure_impl) + .with_inherited(|params, _| params) + .no_cli(), + ) } pub async fn configure_impl( @@ -89,8 +80,6 @@ pub async fn configure_impl( dependency_id, }: ConfigureParams, ) -> Result<(), Error> { - let breakages = BTreeMap::new(); - let overrides = Default::default(); let ConfigDryRes { old_config: _, new_config, @@ -98,11 +87,8 @@ pub async fn configure_impl( } = configure_logic(ctx.clone(), (dependent_id, dependency_id.clone())).await?; let configure_context = ConfigureContext { - breakages, timeout: Some(Duration::from_secs(3).into()), config: Some(new_config), - dry_run: false, - overrides, }; ctx.services .get(&dependency_id) @@ -127,19 +113,6 @@ pub struct ConfigDryRes { pub spec: ConfigSpec, } -// #[command(rename = "dry", display(display_serializable))] -#[instrument(skip_all)] -pub async fn configure_dry( - ctx: RpcContext, - _: Empty, - ConfigureParams { - dependent_id, - dependency_id, - }: ConfigureParams, -) -> Result { - configure_logic(ctx, (dependent_id, dependency_id)).await -} - pub async fn configure_logic( ctx: RpcContext, (dependent_id, dependency_id): (PackageId, PackageId), @@ -226,6 +199,7 @@ pub fn add_dependent_to_current_dependents_lists( ) -> Result<(), Error> { for (dependency, dep_info) in ¤t_dependencies.0 { if let Some(dependency_dependents) = db + .as_public_mut() .as_package_data_mut() .as_idx_mut(dependency) .and_then(|pde| pde.as_installed_mut()) @@ -237,46 +211,6 @@ pub fn add_dependent_to_current_dependents_lists( Ok(()) } -pub fn set_dependents_with_live_pointers_to_needs_config( - db: &mut Peeked, - id: &PackageId, -) -> Result, Error> { - let mut res = Vec::new(); - for (dep, info) in db - .as_package_data() - .as_idx(id) - .or_not_found(id)? - .as_installed() - .or_not_found(id)? - .as_current_dependents() - .de()? - .0 - { - if info.pointers.iter().any(|ptr| match ptr { - // dependency id matches the package being uninstalled - PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == id && &dep != id, - PackagePointerSpec::LanAddress(ptr) => &ptr.package_id == id && &dep != id, - // we never need to retarget these - PackagePointerSpec::TorKey(_) => false, - PackagePointerSpec::Config(_) => false, - }) { - let installed = db - .as_package_data_mut() - .as_idx_mut(&dep) - .or_not_found(&dep)? - .as_installed_mut() - .or_not_found(&dep)?; - let version = installed.as_manifest().as_version().de()?; - let configured = installed.as_status_mut().as_configured_mut(); - if configured.de()? { - configured.ser(&false)?; - res.push((dep, version)); - } - } - } - Ok(res) -} - #[instrument(skip_all)] pub async fn compute_dependency_config_errs( ctx: &RpcContext, diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index dfc13e068..fab80ab09 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -218,7 +218,7 @@ pub async fn init(cfg: &ServerConfig) -> Result { let db = cfg.db(&account).await?; tracing::info!("Opened PatchDB"); let peek = db.peek().await; - let mut server_info = peek.as_server_info().de()?; + let mut server_info = peek.as_public().as_server_info().de()?; // write to ca cert store tokio::fs::write( @@ -343,7 +343,7 @@ pub async fn init(cfg: &ServerConfig) -> Result { }; db.mutate(|v| { - v.as_server_info_mut().ser(&server_info)?; + v.as_public_mut().as_server_info_mut().ser(&server_info)?; Ok(()) }) .await?; diff --git a/core/startos/src/install/mod.rs b/core/startos/src/install/mod.rs index 110443162..ac00a750b 100644 --- a/core/startos/src/install/mod.rs +++ b/core/startos/src/install/mod.rs @@ -41,7 +41,7 @@ pub const PKG_WASM_DIR: &str = "package-data/wasm"; // #[command(display(display_serializable))] pub async fn list(ctx: RpcContext) -> Result { - Ok(ctx.db.peek().await.as_package_data().as_entries()? + Ok(ctx.db.peek().await.as_public().as_package_data().as_entries()? .iter() .filter_map(|(id, pde)| { let status = match pde.as_match() { @@ -185,7 +185,13 @@ pub async fn sideload(ctx: RpcContext) -> Result { let (err_send, err_recv) = oneshot::channel(); let progress = RequestGuid::new(); let db = ctx.db.clone(); - let mut sub = db.subscribe().await; + let mut sub = db + .subscribe( + "/package-data/{id}/install-progress" + .parse::() + .with_kind(ErrorKind::Database)?, + ) + .await; ctx.add_continuation( progress.clone(), RpcContinuation::ws( @@ -199,17 +205,15 @@ pub async fn sideload(ctx: RpcContext) -> Result { ErrorKind::Cancelled, ) })?; - let progress_path = - JsonPointer::parse(format!("/package-data/{id}/install-progress")) - .with_kind(ErrorKind::Database)?; tokio::select! { res = async { while let Some(rev) = sub.recv().await { - if rev.patch.affects_path(&progress_path) { + if !rev.patch.0.is_empty() { // TODO: don't send empty patches? ws.send(Message::Text( serde_json::to_string(&if let Some(p) = db .peek() .await + .as_public() .as_package_data() .as_idx(&id) .and_then(|e| e.as_install_progress()) @@ -230,16 +234,18 @@ pub async fn sideload(ctx: RpcContext) -> Result { } => res?, err = err_recv => { if let Ok(e) = err { - ws.send(Message::Text( - serde_json::to_string(&Err::<(), _>(e)) - .with_kind(ErrorKind::Serialization)?, - )) - .await - .with_kind(ErrorKind::Network)?; + ws.send(Message::Text( + serde_json::to_string(&Err::<(), _>(e)) + .with_kind(ErrorKind::Serialization)?, + )) + .await + .with_kind(ErrorKind::Network)?; } } } + ws.close().await.with_kind(ErrorKind::Network)?; + Ok::<_, Error>(()) } .await @@ -250,7 +256,7 @@ pub async fn sideload(ctx: RpcContext) -> Result { } .boxed() }), - Duration::from_secs(30), + Duration::from_secs(600), ), ) .await; @@ -405,26 +411,31 @@ pub async fn uninstall( ) -> Result { ctx.db .mutate(|db| { - let (manifest, static_files, installed) = - match db.as_package_data().as_idx(&id).or_not_found(&id)?.de()? { - PackageDataEntry::Installed(PackageDataEntryInstalled { - manifest, - static_files, - installed, - }) => (manifest, static_files, installed), - _ => { - return Err(Error::new( - eyre!("Package is not installed."), - crate::ErrorKind::NotFound, - )); - } - }; + let (manifest, static_files, installed) = match db + .as_public() + .as_package_data() + .as_idx(&id) + .or_not_found(&id)? + .de()? + { + PackageDataEntry::Installed(PackageDataEntryInstalled { + manifest, + static_files, + installed, + }) => (manifest, static_files, installed), + _ => { + return Err(Error::new( + eyre!("Package is not installed."), + crate::ErrorKind::NotFound, + )); + } + }; let pde = PackageDataEntry::Removing(PackageDataEntryRemoving { manifest, static_files, removing: installed, }); - db.as_package_data_mut().insert(&id, &pde) + db.as_public_mut().as_package_data_mut().insert(&id, &pde) }) .await?; diff --git a/core/startos/src/net/dhcp.rs b/core/startos/src/net/dhcp.rs index 1c9d65d24..a8dbcabb0 100644 --- a/core/startos/src/net/dhcp.rs +++ b/core/startos/src/net/dhcp.rs @@ -75,7 +75,8 @@ pub async fn update( let ip_info = IpInfo::for_interface(&interface).await?; ctx.db .mutate(|db| { - db.as_server_info_mut() + db.as_public_mut() + .as_server_info_mut() .as_ip_info_mut() .insert(&interface, &ip_info) }) diff --git a/core/startos/src/net/dns.rs b/core/startos/src/net/dns.rs index 7b2784a50..9eb5d3750 100644 --- a/core/startos/src/net/dns.rs +++ b/core/startos/src/net/dns.rs @@ -163,13 +163,13 @@ impl DnsController { Command::new("resolvectl") .arg("dns") - .arg("br-start9") + .arg("lxcbr0") .arg("127.0.0.1") .invoke(ErrorKind::Network) .await?; Command::new("resolvectl") .arg("domain") - .arg("br-start9") + .arg("lxcbr0") .arg("embassy") .invoke(ErrorKind::Network) .await?; diff --git a/core/startos/src/net/host/mod.rs b/core/startos/src/net/host/mod.rs new file mode 100644 index 000000000..b2b991698 --- /dev/null +++ b/core/startos/src/net/host/mod.rs @@ -0,0 +1,29 @@ +use imbl_value::InternedString; +use serde::{Deserialize, Serialize}; + +use crate::net::host::multi::MultiHost; + +pub mod multi; + +pub enum Host { + Multi(MultiHost), + // Single(SingleHost), + // Static(StaticHost), +} + +#[derive(Deserialize, Serialize)] +pub struct BindOptions { + scheme: InternedString, + preferred_external_port: u16, + add_ssl: Option, + secure: bool, + ssl: bool, +} + +#[derive(Deserialize, Serialize)] +pub struct AddSslOptions { + scheme: InternedString, + preferred_external_port: u16, + #[serde(default)] + add_x_forwarded_headers: bool, +} diff --git a/core/startos/src/net/host/multi.rs b/core/startos/src/net/host/multi.rs new file mode 100644 index 000000000..511619201 --- /dev/null +++ b/core/startos/src/net/host/multi.rs @@ -0,0 +1,13 @@ +use std::collections::BTreeMap; + +use imbl_value::InternedString; +use serde::{Deserialize, Serialize}; + +use crate::net::host::BindOptions; +use crate::net::keys::Key; + +pub struct MultiHost { + id: InternedString, + key: Key, + binds: BTreeMap, +} diff --git a/core/startos/src/net/interface.rs b/core/startos/src/net/interface.rs deleted file mode 100644 index f1fa1e406..000000000 --- a/core/startos/src/net/interface.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::collections::BTreeMap; - -use indexmap::IndexSet; -pub use models::InterfaceId; -use models::PackageId; -use serde::{Deserialize, Deserializer, Serialize}; -use sqlx::{Executor, Postgres}; -use tracing::instrument; - -use crate::db::model::{InterfaceAddressMap, InterfaceAddresses}; -use crate::net::keys::Key; -use crate::util::serde::Port; -use crate::{Error, ResultExt}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct Interfaces(pub BTreeMap); // TODO -impl Interfaces { - #[instrument(skip_all)] - pub fn validate(&self) -> Result<(), Error> { - for (_, interface) in &self.0 { - interface.validate().with_ctx(|_| { - ( - crate::ErrorKind::ValidateS9pk, - format!("Interface {}", interface.name), - ) - })?; - } - Ok(()) - } - #[instrument(skip_all)] - pub async fn install( - &self, - secrets: &mut Ex, - package_id: &PackageId, - ) -> Result - where - for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, - { - let mut interface_addresses = InterfaceAddressMap(BTreeMap::new()); - for (id, iface) in &self.0 { - let mut addrs = InterfaceAddresses { - tor_address: None, - lan_address: None, - }; - if iface.tor_config.is_some() || iface.lan_config.is_some() { - let key = - Key::for_interface(secrets, Some((package_id.clone(), id.clone()))).await?; - if iface.tor_config.is_some() { - addrs.tor_address = Some(key.tor_address().to_string()); - } - if iface.lan_config.is_some() { - addrs.lan_address = Some(key.local_address()); - } - } - interface_addresses.0.insert(id.clone(), addrs); - } - Ok(interface_addresses) - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct Interface { - pub name: String, - pub description: String, - pub tor_config: Option, - pub lan_config: Option>, - pub ui: bool, - pub protocols: IndexSet, -} -impl Interface { - #[instrument(skip_all)] - pub fn validate(&self) -> Result<(), color_eyre::eyre::Report> { - if self.tor_config.is_some() && !self.protocols.contains("tcp") { - color_eyre::eyre::bail!("must support tcp to set up a tor hidden service"); - } - if self.lan_config.is_some() && !self.protocols.contains("http") { - color_eyre::eyre::bail!("must support http to set up a lan service"); - } - if self.ui && !(self.protocols.contains("http") || self.protocols.contains("https")) { - color_eyre::eyre::bail!("must support http or https to serve a ui"); - } - Ok(()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct TorConfig { - pub port_mapping: BTreeMap, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct LanPortConfig { - pub ssl: bool, - pub internal: u16, -} -impl<'de> Deserialize<'de> for LanPortConfig { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(rename_all = "kebab-case")] - struct PermissiveLanPortConfig { - ssl: bool, - internal: Option, - mapping: Option, - } - - let config = PermissiveLanPortConfig::deserialize(deserializer)?; - Ok(LanPortConfig { - ssl: config.ssl, - internal: config - .internal - .or(config.mapping) - .ok_or_else(|| serde::de::Error::missing_field("internal"))?, - }) - } -} diff --git a/core/startos/src/net/keys.rs b/core/startos/src/net/keys.rs index 4816fd98a..1079d4a98 100644 --- a/core/startos/src/net/keys.rs +++ b/core/startos/src/net/keys.rs @@ -1,6 +1,6 @@ use clap::Parser; use color_eyre::eyre::eyre; -use models::{Id, InterfaceId, PackageId}; +use models::{HostId, Id, PackageId}; use openssl::pkey::{PKey, Private}; use openssl::sha::Sha256; use openssl::x509::X509; @@ -22,13 +22,13 @@ use crate::util::crypto::ed25519_expand_key; // TODO: delete once we may change tor addresses async fn compat( secrets: impl PgExecutor<'_>, - interface: &Option<(PackageId, InterfaceId)>, + host: &Option<(PackageId, HostId)>, ) -> Result, Error> { - if let Some((package, interface)) = interface { + if let Some((package, host)) = host { if let Some(r) = sqlx::query!( "SELECT key FROM tor WHERE package = $1 AND interface = $2", package, - interface + host ) .fetch_optional(secrets) .await? @@ -60,19 +60,19 @@ async fn compat( #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Key { - interface: Option<(PackageId, InterfaceId)>, + host: Option<(PackageId, HostId)>, base: [u8; 32], tor_key: [u8; 64], // Does NOT necessarily match base } impl Key { - pub fn interface(&self) -> Option<(PackageId, InterfaceId)> { - self.interface.clone() + pub fn host(&self) -> Option<(PackageId, HostId)> { + self.host.clone() } pub fn as_bytes(&self) -> [u8; 32] { self.base } pub fn internal_address(&self) -> String { - self.interface + self.host .as_ref() .map(|(pkg_id, _)| format!("{}.embassy", pkg_id)) .unwrap_or_else(|| "embassy".to_owned()) @@ -111,21 +111,21 @@ impl Key { Ed25519PrivateKey::from_bytes(&self.base) } pub(crate) fn from_pair( - interface: Option<(PackageId, InterfaceId)>, + host: Option<(PackageId, HostId)>, bytes: [u8; 32], tor_key: [u8; 64], ) -> Self { Self { - interface, + host, tor_key, base: bytes, } } - pub fn from_bytes(interface: Option<(PackageId, InterfaceId)>, bytes: [u8; 32]) -> Self { - Self::from_pair(interface, bytes, ed25519_expand_key(&bytes)) + pub fn from_bytes(host: Option<(PackageId, HostId)>, bytes: [u8; 32]) -> Self { + Self::from_pair(host, bytes, ed25519_expand_key(&bytes)) } - pub fn new(interface: Option<(PackageId, InterfaceId)>) -> Self { - Self::from_bytes(interface, rand::random()) + pub fn new(host: Option<(PackageId, HostId)>) -> Self { + Self::from_bytes(host, rand::random()) } pub(super) fn with_certs(self, certs: CertPair, int: X509, root: X509) -> KeyInfo { KeyInfo { @@ -163,10 +163,7 @@ impl Key { .await? .into_iter() .map(|row| { - let interface = Some(( - package.clone(), - InterfaceId::from(Id::try_from(row.interface)?), - )); + let host = Some((package.clone(), HostId::from(Id::try_from(row.interface)?))); let bytes = row.key.try_into().map_err(|e: Vec| { Error::new( eyre!("Invalid length for network key {} expected 32", e.len()), @@ -175,7 +172,7 @@ impl Key { })?; Ok(match row.tor_key { Some(tor_key) => Key::from_pair( - interface, + host, bytes, tor_key.try_into().map_err(|e: Vec| { Error::new( @@ -184,20 +181,20 @@ impl Key { ) })?, ), - None => Key::from_bytes(interface, bytes), + None => Key::from_bytes(host, bytes), }) }) .collect() } - pub async fn for_interface( + pub async fn for_host( secrets: &mut Ex, - interface: Option<(PackageId, InterfaceId)>, + host: Option<(PackageId, HostId)>, ) -> Result where for<'a> &'a mut Ex: PgExecutor<'a>, { let tentative = rand::random::<[u8; 32]>(); - let actual = if let Some((pkg, iface)) = &interface { + let actual = if let Some((pkg, iface)) = &host { let k = tentative.as_slice(); let actual = sqlx::query!( "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET package = EXCLUDED.package RETURNING key", @@ -229,8 +226,8 @@ impl Key { })?); bytes }; - let mut res = Self::from_bytes(interface, actual); - if let Some(tor_key) = compat(secrets, &res.interface).await? { + let mut res = Self::from_bytes(host, actual); + if let Some(tor_key) = compat(secrets, &res.host).await? { res.tor_key = tor_key; } Ok(res) @@ -288,43 +285,43 @@ pub fn display_requires_reboot(_: RotateKeysParams, args: RequiresReboot) { #[command(rename_all = "kebab-case")] pub struct RotateKeysParams { package: Option, - interface: Option, + host: Option, } // #[command(display(display_requires_reboot))] pub async fn rotate_key( ctx: RpcContext, - RotateKeysParams { package, interface }: RotateKeysParams, + RotateKeysParams { package, host }: RotateKeysParams, ) -> Result { let mut pgcon = ctx.secret_store.acquire().await?; let mut tx = pgcon.begin().await?; if let Some(package) = package { - let Some(interface) = interface else { + let Some(host) = host else { return Err(Error::new( - eyre!("Must specify interface"), + eyre!("Must specify host"), ErrorKind::InvalidRequest, )); }; sqlx::query!( "DELETE FROM tor WHERE package = $1 AND interface = $2", &package, - &interface, + &host, ) .execute(&mut *tx) .await?; sqlx::query!( "DELETE FROM network_keys WHERE package = $1 AND interface = $2", &package, - &interface, + &host, ) .execute(&mut *tx) .await?; - let new_key = - Key::for_interface(&mut *tx, Some((package.clone(), interface.clone()))).await?; + let new_key = Key::for_host(&mut *tx, Some((package.clone(), host.clone()))).await?; let needs_config = ctx .db .mutate(|v| { let installed = v + .as_public_mut() .as_package_data_mut() .as_idx_mut(&package) .or_not_found(&package)? @@ -332,8 +329,8 @@ pub async fn rotate_key( .or_not_found("installed")?; let addrs = installed .as_interface_addresses_mut() - .as_idx_mut(&interface) - .or_not_found(&interface)?; + .as_idx_mut(&host) + .or_not_found(&host)?; if let Some(lan) = addrs.as_lan_address_mut().transpose_mut() { lan.ser(&new_key.local_address())?; } @@ -380,10 +377,15 @@ pub async fn rotate_key( sqlx::query!("UPDATE account SET tor_key = NULL, network_key = gen_random_bytes(32)") .execute(&mut *tx) .await?; - let new_key = Key::for_interface(&mut *tx, None).await?; + let new_key = Key::for_host(&mut *tx, None).await?; let url = format!("https://{}", new_key.tor_address()).parse()?; ctx.db - .mutate(|v| v.as_server_info_mut().as_tor_address_mut().ser(&url)) + .mutate(|v| { + v.as_public_mut() + .as_server_info_mut() + .as_tor_address_mut() + .ser(&url) + }) .await?; tx.commit().await?; Ok(RequiresReboot(true)) diff --git a/core/startos/src/net/mod.rs b/core/startos/src/net/mod.rs index 25d7a9647..a0a2ed166 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -4,7 +4,7 @@ use crate::context::CliContext; pub mod dhcp; pub mod dns; -pub mod interface; +pub mod host; pub mod keys; pub mod mdns; pub mod net_controller; diff --git a/core/startos/src/net/net_controller.rs b/core/startos/src/net/net_controller.rs index 38aa079af..9b9145531 100644 --- a/core/startos/src/net/net_controller.rs +++ b/core/startos/src/net/net_controller.rs @@ -3,7 +3,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::{Arc, Weak}; use color_eyre::eyre::eyre; -use models::{InterfaceId, PackageId}; +use models::{HostId, PackageId}; use sqlx::PgExecutor; use tracing::instrument; @@ -20,7 +20,7 @@ use crate::{Error, HOST_IP}; pub struct NetController { pub(super) tor: TorController, pub(super) vhost: VHostController, - // pub(super) dns: DnsController, + pub(super) dns: DnsController, pub(super) ssl: Arc, pub(super) os_bindings: Vec>, } @@ -39,7 +39,7 @@ impl NetController { let mut res = Self { tor: TorController::new(tor_control, tor_socks), vhost: VHostController::new(ssl.clone()), - // dns: DnsController::init(dns_bind).await?, + dns: DnsController::init(dns_bind).await?, ssl, os_bindings: Vec::new(), }; @@ -60,8 +60,8 @@ impl NetController { alpn.clone(), ) .await?; - // self.os_bindings - // .push(self.dns.add(None, HOST_IP.into()).await?); + self.os_bindings + .push(self.dns.add(None, HOST_IP.into()).await?); // LAN IP self.os_bindings.push( @@ -147,13 +147,13 @@ impl NetController { package: PackageId, ip: Ipv4Addr, ) -> Result { - // let dns = self.dns.add(Some(package.clone()), ip).await?; + let dns = self.dns.add(Some(package.clone()), ip).await?; Ok(NetService { shutdown: false, id: package, ip, - // dns, + dns, controller: Arc::downgrade(self), tor: BTreeMap::new(), lan: BTreeMap::new(), @@ -212,10 +212,10 @@ pub struct NetService { shutdown: bool, id: PackageId, ip: Ipv4Addr, - // dns: Arc<()>, + dns: Arc<()>, controller: Weak, - tor: BTreeMap<(InterfaceId, u16), (Key, Vec>)>, - lan: BTreeMap<(InterfaceId, u16), (Key, Vec>)>, + tor: BTreeMap<(HostId, u16), (Key, Vec>)>, + lan: BTreeMap<(HostId, u16), (Key, Vec>)>, } impl NetService { fn net_controller(&self) -> Result, Error> { @@ -229,14 +229,14 @@ impl NetService { pub async fn add_tor( &mut self, secrets: &mut Ex, - id: InterfaceId, + id: HostId, external: u16, internal: u16, ) -> Result<(), Error> where for<'a> &'a mut Ex: PgExecutor<'a>, { - let key = Key::for_interface(secrets, Some((self.id.clone(), id.clone()))).await?; + let key = Key::for_host(secrets, Some((self.id.clone(), id.clone()))).await?; let ctrl = self.net_controller()?; let tor_idx = (id, external); let mut tor = self @@ -251,7 +251,7 @@ impl NetService { self.tor.insert(tor_idx, tor); Ok(()) } - pub async fn remove_tor(&mut self, id: InterfaceId, external: u16) -> Result<(), Error> { + pub async fn remove_tor(&mut self, id: HostId, external: u16) -> Result<(), Error> { let ctrl = self.net_controller()?; if let Some((key, rcs)) = self.tor.remove(&(id, external)) { ctrl.remove_tor(&key, external, rcs).await?; @@ -261,7 +261,7 @@ impl NetService { pub async fn add_lan( &mut self, secrets: &mut Ex, - id: InterfaceId, + id: HostId, external: u16, internal: u16, connect_ssl: Result<(), AlpnInfo>, @@ -269,7 +269,7 @@ impl NetService { where for<'a> &'a mut Ex: PgExecutor<'a>, { - let key = Key::for_interface(secrets, Some((self.id.clone(), id.clone()))).await?; + let key = Key::for_host(secrets, Some((self.id.clone(), id.clone()))).await?; let ctrl = self.net_controller()?; let lan_idx = (id, external); let mut lan = self @@ -289,7 +289,7 @@ impl NetService { self.lan.insert(lan_idx, lan); Ok(()) } - pub async fn remove_lan(&mut self, id: InterfaceId, external: u16) -> Result<(), Error> { + pub async fn remove_lan(&mut self, id: HostId, external: u16) -> Result<(), Error> { let ctrl = self.net_controller()?; if let Some((key, rcs)) = self.lan.remove(&(id, external)) { ctrl.remove_lan(&key, external, rcs).await?; @@ -299,13 +299,13 @@ impl NetService { pub async fn export_cert( &self, secrets: &mut Ex, - id: &InterfaceId, + id: &HostId, ip: IpAddr, ) -> Result<(), Error> where for<'a> &'a mut Ex: PgExecutor<'a>, { - let key = Key::for_interface(secrets, Some((self.id.clone(), id.clone()))).await?; + let key = Key::for_host(secrets, Some((self.id.clone(), id.clone()))).await?; let ctrl = self.net_controller()?; let cert = ctrl.ssl.with_certs(key, ip).await?; let cert_dir = cert_dir(&self.id, id); @@ -332,8 +332,8 @@ impl NetService { for ((_, external), (key, rcs)) in std::mem::take(&mut self.tor) { errors.handle(ctrl.remove_tor(&key, external, rcs).await); } - // std::mem::take(&mut self.dns); - // errors.handle(ctrl.dns.gc(Some(self.id.clone()), self.ip).await); + std::mem::take(&mut self.dns); + errors.handle(ctrl.dns.gc(Some(self.id.clone()), self.ip).await); errors.into_result() } else { tracing::warn!("NetService dropped after NetController is shutdown"); @@ -355,7 +355,7 @@ impl Drop for NetService { shutdown: true, id: Default::default(), ip: Ipv4Addr::new(0, 0, 0, 0), - // dns: Default::default(), + dns: Default::default(), controller: Default::default(), tor: Default::default(), lan: Default::default(), diff --git a/core/startos/src/net/ssl.rs b/core/startos/src/net/ssl.rs index a3a6a24c9..f9502c86b 100644 --- a/core/startos/src/net/ssl.rs +++ b/core/startos/src/net/ssl.rs @@ -336,7 +336,7 @@ pub struct SANInfo { impl SANInfo { pub fn new(key: &Key, hostname: &Hostname, ips: BTreeSet) -> Self { let mut dns = BTreeSet::new(); - if let Some((id, _)) = key.interface() { + if let Some((id, _)) = key.host() { dns.insert(MaybeWildcard::WithWildcard(format!("{id}.embassy"))); dns.insert(MaybeWildcard::WithWildcard(key.local_address().to_string())); } else { diff --git a/core/startos/src/net/wifi.rs b/core/startos/src/net/wifi.rs index be1c49fdc..5a86ad720 100644 --- a/core/startos/src/net/wifi.rs +++ b/core/startos/src/net/wifi.rs @@ -682,7 +682,8 @@ impl WpaCli { pub async fn save_config(&mut self, db: PatchDb) -> Result<(), Error> { let new_country = self.get_country_low().await?; db.mutate(|d| { - d.as_server_info_mut() + d.as_public_mut() + .as_server_info_mut() .as_last_wifi_region_mut() .ser(&new_country) }) diff --git a/core/startos/src/notifications.rs b/core/startos/src/notifications.rs index aa0b0b963..f16eab176 100644 --- a/core/startos/src/notifications.rs +++ b/core/startos/src/notifications.rs @@ -102,7 +102,8 @@ pub async fn list( ctx.db .mutate(|d| { - d.as_server_info_mut() + d.as_public_mut() + .as_server_info_mut() .as_unread_notification_count_mut() .ser(&0) }) @@ -308,7 +309,11 @@ impl NotificationManager { { return Ok(()); } - let mut count = peek.as_server_info().as_unread_notification_count().de()?; + let mut count = peek + .as_public() + .as_server_info() + .as_unread_notification_count() + .de()?; let sql_package_id = package_id.as_ref().map(|p| &**p); let sql_code = T::CODE; let sql_level = format!("{}", level); @@ -325,7 +330,8 @@ impl NotificationManager { ).execute(&self.sqlite).await?; count += 1; db.mutate(|db| { - db.as_server_info_mut() + db.as_public_mut() + .as_server_info_mut() .as_unread_notification_count_mut() .ser(&count) }) diff --git a/core/startos/src/service/config.rs b/core/startos/src/service/config.rs index c64e2be65..06dc8bd55 100644 --- a/core/startos/src/service/config.rs +++ b/core/startos/src/service/config.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use models::PackageId; +use models::{ActionId, PackageId, ProcedureName}; use crate::config::ConfigureContext; use crate::prelude::*; @@ -9,14 +9,13 @@ use crate::service::Service; impl Service { pub async fn configure( &self, - ConfigureContext { - breakages, - timeout, - config, - overrides, - dry_run, - }: ConfigureContext, - ) -> Result, Error> { - todo!() + ConfigureContext { timeout, config }: ConfigureContext, + ) -> Result<(), Error> { + let container = &self.seed.persistent_container; + container + .execute::(ProcedureName::SetConfig, to_value(&config)?, timeout) + .await + .with_kind(ErrorKind::Action)?; + Ok(()) } } diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index fd8412c02..b572caa89 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -7,7 +7,7 @@ use futures::future::BoxFuture; use imbl::OrdMap; use models::{ActionId, HealthCheckId, PackageId, ProcedureName}; use persistent_container::PersistentContainer; -use rpc_toolkit::{from_fn_async, CallRemoteHandler, Handler, HandlerArgs}; +use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, Handler, HandlerArgs}; use serde::{Deserialize, Serialize}; use start_stop::StartStop; use tokio::sync::{watch, Notify}; @@ -128,6 +128,7 @@ impl Service { .db .peek() .await + .into_public() .into_package_data() .into_idx(id) .map(|pde| pde.into_match()) @@ -151,7 +152,7 @@ impl Service { } // TODO: delete s9pk? ctx.db - .mutate(|v| v.as_package_data_mut().remove(id)) + .mutate(|v| v.as_public_mut().as_package_data_mut().remove(id)) .await?; Ok(None) } @@ -188,7 +189,8 @@ impl Service { .mutate({ let manifest = s9pk.as_manifest().clone(); |db| { - db.as_package_data_mut() + db.as_public_mut() + .as_package_data_mut() .as_idx_mut(&manifest.id) .or_not_found(&manifest.id)? .ser(&PackageDataEntry::Installed(PackageDataEntryInstalled { @@ -229,7 +231,7 @@ impl Service { } ctx.db - .mutate(|v| v.as_package_data_mut().remove(id)) + .mutate(|v| v.as_public_mut().as_package_data_mut().remove(id)) .await?; Ok(None) @@ -274,7 +276,8 @@ impl Service { } ctx.db .mutate(|d| { - d.as_package_data_mut() + d.as_public_mut() + .as_package_data_mut() .as_idx_mut(&manifest.id) .or_not_found(&manifest.id)? .ser(&PackageDataEntry::Installed(PackageDataEntryInstalled { @@ -346,6 +349,11 @@ impl Service { .await; if let Some((hdl, shutdown)) = self.seed.persistent_container.rpc_server.send_replace(None) { + self.seed + .persistent_container + .rpc_client + .request(rpc::Exit, Empty {}) + .await?; shutdown.shutdown(); hdl.await.with_kind(ErrorKind::Cancelled)?; } @@ -367,6 +375,12 @@ impl Service { .persistent_container .execute(ProcedureName::Uninit, to_value(&target_version)?, None) // TODO timeout .await?; + let id = self.seed.persistent_container.s9pk.as_manifest().id.clone(); + self.seed + .ctx + .db + .mutate(|d| d.as_public_mut().as_package_data_mut().remove(&id)) + .await?; self.shutdown().await } pub async fn backup(&self, guard: impl GenericMountGuard) -> Result { @@ -416,6 +430,7 @@ impl Actor for ServiceActor { .db .mutate(|d| { if let Some(i) = d + .as_public_mut() .as_package_data_mut() .as_idx_mut(&id) .and_then(|p| p.as_installed_mut()) diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index 6716ed472..eee353a07 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -46,7 +46,7 @@ struct ProcedureId(u64); pub struct PersistentContainer { pub(super) s9pk: S9pk, pub(super) lxc_container: OnceCell, - rpc_client: UnixRpcClient, + pub(super) rpc_client: UnixRpcClient, pub(super) rpc_server: watch::Sender, ShutdownHandle)>>, // procedures: Mutex>, js_mount: MountGuard, @@ -239,8 +239,8 @@ impl PersistentContainer { let lxc_container = self.lxc_container.take(); async move { let mut errs = ErrorCollection::new(); - errs.handle(dbg!(rpc_client.request(rpc::Exit, Empty {}).await)); if let Some((hdl, shutdown)) = rpc_server { + errs.handle(rpc_client.request(rpc::Exit, Empty {}).await); shutdown.shutdown(); errs.handle(hdl.await.with_kind(ErrorKind::Cancelled)); } diff --git a/core/startos/src/service/service_effect_handler.rs b/core/startos/src/service/service_effect_handler.rs index d5a4561f7..8fae3908a 100644 --- a/core/startos/src/service/service_effect_handler.rs +++ b/core/startos/src/service/service_effect_handler.rs @@ -12,6 +12,7 @@ use patch_db::json_ptr::JsonPointer; use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler}; use tokio::process::Command; +use crate::db::model::ExposedUI; use crate::disk::mount::filesystem::idmapped::IdMapped; use crate::disk::mount::filesystem::loop_dev::LoopDev; use crate::disk::mount::filesystem::overlayfs::OverlayGuard; @@ -23,8 +24,7 @@ use crate::service::ServiceActorSeed; use crate::status::health_check::HealthCheckResult; use crate::status::MainStatus; use crate::util::clap::FromStrParser; -use crate::util::new_guid; -use crate::{db::model::ExposedUI, util::Invoke}; +use crate::util::{new_guid, Invoke}; use crate::{echo, ARCH}; #[derive(Clone)] @@ -120,6 +120,10 @@ pub fn service_effect_handler() -> ParentHandler { from_fn_async(get_ssl_certificate).no_cli(), ) .subcommand("getSslKey", from_fn_async(get_ssl_key).no_cli()) + .subcommand( + "getServiceInterface", + from_fn_async(get_service_interface).no_cli(), + ) // TODO @DrBonez when we get the new api for 4.0 // .subcommand("setDependencies",from_fn(set_dependencies)) // .subcommand("embassyGetInterface",from_fn(embassy_get_interface)) @@ -144,6 +148,43 @@ pub fn service_effect_handler() -> ParentHandler { // .subcommand("reverseProxy",from_fn(reverse_pro)xy) // TODO Callbacks } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser)] +#[serde(rename_all = "camelCase")] +struct GetServiceInterfaceParams { + package_id: Option, + service_interface_id: String, + callback: String, +} +async fn get_service_interface( + _: AnyContext, + GetServiceInterfaceParams { + callback, + package_id, + service_interface_id, + }: GetServiceInterfaceParams, +) -> Result { + // TODO @Dr_Bonez + Ok(json!({ + "id": service_interface_id, + "name": service_interface_id, + "description": "This is a fake", + "hasPrimary": false, + "disabled": false, + "addressInfo": json!({ + "username": Value::Null, + "hostId": "HostId?", + "options": json!({ + "scheme": Value::Null, + "preferredExternalPort": 80, + "addSsl":Value::Null, + "secure": false, + "ssl": false + }), + "suffix": "http" + }), + "type": "ui" + })) +} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser)] #[serde(rename_all = "camelCase")] @@ -255,6 +296,7 @@ async fn get_store( let peeked = context.ctx.db.peek().await; let package_id = package_id.unwrap_or(context.id.clone()); let value = peeked + .as_public() .as_package_data() .as_idx(&package_id) .or_not_found(&package_id)? @@ -286,6 +328,7 @@ async fn set_store( .db .mutate(|db| { let model = db + .as_public_mut() .as_package_data_mut() .as_idx_mut(&package_id) .or_not_found(&package_id)? @@ -317,7 +360,8 @@ async fn expose_for_dependents( .ctx .db .mutate(|db| { - db.as_package_data_mut() + db.as_public_mut() + .as_package_data_mut() .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_installed_mut() @@ -344,7 +388,8 @@ async fn expose_ui( .ctx .db .mutate(|db| { - db.as_package_data_mut() + db.as_public_mut() + .as_package_data_mut() .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_installed_mut() @@ -369,7 +414,11 @@ struct ParamsMaybePackageId { async fn exists(context: EffectContext, params: ParamsPackageId) -> Result { let context = context.deref()?; let peeked = context.ctx.db.peek().await; - let package = peeked.as_package_data().as_idx(¶ms.package).is_some(); + let package = peeked + .as_public() + .as_package_data() + .as_idx(¶ms.package) + .is_some(); Ok(json!(package)) } @@ -408,6 +457,7 @@ async fn get_configured(context: EffectContext, _: Empty) -> Result Result let peeked = context.ctx.db.peek().await; let package_id = params.package_id.unwrap_or_else(|| context.id.clone()); let package = peeked + .as_public() .as_package_data() .as_idx(&package_id) .or_not_found(&package_id)? @@ -439,6 +490,7 @@ async fn running(context: EffectContext, params: ParamsMaybePackageId) -> Result let peeked = context.ctx.db.peek().await; let package_id = params.package_id.unwrap_or_else(|| context.id.clone()); let package = peeked + .as_public() .as_package_data() .as_idx(&package_id) .or_not_found(&package_id)? @@ -489,7 +541,8 @@ async fn set_configured(context: EffectContext, params: SetConfigured) -> Result .ctx .db .mutate(|db| { - db.as_package_data_mut() + db.as_public_mut() + .as_package_data_mut() .as_idx_mut(package_id) .or_not_found(package_id)? .as_installed_mut() @@ -578,6 +631,7 @@ async fn set_health(context: EffectContext, params: SetHealth) -> Result Result return Ok(()), }; - db.as_package_data_mut() + db.as_public_mut() + .as_package_data_mut() .as_idx_mut(package_id) .or_not_found(package_id)? .as_installed_mut() diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index 1fddbb8d1..7ac555aff 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -61,7 +61,7 @@ impl ServiceMap { #[instrument(skip_all)] pub async fn init(&self, ctx: &RpcContext) -> Result<(), Error> { - for id in ctx.db.peek().await.as_package_data().keys()? { + for id in ctx.db.peek().await.as_public().as_package_data().keys()? { if let Err(e) = self.load(ctx, &id, LoadDisposition::Retry).await { tracing::error!("Error loading installed package as service: {e}"); tracing::debug!("{e:?}"); @@ -136,6 +136,7 @@ impl ServiceMap { let install_progress = progress.snapshot(); move |db| { let pde = match db + .as_public() .as_package_data() .as_idx(&id) .map(|x| x.de()) @@ -174,7 +175,9 @@ impl ServiceMap { )) } }; - db.as_package_data_mut().insert(&manifest.id, &pde) + db.as_public_mut() + .as_package_data_mut() + .insert(&manifest.id, &pde) } })) .await?; @@ -194,7 +197,8 @@ impl ServiceMap { NonDetachingJoinHandle::from(tokio::spawn(progress.sync_to_db( ctx.db.clone(), move |v| { - v.as_package_data_mut() + v.as_public_mut() + .as_package_data_mut() .as_idx_mut(&deref_id) .and_then(|e| e.as_install_progress_mut()) }, diff --git a/core/startos/src/setup.rs b/core/startos/src/setup.rs index 0f47874e9..9c8d54db3 100644 --- a/core/startos/src/setup.rs +++ b/core/startos/src/setup.rs @@ -84,7 +84,8 @@ async fn setup_init( account.set_password(&password)?; account.save(secrets_tx.as_mut()).await?; db.mutate(|m| { - m.as_server_info_mut() + m.as_public_mut() + .as_server_info_mut() .as_password_hash_mut() .ser(&account.password) }) @@ -310,11 +311,6 @@ pub async fn execute( tokio::task::spawn({ async move { let ctx = ctx.clone(); - let recovery_source = recovery_source; - - let embassy_password = embassy_password; - let recovery_source = recovery_source; - let recovery_password = recovery_password; match execute_inner( ctx.clone(), embassy_logicalname, diff --git a/core/startos/src/shutdown.rs b/core/startos/src/shutdown.rs index bd99bbbd1..f6a984897 100644 --- a/core/startos/src/shutdown.rs +++ b/core/startos/src/shutdown.rs @@ -78,7 +78,8 @@ impl Shutdown { pub async fn shutdown(ctx: RpcContext) -> Result<(), Error> { ctx.db .mutate(|db| { - db.as_server_info_mut() + db.as_public_mut() + .as_server_info_mut() .as_status_info_mut() .as_shutting_down_mut() .ser(&true) @@ -97,7 +98,8 @@ pub async fn shutdown(ctx: RpcContext) -> Result<(), Error> { pub async fn restart(ctx: RpcContext) -> Result<(), Error> { ctx.db .mutate(|db| { - db.as_server_info_mut() + db.as_public_mut() + .as_server_info_mut() .as_status_info_mut() .as_restarting_mut() .ser(&true) diff --git a/core/startos/src/system.rs b/core/startos/src/system.rs index 5a5509da7..2525491f6 100644 --- a/core/startos/src/system.rs +++ b/core/startos/src/system.rs @@ -83,7 +83,7 @@ pub struct ZramParams { pub async fn zram(ctx: RpcContext, ZramParams { enable }: ZramParams) -> Result<(), Error> { let db = ctx.db.peek().await; - let zram = db.as_server_info().as_zram().de()?; + let zram = db.as_public().as_server_info().as_zram().de()?; if enable == zram { return Ok(()); } @@ -100,7 +100,10 @@ pub async fn zram(ctx: RpcContext, ZramParams { enable }: ZramParams) -> Result< } ctx.db .mutate(|v| { - v.as_server_info_mut().as_zram_mut().ser(&enable)?; + v.as_public_mut() + .as_server_info_mut() + .as_zram_mut() + .ser(&enable)?; Ok(()) }) .await?; @@ -153,10 +156,22 @@ pub async fn governor( } set_governor(&set).await?; ctx.db - .mutate(|d| d.as_server_info_mut().as_governor_mut().ser(&Some(set))) + .mutate(|d| { + d.as_public_mut() + .as_server_info_mut() + .as_governor_mut() + .ser(&Some(set)) + }) .await?; } - let current = ctx.db.peek().await.as_server_info().as_governor().de()?; + let current = ctx + .db + .peek() + .await + .as_public() + .as_server_info() + .as_governor() + .de()?; Ok(GovernorInfo { current, available }) } diff --git a/core/startos/src/update/mod.rs b/core/startos/src/update/mod.rs index a1d9a8363..31693aba0 100644 --- a/core/startos/src/update/mod.rs +++ b/core/startos/src/update/mod.rs @@ -93,7 +93,7 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result { Some((msg, reply)) if shutdown_recv.try_recv() == Err(TryRecvError::Empty) => { let mut new_bg = BackgroundJobs::default(); tokio::select! { - res = msg.handle_with(&mut actor, &mut new_bg) => { reply.send(res); }, + res = msg.handle_with(&mut actor, &mut new_bg) => { let _ = reply.send(res); }, _ = &mut bg => (), } bg.jobs.append(&mut new_bg.jobs); @@ -129,7 +129,9 @@ impl SimpleActor { )))); } let (reply_send, reply_recv) = oneshot::channel(); - self.messenger.send((Box::new(message), reply_send)); + self.messenger + .send((Box::new(message), reply_send)) + .unwrap(); futures::future::Either::Right( reply_recv .map_err(|_| Error::new(eyre!("actor runtime has exited"), ErrorKind::Unknown)) @@ -159,11 +161,11 @@ impl SimpleActor { drop(self.messenger); let timeout = match strategy { PendingMessageStrategy::CancelAll => { - self.shutdown.send(()); + self.shutdown.send(()).unwrap(); Some(Duration::from_secs(0)) } PendingMessageStrategy::FinishCurrentCancelPending { timeout } => { - self.shutdown.send(()); + self.shutdown.send(()).unwrap(); timeout } PendingMessageStrategy::FinishAll { timeout } => timeout, diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index 3e4f7c4a2..a2ef069ac 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -70,8 +70,12 @@ where let semver = self.semver().into(); let compat = self.compat().clone(); db.mutate(|d| { - d.as_server_info_mut().as_version_mut().ser(&semver)?; - d.as_server_info_mut() + d.as_public_mut() + .as_server_info_mut() + .as_version_mut() + .ser(&semver)?; + d.as_public_mut() + .as_server_info_mut() .as_eos_version_compat_mut() .ser(&compat)?; Ok(()) @@ -166,7 +170,14 @@ where } pub async fn init(db: &PatchDb, secrets: &PgPool) -> Result<(), Error> { - let version = Version::from_util_version(db.peek().await.as_server_info().as_version().de()?); + let version = Version::from_util_version( + db.peek() + .await + .as_public() + .as_server_info() + .as_version() + .de()?, + ); match version { Version::V0_3_4(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?, diff --git a/core/startos/src/version/v0_3_4.rs b/core/startos/src/version/v0_3_4.rs index e33dcb931..52a0d6960 100644 --- a/core/startos/src/version/v0_3_4.rs +++ b/core/startos/src/version/v0_3_4.rs @@ -56,20 +56,23 @@ impl VersionT for Version { let mut account = AccountInfo::load(secrets).await?; let account = db .mutate(|d| { - d.as_server_info_mut().as_pubkey_mut().ser( + d.as_public_mut().as_server_info_mut().as_pubkey_mut().ser( &ssh_key::PublicKey::from(Ed25519PublicKey::from(&account.key.ssh_key())) .to_openssh()?, )?; - d.as_server_info_mut().as_ca_fingerprint_mut().ser( - &account - .root_ca_cert - .digest(MessageDigest::sha256()) - .unwrap() - .iter() - .map(|x| format!("{x:X}")) - .join(":"), - )?; - let server_info = d.as_server_info(); + d.as_public_mut() + .as_server_info_mut() + .as_ca_fingerprint_mut() + .ser( + &account + .root_ca_cert + .digest(MessageDigest::sha256()) + .unwrap() + .iter() + .map(|x| format!("{x:X}")) + .join(":"), + )?; + let server_info = d.as_public_mut().as_server_info(); account.hostname = server_info.as_hostname().de().map(Hostname)?; account.server_id = server_info.as_id().de()?; @@ -81,15 +84,16 @@ impl VersionT for Version { let parsed_url = Some(COMMUNITY_URL.parse().unwrap()); db.mutate(|d| { - let mut ui = d.as_ui().de()?; + let mut ui = d.as_public().as_ui().de()?; use imbl_value::json; ui["marketplace"]["known-hosts"][COMMUNITY_URL] = json!({}); ui["marketplace"]["known-hosts"][MAIN_REGISTRY] = json!({}); - for package_id in d.as_package_data().keys()? { + for package_id in d.as_public().as_package_data().keys()? { if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) { continue; } - d.as_package_data_mut() + d.as_public_mut() + .as_package_data_mut() .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_installed_mut() @@ -100,19 +104,20 @@ impl VersionT for Version { ui["theme"] = json!("Dark".to_string()); ui["widgets"] = json!([]); - d.as_ui_mut().ser(&ui) + d.as_public_mut().as_ui_mut().ser(&ui) }) .await } async fn down(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { db.mutate(|d| { - let mut ui = d.as_ui().de()?; + let mut ui = d.as_public().as_ui().de()?; let parsed_url = Some(MAIN_REGISTRY.parse().unwrap()); - for package_id in d.as_package_data().keys()? { + for package_id in d.as_public().as_package_data().keys()? { if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) { continue; } - d.as_package_data_mut() + d.as_public_mut() + .as_package_data_mut() .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_installed_mut() @@ -128,7 +133,7 @@ impl VersionT for Version { ui["marketplace"]["known-hosts"][COMMUNITY_URL].take(); ui["marketplace"]["known-hosts"][MAIN_REGISTRY].take(); - d.as_ui_mut().ser(&ui) + d.as_public_mut().as_ui_mut().ser(&ui) }) .await } diff --git a/core/startos/src/version/v0_3_4_4.rs b/core/startos/src/version/v0_3_4_4.rs index b6345ca4c..686c00826 100644 --- a/core/startos/src/version/v0_3_4_4.rs +++ b/core/startos/src/version/v0_3_4_4.rs @@ -26,7 +26,7 @@ impl VersionT for Version { } async fn up(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { db.mutate(|v| { - let tor_address_lens = v.as_server_info_mut().as_tor_address_mut(); + let tor_address_lens = v.as_public_mut().as_server_info_mut().as_tor_address_mut(); let mut tor_addr = tor_address_lens.de()?; tor_addr .set_scheme("https") diff --git a/core/startos/src/version/v0_3_5.rs b/core/startos/src/version/v0_3_5.rs index ba28cd468..485ec5f81 100644 --- a/core/startos/src/version/v0_3_5.rs +++ b/core/startos/src/version/v0_3_5.rs @@ -30,7 +30,7 @@ impl VersionT for Version { async fn up(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { let peek = db.peek().await; let mut url_replacements = BTreeMap::new(); - for (_, pde) in peek.as_package_data().as_entries()? { + for (_, pde) in peek.as_public().as_package_data().as_entries()? { for (dependency, info) in pde .as_installed() .map(|i| i.as_dependency_info().as_entries()) @@ -63,7 +63,7 @@ impl VersionT for Version { } let prev_zram = db .mutate(|v| { - for (_, pde) in v.as_package_data_mut().as_entries_mut()? { + for (_, pde) in v.as_public_mut().as_package_data_mut().as_entries_mut()? { for (dependency, info) in pde .as_installed_mut() .map(|i| i.as_dependency_info_mut().as_entries_mut()) @@ -95,7 +95,10 @@ impl VersionT for Version { } } } - v.as_server_info_mut().as_zram_mut().replace(&true) + v.as_public_mut() + .as_server_info_mut() + .as_zram_mut() + .replace(&true) }) .await?; if !prev_zram { diff --git a/core/startos/src/volume.rs b/core/startos/src/volume.rs index 47d1ffc34..f8c46c71e 100644 --- a/core/startos/src/volume.rs +++ b/core/startos/src/volume.rs @@ -3,17 +3,15 @@ use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; pub use helpers::script_dir; -use models::PackageId; pub use models::VolumeId; +use models::{HostId, PackageId}; use serde::{Deserialize, Serialize}; use tracing::instrument; use crate::context::RpcContext; -use crate::net::interface::{InterfaceId, Interfaces}; use crate::net::PACKAGE_CERT_PATH; use crate::prelude::*; use crate::util::Version; -use crate::{Error, ResultExt}; pub const PKG_VOLUME_DIR: &str = "package-data/volumes"; pub const BACKUP_DIR: &str = "/media/embassy/backups"; @@ -21,21 +19,6 @@ pub const BACKUP_DIR: &str = "/media/embassy/backups"; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Volumes(BTreeMap); impl Volumes { - #[instrument(skip_all)] - pub fn validate(&self, interfaces: &Interfaces) -> Result<(), Error> { - for (id, volume) in &self.0 { - volume - .validate(interfaces) - .with_ctx(|_| (crate::ErrorKind::ValidateS9pk, format!("Volume {}", id)))?; - if let Volume::Backup { .. } = volume { - return Err(Error::new( - eyre!("Invalid volume type \"backup\""), - ErrorKind::ParseS9pk, - )); // Volume::Backup is for internal use and shouldn't be declared in manifest - } - } - Ok(()) - } #[instrument(skip_all)] pub async fn install( &self, @@ -112,8 +95,8 @@ pub fn backup_dir(pkg_id: &PackageId) -> PathBuf { Path::new(BACKUP_DIR).join(pkg_id).join("data") } -pub fn cert_dir(pkg_id: &PackageId, interface_id: &InterfaceId) -> PathBuf { - Path::new(PACKAGE_CERT_PATH).join(pkg_id).join(interface_id) +pub fn cert_dir(pkg_id: &PackageId, host_id: &HostId) -> PathBuf { + Path::new(PACKAGE_CERT_PATH).join(pkg_id).join(host_id) } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -135,23 +118,11 @@ pub enum Volume { readonly: bool, }, #[serde(rename_all = "kebab-case")] - Certificate { interface_id: InterfaceId }, + Certificate { interface_id: HostId }, #[serde(rename_all = "kebab-case")] Backup { readonly: bool }, } impl Volume { - #[instrument(skip_all)] - pub fn validate(&self, interfaces: &Interfaces) -> Result<(), color_eyre::eyre::Report> { - match self { - Volume::Certificate { interface_id } => { - if !interfaces.0.contains_key(interface_id) { - color_eyre::eyre::bail!("unknown interface: {}", interface_id); - } - } - _ => (), - } - Ok(()) - } pub async fn install( &self, path: &PathBuf, diff --git a/patch-db b/patch-db index 7096f15e9..899c63afb 160000 --- a/patch-db +++ b/patch-db @@ -1 +1 @@ -Subproject commit 7096f15e9b218f59b8ded1fd1133c70b82de74c5 +Subproject commit 899c63afb5367c66bca6cafb98164d8cbd78f7e3 diff --git a/sdk/lib/interfaces/Host.ts b/sdk/lib/interfaces/Host.ts index e767bd18b..80368fe51 100644 --- a/sdk/lib/interfaces/Host.ts +++ b/sdk/lib/interfaces/Host.ts @@ -35,6 +35,11 @@ const knownProtocols = { ssl: false, defaultPort: 8333, }, + lightning: { + secure: true, + ssl: true, + defaultPort: 9735, + }, grpc: { secure: true, ssl: true, @@ -47,11 +52,11 @@ const knownProtocols = { }, } as const -type Scheme = string | null +export type Scheme = string | null type AddSslOptions = { - preferredExternalPort: number scheme: Scheme + preferredExternalPort: number addXForwardedHeaders?: boolean /** default: false */ } type Security = { secure: false; ssl: false } | { secure: true; ssl: boolean } @@ -73,7 +78,7 @@ type NotProtocolsWithSslVariants = Exclude< ProtocolsWithSslVariants > -type PortOptionsByKnownProtocol = +type BindOptionsByKnownProtocol = | ({ protocol: ProtocolsWithSslVariants preferredExternalPort?: number @@ -85,7 +90,7 @@ type PortOptionsByKnownProtocol = scheme?: Scheme addSsl?: AddSslOptions | null } -type PortOptionsByProtocol = PortOptionsByKnownProtocol | BindOptions +type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions export type HostKind = "static" | "single" | "multi" @@ -104,7 +109,7 @@ export class Host { async bindPort( internalPort: number, - options: PortOptionsByProtocol, + options: BindOptionsByProtocol, ): Promise> { if (hasStringProtocol(options)) { return await this.bindPortForKnown(options, internalPort) @@ -138,7 +143,7 @@ export class Host { } private async bindPortForKnown( - options: PortOptionsByKnownProtocol, + options: BindOptionsByKnownProtocol, internalPort: number, ) { const scheme = @@ -174,7 +179,7 @@ export class Host { } private getAddSsl( - options: PortOptionsByKnownProtocol, + options: BindOptionsByKnownProtocol, protoInfo: KnownProtocols[keyof KnownProtocols], ): AddSslOptions | null { if ("noAddSsl" in options && options.noAddSsl) return null diff --git a/sdk/lib/interfaces/Origin.ts b/sdk/lib/interfaces/Origin.ts index 053e76ae8..aaadbea50 100644 --- a/sdk/lib/interfaces/Origin.ts +++ b/sdk/lib/interfaces/Origin.ts @@ -1,5 +1,7 @@ import { AddressInfo } from "../types" -import { Host, BindOptions } from "./Host" +import { AddressReceipt } from "./AddressReceipt" +import { Host, BindOptions, Scheme } from "./Host" +import { ServiceInterfaceBuilder } from "./ServiceInterfaceBuilder" export class Origin { constructor( @@ -7,7 +9,7 @@ export class Origin { readonly options: BindOptions, ) {} - build({ username, path, search }: BuildOptions): AddressInfo { + build({ username, path, search, schemeOverride }: BuildOptions): AddressInfo { const qpEntries = Object.entries(search) .map( ([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`, @@ -18,15 +20,77 @@ export class Origin { return { hostId: this.host.options.id, - options: this.options, + bindOptions: { + ...this.options, + scheme: schemeOverride ? schemeOverride.noSsl : this.options.scheme, + addSsl: this.options.addSsl + ? { + ...this.options.addSsl, + scheme: schemeOverride + ? schemeOverride.ssl + : this.options.addSsl.scheme, + } + : null, + }, suffix: `${path}${qp}`, username, } } + + /** + * A function to register a group of origins ( :// : ) with StartOS + * + * The returned addressReceipt serves as proof that the addresses were registered + * + * @param addressInfo + * @returns + */ + async export( + serviceInterfaces: ServiceInterfaceBuilder[], + ): Promise { + const addressesInfo = [] + for (let serviceInterface of serviceInterfaces) { + const { + name, + description, + hasPrimary, + disabled, + id, + type, + username, + path, + search, + schemeOverride, + masked, + } = serviceInterface.options + + const addressInfo = this.build({ + username, + path, + search, + schemeOverride, + }) + + await serviceInterface.options.effects.exportServiceInterface({ + id, + name, + description, + hasPrimary, + disabled, + addressInfo, + type, + masked, + }) + + addressesInfo.push(addressInfo) + } + + return addressesInfo as AddressInfo[] & AddressReceipt + } } type BuildOptions = { - scheme: string | null + schemeOverride: { ssl: Scheme; noSsl: Scheme } | null username: string | null path: string search: Record diff --git a/sdk/lib/interfaces/ServiceInterfaceBuilder.ts b/sdk/lib/interfaces/ServiceInterfaceBuilder.ts index 241cf52dc..c7b99b2d5 100644 --- a/sdk/lib/interfaces/ServiceInterfaceBuilder.ts +++ b/sdk/lib/interfaces/ServiceInterfaceBuilder.ts @@ -1,8 +1,6 @@ -import { AddressInfo, Effects } from "../types" +import { Effects } from "../types" import { ServiceInterfaceType } from "../util/utils" -import { AddressReceipt } from "./AddressReceipt" -import { Host } from "./Host" -import { Origin } from "./Origin" +import { Scheme } from "./Host" /** * A helper class for creating a Network Interface @@ -25,47 +23,11 @@ export class ServiceInterfaceBuilder { hasPrimary: boolean disabled: boolean type: ServiceInterfaceType - username: null | string + username: string | null path: string search: Record + schemeOverride: { ssl: Scheme; noSsl: Scheme } | null + masked: boolean }, ) {} - - /** - * A function to register a group of origins ( :// : ) with StartOS - * - * The returned addressReceipt serves as proof that the addresses were registered - * - * @param addressInfo - * @returns - */ - async export>( - origin: OriginForHost, - ): Promise { - const { - name, - description, - hasPrimary, - disabled, - id, - type, - username, - path, - search, - } = this.options - - const addressInfo = origin.build({ username, path, search, scheme: null }) - - await this.options.effects.exportServiceInterface({ - id, - name, - description, - hasPrimary, - disabled, - addressInfo, - type, - }) - - return addressInfo as AddressInfo & AddressReceipt - } } diff --git a/sdk/lib/test/host.test.ts b/sdk/lib/test/host.test.ts index 880a8f1a1..d891bd52c 100644 --- a/sdk/lib/test/host.test.ts +++ b/sdk/lib/test/host.test.ts @@ -19,9 +19,11 @@ describe("host", () => { username: "bar", path: "/baz", search: { qux: "yes" }, + schemeOverride: null, + masked: false, }) - await fooInterface.export([fooOrigin]) + await fooOrigin.export([fooInterface]) } }) }) diff --git a/sdk/lib/types.ts b/sdk/lib/types.ts index de03e34b2..e0bc4a259 100644 --- a/sdk/lib/types.ts +++ b/sdk/lib/types.ts @@ -1,7 +1,7 @@ export * as configTypes from "./config/configTypes" import { InputSpec } from "./config/configTypes" import { DependenciesReceipt } from "./config/setupConfig" -import { HostKind, BindOptions } from "./interfaces/Host" +import { BindOptions } from "./interfaces/Host" import { Daemons } from "./mainFn/Daemons" import { UrlString } from "./util/getServiceInterface" import { ServiceInterfaceType, Signals } from "./util/utils" @@ -10,7 +10,7 @@ export type ExportedAction = (options: { effects: Effects input?: Record }) => Promise -export type MaybePromise = A | Promise +export type MaybePromise = Promise | A export namespace ExpectedExports { version: 1 /** Set configuration is called after we have modified and saved the configuration in the start9 ui. Use this to make a file for the docker to read from for configuration. */ @@ -164,19 +164,21 @@ export type ActionMetadata = { group?: string } export declare const hostName: unique symbol +// asdflkjadsf.onion | 1.2.3.4 export type Hostname = string & { [hostName]: never } /** ${scheme}://${username}@${host}:${externalPort}${suffix} */ export type AddressInfo = { username: string | null hostId: string - options: BindOptions + bindOptions: BindOptions suffix: string } export type HostnameInfoIp = { kind: "ip" networkInterfaceId: string + public: boolean hostname: | { kind: "ipv4" | "ipv6" | "local" @@ -201,11 +203,13 @@ export type HostnameInfoOnion = { export type HostnameInfo = HostnameInfoIp | HostnameInfoOnion export type SingleHost = { + id: string kind: "single" | "static" hostname: HostnameInfo | null } export type MultiHost = { + id: string kind: "multi" hostnames: HostnameInfo[] } @@ -224,11 +228,18 @@ export type ServiceInterface = { hasPrimary: boolean /** Disabled interfaces do not serve, but they retain their metadata and addresses */ disabled: boolean + /** Whether or not to mask the URIs for this interface. Useful if the URIs contain sensitive information, such as a password, macaroon, or API key */ + masked: boolean /** URI Information */ addressInfo: AddressInfo /** The network interface could be several types, something like ui, p2p, or network */ type: ServiceInterfaceType } + +export type ServiceInterfaceWithHostInfo = ServiceInterface & { + hostInfo: HostInfo +} + // prettier-ignore export type ExposeAllServicePaths = Store extends Record ? {[K in keyof Store & string]: ExposeAllServicePaths}[keyof Store & string] : @@ -278,18 +289,18 @@ export type Effects = { } & BindOptions, ): Promise /** Retrieves the current hostname(s) associated with a host id */ - getHostnames(options: { + getHostInfo(options: { kind: "static" | "single" - hostId: string + serviceInterfaceId: string packageId?: string callback: () => void - }): Promise<[] | [Hostname]> - getHostnames(options: { + }): Promise + getHostInfo(options: { kind?: "multi" + serviceInterfaceId: string packageId?: string - hostId: string callback: () => void - }): Promise + }): Promise // /** // * Run rsync between two volumes. This is used to backup data between volumes. @@ -329,13 +340,6 @@ export type Effects = { callback: (config: unknown, previousConfig: unknown) => void }): Promise - getLocalHostname(): Promise - getIPHostname(): Promise - /** Get the address for another service for tor interfaces */ - getServiceTorHostname( - serviceInterfaceId: ServiceInterfaceId, - packageId?: string, - ): Promise /** Get the IP address of the container */ getContainerIp(): Promise /** @@ -419,14 +423,16 @@ export type Effects = { * @returns PEM encoded fullchain (ecdsa) */ getSslCertificate: ( - packageId?: string, + packageId: string | null, + hostId: string, algorithm?: "ecdsa" | "ed25519", ) => Promise<[string, string, string]> /** * @returns PEM encoded ssl key (ecdsa) */ getSslKey: ( - packageId?: string, + packageId: string | null, + hostId: string, algorithm?: "ecdsa" | "ed25519", ) => Promise diff --git a/sdk/lib/util/getServiceInterface.ts b/sdk/lib/util/getServiceInterface.ts index 69083c66f..6f03e75e5 100644 --- a/sdk/lib/util/getServiceInterface.ts +++ b/sdk/lib/util/getServiceInterface.ts @@ -1,4 +1,11 @@ -import { AddressInfo, Effects, Hostname, ServiceInterface } from "../types" +import { + AddressInfo, + Effects, + HostInfo, + Hostname, + HostnameInfo, + ServiceInterface, +} from "../types" import * as regexes from "./regexes" import { ServiceInterfaceType } from "./utils" @@ -22,7 +29,6 @@ export type Filled = { ipv4Hostnames: Hostname[] ipv6Hostnames: Hostname[] nonIpHostnames: Hostname[] - allHostnames: Hostname[] urls: UrlString[] onionUrls: UrlString[] @@ -31,7 +37,6 @@ export type Filled = { ipv4Urls: UrlString[] ipv6Urls: UrlString[] nonIpUrls: UrlString[] - allUrls: UrlString[] } export type FilledAddressInfo = AddressInfo & Filled export type ServiceInterfaceFilled = { @@ -44,6 +49,10 @@ export type ServiceInterfaceFilled = { hasPrimary: boolean /** Whether or not the interface disabled */ disabled: boolean + /** Whether or not to mask the URIs for this interface. Useful if the URIs contain sensitive information, such as a password, macaroon, or API key */ + masked: boolean + /** Information about the host for this binding */ + hostInfo: HostInfo /** URI information */ addressInfo: FilledAddressInfo /** Indicates if we are a ui/p2p/api for the kind of interface that this is representing */ @@ -62,75 +71,110 @@ const negate = (a: A) => !fn(a) const unique = (values: A[]) => Array.from(new Set(values)) +function stringifyHostname(info: HostnameInfo): Hostname { + let base: string + if ("kind" in info.hostname && info.hostname.kind === "domain") { + base = info.hostname.subdomain + ? `${info.hostname.subdomain}.${info.hostname.domain}` + : info.hostname.domain + } else { + base = info.hostname.value + } + if (info.hostname.port && info.hostname.sslPort) { + return `${base}:${info.hostname.port}` as Hostname + } else if (info.hostname.sslPort) { + return `${base}:${info.hostname.sslPort}` as Hostname + } else if (info.hostname.port) { + return `${base}:${info.hostname.port}` as Hostname + } + return base as Hostname +} const addressHostToUrl = ( - { options, username, suffix }: AddressInfo, + { bindOptions, username, suffix }: AddressInfo, host: Hostname, ): UrlString => { const scheme = host.endsWith(".onion") - ? options.scheme - : options.addSsl - ? options.addSsl.scheme - : options.scheme // TODO: encode whether hostname transport is "secure"? + ? bindOptions.scheme + : bindOptions.addSsl + ? bindOptions.addSsl.scheme + : bindOptions.scheme // TODO: encode whether hostname transport is "secure"? return `${scheme ? `${scheme}//` : ""}${ username ? `${username}@` : "" }${host}${suffix}` } export const filledAddress = ( - hostnames: Hostname[], + hostInfo: HostInfo, addressInfo: AddressInfo, ): FilledAddressInfo => { const toUrl = addressHostToUrl.bind(null, addressInfo) + const hostnameInfo = + hostInfo.kind == "multi" + ? hostInfo.hostnames + : hostInfo.hostname + ? [hostInfo.hostname] + : [] return { ...addressInfo, - hostnames, + hostnames: hostnameInfo.flatMap((h) => stringifyHostname(h)), get onionHostnames() { - return hostnames.filter(regexes.torHostname.test) + return hostnameInfo + .filter((h) => h.kind === "onion") + .map((h) => stringifyHostname(h)) }, get localHostnames() { - return hostnames.filter(regexes.localHostname.test) + return hostnameInfo + .filter((h) => h.kind === "ip" && h.hostname.kind === "local") + .map((h) => stringifyHostname(h)) }, get ipHostnames() { - return hostnames.filter(either(regexes.ipv4.test, regexes.ipv6.test)) + return hostnameInfo + .filter( + (h) => + h.kind === "ip" && + (h.hostname.kind === "ipv4" || h.hostname.kind === "ipv6"), + ) + .map((h) => stringifyHostname(h)) }, get ipv4Hostnames() { - return hostnames.filter(regexes.ipv4.test) + return hostnameInfo + .filter((h) => h.kind === "ip" && h.hostname.kind === "ipv4") + .map((h) => stringifyHostname(h)) }, get ipv6Hostnames() { - return hostnames.filter(regexes.ipv6.test) + return hostnameInfo + .filter((h) => h.kind === "ip" && h.hostname.kind === "ipv6") + .map((h) => stringifyHostname(h)) }, get nonIpHostnames() { - return hostnames.filter( - negate(either(regexes.ipv4.test, regexes.ipv6.test)), - ) + return hostnameInfo + .filter( + (h) => + h.kind === "ip" && + h.hostname.kind !== "ipv4" && + h.hostname.kind !== "ipv6", + ) + .map((h) => stringifyHostname(h)) }, - allHostnames: hostnames, get urls() { - return hostnames.map(toUrl) + return this.hostnames.map(toUrl) }, get onionUrls() { - return hostnames.filter(regexes.torHostname.test).map(toUrl) + return this.onionHostnames.map(toUrl) }, get localUrls() { - return hostnames.filter(regexes.localHostname.test).map(toUrl) + return this.localHostnames.map(toUrl) }, get ipUrls() { - return hostnames - .filter(either(regexes.ipv4.test, regexes.ipv6.test)) - .map(toUrl) + return this.ipHostnames.map(toUrl) }, get ipv4Urls() { - return hostnames.filter(regexes.ipv4.test).map(toUrl) + return this.ipv4Hostnames.map(toUrl) }, get ipv6Urls() { - return hostnames.filter(regexes.ipv6.test).map(toUrl) + return this.ipv6Hostnames.map(toUrl) }, get nonIpUrls() { - return hostnames - .filter(negate(either(regexes.ipv4.test, regexes.ipv6.test))) - .map(toUrl) - }, - get allUrls() { - return hostnames.map(toUrl) + return this.nonIpHostnames.map(toUrl) }, } } @@ -151,9 +195,9 @@ const makeInterfaceFilled = async ({ packageId, callback, }) - const hostIdRecord = await effects.getHostnames({ + const hostInfo = await effects.getHostInfo({ packageId, - hostId: serviceInterfaceValue.addressInfo.hostId, + serviceInterfaceId: serviceInterfaceValue.id, callback, }) const primaryUrl = await effects.getPrimaryUrl({ @@ -165,7 +209,8 @@ const makeInterfaceFilled = async ({ const interfaceFilled: ServiceInterfaceFilled = { ...serviceInterfaceValue, primaryUrl: primaryUrl, - addressInfo: filledAddress(hostIdRecord, serviceInterfaceValue.addressInfo), + hostInfo, + addressInfo: filledAddress(hostInfo, serviceInterfaceValue.addressInfo), get primaryHostname() { if (primaryUrl == null) return null return getHostname(primaryUrl) diff --git a/sdk/lib/util/getServiceInterfaces.ts b/sdk/lib/util/getServiceInterfaces.ts index 9c45fd002..176918bb3 100644 --- a/sdk/lib/util/getServiceInterfaces.ts +++ b/sdk/lib/util/getServiceInterfaces.ts @@ -20,19 +20,13 @@ const makeManyInterfaceFilled = async ({ }) const hostIdsRecord = Object.fromEntries( await Promise.all( - Array.from( - new Set( - serviceInterfaceValues - .flatMap((x) => x.addressInfo) - .map((x) => x.hostId), - ), - ).map( - async (hostId) => + Array.from(new Set(serviceInterfaceValues.map((x) => x.id))).map( + async (id) => [ - hostId, - await effects.getHostnames({ + id, + await effects.getHostInfo({ packageId, - hostId, + serviceInterfaceId: id, callback, }), ] as const, @@ -42,9 +36,9 @@ const makeManyInterfaceFilled = async ({ const serviceInterfacesFilled: ServiceInterfaceFilled[] = await Promise.all( serviceInterfaceValues.map(async (serviceInterfaceValue) => { - const hostIdRecord = await effects.getHostnames({ + const hostInfo = await effects.getHostInfo({ packageId, - hostId: serviceInterfaceValue.addressInfo.hostId, + serviceInterfaceId: serviceInterfaceValue.id, callback, }) const primaryUrl = await effects.getPrimaryUrl({ @@ -55,10 +49,8 @@ const makeManyInterfaceFilled = async ({ return { ...serviceInterfaceValue, primaryUrl: primaryUrl, - addressInfo: filledAddress( - hostIdRecord, - serviceInterfaceValue.addressInfo, - ), + hostInfo, + addressInfo: filledAddress(hostInfo, serviceInterfaceValue.addressInfo), get primaryHostname() { if (primaryUrl == null) return null return getHostname(primaryUrl) diff --git a/sdk/lib/util/utils.ts b/sdk/lib/util/utils.ts index 8f28191e4..af6ae89cb 100644 --- a/sdk/lib/util/utils.ts +++ b/sdk/lib/util/utils.ts @@ -25,7 +25,7 @@ import { NamedPath, Path, } from "../dependency/setupDependencyMounts" -import { MultiHost, SingleHost, StaticHost } from "../interfaces/Host" +import { MultiHost, Scheme, SingleHost, StaticHost } from "../interfaces/Host" import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder" import { GetServiceInterface, getServiceInterface } from "./getServiceInterface" import { @@ -83,6 +83,8 @@ export type Utils< username: null | string path: string search: Record + schemeOverride: { ssl: Scheme; noSsl: Scheme } | null + masked: boolean }) => ServiceInterfaceBuilder getSystemSmtp: () => GetSystemSmtp & WrapperOverWrite host: { @@ -158,6 +160,8 @@ export const createUtils = < username: null | string path: string search: Record + schemeOverride: { ssl: Scheme; noSsl: Scheme } | null + masked: boolean }) => new ServiceInterfaceBuilder({ ...options, effects }), childProcess, getSystemSmtp: () =>