1# nz23Push a directory of HTML, get a url back.45```console6$ nz push ./report --password meow7expires 2026-11-17T19:40:02.000Z, password protected8https://k3x9q.example.com/9```1011## Minimalism1213nz is a few files you can read in one sitting:1415| file | what |16| ------------- | ------------------------------------------------------ |17| `worker.js` | Cloudflare Worker, plain JS, no dependencies, no build |18| `nz` | POSIX `sh` CLI: `tar` + `curl`, nothing to install |19| `nz.ps1` | the same CLI for PowerShell on Windows |20| `test.sh` | end-to-end test against `wrangler dev` |2122A site is **one KV entry**: the value is a tar of the directory, and the metadata is23`{ exp, pw }`. KV expiration deletes it when the TTL runs out.2425## Install2627```sh28wrangler kv namespace create SITES # paste the id into wrangler.jsonc29# set DOMAIN + routes in wrangler.jsonc, add a proxied DNS record: * AAAA 100::30wrangler secret put TOKEN31wrangler deploy3233ln -s "$PWD/nz" ~/bin/nz34mkdir -p ~/.config/nz && printf 'NZ_SERVER=https://example.com\nNZ_TOKEN=...\n' > ~/.config/nz/config35```3637On Windows, put `nz.ps1` somewhere on your `PATH` and write the same config. It needs38Windows 10 or later, which ships `tar.exe` and `curl.exe`:3940```powershell41Unblock-File .\nz.ps1 # if it came from a download42New-Item -ItemType Directory -Force ~\.config\nz | Out-Null43"NZ_SERVER=https://example.com`nNZ_TOKEN=..." | Set-Content -Encoding ascii ~\.config\nz\config44nz.ps1 push .\site --ttl 7d --password meow45```4647## CLI4849```text50nz push <dir|file> [--ttl 14d|never] [--password PW] [--new]51nz update <id> [dir|file] [--ttl 14d|never] [--password PW | --no-password]52```5354- `push` updates the site last pushed from the same path (`~/.config/nz/sites`, one55 `id<TAB>path` per line), or makes a new one if there is none, it expired, or `--new`.56- `update` targets an id. Without a path it only changes ttl/password.57- `nz.ps1` takes the same commands, and also accepts PowerShell-style `-ttl`, `-password`, `-new`.58- A single file is published as `index.html`. Dotfiles and `node_modules` are skipped.59- TTL: `30m`, `12h`, `14d`, `2w`, `never`. Default `DEFAULT_TTL` (14d). Omitted on update = kept.60- The url goes to stdout and details to stderr, so `open "$(nz push .)"` works.6162## API6364The CLI is a thin wrapper around this. Everything lives on the apex host:6566```sh67tar --format=ustar -cf - -C site . |68 curl -H "authorization: Bearer $TOKEN" -H 'nz-ttl: 7d' -H 'nz-password: meow' \69 --data-binary @- https://example.com/ # POST: new site -> 20170curl -X PUT ... --data-binary @site.tar https://example.com/k3x9q # replace content71curl -X PUT -H "authorization: ..." -H 'nz-password;' https://example.com/k3x9q # policy only72```7374The response is the url on line 1 and a details line on line 2. `nz-password;` (empty) removes the75password. Omitted headers keep their current value on `PUT`.7677Sites live at `https://<id>.DOMAIN/`, so each one is its own browser origin. Password-protected78sites show a form. Unlocking sets a host-only `__Host-nz` cookie that lasts 7 days and is signed79over the password verifier, so changing the password logs everyone out. Unlock attempts are80rate-limited to 5 per minute per ip and site.8182## License83840BSD