From 5f358c2fa2851bd9c85d1a51a97c86262c516003 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Thu, 4 Dec 2025 16:14:10 -0500 Subject: [PATCH 1/7] script v3 changes --- eshtek/apps.ts | 12 +++++++++++- eshtek/errors.ts | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index fac1756..023d6c4 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -165,7 +165,17 @@ interface AppsInstallScriptV2 { app_values: Record; } -export type AppsInstallScript = AppsInstallScriptV1 | AppsInstallScriptV2; +interface AppsInstallScriptV3 extends Omit { + version: 3; + script: { + version: string; + updateCompatibility?: string; + changeLog?: string; + }; + requirements: AppRequirements; // Required in V3 +} + +export type AppsInstallScript = AppsInstallScriptV1 | AppsInstallScriptV2 | AppsInstallScriptV3; export interface InstallScriptCuration { name: string; diff --git a/eshtek/errors.ts b/eshtek/errors.ts index 85e1876..d639e4f 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -29,6 +29,10 @@ export enum GlobalErrorCode { PROCESS_INSTALL_SCRIPT_INVALID_MEMORY_VALUE = 'PROCESS_INSTALL_SCRIPT_INVALID_MEMORY_VALUE', PROCESS_INSTALL_SCRIPT_MISSING_QUESTION_RESPONSE = 'PROCESS_INSTALL_SCRIPT_MISSING_QUESTION_RESPONSE', PROCESS_INSTALL_SCRIPT_INVALID_QUESTION_RESPONSE_TYPE = 'PROCESS_INSTALL_SCRIPT_INVALID_QUESTION_RESPONSE_TYPE', + PROCESS_INSTALL_SCRIPT_MISSING_SCRIPT_METADATA = 'PROCESS_INSTALL_SCRIPT_MISSING_SCRIPT_METADATA', + PROCESS_INSTALL_SCRIPT_MISSING_REQUIREMENTS = 'PROCESS_INSTALL_SCRIPT_MISSING_REQUIREMENTS', + PROCESS_INSTALL_SCRIPT_INVALID_SCRIPT_VERSION = 'PROCESS_INSTALL_SCRIPT_INVALID_SCRIPT_VERSION', + PROCESS_INSTALL_SCRIPT_INVALID_IF_CONDITION = 'PROCESS_INSTALL_SCRIPT_INVALID_IF_CONDITION', RUN_INSTALL_SCRIPT_UNSUPPORTED_VERSION = 'RUN_INSTALL_SCRIPT_UNSUPPORTED_VERSION', DDP_CLIENT_DISCONNECTED = 'DDP_CLIENT_DISCONNECTED', TASK_FAILED_STALE = "TASK_FAILED_STALE" From d48f87e8339c145155ee9463ee476722411adf79 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 7 Dec 2025 21:41:14 -0500 Subject: [PATCH 2/7] script v3 continuation / app updates / analysis wip --- eshtek/apps.ts | 1 + eshtek/errors.ts | 8 ++++++- eshtek/routes.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ eshtek/tasks.ts | 24 +++++++++++++++++++- 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 023d6c4..8a21f3a 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -89,6 +89,7 @@ export interface AppInfo extends AppBasics { status: AppState; url_webui: string; upgradeAvailable: boolean; + updatedScriptAvailable: boolean; latestVersion: string; } export interface AppInfoDetailed extends AppInfo { diff --git a/eshtek/errors.ts b/eshtek/errors.ts index d639e4f..d1c3ee4 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -35,6 +35,12 @@ export enum GlobalErrorCode { PROCESS_INSTALL_SCRIPT_INVALID_IF_CONDITION = 'PROCESS_INSTALL_SCRIPT_INVALID_IF_CONDITION', RUN_INSTALL_SCRIPT_UNSUPPORTED_VERSION = 'RUN_INSTALL_SCRIPT_UNSUPPORTED_VERSION', DDP_CLIENT_DISCONNECTED = 'DDP_CLIENT_DISCONNECTED', - TASK_FAILED_STALE = "TASK_FAILED_STALE" + TASK_FAILED_STALE = "TASK_FAILED_STALE", + UPDATE_APP_NOT_FOUND = 'UPDATE_APP_NOT_FOUND', + UPDATE_APP_NOT_IN_CATALOG = 'UPDATE_APP_NOT_IN_CATALOG', + UPDATE_APP_INVALID_SCRIPT = 'UPDATE_APP_INVALID_SCRIPT', + UPDATE_APP_INVALID_CATALOG_SCRIPT = 'UPDATE_APP_INVALID_CATALOG_SCRIPT', + UPDATE_APP_ANALYSIS_FAILED = 'UPDATE_APP_ANALYSIS_FAILED', + UPDATE_APP_FAILED = 'UPDATE_APP_FAILED' } diff --git a/eshtek/routes.ts b/eshtek/routes.ts index c9dc908..7e5f90b 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -122,6 +122,63 @@ export interface RequestAppInstall { questionResponses?: Record; } +export interface RequestAppUpdate { + installScript?: string; + questionResponses?: Record; +} + +export interface RequestAppUpdateAnalysis { + installScript?: string; + questionResponses?: Record; +} + +export enum ResourceChangeType { + STORAGE = 'storage', + NETWORK = 'network', + ENVIRONMENT = 'environment', + RESOURCE = 'resource', + PERMISSION = 'permission', + CONFIG = 'config' +} + +export enum ResourceChangeAction { + CREATE = 'create', + UPDATE = 'update', + DELETE = 'delete', + NO_OP = 'no-op' +} + +export interface ResponseResourceChanges { + resourceChanges: Array<{ + address: string; + type: ResourceChangeType; + change: { + actions: Array; + before: any; + after: any; + }; + }>; + planSummary: { + totalChanges: number; + changesByAction: { + create: number; + update: number; + delete: number; + noOp: number; + }; + }; + validation: { + updateCompatible: boolean; + compatibilityConstraint?: string; + warnings: string[]; + }; + versionInfo: { + current: string; + target: string; + changelog?: string; + }; +} + export interface RequestAppDelete { deleteData?: boolean; } diff --git a/eshtek/tasks.ts b/eshtek/tasks.ts index 4d5a83f..52f421e 100644 --- a/eshtek/tasks.ts +++ b/eshtek/tasks.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // tasks.ts +import type { ResponseResourceChanges } from './routes'; import type { DiskType } from '../truenas/webui/enums/disk-type.enum'; export interface HexTaskBase { @@ -115,6 +116,27 @@ type HexTaskMeta = + R extends HexTaskAppUpdateReason ? HexTaskAppUpdateDataMap[R] : HexTaskAppUpdateDataMap[HexTaskAppUpdateReason]; + export type HexTaskDataMap = { [HexTaskType.RESTART]: HexTaskMeta; [HexTaskType.SHUTDOWN]: HexTaskMeta; @@ -137,7 +159,7 @@ export type HexTaskDataMap = { [HexTaskType.APP_INSTALL]: HexTaskMeta; [HexTaskType.APP_UNINSTALL]: HexTaskMeta; [HexTaskType.APP_UPGRADE]: HexTaskMeta; - [HexTaskType.APP_UPDATE]: HexTaskMeta; + [HexTaskType.APP_UPDATE]: HexTaskMeta; [HexTaskType.PREFERENCE_LOCATION_PATH_MIGRATION]: HexTaskMeta; [HexTaskType.DRIVE_REPLACE]: HexTaskMeta; [HexTaskType.DOCKER_UPDATE]: HexTaskMeta; From 9464795ca3e1424e1e6f8907ed5ffff521bf4b0b Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Mon, 8 Dec 2025 08:56:22 -0500 Subject: [PATCH 3/7] convert types for easier use in config get endpoint --- eshtek/apps.ts | 5 +++++ eshtek/routes.ts | 17 +++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 8a21f3a..38c6afb 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -178,6 +178,11 @@ interface AppsInstallScriptV3 extends Omit; +} + export interface InstallScriptCuration { name: string; url: string; diff --git a/eshtek/routes.ts b/eshtek/routes.ts index 7e5f90b..c94437e 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -1,4 +1,5 @@ import { GlobalErrorCode } from './errors'; +import type { AppConfiguration } from './apps'; import type { LocationPreferenceId } from './preferences'; import type { ServerPool, ServerPoolNew, ServerUser, ServerUserType } from './server'; import type { VMBasics, VMSettings } from './vms'; @@ -115,22 +116,14 @@ export interface RequestDockerUpdatePool { poolName: string; } -export interface RequestAppInstall { +export interface RequestAppInstall extends AppConfiguration { id: string; train?: 'community' | 'stable'; - installScript?: string; - questionResponses?: Record; } -export interface RequestAppUpdate { - installScript?: string; - questionResponses?: Record; -} +export interface RequestAppUpdate extends AppConfiguration {} +export interface RequestAppUpdateAnalysis extends AppConfiguration {} -export interface RequestAppUpdateAnalysis { - installScript?: string; - questionResponses?: Record; -} export enum ResourceChangeType { STORAGE = 'storage', @@ -173,7 +166,7 @@ export interface ResponseResourceChanges { warnings: string[]; }; versionInfo: { - current: string; + current?: string; target: string; changelog?: string; }; From 687734b59ad176c2d34662975f38fd1697d41e71 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 9 Dec 2025 15:48:19 -0500 Subject: [PATCH 4/7] interface changes to support gpu macro implementation --- eshtek/apps-schema.ts | 136 ++++++++++-------- eshtek/routes.ts | 1 + eshtek/server-schema.ts | 15 +- eshtek/tasks.ts | 2 +- .../api/api-call-directory.interface.ts | 2 + truenas/webui/interfaces/gpu.interface.ts | 20 +++ ts-to-zod.config.js | 10 +- 7 files changed, 118 insertions(+), 68 deletions(-) create mode 100644 truenas/webui/interfaces/gpu.interface.ts diff --git a/eshtek/apps-schema.ts b/eshtek/apps-schema.ts index 0beecd0..887efa6 100644 --- a/eshtek/apps-schema.ts +++ b/eshtek/apps-schema.ts @@ -7,8 +7,11 @@ import { AppsError, AppsWarning, AppsActions, + InstallationQuestionType, } from "./apps"; +import { fileAccessSchema } from "./server-schema"; + export const appJobActionSchema = z.nativeEnum(AppJobAction); export const appBasicsSchema = z.object({ @@ -24,6 +27,11 @@ export const appSpecSchema = z.nativeEnum(AppSpec); export const appPermissionSchema = z.nativeEnum(AppPermission); +export const appMaintainerSchema = z.object({ + name: z.string(), + email: z.string(), +}); + export const appsErrorSchema = z.nativeEnum(AppsError); export const appsWarningSchema = z.nativeEnum(AppsWarning); @@ -37,48 +45,29 @@ export const appsHealthSchema = z.object({ actions_available: z.array(appsActionsSchema), }); -const locationPreferenceIdSchema = z.any(); +export const installationQuestionTypeSchema = z.nativeEnum( + InstallationQuestionType, +); -export const appMaintainerSchema = z.object({ - email: z.string(), - name: z.string(), - url: z.string(), +export const installationQuestionOptionSchema = z.object({ + text: z.string(), + value: z.union([z.string(), z.number(), z.boolean()]), }); -const appMetadataSchema = z.any(); - -const apiTimestampSchema = z.any(); - -export const availableAppSchema = z.object({ - healthy: z.boolean(), - installed: z.boolean(), - categories: z.array(z.string()), - name: z.string(), - title: z.string(), - description: z.string(), - app_readme: z.string(), - app_metadata: appMetadataSchema, - location: z.string(), - healthy_error: z.string(), - latest_version: z.string(), - latest_app_version: z.string(), - icon_url: z.string(), - train: z.string(), - catalog: z.string(), - last_update: apiTimestampSchema, - recommended: z.boolean(), - maintainers: z.array(appMaintainerSchema), - tags: z.array(z.string()), - home: z.string(), - latest_human_version: z.string(), - screenshots: z.array(z.string()), - sources: z.array(z.string()), - versions: z.unknown(), +export const installationQuestionSchema = z.object({ + question: z.string(), + type: installationQuestionTypeSchema, + key: z.string(), + options: z.array(installationQuestionOptionSchema).optional(), + required: z.boolean().optional(), + default: z.union([z.string(), z.number(), z.boolean()]).optional(), + placeholder: z.string().optional(), + description: z.string().optional(), }); -const appStateSchema = z.any(); +const locationPreferenceIdSchema = z.any(); -const fileAccessSchema = z.any(); +const appStateSchema = z.any(); const chartFormValueSchema = z.any(); @@ -104,39 +93,42 @@ export const appRequirementsCheckSchema = z.object({ }), }); -export const appListingSchema = availableAppSchema.extend({ - hexos: z.boolean(), - recommended_during_setup: z.boolean().optional(), - requirements: appRequirementsSchema, +export const appListingSchema = z.object({ + appId: z.string(), + name: z.string(), + train: z.union([z.literal("stable"), z.literal("community")]), + version: z.string(), + appVersion: z.string(), + description: z.string(), + icon: z.string(), + categories: z.array(z.string()), + keywords: z.array(z.string()), + maintainers: z.array(appMaintainerSchema), + screenshots: z.array(z.string()), + sources: z.array(z.string()), + homepage: z.string(), + recommended: z.boolean(), + supported: z.boolean(), + fresh: z.boolean(), + installScript: z.string().optional(), + requirements: appRequirementsSchema.optional(), }); export const appInfoSchema = appBasicsSchema.extend({ status: appStateSchema, url_webui: z.string(), + upgradeAvailable: z.boolean(), + updatedScriptAvailable: z.boolean(), + latestVersion: z.string(), }); export const appInfoDetailedSchema = appInfoSchema.extend({ data: z.array(z.array(z.number())), }); -export const installationQuestionTypeSchema = z.enum(['text', 'number', 'select', 'boolean']); - -export const installationQuestionOptionSchema = z.object({ - text: z.string(), - value: z.union([z.string(), z.number(), z.boolean()]), -}); - -export const installationQuestionSchema = z.object({ - question: z.string(), - type: installationQuestionTypeSchema, - key: z.string(), - options: z.array(installationQuestionOptionSchema).optional(), - required: z.boolean().optional(), - default: z.union([z.string(), z.number(), z.boolean()]).optional(), -}); - const appsInstallScriptV1Schema = z.object({ version: z.literal(1), + requirements: appRequirementsSchema.optional(), ensure_directories_exists: z .array( z.union([ @@ -168,6 +160,7 @@ const appsInstallScriptV1Schema = z.object({ const appsInstallScriptV2Schema = z.object({ version: z.literal(2), + requirements: appRequirementsSchema.optional(), installation_questions: z.array(installationQuestionSchema).optional(), ensure_directories_exists: z .array( @@ -198,4 +191,33 @@ const appsInstallScriptV2Schema = z.object({ app_values: z.record(chartFormValueSchema), }); -export const appsInstallScriptSchema = z.union([appsInstallScriptV1Schema, appsInstallScriptV2Schema]); +const appsInstallScriptV3Schema = appsInstallScriptV2Schema + .omit({ version: true, requirements: true }) + .extend({ + version: z.literal(3), + script: z.object({ + version: z.string(), + updateCompatibility: z.string().optional(), + changeLog: z.string().optional(), + }), + requirements: appRequirementsSchema, + }); + +export const appsInstallScriptSchema = z.union([ + appsInstallScriptV1Schema, + appsInstallScriptV2Schema, + appsInstallScriptV3Schema, +]); + +export const appConfigurationSchema = z.object({ + installScript: appsInstallScriptSchema.optional().nullable(), + questionResponses: z + .record(z.union([z.string(), z.number(), z.boolean()])) + .optional(), +}); + +export const installScriptCurationSchema = z.object({ + name: z.string(), + url: z.string(), + script: appsInstallScriptSchema, +}); diff --git a/eshtek/routes.ts b/eshtek/routes.ts index c94437e..e60197b 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -170,6 +170,7 @@ export interface ResponseResourceChanges { target: string; changelog?: string; }; + partialUpdate?: Record; } export interface RequestAppDelete { diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index 4bb5206..9f16721 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -26,6 +26,7 @@ import { ServerActions, } from "./server"; +import { appsHealthSchema } from "./apps-schema"; import { vMSHealthSchema } from "./vms-schema"; export const serverUserTypeSchema = z.nativeEnum(ServerUserType); @@ -135,6 +136,12 @@ export const serverSystemDataSystemDeviceDataSchema = z.object({ networking: z.array(serverSystemDataSystemDeviceSchema).optional(), }); +export const serverSystemDataApplicationsSchema = + serverStatusBasicsSchema.extend({ + type: z.literal(ServerStatusType.APPLICATIONS), + health: appsHealthSchema, + }); + export const serverSystemDataVirtualizationSchema = serverStatusBasicsSchema.extend({ type: z.literal(ServerStatusType.VIRTUALIZATION), @@ -220,8 +227,6 @@ const fileTypeSchema = z.any(); const topologyItemStatusSchema = z.any(); -const appsHealthSchema = z.any(); - const networkInterfaceTypeSchema = z.any(); export const serverPoolBasicsSchema = z.object({ @@ -316,12 +321,6 @@ export const serverSystemDataStorageSchema = serverStatusBasicsSchema.extend({ }), }); -export const serverSystemDataApplicationsSchema = - serverStatusBasicsSchema.extend({ - type: z.literal(ServerStatusType.APPLICATIONS), - health: appsHealthSchema, - }); - export const serverSystemDataSchema = z.union([ serverSystemDataSystemSchema, serverSystemDataStorageSchema, diff --git a/eshtek/tasks.ts b/eshtek/tasks.ts index 52f421e..3c8d042 100644 --- a/eshtek/tasks.ts +++ b/eshtek/tasks.ts @@ -162,7 +162,7 @@ export type HexTaskDataMap = { [HexTaskType.APP_UPDATE]: HexTaskMeta; [HexTaskType.PREFERENCE_LOCATION_PATH_MIGRATION]: HexTaskMeta; [HexTaskType.DRIVE_REPLACE]: HexTaskMeta; - [HexTaskType.DOCKER_UPDATE]: HexTaskMeta; + [HexTaskType.DOCKER_UPDATE]: HexTaskMeta; }; // This looks a little strange with duplicated code, but we need a runtime const avail for the utils file diff --git a/truenas/webui/interfaces/api/api-call-directory.interface.ts b/truenas/webui/interfaces/api/api-call-directory.interface.ts index 1f4bf14..4b59abb 100644 --- a/truenas/webui/interfaces/api/api-call-directory.interface.ts +++ b/truenas/webui/interfaces/api/api-call-directory.interface.ts @@ -204,6 +204,7 @@ import type { SystemHealth } from '../system-health.interface'; import type { App, AppQueryParams, AppUpgradeParams } from '../app.interface'; import type { DetailsDisk, DiskSmartAttribute } from '../disk.interface'; import { DockerConfig, DockerStatusData } from '../../enums/docker-config.interface'; +import type { GpuChoices } from '../gpu.interface'; /** * API definitions for `call` methods. @@ -267,6 +268,7 @@ export interface ApiCallDirectory { 'app.similar': { params: [app_name: string, train: string]; response: AvailableApp[] }; 'app.rollback_versions': { params: [app_name: string]; response: string[] }; 'app.used_ports': { params: void; response: number[] }; + 'app.gpu_choices': { params: void; response: GpuChoices }; // Audit 'audit.config': { params: void; response: AuditConfig }; diff --git a/truenas/webui/interfaces/gpu.interface.ts b/truenas/webui/interfaces/gpu.interface.ts new file mode 100644 index 0000000..0f8342a --- /dev/null +++ b/truenas/webui/interfaces/gpu.interface.ts @@ -0,0 +1,20 @@ +/** + * GPU interface types based on TrueNAS middleware API + * From middlewared/middlewared/api/v25_04_1/app.py + */ + +export interface GpuVendorSpecificConfig { + uuid?: string; + [key: string]: unknown; +} + +export interface GpuDevice { + vendor: string | null; + description: string | null; + error: string | null; + vendor_specific_config: GpuVendorSpecificConfig; + gpu_details: Record; + pci_slot: string | null; +} + +export type GpuChoices = Record; \ No newline at end of file diff --git a/ts-to-zod.config.js b/ts-to-zod.config.js index 4a9c3e9..ff8603b 100644 --- a/ts-to-zod.config.js +++ b/ts-to-zod.config.js @@ -1,6 +1,6 @@ module.exports = [ { - name: "vms", + name: "apps", input: "eshtek/vms.ts", output: "eshtek/vms-schema.ts", }, @@ -18,5 +18,11 @@ module.exports = [ name: "user", input: "eshtek/user.ts", output: "eshtek/user-schema.ts", - } + }, + { + name: "apps", + input: "eshtek/apps.ts", + output: "eshtek/apps-schema.ts", + }, + ]; From 4cc0bda0c45552c89e658fe14f413146a597c912 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 9 Dec 2025 16:06:58 -0500 Subject: [PATCH 5/7] remove apps-schema --- eshtek/apps-schema.ts | 223 ---------------------------------------- eshtek/server-schema.ts | 3 +- ts-to-zod.config.js | 8 +- 3 files changed, 3 insertions(+), 231 deletions(-) delete mode 100644 eshtek/apps-schema.ts diff --git a/eshtek/apps-schema.ts b/eshtek/apps-schema.ts deleted file mode 100644 index 887efa6..0000000 --- a/eshtek/apps-schema.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Generated by ts-to-zod -import { z } from "zod"; -import { - AppJobAction, - AppSpec, - AppPermission, - AppsError, - AppsWarning, - AppsActions, - InstallationQuestionType, -} from "./apps"; - -import { fileAccessSchema } from "./server-schema"; - -export const appJobActionSchema = z.nativeEnum(AppJobAction); - -export const appBasicsSchema = z.object({ - id: z.string(), - name: z.string(), - description: z.string(), - version: z.string(), - icon: z.string(), - train: z.string(), -}); - -export const appSpecSchema = z.nativeEnum(AppSpec); - -export const appPermissionSchema = z.nativeEnum(AppPermission); - -export const appMaintainerSchema = z.object({ - name: z.string(), - email: z.string(), -}); - -export const appsErrorSchema = z.nativeEnum(AppsError); - -export const appsWarningSchema = z.nativeEnum(AppsWarning); - -export const appsActionsSchema = z.nativeEnum(AppsActions); - -export const appsHealthSchema = z.object({ - healthy: z.boolean(), - errors: z.array(appsErrorSchema), - warnings: z.array(appsWarningSchema), - actions_available: z.array(appsActionsSchema), -}); - -export const installationQuestionTypeSchema = z.nativeEnum( - InstallationQuestionType, -); - -export const installationQuestionOptionSchema = z.object({ - text: z.string(), - value: z.union([z.string(), z.number(), z.boolean()]), -}); - -export const installationQuestionSchema = z.object({ - question: z.string(), - type: installationQuestionTypeSchema, - key: z.string(), - options: z.array(installationQuestionOptionSchema).optional(), - required: z.boolean().optional(), - default: z.union([z.string(), z.number(), z.boolean()]).optional(), - placeholder: z.string().optional(), - description: z.string().optional(), -}); - -const locationPreferenceIdSchema = z.any(); - -const appStateSchema = z.any(); - -const chartFormValueSchema = z.any(); - -export const appRequirementsSchema = z.object({ - permissions: z.array(appPermissionSchema), - specifications: z.array(appSpecSchema), - locations: z.array(locationPreferenceIdSchema), - ports: z.array(z.number()), -}); - -export const appRequirementsCheckSchema = z.object({ - permissions: z.object({ - met: z.array(appPermissionSchema), - unmet: z.array(appPermissionSchema), - }), - specifications: z.object({ - met: z.array(appSpecSchema), - unmet: z.array(appSpecSchema), - }), - locations: z.object({ - met: z.array(locationPreferenceIdSchema), - unmet: z.array(locationPreferenceIdSchema), - }), -}); - -export const appListingSchema = z.object({ - appId: z.string(), - name: z.string(), - train: z.union([z.literal("stable"), z.literal("community")]), - version: z.string(), - appVersion: z.string(), - description: z.string(), - icon: z.string(), - categories: z.array(z.string()), - keywords: z.array(z.string()), - maintainers: z.array(appMaintainerSchema), - screenshots: z.array(z.string()), - sources: z.array(z.string()), - homepage: z.string(), - recommended: z.boolean(), - supported: z.boolean(), - fresh: z.boolean(), - installScript: z.string().optional(), - requirements: appRequirementsSchema.optional(), -}); - -export const appInfoSchema = appBasicsSchema.extend({ - status: appStateSchema, - url_webui: z.string(), - upgradeAvailable: z.boolean(), - updatedScriptAvailable: z.boolean(), - latestVersion: z.string(), -}); - -export const appInfoDetailedSchema = appInfoSchema.extend({ - data: z.array(z.array(z.number())), -}); - -const appsInstallScriptV1Schema = z.object({ - version: z.literal(1), - requirements: appRequirementsSchema.optional(), - ensure_directories_exists: z - .array( - z.union([ - z.string(), - z.object({ - path: z.string(), - network_share: z.boolean().optional(), - posix: z.boolean().optional(), - }), - ]), - ) - .optional(), - ensure_permissions_exists: z - .array( - z.object({ - path: z.string(), - username: z.string(), - access: fileAccessSchema, - posix: z - .object({ - groupname: z.string(), - }) - .optional(), - }), - ) - .optional(), - app_values: z.record(chartFormValueSchema), -}); - -const appsInstallScriptV2Schema = z.object({ - version: z.literal(2), - requirements: appRequirementsSchema.optional(), - installation_questions: z.array(installationQuestionSchema).optional(), - ensure_directories_exists: z - .array( - z.union([ - z.string(), - z.object({ - path: z.string(), - network_share: z.boolean().optional(), - posix: z.boolean().optional(), - }), - ]), - ) - .optional(), - ensure_permissions_exists: z - .array( - z.object({ - path: z.string(), - username: z.string(), - access: fileAccessSchema, - posix: z - .object({ - groupname: z.string(), - }) - .optional(), - }), - ) - .optional(), - app_values: z.record(chartFormValueSchema), -}); - -const appsInstallScriptV3Schema = appsInstallScriptV2Schema - .omit({ version: true, requirements: true }) - .extend({ - version: z.literal(3), - script: z.object({ - version: z.string(), - updateCompatibility: z.string().optional(), - changeLog: z.string().optional(), - }), - requirements: appRequirementsSchema, - }); - -export const appsInstallScriptSchema = z.union([ - appsInstallScriptV1Schema, - appsInstallScriptV2Schema, - appsInstallScriptV3Schema, -]); - -export const appConfigurationSchema = z.object({ - installScript: appsInstallScriptSchema.optional().nullable(), - questionResponses: z - .record(z.union([z.string(), z.number(), z.boolean()])) - .optional(), -}); - -export const installScriptCurationSchema = z.object({ - name: z.string(), - url: z.string(), - script: appsInstallScriptSchema, -}); diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index 9f16721..e0463ea 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -26,8 +26,9 @@ import { ServerActions, } from "./server"; -import { appsHealthSchema } from "./apps-schema"; import { vMSHealthSchema } from "./vms-schema"; +const appsHealthSchema = z.any(); + export const serverUserTypeSchema = z.nativeEnum(ServerUserType); diff --git a/ts-to-zod.config.js b/ts-to-zod.config.js index ff8603b..e02bcdf 100644 --- a/ts-to-zod.config.js +++ b/ts-to-zod.config.js @@ -18,11 +18,5 @@ module.exports = [ name: "user", input: "eshtek/user.ts", output: "eshtek/user-schema.ts", - }, - { - name: "apps", - input: "eshtek/apps.ts", - output: "eshtek/apps-schema.ts", - }, - + } ]; From a5e69b3efe2c653dde2707ec30fcb9f6d42a38a0 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Wed, 10 Dec 2025 16:42:54 -0500 Subject: [PATCH 6/7] preserve added to resource change action --- eshtek/routes.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/eshtek/routes.ts b/eshtek/routes.ts index e60197b..b13b785 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -138,7 +138,8 @@ export enum ResourceChangeAction { CREATE = 'create', UPDATE = 'update', DELETE = 'delete', - NO_OP = 'no-op' + NO_OP = 'no-op', + PRESERVE = 'preserve' } export interface ResponseResourceChanges { @@ -158,6 +159,7 @@ export interface ResponseResourceChanges { update: number; delete: number; noOp: number; + preserve: number; }; }; validation: { @@ -167,10 +169,9 @@ export interface ResponseResourceChanges { }; versionInfo: { current?: string; - target: string; + target?: string; changelog?: string; }; - partialUpdate?: Record; } export interface RequestAppDelete { From dd5e7b8e1af5e9c74f5cfd824a31b8ab874dc86b Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 16 Dec 2025 14:03:09 -0500 Subject: [PATCH 7/7] Adds app recommendation and config error handling Adds a flag to indicate if an app is recommended and introduces a new error code to handle cases where no current configuration is available during app updates. --- eshtek/apps.ts | 1 + eshtek/errors.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 38c6afb..87ab391 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -91,6 +91,7 @@ export interface AppInfo extends AppBasics { upgradeAvailable: boolean; updatedScriptAvailable: boolean; latestVersion: string; + recommended: boolean; } export interface AppInfoDetailed extends AppInfo { data: number[][]; diff --git a/eshtek/errors.ts b/eshtek/errors.ts index d1c3ee4..0dc831e 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -41,6 +41,7 @@ export enum GlobalErrorCode { UPDATE_APP_INVALID_SCRIPT = 'UPDATE_APP_INVALID_SCRIPT', UPDATE_APP_INVALID_CATALOG_SCRIPT = 'UPDATE_APP_INVALID_CATALOG_SCRIPT', UPDATE_APP_ANALYSIS_FAILED = 'UPDATE_APP_ANALYSIS_FAILED', - UPDATE_APP_FAILED = 'UPDATE_APP_FAILED' + UPDATE_APP_FAILED = 'UPDATE_APP_FAILED', + UPDATE_APP_NO_CURRENT_CONFIG = 'UPDATE_APP_NO_CURRENT_CONFIG', }