@fabrika/provider-zerops 0.4.0 → 0.4.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabrika/provider-zerops",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,12 +43,12 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "zod": "^3.25.76",
46
- "@fabrika/auth": "^0.4.0",
47
- "@fabrika/auth-core": "^0.4.0",
48
- "@fabrika/iam-contract": "^0.4.0",
49
- "@fabrika/installation-init": "^0.4.0",
50
- "@fabrika/provider-contract": "^0.4.0",
51
- "@fabrika/proxy-core": "^0.4.0",
46
+ "@fabrika/auth": "^0.4.2",
47
+ "@fabrika/auth-core": "^0.4.2",
48
+ "@fabrika/iam-contract": "^0.4.2",
49
+ "@fabrika/installation-init": "^0.4.2",
50
+ "@fabrika/provider-contract": "^0.4.2",
51
+ "@fabrika/proxy-core": "^0.4.2",
52
52
  "yaml": "^2.9.1"
53
53
  }
54
54
  }
@@ -0,0 +1,207 @@
1
+ # Installing fabrika on Zerops — the operator's guide
2
+
3
+ From an empty Zerops account to a serving application. This guide ships with the CLI, so it matches the
4
+ release you run. It gives the order and the traps. Each command's flags are in its own help, and that text
5
+ is authoritative:
6
+
7
+ ```bash
8
+ fabrika platform --provider=zerops --help
9
+ fabrika control --help
10
+ ```
11
+
12
+ Run the steps in order and check each one before the next. Every command refuses a missing value with a
13
+ message that names the flag or the variable to set. Read the message: it names the fix.
14
+
15
+ ## Before you start
16
+
17
+ **Ask the human for these first.** You cannot produce them, and each one blocks a later step:
18
+
19
+ | What | Needed at |
20
+ | ------------------------------------------------------------------------------ | ---------- |
21
+ | A Zerops personal access token, generated in the Zerops GUI | step 1 |
22
+ | A `gh` login allowed to create a repository in the organization that owns it | step 3 |
23
+ | The administrator's e-mail, and the human to open the enrollment URL | step 2 |
24
+ | For a PRIVATE application repository: the GitHub App connected in the console | step 5 |
25
+ | The fabrika release tag to install, for example `v0.4.2` (a branch is refused) | steps 1, 3 |
26
+
27
+ Also needed on the machine: [Bun](https://bun.sh) ≥ 1.3, `git`, `gh`, `curl` and `jq`.
28
+
29
+ ## Secrets: rules for an agent
30
+
31
+ - Every credential is read from the ENVIRONMENT ONLY. No command takes one as a flag.
32
+ - Never print a credential, never put one in a command line you echo, never commit one.
33
+ - Keep each one in its own file with mode `0600` (`umask 077` first) and pass it as
34
+ `NAME="$(cat <file>)" fabrika …`. Check a file with `wc -c`, never with `cat`.
35
+ - The provisioning key from step 1 is printed ONCE. Capture it from the output into its file at once.
36
+ The only other copies are in the `iam` and `control` service environments.
37
+
38
+ ## Step 0. Get the CLI at the release tag
39
+
40
+ `platform install` and `platform deploy` upload the source of the checkout the CLI runs from, and refuse
41
+ anything else (`--source-dir`). So the bring-up runs from a clean clone at the tag, not from a CLI installed
42
+ with `bun add --global`:
43
+
44
+ ```bash
45
+ git clone --depth 1 --branch <tag> https://github.com/contember/fabrika-platform.git fabrika-platform
46
+ cd fabrika-platform && bun install --frozen-lockfile
47
+ ```
48
+
49
+ In this guide, `fabrika` means `bun <clone>/packages/cli/src/index.ts`. Do not edit the clone: a dirty
50
+ checkout is refused, and a local commit is not on GitHub, so namespaces cannot fetch it.
51
+
52
+ ## Step 1. Install the platform
53
+
54
+ ```bash
55
+ export FABRIKA_ZEROPS_ACCESS_TOKEN="$(cat <personal token file>)"
56
+ 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> --source-dir=. --yes > install.log 2>&1
59
+ ```
60
+
61
+ - Run it from the clone root. It runs for many minutes: it imports six services, builds the proxy once to
62
+ learn the public hosts, then deploys everything.
63
+ - `--yes` is required when stdin is not a terminal.
64
+ - Capture the key right away and keep the log away from anything that publishes it:
65
+ `grep -o 'FABRIKA_IAM_PROVISIONING_KEY=px_[A-Za-z0-9_-]*' install.log | cut -d= -f2 > provisioning.key`
66
+ - The project id is printed BEFORE the long wait. If the run is interrupted, re-run with
67
+ `--project-id=<id>` instead of `--create-project`.
68
+ - A project that already holds generated secrets is refused. Do not try to force it: a second bring-up
69
+ over the same project would destroy the vault key. Start over in a new empty project.
70
+ - The account must be in the default region, or set `FABRIKA_ZEROPS_API_URL` for every command.
71
+
72
+ **Check.** The log ends with `✓ iam <host>`, the console origin and the provisioning key box. Write down
73
+ the three hosts: IAM, console and Operations.
74
+
75
+ ## Step 2. Admit the first administrator
76
+
77
+ ```bash
78
+ FABRIKA_IAM_PROVISIONING_KEY="$(cat provisioning.key)" \
79
+ fabrika platform admin --provider=zerops --email=<address> --iam-host=<IAM host from step 1>
80
+ ```
81
+
82
+ It prints one enrollment URL. The URL is a credential: give it to the human only, and do not log it. The
83
+ human opens it, sets a password and signs in at the console origin. A re-run creates nothing new;
84
+ `--reissue` replaces an enrollment that expired unopened.
85
+
86
+ ## Step 3. Create the sidecar repository
87
+
88
+ The sidecar is a GitHub repository the operator owns. Its workflow runs `platform deploy` at the pinned tag.
89
+
90
+ Read the integration token that step 1 minted. Never use the personal token here:
91
+
92
+ ```bash
93
+ A=https://api.app-prg1.zerops.io/api/rest/public
94
+ H="Authorization: Bearer $FABRIKA_ZEROPS_ACCESS_TOKEN"
95
+ control=$(curl -s -H "$H" "$A/project/<project id>/service-stack?limit=100&offset=0" | jq -r '.list[] | select(.name=="control") | .id')
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:
101
+
102
+ ```bash
103
+ FABRIKA_ZEROPS_ACCESS_TOKEN="$(cat integration.token)" FABRIKA_IAM_PROVISIONING_KEY="$(cat provisioning.key)" \
104
+ fabrika platform init --provider=zerops <installation> --repo=<owner>/<name> --tag=<tag> \
105
+ --project-id=<project id> --env=<name from step 1> --yes < /dev/null
106
+ ```
107
+
108
+ - Without `--repo` the repository is `contember/fabrika-zerops-<installation>`. Pass `--repo` for any
109
+ other organization.
110
+ - Keep the local checkout path. `platform upgrade` needs it later (`--sidecar=<dir>`).
111
+
112
+ **Check.** The `platform` workflow run in the sidecar repository ends green:
113
+ `gh run list --repo <owner>/<name>`.
114
+
115
+ ## Step 4. Mint a machine key for `fabrika control`
116
+
117
+ ```bash
118
+ iam=$(curl -s -H "$H" "$A/project/<project id>/service-stack?limit=100&offset=0" | jq -r '.list[] | select(.name=="iam") | .id')
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)" \
121
+ fabrika control key issue --label=<name> --permissions=app.manage,namespace.manage,deploy.trigger,deploy.read \
122
+ --expires-in=2592000 > control-key.out
123
+ grep -o 'px_[A-Za-z0-9_-]*' control-key.out | head -1 > control.key && rm control-key.out
124
+ ```
125
+
126
+ - `FABRIKA_IAM_RPC_URL` is IAM's PUBLIC `https://` origin. The private `http://iam:3000` answers only inside
127
+ the Zerops project.
128
+ - A key expires after at most 30 days. Mint a new one when it does.
129
+
130
+ For every `fabrika control` command from here on:
131
+
132
+ ```bash
133
+ export FABRIKA_CONTROL_URL=https://<console host>
134
+ FABRIKA_CONTROL_KEY="$(cat control.key)" fabrika control apps list
135
+ ```
136
+
137
+ **Check.** `apps list` answers, with an empty list on a fresh installation.
138
+
139
+ ## Step 5. Deploy the first application
140
+
141
+ The public example is [`contember/fabrika-example-zerops`](https://github.com/contember/fabrika-example-zerops).
142
+ Its `fabrika.config.ts` places the app with its own database (`mid` preset), and `fabrika.cheap.config.ts`
143
+ places it on the namespace's shared PostgreSQL (`cheap` preset). Use the config that matches the preset.
144
+
145
+ 1. **Create a namespace.** It is the Zerops project applications are placed in. Without a custom domain
146
+ bound to its proxy, pass `--public-access=zerops-subdomain`; the default is `custom-domain`, which serves
147
+ no host until you bind one.
148
+
149
+ ```bash
150
+ fabrika control namespaces plan --namespace=<id> --env=<name> --preset=mid --public-access=zerops-subdomain
151
+ fabrika control namespaces create --namespace=<id> --env=<name> --preset=mid --public-access=zerops-subdomain
152
+ fabrika control namespaces get --namespace=<id>
153
+ ```
154
+
155
+ `create` returns while the state is `pending`. Provisioning takes several minutes. Poll `get` every
156
+ 30 seconds until it is `ready`, then pick a host it lists as `free`.
157
+
158
+ 2. **Build and register**, from the application's repository root. The repository must be pushed: control
159
+ deploys the commit on GitHub, not your working tree.
160
+
161
+ ```bash
162
+ bun install
163
+ bunx fabrika app build --env=<name> # cheap: add --config=fabrika.cheap.config.ts
164
+ FABRIKA_CONTROL_KEY="$(cat control.key)" bunx fabrika control register --provider=zerops --app=<id> \
165
+ --repo=https://github.com/<owner>/<name> --env=<name> --manifest=fabrika.manifest.json \
166
+ --namespace=<namespace id> --domain=<free host> --installation-id=none
167
+ ```
168
+
169
+ - `--installation-id=none` is for a PUBLIC repository. A private one needs the GitHub App connected in
170
+ the console (Settings → Source, a browser flow for the human); then omit the flag.
171
+ - `register` CREATES and refuses a second registration. A changed manifest goes through
172
+ `control apps environments put` with the same placement flags.
173
+
174
+ 3. **Deploy and follow it.**
175
+
176
+ ```bash
177
+ FABRIKA_CONTROL_KEY="$(cat control.key)" bunx fabrika control deploy --app=<id> --env=<name>
178
+ FABRIKA_CONTROL_KEY="$(cat control.key)" bunx fabrika control runs list --app=<id>
179
+ FABRIKA_CONTROL_KEY="$(cat control.key)" bunx fabrika control runs log --run=<run id>
180
+ ```
181
+
182
+ The deploy fails when the repository at the deployed commit does not match the registered manifest.
183
+ Commit and push, rebuild the manifest, `apps environments put` it, and deploy again.
184
+
185
+ On an environment named `prod`, a change that widens access waits for a person to approve it in the
186
+ console before it deploys.
187
+
188
+ **Check.** The run ends `succeeded`. `https://<host>/healthz` answers 200, and `/` redirects to the IAM
189
+ sign-in. A green run proves only that Zerops accepted the build: have the human sign in and use the app.
190
+
191
+ ## Step 6. Upgrade later
192
+
193
+ ```bash
194
+ fabrika platform upgrade --provider=zerops --to=<newer tag> --sidecar=<sidecar checkout>
195
+ ```
196
+
197
+ 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. Existing namespaces keep their proxy version.
199
+
200
+ ## When something fails
201
+
202
+ - Every refusal names its flag or variable. Fix that one value and re-run. `init`, `admin`, `deploy` and
203
+ `upgrade` are safe to re-run. `install` is not: see step 1.
204
+ - A Zerops build or deploy failure: read the service log in the Zerops GUI or with
205
+ `zcli service log -S <service id> --show-build-logs`.
206
+ - Do not work around a refusal by editing the clone or the generated sidecar. Report what failed, with the
207
+ command and its message, to the people who maintain fabrika.
@@ -9,6 +9,7 @@ import {
9
9
  } from '@fabrika/installation-init'
10
10
  import type { SchemaReconciler } from '@fabrika/provider-contract'
11
11
  import type { InstallationCli, InstallationCommand } from '@fabrika/provider-contract/installation'
12
+ import { fileURLToPath } from 'node:url'
12
13
  import { createZeropsApi, defaultSleep } from '../index'
13
14
  import { consoleAdminCollaborators, runPlatformAdmin } from './admin'
14
15
  import { parsePlatformAdminArgs, REISSUE_FLAG } from './admin-options'
@@ -44,7 +45,11 @@ export {
44
45
 
45
46
  const USAGE = `Zerops installation
46
47
 
48
+ New here, or an agent? Run \`fabrika platform guide --provider=zerops\` first: the whole bring-up in order,
49
+ what to ask the human for, and the traps this text cannot show.
50
+
47
51
  Commands:
52
+ fabrika platform guide --provider=zerops
48
53
  fabrika platform install --provider=zerops [options]
49
54
  fabrika platform init --provider=zerops <installation> --tag=<tag> --project-id=<id> [options]
50
55
  fabrika platform plan --provider=zerops
@@ -305,6 +310,16 @@ laptop and not in the pipeline it triggers.
305
310
  /** No cancellation source exists behind a CLI invocation; the ports all take a signal regardless. */
306
311
  const neverAborted = (): AbortSignal => new AbortController().signal
307
312
 
313
+ /** The operator's guide of this release, shipped beside this module. */
314
+ export const readInstallationGuide = (): Promise<string> => Bun.file(fileURLToPath(new URL('./guide.md', import.meta.url))).text()
315
+
316
+ const runGuide = async (argv: readonly string[]): Promise<void> => {
317
+ if (argv.length > 0) {
318
+ throw new Error(`Unexpected Zerops guide arguments: ${argv.map(describeArgument).join(' ')}`)
319
+ }
320
+ console.info(await readInstallationGuide())
321
+ }
322
+
308
323
  const runPlan = (argv: readonly string[]): void => {
309
324
  if (argv.length > 0) {
310
325
  throw new Error(`Unexpected Zerops plan arguments: ${argv.map(describeArgument).join(' ')}`)
@@ -368,9 +383,13 @@ const runPlatformUpgrade = async (argv: readonly string[]): Promise<void> => {
368
383
 
369
384
  export const installationCli: InstallationCli = {
370
385
  provider: 'zerops',
371
- commands: ['install', 'init', 'plan', 'deploy', 'admin', 'upgrade'],
386
+ commands: ['install', 'init', 'plan', 'deploy', 'admin', 'upgrade', 'guide'],
372
387
  usage: USAGE,
373
388
  run: async (command: InstallationCommand, argv: readonly string[]) => {
389
+ if (command === 'guide') {
390
+ await runGuide(argv)
391
+ return
392
+ }
374
393
  if (command === 'install') {
375
394
  await runPlatformInstall(argv)
376
395
  return