Compare commits

..
Author SHA1 Message Date
joshuaandClaude Opus 5.5 cc855ec8bb Docs: Hub 2.0.0 released (phase 1); hub-protocol.md with protocol v2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 09:10:33 +02:00
joshuaandClaude Opus 5.5 0a9b08e1fd Merge branch 'docs/sites-folder'
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:54:05 +02:00
joshuaandClaude Opus 5.5 d6ff08f5d9 Docs: church installs under /var/www/sites/<church>/<product>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:54:05 +02:00
joshuaandClaude Opus 5.5 b1a5c5abc3 Merge branch 'docs/roadmap-monorepo-deploy'
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:48:36 +02:00
3 changed files with 88 additions and 30 deletions
+12 -9
View File
@@ -1,7 +1,8 @@
# Handover — where things stand and how to carry on
Last updated: 2026-11-04, after **2.1.1** (deployed), Hub **1.7.0** and **contracts 1.0.0**;
ROADMAP-v3 updated for the monorepo move and the `crosscode` deploy script (decisions 21–24). **The plan from here is
Last updated: 2026-11-05, after **2.1.1** (deployed), **Hub 2.0.0** (released, not deployed yet),
**contracts 1.1.0** and the crosscode-site address prices (not deployed yet); ROADMAP-v3 updated for
the monorepo move and the `crosscode` deploy script (decisions 21–24). **The plan from here is
[ROADMAP-v3.md](./ROADMAP-v3.md)** (Accounts, Church Website, Church App, Cross Code Hub; decisions
table near the end). How Cross Events got to 2.x is in [ROADMAP-v2.md](./ROADMAP-v2.md); the round
before that in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
@@ -95,13 +96,15 @@ encrypted settings unreadable or break existing installs.
crosscode.co.za, with prices) is live. Build order (§9):
- **0 — contracts 1.0.0: released 2026-11-04** (`../crosscode-platform`, tag `contracts@1.0.0`):
`crosscode.event/1`, `crosscode.church/1`, `crosscode.identity/1`, Hub protocol v2, generated
JSON Schema in `packages/contracts/schemas/`. Not published to Gitea's package registry, and
won't be (decision 22).
- **1 — Hub 2.0.0** (`../events-hub`): Cross Code Hub — products, one licence per product,
**one install of each product per customer**, domain per customer, bundles, sibling discovery,
**releases per product** (Events from `crosscode-platform` `events@…` tags, keeping the
`hope-events` `v…` releases) **with requirements** (`— requires accounts 1.2.0`, Request update
disabled until met). Contracts as `vendor/crosscode-contracts-1.0.0.tgz` (ROADMAP-v3 §2, §5).
JSON Schema in `packages/contracts/schemas/`. **1.1.0** (2026-11-04) adds `requires` on Hub
releases. Not published to Gitea's package registry, and won't be (decision 22).
- **1 — Hub 2.0.0: released 2026-11-05** (`../events-hub`, tag `v2.0.0`; plan and what each step
did in its `docs/HUB-2.0-PLAN.md` and CHANGELOG): products and one licence per product, protocol
v2 next to v1 (Linked products), releases per product with requirements, billing per church
(bundles, founding, Website storage, App tiers, prorating), the church's address, AI per church,
dashboard per product. **Deploying it:** `events-hub/deployment.md` → "Upgrading to 2.0" (run
`check-hub2` first). crosscode-site has the address prices (merged, deploy it too). Then
installs show up per product; Cross Events 2.1.x keeps speaking protocol 1 until 2.4.0.
- **1b — Cross Events 2.2.0: moves into the monorepo** as `apps/events` with its history, and the
**`crosscode` script** (`deploy <church> [events] [website] [app]` — Accounts always —,
`status`, `install`, `adopt`) replaces `scripts/deploy.sh` / `install.sh`. Run
+34 -16
View File
@@ -482,18 +482,23 @@ picks the tags up per product (§5).
per product, each at that product's own tag:
```
/opt/crosscode/
tools/ the crosscode script (a checkout of main, updated before every run)
churches/hopefamily/
/var/www/sites/
hopefamily/
church.env slug, products, domains, ports, PM2 names, database names, folders
accounts/ crosscode-platform at accounts@1.0.0
events/ crosscode-platform at events@3.1.0
website/ crosscode-platform at website@1.2.0
gracechurch/
…
/opt/crosscode/tools/ the crosscode script (a checkout of main, updated before every run)
```
A product's folder can live elsewhere (`church.env` says where), so existing installs such as
`/opt/hope-events` don't have to move, and their PM2 names, nginx names and database names stay
as they are. Each checkout installs and builds only its own app (`npm ci -w apps/website`, its
Every church's installs live under **`/var/www/sites/<church>/`** (decided 2026-11-04; the
existing Events installs are in `/var/www` today and move there). The script finds churches by
their `church.env`, so `--every-church` is every folder in `/var/www/sites` that has one. A
product's folder can be somewhere else if `church.env` says so, but the standard layout is the
default for `install` and `adopt`. PM2 names, nginx names and database names keep what an install
already has. Each checkout installs and builds only its own app (`npm ci -w apps/website`, its
own migrations), so a Website install never builds Events.
**The script** (run as root or the app user; `crosscode help` lists everything):
@@ -508,7 +513,7 @@ own migrations), so a Website install never builds Events.
| `crosscode status [<church>]` | Each product's version, latest release, health (from `/health`) and last backup |
| `crosscode install <church> --products events,website --domain … ` | A new church (from today's `install.sh`): Accounts always, plus the products named; databases, `.env` files, PM2, nginx and certificates; prints the DNS records needed |
| `crosscode add <church> app` | Adds a product to an existing church |
| `crosscode adopt <church> --events-dir /opt/hope-events` | Once per existing Cross Events install: moves it onto the monorepo (below) |
| `crosscode adopt <church> --events-dir /var/www/<old folder>` | Once per existing Cross Events install: moves it onto the monorepo and into `/var/www/sites/<church>/events` (below) |
Before Accounts exists (until phase 3), "always Accounts" is skipped for churches that don't have
it yet.
@@ -528,15 +533,28 @@ it yet.
reached.
**Moving existing Cross Events installs (Events 2.2.0, `crosscode adopt`).** Once per server
install, run by Joshua. It writes `church.env` (pointing at the existing folder), changes the
checkout's remote to `crosscode-platform`, moves the install's own files to the new paths
(`backend/.env` → `apps/events/backend/.env`, likewise the frontend `.env`,
`backend/public/uploads/`, `backend/backups/`, `backend/data/*.json`, and `ecosystem.config.js`
with its `cwd` paths updated), checks out `events@2.2.0`, builds and reloads. The folder, PM2
names, nginx config (it only proxies to ports), database and its name stay the same. If anything
fails it moves the files back and returns to the `hope-events` remote and the version it was on.
install, run by Joshua. It:
1. Moves the install's folder from `/var/www/<old folder>` to `/var/www/sites/<church>/events`
(skipped if it's already there, e.g. moved by hand) and writes `church.env`.
2. Changes the checkout's remote to `crosscode-platform` and moves the install's own files to
the new paths: `backend/.env` → `apps/events/backend/.env`, likewise the frontend `.env`,
`backend/public/uploads/`, `backend/backups/`, `backend/data/*.json`, and
`ecosystem.config.js` with its `cwd` paths updated.
3. Checks out `events@2.2.0`, builds, and **re-registers the PM2 processes** under their existing
names from the new folder (`pm2 delete` + `pm2 start` + `pm2 save`: PM2 remembers the folder a
process was started from, so a reload isn't enough after a move).
The PM2 names, nginx config (it only proxies to ports, so it doesn't change when the folder
moves), database and its name stay the same. If anything fails it moves everything back, returns
to the `hope-events` remote and the version it was on, and starts PM2 from the old folder again.
Installs that haven't moved yet keep working with `deploy.sh` on the archived repo.
**Moving a folder by hand before 2.2.0** works too: move it into `/var/www/sites/<church>/events`,
then `pm2 delete` its two processes, `pm2 start ecosystem.config.js` from the new folder and
`pm2 save`. `deploy.sh` works from wherever the install is, and `adopt` later sees it's already in
place.
---
## 9. Build order
@@ -544,7 +562,7 @@ Installs that haven't moved yet keep working with `deploy.sh` on the archived re
| Phase | Version | What | Why this order |
|-------|---------|------|----------------|
| 0 | contracts 1.0.0 | `crosscode.event/1`, `crosscode.church/1`, `crosscode.identity/1`, Hub protocol v2: schemas, generated JSON Schema, docs, sample payloads, the monorepo set up. **Released 2026-11-04** | Everything else depends on them |
| 1 | **Hub 2.0.0** | Cross Code Hub: products, licence per product, per-customer billing with bundles, protocol v2 (v1 still accepted), sibling discovery, AI usage per product, **releases per product with requirements** (§5), contracts as a committed file (§2) | Must exist before a second product is sold, and before Events releases come from the monorepo |
| 1 | **Hub 2.0.0** | Cross Code Hub: products, licence per product, per-customer billing with bundles, protocol v2 (v1 still accepted), sibling discovery, AI usage per product, **releases per product with requirements** (§5), contracts as a committed file (§2). **Released 2026-11-05** (crosscode-site address prices merged) | Must exist before a second product is sold, and before Events releases come from the monorepo |
| 1b | **Cross Events 2.2.0** | **Moves into the monorepo** as `apps/events`, with its git history (merged in, not copied); its packages become workspaces; the **`crosscode` script** (`deploy`, `status`, `install`, `adopt`; §8) replaces `scripts/deploy.sh` and `scripts/install.sh`; every install moved with `crosscode adopt`; `hope-events` archived. No feature changes, so a problem is clearly the move | Before more products, so every product is released and deployed the same way from the start |
| 1c | **Cross Events 2.3.0** | **Authenticator app 2FA** (TOTP) next to email and WhatsApp, with recovery codes; admins can see which method each staff account uses | Independent and useful now; the Accounts migration later carries it over |
| 2 | **Cross Events 2.4.0** | Reports `product: "events"` (protocol v2), **Linked products**, the event feed (publish + show events from siblings), `packages/seo` (§11) with canonical links for imported events | Small; ready for when the Website exists |
@@ -748,7 +766,7 @@ ranking.
|---|----------|----------|
| 21 | Repos | **Cross Events moves into `crosscode-platform`** (`apps/events`, Events 2.2.0, history kept); `hope-events` is archived. The Hub stays in its own repo (own server, never installed for a church) |
| 22 | Contracts for the Hub | A packed release **committed to the Hub's repo** (`file:vendor/…`), not Gitea's package registry, so no deploy depends on a registry or a token (§2) |
| 23 | Deploying | **One `crosscode` script** for every product: `crosscode deploy <church> [events] [website] [app]` always updates Accounts and then the products named, in a fixed order, each rolled back on its own if it fails. Still git checkouts of tags, still run by Joshua; the Hub never deploys (§8) |
| 23 | Deploying | **One `crosscode` script** for every product, with every church under `/var/www/sites/<church>/<product>`: `crosscode deploy <church> [events] [website] [app]` always updates Accounts and then the products named, in a fixed order, each rolled back on its own if it fails. Still git checkouts of tags, still run by Joshua; the Hub never deploys (§8) |
| 24 | Product requirements | A release can require a minimum version of a sibling (`— requires accounts 1.2.0` in `WHATS-NEW.md`). The Hub **disables Request update** until it's met, and the script refuses to deploy without it (§5, §8) |
## Still open
+42 -5
View File
@@ -1,9 +1,13 @@
# Hub protocol (version 1)
# Hub protocol (versions 1 and 2)
The contract between a **Cross Events install** (the church's site) and the **Cross Events
Hub** (`events-hub.crosscode.co.za`). A copy of this file lives in both repositories
(`joshua/hope-events` → `docs/hub-protocol.md`, `joshua/events-hub` → `docs/hub-protocol.md`);
change both together.
The contract between an **install** (one of a church's products: Cross Events, Accounts, Church
Website, Church App) and **Cross Code Hub** (`hub.crosscode.co.za`; `events-hub.crosscode.co.za`
keeps working as an alias). Sections 1–5 describe **version 1**, which Cross Events 2.1 speaks;
**[version 2](#6-protocol-version-2-hub-20)** (every product, Hub 2.0) is section 6. A copy of this
file lives in both repositories (`joshua/hope-events` → `docs/hub-protocol.md`,
`joshua/events-hub` → `docs/hub-protocol.md`); change both together. From Hub 2.0 the formats are
defined once, in `@crosscode/contracts` (`crosscode-platform/packages/contracts`, `src/hub.ts`,
with JSON Schema and samples).
Principles:
@@ -288,3 +292,36 @@ grace starts). Blocking the Hub can't keep a suspended licence active for more t
fields to a heartbeat or a token doesn't need a new version — both sides ignore fields they
don't know.
- The Hub accepts protocol `N` and `N-1`; anything else is `400 UNSUPPORTED_PROTOCOL`.
## 6. Protocol version 2 (Hub 2.0)
The formats are in `@crosscode/contracts` (`hub.registerRequestSchema`, `hub.heartbeatRequestSchema`,
… with JSON Schema in `schemas/hub-*.json` and samples in `samples/hub-*.json`). The Hub accepts
versions 1 and 2 (`/api/public-key` lists `[1, 2]`); a v1 request is treated as Cross Events and
answered exactly as in sections 1–5. Signing, offline licence checks, update requests and AI calls
are as in version 1.
What version 2 changes:
| | Version 1 | Version 2 |
|---|---|---|
| Who | Cross Events | Every product: `product` is `events`, `accounts`, `website` or `app` |
| Licence keys | `CEH-…` | `CEH-` Events, `CCA-` Accounts, `CCW-` Website, `CCP-` App |
| Register | as §2 | adds `product` (must match the licence: `409 WRONG_PRODUCT`) and `install.publicKey` (the install's Ed25519 public key, PEM) |
| Heartbeat | as §3 | adds `product` (must match the install: `409 WRONG_PRODUCT`); `stats` per product (Events as v1; Website: pages, messages, events, editors, `storage`; App: `activeMembers90d` band, modules, prayer coverage; Accounts: accounts, 2FA methods) |
| Replies | as §2, §3 | add `linkedProducts`: the church's other installs in use that have a public key, as `{ product, installId, url, publicKey }`, nothing else. Releases carry `requires` (below) |
| Bodies | checked by hand | checked against the contract schemas: `400 INVALID_REQUEST` with the field that's wrong; unknown fields are dropped |
| Licence token | as §4 | also `customerId` and `product` (still `v: 1`; a v1 install ignores them). `limits.aiMonthlyCapCents` is the church's cap |
**Releases per product.** An install is told its own product's releases and latest version. A
release can need a minimum version of a sibling (`— requires accounts 1.2.0` in its WHATS-NEW.md
heading): v2 replies carry it as `requires: [{ product, version }]`, and an update request answers
`409 REQUIREMENT_NOT_MET` (with `requirements: [{ product, version, current }]` and a message saying
what's needed) until the church's install of that product runs that version or later.
**AI.** The monthly cap and markup are the church's, shared by all its installs: a call from any of
them counts what all of them used this month.
**Moving an install to v2** (e.g. Cross Events 2.4.0): register again with `protocolVersion: 2`,
the product and a public key (same licence and address: same install id, new secret), then send v2
heartbeats. Nothing on the Hub needs changing.