Files

172 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 NekoNests per-device E2E pairing and receive that devices 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.