diff --git a/backups.ts b/backups.ts index d795352..cecad95 100644 --- a/backups.ts +++ b/backups.ts @@ -33,7 +33,7 @@ type BackupSet = { * srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP * }, { * srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}} - * ).build() + * ).build()q * ``` */ export class Backups { @@ -84,7 +84,35 @@ export class Backups { return this; } build() { - const createBackup: T.ExpectedExports.createBackup = async (effects) => { + const createBackup: T.ExpectedExports.createBackup = async ( + { effects }, + ) => { + const previousItems = (await effects.readDir({ + volumeId: Backups.BACKUP, + path: ".", + }).catch(() => [])).map((x) => `${x}`); + const backupPaths = this.backupSet.filter((x) => + x.dstVolume === Backups.BACKUP + ).map((x) => x.dstPath).map((x) => x.replace(/\.\/([^]*)\//, "$1")); + const filteredItems = previousItems.filter((x) => + backupPaths.indexOf(x) === -1 + ); + for ( + const itemToRemove of filteredItems + ) { + effects.error(`Trying to remove ${itemToRemove}`); + await effects.removeDir({ + volumeId: Backups.BACKUP, + path: itemToRemove, + }).catch(() => + effects.removeFile({ + volumeId: Backups.BACKUP, + path: itemToRemove, + }) + ).catch(() => { + effects.warn(`Failed to remove ${itemToRemove} from backup volume`); + }); + } for (const item of this.backupSet) { if (notEmptyPath(item.dstPath)) { await effects.createDir({ @@ -102,7 +130,9 @@ export class Backups { } return ok; }; - const restoreBackup: T.ExpectedExports.restoreBackup = async (effects) => { + const restoreBackup: T.ExpectedExports.restoreBackup = async ( + { effects }, + ) => { for (const item of this.backupSet) { if (notEmptyPath(item.srcPath)) { await effects.createDir({ diff --git a/compat/getConfig.ts b/compat/getConfig.ts index e93166a..d76c042 100644 --- a/compat/getConfig.ts +++ b/compat/getConfig.ts @@ -1,7 +1,7 @@ import { Config } from "../config_builder/config.ts"; import { YAML } from "../dependencies.ts"; import { matches } from "../dependencies.ts"; -import { ExpectedExports } from "../types.ts"; +import { LegacyExpectedExports as ExpectedExports } from "../types.ts"; import { ConfigSpec } from "../types/config-types.ts"; import { TypeFromProps, typeFromProps } from "../utils/propertiesMatcher.ts"; diff --git a/compat/migrations.ts b/compat/migrations.ts index 2b93af7..4500315 100644 --- a/compat/migrations.ts +++ b/compat/migrations.ts @@ -1,5 +1,7 @@ import { getConfig, setConfig } from "./mod.ts"; import * as T from "../types.ts"; + +import { LegacyExpectedExports as ExpectedExports } from "../types.ts"; import * as M from "../migrations.ts"; import * as util from "../util.ts"; import { EmVer } from "../emver-lite/mod.ts"; @@ -116,7 +118,7 @@ export async function initNoRepeat( export function fromMapping( migrations: M.MigrationMapping, currentVersion: string, -): T.ExpectedExports.migration { +): ExpectedExports.migration { const inner = M.fromMapping(migrations, currentVersion); return async (effects: T.Effects, version: string, direction?: unknown) => { await initNoRepeat( diff --git a/compat/mod.ts b/compat/mod.ts index 4eae78a..7efa6ba 100644 --- a/compat/mod.ts +++ b/compat/mod.ts @@ -1,4 +1,4 @@ -export { properties } from "./properties.ts"; +export { noPropertiesFound, properties, propertiesv2 } from "./properties.ts"; export { setConfig } from "./setConfig.ts"; export { getConfig, getConfigAndMatcher } from "./getConfig.ts"; export * as migrations from "./migrations.ts"; diff --git a/compat/properties.ts b/compat/properties.ts index eb6f6ca..26deff5 100644 --- a/compat/properties.ts +++ b/compat/properties.ts @@ -1,10 +1,16 @@ import { YAML } from "../dependencies.ts"; import { exists } from "../util.ts"; -import { Effects, ExpectedExports, Properties, ResultType } from "../types.ts"; +import { + Effects, + ExpectedExports, + LegacyExpectedExports, + Properties, + ResultType, +} from "../types.ts"; // deno-lint-ignore no-explicit-any const asResult = (result: any) => ({ result: result as Properties }); -const noPropertiesFound: ResultType = { +export const noPropertiesFound: ResultType = { result: { version: 2, data: { @@ -26,7 +32,7 @@ const noPropertiesFound: ResultType = { * @param effects * @returns */ -export const properties: ExpectedExports.properties = async ( +export const properties: LegacyExpectedExports.properties = async ( effects: Effects, ) => { if ( @@ -40,3 +46,24 @@ export const properties: ExpectedExports.properties = async ( volumeId: "main", }).then(YAML.parse).then(asResult); }; +/** + * Default will pull from a file (start9/stats.yaml) expected to be made on the main volume + * Assumption: start9/stats.yaml is created by some process + * Throws: stats.yaml isn't yaml + * @param effects + * @returns + */ +export const propertiesv2: ExpectedExports.properties = async ( + { 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); +}; diff --git a/compat/setConfig.ts b/compat/setConfig.ts index 49c0594..a4f68b4 100644 --- a/compat/setConfig.ts +++ b/compat/setConfig.ts @@ -1,5 +1,9 @@ import { YAML } from "../dependencies.ts"; -import { DependsOn, Effects, ExpectedExports } from "../types.ts"; +import { + DependsOn, + Effects, + LegacyExpectedExports as ExpectedExports, +} from "../types.ts"; import { okOf } from "../util.ts"; /** diff --git a/config_tools/setup_config_export.ts b/config_tools/setup_config_export.ts index a0ca3ca..1a585fd 100644 --- a/config_tools/setup_config_export.ts +++ b/config_tools/setup_config_export.ts @@ -15,7 +15,7 @@ export function setupConfigExports(options: { }) { const validator = options.spec.validator(); return { - setConfig: (async (effects: Effects, config: unknown) => { + setConfig: (async ({ effects, input: config }) => { if (!validator.test(config)) { await effects.error(String(validator.errorMessage(config))); return { error: "Set config type error for config" }; @@ -26,7 +26,7 @@ export function setupConfigExports(options: { "depends-on": options.dependsOn, }); }) as ExpectedExports.setConfig, - getConfig: (async (effects: Effects) => { + getConfig: (async ({ effects }) => { return okOf({ spec: options.spec.build(), config: nullIfEmpty(await options.read(effects)), diff --git a/migrations.ts b/migrations.ts index 2aab863..f8b41fd 100644 --- a/migrations.ts +++ b/migrations.ts @@ -1,6 +1,36 @@ import { types as T } from "./mod.ts"; import { EmVer } from "./emver-lite/mod.ts"; import { matches } from "./dependencies.ts"; +import { LegacyExpectedExports as ExpectedExports } from "./types.ts"; + +export class Migration2 { + version: Version; + up: ( + effects: T.Effects, + ) => Promise; + down: ( + effects: T.Effects, + ) => Promise; + constructor(options: { + version: Version; + up: ( + effects: T.Effects, + ) => Promise; + down: ( + effects: T.Effects, + ) => Promise; + }) { + this.version = options.version; + this.up = options.up; + this.down = options.down; + } +} +export class MigrationMapping2[]> { + constructor( + readonly migrations: Migrations, + ) { + } +} export type MigrationFn = ( effects: T.Effects, @@ -26,7 +56,7 @@ export type MigrationMapping = { export function fromMapping( migrations: MigrationMapping, currentVersion: string, -): T.ExpectedExports.migration { +): ExpectedExports.migration { const directionShape = matches.literals("from", "to"); return async ( effects: T.Effects, diff --git a/mod.ts b/mod.ts index c9a2044..f7b65d9 100644 --- a/mod.ts +++ b/mod.ts @@ -1,7 +1,7 @@ export { matches, YAML } from "./dependencies.ts"; export * as types from "./types.ts"; export * as compat from "./compat/mod.ts"; -export * as migrations from "./migrations.ts"; +// export * as migrations from "./migrations.ts"; export * as healthUtil from "./healthUtil.ts"; export * as util from "./util.ts"; export * as configBuilder from "./config_builder/mod.ts"; diff --git a/types.ts b/types.ts index 66334b1..ce6f97a 100644 --- a/types.ts +++ b/types.ts @@ -3,8 +3,86 @@ import { ConfigSpec } from "./types/config-types.ts"; // deno-lint-ignore no-namespace export namespace ExpectedExports { + // deno-lint-ignore no-unused-labels version: - 2; + 1; + /** 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 = (options: { + effects: Effects; + input: Record; + }) => Promise>; + /** 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 = ( + options: { effects: Effects }, + ) => Promise>; + /** These are how we make sure the our dependency configurations are valid and if not how to fix them. */ + export type dependencies = Dependencies; + /** For backing up service data though the embassyOS UI */ + export type createBackup = ( + options: { effects: Effects }, + ) => Promise>; + /** For restoring service data that was previously backed up using the embassyOS UI create backup flow. Backup restores are also triggered via the embassyOS UI, or doing a system restore flow during setup. */ + export type restoreBackup = ( + options: { + effects: Effects; + }, + ) => Promise>; + /** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */ + export type properties = ( + options: { + effects: Effects; + }, + ) => Promise>; + + /** Health checks are used to determine if the service is working properly after starting + * A good use case is if we are using a web server, seeing if we can get to the web server. + */ + export type health = { + /** Should be the health check id */ + [id: string]: (options: { + effects: Effects; + input: TimeMs; + }) => Promise>; + }; + + /** + * Migrations are used when we are changing versions when updating/ downgrading. + * There are times that we need to move files around, and do other operations during a migration. + */ + export type migration = ( + options: { + effects: Effects; + input: VersionString; + args: unknown[]; + }, + ) => Promise>; + + /** + * Actions are used so we can effect the service, like deleting a directory. + * One old use case is to add a action where we add a file, that will then be run during the + * service starting, and that file would indicate that it would rescan all the data. + */ + export type action = { + [id: string]: (options: { + effects: Effects; + input?: Record; + }) => Promise>; + }; + + /** + * This is the entrypoint for the main container. Used to start up something like the service that the + * package represents, like running a bitcoind in a bitcoind-wrapper. + */ + export type main = ( + options: { effects: Effects; started(): null }, + ) => Promise>; +} +export type TimeMs = number; +export type VersionString = string; + +/** @deprecated */ +// deno-lint-ignore no-namespace +export namespace LegacyExpectedExports { /** 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, @@ -108,7 +186,7 @@ export type Effects = { chown(input: { volumeId: string; path: string; uid: string }): Promise; chmod(input: { volumeId: string; path: string; mode: string }): Promise; - sleep(timeMs: number): Promise; + sleep(timeMs: TimeMs): Promise; /** Log at the trace level */ trace(whatToPrint: string): void;