mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Bugfix/ssl proxy to ssl (#2956)
* fix registry rm command * fix bind with addSsl on ssl proto * fix bind with addSsl on ssl proto * Add pre-release version migrations * fix os build * add mime to package deps * update lockfile * more ssl fixes * add waitFor * improve restart lockup * beta.26 * fix dependency health check logic * handle missing health check * fix port forwards --------- Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -81,9 +81,13 @@ export async function checkDependencies<
|
||||
) {
|
||||
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
||||
}
|
||||
const errors = Object.entries(dep.result.healthChecks)
|
||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||
.filter(([_, res]) => res.result !== "success")
|
||||
const errors =
|
||||
dep.requirement.kind === "running"
|
||||
? dep.requirement.healthChecks
|
||||
.map((id) => [id, dep.result.healthChecks[id] ?? null] as const)
|
||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||
.filter(([_, res]) => res?.result !== "success")
|
||||
: []
|
||||
return errors.length === 0
|
||||
}
|
||||
const pkgSatisfied = (packageId: DependencyId) =>
|
||||
@@ -153,15 +157,20 @@ export async function checkDependencies<
|
||||
) {
|
||||
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
||||
}
|
||||
const errors = Object.entries(dep.result.healthChecks)
|
||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||
.filter(([_, res]) => res.result !== "success")
|
||||
const errors =
|
||||
dep.requirement.kind === "running"
|
||||
? dep.requirement.healthChecks
|
||||
.map((id) => [id, dep.result.healthChecks[id] ?? null] as const)
|
||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||
.filter(([_, res]) => res?.result !== "success")
|
||||
: []
|
||||
if (errors.length) {
|
||||
throw new Error(
|
||||
errors
|
||||
.map(
|
||||
([_, e]) =>
|
||||
`Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ""}`,
|
||||
.map(([id, e]) =>
|
||||
e
|
||||
? `Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ""}`
|
||||
: `Health Check ${id} of ${dep.result.title} does not exist`,
|
||||
)
|
||||
.join("; "),
|
||||
)
|
||||
|
||||
@@ -134,19 +134,26 @@ export class MultiHost {
|
||||
const preferredExternalPort =
|
||||
options.preferredExternalPort ||
|
||||
knownProtocols[options.protocol].defaultPort
|
||||
const sslProto = this.getSslProto(options, protoInfo)
|
||||
const addSsl =
|
||||
sslProto && "alpn" in protoInfo
|
||||
const sslProto = this.getSslProto(options)
|
||||
const addSsl = sslProto
|
||||
? {
|
||||
// addXForwardedHeaders: null,
|
||||
preferredExternalPort: knownProtocols[sslProto].defaultPort,
|
||||
scheme: sslProto,
|
||||
alpn: "alpn" in protoInfo ? protoInfo.alpn : null,
|
||||
...("addSsl" in options ? options.addSsl : null),
|
||||
}
|
||||
: options.addSsl
|
||||
? {
|
||||
// addXForwardedHeaders: null,
|
||||
preferredExternalPort: knownProtocols[sslProto].defaultPort,
|
||||
preferredExternalPort: 443,
|
||||
scheme: sslProto,
|
||||
alpn: protoInfo.alpn,
|
||||
alpn: null,
|
||||
...("addSsl" in options ? options.addSsl : null),
|
||||
}
|
||||
: null
|
||||
|
||||
const secure: Security | null = !protoInfo.secure ? null : { ssl: false }
|
||||
const secure: Security | null = protoInfo.secure ?? null
|
||||
|
||||
await this.options.effects.bind({
|
||||
id: this.options.id,
|
||||
@@ -159,12 +166,12 @@ export class MultiHost {
|
||||
return new Origin(this, internalPort, options.protocol, sslProto)
|
||||
}
|
||||
|
||||
private getSslProto(
|
||||
options: BindOptionsByKnownProtocol,
|
||||
protoInfo: KnownProtocols[keyof KnownProtocols],
|
||||
) {
|
||||
private getSslProto(options: BindOptionsByKnownProtocol) {
|
||||
const proto = options.protocol
|
||||
const protoInfo = knownProtocols[proto]
|
||||
if (inObject("noAddSsl", options) && options.noAddSsl) return null
|
||||
if ("withSsl" in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
|
||||
if (protoInfo.secure?.ssl) return proto
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,30 @@ export class GetSystemSmtp {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches the system SMTP credentials. Returns when the predicate is true
|
||||
*/
|
||||
async waitFor(pred: (value: T.SmtpValue | null) => boolean) {
|
||||
const resolveCell = { resolve: () => {} }
|
||||
this.effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
while (this.effects.isInContext) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve
|
||||
resolveCell.resolve = resolve
|
||||
})
|
||||
const res = await this.effects.getSystemSmtp({
|
||||
callback: () => callback(),
|
||||
})
|
||||
if (pred(res)) {
|
||||
resolveCell.resolve()
|
||||
return res
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +366,36 @@ export class GetServiceInterface {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches the requested service interface. Returns when the predicate is true
|
||||
*/
|
||||
async waitFor(pred: (value: ServiceInterfaceFilled | null) => boolean) {
|
||||
const { id, packageId } = this.opts
|
||||
const resolveCell = { resolve: () => {} }
|
||||
this.effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
while (this.effects.isInContext) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve
|
||||
resolveCell.resolve = resolve
|
||||
})
|
||||
const res = await makeInterfaceFilled({
|
||||
effects: this.effects,
|
||||
id,
|
||||
packageId,
|
||||
callback,
|
||||
})
|
||||
if (pred(res)) {
|
||||
resolveCell.resolve()
|
||||
return res
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
export function getServiceInterface(
|
||||
effects: Effects,
|
||||
|
||||
@@ -130,6 +130,35 @@ export class GetServiceInterfaces {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches the service interfaces for the package. Returns when the predicate is true
|
||||
*/
|
||||
async waitFor(pred: (value: ServiceInterfaceFilled[] | null) => boolean) {
|
||||
const { packageId } = this.opts
|
||||
const resolveCell = { resolve: () => {} }
|
||||
this.effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
while (this.effects.isInContext) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve
|
||||
resolveCell.resolve = resolve
|
||||
})
|
||||
const res = await makeManyInterfaceFilled({
|
||||
effects: this.effects,
|
||||
packageId,
|
||||
callback,
|
||||
})
|
||||
if (pred(res)) {
|
||||
resolveCell.resolve()
|
||||
return res
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
export function getServiceInterfaces(
|
||||
effects: Effects,
|
||||
|
||||
@@ -249,6 +249,26 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
),
|
||||
)
|
||||
},
|
||||
waitFor: async (pred: (value: string | null) => boolean) => {
|
||||
const resolveCell = { resolve: () => {} }
|
||||
effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
while (effects.isInContext) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve
|
||||
resolveCell.resolve = resolve
|
||||
})
|
||||
const res = await effects.getContainerIp({ ...options, callback })
|
||||
if (pred(res)) {
|
||||
resolveCell.resolve()
|
||||
return res
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return null
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -189,6 +189,8 @@ async function runRsync(rsyncOptions: {
|
||||
}> {
|
||||
const { srcPath, dstPath, options } = rsyncOptions
|
||||
|
||||
await fs.mkdir(dstPath, { recursive: true })
|
||||
|
||||
const command = "rsync"
|
||||
const args: string[] = []
|
||||
if (options.delete) {
|
||||
|
||||
@@ -82,4 +82,32 @@ export class GetSslCertificate {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches the SSL Certificate for the given hostnames if permitted. Returns when the predicate is true
|
||||
*/
|
||||
async waitFor(pred: (value: [string, string, string] | null) => boolean) {
|
||||
const resolveCell = { resolve: () => {} }
|
||||
this.effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
while (this.effects.isInContext) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve
|
||||
resolveCell.resolve = resolve
|
||||
})
|
||||
const res = await this.effects.getSslCertificate({
|
||||
hostnames: this.hostnames,
|
||||
algorithm: this.algorithm,
|
||||
callback: () => callback(),
|
||||
})
|
||||
if (pred(res)) {
|
||||
resolveCell.resolve()
|
||||
return res
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,17 @@ async function onCreated(path: string) {
|
||||
await onCreated(parent)
|
||||
const ctrl = new AbortController()
|
||||
const watch = fs.watch(parent, { persistent: false, signal: ctrl.signal })
|
||||
if (await exists(path)) {
|
||||
ctrl.abort()
|
||||
return
|
||||
}
|
||||
if (
|
||||
await fs.access(path).then(
|
||||
() => true,
|
||||
() => false,
|
||||
)
|
||||
) {
|
||||
ctrl.abort("finished")
|
||||
ctrl.abort()
|
||||
return
|
||||
}
|
||||
for await (let event of watch) {
|
||||
@@ -100,6 +104,10 @@ type ReadType<A> = {
|
||||
effects: T.Effects,
|
||||
callback: (value: A | null, error?: Error) => void | Promise<void>,
|
||||
) => void
|
||||
waitFor: (
|
||||
effects: T.Effects,
|
||||
pred: (value: A | null) => boolean,
|
||||
) => Promise<A | null>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +236,7 @@ export class FileHelper<A> {
|
||||
const listen = Promise.resolve()
|
||||
.then(async () => {
|
||||
for await (const _ of watch) {
|
||||
ctrl.abort("finished")
|
||||
ctrl.abort()
|
||||
return null
|
||||
}
|
||||
})
|
||||
@@ -271,6 +279,40 @@ export class FileHelper<A> {
|
||||
)
|
||||
}
|
||||
|
||||
private async readWaitFor<B>(
|
||||
effects: T.Effects,
|
||||
pred: (value: B | null, error?: Error) => boolean,
|
||||
map: (value: A) => B,
|
||||
): Promise<B | null> {
|
||||
while (effects.isInContext) {
|
||||
if (await exists(this.path)) {
|
||||
const ctrl = new AbortController()
|
||||
const watch = fs.watch(this.path, {
|
||||
persistent: false,
|
||||
signal: ctrl.signal,
|
||||
})
|
||||
const newRes = await this.readOnce(map)
|
||||
const listen = Promise.resolve()
|
||||
.then(async () => {
|
||||
for await (const _ of watch) {
|
||||
ctrl.abort()
|
||||
return null
|
||||
}
|
||||
})
|
||||
.catch((e) => console.error(asError(e)))
|
||||
if (pred(newRes)) {
|
||||
ctrl.abort()
|
||||
return newRes
|
||||
}
|
||||
await listen
|
||||
} else {
|
||||
if (pred(null)) return null
|
||||
await onCreated(this.path).catch((e) => console.error(asError(e)))
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
read(): ReadType<A>
|
||||
read<B>(
|
||||
map: (value: A) => B,
|
||||
@@ -290,6 +332,8 @@ export class FileHelper<A> {
|
||||
effects: T.Effects,
|
||||
callback: (value: A | null, error?: Error) => void | Promise<void>,
|
||||
) => this.readOnChange(effects, callback, map, eq),
|
||||
waitFor: (effects: T.Effects, pred: (value: A | null) => boolean) =>
|
||||
this.readWaitFor(effects, pred, map),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
sdk/package/package-lock.json
generated
32
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.25",
|
||||
"version": "0.4.0-beta.26",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.25",
|
||||
"version": "0.4.0-beta.26",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
@@ -16,7 +16,7 @@
|
||||
"deep-equality-data-structures": "^2.0.0",
|
||||
"ini": "^5.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"mime-types": "^3.0.1",
|
||||
"mime": "^4.0.7",
|
||||
"ts-matches": "^6.3.2",
|
||||
"yaml": "^2.7.1"
|
||||
},
|
||||
@@ -3865,25 +3865,19 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"node_modules/mime": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz",
|
||||
"integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa"
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.54.0"
|
||||
"bin": {
|
||||
"mime": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.25",
|
||||
"version": "0.4.0-beta.26",
|
||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||
"main": "./package/lib/index.js",
|
||||
"types": "./package/lib/index.d.ts",
|
||||
@@ -32,7 +32,7 @@
|
||||
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
|
||||
"dependencies": {
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"mime-types": "^3.0.1",
|
||||
"mime": "^4.0.7",
|
||||
"ts-matches": "^6.3.2",
|
||||
"yaml": "^2.7.1",
|
||||
"deep-equality-data-structures": "^2.0.0",
|
||||
|
||||
Reference in New Issue
Block a user