Merge branch 'next/minor' of github.com:Start9Labs/start-os into next/major

This commit is contained in:
Aiden McClelland
2025-03-03 12:51:40 -07:00
213 changed files with 53468 additions and 12274 deletions

View File

@@ -4,6 +4,8 @@ Description=StartOS Container Runtime
[Service]
Type=simple
ExecStart=/usr/bin/node --experimental-detect-module --unhandled-rejections=warn /usr/lib/startos/init/index.js
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target

File diff suppressed because it is too large Load Diff

View File

@@ -198,6 +198,9 @@ export function makeEffects(context: EffectContext): Effects {
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,

View File

@@ -1,4 +1,9 @@
import { ExtendedVersion, types as T, utils } from "@start9labs/start-sdk"
import {
ExtendedVersion,
types as T,
utils,
VersionRange,
} from "@start9labs/start-sdk"
import * as fs from "fs/promises"
import { polyfillEffects } from "./polyfillEffects"
@@ -345,13 +350,11 @@ export class SystemForEmbassy implements System {
const previousVersion = await effects.getDataVersion()
if (previousVersion) {
if (
(await this.migration(effects, previousVersion, timeoutMs)).configured
(await this.migration(effects, { from: previousVersion }, timeoutMs))
.configured
) {
await effects.action.clearRequests({ only: ["needs-config"] })
}
await effects.setDataVersion({
version: ExtendedVersion.parseEmver(this.manifest.version).toString(),
})
} else if (this.manifest.config) {
await effects.action.request({
packageId: this.manifest.id,
@@ -361,6 +364,9 @@ export class SystemForEmbassy implements System {
reason: "This service must be configured before it can be run",
})
}
await effects.setDataVersion({
version: ExtendedVersion.parseEmver(this.manifest.version).toString(),
})
}
async exportNetwork(effects: Effects) {
for (const [id, interfaceValue] of Object.entries(
@@ -542,7 +548,10 @@ export class SystemForEmbassy implements System {
nextVersion: Optional<string>,
timeoutMs: number | null,
): Promise<void> {
// TODO Do a migration down if the version exists
await this.currentRunning?.clean({ timeout: timeoutMs ?? undefined })
if (nextVersion) {
await this.migration(effects, { to: nextVersion }, timeoutMs)
}
await effects.setMainStatus({ status: "stopped" })
}
@@ -746,46 +755,37 @@ export class SystemForEmbassy implements System {
async migration(
effects: Effects,
fromVersion: string,
version: { from: string } | { to: string },
timeoutMs: number | null,
): Promise<{ configured: boolean }> {
const fromEmver = ExtendedVersion.parseEmver(fromVersion)
const currentEmver = ExtendedVersion.parseEmver(this.manifest.version)
if (!this.manifest.migrations) return { configured: true }
const fromMigration = Object.entries(this.manifest.migrations.from)
.map(
([version, procedure]) =>
[ExtendedVersion.parseEmver(version), procedure] as const,
)
.find(
([versionEmver, procedure]) =>
versionEmver.greaterThan(fromEmver) &&
versionEmver.lessThanOrEqual(currentEmver),
)
const toMigration = Object.entries(this.manifest.migrations.to)
.map(
([version, procedure]) =>
[ExtendedVersion.parseEmver(version), procedure] as const,
)
.find(
([versionEmver, procedure]) =>
versionEmver.greaterThan(fromEmver) &&
versionEmver.lessThanOrEqual(currentEmver),
)
// prettier-ignore
const migration = (
fromEmver.greaterThan(currentEmver) ? [toMigration, fromMigration] :
[fromMigration, toMigration]).filter(Boolean)[0]
let migration
let args: [string, ...string[]]
if ("from" in version) {
args = [version.from, "from"]
const fromExver = ExtendedVersion.parse(version.from)
if (!this.manifest.migrations) return { configured: true }
migration = Object.entries(this.manifest.migrations.from)
.map(
([version, procedure]) =>
[VersionRange.parseEmver(version), procedure] as const,
)
.find(([versionEmver, _]) => versionEmver.satisfiedBy(fromExver))
} else {
args = [version.to, "to"]
const toExver = ExtendedVersion.parse(version.to)
if (!this.manifest.migrations) return { configured: true }
migration = Object.entries(this.manifest.migrations.to)
.map(
([version, procedure]) =>
[VersionRange.parseEmver(version), procedure] as const,
)
.find(([versionEmver, _]) => versionEmver.satisfiedBy(toExver))
}
if (migration) {
const [version, procedure] = migration
const [_, procedure] = migration
if (procedure.type === "docker") {
const commands = [
procedure.entrypoint,
...procedure.args,
JSON.stringify(fromVersion),
]
const commands = [procedure.entrypoint, ...procedure.args]
const container = await DockerProcedureContainer.of(
effects,
this.manifest.id,
@@ -794,7 +794,11 @@ export class SystemForEmbassy implements System {
`Migration - ${commands.join(" ")}`,
)
return JSON.parse(
(await container.execFail(commands, timeoutMs)).stdout.toString(),
(
await container.execFail(commands, timeoutMs, {
input: JSON.stringify(args[0]),
})
).stdout.toString(),
)
} else if (procedure.type === "script") {
const moduleCode = await this.moduleCode
@@ -803,7 +807,7 @@ export class SystemForEmbassy implements System {
throw new Error("Expecting that the method migration exists")
return (await method(
polyfillEffects(effects, this.manifest),
fromVersion as string,
...args,
).then((x) => {
if ("result" in x) return x.result
if ("error" in x) throw new Error("Error getting config: " + x.error)
@@ -974,7 +978,7 @@ export class SystemForEmbassy implements System {
})) as U.Config
if (!oldConfig) return
const moduleCode = await this.moduleCode
const method = moduleCode.dependencies?.[id]?.autoConfigure
const method = moduleCode?.dependencies?.[id]?.autoConfigure
if (!method) return
const newConfig = (await method(
polyfillEffects(effects, this.manifest),
@@ -1177,9 +1181,14 @@ function extractServiceInterfaceId(manifest: Manifest, specInterface: string) {
return serviceInterfaceId
}
async function convertToNewConfig(value: OldGetConfigRes) {
const valueSpec: OldConfigSpec = matchOldConfigSpec.unsafeCast(value.spec)
const spec = transformConfigSpec(valueSpec)
if (!value.config) return { spec, config: null }
const config = transformOldConfigToNew(valueSpec, value.config)
return { spec, config }
try {
const valueSpec: OldConfigSpec = matchOldConfigSpec.unsafeCast(value.spec)
const spec = transformConfigSpec(valueSpec)
if (!value.config) return { spec, config: null }
const config = transformOldConfigToNew(valueSpec, value.config) ?? null
return { spec, config }
} catch (e) {
console.error(e)
throw e
}
}

View File

@@ -444,8 +444,8 @@ function parseDfOutput(output: string): { used: number; total: number } {
.map((x) => x.split(/\s+/))
const index = lines.splice(0, 1)[0].map((x) => x.toLowerCase())
const usedIndex = index.indexOf("used")
const availableIndex = index.indexOf("available")
const sizeIndex = index.indexOf("size")
const used = lines.map((x) => Number.parseInt(x[usedIndex]))[0] || 0
const total = lines.map((x) => Number.parseInt(x[availableIndex]))[0] || 0
const total = lines.map((x) => Number.parseInt(x[sizeIndex]))[0] || 0
return { used, total }
}

View File

@@ -146,6 +146,7 @@ export function transformOldConfigToNew(
spec: OldConfigSpec,
config: Record<string, any>,
): Record<string, any> {
if (!config) return config
return Object.entries(spec).reduce((obj, [key, val]) => {
let newVal = config[key]
@@ -157,9 +158,16 @@ export function transformOldConfigToNew(
}
if (isUnion(val)) {
const selection = config[key][val.tag.id]
if (!config[key]) return obj
const selection = config[key]?.[val.tag.id]
if (!selection) return obj
delete config[key][val.tag.id]
if (!val.variants[selection]) return obj
newVal = {
selection,
value: transformOldConfigToNew(
@@ -170,6 +178,8 @@ export function transformOldConfigToNew(
}
if (isList(val) && isObjectList(val)) {
if (!config[key]) return obj
newVal = (config[key] as object[]).map((obj) =>
transformOldConfigToNew(
matchOldConfigSpec.unsafeCast(val.spec.spec),