@forgezero/agent 0.1.53 → 0.1.55
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/README.md +118 -9
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +110 -69
- package/dist/community-rehearsal-host.js +37 -5
- package/dist/container-supervisor.d.ts +36 -0
- package/dist/definition.js +37 -5
- package/dist/deploy-actions.d.ts +87 -0
- package/dist/deploy-compiler.d.ts +26 -0
- package/dist/deploy-compiler.js +1050 -0
- package/dist/deploy-file.js +44 -12
- package/dist/deploy-plan-runner.d.ts +65 -0
- package/dist/deploy-plan-runner.js +1205 -0
- package/dist/deploy-plan.d.ts +41 -0
- package/dist/deploy-plan.js +925 -0
- package/dist/deploy-providers.d.ts +85 -0
- package/dist/deploy.d.ts +439 -0
- package/dist/deploy.js +169 -0
- package/dist/deployment.d.ts +5 -0
- package/dist/fz-agent.js +2113 -326
- package/dist/fz.js +1444 -313
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +167 -126
- package/dist/platform-bootstrap-runtime.js +1 -1
- package/dist/platform-fleet-verification.js +499 -120
- package/dist/provision.js +399 -20
- package/dist/software-helper.d.ts +5 -0
- package/dist/software-helper.js +399 -18
- package/dist/software.d.ts +1 -1
- package/dist/software.js +37 -5
- package/dist/version.d.ts +1 -1
- package/package.json +17 -1
package/README.md
CHANGED
|
@@ -59,7 +59,11 @@ Every row links to the detailed explanation and named-import/example area below.
|
|
|
59
59
|
| @forgezero/agent/mesh-connector | Fixed Cloudflare Mesh/WARP connector enrollment using only the unit-loaded connector credential. | Bun/Node host | [Details + example](#forgezero-agent-mesh-connector) |
|
|
60
60
|
| @forgezero/agent/compute | Compute claim, renewal, execution and completion client contracts. | Bun/Node host | [Details + example](#forgezero-agent-compute) |
|
|
61
61
|
| @forgezero/agent/credential-schema | Canonical systemd credential names and validation shared by bootstrap and services. | Bun/Node host | [Details + example](#forgezero-agent-credential-schema) |
|
|
62
|
-
| @forgezero/agent/definition |
|
|
62
|
+
| @forgezero/agent/definition | Legacy version 3 JSON deployment compatibility contract. | Bun/Node host | [Details + example](#forgezero-agent-definition) |
|
|
63
|
+
| @forgezero/agent/deploy | Typed deployment authoring builders, built-in provider requirements and versioned action constructors. | Bun/Node host | [Details + example](#forgezero-agent-deploy) |
|
|
64
|
+
| @forgezero/agent/deploy-plan | Runtime validation, canonicalization and semantic digest for inert deployment plans. | Bun/Node host | [Details + example](#forgezero-agent-deploy-plan) |
|
|
65
|
+
| @forgezero/agent/deploy-plan-runner | Conditional DAG execution with bounded retry, compensation and failure policy over injected action handlers. | Bun/Node host | [Details + example](#forgezero-agent-deploy-plan-runner) |
|
|
66
|
+
| @forgezero/agent/deploy-compiler | Explicit developer/CI compiler from forgezero.deploy.ts to canonical .fz/deploy.plan.json; never used to execute project TypeScript in production. | Bun/Node host | [Details + example](#forgezero-agent-deploy-compiler) |
|
|
63
67
|
| @forgezero/agent/deploy-file | Read and validate a checked-out .fz deployment file without executing project input. | Bun/Node host | [Details + example](#forgezero-agent-deploy-file) |
|
|
64
68
|
| @forgezero/agent/metal-bootstrap | Physical-metal bootstrap planning, application and status evidence. | Bun/Node host | [Details + example](#forgezero-agent-metal-bootstrap) |
|
|
65
69
|
| @forgezero/agent/metal-provision | Validated confidential guest provisioning profiles and cloud-init rendering. | Bun/Node host | [Details + example](#forgezero-agent-metal-provision) |
|
|
@@ -75,6 +79,9 @@ Every row links to the detailed explanation and named-import/example area below.
|
|
|
75
79
|
## Commands
|
|
76
80
|
|
|
77
81
|
bun add -g @forgezero/agent — Install the version-matched fz operator CLI and fz-agent daemon.
|
|
82
|
+
fz deploy init — Create a typed forgezero.deploy.ts and its canonical inert execution plan.
|
|
83
|
+
fz deploy compile — Compile and validate TypeScript deployment intent into .fz/deploy.plan.json.
|
|
84
|
+
fz deploy check — Refuse stale plans, unsafe provider/action coordinates and unresolved deployment blockers.
|
|
78
85
|
fz bootstrap platform remote prepare --bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json — Dry-plan one pinned operator-to-guest bootstrap; add --apply only after review.
|
|
79
86
|
fz bootstrap platform --help — Inspect attended platform bootstrap inputs before applying them.
|
|
80
87
|
fz bootstrap metal --help — Inspect the identity-only physical provisioner bootstrap.
|
|
@@ -83,6 +90,9 @@ fz status — Read installed service and bootstrap evidence.
|
|
|
83
90
|
|
|
84
91
|
```text
|
|
85
92
|
bun add -g @forgezero/agent
|
|
93
|
+
fz deploy init
|
|
94
|
+
fz deploy compile
|
|
95
|
+
fz deploy check
|
|
86
96
|
fz bootstrap platform remote prepare --bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json
|
|
87
97
|
fz bootstrap platform --help
|
|
88
98
|
fz bootstrap metal --help
|
|
@@ -393,13 +403,111 @@ import type { AgentCredentialLocation, DeploymentCredentialBinding, DeploymentCr
|
|
|
393
403
|
<a id="forgezero-agent-definition"></a>
|
|
394
404
|
## @forgezero/agent/definition
|
|
395
405
|
|
|
396
|
-
|
|
406
|
+
Legacy version 3 JSON deployment compatibility contract. Named value imports: DEPLOY_SCHEMA_URL, DefinitionError, PIPELINE_VERSION, parseDeployDefinition, phasePipeline, validateDeploymentService. Named type imports: DeployDefinition, DeployStep, DeploymentPortAllocation, DeploymentService, PipelineProfile. Import only the names used by this file.
|
|
397
407
|
|
|
398
408
|
```text
|
|
399
409
|
import { DEPLOY_SCHEMA_URL, DefinitionError, PIPELINE_VERSION, parseDeployDefinition, phasePipeline, validateDeploymentService } from '@forgezero/agent/definition';
|
|
400
410
|
import type { DeployDefinition, DeployStep, DeploymentPortAllocation, DeploymentService, PipelineProfile } from '@forgezero/agent/definition';
|
|
401
411
|
```
|
|
402
412
|
|
|
413
|
+
<a id="forgezero-agent-deploy"></a>
|
|
414
|
+
## @forgezero/agent/deploy
|
|
415
|
+
|
|
416
|
+
Typed deployment authoring builders, built-in provider requirements and versioned action constructors. Named value imports: actions, application, conditions, credential, database, defineDeployment, input, providers, ref, requirement, stage, step, target, workflow. Named type imports: ApplicationComponent, ApplicationRuntime, ArangoDbProviderConfig, BunProviderConfig, CloudflaredProviderConfig, ComponentDefinition, Condition, ConditionOperand, CredentialDefinition, DatabaseComponent, DeploymentSource, DockerProviderConfig, HealthDefinition, InputDefinition, JsonObject, JsonPrimitive, JsonValue, NetworkDefinition, NginxProviderConfig, OpenSshProviderConfig, RequirementDefinition, ResolvableJsonObject, ResolvableJsonValue, ResourceDefinition, RetryDefinition, StageDefinition, StepScope, StorageMount, SystemdProviderConfig, TargetDefinition, UfwProviderConfig, ValueReference, WarpProviderConfig, WorkflowDefinition, WorkflowStepDefinition. Import only the names used by this file.
|
|
417
|
+
|
|
418
|
+
```text
|
|
419
|
+
import { actions, application, conditions, credential, database, defineDeployment } from '@forgezero/agent/deploy';
|
|
420
|
+
import { input, providers, ref, requirement, stage, step } from '@forgezero/agent/deploy';
|
|
421
|
+
import { target, workflow } from '@forgezero/agent/deploy';
|
|
422
|
+
import type { ApplicationComponent, ApplicationRuntime, ArangoDbProviderConfig, BunProviderConfig, CloudflaredProviderConfig, ComponentDefinition } from '@forgezero/agent/deploy';
|
|
423
|
+
import type { Condition, ConditionOperand, CredentialDefinition, DatabaseComponent, DeploymentSource, DockerProviderConfig } from '@forgezero/agent/deploy';
|
|
424
|
+
import type { HealthDefinition, InputDefinition, JsonObject, JsonPrimitive, JsonValue, NetworkDefinition } from '@forgezero/agent/deploy';
|
|
425
|
+
import type { NginxProviderConfig, OpenSshProviderConfig, RequirementDefinition, ResolvableJsonObject, ResolvableJsonValue, ResourceDefinition } from '@forgezero/agent/deploy';
|
|
426
|
+
import type { RetryDefinition, StageDefinition, StepScope, StorageMount, SystemdProviderConfig, TargetDefinition } from '@forgezero/agent/deploy';
|
|
427
|
+
import type { UfwProviderConfig, ValueReference, WarpProviderConfig, WorkflowDefinition, WorkflowStepDefinition } from '@forgezero/agent/deploy';
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## @forgezero/agent/deploy — Author a typed native or Docker deployment
|
|
431
|
+
|
|
432
|
+
TypeScript is authoring only. The checked-in canonical plan is what Agents validate and execute. Docker is additive: native Bun/ArangoDB remains available, while container applications must declare hard CPU, memory and PID limits, storage class, loopback ingress and a fixed health gate.
|
|
433
|
+
|
|
434
|
+
```text
|
|
435
|
+
import { actions, application, defineDeployment, providers, stage, target, workflow } from '@forgezero/agent/deploy';
|
|
436
|
+
|
|
437
|
+
export default defineDeployment({
|
|
438
|
+
apiVersion: 'deploy.forgezero.net/v1', kind: 'Deployment',
|
|
439
|
+
metadata: { name: 'orders-api' },
|
|
440
|
+
spec: {
|
|
441
|
+
targets: { api: target.compute({ selector: { profiles: ['api'] }, cardinality: { minimum: 1, desired: 2, maximum: 16 } }) },
|
|
442
|
+
requirements: { docker: providers.docker.require(), nginx: providers.nginx.require() },
|
|
443
|
+
components: {
|
|
444
|
+
api: application({
|
|
445
|
+
target: 'api',
|
|
446
|
+
runtime: { kind: 'container', provider: 'forgezero.docker', requirement: 'docker', image: { source: { kind: 'build', context: '.', dockerfile: 'Dockerfile' } }, security: { privileged: false, noNewPrivileges: true, root: 'read-only', dropCapabilities: ['ALL'] } },
|
|
447
|
+
service: { protocol: 'http', port: 3000, health: { protocol: 'http', method: 'GET', path: '/health', expectedStatus: [200], timeoutMs: 2000 } },
|
|
448
|
+
resources: { cpu: { limit: 2 }, memory: { limitMiB: 2048, swap: 'disabled' }, pids: { limit: 256 } },
|
|
449
|
+
storage: [{ class: 'ephemeral', path: '/tmp', type: 'tmpfs', sizeMiB: 128 }],
|
|
450
|
+
network: { ingress: { exposure: 'loopback', stablePort: 3000 }, container: { mode: 'bridge', network: 'api' } },
|
|
451
|
+
rollout: { strategy: 'blue-green', proxy: 'nginx', automaticRollback: true }
|
|
452
|
+
})
|
|
453
|
+
},
|
|
454
|
+
workflows: { deploy: workflow({ stages: { prepare: stage({ strategy: { mode: 'sequential' }, steps: { software: actions.software.ensure({ requirements: ['docker', 'nginx'] }, { scope: { kind: 'each-target', target: 'api' } }) } }) } }) }
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
<a id="forgezero-agent-deploy-plan"></a>
|
|
460
|
+
## @forgezero/agent/deploy-plan
|
|
461
|
+
|
|
462
|
+
Runtime validation, canonicalization and semantic digest for inert deployment plans. Named value imports: DEPLOY_PLAN_FORMAT, DEPLOY_PLAN_VERSION, DeploymentPlanError, canonicalJson, compileDeployment, deploymentPlanDigest, deploymentSourceDigest, parseDeploymentPlan. Named type imports: DeploymentPlan, PlannedStage, PlannedStep, PlannedWorkflow. Import only the names used by this file.
|
|
463
|
+
|
|
464
|
+
```text
|
|
465
|
+
import { DEPLOY_PLAN_FORMAT, DEPLOY_PLAN_VERSION, DeploymentPlanError, canonicalJson, compileDeployment, deploymentPlanDigest } from '@forgezero/agent/deploy-plan';
|
|
466
|
+
import { deploymentSourceDigest, parseDeploymentPlan } from '@forgezero/agent/deploy-plan';
|
|
467
|
+
import type { DeploymentPlan, PlannedStage, PlannedStep, PlannedWorkflow } from '@forgezero/agent/deploy-plan';
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
<a id="forgezero-agent-deploy-plan-runner"></a>
|
|
471
|
+
## @forgezero/agent/deploy-plan-runner
|
|
472
|
+
|
|
473
|
+
Conditional DAG execution with bounded retry, compensation and failure policy over injected action handlers. Named value imports: DeploymentActionError, evaluateCondition, runDeploymentPlan. Named type imports: ActionContext, ActionExecutionResult, ActionFailureKind, ActionHandler, PlanRunResult, PlanStepStatus, RunDeploymentPlanOptions, StepExecutionRecord. Import only the names used by this file.
|
|
474
|
+
|
|
475
|
+
```text
|
|
476
|
+
import { DeploymentActionError, evaluateCondition, runDeploymentPlan } from '@forgezero/agent/deploy-plan-runner';
|
|
477
|
+
import type { ActionContext, ActionExecutionResult, ActionFailureKind, ActionHandler, PlanRunResult, PlanStepStatus } from '@forgezero/agent/deploy-plan-runner';
|
|
478
|
+
import type { RunDeploymentPlanOptions, StepExecutionRecord } from '@forgezero/agent/deploy-plan-runner';
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## @forgezero/agent/deploy-plan-runner — Extend the plan with a namespaced action
|
|
482
|
+
|
|
483
|
+
Public projects can define a versioned action coordinate and install an exact handler. Built-in forgezero.* coordinates stay closed; unknown built-ins fail validation.
|
|
484
|
+
|
|
485
|
+
```text
|
|
486
|
+
import { actions } from '@forgezero/agent/deploy';
|
|
487
|
+
import { runDeploymentPlan } from '@forgezero/agent/deploy-plan-runner';
|
|
488
|
+
|
|
489
|
+
const notify = actions.extension('example.notifications/publish@1', { channel: 'deployments' }, {
|
|
490
|
+
scope: { kind: 'release-executor' },
|
|
491
|
+
retry: { attempts: 3, backoff: { kind: 'exponential', initialMs: 250, maximumMs: 5000 }, retryOn: ['network'] }
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
await runDeploymentPlan({
|
|
495
|
+
plan, workflow: 'deploy', inputs: {},
|
|
496
|
+
actions: { 'example.notifications/publish@1': async ({ resolved }) => ({ changed: true, evidence: resolved }) }
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
<a id="forgezero-agent-deploy-compiler"></a>
|
|
501
|
+
## @forgezero/agent/deploy-compiler
|
|
502
|
+
|
|
503
|
+
Explicit developer/CI compiler from forgezero.deploy.ts to canonical .fz/deploy.plan.json; never used to execute project TypeScript in production. Named value imports: DEPLOY_PLAN_FILE, DEPLOY_SOURCE_FILE, compileDeploymentProject, defaultTypeScriptDeployment, initializeTypeScriptDeployment, inspectCompiledDeployment, loadDeploymentSource. Named type imports: DeploymentCompilation. Import only the names used by this file.
|
|
504
|
+
|
|
505
|
+
```text
|
|
506
|
+
import { DEPLOY_PLAN_FILE, DEPLOY_SOURCE_FILE, compileDeploymentProject, defaultTypeScriptDeployment, initializeTypeScriptDeployment, inspectCompiledDeployment } from '@forgezero/agent/deploy-compiler';
|
|
507
|
+
import { loadDeploymentSource } from '@forgezero/agent/deploy-compiler';
|
|
508
|
+
import type { DeploymentCompilation } from '@forgezero/agent/deploy-compiler';
|
|
509
|
+
```
|
|
510
|
+
|
|
403
511
|
<a id="forgezero-agent-deploy-file"></a>
|
|
404
512
|
## @forgezero/agent/deploy-file
|
|
405
513
|
|
|
@@ -414,11 +522,11 @@ import type { DeployFileSummary, InitializedDeployFile } from '@forgezero/agent/
|
|
|
414
522
|
<a id="forgezero-agent-metal-bootstrap"></a>
|
|
415
523
|
## @forgezero/agent/metal-bootstrap
|
|
416
524
|
|
|
417
|
-
Physical-metal bootstrap planning, application and status evidence. Named value imports: METAL_BOOTSTRAP_STATE_PATH, MetalBootstrapError, applyMetalBootstrap, metalBootstrapStatus, planMetalBootstrap, readMetalBootstrapConfig, renderMetalUnits, validateMetalBootstrapConfig, validateOwnerOnlyPath. Named type imports: MetalBootstrapApplyOptions, MetalBootstrapConfig, MetalBootstrapExec, MetalBootstrapPlan, MetalBootstrapResult, MetalBootstrapStatus. Import only the names used by this file.
|
|
525
|
+
Physical-metal bootstrap planning, application and status evidence. Named value imports: METAL_BOOTSTRAP_STATE_PATH, MetalBootstrapError, applyMetalBootstrap, metalBootstrapStatus, normalizeMetalPublicIdentity, planMetalBootstrap, readMetalBootstrapConfig, renderMetalUnits, validateMetalBootstrapConfig, validateOwnerOnlyPath. Named type imports: MetalBootstrapApplyOptions, MetalBootstrapConfig, MetalBootstrapExec, MetalBootstrapPlan, MetalBootstrapResult, MetalBootstrapStatus. Import only the names used by this file.
|
|
418
526
|
|
|
419
527
|
```text
|
|
420
|
-
import { METAL_BOOTSTRAP_STATE_PATH, MetalBootstrapError, applyMetalBootstrap, metalBootstrapStatus,
|
|
421
|
-
import { renderMetalUnits, validateMetalBootstrapConfig, validateOwnerOnlyPath } from '@forgezero/agent/metal-bootstrap';
|
|
528
|
+
import { METAL_BOOTSTRAP_STATE_PATH, MetalBootstrapError, applyMetalBootstrap, metalBootstrapStatus, normalizeMetalPublicIdentity, planMetalBootstrap } from '@forgezero/agent/metal-bootstrap';
|
|
529
|
+
import { readMetalBootstrapConfig, renderMetalUnits, validateMetalBootstrapConfig, validateOwnerOnlyPath } from '@forgezero/agent/metal-bootstrap';
|
|
422
530
|
import type { MetalBootstrapApplyOptions, MetalBootstrapConfig, MetalBootstrapExec, MetalBootstrapPlan, MetalBootstrapResult, MetalBootstrapStatus } from '@forgezero/agent/metal-bootstrap';
|
|
423
531
|
```
|
|
424
532
|
|
|
@@ -491,11 +599,11 @@ if (!validate(deploymentDefinition)) throw new Error(JSON.stringify(validate.err
|
|
|
491
599
|
<a id="forgezero-agent-software"></a>
|
|
492
600
|
## @forgezero/agent/software
|
|
493
601
|
|
|
494
|
-
Exact managed-software inventory, requirement and evidence contracts. Named value imports: BUN_RELEASE_SHA256, OS_CATALOG, PINNED_BUN_VERSION, SOFTWARE_CATALOG, ensureSoftwareRequirements, executeSoftwareOperation, observeSoftwareHost, validateSoftwareRequirements. Named type imports: CatalogStatus, DeploymentChannel, OsCatalogEntry, SoftwareCatalogEntry, SoftwareCommandResult, SoftwareExec, SoftwareId, SoftwareObservation, SoftwareOperation, SoftwareRequirement. Import only the names used by this file.
|
|
602
|
+
Exact managed-software inventory, requirement and evidence contracts. Named value imports: BUN_RELEASE_SHA256, OS_CATALOG, PINNED_BUN_VERSION, SOFTWARE_CATALOG, ensureSoftwareRequirements, executeSoftwareOperation, observeSoftwareHost, runSoftwareCommand, softwareSpawnFailure, validateSoftwareRequirements. Named type imports: CatalogStatus, DeploymentChannel, OsCatalogEntry, SoftwareCatalogEntry, SoftwareCommandResult, SoftwareExec, SoftwareId, SoftwareObservation, SoftwareOperation, SoftwareRequirement. Import only the names used by this file.
|
|
495
603
|
|
|
496
604
|
```text
|
|
497
605
|
import { BUN_RELEASE_SHA256, OS_CATALOG, PINNED_BUN_VERSION, SOFTWARE_CATALOG, ensureSoftwareRequirements, executeSoftwareOperation } from '@forgezero/agent/software';
|
|
498
|
-
import { observeSoftwareHost, validateSoftwareRequirements } from '@forgezero/agent/software';
|
|
606
|
+
import { observeSoftwareHost, runSoftwareCommand, softwareSpawnFailure, validateSoftwareRequirements } from '@forgezero/agent/software';
|
|
499
607
|
import type { CatalogStatus, DeploymentChannel, OsCatalogEntry, SoftwareCatalogEntry, SoftwareCommandResult, SoftwareExec } from '@forgezero/agent/software';
|
|
500
608
|
import type { SoftwareId, SoftwareObservation, SoftwareOperation, SoftwareRequirement } from '@forgezero/agent/software';
|
|
501
609
|
```
|
|
@@ -503,10 +611,11 @@ import type { SoftwareId, SoftwareObservation, SoftwareOperation, SoftwareRequir
|
|
|
503
611
|
<a id="forgezero-agent-software-helper"></a>
|
|
504
612
|
## @forgezero/agent/software-helper
|
|
505
613
|
|
|
506
|
-
Root-owned fixed installation/update boundary for declared software. Named value imports: DEFAULT_SOFTWARE_HELPER_SOCKET, SOFTWARE_HELPER_GROUP, SOFTWARE_HELPER_UNIT_PATH, requestServiceActivation, requestSoftware, startSoftwareHelper. Named type imports: none. Import only the names used by this file.
|
|
614
|
+
Root-owned fixed installation/update boundary for declared software. Named value imports: DEFAULT_SOFTWARE_HELPER_SOCKET, SOFTWARE_HELPER_GROUP, SOFTWARE_HELPER_UNIT_PATH, requestContainerActivation, requestContainerBuild, requestServiceActivation, requestSoftware, startSoftwareHelper. Named type imports: none. Import only the names used by this file.
|
|
507
615
|
|
|
508
616
|
```text
|
|
509
|
-
import { DEFAULT_SOFTWARE_HELPER_SOCKET, SOFTWARE_HELPER_GROUP, SOFTWARE_HELPER_UNIT_PATH,
|
|
617
|
+
import { DEFAULT_SOFTWARE_HELPER_SOCKET, SOFTWARE_HELPER_GROUP, SOFTWARE_HELPER_UNIT_PATH, requestContainerActivation, requestContainerBuild, requestServiceActivation } from '@forgezero/agent/software-helper';
|
|
618
|
+
import { requestSoftware, startSoftwareHelper } from '@forgezero/agent/software-helper';
|
|
510
619
|
```
|
|
511
620
|
|
|
512
621
|
<a id="forgezero-agent-ubuntu"></a>
|
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -1180,15 +1180,15 @@ async function finalizeCloudflareBootstrapAcceptance(request, fetcher = fetch) {
|
|
|
1180
1180
|
import { createHash, createHmac, randomBytes as randomBytes3 } from "crypto";
|
|
1181
1181
|
import {
|
|
1182
1182
|
chmodSync as chmodSync2,
|
|
1183
|
-
existsSync as
|
|
1183
|
+
existsSync as existsSync4,
|
|
1184
1184
|
lstatSync as lstatSync2,
|
|
1185
|
-
mkdirSync as
|
|
1186
|
-
readFileSync as
|
|
1187
|
-
renameSync as
|
|
1188
|
-
rmSync as
|
|
1189
|
-
writeFileSync as
|
|
1185
|
+
mkdirSync as mkdirSync4,
|
|
1186
|
+
readFileSync as readFileSync4,
|
|
1187
|
+
renameSync as renameSync4,
|
|
1188
|
+
rmSync as rmSync4,
|
|
1189
|
+
writeFileSync as writeFileSync4
|
|
1190
1190
|
} from "fs";
|
|
1191
|
-
import { dirname as
|
|
1191
|
+
import { dirname as dirname5 } from "path";
|
|
1192
1192
|
import { fileURLToPath } from "url";
|
|
1193
1193
|
|
|
1194
1194
|
// src/agent-update-helper.ts
|
|
@@ -1207,11 +1207,19 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1207
1207
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1208
1208
|
|
|
1209
1209
|
// src/version.ts
|
|
1210
|
-
var VERSION = "0.1.
|
|
1210
|
+
var VERSION = "0.1.55";
|
|
1211
1211
|
|
|
1212
1212
|
// src/software.ts
|
|
1213
1213
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
1214
1214
|
var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
|
|
1215
|
+
var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
|
|
1216
|
+
"data-root": "/var/lib/docker",
|
|
1217
|
+
"storage-driver": "overlay2",
|
|
1218
|
+
"live-restore": true,
|
|
1219
|
+
"log-driver": "local",
|
|
1220
|
+
"log-opts": { "max-size": "10m", "max-file": "3" }
|
|
1221
|
+
}, null, 2)}
|
|
1222
|
+
`;
|
|
1215
1223
|
|
|
1216
1224
|
// src/service-supervisor.ts
|
|
1217
1225
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
@@ -1273,6 +1281,39 @@ var defaultHost = {
|
|
|
1273
1281
|
now: Date.now
|
|
1274
1282
|
};
|
|
1275
1283
|
|
|
1284
|
+
// src/container-supervisor.ts
|
|
1285
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1286
|
+
import { dirname as dirname3, resolve as resolve3, sep as sep2 } from "path";
|
|
1287
|
+
var defaultHost2 = {
|
|
1288
|
+
realpath: realpathSync2,
|
|
1289
|
+
exists: existsSync2,
|
|
1290
|
+
read: (path) => readFileSync2(path, "utf8"),
|
|
1291
|
+
write(path, content, mode) {
|
|
1292
|
+
mkdirSync2(dirname3(path), { recursive: true, mode: 493 });
|
|
1293
|
+
const next = `${path}.next`;
|
|
1294
|
+
writeFileSync2(next, content, { mode });
|
|
1295
|
+
renameSync2(next, path);
|
|
1296
|
+
},
|
|
1297
|
+
remove: (path) => rmSync2(path, { force: true }),
|
|
1298
|
+
list: (path) => existsSync2(path) ? readdirSync2(path) : [],
|
|
1299
|
+
mkdir: (path, mode) => mkdirSync2(path, { recursive: true, mode }),
|
|
1300
|
+
async exec(argv) {
|
|
1301
|
+
const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" } });
|
|
1302
|
+
const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
|
|
1303
|
+
return { exitCode, output: `${stdout}${stderr}` };
|
|
1304
|
+
},
|
|
1305
|
+
async health(port, path, timeoutMs, method, expectedStatus) {
|
|
1306
|
+
try {
|
|
1307
|
+
const response = await fetch(`http://127.0.0.1:${port}${path}`, { method, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
|
|
1308
|
+
return expectedStatus.includes(response.status);
|
|
1309
|
+
} catch {
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
sleep: (ms) => Bun.sleep(ms),
|
|
1314
|
+
now: Date.now
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1276
1317
|
// src/software-helper.ts
|
|
1277
1318
|
var DEFAULT_SOFTWARE_HELPER_SOCKET = "/run/forgezero-software/helper.sock";
|
|
1278
1319
|
var SOFTWARE_HELPER_GROUP = "forgezero-software";
|
|
@@ -2255,17 +2296,17 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
2255
2296
|
import {
|
|
2256
2297
|
chmodSync,
|
|
2257
2298
|
copyFileSync,
|
|
2258
|
-
existsSync as
|
|
2299
|
+
existsSync as existsSync3,
|
|
2259
2300
|
lstatSync,
|
|
2260
|
-
mkdirSync as
|
|
2261
|
-
readFileSync as
|
|
2262
|
-
realpathSync as
|
|
2263
|
-
renameSync as
|
|
2264
|
-
rmSync as
|
|
2301
|
+
mkdirSync as mkdirSync3,
|
|
2302
|
+
readFileSync as readFileSync3,
|
|
2303
|
+
realpathSync as realpathSync3,
|
|
2304
|
+
renameSync as renameSync3,
|
|
2305
|
+
rmSync as rmSync3,
|
|
2265
2306
|
symlinkSync,
|
|
2266
|
-
writeFileSync as
|
|
2307
|
+
writeFileSync as writeFileSync3
|
|
2267
2308
|
} from "fs";
|
|
2268
|
-
import { dirname as
|
|
2309
|
+
import { dirname as dirname4 } from "path";
|
|
2269
2310
|
async function readCapabilities(run) {
|
|
2270
2311
|
const answers = {};
|
|
2271
2312
|
const checks = Object.entries(CAPABILITY_CHECKS);
|
|
@@ -2324,28 +2365,28 @@ var runProvisionOperation = async (operation) => {
|
|
|
2324
2365
|
}
|
|
2325
2366
|
if (operation.kind === "install-runtime") {
|
|
2326
2367
|
const release = `/opt/forgezero/agent/versions/${operation.version}`;
|
|
2327
|
-
|
|
2328
|
-
|
|
2368
|
+
mkdirSync3(`${release}/dist`, { recursive: true, mode: 493 });
|
|
2369
|
+
mkdirSync3(dirname4(operation.binary), { recursive: true, mode: 493 });
|
|
2329
2370
|
copyFileSync(operation.source, `${release}/dist/fz-agent.js`);
|
|
2330
2371
|
chmodSync(`${release}/dist/fz-agent.js`, 493);
|
|
2331
|
-
const gitSshSource = `${
|
|
2332
|
-
if (!
|
|
2372
|
+
const gitSshSource = `${dirname4(operation.source)}/fz-git-ssh.js`;
|
|
2373
|
+
if (!existsSync3(gitSshSource))
|
|
2333
2374
|
return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
|
|
2334
2375
|
copyFileSync(gitSshSource, `${release}/dist/fz-git-ssh.js`);
|
|
2335
2376
|
chmodSync(`${release}/dist/fz-git-ssh.js`, 493);
|
|
2336
2377
|
const pending = "/opt/forgezero/agent/current.next";
|
|
2337
|
-
|
|
2378
|
+
rmSync3(pending, { force: true });
|
|
2338
2379
|
symlinkSync(`versions/${operation.version}`, pending);
|
|
2339
|
-
|
|
2340
|
-
|
|
2380
|
+
renameSync3(pending, "/opt/forgezero/agent/current");
|
|
2381
|
+
rmSync3(operation.binary, { force: true });
|
|
2341
2382
|
symlinkSync("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
|
|
2342
2383
|
const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
|
|
2343
|
-
|
|
2384
|
+
rmSync3(gitSshBinary, { force: true });
|
|
2344
2385
|
symlinkSync("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
|
|
2345
2386
|
return { stdout: "", exitCode: 0 };
|
|
2346
2387
|
}
|
|
2347
2388
|
if (operation.kind === "ensure-seed") {
|
|
2348
|
-
if (
|
|
2389
|
+
if (existsSync3(operation.credential) && lstatSync(operation.credential).size > 0)
|
|
2349
2390
|
return { stdout: "", exitCode: 0 };
|
|
2350
2391
|
const seed = randomBytes2(32).toString("base64url");
|
|
2351
2392
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
|
|
@@ -2357,9 +2398,9 @@ var runProvisionOperation = async (operation) => {
|
|
|
2357
2398
|
const key = "/run/forgezero-git-deploy-key";
|
|
2358
2399
|
const publicKey = `${key}.pub`;
|
|
2359
2400
|
try {
|
|
2360
|
-
if (!
|
|
2361
|
-
|
|
2362
|
-
|
|
2401
|
+
if (!existsSync3(operation.credential) || lstatSync(operation.credential).size < 1) {
|
|
2402
|
+
rmSync3(key, { force: true });
|
|
2403
|
+
rmSync3(publicKey, { force: true });
|
|
2363
2404
|
let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
|
|
2364
2405
|
if (result.exitCode !== 0)
|
|
2365
2406
|
return result;
|
|
@@ -2368,8 +2409,8 @@ var runProvisionOperation = async (operation) => {
|
|
|
2368
2409
|
return result;
|
|
2369
2410
|
chmodSync(operation.credential, 256);
|
|
2370
2411
|
}
|
|
2371
|
-
if (!
|
|
2372
|
-
if (!
|
|
2412
|
+
if (!existsSync3(operation.publicKey) || lstatSync(operation.publicKey).size < 1) {
|
|
2413
|
+
if (!existsSync3(key)) {
|
|
2373
2414
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
|
|
2374
2415
|
if (decrypted.exitCode !== 0)
|
|
2375
2416
|
return decrypted;
|
|
@@ -2377,25 +2418,25 @@ var runProvisionOperation = async (operation) => {
|
|
|
2377
2418
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
2378
2419
|
if (derived.exitCode !== 0)
|
|
2379
2420
|
return derived;
|
|
2380
|
-
|
|
2421
|
+
writeFileSync3(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
|
|
2381
2422
|
`, { mode: 292 });
|
|
2382
2423
|
}
|
|
2383
2424
|
return { stdout: "", exitCode: 0 };
|
|
2384
2425
|
} finally {
|
|
2385
|
-
|
|
2386
|
-
|
|
2426
|
+
rmSync3(key, { force: true });
|
|
2427
|
+
rmSync3(publicKey, { force: true });
|
|
2387
2428
|
}
|
|
2388
2429
|
}
|
|
2389
2430
|
if (operation.kind === "ensure-bootstrap-ssh-identity") {
|
|
2390
2431
|
const key = "/run/forgezero-bootstrap-ssh-key";
|
|
2391
2432
|
const generatedPublicKey = `${key}.pub`;
|
|
2392
2433
|
try {
|
|
2393
|
-
if (!
|
|
2394
|
-
|
|
2395
|
-
|
|
2434
|
+
if (!existsSync3(operation.credential) || lstatSync(operation.credential).size < 1) {
|
|
2435
|
+
rmSync3(key, { force: true });
|
|
2436
|
+
rmSync3(generatedPublicKey, { force: true });
|
|
2396
2437
|
let result;
|
|
2397
2438
|
if (operation.source) {
|
|
2398
|
-
const source =
|
|
2439
|
+
const source = existsSync3(operation.source) ? lstatSync(operation.source) : undefined;
|
|
2399
2440
|
if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
|
|
2400
2441
|
return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
|
|
2401
2442
|
}
|
|
@@ -2415,8 +2456,8 @@ var runProvisionOperation = async (operation) => {
|
|
|
2415
2456
|
return result;
|
|
2416
2457
|
chmodSync(operation.credential, 256);
|
|
2417
2458
|
}
|
|
2418
|
-
if (!
|
|
2419
|
-
if (!
|
|
2459
|
+
if (!existsSync3(operation.publicKey) || lstatSync(operation.publicKey).size < 1) {
|
|
2460
|
+
if (!existsSync3(key)) {
|
|
2420
2461
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
|
|
2421
2462
|
if (decrypted.exitCode !== 0)
|
|
2422
2463
|
return decrypted;
|
|
@@ -2426,28 +2467,28 @@ var runProvisionOperation = async (operation) => {
|
|
|
2426
2467
|
if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
|
|
2427
2468
|
return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
|
|
2428
2469
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2470
|
+
mkdirSync3(dirname4(operation.publicKey), { recursive: true, mode: 493 });
|
|
2471
|
+
writeFileSync3(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
|
|
2431
2472
|
`, { mode: 292 });
|
|
2432
2473
|
chmodSync(operation.publicKey, 292);
|
|
2433
2474
|
}
|
|
2434
2475
|
if (operation.source)
|
|
2435
|
-
|
|
2476
|
+
rmSync3(operation.source, { force: true });
|
|
2436
2477
|
return { stdout: "", exitCode: 0 };
|
|
2437
2478
|
} finally {
|
|
2438
|
-
|
|
2439
|
-
|
|
2479
|
+
rmSync3(key, { force: true });
|
|
2480
|
+
rmSync3(generatedPublicKey, { force: true });
|
|
2440
2481
|
}
|
|
2441
2482
|
}
|
|
2442
2483
|
if (operation.kind === "ensure-enrolment") {
|
|
2443
|
-
if (
|
|
2484
|
+
if (existsSync3(operation.state) && lstatSync(operation.state).size > 0 || existsSync3(operation.credential) && lstatSync(operation.credential).size > 0)
|
|
2444
2485
|
return { stdout: "", exitCode: 0 };
|
|
2445
|
-
if (!
|
|
2486
|
+
if (!existsSync3(operation.source))
|
|
2446
2487
|
return { stdout: "enrolment source is missing", exitCode: 1 };
|
|
2447
2488
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
|
|
2448
2489
|
if (result.exitCode === 0) {
|
|
2449
2490
|
chmodSync(operation.credential, 256);
|
|
2450
|
-
|
|
2491
|
+
rmSync3(operation.source, { force: true });
|
|
2451
2492
|
}
|
|
2452
2493
|
return result;
|
|
2453
2494
|
}
|
|
@@ -2462,7 +2503,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
2462
2503
|
return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
|
|
2463
2504
|
}
|
|
2464
2505
|
if (operation.kind === "verify-file")
|
|
2465
|
-
return
|
|
2506
|
+
return existsSync3(operation.path) && lstatSync(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
|
|
2466
2507
|
if (operation.kind === "verify-egress") {
|
|
2467
2508
|
const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
|
|
2468
2509
|
if (active.exitCode !== 0)
|
|
@@ -2478,31 +2519,31 @@ var runProvisionOperation = async (operation) => {
|
|
|
2478
2519
|
if (operation.kind === "verify-resolved-stub") {
|
|
2479
2520
|
try {
|
|
2480
2521
|
const expected = "/run/systemd/resolve/stub-resolv.conf";
|
|
2481
|
-
return
|
|
2522
|
+
return realpathSync3("/etc/resolv.conf") === expected && realpathSync3(expected) === expected ? { stdout: expected, exitCode: 0 } : { stdout: "resolver stub mismatch", exitCode: 1 };
|
|
2482
2523
|
} catch {
|
|
2483
2524
|
return { stdout: "resolver stub missing", exitCode: 1 };
|
|
2484
2525
|
}
|
|
2485
2526
|
}
|
|
2486
2527
|
if (operation.kind === "install-warp") {
|
|
2487
|
-
const os =
|
|
2528
|
+
const os = readFileSync3("/etc/os-release", "utf8");
|
|
2488
2529
|
if (!/^ID=ubuntu$/m.test(os) || !/^VERSION_ID="?26\.04"?$/m.test(os))
|
|
2489
2530
|
return { stdout: "unsupported WARP host OS", exitCode: 1 };
|
|
2490
2531
|
const response = await fetch("https://pkg.cloudflareclient.com/pubkey.gpg", { signal: AbortSignal.timeout(30000) });
|
|
2491
2532
|
if (!response.ok)
|
|
2492
2533
|
return { stdout: `WARP key HTTP ${response.status}`, exitCode: 1 };
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2534
|
+
mkdirSync3("/usr/share/keyrings", { recursive: true, mode: 493 });
|
|
2535
|
+
mkdirSync3("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
|
|
2536
|
+
mkdirSync3("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
|
|
2496
2537
|
const key = "/run/cloudflare-warp-key.gpg";
|
|
2497
|
-
|
|
2538
|
+
writeFileSync3(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
|
|
2498
2539
|
let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
|
|
2499
|
-
|
|
2540
|
+
rmSync3(key, { force: true });
|
|
2500
2541
|
if (result.exitCode !== 0)
|
|
2501
2542
|
return result;
|
|
2502
2543
|
const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
|
|
2503
2544
|
if (!codename)
|
|
2504
2545
|
return { stdout: "Ubuntu codename missing", exitCode: 1 };
|
|
2505
|
-
|
|
2546
|
+
writeFileSync3("/etc/apt/sources.list.d/cloudflare-client.list", `deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${codename} main
|
|
2506
2547
|
`, { mode: 420 });
|
|
2507
2548
|
result = await fixed(["/usr/bin/apt-get", "update", "-qq"]);
|
|
2508
2549
|
return result.exitCode === 0 ? fixed(["/usr/bin/apt-get", "install", "-y", "cloudflare-warp"]) : result;
|
|
@@ -4175,7 +4216,7 @@ function readBootstrapConfig(path) {
|
|
|
4175
4216
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== (process.getuid?.() ?? metadata.uid) || metadata.nlink !== 1 || (metadata.mode & 63) !== 0 || metadata.size > 64 * 1024) {
|
|
4176
4217
|
throw new Error("bootstrap config must be an owner-only regular file with one link and at most 64 KiB");
|
|
4177
4218
|
}
|
|
4178
|
-
return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(
|
|
4219
|
+
return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync4(path, "utf8"))));
|
|
4179
4220
|
}
|
|
4180
4221
|
function localBootstrapHost() {
|
|
4181
4222
|
const execute = async (argv, options = {}) => {
|
|
@@ -4193,17 +4234,17 @@ function localBootstrapHost() {
|
|
|
4193
4234
|
};
|
|
4194
4235
|
return {
|
|
4195
4236
|
uid: () => process.getuid?.() ?? -1,
|
|
4196
|
-
exists:
|
|
4197
|
-
read: (path) =>
|
|
4237
|
+
exists: existsSync4,
|
|
4238
|
+
read: (path) => readFileSync4(path, "utf8"),
|
|
4198
4239
|
write(path, content, mode) {
|
|
4199
|
-
|
|
4240
|
+
mkdirSync4(dirname5(path), { recursive: true, mode: 493 });
|
|
4200
4241
|
const temporary = `${path}.next.${process.pid}`;
|
|
4201
|
-
|
|
4242
|
+
writeFileSync4(temporary, content, { mode });
|
|
4202
4243
|
chmodSync2(temporary, mode);
|
|
4203
|
-
|
|
4244
|
+
renameSync4(temporary, path);
|
|
4204
4245
|
},
|
|
4205
|
-
mkdir: (path, mode) =>
|
|
4206
|
-
remove: (path) =>
|
|
4246
|
+
mkdir: (path, mode) => mkdirSync4(path, { recursive: true, mode }),
|
|
4247
|
+
remove: (path) => rmSync4(path, { force: true }),
|
|
4207
4248
|
inspect(path) {
|
|
4208
4249
|
const value = lstatSync2(path);
|
|
4209
4250
|
return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
|
|
@@ -4227,7 +4268,7 @@ function localBootstrapHost() {
|
|
|
4227
4268
|
async installAgent(config, enrolTokenSourcePath) {
|
|
4228
4269
|
const capabilities = await readCapabilities(localRunner);
|
|
4229
4270
|
const deployRoot = config.deployRoot ?? "/opt/forgezero";
|
|
4230
|
-
const hasBinding = config.kind === "enrolled-compute" || Boolean(enrolTokenSourcePath) ||
|
|
4271
|
+
const hasBinding = config.kind === "enrolled-compute" || Boolean(enrolTokenSourcePath) || existsSync4("/var/lib/forgezero/enrolment.json");
|
|
4231
4272
|
if (config.kind === "platform") {
|
|
4232
4273
|
const lifecycle = config.database.role === "none" ? {
|
|
4233
4274
|
apiUnits: ["forgezero@blue.service", "forgezero@green.service"],
|
|
@@ -4239,8 +4280,8 @@ function localBootstrapHost() {
|
|
|
4239
4280
|
databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
|
|
4240
4281
|
databasePorts: [8529]
|
|
4241
4282
|
};
|
|
4242
|
-
|
|
4243
|
-
|
|
4283
|
+
mkdirSync4(dirname5(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
|
|
4284
|
+
writeFileSync4(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
4244
4285
|
`, { mode: 256 });
|
|
4245
4286
|
}
|
|
4246
4287
|
const plan = planInstall({
|
|
@@ -4281,8 +4322,8 @@ function localBootstrapHost() {
|
|
|
4281
4322
|
} : {}
|
|
4282
4323
|
});
|
|
4283
4324
|
for (const unit of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
4284
|
-
|
|
4285
|
-
|
|
4325
|
+
mkdirSync4(dirname5(unit.path), { recursive: true, mode: 493 });
|
|
4326
|
+
writeFileSync4(unit.path, unit.unit, { mode: 420 });
|
|
4286
4327
|
}
|
|
4287
4328
|
await applyPlan(plan, localRunner);
|
|
4288
4329
|
return plan;
|