mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
move mounts to daemons constructor
This commit is contained in:
@@ -46,7 +46,6 @@ import { setupMain } from "./mainFn"
|
||||
import { defaultTrigger } from "./trigger/defaultTrigger"
|
||||
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
|
||||
import setupConfig, { Read, Save } from "./config/setupConfig"
|
||||
import { setupDependencyMounts } from "./dependency/setupDependencyMounts"
|
||||
import {
|
||||
InterfacesReceipt,
|
||||
SetInterfaces,
|
||||
@@ -170,7 +169,6 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
},
|
||||
) => setupDependencyConfig<Store, Input, Manifest>(config, autoConfigs),
|
||||
setupExports: (fn: SetupExports<Store>) => fn,
|
||||
setupDependencyMounts,
|
||||
setupInit: (
|
||||
migrations: Migrations<Manifest, Store>,
|
||||
install: Install<Manifest, Store>,
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Effects } from "../types"
|
||||
import { _ } from "../util"
|
||||
import {
|
||||
Path,
|
||||
ManifestId,
|
||||
VolumeName,
|
||||
NamedPath,
|
||||
matchPath,
|
||||
} from "./setupDependencyMounts"
|
||||
|
||||
export type MountDependenciesOut<A> = _<
|
||||
// prettier-ignore
|
||||
A extends Path ? string : A extends Record<string, unknown> ? {
|
||||
[P in keyof A]: MountDependenciesOut<A[P]>;
|
||||
} : never
|
||||
>
|
||||
export async function mountDependencies<
|
||||
In extends
|
||||
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||
| Record<VolumeName, Record<NamedPath, Path>>
|
||||
| Record<NamedPath, Path>
|
||||
| Path,
|
||||
>(effects: Effects, value: In): Promise<MountDependenciesOut<In>> {
|
||||
if (matchPath.test(value)) {
|
||||
const mountPath = `${value.manifestId}/${value.volume}/${value.name}`
|
||||
|
||||
return (await effects.mount({
|
||||
location: mountPath,
|
||||
target: {
|
||||
packageId: value.manifestId,
|
||||
path: value.path,
|
||||
readonly: value.readonly,
|
||||
volumeId: value.volume,
|
||||
},
|
||||
})) as MountDependenciesOut<In>
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, value]) => [
|
||||
key,
|
||||
mountDependencies(effects, value),
|
||||
]),
|
||||
) as Record<string, unknown> as MountDependenciesOut<In>
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { boolean, object, string } from "ts-matches"
|
||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||
import { deepMerge } from "../util/deepMerge"
|
||||
|
||||
export type VolumeName = string
|
||||
export type NamedPath = string
|
||||
export type ManifestId = string
|
||||
|
||||
export const matchPath = object({
|
||||
name: string,
|
||||
volume: string,
|
||||
path: string,
|
||||
manifestId: string,
|
||||
readonly: boolean,
|
||||
})
|
||||
export type Path = typeof matchPath._TYPE
|
||||
export type BuildPath<
|
||||
ManifestId extends string,
|
||||
VolumeId extends string,
|
||||
PathName extends string,
|
||||
Value extends Path,
|
||||
> = {
|
||||
[PId in ManifestId]: {
|
||||
[V in VolumeId]: {
|
||||
[N in PathName]: Value
|
||||
}
|
||||
}
|
||||
}
|
||||
class SetupDependencyMounts<Building> {
|
||||
private constructor(readonly building: Building) {}
|
||||
|
||||
static of() {
|
||||
return new SetupDependencyMounts({})
|
||||
}
|
||||
|
||||
addPath<
|
||||
Name extends string,
|
||||
Volume extends M["volumes"][0] & string,
|
||||
Path extends string,
|
||||
ManifestId extends M["id"],
|
||||
M extends SDKManifest,
|
||||
>(addPath: {
|
||||
name: Name
|
||||
volume: Volume
|
||||
path: Path
|
||||
manifest: M
|
||||
readonly: boolean
|
||||
}) {
|
||||
const { manifest, ...restPath } = addPath
|
||||
const newPath = {
|
||||
...restPath,
|
||||
manifestId: manifest.id as ManifestId,
|
||||
} as const
|
||||
type NewBuilding = Building &
|
||||
BuildPath<ManifestId, Volume, Name, typeof newPath>
|
||||
const building = deepMerge(this.building, {
|
||||
[newPath.manifestId]: {
|
||||
[newPath.volume]: {
|
||||
[newPath.name]: newPath,
|
||||
},
|
||||
},
|
||||
}) as NewBuilding
|
||||
return new SetupDependencyMounts(building)
|
||||
}
|
||||
build() {
|
||||
return this.building
|
||||
}
|
||||
}
|
||||
|
||||
export function setupDependencyMounts() {
|
||||
return SetupDependencyMounts.of()
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types"
|
||||
import { createUtils } from "../util"
|
||||
import { Signals } from "../util/utils"
|
||||
import { Mounts } from "./Mounts"
|
||||
type Daemon<
|
||||
Manifest extends SDKManifest,
|
||||
Ids extends string,
|
||||
@@ -16,6 +17,7 @@ type Daemon<
|
||||
id: "" extends Id ? never : Id
|
||||
command: ValidIfNoStupidEscape<Command> | [string, ...string[]]
|
||||
imageId: Manifest["images"][number]
|
||||
mounts: Mounts<Manifest>
|
||||
env?: Record<string, string>
|
||||
ready: {
|
||||
display: string | null
|
||||
@@ -106,7 +108,10 @@ export class Daemons<Manifest extends SDKManifest, Ids extends string> {
|
||||
const { command, imageId } = daemon
|
||||
const utils = createUtils<Manifest>(effects)
|
||||
|
||||
const child = utils.runDaemon(imageId, command, { env: daemon.env })
|
||||
const child = utils.runDaemon(imageId, command, {
|
||||
env: daemon.env,
|
||||
mounts: daemon.mounts.build(),
|
||||
})
|
||||
let currentInput: TriggerInput = {}
|
||||
const getCurrentInput = () => currentInput
|
||||
const trigger = (daemon.ready.trigger ?? defaultTrigger)(
|
||||
|
||||
126
sdk/lib/mainFn/Mounts.ts
Normal file
126
sdk/lib/mainFn/Mounts.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||
import { Effects } from "../types"
|
||||
import { MountOptions } from "../util/Overlay"
|
||||
|
||||
type MountArray = { path: string; options: MountOptions }[]
|
||||
|
||||
export class Mounts<Manifest extends SDKManifest> {
|
||||
private constructor(
|
||||
readonly volumes: {
|
||||
id: Manifest["volumes"][number]
|
||||
subpath: string | null
|
||||
mountpoint: string
|
||||
readonly: boolean
|
||||
}[],
|
||||
readonly assets: {
|
||||
id: Manifest["assets"][number]
|
||||
subpath: string | null
|
||||
mountpoint: string
|
||||
}[],
|
||||
readonly dependencies: {
|
||||
dependencyId: string
|
||||
volumeId: string
|
||||
subpath: string | null
|
||||
mountpoint: string
|
||||
readonly: boolean
|
||||
}[],
|
||||
) {}
|
||||
|
||||
static of<Manifest extends SDKManifest>() {
|
||||
return new Mounts<Manifest>([], [], [])
|
||||
}
|
||||
|
||||
addVolume(
|
||||
id: Manifest["volumes"][number],
|
||||
subpath: string | null,
|
||||
mountpoint: string,
|
||||
readonly: boolean,
|
||||
) {
|
||||
this.volumes.push({
|
||||
id,
|
||||
subpath,
|
||||
mountpoint,
|
||||
readonly,
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
addAssets(
|
||||
id: Manifest["assets"][number],
|
||||
subpath: string | null,
|
||||
mountpoint: string,
|
||||
) {
|
||||
this.assets.push({
|
||||
id,
|
||||
subpath,
|
||||
mountpoint,
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
addDependency<DependencyManifest extends SDKManifest>(
|
||||
dependencyId: keyof Manifest["dependencies"] & string,
|
||||
volumeId: DependencyManifest["volumes"][number],
|
||||
subpath: string | null,
|
||||
mountpoint: string,
|
||||
readonly: boolean,
|
||||
) {
|
||||
this.dependencies.push({
|
||||
dependencyId,
|
||||
volumeId,
|
||||
subpath,
|
||||
mountpoint,
|
||||
readonly,
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
build(): MountArray {
|
||||
const mountpoints = new Set()
|
||||
for (let mountpoint of this.volumes
|
||||
.map((v) => v.mountpoint)
|
||||
.concat(this.assets.map((a) => a.mountpoint))
|
||||
.concat(this.dependencies.map((d) => d.mountpoint))) {
|
||||
if (mountpoints.has(mountpoint)) {
|
||||
throw new Error(
|
||||
`cannot mount more than once to mountpoint ${mountpoint}`,
|
||||
)
|
||||
}
|
||||
mountpoints.add(mountpoint)
|
||||
}
|
||||
return ([] as MountArray)
|
||||
.concat(
|
||||
this.volumes.map((v) => ({
|
||||
path: v.mountpoint,
|
||||
options: {
|
||||
type: "volume",
|
||||
id: v.id,
|
||||
subpath: v.subpath,
|
||||
readonly: v.readonly,
|
||||
},
|
||||
})),
|
||||
)
|
||||
.concat(
|
||||
this.assets.map((a) => ({
|
||||
path: a.mountpoint,
|
||||
options: {
|
||||
type: "assets",
|
||||
id: a.id,
|
||||
subpath: a.subpath,
|
||||
},
|
||||
})),
|
||||
)
|
||||
.concat(
|
||||
this.dependencies.map((d) => ({
|
||||
path: d.mountpoint,
|
||||
options: {
|
||||
type: "pointer",
|
||||
packageId: d.dependencyId,
|
||||
volumeId: d.volumeId,
|
||||
subpath: d.subpath,
|
||||
readonly: d.readonly,
|
||||
},
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { setupManifest } from "../manifest/setupManifest"
|
||||
import { mountDependencies } from "../dependency/mountDependencies"
|
||||
import {
|
||||
BuildPath,
|
||||
setupDependencyMounts,
|
||||
} from "../dependency/setupDependencyMounts"
|
||||
|
||||
describe("mountDependencies", () => {
|
||||
const clnManifest = setupManifest({
|
||||
id: "cln",
|
||||
title: "",
|
||||
version: "1",
|
||||
releaseNotes: "",
|
||||
license: "",
|
||||
replaces: [],
|
||||
wrapperRepo: "",
|
||||
upstreamRepo: "",
|
||||
supportSite: "",
|
||||
marketingSite: "",
|
||||
donationUrl: null,
|
||||
description: {
|
||||
short: "",
|
||||
long: "",
|
||||
},
|
||||
assets: [],
|
||||
images: [],
|
||||
volumes: ["main"],
|
||||
alerts: {
|
||||
install: null,
|
||||
update: null,
|
||||
uninstall: null,
|
||||
restore: null,
|
||||
start: null,
|
||||
stop: null,
|
||||
},
|
||||
dependencies: {},
|
||||
})
|
||||
const clnManifestVolumes = clnManifest.volumes
|
||||
const lndManifest = setupManifest({
|
||||
id: "lnd",
|
||||
title: "",
|
||||
version: "1",
|
||||
releaseNotes: "",
|
||||
license: "",
|
||||
replaces: [],
|
||||
wrapperRepo: "",
|
||||
upstreamRepo: "",
|
||||
supportSite: "",
|
||||
marketingSite: "",
|
||||
donationUrl: null,
|
||||
description: {
|
||||
short: "",
|
||||
long: "",
|
||||
},
|
||||
assets: [],
|
||||
images: [],
|
||||
volumes: ["main2"],
|
||||
alerts: {
|
||||
install: null,
|
||||
update: null,
|
||||
uninstall: null,
|
||||
restore: null,
|
||||
start: null,
|
||||
stop: null,
|
||||
},
|
||||
dependencies: {},
|
||||
})
|
||||
clnManifest.id
|
||||
|
||||
test("Types work", () => {
|
||||
const dependencyMounts = setupDependencyMounts()
|
||||
.addPath({
|
||||
name: "root",
|
||||
volume: "main",
|
||||
path: "/",
|
||||
manifest: clnManifest,
|
||||
readonly: true,
|
||||
})
|
||||
.addPath({
|
||||
name: "root",
|
||||
manifest: lndManifest,
|
||||
volume: "main2",
|
||||
path: "/",
|
||||
readonly: true,
|
||||
})
|
||||
.addPath({
|
||||
name: "root",
|
||||
manifest: lndManifest,
|
||||
// @ts-expect-error Expect that main will throw because it is not in the thing
|
||||
volume: "main",
|
||||
path: "/",
|
||||
readonly: true,
|
||||
})
|
||||
.build()
|
||||
;() => {
|
||||
const test = mountDependencies(
|
||||
null as any,
|
||||
dependencyMounts,
|
||||
) satisfies Promise<{
|
||||
cln: {
|
||||
main: {
|
||||
root: string
|
||||
}
|
||||
}
|
||||
lnd: {
|
||||
main2: {
|
||||
root: string
|
||||
}
|
||||
}
|
||||
}>
|
||||
const test2 = mountDependencies(
|
||||
null as any,
|
||||
dependencyMounts.cln,
|
||||
) satisfies Promise<{
|
||||
main: { root: string }
|
||||
}>
|
||||
const test3 = mountDependencies(
|
||||
null as any,
|
||||
dependencyMounts.cln.main,
|
||||
) satisfies Promise<{
|
||||
root: string
|
||||
}>
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -500,7 +500,7 @@ export type Effects = {
|
||||
target: {
|
||||
packageId: string
|
||||
volumeId: string
|
||||
path: string
|
||||
subpath: string | null
|
||||
readonly: boolean
|
||||
}
|
||||
}): Promise<string>
|
||||
|
||||
@@ -32,15 +32,25 @@ export class Overlay {
|
||||
? `${this.rootfs}${path}`
|
||||
: `${this.rootfs}/${path}`
|
||||
if (options.type === "volume") {
|
||||
const subpath = options.subpath
|
||||
? options.subpath.startsWith("/")
|
||||
? options.subpath
|
||||
: `/${options.subpath}`
|
||||
: "/"
|
||||
await execFile("mount", [
|
||||
"--bind",
|
||||
`/media/startos/volumes/${options.id}`,
|
||||
`/media/startos/volumes/${options.id}${subpath}`,
|
||||
path,
|
||||
])
|
||||
} else if (options.type === "assets") {
|
||||
const subpath = options.subpath
|
||||
? options.subpath.startsWith("/")
|
||||
? options.subpath
|
||||
: `/${options.subpath}`
|
||||
: "/"
|
||||
await execFile("mount", [
|
||||
"--bind",
|
||||
`/media/startos/assets/${options.id}`,
|
||||
`/media/startos/assets/${options.id}${subpath}`,
|
||||
path,
|
||||
])
|
||||
} else if (options.type === "pointer") {
|
||||
@@ -140,17 +150,20 @@ export type MountOptions =
|
||||
export type MountOptionsVolume = {
|
||||
type: "volume"
|
||||
id: string
|
||||
subpath: string | null
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
export type MountOptionsAssets = {
|
||||
type: "assets"
|
||||
id: string
|
||||
subpath: string | null
|
||||
}
|
||||
|
||||
export type MountOptionsPointer = {
|
||||
type: "pointer"
|
||||
packageId: string
|
||||
volumeId: string
|
||||
path: string
|
||||
subpath: string | null
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
@@ -15,16 +15,6 @@ import {
|
||||
} from "../types"
|
||||
import { GetSystemSmtp } from "./GetSystemSmtp"
|
||||
import { GetStore, getStore } from "../store/getStore"
|
||||
import {
|
||||
MountDependenciesOut,
|
||||
mountDependencies,
|
||||
} from "../dependency/mountDependencies"
|
||||
import {
|
||||
ManifestId,
|
||||
VolumeName,
|
||||
NamedPath,
|
||||
Path,
|
||||
} from "../dependency/setupDependencyMounts"
|
||||
import { MultiHost, Scheme, SingleHost, StaticHost } from "../interfaces/Host"
|
||||
import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder"
|
||||
import { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
|
||||
@@ -93,15 +83,6 @@ export type Utils<
|
||||
single: (id: string) => SingleHost
|
||||
multi: (id: string) => MultiHost
|
||||
}
|
||||
mountDependencies: <
|
||||
In extends
|
||||
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||
| Record<VolumeName, Record<NamedPath, Path>>
|
||||
| Record<NamedPath, Path>
|
||||
| Path,
|
||||
>(
|
||||
value: In,
|
||||
) => Promise<MountDependenciesOut<In>>
|
||||
serviceInterface: {
|
||||
getOwn: (id: ServiceInterfaceId) => GetServiceInterface & WrapperOverWrite
|
||||
get: (opts: {
|
||||
@@ -306,16 +287,6 @@ export const createUtils = <
|
||||
},
|
||||
checkPortListening: checkPortListening.bind(null, effects),
|
||||
checkWebUrl: checkWebUrl.bind(null, effects),
|
||||
|
||||
mountDependencies: <
|
||||
In extends
|
||||
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||
| Record<VolumeName, Record<NamedPath, Path>>
|
||||
| Record<NamedPath, Path>
|
||||
| Path,
|
||||
>(
|
||||
value: In,
|
||||
) => mountDependencies(effects, value),
|
||||
}
|
||||
}
|
||||
function noop(): void {}
|
||||
|
||||
Reference in New Issue
Block a user