AkurAI Build
Menu

AkurAI-Build

public
0cf28150faedd8c150474a4f16be4c9922c04f56 43 commits 27 branches 10 tags
README.md

bunfork

bunfork is a Linux-first Rust web runtime with one CLI for verified static artifacts, server-rendered MiniJinja pages, SQLCipher migrations/backups, and bounded exhaustive vector search.

It hosts prebuilt static output from documented Svelte, SvelteKit, Next.js, Vue, Nuxt, and Vite configurations. It is not source-, build-, adapter-, or server-runtime-compatible with those frameworks: Bunfork does not embed JavaScript, Node, V8, JavaScriptCore, RSC, Nitro, or a framework compiler. Browser JavaScript in an admitted artifact is served byte-for-byte and runs only in the browser.

The default listener is 0.0.0.0:3100; port 3000 is never used by default.

Quick starts

Build Bunfork (Rust 1.89 or newer, edition 2024):

cargo build --release --locked

Run the native MiniJinja/vector application:

./target/release/bunfork keygen
./target/release/bunfork keygen --out .bunfork.token
./target/release/bunfork migrate
./target/release/bunfork dev

The database key and API token must decode to different 32-byte secrets; case-only variations of the same hexadecimal key are rejected.

Run a prebuilt static export with no database, key, token, migration, tenant, or model state:

./target/release/bunfork admit \
  --artifact frontend/dist \
  --manifest bunfork-static.json \
  --mode spa \
  --fallback index.html \
  --trailing-slash ignore

./target/release/bunfork doctor \
  --static frontend/dist \
  --manifest bunfork-static.json

./target/release/bunfork serve \
  --static frontend/dist \
  --manifest bunfork-static.json

One CLI

Global data options precede the command:

OptionEnvironmentDefault
--database <PATH>BUNFORK_DBdata/bunfork.db
--key-file <PATH>BUNFORK_KEY_FILE.bunfork.key
--tenant <ID>BUNFORK_TENANTdefault
--model <ID>BUNFORK_MODELdefault
<!-- markdownlint-disable MD013 -->
CommandPurpose
keygen [--out <PATH>]Create a private 256-bit secret.
build --run <PROGRAM> --arg <ARG>... --output <DIR> --manifest <FILE> [artifact options] [--json]Run one explicit argv build command with a reduced environment, then admit its output. No shell or framework detection is inserted.
admit --artifact <DIR> --manifest <FILE> [--mode mpa|spa] [--base /] [--fallback FILE] [--trailing-slash ...] [--json]Scan a completed static directory and atomically write its deterministic manifest.
doctor --static <DIR> --manifest <FILE> [--json]Verify every file, byte length, SHA-256 digest, and manifest invariant offline. Exit code 3 means verification failed; the report names the first divergent file.
dev [native server options]Rescan native pages and re-read public assets per request. This is reload, not HMR.
dev --static <DIR> --manifest <FILE>Serve a frozen artifact without data state; rebuild/restart to see changes.
serve [native server options]Serve native pages and vector APIs. start is an alias.
serve --static <DIR> --manifest <FILE>Verify, preload, and serve a static artifact without opening SQLCipher.
migrateApply embedded migrations transactionally and verify the exact schema.
backup [--out <PATH>] [--json]Create, publish, and verify an encrypted no-overwrite backup.
restore <BACKUP> --yesVerify and restore a backup while preserving the previous live database.
vector put|search|delete ...Manage tenant/model-scoped encrypted vectors.
test [--pages <DIR>] [--public <DIR>]Run toolchain-free route, migration, vector, and backup smoke checks against a throwaway encrypted database. cargo test runs in CI and during deploy.
deploy [--out <DIR>] [--force]Build a digest-owned native bundle; default output is bunfork-dist.
deploy --artifact <DIR> --static-manifest <FILE> [--out <DIR>] [--force]Build a digest-owned static bundle with the Bunfork binary and systemd units.
<!-- markdownlint-enable MD013 -->

Secret files take precedence over BUNFORK_DB_KEY and BUNFORK_API_TOKEN environment values.

bunfork-static-v1

Admission accepts an immutable directory of ordinary browser files and writes a strict manifest:

{
  "schema": "bunfork-static-v1",
  "root": ".",
  "mode": "mpa",
  "base": "/",
  "fallback": null,
  "trailing_slash": "directory",
  "precompressed": true,
  "immutable": true,
  "files": {
    "index.html": {
      "sha256": "…64 lowercase hexadecimal characters…",
      "bytes": 1234
    }
  }
}

Unknown/duplicate fields, extra/missing/mutated files, symlinks, hard links, special files, hidden/sensitive paths, malformed URLs, and traversal fail closed. Scans are bounded to 50,000 files, 256 MiB per file, and 512 MiB total. Serving preloads the entire verified artifact after re-hashing, so admission and serving share one bound and post-bind filesystem swaps are eliminated.

Static HTTP behavior includes base paths, directory routes, .html exports, MPA 404 pages, explicit SPA fallbacks, asset/API fallback exclusions, GET/HEAD/405, ETags/304, conservative document caching, immutable caching only for semantic SvelteKit/Next namespaces, and quality-aware .br/.gz negotiation. Uncertain Vite/Nuxt/custom assets revalidate instead of relying on filename guesses. trailing_slash selects the canonical URL shape: directory redirects /guide to /guide/, redirect canonicalizes /guide/ to /guide, and ignore serves both without redirects. /_bunfork/health and /_bunfork/ready are reserved.

Producer configurations:

<!-- markdownlint-disable MD013 -->
ProducerAdmitted outputBunfork mode
Svelte or Vue with Vitevite build output, normally distspa --fallback index.html --trailing-slash ignore for a client router; otherwise mpa
SvelteKitadapter-static output, normally buildmpa; use spa only with an explicit adapter fallback
Next.jsoutput: 'export' output, normally out; trailingSlash: true is recommendedmpa --trailing-slash directory
Nuxtnuxt generate / prerendered .output/publicmpa, or explicit spa for a client-only fallback export
<!-- markdownlint-enable MD013 -->

.next/server, Next standalone, SvelteKit adapter-node, Vite SSR, and Nuxt/Nitro server output are rejected scope, not partially emulated features.

Native Rust application contract

Bunfork exposes a compiled-in Rust application contract in bunfork::app. Applications provide an ordinary static route table implementing App; routes may register Rust loaders and actions without procedural macros, dynamic plugins, JavaScript, or framework adapters. RequestContext provides bounded request data, parsed query/cookies/form values, and an optional encrypted Store. validate_csrf and constant-time token comparison are provided for state-changing actions. Static artifacts never invoke this contract.

This is a library API for applications compiled into the Bunfork binary; the CLI does not discover or execute arbitrary application binaries.

Native pages and assets

Only .html files below app/pages become routes. Native pages may not use /api, /assets, or /_bunfork prefixes; those namespaces belong to Bunfork's fixed HTTP endpoints and asset server.

<!-- markdownlint-disable MD013 -->
FileRoute
index.html/
about.html/about
notes/[slug].html/notes/:slug
docs/[...path].htmlrequired terminal catch-all
docs/[[...path]].htmloptional terminal catch-all
(site)/about.html/about; route groups do not enter the URL
<!-- markdownlint-enable MD013 -->

Repeated parameters, ambiguous routes, invalid templates, hidden entries, links, and special files fail startup/deploy. Production pages are compiled and loaded before binding, so later path swaps cannot expose files.

Templates in app/templates support MiniJinja inheritance/includes/macros. The recommended role-focused layout is layouts/ for inherited shells, partials/ for included fragments, and macros/ only when reuse warrants it. the sample uses base.html. _404.html and _500.html are optional error pages. Pages are registered into the same MiniJinja environment under generated page:<pattern> names. A shared template whose path resolves to one of those generated names fails startup with the colliding name rather than being silently overwritten. Pages receive auto-escaped params, bounded multi-value query, pathname, tenant, and model; the included /notes/:slug demo visualizes the matched path, extracted slug, and repeated query values. Files in public are validated, hashed, and preloaded before bind, then served below /assets without request-time filesystem reads.

Ponytail UI foundation

The sample native application includes public/ponytail.css, a clean-room, CSS-first layer with semantic tokens, light and dark modes, four accent palettes, composition primitives, and a complete token-driven skin for native controls and states. It is inspired by Ponytail's platform-first discipline and modern design-system research; it is not a Tailwind CSS fork or compatibility layer and requires no frontend build step.

Open /docs/design-system for the token and usage guide and /examples/components for 20 live component genres. Contributor-facing design rules live in DESIGN.md; the pinned source, license, and behavior matrix lives in docs/ui-foundation-research.md.

Vector API

GET /api/health, /api/ready, /_bunfork/health, and /_bunfork/ready are public operational probes. The two health aliases identify the running service and build, with whole-second uptime:

{"status":"ok","service":"bunfork","version":"...","uptime_seconds":42}

The ready aliases return {"message":"ok"} after startup. Native readiness checks the open encrypted database and returns HTTP 503 with {"message":"unavailable"} when that check fails; static readiness has no database dependency. Vector endpoints require Authorization: Bearer <API_TOKEN>:

PUT    /api/vectors/note-1
       {"id":"note-1","content":"hello","embedding":[1,0,0]}

POST   /api/vectors/search
       {"embedding":[1,0,0],"limit":5}

DELETE /api/vectors/note-1

Invalid identifiers, dimensions, norms, limits, and budgets are client errors rather than HTTP 500s. The HTTP 400 body carries the descriptive validation message (for example, which capacity or budget was reached), not a generic string. The exhaustive cosine-similarity scan is bounded to 4,096 dimensions, 10,000 candidates, 10,000,000 multiply-adds, 25 results, and 64 KiB content. JSON bodies are capped at 1 MiB. Requests time out after 30 seconds with HTTP 408. Each listener serves at most 1,024 concurrent requests and 1,024 concurrent connections, and a matching header-read timeout closes slow-header or idle keep-alive sockets. Set BUNFORK_REQUEST_TIMEOUT_MS to tune that deadline. Bunfork does not load SQLite extensions or claim ANN/full-text search.

Encrypted data and recovery

SQLite is encrypted with bundled SQLCipher. Migrations are embedded and transactional; validation checks application/user versions, the exact migration ledger, exact table/index SQL, and rejects unexpected schema objects.

Backups use the same key as the live database; they are not key rotation. Backup/restore pin file identity with no-follow descriptors, reject links, sync publication, verify the exact encrypted schema, and preserve a timestamped pre-restore database. Stop the server before restore. Same-UID hostile races remain outside the filesystem guarantee; isolate untrusted code under another OS account/container.

Deployment

deploy tests, release-builds, stages, hashes, fsyncs, and atomically installs bunfork-dist. bunfork.json uses the bunfork-deployment-v2 schema and covers every bundled file except itself. --force validates that complete inventory before replacement. Hidden/protected outputs—including .git, .refrepos, app, src, data, backups, deploy, migrations, public, and target—are always refused. The prior verified release is preserved as a sortable bunfork-dist.previous-<unix-timestamp>-<pid> sibling and never automatically deleted; prune old siblings manually when disk space matters.

Native runtime state belongs outside the release:

./bunfork-dist/bunfork \
  --database "$HOME/.local/share/bunfork/bunfork.db" \
  --key-file "$HOME/.config/bunfork/db.key" \
  serve --token-file "$HOME/.config/bunfork/api.token"

Static bundles run from any working directory:

./bunfork-dist/bunfork serve \
  --static ./bunfork-dist/site \
  --manifest ./bunfork-dist/bunfork-static.json

Docker deployment verification

tests/docker_e2e.sh is an opt-in black-box lifecycle test for a native bunfork deploy output. It builds a throwaway non-root runtime image from the completed deployment (never from the source checkout), lets Docker allocate a free loopback port, verifies HTTP readiness/pages/assets/404, captures and validates a 1440×900 browser screenshot, checks the image entrypoint/user/port contract and rejects browser or agent executables in that runtime image. It then removes only its exact container, temporary image, build context, and release directory. Screenshots and inspection JSON remain under the ignored target/docker-e2e/ for review.

It requires Docker, Curl, Python, and a headless Chromium-compatible binary:

BUNFORK_DOCKER_E2E=1 \
  BUNFORK_DOCKER_E2E_BROWSER=chromium \
  tests/docker_e2e.sh

The included native/static user units use %h/.local/lib/bunfork, 0.0.0.0:3100, and systemd filesystem/process hardening. Bunfork does not provision hosts, upload releases, configure DNS/TLS, or delete prior releases.

Security boundaries

  • 0.0.0.0:3100 is intentionally LAN-visible. Firewall it and use a trusted TLS reverse proxy before internet exposure; Bunfork has no built-in TLS.
  • Bearer and decoded-secret comparisons are constant-time. Browser mutations with Origin must match the configured public origin. Without one, the Host-header fallback applies only to loopback listeners running bunfork dev (development mode). Origin-bearing mutations are otherwise rejected, including loopback bunfork serve binds behind a same-host reverse proxy, where the proxy typically forwards the client-supplied Host and both headers are attacker-supplied end-to-end. The bearer token remains primary API authorization.
  • Native CSP is strict. Static CSP permits same-origin code plus inline scripts/styles because official SvelteKit/Next exports require bootstrap inline code. Use a reverse proxy with build-specific CSP hashes/nonces for a stricter policy.
  • Static mode has no data/secret dependency. A server-side foreign runtime is not supervised because a same-UID process could read Bunfork keys/database regardless of environment filtering.
  • Encryption does not protect a compromised live process, service account, root user, TLS proxy, binary, or unlocked key. There are no user accounts, roles, distributed locks, remote backup transport, or Windows hardening guarantees.

Verification and references

Ten read-only Codex CLI red-team passes and the final no-ship checklist are preserved in docs/adversarial. The P0 findings drove digest-owned deploys, secret byte comparison, descriptor-pinned recovery, strict artifact admission, and listener-level regression tests.

A second ten-architect architecture review, its per-commit remediation map, the consciously deferred backlog, and the bunfork-static-v1 schema evolution policy are preserved in docs/architecture-review and docs/static-schema-evolution.md.

Committed listener-level tests start the real binary in both static and native modes. Native coverage includes pages/assets, probes, authentication/origin checks, vector mutations/search, and clean SIGTERM shutdown. Exact package versions and live results for official Svelte/Vite, SvelteKit, Next, Vue/Vite, and Nuxt builds are recorded in docs/framework-e2e.md.

Pinned research checkouts under .refrepos/ are excluded from compilation/deployment:

RepositoryCommitBehavior studied
Bunaaaa53f1compact CLI/runtime boundaries
Denoc537e012artifact/runtime distinctions and shutdown
Svelteb4d1583ae20fcompiled browser artifact boundary
SvelteKit5c38e515db7fadapter-static, fallbacks, routes
Next.js1bd2fd585aacexport/standalone split and static routing
Vuec0606e91798cSFC compiler versus browser output
Nuxt33f0fdb9d208generated public versus Nitro output
Vitec961cae2868cstatic output, base paths, hashed assets
Rari373fa706Rust routing versus V8/RSC boundary
Leptosf81ffcd5request-scoped Rust SSR patterns
Dioxuse1c63426deterministic routes and full-stack boundaries
Andromeda0ef48722Rust runtime and SQLite boundaries
Ponytail14a0d795minimal implementation discipline

Bunfork is MIT licensed; see LICENSE and THIRD_PARTY_NOTICES.md.