diff --git a/lib/health/HealthReceipt.ts b/lib/health/HealthReceipt.ts new file mode 100644 index 0000000..3f0d1e0 --- /dev/null +++ b/lib/health/HealthReceipt.ts @@ -0,0 +1,4 @@ +declare const HealthProof: unique symbol; +export type HealthReceipt = { + [HealthProof]: never; +}; diff --git a/lib/health/ReadyProof.ts b/lib/health/ReadyProof.ts new file mode 100644 index 0000000..b88b064 --- /dev/null +++ b/lib/health/ReadyProof.ts @@ -0,0 +1,4 @@ +export declare const ReadyProof: unique symbol; +export type ReadyReceipt = { + [ReadyProof]: never; +}; diff --git a/lib/health/checkFns/CheckResult.ts b/lib/health/checkFns/CheckResult.ts new file mode 100644 index 0000000..2d7bbda --- /dev/null +++ b/lib/health/checkFns/CheckResult.ts @@ -0,0 +1,6 @@ +import { HealthStatus } from "../../types"; + +export type CheckResult = { + status: HealthStatus; + message?: string; +}; diff --git a/lib/health/checkFns/checkPortListening.ts b/lib/health/checkFns/checkPortListening.ts new file mode 100644 index 0000000..2cd28f6 --- /dev/null +++ b/lib/health/checkFns/checkPortListening.ts @@ -0,0 +1,26 @@ +import { Effects } from "../../types"; +export function containsAddress(x: string, port: number) { + const readPorts = x + .split("\n") + .filter(Boolean) + .splice(1) + .map((x) => x.split(" ").filter(Boolean)[1]?.split(":")?.[1]) + .filter(Boolean) + .map((x) => Number.parseInt(x, 16)) + .filter(Number.isFinite); + return readPorts.indexOf(port) >= 0; +} + +/** + * This is used to check if a port is listening on the system. + * Used during the health check fn or the check main fn. + */ +export async function checkPortListening(effects: Effects, port: number) { + const hasAddress = + containsAddress(await effects.shell("cat /proc/net/tcp"), port) || + containsAddress(await effects.shell("cat /proc/net/udp"), port); + if (hasAddress) { + return; + } + throw new Error(`Port ${port} is not listening`); +} diff --git a/lib/health/checkFns/checkWebUrl.ts b/lib/health/checkFns/checkWebUrl.ts new file mode 100644 index 0000000..57cd357 --- /dev/null +++ b/lib/health/checkFns/checkWebUrl.ts @@ -0,0 +1,31 @@ +import { Effects } from "../../types"; +import { CheckResult } from "./CheckResult"; +import { timeoutPromise } from "./index"; + +/** + * This is a helper function to check if a web url is reachable. + * @param url + * @param createSuccess + * @returns + */ +export const checkWebUrl = async ( + url: string, + effects: Effects, + { + timeout = 1000, + successMessage = `Reached ${url}`, + errorMessage = `Error while fetching URL: ${url}`, + } = {} +): Promise => { + return Promise.race([effects.fetch(url), timeoutPromise(timeout)]) + .then((x) => ({ + status: "passing" as const, + message: successMessage, + })) + .catch((e) => { + effects.warn(`Error while fetching URL: ${url}`); + effects.error(JSON.stringify(e)); + effects.error(e.toString()); + return { status: "failing" as const, message: errorMessage }; + }); +}; diff --git a/lib/health/checkFns/index.ts b/lib/health/checkFns/index.ts new file mode 100644 index 0000000..b4fb0bb --- /dev/null +++ b/lib/health/checkFns/index.ts @@ -0,0 +1,11 @@ +import { runHealthScript } from "./runHealthScript"; +export { checkPortListening } from "./checkPortListening"; +export { CheckResult } from "./CheckResult"; +export { checkWebUrl } from "./checkWebUrl"; + +export function timeoutPromise(ms: number, { message = "Timed out" } = {}) { + return new Promise((resolve, reject) => + setTimeout(() => reject(new Error(message)), ms) + ); +} +export { runHealthScript }; diff --git a/lib/health/checkFns/runHealthScript.ts b/lib/health/checkFns/runHealthScript.ts new file mode 100644 index 0000000..0d75b3a --- /dev/null +++ b/lib/health/checkFns/runHealthScript.ts @@ -0,0 +1,35 @@ +import { Effects } from "../../types"; +import { CheckResult } from "./CheckResult"; +import { timeoutPromise } from "./index"; + +/** + * Running a health script, is used when we want to have a simple + * script in bash or something like that. It should return something that is useful + * in {result: string} else it is considered an error + * @param param0 + * @returns + */ +export const runHealthScript = async ( + effects: Effects, + runCommand: Parameters[0], + { + timeout = 30000, + errorMessage = `Error while running command: ${runCommand}`, + message = (res: string) => + `Have ran script ${runCommand} and the result: ${res}`, + } +): Promise => { + const res = await Promise.race([ + effects.runCommand(runCommand), + timeoutPromise(timeout), + ]).catch((e) => { + effects.warn(errorMessage); + effects.warn(JSON.stringify(e)); + effects.warn(e.toString()); + throw { status: "failing", message: errorMessage } as CheckResult; + }); + return { + status: "passing", + message: message(res), + } as CheckResult; +}; diff --git a/lib/health/healthRunner.ts b/lib/health/healthRunner.ts deleted file mode 100644 index c02449c..0000000 --- a/lib/health/healthRunner.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Types } from ".."; -import { object, string } from "ts-matches"; - -export type HealthCheck = ( - effects: Types.Effects, - dateMs: number -) => Promise; -export type HealthResult = - | { success: string } - | { failure: string } - | { disabled: null } - | { starting: null } - | { loading: string }; -const hasMessage = object({ message: string }).test; -function safelyStringify(e: unknown) { - if (hasMessage(e)) return e.message; - if (string.test(e)) return e; - try { - return JSON.stringify(e); - } catch (e) { - return "unknown"; - } -} -async function timeoutHealth( - effects: Types.Effects, - timeMs: number -): Promise { - await effects.sleep(timeMs); - return { failure: "Timed out " }; -} - -/** - * Health runner is usually used during the main function, and will be running in a loop. - * This health check then will be run every intervalS seconds. - * The return needs a create() - * then from there we need a start(). - * The stop function is used to stop the health check. - */ - -export default function healthRunner(name: string, fn: HealthCheck) { - return { - /** - * All values in seconds. Defaults): - * - * interval: 60s - * - * timeout: 10s - * - * delay: 10s - */ - create( - effects: Types.Effects, - options = { interval: 60, timeout: 10, delay: 10 } - ) { - let running: any; - function startFn() { - clearInterval(running); - setTimeout(() => { - running = setInterval(async () => { - const result = await Promise.race([ - timeoutHealth(effects, options.timeout * 1000), - fn(effects, 123), - ]).catch((e) => { - return { failure: safelyStringify(e) }; - }); - (effects as any).setHealthStatus({ - name, - result, - }); - }, options.interval * 1000); - }, options.delay * 1000); - } - - const self = { - stop() { - clearInterval(running); - return self; - }, - start() { - startFn(); - return self; - }, - }; - return self; - }, - }; -} diff --git a/lib/health/index.ts b/lib/health/index.ts index eb9ce19..37eef96 100644 --- a/lib/health/index.ts +++ b/lib/health/index.ts @@ -1,2 +1,7 @@ -export * from "./util"; -export { default as healthRunner } from "./healthRunner"; +export * as checkFns from "./checkFns"; +export * as trigger from "./trigger"; + +export { TriggerInput } from "./trigger/TriggerInput"; +export { HealthReceipt } from "./HealthReceipt"; +export { readyCheck } from "./readyCheck"; +export { ReadyProof } from "./ReadyProof"; diff --git a/lib/health/readyCheck.ts b/lib/health/readyCheck.ts new file mode 100644 index 0000000..ae5369b --- /dev/null +++ b/lib/health/readyCheck.ts @@ -0,0 +1,55 @@ +import { InterfaceReceipt } from "../mainFn/interfaceReceipt"; +import { Daemon, Effects } from "../types"; +import { CheckResult } from "./checkFns/CheckResult"; +import { ReadyReceipt } from "./ReadyProof"; +import { HealthReceipt } from "./HealthReceipt"; +import { Trigger } from "./trigger"; +import { TriggerInput } from "./trigger/TriggerInput"; +import { defaultTrigger } from "./trigger/defaultTrigger"; + +function readReciptOf(a: A) { + return a as A & ReadyReceipt; +} +export function readyCheck(o: { + effects: Effects; + started(onTerm: () => void): null; + interfaceReceipt: InterfaceReceipt; + healthReceipts: Iterable; + daemonReceipt: Daemon; + name: string; + trigger?: Trigger; + fn(): Promise | CheckResult; + onFirstSuccess?: () => () => Promise | unknown; +}) { + new Promise(async () => { + const trigger = (o.trigger ?? defaultTrigger)(); + let currentValue: TriggerInput = { + lastResult: null, + hadSuccess: false, + }; + for ( + let res = await trigger.next(currentValue); + !res.done; + res = await trigger.next(currentValue) + ) { + try { + const { status, message } = await o.fn(); + if (!currentValue.hadSuccess) { + await o.started(o?.onFirstSuccess ?? (() => o.daemonReceipt.term())); + } + await o.effects.setHealth({ + name: o.name, + status, + message, + }); + currentValue.hadSuccess = true; + currentValue.lastResult = "success"; + } catch (_) { + currentValue.lastResult = "failure"; + } + } + }); + return readReciptOf({ + daemon: o.daemonReceipt, + }); +} diff --git a/lib/health/trigger/TriggerInput.ts b/lib/health/trigger/TriggerInput.ts new file mode 100644 index 0000000..8f5779f --- /dev/null +++ b/lib/health/trigger/TriggerInput.ts @@ -0,0 +1,4 @@ +export type TriggerInput = { + lastResult: "success" | "failure" | null; + hadSuccess: boolean; +}; diff --git a/lib/health/trigger/changeOnFirstSuccess.ts b/lib/health/trigger/changeOnFirstSuccess.ts new file mode 100644 index 0000000..b8ec1a7 --- /dev/null +++ b/lib/health/trigger/changeOnFirstSuccess.ts @@ -0,0 +1,28 @@ +import { TriggerInput } from "./TriggerInput"; +import { Trigger } from "./index"; + +export function changeOnFirstSuccess(o: { + beforeFirstSuccess: Trigger; + afterFirstSuccess: Trigger; +}) { + return async function* () { + const beforeFirstSuccess = o.beforeFirstSuccess(); + let currentValue: TriggerInput = yield; + beforeFirstSuccess.next(currentValue); + for ( + let res = await beforeFirstSuccess.next(currentValue); + currentValue?.lastResult !== "success" && !res.done; + res = await beforeFirstSuccess.next(currentValue) + ) { + currentValue = yield; + } + const afterFirstSuccess = o.afterFirstSuccess(); + for ( + let res = await afterFirstSuccess.next(currentValue); + !res.done; + res = await afterFirstSuccess.next(currentValue) + ) { + currentValue = yield; + } + }; +} diff --git a/lib/health/trigger/cooldownTrigger.ts b/lib/health/trigger/cooldownTrigger.ts new file mode 100644 index 0000000..27454e0 --- /dev/null +++ b/lib/health/trigger/cooldownTrigger.ts @@ -0,0 +1,8 @@ +export function cooldownTrigger(timeMs: number) { + return async function* () { + while (true) { + await new Promise((resolve) => setTimeout(resolve, timeMs)); + yield; + } + }; +} diff --git a/lib/health/trigger/defaultTrigger.ts b/lib/health/trigger/defaultTrigger.ts new file mode 100644 index 0000000..74cdf86 --- /dev/null +++ b/lib/health/trigger/defaultTrigger.ts @@ -0,0 +1,7 @@ +import { cooldownTrigger } from "./cooldownTrigger"; +import { changeOnFirstSuccess } from "./changeOnFirstSuccess"; + +export const defaultTrigger = changeOnFirstSuccess({ + beforeFirstSuccess: cooldownTrigger(0), + afterFirstSuccess: cooldownTrigger(30000), +}); diff --git a/lib/health/trigger/index.ts b/lib/health/trigger/index.ts new file mode 100644 index 0000000..6d4ec88 --- /dev/null +++ b/lib/health/trigger/index.ts @@ -0,0 +1,5 @@ +import { TriggerInput } from "./TriggerInput"; +export { changeOnFirstSuccess } from "./changeOnFirstSuccess"; +export { cooldownTrigger } from "./cooldownTrigger"; + +export type Trigger = () => AsyncIterator; diff --git a/lib/health/util.ts b/lib/health/util.ts deleted file mode 100644 index e8a5d1c..0000000 --- a/lib/health/util.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Effects } from "../types"; -import { isKnownError } from "../util"; -import { HealthResult } from "./healthRunner"; - -/** - * This is a helper function to check if a web url is reachable. - * @param url - * @param createSuccess - * @returns - */ -export const checkWebUrl: ( - url: string, - createSuccess?: null | ((response?: string | null) => string) -) => (effects: Effects, duration: number) => Promise = ( - url, - createSuccess = null -) => { - return async (effects, duration) => { - const errorValue = guardDurationAboveMinimum({ - duration, - minimumTime: 5000, - }); - if (!!errorValue) { - return errorValue; - } - - return await effects - .fetch(url) - .then((x) => ({ - success: createSuccess?.(x.body) ?? `Successfully fetched URL: ${url}`, - })) - .catch((e) => { - effects.warn(`Error while fetching URL: ${url}`); - effects.error(JSON.stringify(e)); - effects.error(e.toString()); - return { failure: `Error while fetching URL: ${url}` }; - }); - }; -}; - -/** - * Running a health script, is used when we want to have a simple - * script in bash or something like that. It should return something that is useful - * in {result: string} else it is considered an error - * @param param0 - * @returns - */ -export const runHealthScript = - ({ - command, - args, - message, - }: { - command: string; - args: string[]; - message: ((result: unknown) => string) | null; - }) => - async (effects: Effects, _duration: number): Promise => { - const res = await effects.runCommand({ command, args }); - return { - success: - message?.(res) ?? `Have ran script ${command} and the result: ${res}`, - }; - }; - -// Ensure the starting duration is pass a minimum -export const guardDurationAboveMinimum = (input: { - duration: number; - minimumTime: number; -}): null | HealthResult => - input.duration <= input.minimumTime ? { starting: null } : null; - -export const catchError = (effects: Effects) => (e: unknown) => { - if (isKnownError(e)) return e; - effects.error(`Health check failed: ${e}`); - return { failure: "Error while running health check" }; -}; diff --git a/lib/index.ts b/lib/index.ts index ca3156e..65df7f3 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,12 +1,13 @@ -export * as matches from "ts-matches"; -export * as TOML from "@iarna/toml"; -export * as YAML from "yaml"; -export * as Types from "./types"; -export * as T from "./types"; -export * as healthUtil from "./health/util"; -export * as util from "./util"; -export * as configBuilder from "./config/builder"; export * as backup from "./backup"; -export * as configTypes from "./config/configTypes"; export * as config from "./config"; +export * as configBuilder from "./config/builder"; +export * as configTypes from "./config/configTypes"; export * as health from "./health"; +export * as healthUtil from "./health/checkFns"; +export * as mainFn from "./mainFn"; +export * as matches from "ts-matches"; +export * as T from "./types"; +export * as TOML from "@iarna/toml"; +export * as Types from "./types"; +export * as util from "./util"; +export * as YAML from "yaml"; diff --git a/lib/mainFn/AddressReceipt.ts b/lib/mainFn/AddressReceipt.ts new file mode 100644 index 0000000..11aef2f --- /dev/null +++ b/lib/mainFn/AddressReceipt.ts @@ -0,0 +1,4 @@ +declare const AddressProof: unique symbol; +export type AddressReceipt = { + [AddressProof]: never; +}; diff --git a/lib/mainFn/LocalBinding.ts b/lib/mainFn/LocalBinding.ts new file mode 100644 index 0000000..c4f45c8 --- /dev/null +++ b/lib/mainFn/LocalBinding.ts @@ -0,0 +1,11 @@ +import { Origin } from "./Origin"; + +export class LocalBinding { + constructor(readonly localAddress: string, readonly ipAddress: string) {} + createOrigins(protocol: string) { + return { + local: new Origin(protocol, this.localAddress), + ip: new Origin(protocol, this.ipAddress), + }; + } +} diff --git a/lib/mainFn/LocalPort.ts b/lib/mainFn/LocalPort.ts new file mode 100644 index 0000000..0ae4da1 --- /dev/null +++ b/lib/mainFn/LocalPort.ts @@ -0,0 +1,13 @@ +import { Effects } from "../types"; +import { LocalBinding } from "./LocalBinding"; + +export class LocalPort { + constructor(readonly id: string, readonly effects: Effects) {} + async bindLan(internalPort: number) { + const [localAddress, ipAddress] = await this.effects.bindLan({ + internalPort, + name: this.id, + }); + return new LocalBinding(localAddress, ipAddress); + } +} diff --git a/lib/mainFn/NetworkBuilder.ts b/lib/mainFn/NetworkBuilder.ts new file mode 100644 index 0000000..6e052d0 --- /dev/null +++ b/lib/mainFn/NetworkBuilder.ts @@ -0,0 +1,17 @@ +import { Effects } from "../types"; +import { LocalPort } from "./LocalPort"; +import { TorHostname } from "./TorHostname"; + +export class NetworkBuilder { + static of(effects: Effects) { + return new NetworkBuilder(effects); + } + private constructor(private effects: Effects) {} + + getTorHostName(id: string) { + return new TorHostname(id, this.effects); + } + getPort(id: string) { + return new LocalPort(id, this.effects); + } +} diff --git a/lib/mainFn/NetworkInterfaceBuilder.ts b/lib/mainFn/NetworkInterfaceBuilder.ts new file mode 100644 index 0000000..8ebcc6c --- /dev/null +++ b/lib/mainFn/NetworkInterfaceBuilder.ts @@ -0,0 +1,38 @@ +import { Effects } from "../types"; +import { AddressReceipt } from "./AddressReceipt"; +import { Origin } from "./Origin"; + +export class NetworkInterfaceBuilder { + constructor( + readonly options: { + effects: Effects; + name: string; + id: string; + description: string; + ui: boolean; + basic?: null | { password: string; username: string }; + path?: string; + search?: Record; + } + ) {} + + async exportAddresses(addresses: Iterable) { + const { name, description, id, ui, path, search } = this.options; + // prettier-ignore + const urlAuth = !!(this.options?.basic) ? `${this.options.basic.username}:${this.options.basic.password}@` : + ''; + for (const origin of addresses) { + const address = `${origin.protocol}://${urlAuth}${origin.address}`; + await this.options.effects.exportAddress({ + name, + description, + address, + id, + ui, + path, + search, + }); + } + return {} as AddressReceipt; + } +} diff --git a/lib/mainFn/Origin.ts b/lib/mainFn/Origin.ts new file mode 100644 index 0000000..5eb09f5 --- /dev/null +++ b/lib/mainFn/Origin.ts @@ -0,0 +1,3 @@ +export class Origin { + constructor(readonly protocol: string, readonly address: string) {} +} diff --git a/lib/mainFn/RunningMainRet.ts b/lib/mainFn/RunningMainRet.ts new file mode 100644 index 0000000..d8d9d22 --- /dev/null +++ b/lib/mainFn/RunningMainRet.ts @@ -0,0 +1,7 @@ +import { Daemon } from "../types"; +import { ReadyProof } from "../health/ReadyProof"; + +export type RunningMainRet = { + [ReadyProof]: never; + daemon: Daemon; +}; diff --git a/lib/mainFn/TorBinding.ts b/lib/mainFn/TorBinding.ts new file mode 100644 index 0000000..85dc11a --- /dev/null +++ b/lib/mainFn/TorBinding.ts @@ -0,0 +1,8 @@ +import { Origin } from "./Origin"; + +export class TorBinding { + constructor(readonly address: string) {} + createOrigin(protocol: string) { + return new Origin(protocol, this.address); + } +} diff --git a/lib/mainFn/TorHostname.ts b/lib/mainFn/TorHostname.ts new file mode 100644 index 0000000..0652659 --- /dev/null +++ b/lib/mainFn/TorHostname.ts @@ -0,0 +1,14 @@ +import { Effects } from "../types"; +import { TorBinding } from "./TorBinding"; + +export class TorHostname { + constructor(readonly id: string, readonly effects: Effects) {} + async bindTor(internalPort: number, externalPort: number) { + const address = await this.effects.bindTor({ + internalPort, + name: this.id, + externalPort, + }); + return new TorBinding(address); + } +} diff --git a/lib/mainFn/exportInterfaces.ts b/lib/mainFn/exportInterfaces.ts new file mode 100644 index 0000000..19ee867 --- /dev/null +++ b/lib/mainFn/exportInterfaces.ts @@ -0,0 +1,8 @@ +import { AddressReceipt } from "./AddressReceipt"; +import { InterfaceReceipt } from "./interfaceReceipt"; + +export const exportInterfaces = ( + _firstProof: AddressReceipt, + ..._rest: AddressReceipt[] +) => ({} as InterfaceReceipt); +export default exportInterfaces; diff --git a/lib/mainFn/index.ts b/lib/mainFn/index.ts new file mode 100644 index 0000000..80587fc --- /dev/null +++ b/lib/mainFn/index.ts @@ -0,0 +1,22 @@ +import { RunningMainRet } from "./RunningMainRet"; +import { Effects, ExpectedExports } from "../types"; +export * as network from "./exportInterfaces"; +export { LocalBinding } from "./LocalBinding"; +export { LocalPort } from "./LocalPort"; +export { NetworkBuilder } from "./NetworkBuilder"; +export { NetworkInterfaceBuilder } from "./NetworkInterfaceBuilder"; +export { Origin } from "./Origin"; +export { TorBinding } from "./TorBinding"; +export { TorHostname } from "./TorHostname"; + +export const runningMain: ( + fn: (o: { + effects: Effects; + started(onTerm: () => void): null; + }) => Promise +) => ExpectedExports.main = (fn) => { + return async (options) => { + const { daemon } = await fn(options); + daemon.wait(); + }; +}; diff --git a/lib/mainFn/interfaceReceipt.ts b/lib/mainFn/interfaceReceipt.ts new file mode 100644 index 0000000..ac25d00 --- /dev/null +++ b/lib/mainFn/interfaceReceipt.ts @@ -0,0 +1,4 @@ +declare const InterfaceProof: unique symbol; +export type InterfaceReceipt = { + [InterfaceProof]: never; +}; diff --git a/lib/test/health.readyCheck.test.ts b/lib/test/health.readyCheck.test.ts new file mode 100644 index 0000000..ef5b9b6 --- /dev/null +++ b/lib/test/health.readyCheck.test.ts @@ -0,0 +1,17 @@ +import { containsAddress } from "../health/checkFns/checkPortListening"; + +describe("Health ready check", () => { + it("Should be able to parse an example information", () => { + let input = ` + + sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + 0: 00000000:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634478 1 0000000000000000 100 0 0 10 0 + 1: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634477 1 0000000000000000 100 0 0 10 0 + 2: 0B00007F:9671 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21635458 1 0000000000000000 100 0 0 10 0 + 3: 00000000:0D73 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634479 1 0000000000000000 100 0 0 10 0 + `; + + expect(containsAddress(input, 80)).toBe(true); + expect(containsAddress(input, 1234)).toBe(false); + }); +}); diff --git a/lib/types.ts b/lib/types.ts index 00f7486..a13eeb9 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -85,6 +85,19 @@ export type ConfigRes = { /** Shape that is describing the form in the ui */ spec: InputSpec; }; + +declare const DaemonProof: unique symbol; +export type DaemonReceipt = { + [DaemonProof]: never; +}; +export type Daemon = { + wait(): Promise; + term(): Promise; + [DaemonProof]: never; +}; + +export type HealthStatus = "passing" | "warning" | "failing" | "disabled"; + /** Used to reach out from the pure js runtime */ export type Effects = { /** Usable when not sandboxed */ @@ -125,6 +138,10 @@ export type Effects = { args?: string[]; timeoutMillis?: number; }): Promise; + runShellDaemon(command: string): { + wait(): Promise; + term(): Promise; + }; runDaemon(input: { command: string; args?: string[] }): { wait(): Promise; term(): Promise; @@ -154,11 +171,7 @@ export type Effects = { /** Check that a file exists or not */ exists(input: { volumeId: string; path: string }): Promise; /** Declaring that we are opening a interface on some protocal for local network */ - bindLocal(options: { - internalPort: number; - name: string; - externalPort: number; - }): Promise; + bindLan(options: { internalPort: number; name: string }): Promise; /** Declaring that we are opening a interface on some protocal for tor network */ bindTor(options: { internalPort: number; @@ -244,6 +257,20 @@ export type Effects = { * ui interface */ ui?: boolean; + + /** + * The id is that a path will create a link in the ui that can go to specific pages, like + * admin, or settings, or something like that. + * Default = '' + */ + path?: string; + + /** + * This is the query params in the url, and is a map of key value pairs + * Default = {} + * if empty then will not be added to the url + */ + search?: Record; }): Promise; /** @@ -291,6 +318,12 @@ export type Effects = { * @returns PEM encoded ssl key (ecdsa) */ getSslKey: (packageId: string, algorithm?: "ecdsa" | "ed25519") => string; + + setHealth(o: { + name: string; + status: HealthStatus; + message?: string; + }): Promise; }; /* rsync options: https://linux.die.net/man/1/rsync diff --git a/lib/util/index.ts b/lib/util/index.ts index 7ecfe55..c09b0a9 100644 --- a/lib/util/index.ts +++ b/lib/util/index.ts @@ -3,6 +3,7 @@ import * as T from "../types"; export { guardAll, typeFromProps } from "./propertiesMatcher"; export { default as nullIfEmpty } from "./nullIfEmpty"; export { FileHelper } from "./fileHelper"; +export { sh } from "./shell"; /** Used to check if the file exists before hand */ export const exists = ( diff --git a/lib/util/shell.ts b/lib/util/shell.ts new file mode 100644 index 0000000..851da14 --- /dev/null +++ b/lib/util/shell.ts @@ -0,0 +1,8 @@ +import { Effects } from "../types"; + +export function sh(shellCommand: string) { + return { + command: "sh", + args: ["-c", shellCommand], + } as Partial[0]>; +} diff --git a/package-lock.json b/package-lock.json index f5afe80..3b3d6e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.charlie2", + "version": "0.4.0-lib0.charlie3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "start-sdk", - "version": "0.4.0-lib0.charlie2", + "version": "0.4.0-lib0.charlie3", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index 0f84ec6..f48e46a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.charlie2", + "version": "0.4.0-lib0.charlie3", "description": "For making the patterns that are wanted in making services for the startOS.", "main": "./lib/index.js", "types": "./lib/index.d.ts",