@fabrika/provider-zerops 0.8.1 → 0.9.0-cloudflare.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +16 -14
- package/src/api/client.ts +18 -0
- package/src/api/readers.ts +29 -0
- package/src/api/types.ts +64 -0
- package/src/api.ts +2 -0
- package/src/cli.ts +11 -0
- package/src/collaborators.ts +0 -14
- package/src/control.ts +35 -57
- package/src/execution.ts +113 -102
- package/src/index.ts +2 -0
- package/src/installation/admin.ts +1 -1
- package/src/installation/deploy-options.ts +1 -2
- package/src/installation/deploy.ts +225 -80
- package/src/installation/generated/platform-console-schema.ts +3 -0
- package/src/installation/generated/platform-proxy-manifest.ts +21 -1
- package/src/installation/guide.md +48 -18
- package/src/installation/hosts.ts +1 -7
- package/src/installation/index.ts +13 -16
- package/src/installation/init-options.ts +2 -1
- package/src/installation/install-environment.ts +47 -108
- package/src/installation/install-options.ts +3 -1
- package/src/installation/install-secrets.ts +24 -68
- package/src/installation/install.ts +9 -4
- package/src/installation/sidecar.ts +5 -26
- package/src/installation/topology/setups.ts +15 -4
- package/src/installation/topology/topology.ts +31 -21
- package/src/installation/upgrade-options.ts +2 -2
- package/src/managed-env.ts +1 -174
- package/src/manifest.ts +11 -14
- package/src/namespace/claims.ts +56 -1
- package/src/namespace/errors.ts +1 -1
- package/src/namespace/manifest.ts +6 -19
- package/src/namespace/provisioning.ts +24 -4
- package/src/namespace/proxy-attempt.ts +43 -2
- package/src/namespace/proxy-reads.ts +12 -22
- package/src/namespace/types.ts +2 -0
- package/src/namespace-cli.ts +152 -0
- package/src/plan.ts +2 -7
- package/src/provider.ts +128 -126
- package/src/proxy-manifest-carrier.ts +18 -0
- package/src/types.ts +0 -2
- package/types/src/api/client.d.ts +5 -0
- package/types/src/api/errors.d.ts +24 -0
- package/types/src/api/polling.d.ts +41 -0
- package/types/src/api/readers.d.ts +1115 -0
- package/types/src/api/service-env-limit.d.ts +17 -0
- package/types/src/api/service-env-response.d.ts +23 -0
- package/types/src/api/types.d.ts +687 -0
- package/types/src/api.d.ts +6 -0
- package/types/src/authoring.d.ts +9 -0
- package/types/src/cli-args.d.ts +19 -0
- package/types/src/cli.d.ts +13 -0
- package/types/src/collaborators.d.ts +16 -0
- package/types/src/compile.d.ts +91 -0
- package/types/src/control.d.ts +40 -0
- package/types/src/database-budget.d.ts +38 -0
- package/types/src/descriptor.d.ts +28 -0
- package/types/src/execution.d.ts +125 -0
- package/types/src/exposure.d.ts +47 -0
- package/types/src/index.d.ts +31 -0
- package/types/src/installation/admin-options.d.ts +24 -0
- package/types/src/installation/admin.d.ts +35 -0
- package/types/src/installation/checker-release.d.ts +12 -0
- package/types/src/installation/deploy-options.d.ts +37 -0
- package/types/src/installation/deploy.d.ts +136 -0
- package/types/src/installation/generated/platform-console-schema.d.ts +2 -0
- package/types/src/installation/generated/platform-proxy-manifest.d.ts +2 -0
- package/types/src/installation/hosts.d.ts +34 -0
- package/types/src/installation/index.d.ts +7 -0
- package/types/src/installation/init-options.d.ts +38 -0
- package/types/src/installation/init.d.ts +60 -0
- package/types/src/installation/install-collaborators.d.ts +24 -0
- package/types/src/installation/install-environment.d.ts +63 -0
- package/types/src/installation/install-options.d.ts +49 -0
- package/types/src/installation/install-project.d.ts +13 -0
- package/types/src/installation/install-provisioning.d.ts +58 -0
- package/types/src/installation/install-secrets.d.ts +17 -0
- package/types/src/installation/install-token.d.ts +10 -0
- package/types/src/installation/install.d.ts +23 -0
- package/types/src/installation/installation-source.d.ts +8 -0
- package/types/src/installation/log.d.ts +44 -0
- package/types/src/installation/machine-key.d.ts +19 -0
- package/types/src/installation/placement-options.d.ts +11 -0
- package/types/src/installation/platform-archive.d.ts +7 -0
- package/types/src/installation/platform-source.d.ts +8 -0
- package/types/src/installation/platform-upload.d.ts +3 -0
- package/types/src/installation/sidecar.d.ts +49 -0
- package/types/src/installation/source-options.d.ts +1 -0
- package/types/src/installation/topology/artifacts.d.ts +8 -0
- package/types/src/installation/topology/index.d.ts +5 -0
- package/types/src/installation/topology/installation-schemas.d.ts +2242 -0
- package/types/src/installation/topology/invariants.d.ts +23 -0
- package/types/src/installation/topology/setups.d.ts +59 -0
- package/types/src/installation/topology/topology.d.ts +153 -0
- package/types/src/installation/topology/validate.d.ts +11 -0
- package/types/src/installation/upgrade-options.d.ts +26 -0
- package/types/src/installation/upgrade-output.d.ts +6 -0
- package/types/src/installation/upgrade.d.ts +88 -0
- package/types/src/managed-env.d.ts +24 -0
- package/types/src/manifest.d.ts +204 -0
- package/types/src/namespace/claims.d.ts +16 -0
- package/types/src/namespace/codec.d.ts +334 -0
- package/types/src/namespace/constants.d.ts +25 -0
- package/types/src/namespace/errors.d.ts +15 -0
- package/types/src/namespace/manifest.d.ts +4 -0
- package/types/src/namespace/operator.d.ts +12 -0
- package/types/src/namespace/provisioning.d.ts +4 -0
- package/types/src/namespace/proxy-attempt.d.ts +39 -0
- package/types/src/namespace/proxy-reads.d.ts +22 -0
- package/types/src/namespace/proxy-secrets.d.ts +8 -0
- package/types/src/namespace/proxy-source.d.ts +74 -0
- package/types/src/namespace/topology.d.ts +9 -0
- package/types/src/namespace/types.d.ts +101 -0
- package/types/src/namespace/update.d.ts +6 -0
- package/types/src/namespace/validation.d.ts +12 -0
- package/types/src/namespace-cli.d.ts +2 -0
- package/types/src/namespace.d.ts +9 -0
- package/types/src/plan.d.ts +11 -0
- package/types/src/provider.d.ts +63 -0
- package/types/src/proxy-manifest-carrier.d.ts +24 -0
- package/types/src/schema.generated.d.ts +376 -0
- package/types/src/service-names.d.ts +4 -0
- package/types/src/service-references.d.ts +13 -0
- package/types/src/source/constants.d.ts +6 -0
- package/types/src/source/port.d.ts +60 -0
- package/types/src/source/types.d.ts +4 -0
- package/types/src/source/validation.d.ts +19 -0
- package/types/src/source.d.ts +2 -0
- package/types/src/types.d.ts +119 -0
- package/types/src/version-attempt.d.ts +8 -0
- package/src/installation/mcp.ts +0 -102
- package/src/installation/proxy-manifest.ts +0 -214
- package/src/installation/secrets.ts +0 -78
package/src/execution.ts
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
// runs. Of the authored setup only `build.base`, `build.os` and the SPA's `build.buildCommands` survive; the rest of
|
|
3
3
|
// the build, the deploy gate and the whole `run` section are generated here. PURE — no network, no filesystem.
|
|
4
4
|
|
|
5
|
+
import { type ContractRecipe, contractRecipe } from '@fabrika/app/build'
|
|
5
6
|
import {
|
|
6
7
|
type ContractArtifact,
|
|
8
|
+
DEV_LAUNCHER_BOOTED_FILE,
|
|
9
|
+
DEV_LAUNCHER_CONFIG_FILE,
|
|
10
|
+
DEV_LAUNCHER_MODULE,
|
|
11
|
+
type DevLauncherConfig,
|
|
7
12
|
FABRIKA_CONTRACT_DIGEST,
|
|
8
13
|
FABRIKA_SOURCE_MAP_UPLOAD,
|
|
9
14
|
FABRIKA_SOURCE_MAP_UPLOAD_KEY,
|
|
10
15
|
type FrameworkVariableName,
|
|
11
16
|
} from '@fabrika/auth-core'
|
|
12
17
|
import type { OperationsAssetRoot, OperationsSourceMapBuildUpload } from '@fabrika/operations-contract/releases'
|
|
13
|
-
import {
|
|
18
|
+
import type { JsonValue } from '@fabrika/provider-contract'
|
|
14
19
|
import packageManifest from '../package.json'
|
|
15
20
|
import { renderYaml } from './compile'
|
|
16
21
|
import { parseZeropsDescriptor, type PreparedZeropsDescriptor, selectZeropsDescriptorSetup } from './descriptor'
|
|
@@ -29,8 +34,7 @@ export const ZEROPS_APPLICATION_RUNTIME = 'alpine/bun@1.3'
|
|
|
29
34
|
*/
|
|
30
35
|
export const FABRIKA_CHECK_VERSION: string = packageManifest.version
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
export const PLATFORM_TYPESCRIPT_VERSION = '5.9.3'
|
|
37
|
+
export { PLATFORM_TYPESCRIPT_VERSION } from '@fabrika/app/build'
|
|
34
38
|
|
|
35
39
|
/** System packages an application may add to its runtime image, installed by the generated `run.prepareCommands`. */
|
|
36
40
|
export const ZEROPS_RUNTIME_PACKAGES = ['ffmpeg', 'ghostscript', 'imagemagick', 'poppler-utils', 'vips'] as const
|
|
@@ -42,12 +46,6 @@ export interface ZeropsContractJob {
|
|
|
42
46
|
readonly schedule: string
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
/** The framework's file scan job (`@fabrika/app`'s `FILE_SCAN_JOB`), run every minute when a collection scans. */
|
|
46
|
-
export const ZEROPS_FILE_SCAN_JOB = 'fabrika-file-scan'
|
|
47
|
-
/** The framework's file sweep job (`@fabrika/app`'s `FILE_SWEEP_JOB` and its schedule), run when the contract has files. */
|
|
48
|
-
export const ZEROPS_FILE_SWEEP_JOB = 'fabrika-file-sweep'
|
|
49
|
-
const FILE_SWEEP_SCHEDULE = '41 * * * *'
|
|
50
|
-
|
|
51
49
|
/**
|
|
52
50
|
* The RAM in GB a container of a scanning application starts with: one `clamscan` over a batch peaks at about 1 GB
|
|
53
51
|
* resident while it loads its signatures (measured with ClamAV 1.4), beside the application's own process.
|
|
@@ -107,13 +105,26 @@ export interface ZeropsLockedExecution {
|
|
|
107
105
|
readonly runtimePackages: readonly ZeropsRuntimePackage[]
|
|
108
106
|
/** Where the build uploads its source maps, without the bearer; null when Control minted no upload for the run. */
|
|
109
107
|
readonly sourceMaps: OperationsSourceMapBuildUpload | null
|
|
108
|
+
/**
|
|
109
|
+
* Present for an environment of kind `dev` (ADR-0049): `run.start` runs the dev launcher instead of the serving
|
|
110
|
+
* entry. The environment's kind selects it; an application never authors it.
|
|
111
|
+
*/
|
|
112
|
+
readonly dev?: ZeropsDevExecution
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ZeropsDevExecution {
|
|
116
|
+
/** The commit the bootstrap run builds: what a new container holds before the launcher's first sync. */
|
|
117
|
+
readonly head: string
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
/** The application ids the generated scripts may name: the contract's own id rule, so a script needs no quoting. */
|
|
113
|
-
export
|
|
121
|
+
export { CONTRACT_APP_ID_PATTERN as LOCKED_APP_ID_PATTERN } from '@fabrika/app/build'
|
|
114
122
|
|
|
115
123
|
export const CONTRACT_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/
|
|
116
124
|
|
|
125
|
+
/** A literal path of unreserved characters: it is written into the generated probes unquoted by any shell. */
|
|
126
|
+
export const READINESS_PATH_PATTERN = /^(?:\/[A-Za-z0-9_~-][A-Za-z0-9._~-]*)+$/
|
|
127
|
+
|
|
117
128
|
const byName = <T extends { readonly name: string }>(left: T, right: T): number => left.name < right.name ? -1 : left.name > right.name ? 1 : 0
|
|
118
129
|
|
|
119
130
|
/**
|
|
@@ -241,31 +252,6 @@ export function lockedAuthoredBuild(entry: { readonly [key: string]: JsonValue }
|
|
|
241
252
|
|
|
242
253
|
const SOURCE_ROOT = '/build/source'
|
|
243
254
|
const RUNTIME_ROOT = '/var/www'
|
|
244
|
-
const FABRIKA_BIN = './node_modules/.bin/fabrika'
|
|
245
|
-
const MIGRATE_SCRIPT = 'fabrika/migrate.generated.sh'
|
|
246
|
-
const VERIFY_SCRIPT = 'fabrika/verify.generated.sh'
|
|
247
|
-
const START_SCRIPT = 'fabrika/start.generated.sh'
|
|
248
|
-
const PLATFORM_TSCONFIG = 'fabrika/tsconfig.platform.json'
|
|
249
|
-
|
|
250
|
-
/** The compiler options of the platform's type check; the application's own `tsconfig.json` and its `paths` are not read. */
|
|
251
|
-
const PLATFORM_TSCONFIG_CONTENTS = JSON.stringify({
|
|
252
|
-
compilerOptions: {
|
|
253
|
-
target: 'ES2022',
|
|
254
|
-
module: 'ESNext',
|
|
255
|
-
moduleResolution: 'bundler',
|
|
256
|
-
lib: ['ES2022', 'DOM', 'DOM.Iterable'],
|
|
257
|
-
types: ['bun'],
|
|
258
|
-
strict: true,
|
|
259
|
-
noUncheckedIndexedAccess: true,
|
|
260
|
-
noImplicitOverride: true,
|
|
261
|
-
forceConsistentCasingInFileNames: true,
|
|
262
|
-
skipLibCheck: true,
|
|
263
|
-
resolveJsonModule: true,
|
|
264
|
-
isolatedModules: true,
|
|
265
|
-
noEmit: true,
|
|
266
|
-
},
|
|
267
|
-
include: ['../fabrika.contract.ts', '../src', 'entry.generated.ts'],
|
|
268
|
-
})
|
|
269
255
|
|
|
270
256
|
/** One `printf` writing `lines` to `path`. Each line is single-quoted, so it may hold neither `'` nor an expansion. */
|
|
271
257
|
const writeFileCommand = (path: string, lines: readonly string[]): string => {
|
|
@@ -275,39 +261,6 @@ const writeFileCommand = (path: string, lines: readonly string[]): string => {
|
|
|
275
261
|
return `printf '%s\\n' ${lines.map((line) => `'${line}'`).join(' ')} > ${path}`
|
|
276
262
|
}
|
|
277
263
|
|
|
278
|
-
const migrateScript = (appId: string): readonly string[] => [
|
|
279
|
-
'#!/bin/sh',
|
|
280
|
-
'set -eu',
|
|
281
|
-
`cd ${RUNTIME_ROOT}`,
|
|
282
|
-
`FABRIKA_APP_ID=${appId} exec ${FABRIKA_BIN} db migrate`,
|
|
283
|
-
]
|
|
284
|
-
|
|
285
|
-
// The committed schema types must match the migrated schema and every generated isolation test must pass on a throwaway
|
|
286
|
-
// schema. The build has no database, so they run at container start, after the migrations, as init commands: a failed
|
|
287
|
-
// init command stops the activation, and the readiness window opens only with `run.start`, after the init commands (both
|
|
288
|
-
// verified live), so a run as long as the CRM's ~3 minutes of generated cases does not race it.
|
|
289
|
-
const verifyScript = (appId: string): readonly string[] => [
|
|
290
|
-
'#!/bin/sh',
|
|
291
|
-
'set -eu',
|
|
292
|
-
`cd ${RUNTIME_ROOT}`,
|
|
293
|
-
`FABRIKA_APP_ID=${appId} ${FABRIKA_BIN} db codegen --verify`,
|
|
294
|
-
`FABRIKA_APP_ID=${appId} exec ${FABRIKA_BIN} db test-isolation`,
|
|
295
|
-
]
|
|
296
|
-
|
|
297
|
-
const startScript: readonly string[] = ['#!/bin/sh', 'set -eu', `cd ${RUNTIME_ROOT}`, 'exec bun fabrika/entry.generated.ts']
|
|
298
|
-
|
|
299
|
-
/** The scripts the build writes and every container's init commands run, in this order. */
|
|
300
|
-
const initScripts = (execution: ZeropsLockedExecution): readonly { readonly path: string; readonly lines: readonly string[] }[] =>
|
|
301
|
-
execution.databaseUrl === null
|
|
302
|
-
? []
|
|
303
|
-
: [{ path: MIGRATE_SCRIPT, lines: migrateScript(execution.appId) }, { path: VERIFY_SCRIPT, lines: verifyScript(execution.appId) }]
|
|
304
|
-
|
|
305
|
-
const INSTALL = 'bun install --frozen-lockfile --ignore-scripts'
|
|
306
|
-
|
|
307
|
-
// After every check: a map is uploaded only for a build that can deploy. A failed upload never fails the build; the
|
|
308
|
-
// provider reads the state from the report line.
|
|
309
|
-
const sourceMapUpload = (release: string): string => `cd /tmp && bun x --bun -p @fabrika/check@${release} fabrika-source-maps ${SOURCE_ROOT} || true`
|
|
310
|
-
|
|
311
264
|
/**
|
|
312
265
|
* The upload's build variables: the destination and asset roots as a literal, and the bearer lifted from the service
|
|
313
266
|
* variable the deploy wrote sensitive, so the descriptor never holds it.
|
|
@@ -319,26 +272,81 @@ const sourceMapBuildVariables = (upload: OperationsSourceMapBuildUpload): Record
|
|
|
319
272
|
return { [FABRIKA_SOURCE_MAP_UPLOAD]: value, [FABRIKA_SOURCE_MAP_UPLOAD_KEY]: `\${RUNTIME_${FABRIKA_SOURCE_MAP_UPLOAD_KEY}}` }
|
|
320
273
|
}
|
|
321
274
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
return
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
275
|
+
// The readiness check is retried no sooner than every 10 s (the platform's floor), and a server is rarely listening at
|
|
276
|
+
// the first attempt, one second after `run.start`. So one attempt waits for the health endpoint itself, for less than
|
|
277
|
+
// the retry period; it passes only on a 2xx, like the `httpGet` it replaces. A dev container also passes once its launcher
|
|
278
|
+
// reported the boot, served or failed: the launcher keeps running and reports a failed migration with its remedy.
|
|
279
|
+
const readinessCommand = (path: string, dev: boolean): string => {
|
|
280
|
+
if (!READINESS_PATH_PATTERN.test(path)) throw new Error('zerops: a readiness path must be a literal path of unreserved characters')
|
|
281
|
+
const url = `http://127.0.0.1:${ZEROPS_APPLICATION_PORT}${path}`
|
|
282
|
+
const booted = dev ? `if (await Bun.file('${RUNTIME_ROOT}/${DEV_LAUNCHER_BOOTED_FILE}').exists()) process.exit(0); ` : ''
|
|
283
|
+
return `bun -e "const end = Date.now() + 9000; while (Date.now() < end) { ${booted}try { if ((await fetch('${url}', { signal: AbortSignal.timeout(2000) })).ok) process.exit(0) } catch {} await Bun.sleep(200) } process.exit(1)"`
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** `@fabrika/app/build`'s recipe at this installation's paths; the dev launcher checks in the runtime root. */
|
|
287
|
+
const recipeOf = (execution: ZeropsLockedExecution, sourceRoot: string = SOURCE_ROOT): ContractRecipe =>
|
|
288
|
+
contractRecipe({
|
|
289
|
+
appId: execution.appId,
|
|
290
|
+
checkRelease: execution.checkRelease,
|
|
291
|
+
sourceRoot,
|
|
292
|
+
runtimeRoot: RUNTIME_ROOT,
|
|
293
|
+
// The build has no database; a container's init commands run the migrations and schema checks, and the readiness
|
|
294
|
+
// window opens only with `run.start`, after them (both verified live), so the CRM's ~3 minutes does not race it.
|
|
295
|
+
// A dev container has none: its init would run before Control says whether a reset is pending, over the schema the
|
|
296
|
+
// previous container left, so its launcher migrates and verifies after that answer.
|
|
297
|
+
database: execution.databaseUrl !== null && execution.dev === undefined,
|
|
298
|
+
sourceMaps: execution.sourceMaps !== null,
|
|
299
|
+
jobs: execution.contract.jobs,
|
|
300
|
+
files: execution.contract.files === true,
|
|
301
|
+
scan: execution.contract.scan === true,
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
// A dev environment starts the launcher of the application's own framework release, which runs the serving entry as its
|
|
305
|
+
// child. Double quotes inside: a generated file line may hold no single quote.
|
|
306
|
+
const devStartScript: readonly string[] = [
|
|
307
|
+
'#!/bin/sh',
|
|
308
|
+
'set -eu',
|
|
309
|
+
`cd ${RUNTIME_ROOT}`,
|
|
310
|
+
`exec bun -e "await import(\\"${DEV_LAUNCHER_MODULE}\\").then((dev) => dev.runDevLauncher(process.env))"`,
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
/** One command writing `content` to `path` through base64: the content may hold quotes and expansions, the command none. */
|
|
314
|
+
const writeBase64Command = (path: string, content: string): string => {
|
|
315
|
+
const encoded = btoa(String.fromCharCode(...new TextEncoder().encode(content)))
|
|
316
|
+
return `printf '%s' '${encoded}' | base64 -d > ${path}`
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** What the dev launcher reads (`DEV_LAUNCHER_CONFIG_FILE`): the platform's, written after the checks. */
|
|
320
|
+
export function devLauncherConfig(execution: ZeropsLockedExecution, authoredSpa: readonly string[]): DevLauncherConfig {
|
|
321
|
+
const dev = execution.dev
|
|
322
|
+
if (dev === undefined) throw new Error('zerops: a dev launcher configuration needs a dev environment')
|
|
323
|
+
if (!/^[0-9a-f]{40}$/.test(dev.head)) throw new Error('zerops: a dev environment builds an exact commit')
|
|
324
|
+
const recipe = recipeOf(execution)
|
|
325
|
+
return {
|
|
326
|
+
format: 1,
|
|
327
|
+
appId: execution.appId,
|
|
328
|
+
head: dev.head,
|
|
329
|
+
port: ZEROPS_APPLICATION_PORT,
|
|
330
|
+
healthPath: execution.contract.readinessPath,
|
|
331
|
+
serve: recipe.serve,
|
|
332
|
+
migrate: recipe.migrate,
|
|
333
|
+
spaBuild: authoredSpa,
|
|
334
|
+
// The build's check chain, in its order, on what the container holds.
|
|
335
|
+
checks: recipeOf(execution, RUNTIME_ROOT).checks,
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** The generated build commands, in order; the authored SPA commands run before all of them. */
|
|
340
|
+
export function generatedBuildCommands(execution: ZeropsLockedExecution, authoredSpa: readonly string[] = []): readonly string[] {
|
|
341
|
+
const recipe = recipeOf(execution)
|
|
342
|
+
return recipe.build.flatMap((step) => {
|
|
343
|
+
if ('run' in step) return [step.run]
|
|
344
|
+
if (execution.dev === undefined || step.write !== recipe.start) return [writeFileCommand(step.write, step.lines)]
|
|
345
|
+
return [
|
|
346
|
+
writeFileCommand(recipe.start, devStartScript),
|
|
347
|
+
writeBase64Command(DEV_LAUNCHER_CONFIG_FILE, JSON.stringify(devLauncherConfig(execution, authoredSpa))),
|
|
348
|
+
]
|
|
349
|
+
})
|
|
342
350
|
}
|
|
343
351
|
|
|
344
352
|
const sortedUnique = (values: readonly string[]): string[] => [...new Set(values)].sort()
|
|
@@ -354,16 +362,11 @@ const storageVariables = (hostname: string): Record<StorageVariable, string> =>
|
|
|
354
362
|
})
|
|
355
363
|
|
|
356
364
|
/** The crontab lines that run a job of the contract or of the framework, each on one container. */
|
|
357
|
-
const jobCrontab = (
|
|
358
|
-
|
|
359
|
-
...(contract.files === true
|
|
360
|
-
? [{ timing: FILE_SWEEP_SCHEDULE, command: `${FABRIKA_BIN} job run ${ZEROPS_FILE_SWEEP_JOB}`, allContainers: false }]
|
|
361
|
-
: []),
|
|
362
|
-
...(contract.scan === true ? [{ timing: '* * * * *', command: `${FABRIKA_BIN} job run ${ZEROPS_FILE_SCAN_JOB}`, allContainers: false }] : []),
|
|
363
|
-
]
|
|
365
|
+
const jobCrontab = (execution: ZeropsLockedExecution): { readonly timing: string; readonly command: string; readonly allContainers: boolean }[] =>
|
|
366
|
+
recipeOf(execution).jobs.map((job) => ({ timing: job.schedule, command: job.command, allContainers: false }))
|
|
364
367
|
|
|
365
368
|
/** How many job processes the crontab may run at once, each opening the application's database. */
|
|
366
|
-
export const zeropsJobProcesses = (
|
|
369
|
+
export const zeropsJobProcesses = (execution: ZeropsLockedExecution): number => jobCrontab(execution).length
|
|
367
370
|
|
|
368
371
|
/** The commands every container runs before `run.start`, in order; any failure stops the activation. */
|
|
369
372
|
export function generatedInitCommands(execution: ZeropsLockedExecution): string[] {
|
|
@@ -371,7 +374,7 @@ export function generatedInitCommands(execution: ZeropsLockedExecution): string[
|
|
|
371
374
|
// A container starts from the prepared image, whose signatures are as old as its build; a failed refresh leaves
|
|
372
375
|
// them to the daily one, and the scanner refuses signatures older than three days, so the scan fails closed.
|
|
373
376
|
...(execution.contract.scan === true ? [`${FRESHCLAM} || echo 'fabrika: freshclam did not refresh the malware signatures'`] : []),
|
|
374
|
-
...
|
|
377
|
+
...recipeOf(execution).init.map((path) => `sh ${RUNTIME_ROOT}/${path}`),
|
|
375
378
|
]
|
|
376
379
|
}
|
|
377
380
|
|
|
@@ -396,7 +399,7 @@ export function renderLockedSetup(
|
|
|
396
399
|
const scan = contract.scan === true
|
|
397
400
|
const packages = sortedUnique([...execution.runtimePackages, ...(scan ? [SCANNER_PACKAGE] : [])])
|
|
398
401
|
// Every container keeps its own signatures; one scan pass at a time holds the job's lease.
|
|
399
|
-
const crontab = [...jobCrontab(
|
|
402
|
+
const crontab = [...jobCrontab(execution), ...(scan ? [{ timing: FRESHCLAM_SCHEDULE, command: FRESHCLAM, allContainers: true }] : [])]
|
|
400
403
|
const initCommands = generatedInitCommands(execution)
|
|
401
404
|
const prepareCommands = packages.length === 0 ? [] : [`sudo apk add --no-cache ${packages.join(' ')}`, ...(scan ? [FRESHCLAM] : [])]
|
|
402
405
|
return {
|
|
@@ -404,21 +407,29 @@ export function renderLockedSetup(
|
|
|
404
407
|
build: {
|
|
405
408
|
base: authored.base ?? [ZEROPS_APPLICATION_RUNTIME],
|
|
406
409
|
...(authored.os === null ? {} : { os: authored.os }),
|
|
407
|
-
buildCommands: [...authored.commands, ...generatedBuildCommands(execution)],
|
|
410
|
+
buildCommands: [...authored.commands, ...generatedBuildCommands(execution, authored.commands)],
|
|
408
411
|
...(execution.sourceMaps === null ? {} : { envVariables: sourceMapBuildVariables(execution.sourceMaps) }),
|
|
409
412
|
deployFiles: ['./'],
|
|
410
413
|
cache: ['node_modules'],
|
|
411
414
|
},
|
|
412
415
|
deploy: {
|
|
413
|
-
readinessCheck: {
|
|
416
|
+
readinessCheck: {
|
|
417
|
+
exec: { command: readinessCommand(contract.readinessPath, execution.dev !== undefined) },
|
|
418
|
+
retryPeriod: '10s',
|
|
419
|
+
failureTimeout: '180s',
|
|
420
|
+
},
|
|
414
421
|
},
|
|
415
422
|
run: {
|
|
416
423
|
base: ZEROPS_APPLICATION_RUNTIME,
|
|
417
424
|
...(prepareCommands.length === 0 ? {} : { prepareCommands }),
|
|
418
425
|
...(initCommands.length === 0 ? {} : { initCommands }),
|
|
419
|
-
start: `sh ${RUNTIME_ROOT}/${
|
|
426
|
+
start: `sh ${RUNTIME_ROOT}/${recipeOf(execution).start}`,
|
|
420
427
|
ports: [{ port: ZEROPS_APPLICATION_PORT, httpSupport: true }],
|
|
421
|
-
|
|
428
|
+
// A dev container keeps its launcher running while the application it respawns fails: Zerops restarting it
|
|
429
|
+
// would only boot the same failing head again.
|
|
430
|
+
...(execution.dev === undefined
|
|
431
|
+
? { healthCheck: { httpGet: probe, execPeriod: '10s', disconnectTimeout: '30s', recoveryTimeout: '10s', failureTimeout: '120s' } }
|
|
432
|
+
: {}),
|
|
422
433
|
...(crontab.length === 0 ? {} : { crontab }),
|
|
423
434
|
envVariables,
|
|
424
435
|
},
|
package/src/index.ts
CHANGED
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
ensureFirstAdministrator,
|
|
16
16
|
type FirstAdministratorApi,
|
|
17
17
|
type FirstAdministratorResult,
|
|
18
|
+
platformOrigin,
|
|
18
19
|
} from '@fabrika/installation-init'
|
|
19
20
|
import type { PlatformAdminInput } from './admin-options'
|
|
20
21
|
import { REISSUE_FLAG } from './admin-options'
|
|
21
|
-
import { platformOrigin } from './hosts'
|
|
22
22
|
import { MACHINE_KEY_NEXT_STEP_TITLE, machineKeyNextStepLines } from './machine-key'
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
// The command is unattended by construction: nothing here prompts, and a missing required value is an
|
|
13
13
|
// error naming both the flag and the variable that would supply it.
|
|
14
14
|
|
|
15
|
-
import { describeArgument, readEnv, requiredSetting, setting } from '@fabrika/installation-init'
|
|
16
|
-
import { type McpClient, parseMcpClientsJson } from './mcp'
|
|
15
|
+
import { describeArgument, type McpClient, parseMcpClientsJson, readEnv, requiredSetting, setting } from '@fabrika/installation-init'
|
|
17
16
|
import { parsePlatformHosts, parsePlatformScheme } from './placement-options'
|
|
18
17
|
import { readInstallationSourceDir } from './source-options'
|
|
19
18
|
|