172 lines
7.5 KiB
Markdown
172 lines
7.5 KiB
Markdown
# NekoNest Cloud
|
||
|
||
NekoNest Cloud is the private commercial control plane and managed Relay for
|
||
the open-source NekoNest data plane. Self-hosted NekoNest remains independent:
|
||
it does not contact this repository, read subscriptions, or display Cloud
|
||
concepts.
|
||
|
||
The managed architecture is deliberately not “one Docker Server per customer”:
|
||
|
||
```text
|
||
Daemon / Cloud PWA
|
||
-> stable Connect origin
|
||
-> regional Cloud Relay
|
||
-> TenantEngineRegistry
|
||
-> one isolated relaycore.Engine per tenant
|
||
-> per-tenant SQLite and attachment root
|
||
|
||
Cloud control plane
|
||
-> account, entitlement, host slots, placement, authorization revisions
|
||
-> short-lived signed authorization snapshots for Relay nodes
|
||
```
|
||
|
||
One account maps to one tenant/Nest. A host slot is consumed by one non-revoked
|
||
host identity, whether it is online or not. A copied device credential remains
|
||
the same identity and the managed Relay rejects a second simultaneous daemon;
|
||
software cannot prove that a fully copied private key resides on a different
|
||
physical machine.
|
||
|
||
## Current commercial policy
|
||
|
||
- Open-source self-hosting remains free.
|
||
- Cloud is a closed, free test while operational and legal gates remain open.
|
||
- No quote, order, payment, balance, automatic charge, or token resale path is
|
||
enabled.
|
||
- The existing entitlement/capacity source is authoritative for host slots.
|
||
Registering the N+1 non-revoked identity returns
|
||
`device_capacity_exceeded`; revoking a host releases its slot.
|
||
- Lowering capacity below the active host count is rejected until the user
|
||
explicitly revokes hosts. Cloud never chooses a device to evict.
|
||
- Price, billing period, refund, subscription-expiry, and payment-provider
|
||
policy remain outside this implementation.
|
||
|
||
See [`docs/commercial-contract.md`](./docs/commercial-contract.md) and
|
||
[`docs/launch-gates.md`](./docs/launch-gates.md).
|
||
|
||
## Implemented control-plane contracts
|
||
|
||
- Account, beta entitlement, host-slot, pairing, claim, device credential,
|
||
revocation, audit, feedback, retention, and account-exit domain state in D1.
|
||
- Atomic host claiming: an identity retry is idempotent and does not consume a
|
||
second slot; a different N+1 identity is refused.
|
||
- Stable daemon registration. `POST /api/devices/register` returns the existing
|
||
device fields plus `connection_state: ready | provisioning` and optional
|
||
`retry_after_seconds`; it never returns a tenant Relay URL.
|
||
- Regions, Relay nodes, tenant placement, placement generation,
|
||
authorization revision, opaque route handles, and single-use handoff tickets.
|
||
- Ed25519-signed canonical authorization snapshots with `kid`, issue/expiry,
|
||
tenant state, home region, target node, generation, revision, and active
|
||
credential digests.
|
||
- Relay-node-only internal APIs for live authorization, revision/delta polling,
|
||
route resolution, and heartbeat. Node calls use scoped identity; clients
|
||
cannot submit a raw tenant ID.
|
||
- A 60-second, origin-bound, single-use Dashboard-to-PWA handoff. D1 stores only
|
||
the ticket digest. Exchange creates an independent revocable `phone_id`,
|
||
`phone_token`, and non-authorizing opaque route handle. Completion first
|
||
records a non-authorizing pending principal; only the first request proving
|
||
possession of the exact token and route handle activates it and advances the
|
||
tenant authorization revision. A lost exchange response therefore cannot
|
||
leave an unused active phone identity.
|
||
- Login or handoff never grants a phone access to a host. The phone must still
|
||
complete NekoNest’s per-device E2E pairing and receive that device’s key
|
||
package.
|
||
- Stable ingress resolves the current placement for every new connection and
|
||
uses authenticated internal HTTPS/WSS forwarding when the home node is
|
||
elsewhere; clients never receive a node URL or redirect.
|
||
- Fenced backup/restore and migration primitives keep one writable generation.
|
||
Application-layer tenant purge closes the Engine, deletes live data,
|
||
attachments and all tenant backups, then clears active credentials only
|
||
after node acknowledgement. Its completion audit is written only after all
|
||
D1 postconditions hold, and the purge job's `completed` marker is the final
|
||
fenced mutation.
|
||
|
||
The Cloud PWA is built from an exact NekoNest revision. It points at the stable
|
||
Connect origin through NekoNest runtime endpoint configuration and refuses any
|
||
managed endpoint that is not `sealed`.
|
||
|
||
The manual [`cloud-pwa.yml`](./.github/workflows/cloud-pwa.yml) workflow accepts
|
||
only a full 40-character NekoNest commit SHA, verifies the checkout, runs the
|
||
PWA tests/type-check/build, writes the deploy-time Connect config, records the
|
||
source SHA, and emits an immutable build artifact. It does not deploy by itself.
|
||
|
||
## Removed legacy design
|
||
|
||
The unreleased activation poller, `/api/devices/bootstrap`, tenant
|
||
`relay_url` handoff, managed-device manifest, per-tenant Server container, and
|
||
single-VPS Node provisioner are intentionally removed. They are not a fallback
|
||
or a compatibility mode. A daemon with that old managed configuration must
|
||
re-register.
|
||
|
||
## Local control-plane development
|
||
|
||
Requires Node.js 22.13 or newer:
|
||
|
||
```powershell
|
||
npm ci
|
||
npm run dev
|
||
```
|
||
|
||
Production control-plane configuration includes:
|
||
|
||
- D1 binding `DB` from [`.openai/hosting.json`](./.openai/hosting.json);
|
||
- `NEKONEST_CLOUD_ADMIN_EMAILS` for the temporary closed-test admin boundary;
|
||
- `NEKONEST_CLOUD_CREDENTIAL_SECRET` with at least 32 random characters for
|
||
purpose-separated HMAC digests;
|
||
- Relay snapshot signing keys and active `kid`;
|
||
- independent Relay-node mTLS identities and an exact origin allowlist for the
|
||
Cloud PWA;
|
||
- the stable Connect and PWA origins used by registration and handoff.
|
||
|
||
Sites/ChatGPT identity and the development demo viewer are closed-test
|
||
scaffolding, not the final public identity or account-recovery decision.
|
||
|
||
## Daemon distribution
|
||
|
||
The download catalog is fail closed. It accepts only an exact stable NekoNest
|
||
tag, exact platform assets, `checksums.txt`, and configured SHA-256 digests; it
|
||
never follows `latest`. The compatible daemon must implement protocol 1.3,
|
||
stable `server_url`, `ready | provisioning`, and structured service errors.
|
||
Artifact hashes do not replace publisher/code signing.
|
||
|
||
See [`docs/daemon-distribution.md`](./docs/daemon-distribution.md).
|
||
|
||
Relay deployment, internal forwarding, migration and purge configuration are
|
||
documented in [`docs/relay-operations.md`](./docs/relay-operations.md).
|
||
The manual [`cloud-relay.yml`](./.github/workflows/cloud-relay.yml) workflow
|
||
rejects local `replace` directives, verifies the requested `relaycore/vX.Y.Z`
|
||
tag against `go.mod`, and runs tests, vet, race checks, and a provenance build
|
||
with `GOWORK=off`.
|
||
|
||
## Database
|
||
|
||
Drizzle schema is in [`db/schema.ts`](./db/schema.ts), with append-only
|
||
migrations under [`drizzle/`](./drizzle/). Runtime migration tracking uses
|
||
`cloud_schema_migrations`.
|
||
|
||
```powershell
|
||
npm run db:generate
|
||
```
|
||
|
||
Never rewrite a migration already applied to a D1 environment. Because this
|
||
repository has not yet served production users, a disposable environment may
|
||
be recreated; any non-disposable D1 database must be exported before migration.
|
||
|
||
## Verification
|
||
|
||
```powershell
|
||
npm run type-check
|
||
npm run lint
|
||
npm test
|
||
|
||
cd relay
|
||
go test -count=1 ./...
|
||
go vet ./...
|
||
```
|
||
|
||
The Go Relay has its own tests, race checks, and resource-leak checks. A local
|
||
green build is not public-service acceptance. Paid/public launch stays blocked
|
||
until exact-build deployment proves stable ingress, cross-tenant isolation,
|
||
sealed message and attachment paths, 15-second revocation, five-minute snapshot
|
||
expiry, backup/restore, deletion/purge, regional migration and rollback,
|
||
monitoring/on-call, retention, identity, domain/filing, and payment policy.
|