move mounts to daemons constructor

This commit is contained in:
Aiden McClelland
2024-03-18 15:14:36 -06:00
parent b6fe0be1b2
commit 3c6c0b253d
9 changed files with 149 additions and 276 deletions

View File

@@ -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>,

View File

@@ -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>
}

View File

@@ -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()
}

View File

@@ -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
View 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,
},
})),
)
}
}

View File

@@ -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
}>
}
})
})

View File

@@ -500,7 +500,7 @@ export type Effects = {
target: {
packageId: string
volumeId: string
path: string
subpath: string | null
readonly: boolean
}
}): Promise<string>

View File

@@ -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
}

View File

@@ -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 {}