clone

push a directory of html from the terminal and get a four-character url back. a deno cli on cloudflare

feat/source-stable-push
nzip / README.md
README.md 9.7K rendered
1# `nzip`23**Push a directory of HTML from the terminal. Get a four-character URL back.**45_Deno CLI → Cloudflare Worker → R2 + D1. Free-tier-oriented static site hosting with optional6expiration, password protection, owner notifications, and enumeration alerts._78[![JSR](https://jsr.io/badges/@nzip/cli)](https://jsr.io/@nzip/cli)910[github.com/FelineStateMachine/nzip](https://github.com/FelineStateMachine/nzip) ·11[args.io/cat/nzip](https://args.io/cat/nzip)1213```console14$ nzip site push ./demo work:demo --ttl 30d15 bundling ./demo … 3 files, 197 B16 manifest 4a91d75d — 3 new blobs (197 B), 0 deduped17✓ pushed work:demo -> https://12d8.demo.dev/ (expires in 30d, push #1)18```1920## Install2122The CLI ships on [JSR](https://jsr.io/@nzip/cli). With [Deno](https://docs.deno.com/runtime/) on23your `PATH`:2425```sh26deno install -g -A -f -n nzip jsr:@nzip/cli27nzip --version28nzip auth --server https://share.demo.dev29nzip site push ./site work:demo30```3132`nzip auth` prompts for omitted values and saves configuration to `~/.config/nzip/config.json` with33mode 0600. `nzip --version` reports the installed JSR release without requiring authentication; add34`--json` for machine-readable output. Try the CLI without installing it with35`deno run -A jsr:@nzip/cli --help`.3637On another machine, install the CLI and authenticate with the same server and token. Hosted vaults38and sites are already available; only the `nzip site where` breadcrumb registry is machine-local.3940## How addresses work4142Every share lives at four hex characters. The first digit selects one of 16 registered vaults, and43the remaining three select one of 4,096 randomly allocated sites within that vault.4445```text46https://2a3f.demo.dev/47 |+- site 0xa3f48 +-- vault 0x2 ("work")49```5051Aliases such as `work:demo` are resolved by the authenticated API; public URLs do not expose them.52The control-plane shorthand `https://share.demo.dev/2a3f` permanently redirects to the canonical53site hostname. Each site hostname is a separate browser origin, so its storage, service workers,54and host-only cookies are isolated from other nzip sites. The default passkey relying-party ID is55also the site hostname; see the parent-domain caveat in [SECURITY.md](SECURITY.md).5657| form | example | meaning |58| ----------- | ----------- | -------------------------- |59| address | `2a3f` | direct hexadecimal address |60| vault alias | `work:demo` | alias within a named vault |61| bare alias | `demo` | alias in the temporary default vault |6263## Features6465- **Instant and content-addressed.** Push one canonical manifest and only the blobs the server does66 not already have. Identical files are stored once across every site.67- **Lifecycle-aware by default.** Fresh servers use `personal` (slot `0`, 14 days) for temporary68 shares and `public` (slot `f`, forever) for permanent app origins. Every vault can override its69 own default TTL.70- **First-class lofi apps.** `app init` reserves the final browser origin before the first build;71 `app deploy` validates and publishes a root-scoped lofi PWA without ever recycling that origin.72- **Revertible.** The last ten pushes per site are retained and available through73 `nzip site revert`.74- **Password-protectable.** Password policy is committed atomically with content. Signed, host-only75 per-site cookies are invalidated when the policy changes.76- **Recoverable.** `nzip site cp` reconstructs and verifies the currently hosted bundle.77- **Owner notifications.** Explicitly approved phones receive Web Push notifications with bounded,78 same-origin click targets.79- **Machine-local breadcrumbs.** `nzip site where` locates the source directory recorded during a80 push. An untargeted push from the same logical source updates its unique existing site; a81 directory and its `index.html` count as the same source. Pass `--new` to create another address.82- **Vault guardrails.** Optional `allowVaults` configuration refuses disallowed vaults before83 upload. Raw addresses are mapped to their authenticated vault slot before the same check.84- **Observable abuse controls.** Enumeration is rate-limited and evaluated through bounded,85 privacy-preserving telemetry.8687## Commands8889```text90nzip91├─ --version [--json] show the installed CLI version92├─ auth [--server URL] [--token T] authenticate and save config93├─ status show server and vault status94├─ app95│ ├─ init <alias|vault:alias> reserve a stable app URL96│ └─ deploy build and deploy the configured lofi app97├─ vault98│ ├─ add <name> [--slot N] [--default-ttl 14d|forever|inherit]99│ ├─ update <name> [--default-ttl 14d|forever|inherit]100│ ├─ ls list vaults101│ └─ default <temporary|permanent> <name>102├─ site103│ ├─ push <dir|file> [target] [--new] [--ttl …] [--password PW | --no-password]104│ ├─ cp <target> [dir] [--overwrite] copy a hosted bundle105│ ├─ show <target> show site details106│ ├─ policy <target> [--ttl …] [--password PW | --no-password]107│ ├─ ls [vault] list sites108│ ├─ where <target> print this machine's source directory109│ ├─ rm <target> [--yes] delete a site110│ └─ revert <target> [--to N] [--list] inspect or restore push history111└─ notify112 ├─ send <body> [--title TEXT] [--open TARGET] [--tag TEXT]113 ├─ test send a diagnostic notification114 ├─ pair allow device pairing for 10 minutes115 ├─ approve <code> --name NAME [--yes]116 ├─ devices [--all] list notification devices117 └─ revoke <device-id> [--yes] revoke a notification device118```119120Password and TTL values are committed with content. New-site TTL resolves in this order: explicit121`--ttl`, existing-site expiry, vault default, then the global 14-day fallback. Changing a vault122default never rewrites existing sites. `public` names a durable namespace, not an access policy;123sites there may still be password-protected. `status --json` and `vault ls --json` expose124`defaultVaults`, `globalDefaultTtl`, and each vault's effective default for agent planning. Push JSON125reports `ttl` and `ttlSource`. Directory pushes skip dotfiles and `node_modules` and honor a126`.nzipignore` file containing one glob per line.127128Content revisions use `site push`; access and expiry changes use `site policy`. With no explicit129target, `site push` reuses a unique unexpired local breadcrumb for the same logical source. It stops130when multiple sites match instead of guessing. Pass the intended target to resolve the ambiguity,131which rebinds later untargeted pushes to that site, or pass `--new` only when a separate URL is132desired.133134## Host a lofi app135136Reserve the production origin before configuring credentials or building:137138```sh139cd field-notes140nzip app init field-notes141# add the printed origin to src/app.ts credentialOrigins142nzip app deploy143```144145`app init` defaults to the permanent vault, is idempotent, and writes a token-free146`nzip.app.json` that is safe to commit. Before the first deploy, the reserved URL serves a small147not-deployed page. `app deploy` runs the configured Deno build task with `LOFI_BASE_PATH=/`, verifies148the lofi build identity, PWA files, stable credential origin, and optional passkey RP ID, then149publishes `dist/`. It also mirrors lofi's generated CSP as a response header. Deleting or expiring150app content does not free its address: the origin reservation remains a permanent tombstone because151service workers, OPFS, IndexedDB, and passkeys bind application identity to that origin.152153See [`cli/README.md`](cli/README.md) for detailed CLI behavior and examples.154155## Owner notifications156157Pairing is closed by default. Open a 10-minute window, then tap the temporary `pair` action on the158deployment root and approve the displayed code:159160```sh161nzip notify pair162nzip notify approve ABCD-1234 --name "Personal phone"163```164165Wait for `paired` before installing the PWA. In the installed app, tap `notifications off` to166request permission and attach the subscription. Send a notification with:167168```sh169nzip notify send "Build finished" --open work:report170```171172Notification content may be visible on a lock screen. Never include passwords, tokens, private URLs,173or sensitive personal information. See [SECURITY.md](SECURITY.md) for the enrollment and delivery174trust boundaries.175176## Self-hosting177178nzip is self-hosted; there is no bundled public service. The Worker uses an exact control hostname,179a wildcard site route, R2, D1, rate-limit bindings, and optional Email Routing and Web Push180configuration.181182Follow [`worker/setup.md`](worker/setup.md) for provisioning, migrations, secrets, notification183setup, deployment, and operational checks. The system is designed for small personal deployments on184Cloudflare's included usage; see the185[architecture budget notes](ARCHITECTURE.md#free-tier-design-target) before exposing a busy or186multi-tenant instance.187188## Project documentation189190- [ARCHITECTURE.md](ARCHITECTURE.md) — components, push protocol, caching, storage, background work,191 observability, and resource budgets.192- [SECURITY.md](SECURITY.md) — trust boundaries, pairing security, enumeration controls, reporting,193 and monitoring.194- [CONTRIBUTING.md](CONTRIBUTING.md) — development setup, checks, local Worker workflow,195 documentation conventions, and releases.196- [`worker/setup.md`](worker/setup.md) — production Cloudflare deployment and upgrades.197- [`cli/README.md`](cli/README.md) — CLI installation and command reference.198- [`shared/README.md`](shared/README.md) — shared package contract and usage.199200MIT licensed.