mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-30 20:24:47 +00:00
feat: Initial of the sdk
This commit is contained in:
29
compat/getConfig.ts
Normal file
29
compat/getConfig.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
import { YAML } from "../dependencies.ts";
|
||||||
|
import { matches } from "../dependencies.ts";
|
||||||
|
import { ExpectedExports } from "../types.ts";
|
||||||
|
import { ConfigSpec } from "../types.ts";
|
||||||
|
|
||||||
|
const { any, string, dictionary } = matches;
|
||||||
|
|
||||||
|
const matchConfig = dictionary([string, any]);
|
||||||
|
|
||||||
|
export const getConfig = (spec: ConfigSpec): ExpectedExports.getConfig => async (effects) => {
|
||||||
|
const config = await effects
|
||||||
|
.readFile({
|
||||||
|
path: "start9/config.yaml",
|
||||||
|
volumeId: "main",
|
||||||
|
})
|
||||||
|
.then((x) => YAML.parse(x))
|
||||||
|
.then((x) => matchConfig.unsafeCast(x))
|
||||||
|
.catch((e) => {
|
||||||
|
effects.info(`Got error ${e} while trying to read the config`);
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
config,
|
||||||
|
spec,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
3
compat/mod.ts
Normal file
3
compat/mod.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { properties } from "./properties.ts";
|
||||||
|
export { setConfig } from "./setConfig.ts";
|
||||||
|
export { getConfig } from "./getConfig.ts";
|
||||||
38
compat/properties.ts
Normal file
38
compat/properties.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { YAML } from "../dependencies.ts";
|
||||||
|
import { exists } from "../exists.ts";
|
||||||
|
import { ResultType, Properties, ExpectedExports, Effects } from "../types.ts";
|
||||||
|
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
const asResult = (result: any) => ({ result: result as Properties })
|
||||||
|
const noPropertiesFound: ResultType<Properties> = {
|
||||||
|
result: {
|
||||||
|
version: 2,
|
||||||
|
data: {
|
||||||
|
"Not Ready": {
|
||||||
|
type: "string",
|
||||||
|
value: "Could not find properties. The service might still be starting",
|
||||||
|
qr: false,
|
||||||
|
copyable: false,
|
||||||
|
masked: false,
|
||||||
|
description: "Fallback Message When Properties could not be found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
/**
|
||||||
|
* Default will pull from a file (start9/stats.yaml) expected to be made on the main volume
|
||||||
|
* @param effects
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const properties: ExpectedExports.properties = async (
|
||||||
|
effects: Effects,
|
||||||
|
) => {
|
||||||
|
if (await exists(effects, { path: "start9/stats.yaml", volumeId: "main" }) === false) {
|
||||||
|
return noPropertiesFound;
|
||||||
|
}
|
||||||
|
return await effects.readFile({
|
||||||
|
path: "start9/stats.yaml",
|
||||||
|
volumeId: "main",
|
||||||
|
}).then(YAML.parse).then(asResult)
|
||||||
|
};
|
||||||
31
compat/setConfig.ts
Normal file
31
compat/setConfig.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { YAML } from "../dependencies.ts";
|
||||||
|
import { ExpectedExports, Effects, Config, SetResult, DependsOn } from "../types.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will set the config to the default start9/config.yaml
|
||||||
|
* @param effects
|
||||||
|
* @param newConfig Config to be written to start9/config.yaml
|
||||||
|
* @param depends_on This would be the depends on for condition depends_on
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const setConfig: ExpectedExports.setConfig = async (
|
||||||
|
effects: Effects,
|
||||||
|
newConfig: Config,
|
||||||
|
depends_on: DependsOn = {}
|
||||||
|
) => {
|
||||||
|
await effects.createDir({
|
||||||
|
path: "start9",
|
||||||
|
volumeId: "main",
|
||||||
|
});
|
||||||
|
await effects.writeFile({
|
||||||
|
path: "start9/config.yaml",
|
||||||
|
toWrite: YAML.stringify(newConfig),
|
||||||
|
volumeId: "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result: SetResult = {
|
||||||
|
signal: "SIGTERM",
|
||||||
|
"depends-on": {},
|
||||||
|
};
|
||||||
|
return { result, depends_on };
|
||||||
|
};
|
||||||
2
dependencies.ts
Normal file
2
dependencies.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * as matches from "https://deno.land/x/ts_matches@v5.1.8/mod.ts";
|
||||||
|
export * as YAML from "https://deno.land/std@0.140.0/encoding/yaml.ts";
|
||||||
4
exists.ts
Normal file
4
exists.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { Effects } from "./types.ts";
|
||||||
|
|
||||||
|
/** Used to check if the file exists before hand */
|
||||||
|
export const exists = (effects: Effects, props: { path: string, volumeId: string }) => effects.metadata(props).then(_ => true, _ => false);
|
||||||
5
mod.ts
Normal file
5
mod.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export { matches, YAML } from './dependencies.ts'
|
||||||
|
export * as types from './types.ts'
|
||||||
|
|
||||||
|
export { exists } from './exists.ts'
|
||||||
|
export * as compat from './compat/mod.ts'
|
||||||
385
types.ts
Normal file
385
types.ts
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
// deno-lint-ignore no-namespace
|
||||||
|
export namespace ExpectedExports {
|
||||||
|
/** Set configuration is called after we have modified and saved the configuration in the embassy ui. Use this to make a file for the docker to read from for configuration. */
|
||||||
|
export type setConfig = (
|
||||||
|
effects: Effects,
|
||||||
|
input: Config,
|
||||||
|
) => Promise<ResultType<SetResult>>;
|
||||||
|
/** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */
|
||||||
|
export type getConfig = (effects: Effects) => Promise<ResultType<ConfigRes>>;
|
||||||
|
/** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
||||||
|
export type dependencies = Dependencies;
|
||||||
|
/** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */
|
||||||
|
export type properties = (
|
||||||
|
effects: Effects,
|
||||||
|
) => Promise<ResultType<Properties>>;
|
||||||
|
|
||||||
|
export type health = {
|
||||||
|
/** Should be the health check id */
|
||||||
|
[id: string]: (
|
||||||
|
effects: Effects,
|
||||||
|
dateMs: number,
|
||||||
|
) => Promise<ResultType<null | void>>;
|
||||||
|
};
|
||||||
|
export type migration = (
|
||||||
|
effects: Effects,
|
||||||
|
version: string,
|
||||||
|
) => Promise<ResultType<MigrationRes>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Used to reach out from the pure js runtime */
|
||||||
|
export type Effects = {
|
||||||
|
/** Usable when not sandboxed */
|
||||||
|
writeFile(
|
||||||
|
input: { path: string; volumeId: string; toWrite: string },
|
||||||
|
): Promise<void>;
|
||||||
|
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
||||||
|
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
|
||||||
|
/** Create a directory. Usable when not sandboxed */
|
||||||
|
createDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||||
|
/** Remove a directory. Usable when not sandboxed */
|
||||||
|
removeDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||||
|
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
||||||
|
|
||||||
|
/** Write a json file into an object. Usable when not sandboxed */
|
||||||
|
writeJsonFile(
|
||||||
|
input: { volumeId: string; path: string; toWrite: Record<string, unknown> },
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/** Read a json file into an object */
|
||||||
|
readJsonFile(input: { volumeId: string; path: string }): Promise<Record<string, unknown>>;
|
||||||
|
|
||||||
|
/** Log at the trace level */
|
||||||
|
trace(whatToPrint: string): void;
|
||||||
|
/** Log at the warn level */
|
||||||
|
warn(whatToPrint: string): void;
|
||||||
|
/** Log at the error level */
|
||||||
|
error(whatToPrint: string): void;
|
||||||
|
/** Log at the debug level */
|
||||||
|
debug(whatToPrint: string): void;
|
||||||
|
/** Log at the info level */
|
||||||
|
info(whatToPrint: string): void;
|
||||||
|
|
||||||
|
/** Sandbox mode lets us read but not write */
|
||||||
|
is_sandboxed(): boolean;
|
||||||
|
|
||||||
|
exists(input: { volumeId: string; path: string }): Promise<boolean>;
|
||||||
|
|
||||||
|
};
|
||||||
|
export type Metadata = {
|
||||||
|
fileType: string,
|
||||||
|
isDir: boolean,
|
||||||
|
isFile: boolean,
|
||||||
|
isSymlink: boolean,
|
||||||
|
len: number,
|
||||||
|
modified?: Date,
|
||||||
|
accessed?: Date,
|
||||||
|
created?: Date,
|
||||||
|
readonly: boolean,
|
||||||
|
uid: number,
|
||||||
|
gid: number,
|
||||||
|
mode: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MigrationRes = {
|
||||||
|
configured: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ActionResult = {
|
||||||
|
version: "0";
|
||||||
|
message: string;
|
||||||
|
value?: string;
|
||||||
|
copyable: boolean;
|
||||||
|
qr: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigRes = {
|
||||||
|
/** This should be the previous config, that way during set config we start with the previous */
|
||||||
|
config?: Config;
|
||||||
|
/** Shape that is describing the form in the ui */
|
||||||
|
spec: ConfigSpec;
|
||||||
|
};
|
||||||
|
export type Config = {
|
||||||
|
[propertyName: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigSpec = {
|
||||||
|
/** Given a config value, define what it should render with the following spec */
|
||||||
|
[configValue: string]: ValueSpecAny;
|
||||||
|
};
|
||||||
|
export type WithDefault<T, Default> = T & {
|
||||||
|
default?: Default;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WithDescription<T> = T & {
|
||||||
|
description?: string;
|
||||||
|
name: string;
|
||||||
|
warning?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListSpec<T> = {
|
||||||
|
spec: T;
|
||||||
|
range: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Tag<T extends string, V> = V & {
|
||||||
|
type: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Subtype<T extends string, V> = V & {
|
||||||
|
subtype: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Target<T extends string, V> = V & {
|
||||||
|
target: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UniqueBy =
|
||||||
|
| {
|
||||||
|
any: UniqueBy[];
|
||||||
|
}
|
||||||
|
| string
|
||||||
|
| null;
|
||||||
|
|
||||||
|
export type WithNullable<T> = T & {
|
||||||
|
nullable: boolean;
|
||||||
|
};
|
||||||
|
export type DefaultString =
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
/** The chars available for the randome generation */
|
||||||
|
charset?: string;
|
||||||
|
/** Length that we generate to */
|
||||||
|
len: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ValueSpecString =
|
||||||
|
& (
|
||||||
|
// deno-lint-ignore ban-types
|
||||||
|
| {}
|
||||||
|
| {
|
||||||
|
pattern: string;
|
||||||
|
"pattern-description": string;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
& {
|
||||||
|
copyable?: boolean;
|
||||||
|
masked?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
};
|
||||||
|
export type ValueSpecNumber = {
|
||||||
|
/** Something like [3,6] or [0, *) */
|
||||||
|
range?: string;
|
||||||
|
integral?: boolean;
|
||||||
|
/** Used a description of the units */
|
||||||
|
units?: string;
|
||||||
|
placeholder?: number;
|
||||||
|
};
|
||||||
|
export type ValueSpecBoolean = Record<string, unknown>;
|
||||||
|
export type ValueSpecAny =
|
||||||
|
| Tag<"boolean", WithDescription<WithDefault<ValueSpecBoolean, boolean>>>
|
||||||
|
| Tag<
|
||||||
|
"string",
|
||||||
|
WithDescription<WithDefault<WithNullable<ValueSpecString>, DefaultString>>
|
||||||
|
>
|
||||||
|
| Tag<
|
||||||
|
"number",
|
||||||
|
WithDescription<WithDefault<WithNullable<ValueSpecNumber>, number>>
|
||||||
|
>
|
||||||
|
| Tag<
|
||||||
|
"enum",
|
||||||
|
WithDescription<
|
||||||
|
WithDefault<
|
||||||
|
{
|
||||||
|
values: string[];
|
||||||
|
"value-names": {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
string
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
| Tag<"list", ValueSpecList>
|
||||||
|
| Tag<"object", WithDescription<WithDefault<ValueSpecObject, Config>>>
|
||||||
|
| Tag<"union", WithDescription<WithDefault<ValueSpecUnion, string>>>
|
||||||
|
| Tag<
|
||||||
|
"pointer",
|
||||||
|
WithDescription<
|
||||||
|
| Subtype<
|
||||||
|
"package",
|
||||||
|
| Target<
|
||||||
|
"tor-key",
|
||||||
|
{
|
||||||
|
"package-id": string;
|
||||||
|
interface: string;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
| Target<
|
||||||
|
"tor-address",
|
||||||
|
{
|
||||||
|
"package-id": string;
|
||||||
|
interface: string;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
| Target<
|
||||||
|
"lan-address",
|
||||||
|
{
|
||||||
|
"package-id": string;
|
||||||
|
interface: string;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
| Target<
|
||||||
|
"config",
|
||||||
|
{
|
||||||
|
"package-id": string;
|
||||||
|
selector: string;
|
||||||
|
multi: boolean;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>
|
||||||
|
| Subtype<"system", Record<string, unknown>>
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
export type ValueSpecUnion = {
|
||||||
|
/** What tag for the specification, for tag unions */
|
||||||
|
tag: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
"variant-names": {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** The possible enum values */
|
||||||
|
variants: {
|
||||||
|
[key: string]: ConfigSpec;
|
||||||
|
};
|
||||||
|
"display-as"?: string;
|
||||||
|
"unique-by"?: UniqueBy;
|
||||||
|
};
|
||||||
|
export type ValueSpecObject = {
|
||||||
|
spec: ConfigSpec;
|
||||||
|
"display-as"?: string;
|
||||||
|
"unique-by"?: UniqueBy;
|
||||||
|
};
|
||||||
|
export type ValueSpecList =
|
||||||
|
| Subtype<
|
||||||
|
"boolean",
|
||||||
|
WithDescription<WithDefault<ListSpec<ValueSpecBoolean>, boolean[]>>
|
||||||
|
>
|
||||||
|
| Subtype<
|
||||||
|
"string",
|
||||||
|
WithDescription<WithDefault<ListSpec<ValueSpecString>, string[]>>
|
||||||
|
>
|
||||||
|
| Subtype<
|
||||||
|
"number",
|
||||||
|
WithDescription<WithDefault<ListSpec<ValueSpecNumber>, number[]>>
|
||||||
|
>
|
||||||
|
| Subtype<
|
||||||
|
"enum",
|
||||||
|
WithDescription<
|
||||||
|
WithDefault<
|
||||||
|
ListSpec<
|
||||||
|
ValueSpecEnum
|
||||||
|
>,
|
||||||
|
string[]
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
| Subtype<
|
||||||
|
"object",
|
||||||
|
WithDescription<WithDefault<ListSpec<ValueSpecObject>, Record<string, unknown>[]>>
|
||||||
|
>
|
||||||
|
| Subtype<
|
||||||
|
"union",
|
||||||
|
WithDescription<WithDefault<ListSpec<ValueSpecUnion>, string[]>>
|
||||||
|
>;
|
||||||
|
export type ValueSpecEnum = {
|
||||||
|
values: string[];
|
||||||
|
"value-names": { [key: string]: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SetResult = {
|
||||||
|
/** These are the unix process signals */
|
||||||
|
signal:
|
||||||
|
| "SIGTERM"
|
||||||
|
| "SIGHUP"
|
||||||
|
| "SIGINT"
|
||||||
|
| "SIGQUIT"
|
||||||
|
| "SIGILL"
|
||||||
|
| "SIGTRAP"
|
||||||
|
| "SIGABRT"
|
||||||
|
| "SIGBUS"
|
||||||
|
| "SIGFPE"
|
||||||
|
| "SIGKILL"
|
||||||
|
| "SIGUSR1"
|
||||||
|
| "SIGSEGV"
|
||||||
|
| "SIGUSR2"
|
||||||
|
| "SIGPIPE"
|
||||||
|
| "SIGALRM"
|
||||||
|
| "SIGSTKFLT"
|
||||||
|
| "SIGCHLD"
|
||||||
|
| "SIGCONT"
|
||||||
|
| "SIGSTOP"
|
||||||
|
| "SIGTSTP"
|
||||||
|
| "SIGTTIN"
|
||||||
|
| "SIGTTOU"
|
||||||
|
| "SIGURG"
|
||||||
|
| "SIGXCPU"
|
||||||
|
| "SIGXFSZ"
|
||||||
|
| "SIGVTALRM"
|
||||||
|
| "SIGPROF"
|
||||||
|
| "SIGWINCH"
|
||||||
|
| "SIGIO"
|
||||||
|
| "SIGPWR"
|
||||||
|
| "SIGSYS"
|
||||||
|
| "SIGEMT"
|
||||||
|
| "SIGINFO";
|
||||||
|
"depends-on": DependsOn
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DependsOn = {
|
||||||
|
[packageId: string]: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KnownError = { error: string } | {
|
||||||
|
"error-code": [number, string] | readonly [number, string];
|
||||||
|
};
|
||||||
|
export type ResultType<T> = KnownError | { result: T };
|
||||||
|
|
||||||
|
export type PackagePropertiesV2 = {
|
||||||
|
[name: string]: PackagePropertyObject | PackagePropertyString;
|
||||||
|
};
|
||||||
|
export type PackagePropertyString = {
|
||||||
|
type: "string";
|
||||||
|
description?: string;
|
||||||
|
value: string;
|
||||||
|
/** Let's the ui make this copyable button */
|
||||||
|
copyable?: boolean;
|
||||||
|
/** Let the ui create a qr for this field */
|
||||||
|
qr?: boolean;
|
||||||
|
/** Hiding the value unless toggled off for field */
|
||||||
|
masked?: boolean;
|
||||||
|
};
|
||||||
|
export type PackagePropertyObject = {
|
||||||
|
value: PackagePropertiesV2;
|
||||||
|
type: "object";
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Properties = {
|
||||||
|
version: 2;
|
||||||
|
data: PackagePropertiesV2;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Dependencies = {
|
||||||
|
/** Id is the id of the package, should be the same as the manifest */
|
||||||
|
[id: string]: {
|
||||||
|
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
||||||
|
check(effects: Effects, input: Config): Promise<ResultType<void | null>>;
|
||||||
|
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
|
||||||
|
autoConfigure(effects: Effects, input: Config): Promise<ResultType<Config>>;
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user