mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
* add support for inbound proxies * backend changes * fix file type * proxy -> tunnel, implement backend apis * wip start-tunneld * add domains and gateways, remove routers, fix docs links * dont show hidden actions * show and test dns * edit instead of chnage acme and change gateway * refactor: domains page * refactor: gateways page * domains and acme refactor * certificate authorities * refactor public/private gateways * fix fe types * domains mostly finished * refactor: add file control to form service * add ip util to sdk * domains api + migration * start service interface page, WIP * different options for clearnet domains * refactor: styles for interfaces page * minor * better placeholder for no addresses * start sorting addresses * best address logic * comments * fix unnecessary export * MVP of service interface page * domains preferred * fix: address comments * only translations left * wip: start-tunnel & fix build * forms for adding domain, rework things based on new ideas * fix: dns testing * public domain, max width, descriptions for dns * nix StartOS domains, implement public and private domains at interface scope * restart tor instead of reset * better icon for restart tor * dns * fix sort functions for public and private domains * with todos * update types * clean up tech debt, bump dependencies * revert to ts-rs v9 * fix all types * fix dns form * add missing translations * it builds * fix: comments (#3009) * fix: comments * undo default --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix: refactor legacy components (#3010) * fix: comments * fix: refactor legacy components * remove default again --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * more translations * wip * fix deadlock * coukd work * simple renaming * placeholder for empty service interfaces table * honor hidden form values * remove logs * reason instead of description * fix dns * misc fixes * implement toggling gateways for service interface * fix showing dns records * move status column in service list * remove unnecessary truthy check * refactor: refactor forms components and remove legacy Taiga UI package (#3012) * handle wh file uploads * wip: debugging tor * socks5 proxy working * refactor: fix multiple comments (#3013) * refactor: fix multiple comments * styling changes, add documentation to sidebar * translations for dns page * refactor: subtle colors * rearrange service page --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix file_stream and remove non-terminating test * clean up logs * support for sccache * fix gha sccache * more marketplace translations * install wizard clarity * stub hostnameInfo in migration * fix address info after setup, fix styling on SI page, new 040 release notes * remove tor logs from os * misc fixes * reset tor still not functioning... * update ts * minor styling and wording * chore: some fixes (#3015) * fix gateway renames * different handling for public domains * styling fixes * whole navbar should not be clickable on service show page * timeout getState request * remove links from changelog * misc fixes from pairing * use custom name for gateway in more places * fix dns parsing * closes #3003 * closes #2999 * chore: some fixes (#3017) * small copy change * revert hardcoded error for testing * dont require port forward if gateway is public * use old wan ip when not available * fix .const hanging on undefined * fix test * fix doc test * fix renames * update deps * allow specifying dependency metadata directly * temporarily make dependencies not cliackable in marketplace listings * fix socks bind * fix test --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: waterplea <alexander@inkin.ru>
324 lines
11 KiB
TypeScript
324 lines
11 KiB
TypeScript
import {
|
|
ExtendedVersion,
|
|
types as T,
|
|
utils,
|
|
VersionRange,
|
|
} from "@start9labs/start-sdk"
|
|
import * as net from "net"
|
|
import { object, string, number, literals, some, unknown } from "ts-matches"
|
|
import { Effects } from "../Models/Effects"
|
|
|
|
import { CallbackHolder } from "../Models/CallbackHolder"
|
|
import { asError } from "@start9labs/start-sdk/base/lib/util"
|
|
const matchRpcError = object({
|
|
error: object({
|
|
code: number,
|
|
message: string,
|
|
data: some(
|
|
string,
|
|
object({
|
|
details: string,
|
|
debug: string.nullable().optional(),
|
|
}),
|
|
)
|
|
.nullable()
|
|
.optional(),
|
|
}),
|
|
})
|
|
const testRpcError = matchRpcError.test
|
|
const testRpcResult = object({
|
|
result: unknown,
|
|
}).test
|
|
type RpcError = typeof matchRpcError._TYPE
|
|
|
|
const SOCKET_PATH = "/media/startos/rpc/host.sock"
|
|
let hostSystemId = 0
|
|
|
|
export type EffectContext = {
|
|
eventId: string | null
|
|
callbacks?: CallbackHolder
|
|
constRetry?: () => void
|
|
}
|
|
|
|
const rpcRoundFor =
|
|
(eventId: string | null) =>
|
|
<K extends T.EffectMethod | "clearCallbacks">(
|
|
method: K,
|
|
params: Record<string, unknown>,
|
|
) => {
|
|
const id = hostSystemId++
|
|
const client = net.createConnection({ path: SOCKET_PATH }, () => {
|
|
client.write(
|
|
JSON.stringify({
|
|
id,
|
|
method,
|
|
params: { ...params, eventId: eventId ?? undefined },
|
|
}) + "\n",
|
|
)
|
|
})
|
|
let bufs: Buffer[] = []
|
|
return new Promise((resolve, reject) => {
|
|
client.on("data", (data) => {
|
|
try {
|
|
bufs.push(data)
|
|
if (data.reduce((acc, x) => acc || x == 10, false)) {
|
|
const res: unknown = JSON.parse(
|
|
Buffer.concat(bufs).toString().split("\n")[0],
|
|
)
|
|
if (testRpcError(res)) {
|
|
let message = res.error.message
|
|
console.error(
|
|
"Error in host RPC:",
|
|
utils.asError({ method, params, error: res.error }),
|
|
)
|
|
if (string.test(res.error.data)) {
|
|
message += ": " + res.error.data
|
|
console.error(`Details: ${res.error.data}`)
|
|
} else {
|
|
if (res.error.data?.details) {
|
|
message += ": " + res.error.data.details
|
|
console.error(`Details: ${res.error.data.details}`)
|
|
}
|
|
if (res.error.data?.debug) {
|
|
message += "\n" + res.error.data.debug
|
|
console.error(`Debug: ${res.error.data.debug}`)
|
|
}
|
|
}
|
|
reject(new Error(`${message}@${method}`))
|
|
} else if (testRpcResult(res)) {
|
|
resolve(res.result)
|
|
} else {
|
|
reject(new Error(`malformed response ${JSON.stringify(res)}`))
|
|
}
|
|
}
|
|
} catch (error) {
|
|
reject(error)
|
|
}
|
|
client.end()
|
|
})
|
|
client.on("error", (error) => {
|
|
reject(error)
|
|
})
|
|
})
|
|
}
|
|
|
|
export function makeEffects(context: EffectContext): Effects {
|
|
const rpcRound = rpcRoundFor(context.eventId)
|
|
const self: Effects = {
|
|
eventId: context.eventId,
|
|
child: (name) =>
|
|
makeEffects({ ...context, callbacks: context.callbacks?.child(name) }),
|
|
constRetry: context.constRetry,
|
|
isInContext: !!context.callbacks,
|
|
onLeaveContext:
|
|
context.callbacks?.onLeaveContext?.bind(context.callbacks) ||
|
|
(() => {
|
|
console.warn(
|
|
"no context for this effects object",
|
|
new Error().stack?.replace(/^Error/, ""),
|
|
)
|
|
}),
|
|
clearCallbacks(...[options]: Parameters<T.Effects["clearCallbacks"]>) {
|
|
return rpcRound("clear-callbacks", {
|
|
...options,
|
|
}) as ReturnType<T.Effects["clearCallbacks"]>
|
|
},
|
|
action: {
|
|
clear(...[options]: Parameters<T.Effects["action"]["clear"]>) {
|
|
return rpcRound("action.clear", {
|
|
...options,
|
|
}) as ReturnType<T.Effects["action"]["clear"]>
|
|
},
|
|
export(...[options]: Parameters<T.Effects["action"]["export"]>) {
|
|
return rpcRound("action.export", {
|
|
...options,
|
|
}) as ReturnType<T.Effects["action"]["export"]>
|
|
},
|
|
getInput(...[options]: Parameters<T.Effects["action"]["getInput"]>) {
|
|
return rpcRound("action.get-input", {
|
|
...options,
|
|
}) as ReturnType<T.Effects["action"]["getInput"]>
|
|
},
|
|
createTask(...[options]: Parameters<T.Effects["action"]["createTask"]>) {
|
|
return rpcRound("action.create-task", {
|
|
...options,
|
|
}) as ReturnType<T.Effects["action"]["createTask"]>
|
|
},
|
|
run(...[options]: Parameters<T.Effects["action"]["run"]>) {
|
|
return rpcRound("action.run", {
|
|
...options,
|
|
}) as ReturnType<T.Effects["action"]["run"]>
|
|
},
|
|
clearTasks(...[options]: Parameters<T.Effects["action"]["clearTasks"]>) {
|
|
return rpcRound("action.clear-tasks", {
|
|
...options,
|
|
}) as ReturnType<T.Effects["action"]["clearTasks"]>
|
|
},
|
|
},
|
|
bind(...[options]: Parameters<T.Effects["bind"]>) {
|
|
return rpcRound("bind", {
|
|
...options,
|
|
stack: new Error().stack,
|
|
}) as ReturnType<T.Effects["bind"]>
|
|
},
|
|
clearBindings(...[options]: Parameters<T.Effects["clearBindings"]>) {
|
|
return rpcRound("clear-bindings", { ...options }) as ReturnType<
|
|
T.Effects["clearBindings"]
|
|
>
|
|
},
|
|
clearServiceInterfaces(
|
|
...[options]: Parameters<T.Effects["clearServiceInterfaces"]>
|
|
) {
|
|
return rpcRound("clear-service-interfaces", { ...options }) as ReturnType<
|
|
T.Effects["clearServiceInterfaces"]
|
|
>
|
|
},
|
|
getInstalledPackages(...[]: Parameters<T.Effects["getInstalledPackages"]>) {
|
|
return rpcRound("get-installed-packages", {}) as ReturnType<
|
|
T.Effects["getInstalledPackages"]
|
|
>
|
|
},
|
|
subcontainer: {
|
|
createFs(options: { imageId: string; name: string }) {
|
|
return rpcRound("subcontainer.create-fs", options) as ReturnType<
|
|
T.Effects["subcontainer"]["createFs"]
|
|
>
|
|
},
|
|
destroyFs(options: { guid: string }): Promise<null> {
|
|
return rpcRound("subcontainer.destroy-fs", options) as ReturnType<
|
|
T.Effects["subcontainer"]["destroyFs"]
|
|
>
|
|
},
|
|
},
|
|
exportServiceInterface: ((
|
|
...[options]: Parameters<Effects["exportServiceInterface"]>
|
|
) => {
|
|
return rpcRound("export-service-interface", options) as ReturnType<
|
|
T.Effects["exportServiceInterface"]
|
|
>
|
|
}) as Effects["exportServiceInterface"],
|
|
getContainerIp(...[options]: Parameters<T.Effects["getContainerIp"]>) {
|
|
return rpcRound("get-container-ip", options) as ReturnType<
|
|
T.Effects["getContainerIp"]
|
|
>
|
|
},
|
|
getOsIp(...[]: Parameters<T.Effects["getOsIp"]>) {
|
|
return rpcRound("get-os-ip", {}) as ReturnType<T.Effects["getOsIp"]>
|
|
},
|
|
getHostInfo: ((...[allOptions]: Parameters<T.Effects["getHostInfo"]>) => {
|
|
const options = {
|
|
...allOptions,
|
|
callback: context.callbacks?.addCallback(allOptions.callback) || null,
|
|
}
|
|
return rpcRound("get-host-info", options) as ReturnType<
|
|
T.Effects["getHostInfo"]
|
|
> as any
|
|
}) as Effects["getHostInfo"],
|
|
getServiceInterface(
|
|
...[options]: Parameters<T.Effects["getServiceInterface"]>
|
|
) {
|
|
return rpcRound("get-service-interface", {
|
|
...options,
|
|
callback: context.callbacks?.addCallback(options.callback) || null,
|
|
}) as ReturnType<T.Effects["getServiceInterface"]>
|
|
},
|
|
|
|
getServicePortForward(
|
|
...[options]: Parameters<T.Effects["getServicePortForward"]>
|
|
) {
|
|
return rpcRound("get-service-port-forward", options) as ReturnType<
|
|
T.Effects["getServicePortForward"]
|
|
>
|
|
},
|
|
getSslCertificate(options: Parameters<T.Effects["getSslCertificate"]>[0]) {
|
|
return rpcRound("get-ssl-certificate", options) as ReturnType<
|
|
T.Effects["getSslCertificate"]
|
|
>
|
|
},
|
|
getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) {
|
|
return rpcRound("get-ssl-key", options) as ReturnType<
|
|
T.Effects["getSslKey"]
|
|
>
|
|
},
|
|
getSystemSmtp(...[options]: Parameters<T.Effects["getSystemSmtp"]>) {
|
|
return rpcRound("get-system-smtp", {
|
|
...options,
|
|
callback: context.callbacks?.addCallback(options.callback) || null,
|
|
}) as ReturnType<T.Effects["getSystemSmtp"]>
|
|
},
|
|
listServiceInterfaces(
|
|
...[options]: Parameters<T.Effects["listServiceInterfaces"]>
|
|
) {
|
|
return rpcRound("list-service-interfaces", {
|
|
...options,
|
|
callback: context.callbacks?.addCallback(options.callback) || null,
|
|
}) as ReturnType<T.Effects["listServiceInterfaces"]>
|
|
},
|
|
mount(...[options]: Parameters<T.Effects["mount"]>) {
|
|
return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
|
|
},
|
|
restart(...[]: Parameters<T.Effects["restart"]>) {
|
|
console.log("Restarting service...")
|
|
return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
|
|
},
|
|
setDependencies(
|
|
dependencies: Parameters<T.Effects["setDependencies"]>[0],
|
|
): ReturnType<T.Effects["setDependencies"]> {
|
|
return rpcRound("set-dependencies", dependencies) as ReturnType<
|
|
T.Effects["setDependencies"]
|
|
>
|
|
},
|
|
checkDependencies(
|
|
options: Parameters<T.Effects["checkDependencies"]>[0],
|
|
): ReturnType<T.Effects["checkDependencies"]> {
|
|
return rpcRound("check-dependencies", options) as ReturnType<
|
|
T.Effects["checkDependencies"]
|
|
>
|
|
},
|
|
getDependencies(): ReturnType<T.Effects["getDependencies"]> {
|
|
return rpcRound("get-dependencies", {}) as ReturnType<
|
|
T.Effects["getDependencies"]
|
|
>
|
|
},
|
|
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
|
|
return rpcRound("set-health", options) as ReturnType<
|
|
T.Effects["setHealth"]
|
|
>
|
|
},
|
|
|
|
getStatus(...[o]: Parameters<T.Effects["getStatus"]>) {
|
|
return rpcRound("get-status", o) as ReturnType<T.Effects["getStatus"]>
|
|
},
|
|
setMainStatus(o: { status: "running" | "stopped" }): Promise<null> {
|
|
return rpcRound("set-main-status", o) as ReturnType<
|
|
T.Effects["setHealth"]
|
|
>
|
|
},
|
|
|
|
shutdown(...[]: Parameters<T.Effects["shutdown"]>) {
|
|
return rpcRound("shutdown", {}) as ReturnType<T.Effects["shutdown"]>
|
|
},
|
|
getDataVersion() {
|
|
return rpcRound("get-data-version", {}) as ReturnType<
|
|
T.Effects["getDataVersion"]
|
|
>
|
|
},
|
|
setDataVersion(...[options]: Parameters<T.Effects["setDataVersion"]>) {
|
|
return rpcRound("set-data-version", options) as ReturnType<
|
|
T.Effects["setDataVersion"]
|
|
>
|
|
},
|
|
}
|
|
if (context.callbacks?.onLeaveContext)
|
|
self.onLeaveContext(() => {
|
|
self.isInContext = false
|
|
self.onLeaveContext = () => {
|
|
console.warn(
|
|
"this effects object is already out of context",
|
|
new Error().stack?.replace(/^Error/, ""),
|
|
)
|
|
}
|
|
})
|
|
return self
|
|
}
|