@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
|
@@ -31,22 +31,18 @@ import { parsePlatformUpgradeArgs, UPGRADE_DRY_RUN_FLAG } from './upgrade-option
|
|
|
31
31
|
export { PLATFORM_CONCURRENT_DEPLOY, PLATFORM_DEPLOY_ORDER, PLATFORM_PROXY_SERVICE, PLATFORM_SEQUENTIAL_DEPLOY } from './deploy'
|
|
32
32
|
export { PLATFORM_CONSOLE_APP_SCHEMA } from './generated/platform-console-schema'
|
|
33
33
|
export { PLATFORM_PROXY_MANIFEST_TEMPLATE } from './generated/platform-proxy-manifest'
|
|
34
|
-
export { type IamOAuthConfig, type McpClient, parseMcpClientsJson, platformIamOAuthConfig } from './mcp'
|
|
35
|
-
export {
|
|
36
|
-
type PlatformOAuthResourceTemplate,
|
|
37
|
-
type PlatformProxyApp,
|
|
38
|
-
platformProxyAppFor,
|
|
39
|
-
type PlatformProxyComposition,
|
|
40
|
-
type PlatformProxyManifest,
|
|
41
|
-
type PlatformProxyManifestTemplate,
|
|
42
|
-
type PlatformProxyPlacement,
|
|
43
|
-
type PlatformProxyService,
|
|
44
|
-
platformServiceTableDigest,
|
|
45
|
-
resolvePlatformProxyManifest,
|
|
46
|
-
} from './proxy-manifest'
|
|
47
34
|
|
|
48
35
|
const EMAIL_PROVIDERS = Object.keys(builtinEmailProviders).join(', ')
|
|
49
|
-
|
|
36
|
+
/** One line per provider: its variables, each marked secret or optional where it is. */
|
|
37
|
+
const EMAIL_PROVIDER_VARIABLES = Object.entries(builtinEmailProviders)
|
|
38
|
+
.map(([name, provider]) => {
|
|
39
|
+
const variables = provider.variables.map((variable) => {
|
|
40
|
+
const marks = [variable.secret ? 'secret' : '', variable.required ? '' : 'optional'].filter((mark) => mark !== '')
|
|
41
|
+
return marks.length === 0 ? variable.name : `${variable.name} (${marks.join(', ')})`
|
|
42
|
+
})
|
|
43
|
+
return `\t ${name}: ${variables.join(', ')}`
|
|
44
|
+
})
|
|
45
|
+
.join('\n')
|
|
50
46
|
|
|
51
47
|
const USAGE = `Zerops installation
|
|
52
48
|
|
|
@@ -132,7 +128,7 @@ Options (a flag beats the environment variable beside it):
|
|
|
132
128
|
FABRIKA_ZEROPS_ACCESS_TOKEN required Zerops access token, environment only and no flag
|
|
133
129
|
FABRIKA_ZEROPS_API_URL optional region API base, when not the default
|
|
134
130
|
FABRIKA_EMAIL_<PROVIDER>_* the selected provider's variables, environment only and no flag:
|
|
135
|
-
|
|
131
|
+
${EMAIL_PROVIDER_VARIABLES}
|
|
136
132
|
|
|
137
133
|
With a mail provider, IAM sends its own mail and Control hands the same transport to every application
|
|
138
134
|
whose job declares \`mailer: 'installation'\`. To set or rotate it later, see \`platform guide\`.
|
|
@@ -254,7 +250,8 @@ process listing:
|
|
|
254
250
|
(a --dry-run does not need it)
|
|
255
251
|
FABRIKA_ZEROPS_API_URL optional region API base, when not the default
|
|
256
252
|
|
|
257
|
-
This command writes
|
|
253
|
+
This command writes one credential, derived from FABRIKA_IAM_PROVISIONING_KEY: the secret the proxy
|
|
254
|
+
proves its requests to IAM with. Every other secret an installation holds is placed at bring-up.
|
|
258
255
|
|
|
259
256
|
Install and deploy require --source-dir explicitly. Run the CLI from that same clean full checkout;
|
|
260
257
|
a packaged CLI cannot supply another revision's manifest or schema. --from-git and
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// so a pipeline or an agent drives init end to end. Its confirmations stay, answered by `--yes`.
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
assertPinnedTag,
|
|
9
10
|
CHECKOUT_FLAG,
|
|
10
11
|
type CommandEnv,
|
|
11
12
|
describeArgument,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
UNATTENDED_FLAG,
|
|
18
19
|
} from '@fabrika/installation-init'
|
|
19
20
|
import { parsePlatformHosts } from './placement-options'
|
|
20
|
-
import {
|
|
21
|
+
import { defaultCheckoutName, defaultSidecarRepo } from './sidecar'
|
|
21
22
|
|
|
22
23
|
/** The name every bypass in this platform is gated on, and therefore the one name an installation cannot take. */
|
|
23
24
|
const LOCAL_ENVIRONMENT = 'local'
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
INSTALLATION_SECRET_VARIABLES,
|
|
3
|
+
type InstallationEmail,
|
|
4
|
+
type InstallationSecrets,
|
|
5
|
+
installationServiceEnvironment,
|
|
6
|
+
type PlatformHosts,
|
|
7
|
+
platformOrigin,
|
|
8
|
+
type PlatformStorage,
|
|
9
|
+
} from '@fabrika/installation-init'
|
|
2
10
|
import { encodeProxyManifestJson } from '@fabrika/proxy-core/manifest'
|
|
3
11
|
import type { ZeropsNamespaceProxySource } from '../index'
|
|
4
12
|
import type { PlatformDeployService, ResolvedService } from './deploy'
|
|
5
|
-
import { type PlatformHosts, platformOrigin } from './hosts'
|
|
6
13
|
import { assertRunning, type InstallCollaborators } from './install-collaborators'
|
|
7
14
|
import { FABRIKA_ZEROPS_PROXY_SOURCE_JSON, serializeNamespaceProxySource } from './installation-source'
|
|
8
|
-
import type { InstallationSecrets } from './secrets'
|
|
9
15
|
import type { PlatformTier } from './topology/topology'
|
|
10
16
|
|
|
11
17
|
/** The private address the proxy dials IAM at inside the project, and pass 1's placeholder issuer. */
|
|
@@ -16,23 +22,16 @@ const PRIVATE_IAM_URL = `http://${PRIVATE_IAM_UPSTREAM}`
|
|
|
16
22
|
const postgresUrl = (service: string): string => `\${${service}_connectionTlsString}/\${${service}_dbName}?sslmode=require`
|
|
17
23
|
|
|
18
24
|
/** An object-storage service's coordinates, as the platform's own `${service_variable}` references. */
|
|
19
|
-
|
|
20
|
-
readonly bucket: string
|
|
21
|
-
readonly accessKeyId: string
|
|
22
|
-
readonly secretAccessKey: string
|
|
23
|
-
readonly endpoint: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const storageOf = (service: string): StorageReferences => ({
|
|
25
|
+
const storageOf = (service: string): PlatformStorage => ({
|
|
27
26
|
bucket: `\${${service}_bucketName}`,
|
|
28
27
|
accessKeyId: `\${${service}_accessKeyId}`,
|
|
29
28
|
secretAccessKey: `\${${service}_secretAccessKey}`,
|
|
30
29
|
endpoint: `\${${service}_apiUrl}`,
|
|
31
30
|
})
|
|
32
31
|
|
|
33
|
-
/** IAM and control share `db` and `storage` on both tiers;
|
|
32
|
+
/** IAM and control share `db` and `storage` on both tiers; Operations has its own database on both, and its own bucket on `standard`. */
|
|
34
33
|
const OPERATIONS_DATA: Readonly<Record<PlatformTier, { readonly database: string; readonly storage: string }>> = {
|
|
35
|
-
light: { database: '
|
|
34
|
+
light: { database: 'operationsdb', storage: 'storage' },
|
|
36
35
|
standard: { database: 'operationsdb', storage: 'operationsstorage' },
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -56,6 +55,8 @@ export const PASS_ONE_PROXY_MANIFEST = encodeProxyManifestJson({ apps: [] })
|
|
|
56
55
|
/** What `installationServiceEnv` needs. Everything in it is settled before the first write. */
|
|
57
56
|
export interface InstallationEnvInput {
|
|
58
57
|
readonly environment: string
|
|
58
|
+
/** The installation's own Zerops project, which no deployment namespace may adopt. */
|
|
59
|
+
readonly projectId: string
|
|
59
60
|
readonly tier: PlatformTier
|
|
60
61
|
readonly scheme: 'http' | 'https'
|
|
61
62
|
readonly hosts: PlatformHosts
|
|
@@ -70,132 +71,70 @@ export interface InstallationEnvInput {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
|
-
* EVERY variable this command places, per service
|
|
74
|
+
* EVERY variable this command places, per service: fabrika's (`installationServiceEnvironment`) over this
|
|
75
|
+
* project's own references, and control's Zerops variables after them.
|
|
74
76
|
*
|
|
75
77
|
* Pure, and exported, because the matrix is the thing most worth pinning: a name that is right on
|
|
76
|
-
* Cloudflare and wrong on Bun is a silent no-op rather than a failure.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* `FABRIKA_IAM_` prefix (`packages/iam/src/node/runtime.ts`). The prefixed spellings below are the
|
|
81
|
-
* ones that file actually reads; the unprefixed ones are the ones it actually reads too.
|
|
82
|
-
* • No key any service declares in its own `zerops.yaml` appears here — `PORT`,
|
|
83
|
-
* `FABRIKA_IAM_RPC_URL`, `FABRIKA_OPERATIONS_URL`, `FABRIKA_CONTROL_ASSETS_DIR`,
|
|
84
|
-
* `FABRIKA_CONTROL_URL`, `FABRIKA_PROXY_MANIFEST`, `FABRIKA_PROXY_PORT`. The env API refuses those
|
|
85
|
-
* (`provider-zerops/src/api.ts`), because the conflict cannot be resolved to a record id.
|
|
86
|
-
*
|
|
87
|
-
* The proxy manifest is deliberately absent: pass 1 writes an empty one and `deployPlatform` owns the
|
|
88
|
-
* real one, in the same run as the code it describes (ADR-0027).
|
|
78
|
+
* Cloudflare and wrong on Bun is a silent no-op rather than a failure. No key any service declares in its
|
|
79
|
+
* own `zerops.yaml` appears here — `PORT`, `FABRIKA_IAM_RPC_URL`, `FABRIKA_OPERATIONS_URL`,
|
|
80
|
+
* `FABRIKA_CONTROL_ASSETS_DIR`, `FABRIKA_CONTROL_URL`, `FABRIKA_PROXY_MANIFEST`, `FABRIKA_PROXY_PORT`. The env
|
|
81
|
+
* API refuses those (`provider-zerops/src/api.ts`), because the conflict cannot be resolved to a record id.
|
|
89
82
|
*/
|
|
90
83
|
export const installationServiceEnv = (input: InstallationEnvInput): ReadonlyMap<PlatformDeployService, ReadonlyMap<string, string>> => {
|
|
91
84
|
const { secrets } = input
|
|
92
|
-
const iamOrigin = platformOrigin(input.scheme, input.hosts.iam)
|
|
93
|
-
const operationsOrigin = platformOrigin(input.scheme, input.hosts.operations)
|
|
94
|
-
const platformDatabase = postgresUrl('db')
|
|
95
|
-
const runLogs = storageOf('storage')
|
|
96
85
|
const operationsData = OPERATIONS_DATA[input.tier]
|
|
97
|
-
const
|
|
86
|
+
const fabrika = installationServiceEnvironment({
|
|
87
|
+
environment: input.environment,
|
|
88
|
+
scheme: input.scheme,
|
|
89
|
+
hosts: input.hosts,
|
|
90
|
+
iamUpstream: PRIVATE_IAM_UPSTREAM,
|
|
91
|
+
secrets,
|
|
92
|
+
email: input.email,
|
|
93
|
+
data: {
|
|
94
|
+
iamDatabase: postgresUrl('db'),
|
|
95
|
+
controlDatabase: postgresUrl('db'),
|
|
96
|
+
operationsDatabase: postgresUrl(operationsData.database),
|
|
97
|
+
runLogs: storageOf('storage'),
|
|
98
|
+
operationsBlobs: storageOf(operationsData.storage),
|
|
99
|
+
},
|
|
100
|
+
})
|
|
98
101
|
const control = new Map([
|
|
99
|
-
|
|
100
|
-
['ENVIRONMENT', input.environment],
|
|
101
|
-
// A bare HOST, not an origin: `controlPublicOrigin` accepts either.
|
|
102
|
-
['FABRIKA_CONTROL_DOMAIN', input.hosts.control],
|
|
103
|
-
['FABRIKA_IAM_ISSUER', iamOrigin],
|
|
104
|
-
['OPERATIONS_ARTIFACT_ORIGIN', operationsOrigin],
|
|
105
|
-
['FABRIKA_CONTROL_RUN_LOGS_BUCKET', runLogs.bucket],
|
|
106
|
-
['FABRIKA_CONTROL_RUN_LOGS_ACCESS_KEY_ID', runLogs.accessKeyId],
|
|
107
|
-
['FABRIKA_CONTROL_RUN_LOGS_SECRET_ACCESS_KEY', runLogs.secretAccessKey],
|
|
108
|
-
['FABRIKA_CONTROL_RUN_LOGS_ENDPOINT', runLogs.endpoint],
|
|
109
|
-
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
110
|
-
['OPERATIONS_SYNC_KEY', secrets.operationsSyncKey],
|
|
111
|
-
['FABRIKA_CONTROL_VAULT_KEY', secrets.vaultKey],
|
|
112
|
-
['FABRIKA_IAM_PROVISIONING_KEY', secrets.provisioningKey],
|
|
102
|
+
...fabrika.control,
|
|
113
103
|
['FABRIKA_ZEROPS_CLIENT_ID', input.clientId],
|
|
114
104
|
[FABRIKA_ZEROPS_PROXY_SOURCE_JSON, serializeNamespaceProxySource(input.proxySource)],
|
|
115
105
|
// The PUBLIC IAM origin an application project's proxy reaches: there is no private network
|
|
116
106
|
// between Zerops projects (ADR-0006).
|
|
117
|
-
['FABRIKA_ZEROPS_PROXY_IAM_URL',
|
|
107
|
+
['FABRIKA_ZEROPS_PROXY_IAM_URL', platformOrigin(input.scheme, input.hosts.iam)],
|
|
118
108
|
['FABRIKA_ZEROPS_PROXY_IAM_KEY', secrets.proxyKey],
|
|
119
109
|
['FABRIKA_ZEROPS_ACCESS_TOKEN', input.zeropsAccessToken],
|
|
110
|
+
['FABRIKA_ZEROPS_PLATFORM_PROJECT_ID', input.projectId],
|
|
120
111
|
])
|
|
121
112
|
if (input.apiBaseUrl !== undefined) {
|
|
122
113
|
// Only when the installation is not on the default region. Control spells it `_API_BASE_URL` while
|
|
123
114
|
// the CLI reads `FABRIKA_ZEROPS_API_URL`; they are different processes reading different sources.
|
|
124
115
|
control.set('FABRIKA_ZEROPS_API_BASE_URL', input.apiBaseUrl)
|
|
125
116
|
}
|
|
126
|
-
const email = input.email.kind === 'none' ? [] : [...input.email.variables]
|
|
127
|
-
for (const [key, value] of email) control.set(key, value)
|
|
128
117
|
return new Map<PlatformDeployService, ReadonlyMap<string, string>>([
|
|
129
|
-
[
|
|
130
|
-
|
|
131
|
-
new Map([
|
|
132
|
-
['FABRIKA_IAM_DATABASE_URL', platformDatabase],
|
|
133
|
-
['ENVIRONMENT', input.environment],
|
|
134
|
-
['ISSUER', iamOrigin],
|
|
135
|
-
// Password only, by decision: OIDC needs an identity provider the operator has not been
|
|
136
|
-
// asked for. Both are STATED because IAM's own defaults are the other way round — OIDC on,
|
|
137
|
-
// password off — and it refuses to boot with neither.
|
|
138
|
-
['FABRIKA_IAM_OIDC_ENABLED', 'false'],
|
|
139
|
-
['FABRIKA_IAM_PASSWORD_ENABLED', 'true'],
|
|
140
|
-
['FABRIKA_IAM_SIGNING_KEYS', secrets.signingKeys],
|
|
141
|
-
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
142
|
-
['FABRIKA_IAM_PROXY_KEY', secrets.proxyKey],
|
|
143
|
-
['FABRIKA_IAM_PROVISIONING_KEY', secrets.provisioningKey],
|
|
144
|
-
...email,
|
|
145
|
-
]),
|
|
146
|
-
],
|
|
147
|
-
[
|
|
148
|
-
'operations',
|
|
149
|
-
new Map([
|
|
150
|
-
['FABRIKA_OPERATIONS_DATABASE_URL', postgresUrl(operationsData.database)],
|
|
151
|
-
['FABRIKA_OPERATIONS_BLOB_BUCKET', operationsBlobs.bucket],
|
|
152
|
-
['FABRIKA_OPERATIONS_BLOB_ACCESS_KEY_ID', operationsBlobs.accessKeyId],
|
|
153
|
-
['FABRIKA_OPERATIONS_BLOB_SECRET_ACCESS_KEY', operationsBlobs.secretAccessKey],
|
|
154
|
-
['FABRIKA_OPERATIONS_BLOB_ENDPOINT', operationsBlobs.endpoint],
|
|
155
|
-
['FABRIKA_OPERATIONS_PUBLIC_HOST', input.hosts.operations],
|
|
156
|
-
['ENVIRONMENT', input.environment],
|
|
157
|
-
['FABRIKA_IAM_ISSUER', iamOrigin],
|
|
158
|
-
['OPERATIONS_SYNC_KEY', secrets.operationsSyncKey],
|
|
159
|
-
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
160
|
-
]),
|
|
161
|
-
],
|
|
118
|
+
['iam', fabrika.iam],
|
|
119
|
+
['operations', fabrika.operations],
|
|
162
120
|
['control', control],
|
|
163
|
-
[
|
|
164
|
-
'proxy',
|
|
165
|
-
new Map([
|
|
166
|
-
['ENVIRONMENT', input.environment],
|
|
167
|
-
// Compared byte-for-byte against a token's `iss`, so this is the PUBLIC issuer and not the
|
|
168
|
-
// private `iam:3000` pass 1 wrote.
|
|
169
|
-
['FABRIKA_IAM_ISSUER', iamOrigin],
|
|
170
|
-
['FABRIKA_IAM_KEY', secrets.proxyKey],
|
|
171
|
-
['FABRIKA_PROXY_SESSION_OWNER_JSON', JSON.stringify({ issuer: iamOrigin, upstream: PRIVATE_IAM_UPSTREAM })],
|
|
172
|
-
]),
|
|
173
|
-
],
|
|
121
|
+
['proxy', fabrika.proxy],
|
|
174
122
|
])
|
|
175
123
|
}
|
|
176
124
|
|
|
177
125
|
/**
|
|
178
|
-
* The keys whose VALUE this command generates, per service
|
|
126
|
+
* The keys whose VALUE this command generates, per service: fabrika's, and the two Zerops names on control
|
|
127
|
+
* that carry the proxy key and the minted token.
|
|
179
128
|
*
|
|
180
129
|
* Their presence on a live service is what "this project already holds an installation" means, and it is
|
|
181
130
|
* checked by KEY only — a value is never read back for comparison. `__tests__/install.test.ts` pins this
|
|
182
131
|
* against the matrix above, so a new generated secret cannot be added without appearing here.
|
|
183
132
|
*/
|
|
184
133
|
export const GENERATED_SECRET_KEYS: ReadonlyMap<PlatformDeployService, readonly string[]> = new Map<PlatformDeployService, readonly string[]>([
|
|
185
|
-
['iam',
|
|
186
|
-
['operations',
|
|
187
|
-
[
|
|
188
|
-
|
|
189
|
-
[
|
|
190
|
-
'FABRIKA_IAM_RPC_KEY',
|
|
191
|
-
'OPERATIONS_SYNC_KEY',
|
|
192
|
-
'FABRIKA_CONTROL_VAULT_KEY',
|
|
193
|
-
'FABRIKA_IAM_PROVISIONING_KEY',
|
|
194
|
-
'FABRIKA_ZEROPS_PROXY_IAM_KEY',
|
|
195
|
-
'FABRIKA_ZEROPS_ACCESS_TOKEN',
|
|
196
|
-
],
|
|
197
|
-
],
|
|
198
|
-
['proxy', ['FABRIKA_IAM_KEY']],
|
|
134
|
+
['iam', INSTALLATION_SECRET_VARIABLES.iam],
|
|
135
|
+
['operations', INSTALLATION_SECRET_VARIABLES.operations],
|
|
136
|
+
['control', [...INSTALLATION_SECRET_VARIABLES.control, 'FABRIKA_ZEROPS_PROXY_IAM_KEY', 'FABRIKA_ZEROPS_ACCESS_TOKEN']],
|
|
137
|
+
['proxy', INSTALLATION_SECRET_VARIABLES.proxy],
|
|
199
138
|
])
|
|
200
139
|
|
|
201
140
|
/**
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { builtinEmailProviders } from '@fabrika/email/providers'
|
|
12
12
|
import {
|
|
13
|
+
checkedSecretsFilePath,
|
|
13
14
|
describeArgument,
|
|
14
15
|
EMAIL_FLAGS,
|
|
15
16
|
type InstallationEmail,
|
|
@@ -17,10 +18,11 @@ import {
|
|
|
17
18
|
readFlag,
|
|
18
19
|
readInstallationEmail,
|
|
19
20
|
requiredSetting,
|
|
21
|
+
SECRETS_FILE_FLAG,
|
|
22
|
+
SECRETS_FILE_VARIABLE,
|
|
20
23
|
setting,
|
|
21
24
|
UNATTENDED_FLAG,
|
|
22
25
|
} from '@fabrika/installation-init'
|
|
23
|
-
import { checkedSecretsFilePath, SECRETS_FILE_FLAG, SECRETS_FILE_VARIABLE } from './install-secrets'
|
|
24
26
|
import { parsePlatformScheme } from './placement-options'
|
|
25
27
|
import { readInstallationSourceDir } from './source-options'
|
|
26
28
|
import type { PlatformTier } from './topology/topology'
|
|
@@ -1,36 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// What a Zerops installation's secrets file records.
|
|
2
2
|
//
|
|
3
3
|
// The services hold the same values in Zerops, but stored SENSITIVE, and Zerops hands a sensitive value
|
|
4
|
-
// back only to a token in sudo mode (measured 2026-10-15).
|
|
5
|
-
//
|
|
4
|
+
// back only to a token in sudo mode (measured 2026-10-15). The file's path rule, quoting and 0600 write
|
|
5
|
+
// are `@fabrika/installation-init`'s `secrets-file.ts`.
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { open } from 'node:fs/promises'
|
|
9
|
-
import { isAbsolute, relative, resolve } from 'node:path'
|
|
10
|
-
import type { InstallationSecrets } from './secrets'
|
|
11
|
-
|
|
12
|
-
/** `--secrets-file`: where the local copy goes. A new file, outside the source checkout. */
|
|
13
|
-
export const SECRETS_FILE_FLAG = '--secrets-file'
|
|
14
|
-
export const SECRETS_FILE_VARIABLE = 'FABRIKA_PLATFORM_SECRETS_FILE'
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Settle the path before anything is contacted.
|
|
18
|
-
*
|
|
19
|
-
* Refused when it exists, because overwriting one would destroy another installation's only copy, and
|
|
20
|
-
* when it is inside the source checkout, where it would dirty the checkout `platform deploy` uploads
|
|
21
|
-
* and could be committed.
|
|
22
|
-
*/
|
|
23
|
-
export const checkedSecretsFilePath = (path: string, sourceDir: string): string => {
|
|
24
|
-
const absolute = resolve(path)
|
|
25
|
-
const fromSource = relative(resolve(sourceDir), absolute)
|
|
26
|
-
if (fromSource === '' || (!fromSource.startsWith('..') && !isAbsolute(fromSource))) {
|
|
27
|
-
throw new Error(`${SECRETS_FILE_FLAG}=${path} is inside the source checkout — name a path outside it, so it is never uploaded or committed`)
|
|
28
|
-
}
|
|
29
|
-
if (existsSync(absolute)) {
|
|
30
|
-
throw new Error(`${SECRETS_FILE_FLAG}=${path} already exists — it may be another installation's only copy; name a new file`)
|
|
31
|
-
}
|
|
32
|
-
return absolute
|
|
33
|
-
}
|
|
7
|
+
import { type InstallationSecrets, renderSecretsFile } from '@fabrika/installation-init'
|
|
34
8
|
|
|
35
9
|
/** What the file records beside the credentials, so it identifies its installation on its own. */
|
|
36
10
|
export interface InstallationSecretsRecord {
|
|
@@ -43,47 +17,29 @@ export interface InstallationSecretsRecord {
|
|
|
43
17
|
}
|
|
44
18
|
|
|
45
19
|
/**
|
|
46
|
-
* The file's text
|
|
20
|
+
* The file's text, named as the commands that read it name each value.
|
|
47
21
|
*
|
|
48
22
|
* `FABRIKA_ZEROPS_ACCESS_TOKEN` is the INTEGRATION token, which is what `platform init` and
|
|
49
23
|
* `platform deploy` take; the personal token the install ran with is not written.
|
|
50
24
|
*/
|
|
51
|
-
export const
|
|
25
|
+
export const renderInstallationSecretsFile = (record: InstallationSecretsRecord): string => {
|
|
52
26
|
const { secrets } = record
|
|
53
|
-
|
|
54
|
-
[
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
[
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return [
|
|
72
|
-
`# fabrika on Zerops — installation \`${record.environment}\`, project ${record.projectId}.`,
|
|
73
|
-
'# Written by `fabrika platform install`. Every value below is a credential: keep this file private.',
|
|
74
|
-
'# Zerops holds the same values as sensitive variables and does not hand them back.',
|
|
75
|
-
'# Load it in a subshell: ( set -a && . <this file> && set +a && fabrika … )',
|
|
76
|
-
...lines,
|
|
77
|
-
'',
|
|
78
|
-
].join('\n')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Create the file with mode 0600, failing rather than replacing one that appeared since the check. */
|
|
82
|
-
export const writeSecretsFile = async (path: string, text: string): Promise<void> => {
|
|
83
|
-
const file = await open(path, 'wx', 0o600)
|
|
84
|
-
try {
|
|
85
|
-
await file.writeFile(text)
|
|
86
|
-
} finally {
|
|
87
|
-
await file.close()
|
|
88
|
-
}
|
|
27
|
+
return renderSecretsFile(
|
|
28
|
+
[
|
|
29
|
+
`fabrika on Zerops — installation \`${record.environment}\`, project ${record.projectId}.`,
|
|
30
|
+
'Written by `fabrika platform install`. Every value below is a credential: keep this file private.',
|
|
31
|
+
'Zerops holds the same values as sensitive variables and does not hand them back.',
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
['FABRIKA_ZEROPS_PROJECT_ID', record.projectId],
|
|
35
|
+
['FABRIKA_ZEROPS_CLIENT_ID', record.clientId],
|
|
36
|
+
['FABRIKA_IAM_PROVISIONING_KEY', secrets.provisioningKey],
|
|
37
|
+
['FABRIKA_ZEROPS_ACCESS_TOKEN', record.zeropsAccessToken],
|
|
38
|
+
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
39
|
+
['FABRIKA_IAM_PROXY_KEY', secrets.proxyKey],
|
|
40
|
+
['OPERATIONS_SYNC_KEY', secrets.operationsSyncKey],
|
|
41
|
+
['FABRIKA_CONTROL_VAULT_KEY', secrets.vaultKey],
|
|
42
|
+
['FABRIKA_IAM_SIGNING_KEYS', secrets.signingKeys],
|
|
43
|
+
],
|
|
44
|
+
)
|
|
89
45
|
}
|
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
import {
|
|
6
6
|
action as consoleAction,
|
|
7
7
|
confirmations,
|
|
8
|
+
generateInstallationSecrets,
|
|
8
9
|
info as consoleInfo,
|
|
10
|
+
type InstallationSecrets,
|
|
9
11
|
ok as consoleOk,
|
|
12
|
+
type PlatformHosts,
|
|
13
|
+
platformOrigin,
|
|
10
14
|
step as consoleStep,
|
|
11
15
|
warn as consoleWarn,
|
|
16
|
+
writeSecretsFile,
|
|
12
17
|
} from '@fabrika/installation-init'
|
|
13
18
|
import type { SchemaReconciler } from '@fabrika/provider-contract'
|
|
14
19
|
import { createZeropsApi, defaultSleep, readableContent, type ZeropsNamespaceProxySource } from '../index'
|
|
@@ -24,7 +29,7 @@ import {
|
|
|
24
29
|
type ResolvedService,
|
|
25
30
|
} from './deploy'
|
|
26
31
|
import { PLATFORM_PROXY_MANIFEST_TEMPLATE } from './generated/platform-proxy-manifest'
|
|
27
|
-
import { derivePlatformHosts,
|
|
32
|
+
import { derivePlatformHosts, ZEROPS_SUBDOMAIN_VARIABLE } from './hosts'
|
|
28
33
|
import { checkedEnvironmentName } from './init-options'
|
|
29
34
|
import { assertRunning, type InstallCollaborators, type InstallPrompts } from './install-collaborators'
|
|
30
35
|
import { GENERATED_SECRET_KEYS, installationServiceEnv, PASS_ONE_PROXY_MANIFEST, passOneProxyEnv, writeServiceEnv } from './install-environment'
|
|
@@ -39,11 +44,10 @@ import {
|
|
|
39
44
|
resolvePlatformServices,
|
|
40
45
|
serviceOf,
|
|
41
46
|
} from './install-provisioning'
|
|
42
|
-
import {
|
|
47
|
+
import { renderInstallationSecretsFile } from './install-secrets'
|
|
43
48
|
import { explainMintFailure } from './install-token'
|
|
44
49
|
import { prepareInstallationSource, prepareNamespaceProxySource, validateInstallationSetups } from './installation-source'
|
|
45
50
|
import type { PreparedPlatformSource } from './platform-source'
|
|
46
|
-
import { generateInstallationSecrets, type InstallationSecrets } from './secrets'
|
|
47
51
|
|
|
48
52
|
export type { InstallCollaborators, InstallPrompts } from './install-collaborators'
|
|
49
53
|
export { GENERATED_SECRET_KEYS, type InstallationEnvInput, installationServiceEnv } from './install-environment'
|
|
@@ -158,7 +162,7 @@ const installPreparedPlatform = async (
|
|
|
158
162
|
log.step('Store the credentials locally')
|
|
159
163
|
// BEFORE the first one is placed: Zerops keeps them sensitive and will not hand them back.
|
|
160
164
|
await collaborators.storeSecrets(
|
|
161
|
-
|
|
165
|
+
renderInstallationSecretsFile({ environment, projectId: project.id, clientId: input.clientId, secrets, zeropsAccessToken: token.token }),
|
|
162
166
|
)
|
|
163
167
|
log.ok(`wrote ${input.secretsFile} (mode 0600) — every credential this installation was created with; nothing else holds a readable copy`)
|
|
164
168
|
|
|
@@ -170,6 +174,7 @@ const installPreparedPlatform = async (
|
|
|
170
174
|
log.step('Write every remaining variable')
|
|
171
175
|
const desired = installationServiceEnv({
|
|
172
176
|
environment,
|
|
177
|
+
projectId: project.id,
|
|
173
178
|
tier: input.tier,
|
|
174
179
|
scheme: input.scheme,
|
|
175
180
|
hosts,
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
//
|
|
13
13
|
// `fabrika.ref` names a published tag of `contember/fabrika-platform`, never a branch: a namespace
|
|
14
14
|
// proxy and an installation both build from tags, so a moving ref would make "which version is this
|
|
15
|
-
// installation running" unanswerable. `assertPinnedTag` refuses a branch
|
|
16
|
-
// the generated workflow refuses one again at run time — two gates,
|
|
17
|
-
// after creation and the second gate is the only one that sees what
|
|
15
|
+
// installation running" unanswerable. `assertPinnedTag` (`@fabrika/installation-init`) refuses a branch
|
|
16
|
+
// when init writes the file, and the generated workflow refuses one again at run time — two gates,
|
|
17
|
+
// because the file is operator-owned after creation and the second gate is the only one that sees what
|
|
18
|
+
// the operator later wrote.
|
|
18
19
|
//
|
|
19
20
|
// ── The templates are not Cloudflare's ────────────────────────────────────────────────────────────
|
|
20
21
|
//
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
// pipeline; this one runs a single step because on Zerops the order is inside the command (ADR-0027).
|
|
24
25
|
// Cloudflare builds a runner image and needs a GitHub App; Zerops has no runner at all (ADR-0003).
|
|
25
26
|
|
|
27
|
+
import { assertPinnedTag } from '@fabrika/installation-init'
|
|
26
28
|
import { resolve } from 'node:path'
|
|
27
29
|
import { z } from 'zod'
|
|
28
30
|
|
|
@@ -47,29 +49,6 @@ export interface SidecarValues {
|
|
|
47
49
|
readonly fabrikaRef: string
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
/**
|
|
51
|
-
* A published release tag: `v` and a semantic version.
|
|
52
|
-
*
|
|
53
|
-
* The same shape `scripts/release.ts` accepts, restated rather than imported — that script is not a
|
|
54
|
-
* package and this check has to hold in a published tarball.
|
|
55
|
-
*/
|
|
56
|
-
const TAG_PATTERN = /^v(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
|
|
57
|
-
|
|
58
|
-
/** The pinned ref, or an error naming what was found. A branch is refused, deliberately and loudly. */
|
|
59
|
-
export const assertPinnedTag = (ref: string): string => {
|
|
60
|
-
const trimmed = ref.trim()
|
|
61
|
-
if (trimmed === '') {
|
|
62
|
-
throw new Error('a published fabrika tag is required, for example v0.1.0')
|
|
63
|
-
}
|
|
64
|
-
if (!TAG_PATTERN.test(trimmed)) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
`\`${trimmed}\` is not a published tag. A sidecar repository pins a TAG such as v0.1.0 and never a branch: `
|
|
67
|
-
+ 'an installation that tracked a moving ref could not say which version it runs (ADR-0025)',
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
return trimmed
|
|
71
|
-
}
|
|
72
|
-
|
|
73
52
|
/** Substitute every placeholder in a template. */
|
|
74
53
|
const render = async (name: string, values: SidecarValues): Promise<string> => {
|
|
75
54
|
const raw = await Bun.file(resolve(TEMPLATES, name)).text()
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
// into the platform's variable store rather than carrying a credential. That reasoning is still sound
|
|
48
48
|
// and it is no longer sufficient, because a reference names a SERVICE — and which data services exist
|
|
49
49
|
// is exactly what differs between the `standard` and `light` tiers (`./topology.ts`). One committed
|
|
50
|
-
// file cannot say `${
|
|
50
|
+
// file cannot say `${operationsstorage_bucketName}` on behalf of a tier that has no `operationsstorage`,
|
|
51
|
+
// nor `${operationsdb_connectionString}` on behalf of a light installation created before Operations
|
|
52
|
+
// had a database of its own.
|
|
51
53
|
// So the file's own rule decides it: a data-service reference is not true for every installation, and
|
|
52
54
|
// it moves to the env API alongside the origins and the secrets.
|
|
53
55
|
//
|
|
@@ -90,6 +92,7 @@
|
|
|
90
92
|
// arrives fully resolved). Only the proxy needs this, and it declares it explicitly below.
|
|
91
93
|
|
|
92
94
|
import type { ZeropsHealthCheckSpec, ZeropsReadinessCheckSpec, ZeropsYamlSetupSpec, ZeropsYamlSpec } from '../../index'
|
|
95
|
+
import { FABRIKA_PROXY_MANIFEST_BRAND } from '../../proxy-manifest-carrier'
|
|
93
96
|
|
|
94
97
|
// ── Readiness and liveness: two mechanisms, two questions ──────────────────────────────────────────
|
|
95
98
|
//
|
|
@@ -181,6 +184,8 @@ const WORKSPACE_DEPLOY_FILES = ['package.json', 'bun.lock', 'node_modules', 'pac
|
|
|
181
184
|
* since the proxy is the only publicly routed component and must not also be able to write audit rows),
|
|
182
185
|
* `FABRIKA_IAM_PROVISIONING_KEY`. Both shared secrets must be at least 32 characters; a shorter one fails
|
|
183
186
|
* the boot rather than standing in front of an internet-reachable surface while looking configured.
|
|
187
|
+
* `FABRIKA_INSTALLATION_PROXY_SECRET_IAM`, which `platform deploy` derives and writes, lets IAM tell the proxy's
|
|
188
|
+
* client-address and request-id headers from a private caller's.
|
|
184
189
|
*/
|
|
185
190
|
const iam: ZeropsYamlSetupSpec = {
|
|
186
191
|
setup: 'iam',
|
|
@@ -291,8 +296,9 @@ const control: ZeropsYamlSetupSpec = {
|
|
|
291
296
|
* `FABRIKA_OPERATIONS_BLOB_*` coordinates, `ENVIRONMENT`, `FABRIKA_OPERATIONS_PUBLIC_HOST` and
|
|
292
297
|
* `FABRIKA_IAM_ISSUER` (the public IAM issuer). `FABRIKA_OPERATIONS_DATABASE_URL` takes the canonical
|
|
293
298
|
* form above. On the `standard` tier the data references are `${operationsdb_*}` and
|
|
294
|
-
* `${operationsstorage_*}`; on `light` they are `${
|
|
295
|
-
*
|
|
299
|
+
* `${operationsstorage_*}`; on `light` they are `${operationsdb_*}` and `${storage_*}` (`${db_*}` on a
|
|
300
|
+
* light installation created before Operations had its own database), which is precisely why they cannot
|
|
301
|
+
* be written here.
|
|
296
302
|
*
|
|
297
303
|
* Secrets (`envSecrets`): `OPERATIONS_SYNC_KEY`, shared only with control for catalog projection, and
|
|
298
304
|
* `FABRIKA_IAM_RPC_KEY`, used only for Operations → IAM management RPC.
|
|
@@ -331,7 +337,8 @@ const operations: ZeropsYamlSetupSpec = {
|
|
|
331
337
|
* IAM service's PUBLIC origin in an apps project, because there is no private network between projects
|
|
332
338
|
* (ADR-0006). The manifest is not a variable; see below.
|
|
333
339
|
*
|
|
334
|
-
* Secret (`envSecrets`): `FABRIKA_IAM_KEY` — the value IAM knows as `FABRIKA_IAM_PROXY_KEY`.
|
|
340
|
+
* Secret (`envSecrets`): `FABRIKA_IAM_KEY` — the value IAM knows as `FABRIKA_IAM_PROXY_KEY`. In the platform project
|
|
341
|
+
* also `FABRIKA_INSTALLATION_PROXY_SECRET_IAM`, which Caddy sends on IAM's route and IAM holds too.
|
|
335
342
|
*
|
|
336
343
|
* ── How the manifest reaches the build ───────────────────────────────────────────────────────────
|
|
337
344
|
*
|
|
@@ -395,6 +402,10 @@ const proxy: ZeropsYamlSetupSpec = {
|
|
|
395
402
|
// it is the archive's data file (see the header).
|
|
396
403
|
envVariables: {
|
|
397
404
|
FABRIKA_PROXY_SESSION_OWNER_JSON: '${RUNTIME_FABRIKA_PROXY_SESSION_OWNER_JSON}',
|
|
405
|
+
// A marker, read by nothing at build time: a namespace pinned to this source has a manifest parser that knows
|
|
406
|
+
// `brand`, so Control may send the installation's client logo (ADR-0066). An older pin's parser refuses the
|
|
407
|
+
// key and fails its build, so it is sent only where this is (`zeropsProxyAcceptsBrand`).
|
|
408
|
+
[FABRIKA_PROXY_MANIFEST_BRAND]: '1',
|
|
398
409
|
},
|
|
399
410
|
// CGO off makes the binary static, which is what lets it run on Alpine/musl.
|
|
400
411
|
prepareCommands: ['CGO_ENABLED=0 GOBIN=/tmp/gobin go install github.com/caddyserver/caddy/v2/cmd/caddy@v2.10.2'],
|