registry/service.nu — the wire: routes + handlers.
Implements exactly the protocol nurlpkg already drives (see stdlib/ext/pkg_fetch.nu + pkg_publish.nu and the Cloudflare Worker this replaces):
GET /index/<name>.json package index (rendered from SQLite) GET /pkgs/<name>/<name>-<version>.tar.gz tarball (disk, immutable) POST /api/v1/publish Bearer + X-Nurl-Package/-Version/-Deps POST /api/v1/yank | /api/v1/unyank owner-only version flag POST /api/v1/revoke delete the presented token GET /api/v1/search?q= · /api/v1/stats JSON GET / · /packages/<name> · /files/<n>/<v>/<path> HTML catalog + assets GET /login · /auth/callback GitHub OAuth (config-gated)
Every route handler is a top-level function; the router closures are one-liners, so the whole service is testable through router_handle with no socket (see tests/). Configuration lives in module globals set once by reg_service_config before the router is built. Handlers open a fresh SQLite connection per request (WAL + busy timeout — see db.nu), so the service is safe on a worker pool.
: ~ s g_reg_data ./data // data dir (tarballs under <data>/pkgs): ~ s g_reg_dbpath ./data/registry.db``: ~ s g_reg_pepper // token-hash pepper (deployment secret): ~ s g_reg_base_url // public base URL, for the OAuth redirect: ~ s g_reg_gh_id // GitHub OAuth app (empty = OAuth disabled): ~ s g_reg_gh_secret ``@ reg_service_config s data s dbpath s pepper s base_url s gh_id s gh_secret → vThe caller OWNS the config strings and must keep them alive for the server's lifetime (main holds them; the anomaly service follows the same contract).
@ reg_service_router → Routerregistry/auth.nu — bearer tokens: mint, hash, verify.
The scheme matches the Worker this registry replaces: a token is 32 CSPRNG bytes rendered as 64 hex chars, shown once at mint time; only sha256(pepper || token) (hex) is stored. The pepper is deployment config (REG_TOKEN_PEPPER), so a copied database alone cannot be brute-forced into live tokens.
The hash input is built as BYTES (bytes_from_str + extend), never through a # s cast — nurl_str_get-style C-string reads are NUL-bounded and would silently truncate binary material (the B3 crypto-block lesson).
@ reg_token_hash s pepper s token → Stringhex sha256(pepper || token)
@ reg_token_new → StringA fresh 64-hex-char token (32 CSPRNG bytes).
@ reg_bearer_of s auth → StringExtract the token from an Authorization: Bearer <tok> header value; "" when the scheme isn't Bearer or the token is empty. Scheme match is case-insensitive, surrounding whitespace tolerated.
registry/pages.nu — server-rendered HTML, via the template package.
Every page is an EMBEDDED template (installed tools are binary-only — there is no views/ directory at runtime; the yoloe-demo lesson) rendered against a Json context built by the service layer. The README on a package page comes from md2html over the tarball's README.md, with its relative links rewritten onto the version-pinned /files/ asset route.
@ reg_page_catalog Json ctx → Stringctx: { "q": str, "items": [ { name, version|null } ], "title": str }
@ reg_page_detail Json ctx → Stringctx: { name, owner|null, repository?, req, versions:[{version,yanked,login}], deps:[{name,req}], readme (pre-rendered HTML, ""=none), title }
@ reg_page_files Json ctx → Stringctx: { name, version, files:[{path,size}] (size pre-formatted), total, title }
@ reg_page_notfound Json ctx → String@ reg_page_token Json ctx → Stringctx: { login, token, title }
@ reg_page_login_help Json ctx → Stringctx: { data, title }
@ reg_readme_rewrite_links s html s name s version → StringRewrite the relative href="…"/src="…" targets in rendered README HTML onto /files/<name>/<version>/… (version-pinned, immutable-cacheable). md2html emits double-quoted attributes; anything else passes through.
@ reg_readme_html s md s name s version → StringMarkdown README → link-rewritten HTML fragment; "" stays "".
registry/extract.nu — read files out of a published .tar.gz in memory.
Powers the package page (README.md render, [package].repository from nurl.toml) and the /files/<name>/<version>/<relpath> asset route (README-referenced images served straight from the tarball). Nothing here touches the filesystem — gunzip + tar_parse over bytes.
@ reg_relpath_norm s rel → StringReject dotted / absolute / traversal segments; "" when unsafe, the normalized path otherwise. (Mirrors the Worker's normalizeRelPath.)
@ reg_targz_member ( Vec u ) gz s relpath → ( Vec u )Extract one member (path match after "./"-normalization, exact) from a gzipped tarball. Empty Vec when absent or the archive is malformed.
@ reg_targz_readme ( Vec u ) gz → StringRoot-level README.md (any case), as a String; "" when absent.
@ reg_targz_list ( Vec u ) gz → JsonEvery regular file in the tarball, in archive order, as a Json array [ { "path", "size" } ] for the files page. Empty array when the archive is malformed. tar_parse reconstructs USTAR prefix long paths already.
@ reg_targz_repository ( Vec u ) gz → String[package].repository from the tarball's root nurl.toml; "" when absent.
registry — the NURL package registry, served by NURL itself.
registry serve [--host H] [--port N] [--data DIR] [--workers N] [--log] [--tls-cert PEM --tls-key PEM] registry token new <login> [--data DIR]
serve speaks the exact wire protocol nurlpkg drives (index/tarball reads, bearer-authenticated publish/yank/revoke, search/stats) plus a server-rendered catalog UI. State: SQLite at <data>/registry.db + tarballs under <data>/pkgs (see src/db.nu, src/store.nu).
token new mints a publish token for a (created-on-first-use) local user and prints it ONCE — only the peppered SHA-256 hash is stored. The same pepper (REG_TOKEN_PEPPER) must be set for mint and serve.
Environment: REG_TOKEN_PEPPER token-hash pepper (deployment secret; "" works but leaves tokens brute-forceable from a DB copy) REG_BASE_URL public base URL (needed for GitHub OAuth redirect) GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET enable the /login OAuth flow (optional; without them /login explains local token minting)
@ main → iregistry/db.nu — SQLite persistence: the single source of truth.
Everything the registry knows lives here: users, tokens (peppered SHA-256 hashes only — never plaintext), package ownership, immutable versions with their tarball checksums, and per-version dependency requirements. The wire-facing index JSON (/index/<name>.json) is RENDERED from these tables on demand — there is no second copy to drift out of sync (the Cloudflare Worker this replaces dual-wrote an index object next to the DB; this design removes that hazard).
Connections are opened per request (WAL + busy timeout), which makes every handler thread-safe at any worker-pool size without a shared handle. Opening a SQLite database is microseconds; registry traffic doesn't notice.
All query helpers return sentinel values on failure (-1 / "" / F) and log nothing — the service layer decides what a failure means on the wire (usually a 500).
@ reg_db_open s path → !Database SqliteErrOpen (creating on first use), harden, and migrate the registry DB.
@ reg_db_user_by_login Database db s login → iUser id for login, or -1.
@ reg_db_user_ensure Database db s login i now → iCreate a local (non-GitHub) user; returns its id, or -1. Idempotent: an existing login is returned as-is.
@ reg_db_user_upsert_github Database db i github_id s login i now → iUpsert a GitHub identity (github_id is the stable key; login follows the GitHub account). Returns the user id, or -1.
: ~ i uid -1@ reg_db_login_of Database db i uid → Stringlogin of user uid; "" when absent.
@ reg_db_token_insert Database db i uid s token_hash s label i now → bStore a token hash for a user.
@ reg_db_auth Database db s token_hash i now → iResolve a token hash to its user id (or -1) and bump last_used_at.
@ reg_db_token_delete Database db s token_hash → bDelete the token row matching token_hash; T when one was deleted.
@ reg_db_pkg_owner Database db s name → iOwner user id of name, or -1 when the package doesn't exist yet.
@ reg_db_pkg_insert Database db s name i owner_uid i now → b@ reg_db_version_exists Database db s name s version → b@ reg_db_version_insert Database db s name s version s checksum i publisher_uid i now → b@ reg_db_dep_insert Database db s name s version s dep_name s dep_req → b@ reg_db_version_set_yanked Database db s name s version b yanked → bFlip the yanked flag; T when a row changed.
@ reg_db_deps_json Database db s name s version → JsonDependency array for one version: [ { "name": n, "req": r }, ... ]
@ reg_db_index_json Database db s name → StringThe package index document the client fetches at /index/<name>.json: { "name": ..., "versions": [ { version, checksum, yanked, deps } ] } "" when the package has no versions (the route then 404s).
@ reg_db_catalog_json Database db s q i limit → JsonCatalog rows as JSON: [ { "name": n, "version": latest-or-null } ] latest = most recently published non-yanked version (display only).
@ reg_db_stats_json Database db → String{ "packages": N, "versions": M } (non-yanked versions, like the Worker).
@ reg_db_pkg_detail_json Database db s name → JsonPackage detail for the HTML page: { "name", "owner", "versions": [ { "version", "yanked", "login", "date" } ], "latest": version-or-null } Versions newest-first (publication order, matching the Worker page). "date" is "YYYY-MM-DD" (UTC) from published_at — epoch seconds here.
: b _j ( json_obj_set root versions varr )registry/store.nu — input validation + the on-disk tarball store.
Layout under the data dir:
<data>/registry.db SQLite (see db.nu) <data>/pkgs/<name>/<name>-<version>.tar.gz
Tarball paths are BUILT from a validated (name, version) pair — no client-supplied path segment is ever joined onto the filesystem, so traversal is impossible by construction rather than by filtering.
@ reg_name_valid s name → bPackage name: ^[a-z0-9][a-z0-9_-]{0,63}$ (the registry convention the Worker enforced; nurlpkg lowercases on its side too).
@ reg_version_valid s version → bVersion: full semver incl. pre-release/build metadata — validated by actually parsing it with the same engine the resolver uses, not by a lookalike regex.
@ reg_tarball_path s data_dir s name s version → String<data>/pkgs/<name>/<name>-<version>.tar.gz — call only with a validated pair.
@ reg_tarball_write s data_dir s name s version ( Vec u ) bytes → bPersist a published tarball; T on success.
@ reg_tarball_read s data_dir s name s version → ( Vec u )Read a published tarball; empty Vec when absent.