mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-30 12:21:57 +00:00
Merge branch 'master' of github.com:Start9Labs/start-sdk
This commit is contained in:
@@ -4,6 +4,7 @@ import * as D from "./configDependencies"
|
|||||||
import { Config, ExtractConfigType } from "./builder/config"
|
import { Config, ExtractConfigType } from "./builder/config"
|
||||||
import { Utils, utils } from "../util/utils"
|
import { Utils, utils } from "../util/utils"
|
||||||
import nullIfEmpty from "../util/nullIfEmpty"
|
import nullIfEmpty from "../util/nullIfEmpty"
|
||||||
|
import { InterfaceReceipt } from "../mainFn/interfaceReceipt"
|
||||||
|
|
||||||
declare const dependencyProof: unique symbol
|
declare const dependencyProof: unique symbol
|
||||||
export type DependenciesReceipt = void & {
|
export type DependenciesReceipt = void & {
|
||||||
@@ -25,6 +26,7 @@ export type Save<
|
|||||||
dependencies: D.ConfigDependencies<Manifest>
|
dependencies: D.ConfigDependencies<Manifest>
|
||||||
}) => Promise<{
|
}) => Promise<{
|
||||||
dependenciesReceipt: DependenciesReceipt
|
dependenciesReceipt: DependenciesReceipt
|
||||||
|
interfaceReceipt: InterfaceReceipt
|
||||||
restart: boolean
|
restart: boolean
|
||||||
}>
|
}>
|
||||||
export type Read<
|
export type Read<
|
||||||
@@ -66,6 +68,8 @@ export function setupConfig<
|
|||||||
await console.error(String(validator.errorMessage(input)))
|
await console.error(String(validator.errorMessage(input)))
|
||||||
return { error: "Set config type error for config" }
|
return { error: "Set config type error for config" }
|
||||||
}
|
}
|
||||||
|
await effects.clearBindings()
|
||||||
|
await effects.clearNetworkInterfaces()
|
||||||
const { restart } = await write({
|
const { restart } = await write({
|
||||||
input: JSON.parse(JSON.stringify(input)),
|
input: JSON.parse(JSON.stringify(input)),
|
||||||
effects,
|
effects,
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ export class Daemons<Ids extends string> {
|
|||||||
static of(config: {
|
static of(config: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
started: (onTerm: () => void) => null
|
started: (onTerm: () => void) => null
|
||||||
interfaceReceipt: InterfaceReceipt
|
|
||||||
healthReceipts: HealthReceipt[]
|
healthReceipts: HealthReceipt[]
|
||||||
}) {
|
}) {
|
||||||
return new Daemons<never>(config.effects, config.started)
|
return new Daemons<never>(config.effects, config.started)
|
||||||
|
|||||||
223
lib/mainFn/Host.ts
Normal file
223
lib/mainFn/Host.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import { object, string } from "ts-matches"
|
||||||
|
import { Effects } from "../types"
|
||||||
|
import { NetworkInterfaceBuilder } from "./NetworkInterfaceBuilder"
|
||||||
|
import { Origin } from "./Origin"
|
||||||
|
|
||||||
|
const knownProtocols = {
|
||||||
|
http: {
|
||||||
|
secure: false,
|
||||||
|
ssl: false,
|
||||||
|
defaultPort: 80,
|
||||||
|
withSsl: "https",
|
||||||
|
},
|
||||||
|
https: {
|
||||||
|
secure: true,
|
||||||
|
ssl: true,
|
||||||
|
defaultPort: 443,
|
||||||
|
},
|
||||||
|
ws: {
|
||||||
|
secure: false,
|
||||||
|
ssl: false,
|
||||||
|
defaultPort: 80,
|
||||||
|
withSsl: "wss",
|
||||||
|
},
|
||||||
|
wss: {
|
||||||
|
secure: true,
|
||||||
|
ssl: true,
|
||||||
|
defaultPort: 443,
|
||||||
|
},
|
||||||
|
ssh: {
|
||||||
|
secure: true,
|
||||||
|
ssl: false,
|
||||||
|
defaultPort: 22,
|
||||||
|
},
|
||||||
|
bitcoin: {
|
||||||
|
secure: true,
|
||||||
|
ssl: false,
|
||||||
|
defaultPort: 8333,
|
||||||
|
},
|
||||||
|
grpc: {
|
||||||
|
secure: true,
|
||||||
|
ssl: true,
|
||||||
|
defaultPort: 50051,
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
secure: true,
|
||||||
|
ssl: false,
|
||||||
|
defaultPort: 53,
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
type Scheme = string | null
|
||||||
|
|
||||||
|
type AddSslOptions = {
|
||||||
|
preferredExternalPort: number
|
||||||
|
scheme: Scheme
|
||||||
|
addXForwardedHeaders?: boolean /** default: false */
|
||||||
|
}
|
||||||
|
type Security = { secure: false; ssl: false } | { secure: true; ssl: boolean }
|
||||||
|
export type PortOptions = {
|
||||||
|
scheme: Scheme
|
||||||
|
preferredExternalPort: number
|
||||||
|
addSsl: AddSslOptions | null
|
||||||
|
} & Security
|
||||||
|
type KnownProtocols = typeof knownProtocols
|
||||||
|
type ProtocolsWithSslVariants = {
|
||||||
|
[K in keyof KnownProtocols]: KnownProtocols[K] extends {
|
||||||
|
withSsl: string
|
||||||
|
}
|
||||||
|
? K
|
||||||
|
: never
|
||||||
|
}[keyof KnownProtocols]
|
||||||
|
type NotProtocolsWithSslVariants = Exclude<
|
||||||
|
keyof KnownProtocols,
|
||||||
|
ProtocolsWithSslVariants
|
||||||
|
>
|
||||||
|
|
||||||
|
type PortOptionsByKnownProtocol =
|
||||||
|
| ({
|
||||||
|
protocol: ProtocolsWithSslVariants
|
||||||
|
preferredExternalPort?: number
|
||||||
|
scheme?: Scheme
|
||||||
|
} & ({ noAddSsl: true } | { addSsl?: Partial<AddSslOptions> }))
|
||||||
|
| {
|
||||||
|
protocol: NotProtocolsWithSslVariants
|
||||||
|
preferredExternalPort?: number
|
||||||
|
scheme?: Scheme
|
||||||
|
addSsl?: AddSslOptions | null
|
||||||
|
}
|
||||||
|
type PortOptionsByProtocol = PortOptionsByKnownProtocol | PortOptions
|
||||||
|
|
||||||
|
const hasStringProtocal = object({
|
||||||
|
protocol: string,
|
||||||
|
}).test
|
||||||
|
|
||||||
|
export class Host {
|
||||||
|
constructor(
|
||||||
|
readonly kind: "static" | "single" | "multi",
|
||||||
|
readonly options: {
|
||||||
|
effects: Effects
|
||||||
|
id: string
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async bindPort(
|
||||||
|
internalPort: number,
|
||||||
|
options: PortOptionsByProtocol,
|
||||||
|
): Promise<Origin<this>> {
|
||||||
|
if (hasStringProtocal(options)) {
|
||||||
|
return await this.bindPortForKnown(options, internalPort)
|
||||||
|
} else {
|
||||||
|
return await this.bindPortForUnknown(internalPort, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async bindPortForUnknown(
|
||||||
|
internalPort: number,
|
||||||
|
options:
|
||||||
|
| ({
|
||||||
|
scheme: Scheme
|
||||||
|
preferredExternalPort: number
|
||||||
|
addSsl: AddSslOptions | null
|
||||||
|
} & { secure: false; ssl: false })
|
||||||
|
| ({
|
||||||
|
scheme: Scheme
|
||||||
|
preferredExternalPort: number
|
||||||
|
addSsl: AddSslOptions | null
|
||||||
|
} & { secure: true; ssl: boolean }),
|
||||||
|
) {
|
||||||
|
await this.options.effects.bind({
|
||||||
|
kind: this.kind,
|
||||||
|
id: this.options.id,
|
||||||
|
internalPort: internalPort,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Origin(this, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async bindPortForKnown(
|
||||||
|
options: PortOptionsByKnownProtocol,
|
||||||
|
internalPort: number,
|
||||||
|
) {
|
||||||
|
const scheme =
|
||||||
|
options.scheme === undefined ? options.protocol : options.scheme
|
||||||
|
const protoInfo = knownProtocols[options.protocol]
|
||||||
|
const preferredExternalPort =
|
||||||
|
options.preferredExternalPort ||
|
||||||
|
knownProtocols[options.protocol].defaultPort
|
||||||
|
const addSsl = this.getAddSsl(options, protoInfo)
|
||||||
|
|
||||||
|
const security: Security = !protoInfo.secure
|
||||||
|
? {
|
||||||
|
secure: protoInfo.secure,
|
||||||
|
ssl: protoInfo.ssl,
|
||||||
|
}
|
||||||
|
: { secure: false, ssl: false }
|
||||||
|
|
||||||
|
const newOptions = {
|
||||||
|
scheme,
|
||||||
|
preferredExternalPort,
|
||||||
|
addSsl,
|
||||||
|
...security,
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.options.effects.bind({
|
||||||
|
kind: this.kind,
|
||||||
|
id: this.options.id,
|
||||||
|
internalPort,
|
||||||
|
...newOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Origin(this, newOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAddSsl(
|
||||||
|
options: PortOptionsByKnownProtocol,
|
||||||
|
protoInfo: KnownProtocols[keyof KnownProtocols],
|
||||||
|
): AddSslOptions | null {
|
||||||
|
if ("noAddSsl" in options && options.noAddSsl) return null
|
||||||
|
if ("withSsl" in protoInfo && protoInfo.withSsl)
|
||||||
|
return {
|
||||||
|
preferredExternalPort: knownProtocols[protoInfo.withSsl].defaultPort,
|
||||||
|
scheme: protoInfo.withSsl,
|
||||||
|
...("addSsl" in options ? options.addSsl : null),
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StaticHost extends Host {
|
||||||
|
constructor(options: { effects: Effects; id: string }) {
|
||||||
|
super("static", options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SingleHost extends Host {
|
||||||
|
constructor(options: { effects: Effects; id: string }) {
|
||||||
|
super("single", options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MultiHost extends Host {
|
||||||
|
constructor(options: { effects: Effects; id: string }) {
|
||||||
|
super("multi", options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test(effects: Effects) {
|
||||||
|
const foo = new MultiHost({ effects, id: "foo" })
|
||||||
|
const fooOrigin = await foo.bindPort(80, { protocol: "http" as const })
|
||||||
|
const fooInterface = new NetworkInterfaceBuilder({
|
||||||
|
effects,
|
||||||
|
name: "Foo",
|
||||||
|
id: "foo",
|
||||||
|
description: "A Foo",
|
||||||
|
ui: true,
|
||||||
|
username: "bar",
|
||||||
|
path: "/baz",
|
||||||
|
search: { qux: "yes" },
|
||||||
|
})
|
||||||
|
|
||||||
|
await fooInterface.export([fooOrigin])
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { once } from "../util/once"
|
|
||||||
import { Origin } from "./Origin"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pulled from https://www.oreilly.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
|
|
||||||
* to test ipv4 addresses
|
|
||||||
*/
|
|
||||||
export const regexToTestIp4 = once(() => /(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
|
|
||||||
/**
|
|
||||||
* Pulled from https://ihateregex.io/expr/ipv6/
|
|
||||||
* to test ipv6 addresses
|
|
||||||
*/
|
|
||||||
export const ipv6 = once(
|
|
||||||
() =>
|
|
||||||
/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/,
|
|
||||||
)
|
|
||||||
/**
|
|
||||||
* Some addresses are local
|
|
||||||
*/
|
|
||||||
export const local = once(() => /\.local/)
|
|
||||||
export class LocalBinding {
|
|
||||||
constructor(readonly localHost: string, readonly ipHosts: string[]) {}
|
|
||||||
createOrigins(protocol: string | null) {
|
|
||||||
const ipHosts = this.ipHosts
|
|
||||||
return {
|
|
||||||
local: new Origin(protocol, this.localHost),
|
|
||||||
get all() {
|
|
||||||
return [this.local, ...this.ip]
|
|
||||||
},
|
|
||||||
get ip() {
|
|
||||||
return ipHosts
|
|
||||||
.filter((x) => !local().test(x))
|
|
||||||
.map((x) => new Origin(protocol, x))
|
|
||||||
},
|
|
||||||
get ipv4() {
|
|
||||||
return ipHosts
|
|
||||||
.filter(regexToTestIp4().test)
|
|
||||||
.map((x) => new Origin(protocol, x))
|
|
||||||
},
|
|
||||||
get ipv6() {
|
|
||||||
return ipHosts.filter(ipv6().test).map((x) => new Origin(protocol, x))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Effects } from "../types"
|
|
||||||
import { LocalBinding } from "./LocalBinding"
|
|
||||||
|
|
||||||
export class LocalPort {
|
|
||||||
constructor(readonly effects: Effects) {}
|
|
||||||
static async bindLan(effects: Effects, internalPort: number) {
|
|
||||||
const port = await effects.bindLan({
|
|
||||||
internalPort,
|
|
||||||
})
|
|
||||||
const localAddress = `${await effects.getLocalHostname()}:${port}`
|
|
||||||
const ipAddress = await (
|
|
||||||
await effects.getIPHostname()
|
|
||||||
).map((x) => `${x}:${port}`)
|
|
||||||
return new LocalBinding(localAddress, ipAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Effects } from "../types"
|
import { Effects } from "../types"
|
||||||
import { AddressReceipt } from "./AddressReceipt"
|
import { AddressReceipt } from "./AddressReceipt"
|
||||||
|
import { Host } from "./Host"
|
||||||
import { Origin } from "./Origin"
|
import { Origin } from "./Origin"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +36,7 @@ export class NetworkInterfaceBuilder {
|
|||||||
* @param addresses
|
* @param addresses
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async export(origins: Iterable<Origin>) {
|
async export(origins: Iterable<Origin<Host>>) {
|
||||||
const { name, description, id, ui, username, path, search } = this.options
|
const { name, description, id, ui, username, path, search } = this.options
|
||||||
|
|
||||||
const addresses = Array.from(origins).map((o) =>
|
const addresses = Array.from(origins).map((o) =>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
export class Origin {
|
import { Address } from "../types"
|
||||||
constructor(readonly protocol: string | null, readonly host: string) {}
|
import { Host, PortOptions } from "./Host"
|
||||||
|
|
||||||
build({ username, path, search }: BuildOptions) {
|
export class Origin<T extends Host> {
|
||||||
// prettier-ignore
|
constructor(readonly host: T, readonly options: PortOptions) {}
|
||||||
const urlAuth = !!(username) ? `${username}@` :
|
|
||||||
'';
|
|
||||||
const protocolSection = this.protocol != null ? `${this.protocol}://` : ""
|
|
||||||
|
|
||||||
|
build({ username, path, search }: BuildOptions): Address {
|
||||||
const qpEntries = Object.entries(search)
|
const qpEntries = Object.entries(search)
|
||||||
.map(
|
.map(
|
||||||
([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
|
([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
|
||||||
@@ -15,7 +13,12 @@ export class Origin {
|
|||||||
|
|
||||||
const qp = qpEntries.length ? `?${qpEntries}` : ""
|
const qp = qpEntries.length ? `?${qpEntries}` : ""
|
||||||
|
|
||||||
return `${protocolSection}${urlAuth}${this.host}${path}${qp}`
|
return {
|
||||||
|
hostId: this.host.options.id,
|
||||||
|
options: this.options,
|
||||||
|
suffix: `${path}${qp}`,
|
||||||
|
username,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
42
lib/types.ts
42
lib/types.ts
@@ -1,6 +1,7 @@
|
|||||||
export * as configTypes from "./config/configTypes"
|
export * as configTypes from "./config/configTypes"
|
||||||
import { InputSpec } from "./config/configTypes"
|
import { InputSpec } from "./config/configTypes"
|
||||||
import { DependenciesReceipt } from "./config/setupConfig"
|
import { DependenciesReceipt } from "./config/setupConfig"
|
||||||
|
import { PortOptions } from "./mainFn/Host"
|
||||||
|
|
||||||
export type ExportedAction = (options: {
|
export type ExportedAction = (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
@@ -166,14 +167,22 @@ export type ActionMetadata = {
|
|||||||
group?: string
|
group?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */
|
||||||
|
export type Address = {
|
||||||
|
username: string | null
|
||||||
|
hostId: string
|
||||||
|
options: PortOptions
|
||||||
|
suffix: string
|
||||||
|
}
|
||||||
|
|
||||||
export type NetworkInterface = {
|
export type NetworkInterface = {
|
||||||
id: string
|
id: string
|
||||||
/** The title of this field to be dsimplayed */
|
/** The title of this field to be displayed */
|
||||||
name: string
|
name: string
|
||||||
/** Human readable description, used as tooltip usually */
|
/** Human readable description, used as tooltip usually */
|
||||||
description: string
|
description: string
|
||||||
/** All URIs */
|
/** All URIs */
|
||||||
addresses: string[]
|
addresses: Address[]
|
||||||
/** Defaults to false, but describes if this address can be opened in a browser as an
|
/** Defaults to false, but describes if this address can be opened in a browser as an
|
||||||
* ui interface
|
* ui interface
|
||||||
*/
|
*/
|
||||||
@@ -206,16 +215,23 @@ export type Effects = {
|
|||||||
|
|
||||||
/** Check that a file exists or not */
|
/** Check that a file exists or not */
|
||||||
exists(input: { volumeId: string; path: string }): Promise<boolean>
|
exists(input: { volumeId: string; path: string }): Promise<boolean>
|
||||||
/** Declaring that we are opening a interface on some protocal for local network
|
|
||||||
* Returns the port exposed
|
/** Removes all network bindings */
|
||||||
*/
|
clearBindings(): Promise<void>
|
||||||
bindLan(options: { internalPort: number }): Promise<number>
|
/** Creates a host connected to the specified port with the provided options */
|
||||||
/** Declaring that we are opening a interface on some protocal for tor network */
|
bind(
|
||||||
bindTor(options: {
|
options: {
|
||||||
internalPort: number
|
kind: "static" | "single" | "multi"
|
||||||
name: string
|
id: string
|
||||||
externalPort: number
|
internalPort: number
|
||||||
}): Promise<string>
|
} & PortOptions,
|
||||||
|
): Promise<void>
|
||||||
|
/** Retrieves the current hostname(s) associated with a host id */
|
||||||
|
getHostNames(options: {
|
||||||
|
kind: "static" | "single"
|
||||||
|
id: string
|
||||||
|
}): Promise<[string]>
|
||||||
|
getHostNames(options: { kind: "multi"; id: string }): Promise<string[]>
|
||||||
|
|
||||||
/** Similar to the fetch api via the mdn, this is simplified but the point is
|
/** Similar to the fetch api via the mdn, this is simplified but the point is
|
||||||
* to get something from some website, and return the response.
|
* to get something from some website, and return the response.
|
||||||
@@ -294,6 +310,8 @@ export type Effects = {
|
|||||||
packageId?: string,
|
packageId?: string,
|
||||||
): Promise<number>
|
): Promise<number>
|
||||||
|
|
||||||
|
/** Removes all network interfaces */
|
||||||
|
clearNetworkInterfaces(): Promise<void>
|
||||||
/** When we want to create a link in the front end interfaces, and example is
|
/** When we want to create a link in the front end interfaces, and example is
|
||||||
* exposing a url to view a web service
|
* exposing a url to view a web service
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user