@fabrika/provider-zerops 0.4.2 → 0.4.4
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 +8 -7
- package/src/api/client.ts +11 -14
- package/src/api/polling.ts +42 -0
- package/src/api/readers.ts +9 -2
- package/src/api/service-env-limit.ts +29 -0
- package/src/api/service-env-response.ts +34 -2
- package/src/api/types.ts +39 -9
- package/src/api.ts +10 -1
- package/src/control.ts +23 -8
- package/src/execution.ts +44 -5
- package/src/index.ts +5 -0
- package/src/installation/admin.ts +4 -2
- package/src/installation/checker-release.ts +44 -0
- package/src/installation/deploy.ts +95 -37
- package/src/installation/generated/platform-console-schema.ts +18 -2
- package/src/installation/generated/platform-proxy-manifest.ts +20 -0
- package/src/installation/guide.md +169 -52
- package/src/installation/index.ts +22 -15
- package/src/installation/init-options.ts +2 -2
- package/src/installation/init.ts +8 -2
- package/src/installation/install-collaborators.ts +4 -0
- package/src/installation/install-environment.ts +52 -30
- package/src/installation/install-options.ts +20 -19
- package/src/installation/install-project.ts +23 -15
- package/src/installation/install-provisioning.ts +12 -12
- package/src/installation/install-secrets.ts +89 -0
- package/src/installation/install-token.ts +37 -0
- package/src/installation/install.ts +34 -21
- package/src/installation/machine-key.ts +3 -2
- package/src/installation/platform-archive.ts +87 -0
- package/src/installation/platform-upload.ts +2 -2
- package/src/installation/secrets.ts +3 -4
- package/src/installation/sidecar.ts +38 -2
- package/src/installation/templates/README.md +1 -1
- package/src/installation/templates/platform.yml +23 -2
- package/src/installation/topology/setups.ts +15 -27
- package/src/installation/topology/topology.ts +105 -104
- package/src/installation/upgrade.ts +24 -7
- package/src/managed-env.ts +11 -0
- package/src/manifest.ts +25 -3
- package/src/namespace/codec.ts +6 -1
- package/src/namespace/errors.ts +3 -1
- package/src/namespace/provisioning.ts +18 -5
- package/src/namespace/proxy-attempt.ts +56 -14
- package/src/namespace/update.ts +50 -8
- package/src/provider.ts +57 -13
- package/src/proxy-manifest-carrier.ts +32 -0
- package/src/source/port.ts +9 -1
- package/src/installation/manifest.ts +0 -95
|
@@ -16,24 +16,72 @@ message that names the flag or the variable to set. Read the message: it names t
|
|
|
16
16
|
|
|
17
17
|
**Ask the human for these first.** You cannot produce them, and each one blocks a later step:
|
|
18
18
|
|
|
19
|
-
| What
|
|
20
|
-
|
|
|
21
|
-
| A Zerops
|
|
22
|
-
| A
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
19
|
+
| What | Needed at |
|
|
20
|
+
| ------------------------------------------------------------------------------------- | ---------- |
|
|
21
|
+
| A Zerops PERSONAL access token of a client OWNER, generated in the Zerops GUI | step 1 |
|
|
22
|
+
| A verified Zerops account (credit topped up once); an unverified one owns ONE project | step 5 |
|
|
23
|
+
| A `gh` login allowed to create a repository in the organization that owns it | step 3 |
|
|
24
|
+
| The administrator's e-mail address, and the human to open the enrollment URL | step 2 |
|
|
25
|
+
| For a PRIVATE application repository: the GitHub App connected in the console | step 5 |
|
|
26
|
+
| The fabrika release tag to install, for example `v0.4.3` (a branch is refused) | steps 1, 3 |
|
|
27
|
+
| The four decisions below | steps 1, 5 |
|
|
28
|
+
|
|
29
|
+
Every namespace is a Zerops project of its own, so the first one fails with `clientProjectCountLimitExceededTopUp`
|
|
30
|
+
on an unverified account (observed 2026-10-15).
|
|
26
31
|
|
|
27
32
|
Also needed on the machine: [Bun](https://bun.sh) ≥ 1.3, `git`, `gh`, `curl` and `jq`.
|
|
28
33
|
|
|
34
|
+
## Decisions: ask the human, with the prices
|
|
35
|
+
|
|
36
|
+
Each choice trades price against stability, and two of them cannot be undone. Show the human this section
|
|
37
|
+
and let them choose; do not pick for them. Prices are Zerops' list prices as of 2026-10-15
|
|
38
|
+
([zerops.io/pricing](https://zerops.io/pricing)): a shared core $0.60, a dedicated core $6.00, RAM $3.00 per
|
|
39
|
+
GB, disk $0.10 per GB and object storage $0.01 per GB, each per 30 days and billed per minute. A Lightweight
|
|
40
|
+
project core is free (15 build hours, 100 GB egress); a Serious one is $10 (150 build hours, 3 TB egress, a
|
|
41
|
+
highly available balancer). The figures below are the idle FLOOR. Every service scales up under load, to 8
|
|
42
|
+
cores and 48 GB per container, and costs more while it does.
|
|
43
|
+
|
|
44
|
+
**1. Platform tier** (`platform install --tier`, step 1). Cannot be changed later: a project's core package
|
|
45
|
+
is upgrade-only, and the data services differ.
|
|
46
|
+
|
|
47
|
+
| Tier | What runs | Idle floor | Fails when |
|
|
48
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------- |
|
|
49
|
+
| `light` (default) | Lightweight core. One single-node PostgreSQL (`oltp-hobby`) and one bucket shared by IAM, control and Operations; one container each. | about $6 | any one container or the database is down: sign-in stops |
|
|
50
|
+
| `standard` | Serious core. HA PostgreSQL (3 nodes, `oltp-production`, dedicated CPU) for IAM and control; a second HA PostgreSQL and bucket for Operations; two containers of IAM and the proxy. | about $100 | survives the loss of one node of each |
|
|
51
|
+
|
|
52
|
+
Most of `standard`'s price is the identity database's dedicated floor (about $72). `light` is right for an
|
|
53
|
+
evaluation and a small fleet that tolerates an outage; `standard` for sign-in that must stay up.
|
|
54
|
+
|
|
55
|
+
**2. Namespace preset** (`control namespaces create --preset`, step 5). A namespace is the Zerops project
|
|
56
|
+
applications run in. Each one adds its own proxy, about $1.08.
|
|
57
|
+
|
|
58
|
+
| Preset | Where an application's database lives | Use with |
|
|
59
|
+
| --------------- | ----------------------------------------------------------------------- | ------------------------------------ |
|
|
60
|
+
| `cheap` | One shared single-node PostgreSQL in the namespace, about $1.45 for all | `fabrika.cheap.config.ts` |
|
|
61
|
+
| `mid` (default) | Each application imports its own database into the namespace | `fabrika.config.ts` |
|
|
62
|
+
| `full` | The namespace belongs to ONE application (`--exclusive-app=<id>`) | an application that must be isolated |
|
|
63
|
+
|
|
64
|
+
**3. The application's database** (the application's `fabrika.config.ts`, before step 5). The example
|
|
65
|
+
declares `postgresql:single@18` at `oltp-hobby`, about $1.45, plus its runtime from about $1.08 per
|
|
66
|
+
container. `postgresql:ha@18` survives a node failure: about $11 at `oltp-staging` (shared CPU), about $72 at
|
|
67
|
+
`oltp-production` (dedicated). Single to HA later means a new service and a data migration.
|
|
68
|
+
|
|
69
|
+
**4. Namespace core package** (`--core-package=LIGHT|SERIOUS`, step 5). `LIGHT` is free and the default on
|
|
70
|
+
every environment. `SERIOUS` adds $10 per namespace and a highly available balancer in front of its proxy,
|
|
71
|
+
and it can never be downgraded again.
|
|
72
|
+
|
|
29
73
|
## Secrets: rules for an agent
|
|
30
74
|
|
|
31
75
|
- Every credential is read from the ENVIRONMENT ONLY. No command takes one as a flag.
|
|
32
76
|
- Never print a credential, never put one in a command line you echo, never commit one.
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
77
|
+
- `platform install` writes every credential it creates to `--secrets-file`, mode `0600`, before it places
|
|
78
|
+
the first one. That file is the ONLY readable copy. Zerops stores them as sensitive variables and answers
|
|
79
|
+
`REDACTED` when they are read back, so never try to read a secret from the Zerops API, and never ask the
|
|
80
|
+
human for sudo mode to do it. Back the file up where the human keeps credentials.
|
|
81
|
+
- Load the file in a subshell, so its integration token never replaces the personal one in your shell:
|
|
82
|
+
`( set -a && . <secrets file> && set +a && fabrika … )`.
|
|
83
|
+
- Keep any other credential in its own `0600` file (`umask 077` first) and pass it as `NAME="$(cat <file>)"`.
|
|
84
|
+
Check a file with `wc -c`, never with `cat`.
|
|
37
85
|
|
|
38
86
|
## Step 0. Get the CLI at the release tag
|
|
39
87
|
|
|
@@ -49,60 +97,73 @@ cd fabrika-platform && bun install --frozen-lockfile
|
|
|
49
97
|
In this guide, `fabrika` means `bun <clone>/packages/cli/src/index.ts`. Do not edit the clone: a dirty
|
|
50
98
|
checkout is refused, and a local commit is not on GitHub, so namespaces cannot fetch it.
|
|
51
99
|
|
|
100
|
+
Install a released tag, not a branch. Every application build fetches `@fabrika/check` at the version of the
|
|
101
|
+
checkout, so `install` and `deploy` refuse a checkout whose version npm does not have yet: `this checkout is
|
|
102
|
+
version X, which is not published yet`. A `main` between a release's version bump and its publish is such a
|
|
103
|
+
checkout.
|
|
104
|
+
|
|
52
105
|
## Step 1. Install the platform
|
|
53
106
|
|
|
54
107
|
```bash
|
|
55
108
|
export FABRIKA_ZEROPS_ACCESS_TOKEN="$(cat <personal token file>)"
|
|
56
109
|
curl -s -H "Authorization: Bearer $FABRIKA_ZEROPS_ACCESS_TOKEN" \
|
|
57
|
-
https://api.app-prg1.zerops.io/api/rest/public/user/info | jq '.clientUserList[] | {clientId, name: .client.accountName}'
|
|
58
|
-
fabrika platform install --provider=zerops --create-project --client-id=<clientId> --env=<name> --
|
|
110
|
+
https://api.app-prg1.zerops.io/api/rest/public/user/info | jq '{email, clients: [.clientUserList[] | {clientId, name: .client.accountName, role: .roleCode}]}'
|
|
111
|
+
fabrika platform install --provider=zerops --create-project --client-id=<clientId> --env=<name> --tier=<light|standard> \
|
|
112
|
+
--secrets-file=<path outside the clone> --source-dir=. --yes > install.log 2>&1
|
|
59
113
|
```
|
|
60
114
|
|
|
61
|
-
-
|
|
62
|
-
|
|
115
|
+
- Check the token kind first. The `email` of a personal token is the person's address, and its `role` on the
|
|
116
|
+
client is `OWNER`. An `email` of `token-<id>@zerops.io` is an INTEGRATION token. The install refuses one before
|
|
117
|
+
it creates anything, because an integration token may not mint the token the control plane needs. Ask the
|
|
118
|
+
human for a personal token instead.
|
|
119
|
+
- Run it from the clone root. It runs for many minutes: it imports the tier's services, builds the proxy
|
|
120
|
+
once to learn the public hosts, then deploys everything.
|
|
63
121
|
- `--yes` is required when stdin is not a terminal.
|
|
64
|
-
-
|
|
65
|
-
|
|
122
|
+
- `--secrets-file` names a NEW file outside the clone, for example `~/fabrika-<env>.secrets.env`. An existing
|
|
123
|
+
file is refused. The log prints no credential.
|
|
66
124
|
- The project id is printed BEFORE the long wait. If the run is interrupted, re-run with
|
|
67
125
|
`--project-id=<id>` instead of `--create-project`.
|
|
68
126
|
- A project that already holds generated secrets is refused. Do not try to force it: a second bring-up
|
|
69
127
|
over the same project would destroy the vault key. Start over in a new empty project.
|
|
70
128
|
- The account must be in the default region, or set `FABRIKA_ZEROPS_API_URL` for every command.
|
|
71
129
|
|
|
72
|
-
**Check.** The log ends with
|
|
73
|
-
the
|
|
130
|
+
**Check.** The log ends with the `Hand off` step: where the credentials are, `The console answers at
|
|
131
|
+
https://<console host>`, and an `ADMIT YOURSELF` box that holds the step 2 command with `--iam-host` filled
|
|
132
|
+
in. The secrets file exists with mode `0600`. The three hosts are printed earlier, right after pass 1, as
|
|
133
|
+
`✓ iam <host>`, `✓ console <host>` and `✓ operations <host>`. Write them down.
|
|
74
134
|
|
|
75
135
|
## Step 2. Admit the first administrator
|
|
76
136
|
|
|
77
137
|
```bash
|
|
78
|
-
|
|
79
|
-
fabrika platform admin --provider=zerops --email=<address> --iam-host=<IAM host from step 1>
|
|
138
|
+
( set -a && . <secrets file> && set +a && \
|
|
139
|
+
fabrika platform admin --provider=zerops --email=<address> --iam-host=<IAM host from step 1> )
|
|
80
140
|
```
|
|
81
141
|
|
|
82
|
-
It prints one enrollment URL
|
|
83
|
-
human
|
|
84
|
-
|
|
142
|
+
It prints one enrollment URL and the time it expires. Nothing is e-mailed: the installation has no mail
|
|
143
|
+
service, so do not tell the human to wait for a message. The URL is a credential: give it to the human only,
|
|
144
|
+
and do not log it. The human opens it, sets a password and signs in at the console origin.
|
|
145
|
+
|
|
146
|
+
The URL is valid for ONE HOUR. Hand it over at once. A re-run creates nothing new: while the enrollment is
|
|
147
|
+
unused it only reports that one is outstanding, and it never prints the URL again. To get a new URL, after
|
|
148
|
+
expiry or after a lost one, run the same command with `--reissue`. It replaces the old enrollment, so the old
|
|
149
|
+
URL stops working.
|
|
85
150
|
|
|
86
151
|
## Step 3. Create the sidecar repository
|
|
87
152
|
|
|
88
153
|
The sidecar is a GitHub repository the operator owns. Its workflow runs `platform deploy` at the pinned tag.
|
|
89
154
|
|
|
90
|
-
|
|
155
|
+
Steps 4 and 5 do not depend on this step: the installation from step 1 already runs. The sidecar is what
|
|
156
|
+
upgrades it later (step 6). Run this step before or after step 5. Only `platform upgrade` needs it.
|
|
91
157
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
curl -s -H "$H" "$A/service-stack/$control/env" | jq -r '.items[] | select(.key=="FABRIKA_ZEROPS_ACCESS_TOKEN") | .content' > integration.token
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Then run `init` from a directory that is NOT inside any git repository. The fabrika clone is a repository,
|
|
100
|
-
and a home directory under dotfiles version control is one too. Otherwise pass `--checkout=<dir>` outside one:
|
|
158
|
+
`init` takes the integration token step 1 minted, never the personal one. The secrets file holds it as
|
|
159
|
+
`FABRIKA_ZEROPS_ACCESS_TOKEN`, beside the provisioning key. Run `init` from a directory that is NOT inside
|
|
160
|
+
any git repository. The fabrika clone is a repository, and a home directory under dotfiles version control is
|
|
161
|
+
one too. Otherwise pass `--checkout=<dir>` outside one:
|
|
101
162
|
|
|
102
163
|
```bash
|
|
103
|
-
|
|
164
|
+
( set -a && . <secrets file> && set +a && \
|
|
104
165
|
fabrika platform init --provider=zerops <installation> --repo=<owner>/<name> --tag=<tag> \
|
|
105
|
-
--project-id=<project id> --env=<name from step 1> --yes < /dev/null
|
|
166
|
+
--project-id=<project id> --env=<name from step 1> --yes < /dev/null )
|
|
106
167
|
```
|
|
107
168
|
|
|
108
169
|
- Without `--repo` the repository is `contember/fabrika-zerops-<installation>`. Pass `--repo` for any
|
|
@@ -115,14 +176,17 @@ FABRIKA_ZEROPS_ACCESS_TOKEN="$(cat integration.token)" FABRIKA_IAM_PROVISIONING_
|
|
|
115
176
|
## Step 4. Mint a machine key for `fabrika control`
|
|
116
177
|
|
|
117
178
|
```bash
|
|
118
|
-
|
|
119
|
-
curl -s -H "$H" "$A/service-stack/$iam/env" | jq -r '.items[] | select(.key=="FABRIKA_IAM_RPC_KEY") | .content' > iam-rpc.key
|
|
120
|
-
FABRIKA_IAM_RPC_URL=https://<IAM host> FABRIKA_IAM_RPC_KEY="$(cat iam-rpc.key)" FABRIKA_IAM_PROVISIONING_KEY="$(cat provisioning.key)" \
|
|
179
|
+
( umask 077 && set -a && . <secrets file> && set +a && FABRIKA_IAM_RPC_URL=https://<IAM host> \
|
|
121
180
|
fabrika control key issue --label=<name> --permissions=app.manage,namespace.manage,deploy.trigger,deploy.read \
|
|
122
|
-
--expires-in=2592000 > control
|
|
123
|
-
|
|
181
|
+
--expires-in=2592000 > control.key )
|
|
182
|
+
wc -c control.key && ls -l control.key
|
|
124
183
|
```
|
|
125
184
|
|
|
185
|
+
The secrets file supplies `FABRIKA_IAM_RPC_KEY` and `FABRIKA_IAM_PROVISIONING_KEY`.
|
|
186
|
+
|
|
187
|
+
- stdout carries the key and nothing else; the credential id goes to stderr. Keep the redirect inside the
|
|
188
|
+
`umask 077` subshell: `control.key` is then created with mode `0600` (`-rw-------`). Outside it, the file
|
|
189
|
+
is readable by other users.
|
|
126
190
|
- `FABRIKA_IAM_RPC_URL` is IAM's PUBLIC `https://` origin. The private `http://iam:3000` answers only inside
|
|
127
191
|
the Zerops project.
|
|
128
192
|
- A key expires after at most 30 days. Mint a new one when it does.
|
|
@@ -134,30 +198,59 @@ export FABRIKA_CONTROL_URL=https://<console host>
|
|
|
134
198
|
FABRIKA_CONTROL_KEY="$(cat control.key)" fabrika control apps list
|
|
135
199
|
```
|
|
136
200
|
|
|
137
|
-
**Check.** `apps list`
|
|
201
|
+
**Check.** On a fresh installation `apps list` prints `no applications registered yet` (on stderr) and
|
|
202
|
+
exits 0. An authentication error means the key or the console origin is wrong.
|
|
138
203
|
|
|
139
204
|
## Step 5. Deploy the first application
|
|
140
205
|
|
|
141
|
-
The
|
|
206
|
+
The example is [`contember/fabrika-example-zerops`](https://github.com/contember/fabrika-example-zerops).
|
|
142
207
|
Its `fabrika.config.ts` places the app with its own database (`mid` preset), and `fabrika.cheap.config.ts`
|
|
143
208
|
places it on the namespace's shared PostgreSQL (`cheap` preset). Use the config that matches the preset.
|
|
144
209
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
210
|
+
**Repository access decides how you register** (step 5.2). Check it first:
|
|
211
|
+
`curl -s -o /dev/null -w '%{http_code}\n' https://api.github.com/repos/<owner>/<name>`.
|
|
212
|
+
|
|
213
|
+
- `200`: the repository is public. Register it with `--installation-id=none`. Control reads it without a token.
|
|
214
|
+
- `404`: GitHub hides it from an anonymous caller, so it is private (or the name is wrong). `register` refuses
|
|
215
|
+
it with `--installation-id=none`. Ask the human to connect the GitHub App in the console first (Settings →
|
|
216
|
+
Source, a browser flow), then register without `--installation-id`.
|
|
217
|
+
|
|
218
|
+
**Use the installation's release in the application.** The application's `@fabrika/*` packages, the CLI
|
|
219
|
+
among them, must be the version of the tag you installed: for `v0.4.3`, `^0.4.3`. Another version can build
|
|
220
|
+
a manifest this installation's control refuses or reads differently. From the fabrika clone of step 0, run
|
|
221
|
+
`bun scripts/release.ts example-pin <application checkout> --tag=<tag>`. It rewrites every `@fabrika/*` range
|
|
222
|
+
and commits nothing. Commit and push the change in the application repository, then run `bun install` there.
|
|
223
|
+
Check with `bun pm ls | grep @fabrika/cli`.
|
|
224
|
+
|
|
225
|
+
1. **Create a namespace** with the preset and core package the human chose (see Decisions). Without a
|
|
226
|
+
custom domain bound to its proxy, pass `--public-access=zerops-subdomain`; the default is
|
|
227
|
+
`custom-domain`, which serves no host until you bind one.
|
|
148
228
|
|
|
149
229
|
```bash
|
|
150
|
-
fabrika control namespaces plan --namespace=<id> --env=<name> --preset
|
|
151
|
-
fabrika control namespaces create --namespace=<id> --env=<name> --preset
|
|
230
|
+
fabrika control namespaces plan --namespace=<id> --env=<name> --preset=<cheap|mid|full> --public-access=zerops-subdomain
|
|
231
|
+
fabrika control namespaces create --namespace=<id> --env=<name> --preset=<cheap|mid|full> --public-access=zerops-subdomain
|
|
152
232
|
fabrika control namespaces get --namespace=<id>
|
|
153
233
|
```
|
|
154
234
|
|
|
155
235
|
`create` returns while the state is `pending`. Provisioning takes several minutes. Poll `get` every
|
|
156
|
-
30 seconds until it is `ready
|
|
236
|
+
30 seconds until it is `ready`.
|
|
237
|
+
|
|
238
|
+
- If the state is `failed`, `get` prints the cause and the command that resumes it on stderr. Run
|
|
239
|
+
`fabrika control namespaces reconcile --namespace=<id>` and poll `get` again. Provisioning resumes from
|
|
240
|
+
where it stopped. Do not create a second namespace.
|
|
241
|
+
- `get` lists one host per line as `<host> <port> free` or `<host> <port> taken by <app>/<env>`. A
|
|
242
|
+
`zerops-subdomain` namespace has six hosts, one per public port of its proxy: 8080 and 8082–8086. No
|
|
243
|
+
port is special or reserved; 8081 is the proxy's health port and is never listed. Use ANY `free` host,
|
|
244
|
+
for example the first one. Each host serves exactly one application environment, so six is the limit
|
|
245
|
+
per namespace.
|
|
157
246
|
|
|
158
247
|
2. **Build and register**, from the application's repository root. The repository must be pushed: control
|
|
159
248
|
deploys the commit on GitHub, not your working tree.
|
|
160
249
|
|
|
250
|
+
`--app=<id>` is the application id the manifest carries: `jq -r .app.id fabrika.manifest.json` after the
|
|
251
|
+
build. The application's `fabrika.config.ts` sets it (`defineApp({ id })`); the example takes it from its
|
|
252
|
+
contract's vocabulary, and it is `notes`.
|
|
253
|
+
|
|
161
254
|
```bash
|
|
162
255
|
bun install
|
|
163
256
|
bunx fabrika app build --env=<name> # cheap: add --config=fabrika.cheap.config.ts
|
|
@@ -166,8 +259,10 @@ places it on the namespace's shared PostgreSQL (`cheap` preset). Use the config
|
|
|
166
259
|
--namespace=<namespace id> --domain=<free host> --installation-id=none
|
|
167
260
|
```
|
|
168
261
|
|
|
169
|
-
- `--installation-id=none`
|
|
170
|
-
|
|
262
|
+
- `--installation-id=none` works only for a repository GitHub serves without a token. `register` reads it
|
|
263
|
+
once and refuses one that is not publicly readable, so the failure shows here and not at deploy. A
|
|
264
|
+
private repository needs the GitHub App connected in the console first (Settings → Source, a browser
|
|
265
|
+
flow for the human); then omit the flag.
|
|
171
266
|
- `register` CREATES and refuses a second registration. A changed manifest goes through
|
|
172
267
|
`control apps environments put` with the same placement flags.
|
|
173
268
|
|
|
@@ -179,6 +274,9 @@ places it on the namespace's shared PostgreSQL (`cheap` preset). Use the config
|
|
|
179
274
|
FABRIKA_CONTROL_KEY="$(cat control.key)" bunx fabrika control runs log --run=<run id>
|
|
180
275
|
```
|
|
181
276
|
|
|
277
|
+
A failed run's log ends with `deploy failed: <reason>`. For example, `source resolve failed
|
|
278
|
+
(ref_not_found)` means control could not read the repository or the ref.
|
|
279
|
+
|
|
182
280
|
The deploy fails when the repository at the deployed commit does not match the registered manifest.
|
|
183
281
|
Commit and push, rebuild the manifest, `apps environments put` it, and deploy again.
|
|
184
282
|
|
|
@@ -195,12 +293,31 @@ fabrika platform upgrade --provider=zerops --to=<newer tag> --sidecar=<sidecar c
|
|
|
195
293
|
```
|
|
196
294
|
|
|
197
295
|
It commits the tag to the sidecar's `fabrika.ref`, pushes, and follows the workflow run it triggers. It
|
|
198
|
-
needs only the `gh` login.
|
|
296
|
+
needs only the `gh` login. When the sidecar's `.github/workflows/platform.yml` differs from the one this CLI
|
|
297
|
+
generates, the same commit rewrites it, so run `upgrade` with the newest CLI: a hand edit of that file does not
|
|
298
|
+
survive. The workflow installs the Bun release the pinned tag names in its `.bun-version` (1.3.14 for a tag
|
|
299
|
+
that has none), never the newest one. From 0.4.2 or earlier, skip 0.4.3: its deploy stops at "Compose the proxy
|
|
300
|
+
manifest" because the proxy's `FABRIKA_PROXY_MANIFEST_JSON` is sensitive. Do not re-save that variable. Upgrade to a
|
|
301
|
+
later tag: its deploy deletes the variable, and needs no sudo mode. Existing namespaces keep their proxy version. To move one to the proxy of the
|
|
302
|
+
new release, after the run succeeded:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
fabrika control namespaces update --namespace=<id> --proxy-source=current
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
It re-pins the namespace and queues the reconcile that rebuilds its proxy; poll `namespaces get` until it is
|
|
309
|
+
`ready`. A namespace pinned to a release before the proxy manifest moved into the build archive carries it in a
|
|
310
|
+
service variable, which Zerops caps at about 32 KB: when a deploy fails with `serviceEnvValueTooLarge`, this is
|
|
311
|
+
the fix, and it also accepts a namespace in `failed`.
|
|
199
312
|
|
|
200
313
|
## When something fails
|
|
201
314
|
|
|
202
315
|
- Every refusal names its flag or variable. Fix that one value and re-run. `init`, `admin`, `deploy` and
|
|
203
316
|
`upgrade` are safe to re-run. `install` is not: see step 1.
|
|
317
|
+
- A failed install pass 2 is finished with `platform deploy`, with the credentials from the secrets file. The
|
|
318
|
+
install prints the command.
|
|
319
|
+
- Lost the secrets file? Nothing in fabrika can recover it, and Zerops will not hand the values back. Start a
|
|
320
|
+
new installation in a new project.
|
|
204
321
|
- A Zerops build or deploy failure: read the service log in the Zerops GUI or with
|
|
205
322
|
`zcli service log -S <service id> --show-build-logs`.
|
|
206
323
|
- Do not work around a refusal by editing the clone or the generated sidecar. Report what failed, with the
|
|
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
13
13
|
import { createZeropsApi, defaultSleep } from '../index'
|
|
14
14
|
import { consoleAdminCollaborators, runPlatformAdmin } from './admin'
|
|
15
15
|
import { parsePlatformAdminArgs, REISSUE_FLAG } from './admin-options'
|
|
16
|
+
import { npmCheckerPublication } from './checker-release'
|
|
16
17
|
import { deployPlatform, PLATFORM_CONCURRENT_DEPLOY, PLATFORM_DEPLOY_ORDER, PLATFORM_SEQUENTIAL_DEPLOY } from './deploy'
|
|
17
18
|
import { parsePlatformDeployArgs } from './deploy-options'
|
|
18
19
|
import { consoleInitCollaborators, runInit } from './init'
|
|
@@ -66,9 +67,9 @@ deploy that push triggers.
|
|
|
66
67
|
|
|
67
68
|
── platform install ──────────────────────────────────────────────────────────────────────────────
|
|
68
69
|
|
|
69
|
-
The from-scratch bring-up. With \`--create-project\` it creates the project too, with core
|
|
70
|
-
LIGHT and \`envIsolation: service\` — the two settings Zerops accepts at project
|
|
71
|
-
afterwards. \`--project-id=<id>\` installs into an empty project you created yourself instead, with both
|
|
70
|
+
The from-scratch bring-up. With \`--create-project\` it creates the project too, with the tier's core
|
|
71
|
+
package (LIGHT or SERIOUS) and \`envIsolation: service\` — the two settings Zerops accepts at project
|
|
72
|
+
CREATION only and never afterwards. \`--project-id=<id>\` installs into an empty project you created yourself instead, with both
|
|
72
73
|
already right; nothing here can read \`envIsolation\` back or correct it.
|
|
73
74
|
|
|
74
75
|
Interactive and laptop-side. It CONFIRMS before every step that leaves your machine: reading the
|
|
@@ -79,27 +80,30 @@ is not consent.
|
|
|
79
80
|
|
|
80
81
|
1. create the project and wait for it to be ACTIVE (\`--create-project\`; its id is printed before
|
|
81
82
|
the wait), or read the one you named — and refuse a core package the tier does not match
|
|
82
|
-
2. import
|
|
83
|
+
2. import the tier's services without code, and wait for the import's processes
|
|
83
84
|
3. generate six secrets and mint the control plane's Zerops INTEGRATION token
|
|
84
|
-
(client role NO_ACCESS, ADMIN on this project only — never your personal token)
|
|
85
|
+
(client role NO_ACCESS, ADMIN on this project only — never your personal token), and write
|
|
86
|
+
all of them to \`--secrets-file\` (mode 0600) before the first one is placed
|
|
85
87
|
4. pass 1: give the proxy an EMPTY manifest and build it, so it publishes one public hostname per
|
|
86
88
|
HTTP port; the three platform hosts are then READ off those, never composed
|
|
87
89
|
5. write every remaining variable on all four runtime services
|
|
88
90
|
6. pass 2: hand the whole ordered sequence to \`platform deploy\`
|
|
89
|
-
7. print the
|
|
91
|
+
7. print the next commands; they read their credentials from the secrets file. Zerops holds the
|
|
92
|
+
same values as SENSITIVE variables and does not hand them back, so that file is the only copy
|
|
90
93
|
|
|
91
94
|
It REFUSES a project that already holds an installation's generated secrets: a second bring-up would
|
|
92
95
|
roll a new vault key over the old one, which is unrecoverable, and new signing keys, which logs
|
|
93
96
|
everyone out. Once every bootstrap variable is placed, \`platform deploy\` can update the installation
|
|
94
|
-
or finish a failed pass 2
|
|
95
|
-
FABRIKA_ZEROPS_ACCESS_TOKEN from control in Zerops if the handoff was interrupted.
|
|
97
|
+
or finish a failed pass 2, with the credentials from the secrets file.
|
|
96
98
|
Neither deploy nor init completes a partial bootstrap. If interrupted after a generated secret was
|
|
97
99
|
placed but before every bootstrap variable was placed, preserve the existing keys and arrange
|
|
98
100
|
operator-owned recovery, or start a fresh installation in a separate empty project.
|
|
99
101
|
Re-running install after any generated secret was placed is refused.
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
Two tiers, one project each. \`light\` (core LIGHT): one single-node PostgreSQL and one bucket shared by
|
|
104
|
+
IAM, control and Operations, one container per service. \`standard\` (core SERIOUS): an HA PostgreSQL for
|
|
105
|
+
IAM and control, a second HA PostgreSQL and bucket for Operations, two containers of IAM and the proxy.
|
|
106
|
+
Authentication is PASSWORD ONLY; no administrator exists yet when this finishes.
|
|
103
107
|
|
|
104
108
|
Options (a flag beats the environment variable beside it):
|
|
105
109
|
|
|
@@ -112,7 +116,8 @@ Options (a flag beats the environment variable beside it):
|
|
|
112
116
|
--env=<name> FABRIKA_PLATFORM_ENVIRONMENT written to every service as ENVIRONMENT
|
|
113
117
|
--scheme=<http|https> FABRIKA_PLATFORM_SCHEME default https
|
|
114
118
|
--source-dir=<checkout> required clean full checkout running this CLI
|
|
115
|
-
--tier
|
|
119
|
+
--tier=<light|standard> FABRIKA_PLATFORM_TIER default light
|
|
120
|
+
--secrets-file=<path> FABRIKA_PLATFORM_SECRETS_FILE required: a NEW file outside the checkout
|
|
116
121
|
${UNATTENDED_FLAG} answer every confirmation yes —
|
|
117
122
|
required when stdin is not a terminal
|
|
118
123
|
|
|
@@ -248,23 +253,24 @@ commit availability before its effects; a local commit is not thereby published
|
|
|
248
253
|
The first human. A fresh installation has none — nothing seeds an admission list, and this command is
|
|
249
254
|
why it does not have to. It is unattended, prompts for nothing, and changes nothing on a re-run:
|
|
250
255
|
|
|
251
|
-
1. find the principal holding that
|
|
256
|
+
1. find the principal holding that address, or create one
|
|
252
257
|
2. grant it the CROSS-APP \`admin\` role, unless it already holds one
|
|
253
258
|
3. issue ONE password enrollment and print its URL
|
|
254
259
|
|
|
255
260
|
The grant is cross-app (\`app: null\`) and that is load-bearing: grants filter to the calling app, so an
|
|
256
261
|
\`admin\` grant scoped to the console's own app id leaves Delivery and Operations working while the
|
|
257
|
-
Access plane refuses. A re-run
|
|
262
|
+
Access plane refuses. A re-run creates nobody twice, grants nothing twice and issues no second
|
|
258
263
|
enrollment — a password that is already set, or an enrollment already outstanding, is REPORTED. Pass
|
|
259
264
|
\`${REISSUE_FLAG}\` to replace an outstanding one, which is the way out when the first expired unopened.
|
|
260
265
|
|
|
261
|
-
|
|
266
|
+
Nothing is e-mailed: a Zerops installation has no mail service. Hand the URL to the administrator
|
|
267
|
+
yourself. It is a credential: it is printed once, on a line of its own, and stored nowhere.
|
|
262
268
|
|
|
263
269
|
\`install\` reports the host this needs as \`✓ iam <host>\` and ends with this command ready to run.
|
|
264
270
|
|
|
265
271
|
Options (a flag beats the environment variable beside it):
|
|
266
272
|
|
|
267
|
-
--email=<address> FABRIKA_PLATFORM_ADMIN_EMAIL the administrator's
|
|
273
|
+
--email=<address> FABRIKA_PLATFORM_ADMIN_EMAIL the administrator's sign-in address
|
|
268
274
|
--iam-host=<host> FABRIKA_PLATFORM_IAM_HOST IAM's public hostname, as \`deploy\` names it
|
|
269
275
|
--scheme=<http|https> FABRIKA_PLATFORM_SCHEME default https
|
|
270
276
|
${REISSUE_FLAG} replace an outstanding enrollment
|
|
@@ -347,6 +353,7 @@ const runDeploy = async (argv: readonly string[]): Promise<void> => {
|
|
|
347
353
|
const input = parsePlatformDeployArgs(argv, process.env)
|
|
348
354
|
await deployPlatform(input, {
|
|
349
355
|
prepareSource: prepareInstallationSource,
|
|
356
|
+
checkerPublished: npmCheckerPublication(),
|
|
350
357
|
uploadFetch: (url, init) => fetch(url, init),
|
|
351
358
|
api: createZeropsApi({
|
|
352
359
|
token: input.accessToken,
|
|
@@ -138,9 +138,9 @@ export const parseInitArgs = (argv: readonly string[], env: CommandEnv, cwd: str
|
|
|
138
138
|
accessToken: credential(
|
|
139
139
|
env,
|
|
140
140
|
'FABRIKA_ZEROPS_ACCESS_TOKEN',
|
|
141
|
-
|
|
141
|
+
'the Zerops INTEGRATION token `platform install` minted, from its secrets file',
|
|
142
142
|
),
|
|
143
|
-
provisioningKey: credential(env, 'FABRIKA_IAM_PROVISIONING_KEY', 'the px_ key `platform install`
|
|
143
|
+
provisioningKey: credential(env, 'FABRIKA_IAM_PROVISIONING_KEY', 'the px_ key from the secrets file `platform install` wrote'),
|
|
144
144
|
unattended: argv.includes(UNATTENDED_FLAG),
|
|
145
145
|
...(hosts === undefined ? {} : { hosts }),
|
|
146
146
|
...(apiBaseUrl === undefined ? {} : { apiBaseUrl }),
|
package/src/installation/init.ts
CHANGED
|
@@ -95,8 +95,14 @@ export const inspectPlatform = async (projectId: string, api: ZeropsApi, signal:
|
|
|
95
95
|
if (control === undefined || control.id === '') throw new Error(`Zerops project ${projectId} has no \`control\` service`)
|
|
96
96
|
const proxy = oneService(services, 'proxy')
|
|
97
97
|
if (proxy === undefined || proxy.id === '') throw new Error(`Zerops project ${projectId} has no \`proxy\` service`)
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
// Only the readable values: everything init prints is derived from non-secret configuration.
|
|
99
|
+
const readEnv = async (serviceId: string): Promise<Map<string, string>> => {
|
|
100
|
+
const readable = new Map<string, string>()
|
|
101
|
+
for (const item of await api.listServiceEnv({ serviceId, signal })) {
|
|
102
|
+
if (!item.sensitive) readable.set(item.key, item.content)
|
|
103
|
+
}
|
|
104
|
+
return readable
|
|
105
|
+
}
|
|
100
106
|
return {
|
|
101
107
|
controlEnv: await readEnv(control.id),
|
|
102
108
|
proxyEnv: await readEnv(proxy.id),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Confirmations } from '@fabrika/installation-init'
|
|
2
2
|
import type { SchemaReconciler } from '@fabrika/provider-contract'
|
|
3
3
|
import type { Sleeper, ZeropsApi } from '../index'
|
|
4
|
+
import type { CheckerPublication } from './checker-release'
|
|
4
5
|
import type { PlatformSourcePreparer } from './installation-source'
|
|
5
6
|
import type { InitLog } from './log'
|
|
6
7
|
import type { PlatformUploadFetch } from './platform-upload'
|
|
@@ -11,6 +12,7 @@ export type InstallPrompts = Confirmations
|
|
|
11
12
|
/** Everything `runInstall` reaches the outside world through. */
|
|
12
13
|
export interface InstallCollaborators {
|
|
13
14
|
readonly prepareSource: PlatformSourcePreparer
|
|
15
|
+
readonly checkerPublished: CheckerPublication
|
|
14
16
|
readonly uploadFetch: PlatformUploadFetch
|
|
15
17
|
readonly api: ZeropsApi
|
|
16
18
|
readonly reconcileSchema: SchemaReconciler
|
|
@@ -18,6 +20,8 @@ export interface InstallCollaborators {
|
|
|
18
20
|
readonly log: InitLog
|
|
19
21
|
readonly prompts: InstallPrompts
|
|
20
22
|
readonly signal: AbortSignal
|
|
23
|
+
/** Create the local secrets file (`--secrets-file`) with this text. Never logged. */
|
|
24
|
+
readonly storeSecrets: (text: string) => Promise<void>
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export const assertRunning = (signal: AbortSignal): void => {
|