@harperfast/template-vue-ssr-studio 1.13.0 → 1.13.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/.agents/skills/harper-best-practices/AGENTS.md +553 -141
- package/.agents/skills/harper-best-practices/SKILL.md +2 -1
- package/.agents/skills/harper-best-practices/rules/creating-a-fabric-account-and-cluster.md +7 -4
- package/.agents/skills/harper-best-practices/rules/creating-harper-apps.md +71 -4
- package/.agents/skills/harper-best-practices/rules/deploying-from-ci.md +358 -0
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +135 -91
- package/.agents/skills/harper-best-practices/rules/load-env.md +37 -49
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +184 -8
- package/agent/skills/harper-best-practices/AGENTS.md +553 -141
- package/agent/skills/harper-best-practices/SKILL.md +2 -1
- package/agent/skills/harper-best-practices/rules/creating-a-fabric-account-and-cluster.md +7 -4
- package/agent/skills/harper-best-practices/rules/creating-harper-apps.md +71 -4
- package/agent/skills/harper-best-practices/rules/deploying-from-ci.md +358 -0
- package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +135 -91
- package/agent/skills/harper-best-practices/rules/load-env.md +37 -49
- package/agent/skills/harper-best-practices/rules.manifest.yaml +184 -8
- package/package.json +1 -1
- package/skills-lock.json +1 -1
|
@@ -2320,24 +2320,25 @@ await fetch('http://localhost:9926/JokeCache/1', {
|
|
|
2320
2320
|
|
|
2321
2321
|
### 4.1 Deploying to Harper Fabric
|
|
2322
2322
|
|
|
2323
|
-
Instructions for the agent to follow when deploying a Harper application to a remote Harper
|
|
2323
|
+
Instructions for the agent to follow when deploying a Harper application to a Harper Fabric cluster, or any remote Harper instance, with the `harper` CLI, and when checking, staging, or going back to a release.
|
|
2324
2324
|
|
|
2325
2325
|
#### When to Use
|
|
2326
2326
|
|
|
2327
|
-
Apply this rule when deploying
|
|
2328
|
-
|
|
2329
|
-
See [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md) to set up a cluster before deploying.
|
|
2327
|
+
Apply this rule when deploying to a remote cluster, choosing how it restarts, reading a deploy's `certification`, staging a release to make live later, or rolling back to a previous release. To deploy from CI with no stored credential, see [deploying-from-ci.md](deploying-from-ci.md). To set up the cluster first, see [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md).
|
|
2330
2328
|
|
|
2331
2329
|
#### How It Works
|
|
2332
2330
|
|
|
2333
|
-
1. **
|
|
2331
|
+
1. **Log in to the cluster once**: Pass the cluster's **Application URL**, from its **Config → Overview** page, to `harper login`. The CLI stores the token, so later commands don't repeat credentials. If the current directory already has a `.env` file that sets no target, it also appends `HARPER_CLI_TARGET` to it.
|
|
2334
2332
|
|
|
2335
2333
|
```bash
|
|
2336
2334
|
harper login <Application URL>
|
|
2337
2335
|
# Provide cluster username and password when prompted
|
|
2338
2336
|
```
|
|
2339
2337
|
|
|
2340
|
-
|
|
2338
|
+
- For CI/CD on GitHub Actions, use workload identity (OIDC) instead, so the pipeline stores no Harper credential. From v5.4.0, `harper deploy setup=true provider=github-actions` sets it up in one command: see [deploying-from-ci.md](deploying-from-ci.md). Elsewhere, give the pipeline a refresh token from `harper login --for-ci` rather than a password.
|
|
2339
|
+
- For a one-off command, `auth_username=` and `auth_password=` also work, and take precedence over environment variables and saved login tokens. Don't use them in production.
|
|
2340
|
+
|
|
2341
|
+
2. **Deploy with a restart**: Run `harper deploy`, with `restart=true` or `restart=rolling` so the release is certified before it serves (step 3).
|
|
2341
2342
|
|
|
2342
2343
|
```bash
|
|
2343
2344
|
harper deploy \
|
|
@@ -2348,141 +2349,494 @@ See [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-clu
|
|
|
2348
2349
|
replicated=true
|
|
2349
2350
|
```
|
|
2350
2351
|
|
|
2351
|
-
|
|
2352
|
+
- On Harper Pro and Fabric, a deploy goes to every node in the cluster unless you pass `replicated=false`. Harper core on its own does not replicate.
|
|
2353
|
+
- `project` defaults to the current directory's name for a directory deploy, or is derived from the package for a package deploy.
|
|
2354
|
+
- A bare `target` host defaults to `https://<host>:9925`.
|
|
2355
|
+
- Pass `json=true` to print the result as JSON instead of YAML.
|
|
2356
|
+
- Every result carries a `deployment_id`, staged or not. It names this release when you go back to it later (step 4).
|
|
2357
|
+
|
|
2358
|
+
3. **Choose the restart, and read `certification`**: `restart=true` restarts Harper after deploying. On a cluster, use `restart=rolling`, the staggered, zero-downtime restart: the command exits `0` once the node you called has taken the release and started the rolling restart's job, and returns its `restartJobId`.
|
|
2352
2359
|
|
|
2353
|
-
|
|
2354
|
-
| ------------------------------------------------------------ | ---------------------------------------------- |
|
|
2355
|
-
| _(omit)_ | Package and deploy the current local directory |
|
|
2356
|
-
| `package="@harperdb/status-check"` | npm package |
|
|
2357
|
-
| `package="HarperFast/status-check"` | GitHub shorthand |
|
|
2358
|
-
| `package="https://github.com/HarperFast/status-check"` | GitHub URL |
|
|
2359
|
-
| `package="git+ssh://git@github.com:HarperDB/secret-app.git"` | Private repo (SSH) |
|
|
2360
|
-
| `package="https://example.com/application.tar.gz"` | Tarball |
|
|
2360
|
+
Both certify the release in a canary worker before rolling it out. The canary replaces worker 0 and loads every component, but is held out of traffic until it reports whether the deployed component loaded. Until then, the workers already running keep serving the previous release. A deploy that succeeds, and is not staged, reports the result in `certification`:
|
|
2361
2361
|
|
|
2362
|
-
|
|
2362
|
+
| `certification` | Meaning |
|
|
2363
|
+
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2364
|
+
| `certified` | The canary loaded the release, and the rollout went on. |
|
|
2365
|
+
| `uncertified` | The rollout went on unchecked: the component ran nothing as it loaded (`loadComponent: dev-only`, an absent `if-installed` component, or safe mode), or no worker was replaced. |
|
|
2366
|
+
| `unavailable` | The deploy restarted unchecked: no worker on this node loads the component, its `isolated` setting changed, it was already live, or it was deployed from a local directory (`package: file:<dir>`). |
|
|
2367
|
+
| `not-requested` | The deploy did not restart. |
|
|
2368
|
+
- **A rejected release fails the deploy.** If the canary throws while loading the component, exits, or doesn't report within a minute, the deploy fails with `400`. The release it replaced is made live again, and nothing is replicated to other nodes. The error's `certification` is an object whose `status` is `rejected`, or `interrupted` for a restart that stopped before the canary decided.
|
|
2369
|
+
- **A release with nothing to go back to fails closed.** A rejected first deploy, or one whose previous release was not kept, stays on disk, but no worker on that node loads it, even after Harper restarts. `get_status` reports the component as failed. Deploy a fix, or once the cause is fixed, activate the same `deployment_id` again with `restart`.
|
|
2370
|
+
- **A rolling deploy exits before every node has taken the release.** The other nodes stage it, and the job activates it on each in turn, each with a canary of its own. A node that rejects it keeps the previous release, or fails closed if it had none. Nothing is rolled back on the others, and the job fails naming each node that did not activate it. The deployment record reports `success` while the job runs and after it fails, so poll the job instead: `harper get_job id=<restartJobId>` ends `COMPLETE`, or `ERROR` with a `message` listing each node's outcome under `activated`.
|
|
2371
|
+
- **Poll the node that received the deploy.** A job is recorded only on the node that ran it, so any other node answers `get_job` with an empty array. A role with an `operations` allowlist, or a token with an `operations` scope, must list `get_job` to poll it.
|
|
2372
|
+
- Another deploy or `drop_component` of the component on that node is refused with `409` until the rollout has finished.
|
|
2373
|
+
- A node running a version before v5.4.0 activates the release without a canary.
|
|
2363
2374
|
|
|
2375
|
+
4. **Go back to a previous release by its `deployment_id`**: When a deploy replaces the live release, Harper keeps the replaced one under the `deployment_id` that deployed it. Activating that id puts back the exact installed release with no fetch, rebuild, or reinstall, where deploying an older commit installs it again from source. Find the id with `list_deployments`, then deploy it. On a cluster, restart with `rolling` and poll its job (step 3):
|
|
2376
|
+
|
|
2377
|
+
```bash
|
|
2378
|
+
harper list_deployments project=my-app status=success
|
|
2379
|
+
harper deploy project=my-app deployment_id=<id> restart=rolling
|
|
2364
2380
|
```
|
|
2365
|
-
|
|
2381
|
+
|
|
2382
|
+
```json
|
|
2383
|
+
{
|
|
2384
|
+
"operation": "deploy_component",
|
|
2385
|
+
"project": "my-app",
|
|
2386
|
+
"deployment_id": "<id of the deployment you want back>",
|
|
2387
|
+
"restart": "rolling"
|
|
2388
|
+
}
|
|
2366
2389
|
```
|
|
2367
2390
|
|
|
2368
|
-
|
|
2391
|
+
- `list_deployments` returns records newest first. Filter by `project` and `status`; omit `limit` to return every matching record. A deploy in progress reads `pending`, or `loading`, until its `completed_at` is set.
|
|
2392
|
+
- The newest `success` is not necessarily the release every node is serving: a rolling deploy's record says `success` before the other nodes have taken the release, and keeps saying it if one rejects it.
|
|
2393
|
+
- Redeploy an older commit by `ref` (step 7) only when the release is no longer kept.
|
|
2394
|
+
- The activation restores the root config entry that deployment published. The release it replaces is kept in turn, so you can go forward again the same way.
|
|
2395
|
+
- Kept releases and staged builds count against `deployment.stagingRetention.maxCount` (default `5`). Each kept release is a full installed copy of the component, `node_modules` included, so budget disk for it. `0` keeps no replaced release.
|
|
2396
|
+
- A release made live before v5.3.0, or deployed from a `file:` directory, is not kept.
|
|
2397
|
+
- Each node answers for itself. A node that never had the release, or no longer keeps it, answers `404`, which `deploy_component` reports as a failed peer. `409` means the build is there but can't be activated.
|
|
2369
2398
|
|
|
2370
|
-
|
|
2371
|
-
|
|
2399
|
+
5. **Stage a release to make live later**: `activate=false` (`activate: false` in the operation) builds and installs the release on each node without making it live, so the slow, fallible part happens when you choose and the cut-over is quick. The result's `deployment_id` names the staged build:
|
|
2400
|
+
|
|
2401
|
+
```json
|
|
2402
|
+
{
|
|
2403
|
+
"operation": "deploy_component",
|
|
2404
|
+
"project": "my-app",
|
|
2405
|
+
"package": "npm:@my-org/my-app@1.2.3",
|
|
2406
|
+
"activate": false
|
|
2407
|
+
}
|
|
2372
2408
|
```
|
|
2373
2409
|
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2410
|
+
```json
|
|
2411
|
+
{
|
|
2412
|
+
"deployment_id": "a3f8c2d1-...",
|
|
2413
|
+
"message": "Staged: my-app. Deploy it with deploy_component deployment_id=a3f8c2d1-..."
|
|
2414
|
+
}
|
|
2415
|
+
```
|
|
2416
|
+
|
|
2417
|
+
Make it live by deploying that `deployment_id`, as in step 4. Nothing changes until then, and `get_deployment` reports it as `staged`.
|
|
2418
|
+
- Pass `restart` on the activation, not the stage: `restart` is rejected alongside `activate: false`.
|
|
2419
|
+
- The activation takes no `package`, `payload`, `credentials`, install options, or `urlPath`/`host`. They are rejected, because the staged build already decided them.
|
|
2420
|
+
- Activating the release that is already live succeeds without a swap, so if an activation fails partway (a peer was unreachable, or the root config entry wasn't written), retry the same `deployment_id`. A node that already switched answers success, one still holding the build switches now, and one that holds neither answers `404`; the retry also finishes what the failed attempt left half done. With `restart`, a node where the release is already live still restarts every worker, isolated applications' included. Without it, the node marks the component as needing a restart.
|
|
2421
|
+
- A staged build outlives later deploys: stage v2, deploy v3, and activating v2's id returns the component to v2.
|
|
2422
|
+
- `file:` directory sources cannot be staged.
|
|
2423
|
+
- Upgrade every node before staging. A node before v5.3.0 doesn't recognize `activate: false`, and serves the release immediately. A peer that doesn't confirm staging fails the operation with the node names.
|
|
2424
|
+
|
|
2425
|
+
6. **Choose a package source**: The `package` field accepts any valid npm dependency value:
|
|
2377
2426
|
|
|
2378
|
-
|
|
2427
|
+
| Source | `package` value |
|
|
2428
|
+
| ----------------------- | --------------------------------------------------------------------------------------------- |
|
|
2429
|
+
| Current local directory | Omit `package` |
|
|
2430
|
+
| npm package | `package="@harperdb/status-check"` |
|
|
2431
|
+
| GitHub (public) | `package="HarperFast/status-check"` or `package="https://github.com/HarperFast/status-check"` |
|
|
2432
|
+
| Private repo (SSH) | `package="git+ssh://git@github.com:HarperDB/secret-app.git"` |
|
|
2433
|
+
| Tarball | `package="https://example.com/application.tar.gz"` |
|
|
2434
|
+
|
|
2435
|
+
For git tags, use the `semver` directive: `HarperFast/application-template#semver:v1.0.0`. For an SSH-based private repo, register the key with `add_ssh_key` first.
|
|
2436
|
+
|
|
2437
|
+
7. **Deploy by reference for a reproducible source**: `by_ref=true` sends a pinned git reference instead of uploading the working directory, and the cluster fetches and builds that exact commit. It resolves the repository's `origin` remote and the current commit, then deploys `package=git+https://github.com/<owner>/<repo>.git#<full commit SHA>`. Pass `ref` to deploy a specific tag or commit instead of `HEAD`; it implies `by_ref`.
|
|
2379
2438
|
|
|
2380
2439
|
```bash
|
|
2440
|
+
harper deploy by_ref=true restart=true replicated=true
|
|
2381
2441
|
harper deploy ref=v1.2.0 restart=true replicated=true
|
|
2382
2442
|
harper deploy ref=9f8c2a1 restart=true replicated=true
|
|
2383
2443
|
```
|
|
2384
2444
|
|
|
2385
|
-
|
|
2445
|
+
- A `ref` is resolved to a full commit SHA before the deploy is sent, from the local checkout or the remote. A full commit SHA is accepted directly. If a `ref` can't be resolved, the deploy stops: run `git fetch` and retry, or pass a full SHA.
|
|
2446
|
+
- Any other `ref` must name something a clone can fetch: `refs/heads/*` or `refs/tags/*`, or a bare branch or tag name. A qualified ref outside those, such as `refs/pull/123/head`, is rejected up front.
|
|
2447
|
+
- Commit and push first: the cluster clones from the remote. `by_ref` warns when the working tree is dirty, and when the commit isn't on any remote branch. The second check is skipped under GitHub Actions.
|
|
2448
|
+
- On a GitHub Actions `pull_request` run, `by_ref` deploys the pull request's head commit. If the event payload isn't readable, the deploy stops; pass the commit explicitly.
|
|
2449
|
+
- The cluster installs and builds from source on each node, so commit your lockfile if the build must be reproducible. If the build can't run on the node, deploy the built output as a payload instead.
|
|
2450
|
+
|
|
2451
|
+
8. **Deploy a private repository by reference**: Pass `credential=true` with `by_ref=true`. The CLI attaches a `credentials` reference, and the cluster resolves the secret in memory at clone time, so no token travels in the operation body or lands on disk.
|
|
2452
|
+
|
|
2453
|
+
```bash
|
|
2454
|
+
harper deploy by_ref=true credential=true restart=true replicated=true
|
|
2455
|
+
```
|
|
2456
|
+
|
|
2457
|
+
Provision that credential once per component and source with `harper deploy setup=true`. (With `provider=github-actions`, it sets up GitHub Actions deploys instead: see [deploying-from-ci.md](deploying-from-ci.md).) It is interactive, and its operations require **super_user**, so run it with an administrative credential, not the CI identity it provisions for. It:
|
|
2458
|
+
1. Fetches the cluster's public key with `get_secrets_public_key`.
|
|
2459
|
+
2. Encrypts the token locally into an `enc:v1:` envelope, so the plaintext never leaves your machine.
|
|
2460
|
+
3. Stores only the ciphertext with `set_secret`, scoped to the component, never in the global `processEnv` tier.
|
|
2461
|
+
4. Grants the component permission to resolve it with `grant_secret`.
|
|
2462
|
+
5. Prints the `credentials` reference for the deploy to use.
|
|
2463
|
+
|
|
2464
|
+
Use a **fine-grained** personal access token with **Contents: Read-only** on that one repository. Avoid the `gh` CLI's session token: it typically carries `repo`, `read:org`, `gist`, and `workflow` scopes across your whole account. The sealed token is durable, and later deploys reuse it.
|
|
2465
|
+
|
|
2466
|
+
#### Examples
|
|
2467
|
+
|
|
2468
|
+
**Deploy after logging in, and wait for a rolling restart:**
|
|
2469
|
+
|
|
2470
|
+
```bash
|
|
2471
|
+
harper login https://my-cluster.harperdbcloud.com
|
|
2472
|
+
harper deploy \
|
|
2473
|
+
project=my-app \
|
|
2474
|
+
package="HarperFast/my-app" \
|
|
2475
|
+
target=https://my-cluster.harperdbcloud.com \
|
|
2476
|
+
restart=rolling \
|
|
2477
|
+
json=true > deploy.json
|
|
2478
|
+
# Poll the node that received the deploy until the job is COMPLETE or ERROR
|
|
2479
|
+
harper get_job id=<restartJobId from deploy.json> json=true
|
|
2480
|
+
```
|
|
2481
|
+
|
|
2482
|
+
**Roll back to the release a deploy replaced, node by node:**
|
|
2483
|
+
|
|
2484
|
+
```bash
|
|
2485
|
+
harper list_deployments project=my-app status=success
|
|
2486
|
+
harper deploy project=my-app deployment_id=<previous deployment_id> restart=rolling json=true
|
|
2487
|
+
# Then poll its restartJobId with get_job, as above
|
|
2488
|
+
```
|
|
2489
|
+
|
|
2490
|
+
**Stage a release now, and make it live in a maintenance window:**
|
|
2491
|
+
|
|
2492
|
+
```bash
|
|
2493
|
+
harper deploy project=my-app package=npm:@my-org/my-app@1.2.3 activate=false
|
|
2494
|
+
# Later, with the deployment_id the stage returned
|
|
2495
|
+
harper deploy project=my-app deployment_id=<staged deployment_id> restart=rolling
|
|
2496
|
+
```
|
|
2497
|
+
|
|
2498
|
+
**Deploy a pull request's head commit by reference in GitHub Actions:**
|
|
2499
|
+
|
|
2500
|
+
```bash
|
|
2501
|
+
harper deploy ref=${{ github.event.pull_request.head.sha }} restart=true replicated=true
|
|
2502
|
+
```
|
|
2503
|
+
|
|
2504
|
+
**Deploy a private repository by reference:**
|
|
2505
|
+
|
|
2506
|
+
```bash
|
|
2507
|
+
# Once, as super_user
|
|
2508
|
+
harper deploy setup=true
|
|
2509
|
+
|
|
2510
|
+
# Every deploy
|
|
2511
|
+
harper deploy by_ref=true credential=true restart=true replicated=true
|
|
2512
|
+
```
|
|
2386
2513
|
|
|
2387
|
-
|
|
2514
|
+
### 4.2 Deploying a Harper Application from GitHub Actions with OIDC
|
|
2388
2515
|
|
|
2389
|
-
|
|
2516
|
+
Instructions for the agent to configure and execute credential-free Harper deployments from GitHub Actions using OIDC trusted publishing, including one-command setup and complete workflow examples.
|
|
2517
|
+
|
|
2518
|
+
#### When to Use
|
|
2519
|
+
|
|
2520
|
+
Apply this rule when asked to deploy from CI, to set up or fix a deploy workflow (including the `.github/workflows/deploy.yaml` that create-harper projects ship), or to move a pipeline off a stored Harper credential such as `HARPER_CLI_REFRESH_TOKEN`. Use it when setting up OIDC trust for the first time, writing or debugging a deploy workflow, or revoking/rotating pipeline access.
|
|
2521
|
+
|
|
2522
|
+
#### How It Works
|
|
2523
|
+
|
|
2524
|
+
##### One-Command Setup (Branch-Based Deploys)
|
|
2525
|
+
|
|
2526
|
+
1. **Run setup once** from the project checkout, signed in as a `super_user`:
|
|
2390
2527
|
|
|
2391
2528
|
```bash
|
|
2392
|
-
harper
|
|
2529
|
+
harper login https://my-cluster.example.com:9925
|
|
2530
|
+
harper deploy setup=true provider=github-actions project=my-app
|
|
2393
2531
|
```
|
|
2394
2532
|
|
|
2395
|
-
|
|
2533
|
+
If your workflow file is not `.github/workflows/deploy.yaml`, pass `workflow=` explicitly — otherwise the policy pins `deploy.yaml` and runs of your workflow cannot match it:
|
|
2396
2534
|
|
|
2397
2535
|
```bash
|
|
2398
|
-
harper deploy setup=true
|
|
2536
|
+
harper deploy setup=true provider=github-actions project=my-app workflow=.github/workflows/deploy.yml
|
|
2399
2537
|
```
|
|
2400
2538
|
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2539
|
+
2. **Understand what setup does** — in order:
|
|
2540
|
+
- Resolves the GitHub repository's numeric id and canonical name from the `origin` remote (or `repo=<owner>/<name>`). Pass `repository_id=<id>` when the API is unavailable (e.g., private repo without `gh`).
|
|
2541
|
+
- **The workflow is checked before anything is written.** It must be able to run on `main` (`branch=`) and run a job in the `production` environment (`environment=`) with `permissions: id-token: write`; one that never could stops setup. What it can't decide is reported as not verified.
|
|
2542
|
+
- Creates a role and a user named `<project>-ci-deploy` with permissions limited to `deploy_component`, `get_job`, and `registration_info`, and a trust policy named `github-actions-<project>`.
|
|
2543
|
+
- Reads the policy back and compares it claim by claim with what it intended to write.
|
|
2544
|
+
- Sets the repository's `HARPER_CLI_TARGET` Actions variable via `gh` if one does not already exist.
|
|
2407
2545
|
|
|
2408
|
-
|
|
2546
|
+
3. **Handle idempotency**: setup only creates — it never updates. A role, user, or policy that already exists but differs stops setup and names what differs. To replace a policy:
|
|
2409
2547
|
|
|
2410
|
-
|
|
2548
|
+
```bash
|
|
2549
|
+
harper drop_oidc_trust id=github-actions-<project>
|
|
2550
|
+
harper deploy setup=true provider=github-actions project=my-app
|
|
2551
|
+
```
|
|
2552
|
+
|
|
2553
|
+
##### Manual Setup (Tag-Based or Custom Deploys)
|
|
2554
|
+
|
|
2555
|
+
Use this path when deploying on version tags, because a branch-pinned policy cannot match a tag ref.
|
|
2556
|
+
|
|
2557
|
+
1. **Create a deploy-only role and user**:
|
|
2411
2558
|
|
|
2412
2559
|
```bash
|
|
2413
|
-
harper
|
|
2560
|
+
harper add_role role=ci_deploy permission='{"operations":["deploy_component","get_job","get_deployment","registration_info"]}'
|
|
2561
|
+
harper add_user username=ci-deploy role=ci_deploy active=true password="$(openssl rand -base64 32)"
|
|
2414
2562
|
```
|
|
2415
2563
|
|
|
2416
|
-
|
|
2564
|
+
- `deploy_component` — the deploy itself.
|
|
2565
|
+
- `get_job` — required for the wait step; an `operations` allowlist refuses anything not listed, so without it the wait step fails with `403`.
|
|
2566
|
+
- `get_deployment` — reads the deployment record.
|
|
2567
|
+
- `registration_info` — version check `harper deploy` makes before uploading.
|
|
2568
|
+
- `restart_service` is not needed: Harper starts the rolling restart's job itself. The password is required to create the user but a GitHub Actions pipeline never uses it, so this command discards it.
|
|
2569
|
+
|
|
2570
|
+
2. **Add a trust policy** pinned to `repository_id`, `workflow_path`, and `environment` (use this shape when the tag is unknown at policy-write time):
|
|
2417
2571
|
|
|
2418
2572
|
```bash
|
|
2419
|
-
harper
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
restart=true \
|
|
2426
|
-
replicated=true
|
|
2573
|
+
harper add_oidc_trust \
|
|
2574
|
+
id=my-app-release \
|
|
2575
|
+
issuer=https://token.actions.githubusercontent.com \
|
|
2576
|
+
audience=https://my-cluster.example.com:9925/ \
|
|
2577
|
+
user=ci-deploy \
|
|
2578
|
+
claims='{"repository_id":"67890","workflow_path":"my-org/my-app/.github/workflows/deploy.yml","environment":"production"}'
|
|
2427
2579
|
```
|
|
2428
2580
|
|
|
2429
|
-
|
|
2581
|
+
Key claim rules:
|
|
2582
|
+
- `repository_id` is immutable — survives renames. Look it up with `gh api repos/my-org/my-app --jq .id`.
|
|
2583
|
+
- `workflow_path` pins the workflow file. Harper derives it from the token's `workflow_ref` by removing the `@<ref>` suffix, so it is `<owner>/<repo>/<path>`.
|
|
2584
|
+
- `environment` is the ref gate. Without it, anyone who can push a branch can mint a token.
|
|
2585
|
+
- `audience` must be the exact target URL with its port and a **trailing slash**.
|
|
2586
|
+
- A workflow that deploys on a push to `main` can pin `workflow_ref` (`my-org/my-app/.github/workflows/deploy.yml@refs/heads/main`) instead, which gates the ref by itself.
|
|
2587
|
+
|
|
2588
|
+
3. **Configure the GitHub environment**: in **Settings → Environments → production**, set **Deployment branches and tags** to the `v*` tag pattern. Add required reviewers if a release needs approval.
|
|
2589
|
+
|
|
2590
|
+
4. **Store the target as a repository variable** (not a secret — it is not sensitive):
|
|
2430
2591
|
|
|
2431
2592
|
```bash
|
|
2432
|
-
|
|
2593
|
+
gh variable set HARPER_CLI_TARGET --repo my-org/my-app --body https://my-cluster.example.com:9925/
|
|
2433
2594
|
```
|
|
2434
2595
|
|
|
2596
|
+
##### How OIDC Authentication Works in the Workflow
|
|
2597
|
+
|
|
2598
|
+
Once a trust policy exists, every `harper` command in the deploy job authenticates through it automatically when all four conditions hold:
|
|
2599
|
+
|
|
2600
|
+
1. **The job may request an identity token.** On GitHub Actions that is `permissions: id-token: write` on the job.
|
|
2601
|
+
2. **`HARPER_CLI_TARGET` is the policy's `audience`.** The CLI requests its token for the target it connects to, so the target must be the URL the policy names, with its port and trailing slash. Use `vars.HARPER_CLI_TARGET` — it is not sensitive, so a repository variable rather than a secret.
|
|
2602
|
+
3. **The run matches the policy's claims**: its repository, its workflow file and branch, and its environment.
|
|
2603
|
+
4. **Nothing else is configured to authenticate.** `auth_username=`/`auth_password=`, credentials in the target URL, `HARPER_CLI_USERNAME`/`HARPER_CLI_PASSWORD`, `HARPER_CLI_REFRESH_TOKEN` or `HARPER_CLI_OPERATION_TOKEN`, and a saved `harper login` all outrank the exchange.
|
|
2604
|
+
|
|
2605
|
+
What each command may then do is up to the role of the user the policy names: a role that lists only `deploy_component`, `get_job` and `registration_info`, like the one setup creates, refuses anything else with a `403`.
|
|
2606
|
+
|
|
2607
|
+
The exchange runs only when GitHub sets `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`, which it does for a job that declares `permissions: id-token: write`. Without them, the CLI falls through to its other credential sources rather than reporting a failure.
|
|
2608
|
+
|
|
2609
|
+
Nothing in the workflow names the policy. The CLI prints to stderr which policy authenticated it and as whom:
|
|
2610
|
+
|
|
2611
|
+
```
|
|
2612
|
+
Requesting a GitHub Actions identity token for https://my-instance.harperdb.io:9925/...
|
|
2613
|
+
Authenticated as 'ci-deploy' via OIDC trust policy 'my-app-prod'.
|
|
2614
|
+
```
|
|
2615
|
+
|
|
2616
|
+
Diagnose a rejected exchange with `list_oidc_trust` (check for `invalid_reason` on each entry) and the instance's `oidc-trust` log. The server deliberately does not report which check failed.
|
|
2617
|
+
|
|
2618
|
+
##### Policy Specificity for GitHub Actions
|
|
2619
|
+
|
|
2620
|
+
For `https://token.actions.githubusercontent.com`, a policy must satisfy all three of these:
|
|
2621
|
+
|
|
2622
|
+
| Requirement | Satisfied by one of | Left open otherwise |
|
|
2623
|
+
| ---------------------- | ------------------------------------------------------------------------ | ----------------------------------------------- |
|
|
2624
|
+
| **Pin the repository** | `repository_id`, `repository` | Any repository |
|
|
2625
|
+
| **Pin the workflow** | `workflow_ref`, `workflow_path`, `job_workflow_ref`, `job_workflow_path` | Any workflow in that repository |
|
|
2626
|
+
| **Gate the ref** | `workflow_ref`, `ref`, `environment` | Any branch that can be pushed to the repository |
|
|
2627
|
+
|
|
2628
|
+
Additional constraints:
|
|
2629
|
+
|
|
2630
|
+
- `repository_id` is preferred over `repository` — it is immutable and survives renames.
|
|
2631
|
+
- A tag-triggered release cannot pin `workflow_ref` since the tag is unknown when the policy is written. Pin `workflow_path` and gate on `environment`.
|
|
2632
|
+
- `ref_type: tag` is not accepted as a ref gate, `sub` is not accepted as a pin, and `job_workflow_ref` pins the workflow without gating the ref. `pull_request_target` runs are denied unless the policy constrains `event_name`.
|
|
2633
|
+
|
|
2634
|
+
##### Revoking Access
|
|
2635
|
+
|
|
2636
|
+
- **Stop future runs**: `harper drop_oidc_trust id=github-actions-<project>` (does not revoke already-issued tokens, which are valid up to one hour).
|
|
2637
|
+
- **Stop an already-issued token**: `harper alter_user username=<project>-ci-deploy active=false`.
|
|
2638
|
+
- **No CI secret to rotate** — there is none.
|
|
2639
|
+
|
|
2435
2640
|
#### Examples
|
|
2436
2641
|
|
|
2437
|
-
|
|
2642
|
+
##### Path A: Deploy a Tagged Release (`v1.2.3`) from the Repository
|
|
2438
2643
|
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2644
|
+
Push a semver tag (`v1.2.3`), and every node clones and installs that commit. `by_ref=true` deploys `git+https://github.com/<owner>/<repo>.git#<sha>`, where the SHA is the commit the tag points to (`GITHUB_SHA`), and every node clones that exact commit even if the tag moves later. For a private repository, add `credential=true` to attach the stored credential (`deploy.my-app.git.github.com`). If the repository needs a build step to be runnable, don't reach for `install_allow_scripts`: use Path B.
|
|
2645
|
+
|
|
2646
|
+
```yaml
|
|
2647
|
+
# .github/workflows/deploy.yml
|
|
2648
|
+
name: Deploy to Harper
|
|
2649
|
+
on:
|
|
2650
|
+
push:
|
|
2651
|
+
tags: ['v*']
|
|
2652
|
+
|
|
2653
|
+
jobs:
|
|
2654
|
+
deploy:
|
|
2655
|
+
runs-on: ubuntu-latest
|
|
2656
|
+
environment: production
|
|
2657
|
+
permissions:
|
|
2658
|
+
contents: read
|
|
2659
|
+
id-token: write
|
|
2660
|
+
env:
|
|
2661
|
+
HARPER_CLI_TARGET: ${{ vars.HARPER_CLI_TARGET }}
|
|
2662
|
+
steps:
|
|
2663
|
+
- uses: actions/checkout@v4
|
|
2664
|
+
with:
|
|
2665
|
+
persist-credentials: false
|
|
2666
|
+
- uses: actions/setup-node@v4
|
|
2667
|
+
with:
|
|
2668
|
+
node-version: 22
|
|
2669
|
+
- run: npm install --global harper@^5.3
|
|
2670
|
+
- name: Deploy the tagged commit
|
|
2671
|
+
run: harper deploy project=my-app by_ref=true restart=rolling json=true > deploy.json
|
|
2672
|
+
- name: Wait for every node to take the release
|
|
2673
|
+
run: |
|
|
2674
|
+
JOB_ID=$(jq -r '.restartJobId // empty' deploy.json)
|
|
2675
|
+
[ -n "$JOB_ID" ] || { echo "The rolling deploy returned no restartJobId"; exit 1; }
|
|
2676
|
+
LAST="could not be read"
|
|
2677
|
+
for attempt in $(seq 60); do
|
|
2678
|
+
sleep 10
|
|
2679
|
+
harper get_job id="$JOB_ID" json=true > job.json || continue
|
|
2680
|
+
case $(jq -r '.[0].status // "MISSING"' job.json) in
|
|
2681
|
+
COMPLETE) exit 0 ;;
|
|
2682
|
+
ERROR) jq -r '.[0].message' job.json; exit 1 ;;
|
|
2683
|
+
MISSING) LAST="was not on the node that answered; its outcome is unknown" ;;
|
|
2684
|
+
*) LAST="was still running" ;;
|
|
2685
|
+
esac
|
|
2686
|
+
done
|
|
2687
|
+
echo "After 60 polls, job $JOB_ID $LAST"
|
|
2688
|
+
exit 1
|
|
2689
|
+
```
|
|
2690
|
+
|
|
2691
|
+
Key details:
|
|
2692
|
+
|
|
2693
|
+
- **No secrets.** `HARPER_CLI_TARGET` is the only thing the workflow is given. `id-token: write` lets the CLI request an identity token, and `environment: production` puts the `environment` claim the trust policy requires into it.
|
|
2694
|
+
- **`persist-credentials: false`** keeps the job's `GITHUB_TOKEN` out of `.git/config`.
|
|
2695
|
+
- **`harper@^5.3`** pins the CLI's major version. 5.3 is the first CLI that authenticates with the identity token.
|
|
2696
|
+
- **`replicated` isn't needed.** On Harper Fabric and Harper Pro, a deploy goes to every node unless you pass `replicated=false`. Each node fetches and installs the package itself.
|
|
2697
|
+
- **`restart=rolling`** restarts nodes one at a time. Use `restart=true` for a single-node or dev instance.
|
|
2698
|
+
- **`json=true`** prints the deploy's result as JSON on stdout; progress and authentication messages go to stderr.
|
|
2699
|
+
- **The wait step** polls `restartJobId` via `get_job` until `COMPLETE` or `ERROR`, checking `.[0].status`. It retries a poll that fails (such as one that reaches a node while it restarts) and gives up after 60 polls (~10 minutes).
|
|
2700
|
+
- **`certification`**: on v5.4.0 and later, `restart=rolling` loads the release in a canary worker on each node before that node serves it, so a release that fails to load is rejected. The `certification` field in the deploy result reflects this. With `restart=rolling` on v5.4.0 and later, the deploy can report `success` before the rolling job has visited all nodes — wait on the job, not the record.
|
|
2701
|
+
- **The job lives on one node.** Through a **load-balanced** cluster URL, polls must eventually reach that node. Routing that always sends polls to a different node than the deploy causes the wait step to fail saying the job was not on the node that answered.
|
|
2702
|
+
|
|
2703
|
+
##### Path B: Build in CI, Publish to GitHub Packages, Deploy the Artifact
|
|
2704
|
+
|
|
2705
|
+
Give `package.json` a scoped name and `"publishConfig": { "registry": "https://npm.pkg.github.com" }`:
|
|
2706
|
+
|
|
2707
|
+
```json
|
|
2708
|
+
{
|
|
2709
|
+
"name": "@my-org/my-app",
|
|
2710
|
+
"version": "1.2.3",
|
|
2711
|
+
"publishConfig": { "registry": "https://npm.pkg.github.com" },
|
|
2712
|
+
"files": ["config.yaml", "schema.graphql", "dist/"]
|
|
2713
|
+
}
|
|
2446
2714
|
```
|
|
2447
2715
|
|
|
2448
|
-
|
|
2716
|
+
The deploy installs the version the tag names, and `npm publish` publishes the version in `package.json`, so they must agree: `npm version 1.2.3` sets one and creates the other as `v1.2.3`.
|
|
2717
|
+
|
|
2718
|
+
```yaml
|
|
2719
|
+
# .github/workflows/deploy.yml
|
|
2720
|
+
name: Build, publish, and deploy to Harper
|
|
2721
|
+
on:
|
|
2722
|
+
push:
|
|
2723
|
+
tags: ['v*']
|
|
2724
|
+
|
|
2725
|
+
jobs:
|
|
2726
|
+
publish:
|
|
2727
|
+
runs-on: ubuntu-latest
|
|
2728
|
+
permissions:
|
|
2729
|
+
contents: read
|
|
2730
|
+
packages: write
|
|
2731
|
+
steps:
|
|
2732
|
+
- uses: actions/checkout@v4
|
|
2733
|
+
with:
|
|
2734
|
+
persist-credentials: false
|
|
2735
|
+
- uses: actions/setup-node@v4
|
|
2736
|
+
with:
|
|
2737
|
+
node-version: 22
|
|
2738
|
+
registry-url: https://npm.pkg.github.com
|
|
2739
|
+
scope: '@my-org'
|
|
2740
|
+
- run: npm ci
|
|
2741
|
+
env:
|
|
2742
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
2743
|
+
- run: npm run build
|
|
2744
|
+
- run: npm test
|
|
2745
|
+
- name: Publish to GitHub Packages
|
|
2746
|
+
run: npm publish
|
|
2747
|
+
env:
|
|
2748
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
2749
|
+
|
|
2750
|
+
deploy:
|
|
2751
|
+
needs: publish
|
|
2752
|
+
runs-on: ubuntu-latest
|
|
2753
|
+
environment: production
|
|
2754
|
+
permissions:
|
|
2755
|
+
id-token: write
|
|
2756
|
+
env:
|
|
2757
|
+
HARPER_CLI_TARGET: ${{ vars.HARPER_CLI_TARGET }}
|
|
2758
|
+
steps:
|
|
2759
|
+
- uses: actions/setup-node@v4
|
|
2760
|
+
with:
|
|
2761
|
+
node-version: 22
|
|
2762
|
+
- run: npm install --global harper@^5.3
|
|
2763
|
+
- name: Deploy the published version
|
|
2764
|
+
run: |
|
|
2765
|
+
harper deploy project=my-app package="@my-org/my-app@${GITHUB_REF_NAME#v}" \
|
|
2766
|
+
credentials='[{"registry":"https://npm.pkg.github.com","scope":"@my-org","secret":"deploy.my-app.npm.pkg.github.com"}]' \
|
|
2767
|
+
restart=rolling json=true > deploy.json
|
|
2768
|
+
- name: Wait for every node to take the release
|
|
2769
|
+
run: |
|
|
2770
|
+
JOB_ID=$(jq -r '.restartJobId // empty' deploy.json)
|
|
2771
|
+
[ -n "$JOB_ID" ] || { echo "The rolling deploy returned no restartJobId"; exit 1; }
|
|
2772
|
+
LAST="could not be read"
|
|
2773
|
+
for attempt in $(seq 60); do
|
|
2774
|
+
sleep 10
|
|
2775
|
+
harper get_job id="$JOB_ID" json=true > job.json || continue
|
|
2776
|
+
case $(jq -r '.[0].status // "MISSING"' job.json) in
|
|
2777
|
+
COMPLETE) exit 0 ;;
|
|
2778
|
+
ERROR) jq -r '.[0].message' job.json; exit 1 ;;
|
|
2779
|
+
MISSING) LAST="was not on the node that answered; its outcome is unknown" ;;
|
|
2780
|
+
*) LAST="was still running" ;;
|
|
2781
|
+
esac
|
|
2782
|
+
done
|
|
2783
|
+
echo "After 60 polls, job $JOB_ID $LAST"
|
|
2784
|
+
exit 1
|
|
2785
|
+
```
|
|
2786
|
+
|
|
2787
|
+
Two jobs keep the identity token isolated: `publish` installs, builds, tests and publishes with `contents: read` and `packages: write` only — it cannot request an identity token. `deploy` runs once `publish` finishes (`needs: publish`), holds `environment: production` and `id-token: write`, and installs only the Harper CLI before deploying.
|
|
2788
|
+
|
|
2789
|
+
Credential roles:
|
|
2790
|
+
|
|
2791
|
+
- **Publishing** and installing private `@my-org` dependencies use the workflow's ephemeral `GITHUB_TOKEN` (`packages: write` includes read). Both happen entirely inside `publish`.
|
|
2792
|
+
- **Installing on Harper nodes** uses the durable `read:packages` token stored as `deploy.my-app.npm.pkg.github.com` — because installs can happen long after this workflow ends.
|
|
2793
|
+
|
|
2794
|
+
For `my-app`, the credential names are `deploy.my-app.git.github.com` for the repository and `deploy.my-app.npm.pkg.github.com` for GitHub Packages. Store a durable credential once from your machine:
|
|
2449
2795
|
|
|
2450
2796
|
```bash
|
|
2451
|
-
harper deploy
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
target=https://my-cluster.harperdbcloud.com \
|
|
2455
|
-
restart=true \
|
|
2456
|
-
replicated=true
|
|
2797
|
+
harper deploy setup=true provider=github project=my-app
|
|
2798
|
+
# or for GitHub Packages:
|
|
2799
|
+
harper deploy setup=true provider=npm project=my-app registry=https://npm.pkg.github.com scope=@my-org
|
|
2457
2800
|
```
|
|
2458
2801
|
|
|
2459
|
-
|
|
2802
|
+
For a private repository, use a GitHub fine-grained personal access token with **Contents: read-only** scoped to the application repository. For GitHub Packages, use a token with the `read:packages` scope. Do not use the workflow's ephemeral `GITHUB_TOKEN` for the durable credential — it expires when the job ends.
|
|
2803
|
+
|
|
2804
|
+
##### Check the Deployment Record
|
|
2460
2805
|
|
|
2461
2806
|
```bash
|
|
2462
|
-
|
|
2463
|
-
|
|
2807
|
+
harper get_deployment deployment_id="$(jq -r .deployment_id deploy.json)" json=true
|
|
2808
|
+
```
|
|
2464
2809
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2810
|
+
Only the node you called writes that record, so with `restart=rolling` on v5.4.0 and later it can report `success` before the job has visited the other nodes, and keeps reporting it if one of them rejects the release. Wait on the job, not the record, to confirm all nodes took the release. `list_deployments` gives the history of what was deployed and when.
|
|
2811
|
+
|
|
2812
|
+
##### List and Inspect Trust Policies
|
|
2813
|
+
|
|
2814
|
+
```bash
|
|
2815
|
+
harper list_oidc_trust
|
|
2467
2816
|
```
|
|
2468
2817
|
|
|
2469
|
-
|
|
2818
|
+
Returns `{ "policies": [ ... ] }`. Each entry includes `id`, `issuer`, `audience`, `claims`, `user`, `enabled`, and optionally `invalid_reason`. Check `invalid_reason` first when diagnosing a rejected exchange — it is set when the policy is malformed or its user was deleted or deactivated.
|
|
2819
|
+
|
|
2820
|
+
##### Drop a Trust Policy
|
|
2470
2821
|
|
|
2471
2822
|
```bash
|
|
2472
|
-
harper
|
|
2823
|
+
harper drop_oidc_trust id=github-actions-<project>
|
|
2473
2824
|
```
|
|
2474
2825
|
|
|
2475
2826
|
#### Notes
|
|
2476
2827
|
|
|
2477
|
-
-
|
|
2478
|
-
- `harper deploy
|
|
2479
|
-
-
|
|
2480
|
-
-
|
|
2481
|
-
-
|
|
2482
|
-
- The
|
|
2483
|
-
-
|
|
2828
|
+
- `HARPER_CLI_REFRESH_TOKEN` and any other stored credential outrank the OIDC exchange. Remove them from the workflow if switching to OIDC.
|
|
2829
|
+
- **Go back to a previous release** by activating its `deployment_id`, with no rebuild or reinstall: `harper deploy project=my-app deployment_id=<id> restart=rolling`. To deploy a release that is no longer kept, re-run the deploy with its tag or version.
|
|
2830
|
+
- **Rotating the deploy credential**: run `harper deploy setup=true` again with the same name and a new value. The pipeline doesn't change.
|
|
2831
|
+
- **Revoking the pipeline's access**: `harper drop_oidc_trust` stops new runs; `harper alter_user` also stops a token already issued (valid up to one hour).
|
|
2832
|
+
- The `<project>-ci-deploy` user created by setup can deploy any component on the cluster, not only the named project. Access control comes down to who can merge to the branch.
|
|
2833
|
+
- The `audience` in `add_oidc_trust` must be the exact target URL with its port and a trailing slash.
|
|
2834
|
+
- For tag-triggered workflows, `provider=github-actions` setup is not sufficient — it pins a branch. Write the policy manually using `add_oidc_trust` with `workflow_path` and `environment` claims.
|
|
2835
|
+
- `token.actions.githubusercontent.com` is the required `issuer` value for GitHub Actions OIDC policies.
|
|
2836
|
+
- Git-host credentials require git ≥ 2.31 on the Harper nodes and are not supported on Windows nodes. Registry credentials have no such constraints.
|
|
2837
|
+
- See [deploying-to-harper-fabric.md](deploying-to-harper-fabric.md) for cluster-level deployment context.
|
|
2484
2838
|
|
|
2485
|
-
### 4.
|
|
2839
|
+
### 4.3 Creating a Harper Fabric Account and Cluster
|
|
2486
2840
|
|
|
2487
2841
|
Follow these steps to set up your Harper Fabric environment for deployment.
|
|
2488
2842
|
|
|
@@ -2493,7 +2847,7 @@ Follow these steps to set up your Harper Fabric environment for deployment.
|
|
|
2493
2847
|
3. **Create a Cluster**: Create a new cluster. This can be on the free tier, no credit card required.
|
|
2494
2848
|
4. **Set Credentials**: During setup, set the cluster username and password to finish configuring it.
|
|
2495
2849
|
5. **Get Application URL**: Navigate to the **Config** tab and copy the **Application URL**.
|
|
2496
|
-
6. **
|
|
2850
|
+
6. **Connect the CLI**: Run `harper login` with the Application URL to store a token for the cluster, and set the URL as `HARPER_CLI_TARGET` in your `.env` file. Prefer this to putting the cluster username and password in `.env`, and don't store them in GitHub Actions secrets: to deploy from GitHub Actions, set up OIDC trusted publishing instead (see [deploying-from-ci](deploying-from-ci.md)).
|
|
2497
2851
|
7. **Next Steps**: See the [deploying-to-harper-fabric](deploying-to-harper-fabric.md) rule for detailed instructions on deploying your application successfully.
|
|
2498
2852
|
|
|
2499
2853
|
#### Examples
|
|
@@ -2501,12 +2855,15 @@ Follow these steps to set up your Harper Fabric environment for deployment.
|
|
|
2501
2855
|
##### Environment Configuration
|
|
2502
2856
|
|
|
2503
2857
|
```bash
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2858
|
+
harper login YOUR_CLUSTER_URL
|
|
2859
|
+
```
|
|
2860
|
+
|
|
2861
|
+
```bash
|
|
2862
|
+
# .env
|
|
2863
|
+
HARPER_CLI_TARGET='YOUR_CLUSTER_URL'
|
|
2507
2864
|
```
|
|
2508
2865
|
|
|
2509
|
-
### 4.
|
|
2866
|
+
### 4.4 Creating Harper Applications
|
|
2510
2867
|
|
|
2511
2868
|
The fastest way to start a new Harper project is using the `create-harper` CLI tool. This command
|
|
2512
2869
|
initializes a project with a standard folder structure, essential configuration files, and basic
|
|
@@ -2549,12 +2906,79 @@ npm create harper@latest my-app --template default
|
|
|
2549
2906
|
|
|
2550
2907
|
#### Next Steps
|
|
2551
2908
|
|
|
2552
|
-
1. **
|
|
2553
|
-
2. **
|
|
2554
|
-
3. **
|
|
2555
|
-
|
|
2909
|
+
1. **Define Schema**: Modify `schema.graphql` to fit your application's data model.
|
|
2910
|
+
2. **Start Development**: Run `npm run dev` to start the local Harper instance.
|
|
2911
|
+
3. **Connect the CLI to your cluster**: Create a cluster (see
|
|
2912
|
+
[creating-a-fabric-account-and-cluster](creating-a-fabric-account-and-cluster.md)), run `harper login` with its
|
|
2913
|
+
Application URL as a super user, and set that URL as `HARPER_CLI_TARGET` in the project's `.env` file. Don't put a
|
|
2914
|
+
cluster username or password in `.env` or in GitHub secrets.
|
|
2915
|
+
4. **Deploy**: See [Deploying](#deploying).
|
|
2916
|
+
|
|
2917
|
+
#### Deploying
|
|
2918
|
+
|
|
2919
|
+
Every template except the legacy `early-hints` example ships a GitHub Actions workflow,
|
|
2920
|
+
`.github/workflows/deploy.yaml`. It tests every pull request and deploys to the cluster when a change merges to `main`.
|
|
2921
|
+
The deploy job holds no Harper credential: its `harper` commands authenticate with GitHub's OIDC identity token under a
|
|
2922
|
+
trust policy on the cluster. The Next.js templates' workflow also runs `next build` before it uploads.
|
|
2923
|
+
|
|
2924
|
+
1. **Set up CI deploys once**: Push the project to a GitHub repository, then run `npm run deploy:setup-ci` from the
|
|
2925
|
+
project, logged in to the cluster as a super user. It runs `harper deploy setup=true provider=github-actions` for the
|
|
2926
|
+
project, which creates a deploy-only role and user and a trust policy matched to the workflow, and sets the
|
|
2927
|
+
repository's `HARPER_CLI_TARGET` variable when the `gh` CLI is signed in (otherwise it prints the command to run).
|
|
2928
|
+
Running it again changes nothing. See [deploying-from-ci](deploying-from-ci.md) for what it checks and how to
|
|
2929
|
+
customize it.
|
|
2930
|
+
|
|
2931
|
+
The command needs the Harper 5.4.0 CLI or later (`harper version`); an earlier CLI can't run it. Without 5.4.0, set
|
|
2932
|
+
up the same role, user, policy and variable by hand, as a super user. The policy must match the scaffolded workflow:
|
|
2933
|
+
its `deploy.yaml` file on `main`, in the `production` environment. The `audience` is the cluster URL with its port
|
|
2934
|
+
and a trailing slash, and `HARPER_CLI_TARGET` must be that same string:
|
|
2935
|
+
|
|
2936
|
+
```bash
|
|
2937
|
+
gh api repos/<owner>/<repo> --jq .id # the repository_id
|
|
2938
|
+
harper add_role role=<name>-ci-deploy permission='{"operations":["deploy_component","get_job","registration_info"]}'
|
|
2939
|
+
harper add_user username=<name>-ci-deploy role=<name>-ci-deploy active=true password="$(openssl rand -base64 32)"
|
|
2940
|
+
harper add_oidc_trust \
|
|
2941
|
+
id=github-actions-<name> \
|
|
2942
|
+
issuer=https://token.actions.githubusercontent.com \
|
|
2943
|
+
audience=https://my-cluster.example.com:9925/ \
|
|
2944
|
+
user=<name>-ci-deploy \
|
|
2945
|
+
claims='{"repository_id":"<id>","workflow_ref":"<owner>/<repo>/.github/workflows/deploy.yaml@refs/heads/main","environment":"production"}'
|
|
2946
|
+
gh variable set HARPER_CLI_TARGET --repo <owner>/<repo> --body https://my-cluster.example.com:9925/
|
|
2947
|
+
```
|
|
2948
|
+
|
|
2949
|
+
If you restrict the `production` environment's deployment branches, allow `main`.
|
|
2950
|
+
|
|
2951
|
+
2. **Deploy by merging to `main`**, or by running the workflow from the **Actions** tab. The workflow deploys with
|
|
2952
|
+
`restart=rolling` and waits for every node to take the release. The run's summary names the `deployment_id` and its
|
|
2953
|
+
`certification`.
|
|
2954
|
+
3. **Deploy by hand with `npm run deploy`**: It runs `harper deploy project=<name> restart=rolling` from your machine, as
|
|
2955
|
+
the user you logged in with.
|
|
2956
|
+
4. **Go back to an earlier release** by activating its `deployment_id`, with nothing reinstalled:
|
|
2957
|
+
|
|
2958
|
+
```bash
|
|
2959
|
+
harper list_deployments project=<name>
|
|
2960
|
+
harper deploy project=<name> deployment_id=<id> restart=rolling
|
|
2961
|
+
```
|
|
2962
|
+
|
|
2963
|
+
See [deploying-to-harper-fabric](deploying-to-harper-fabric.md).
|
|
2964
|
+
|
|
2965
|
+
Use your package manager's equivalent of `npm run` (`pnpm run`, `bun run`, `yarn`) if the project uses another one.
|
|
2966
|
+
|
|
2967
|
+
Projects created before create-harper 1.13.0 deploy on version tags with a `HARPER_CLI_REFRESH_TOKEN` secret, and
|
|
2968
|
+
don't have `deploy:setup-ci`. To move one over:
|
|
2969
|
+
|
|
2970
|
+
1. Scaffold a new project with the same name and template, and copy its `.github/workflows/deploy.yaml` over the old
|
|
2971
|
+
workflow.
|
|
2972
|
+
2. Set up the cluster for it, with the `project=` value that workflow passes to `harper deploy` as `<name>`: with the
|
|
2973
|
+
5.4.0 CLI, `harper deploy setup=true provider=github-actions project=<name>`; with an earlier one, by hand as in
|
|
2974
|
+
step 1.
|
|
2975
|
+
3. Push the new workflow to `main`.
|
|
2976
|
+
4. Once the workflow's run on `main` has succeeded, wait step included, delete the `HARPER_CLI_REFRESH_TOKEN` secret.
|
|
2977
|
+
Deleting it doesn't revoke the token, which stays valid until it expires. To cut it off now, mint a new one for that
|
|
2978
|
+
user with `harper login --for-ci`, which invalidates the old one, or deactivate the user with `alter_user` if
|
|
2979
|
+
nothing else uses it.
|
|
2556
2980
|
|
|
2557
|
-
### 4.
|
|
2981
|
+
### 4.5 Serving Web Content
|
|
2558
2982
|
|
|
2559
2983
|
Instructions for the agent to follow when serving web content from Harper.
|
|
2560
2984
|
|
|
@@ -2661,7 +3085,7 @@ Because `@harperfast/vite` builds on the node and `static` serves the output, de
|
|
|
2661
3085
|
|
|
2662
3086
|
On deploy the plugin runs `vite build` at startup (and rebuilds when `files` change) while `static` serves the result. If you prefer to build in CI, commit the build output, point `static` at it, and omit `files` so the plugin stays idle while `static` serves the prebuilt assets. Either way, `npm create harper@latest` scaffolds a working setup for you.
|
|
2663
3087
|
|
|
2664
|
-
### 4.
|
|
3088
|
+
### 4.6 Harper Logging
|
|
2665
3089
|
|
|
2666
3090
|
Instructions for the agent to follow when implementing logging in Harper applications, including direct logger usage, tagged loggers, and console capture behavior.
|
|
2667
3091
|
|
|
@@ -2817,61 +3241,31 @@ Tagged entries appear in `hdb.log` with the tag in the header:
|
|
|
2817
3241
|
- When logging to standard streams, run Harper in the foreground (`harper`, not `harper start`).
|
|
2818
3242
|
- `TaggedLogger` is bound to the configured log level at creation time — always use `?.` on its methods.
|
|
2819
3243
|
|
|
2820
|
-
### 4.
|
|
3244
|
+
### 4.7 Load Environment Variables with loadEnv
|
|
2821
3245
|
|
|
2822
3246
|
Instructions for the agent to follow when loading environment variables from `.env` files into a Harper application using the `loadEnv` plugin.
|
|
2823
3247
|
|
|
2824
3248
|
#### When to Use
|
|
2825
3249
|
|
|
2826
|
-
Apply this rule when a Harper application needs to supply secrets, API endpoints, or other configuration values to component code via `process.env` without hardcoding them. Use
|
|
3250
|
+
Apply this rule when a Harper application needs to supply secrets, API endpoints, or other configuration values to component code via `process.env` without hardcoding them. Use this whenever a `.env` file must be loaded at application startup.
|
|
2827
3251
|
|
|
2828
3252
|
#### How It Works
|
|
2829
3253
|
|
|
2830
|
-
1. **Declare `loadEnv` in `config.yaml`**:
|
|
2831
|
-
|
|
2832
|
-
```yaml
|
|
2833
|
-
loadEnv:
|
|
2834
|
-
files: '.env'
|
|
2835
|
-
```
|
|
2836
|
-
|
|
2837
|
-
2. **Place `loadEnv` first**: Harper is a single-process application. List `loadEnv` before all other components so that environment variables are available on `process.env` before dependent components start.
|
|
3254
|
+
1. **Declare `loadEnv` first in `config.yaml`**: Place `loadEnv` before all other components so environment variables are available when dependent components start.
|
|
2838
3255
|
|
|
2839
3256
|
```yaml
|
|
2840
3257
|
# config.yaml — loadEnv must come first
|
|
2841
3258
|
loadEnv:
|
|
2842
3259
|
files: '.env'
|
|
2843
|
-
|
|
2844
|
-
rest: true
|
|
2845
|
-
|
|
2846
|
-
myApp:
|
|
2847
|
-
files: './src/*.js'
|
|
2848
|
-
```
|
|
2849
|
-
|
|
2850
|
-
3. **Access loaded values in component code**: After `loadEnv` runs, all loaded values are available on `process.env` and shared across all components.
|
|
2851
|
-
|
|
2852
|
-
4. **Control override behavior**: By default, existing environment variables take precedence over values in `.env` files. Set `override: true` to make loaded values win instead.
|
|
2853
|
-
|
|
2854
|
-
```yaml
|
|
2855
|
-
loadEnv:
|
|
2856
|
-
files: '.env'
|
|
2857
|
-
override: true
|
|
2858
3260
|
```
|
|
2859
3261
|
|
|
2860
|
-
|
|
3262
|
+
2. **Set the `files` option**: Provide one or more paths or glob patterns pointing to the env file(s) to load. This option is required.
|
|
2861
3263
|
|
|
2862
|
-
|
|
2863
|
-
loadEnv:
|
|
2864
|
-
files:
|
|
2865
|
-
- '.env'
|
|
2866
|
-
- '.env.local'
|
|
2867
|
-
```
|
|
3264
|
+
3. **Access variables in component code**: After loading, all values from the specified files are available on `process.env` and shared across all components in the single Harper process.
|
|
2868
3265
|
|
|
2869
|
-
|
|
3266
|
+
4. **Control override behavior with `override`**: By default, existing environment variables take precedence over values in `.env` files. Set `override: true` to reverse this — loaded values will overwrite variables already present in the shell or container environment.
|
|
2870
3267
|
|
|
2871
|
-
|
|
2872
|
-
loadEnv:
|
|
2873
|
-
files: 'env-vars/*'
|
|
2874
|
-
```
|
|
3268
|
+
5. **Load multiple files when needed**: Specify a list or a glob pattern under `files`. Files are loaded in the order specified.
|
|
2875
3269
|
|
|
2876
3270
|
##### Configuration Options
|
|
2877
3271
|
|
|
@@ -2882,10 +3276,17 @@ Apply this rule when a Harper application needs to supply secrets, API endpoints
|
|
|
2882
3276
|
|
|
2883
3277
|
#### Examples
|
|
2884
3278
|
|
|
2885
|
-
**
|
|
3279
|
+
**Basic usage — load a single `.env` file:**
|
|
2886
3280
|
|
|
2887
3281
|
```yaml
|
|
2888
|
-
|
|
3282
|
+
loadEnv:
|
|
3283
|
+
files: '.env'
|
|
3284
|
+
```
|
|
3285
|
+
|
|
3286
|
+
**Full `config.yaml` with `loadEnv` declared first:**
|
|
3287
|
+
|
|
3288
|
+
```yaml
|
|
3289
|
+
# config.yaml — loadEnv must come first
|
|
2889
3290
|
loadEnv:
|
|
2890
3291
|
files: '.env'
|
|
2891
3292
|
|
|
@@ -2895,29 +3296,40 @@ myApp:
|
|
|
2895
3296
|
files: './src/*.js'
|
|
2896
3297
|
```
|
|
2897
3298
|
|
|
2898
|
-
**
|
|
3299
|
+
**Override existing environment variables:**
|
|
3300
|
+
|
|
3301
|
+
```yaml
|
|
3302
|
+
loadEnv:
|
|
3303
|
+
files: '.env'
|
|
3304
|
+
override: true
|
|
3305
|
+
```
|
|
3306
|
+
|
|
3307
|
+
**Load multiple files as a list:**
|
|
2899
3308
|
|
|
2900
3309
|
```yaml
|
|
2901
|
-
# config.yaml
|
|
2902
3310
|
loadEnv:
|
|
2903
3311
|
files:
|
|
2904
3312
|
- '.env'
|
|
2905
3313
|
- '.env.local'
|
|
2906
|
-
|
|
3314
|
+
```
|
|
2907
3315
|
|
|
2908
|
-
|
|
3316
|
+
**Load multiple files using a glob pattern:**
|
|
2909
3317
|
|
|
2910
|
-
|
|
2911
|
-
|
|
3318
|
+
```yaml
|
|
3319
|
+
loadEnv:
|
|
3320
|
+
files: 'env-vars/*'
|
|
2912
3321
|
```
|
|
2913
3322
|
|
|
2914
3323
|
#### Notes
|
|
2915
3324
|
|
|
2916
|
-
- `loadEnv`
|
|
2917
|
-
-
|
|
2918
|
-
-
|
|
3325
|
+
- `loadEnv` is built into Harper — it does not need to be installed, only declared in `config.yaml`.
|
|
3326
|
+
- Variables are loaded onto `process.env` and shared across all components. Declare `loadEnv` before any component that depends on those variables.
|
|
3327
|
+
- `loadEnv` supplies **application** environment variables only. It does **not** configure Harper itself. Harper's own configuration is composed at startup before any `loadEnv` runs, so variables such as `HARPER_CONFIG`, `HARPER_SET_CONFIG`, and `HARPER_DEFAULT_CONFIG` delivered through a `.env` file are read too late and are ignored.
|
|
3328
|
+
- To change Harper's own configuration, use the configuration file directly or export variables in the real process/container environment before Harper starts.
|
|
3329
|
+
- For production credentials, prefer the encrypted secrets store over a committed `.env` file. Secrets are delivered to components via `process.env` and never appear in operation logs or replication payloads.
|
|
3330
|
+
- Individual `.env` values can also be stored encrypted so plaintext never reaches the API, logs, or replication payload.
|
|
2919
3331
|
|
|
2920
|
-
### 4.
|
|
3332
|
+
### 4.8 v5 Upgrade: Breaking Changes and Migration Guide
|
|
2921
3333
|
|
|
2922
3334
|
Instructions for the agent to follow when migrating a Harper application to v5, covering all breaking changes and required code updates.
|
|
2923
3335
|
|
|
@@ -3089,7 +3501,7 @@ record = { ...record, property: 'changed' };
|
|
|
3089
3501
|
- Automatic context tracking simplifies code but requires explicit `commit()` or new `transaction()` calls when you need to observe data written within the same transaction.
|
|
3090
3502
|
- Use `getContext` from `'harper'` rather than passing context manually through every call — this is the recommended pattern in v5.
|
|
3091
3503
|
|
|
3092
|
-
### 4.
|
|
3504
|
+
### 4.9 Delegating to the Built-in Agent
|
|
3093
3505
|
|
|
3094
3506
|
Harper 5.2+ ships with a **built-in agent** that runs _inside_ the server, on the main thread
|
|
3095
3507
|
adjacent to the operations API. Because it runs in-process, it can do things a remote client
|