Self-review of this branch caught a data-loss bug I introduced with the per-app / group files ceilings: the quota check ran AFTER the blob write. `write_atomic_at` renames the new bytes over the FINAL path, so by the time the ceiling refused an update the previous bytes were already gone. The per-app path then unlinked the blob (row survives, file destroyed — every read 404s); the group path left the new bytes in place under the old row's checksum (every read fails `Corrupted`). Either way a user permanently lost a file merely by exceeding a quota — a strictly worse outcome than the unchecked-update bypass the ceiling was added to close. The check now precedes the write on create AND update, per-app and group. As a bonus this stops an over-quota caller driving unbounded write+unlink disk churn: the ceiling now bounds I/O, not just stored bytes. The blob still goes down inside the transaction, under the advisory lock, so a rollback unlinks it and nothing can reference it in between. The original test passed against the bug: it asserted the refused update did not change the stored byte TOTAL — true, while the blob was already destroyed. The regression test now reads the file back through the checksum-verifying `FsFilesRepo::get`, and was confirmed to fail with `Corrupted` against the old ordering. Also in the KV writer (same file): drop the redundant pre-read on the hottest write path. `set`/`set_if` did a SELECT purely to learn whether the write would add a row, when the upsert already returns the previous value. Check after the insert instead (`>` not `>=`) and let the transaction roll back on refusal — identical outcome, one round-trip fewer, and an update pays nothing at all. `kv_repo::get_on` and `FsFilesRepo::final_path` are now unused and deleted; `FilesRepo::delete` delegates to the `delete_meta_on` + `unlink_blob` helpers rather than re-implementing them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PiCloud
A lightweight, self-hosted, event-driven serverless compute platform. Upload a Rhai script, get an HTTP endpoint. Designed to run on a single modest server with no idle CPU cost, and to scale out to a small cluster when you need it.
Status: Phase 1 — MVP scaffolding in progress.
The authoritative design lives in
serverless_cloud_blueprint.md.
Why
Existing serverless platforms are either cloud-locked, heavyweight, or both. PiCloud aims for the opposite end of the spectrum: one binary, one database, one reverse proxy — running on hardware you already own.
Architecture (one paragraph)
PiCloud splits into three logical services — manager (control plane: scripts, schedules, dashboard), orchestrator (per-node event ingress and dispatch), and executor (per-node Rhai sandbox) — each backed by a *-core Rust library. In MVP they run in a single process; in cluster mode they run as three binaries with one manager and one orchestrator + executor per node. Caddy fronts everything; PostgreSQL is the single source of truth.
See CLAUDE.md for working notes and serverless_cloud_blueprint.md for the full design.
Quick Start
Coming as scaffolding lands. For now:
# Rust toolchain (pinned via rust-toolchain.toml)
cargo check --workspace
# Run the all-in-one MVP binary (once main.rs is wired up)
cargo run -p picloud
Repository Layout
crates/
shared/ cross-cutting types
executor-core/ Rhai engine + sandbox
orchestrator-core/ event ingress, dispatch
manager-core/ control plane
picloud/ MVP all-in-one binary
picloud-{manager,orchestrator,executor}/ cluster-mode binaries (skeleton)
dashboard/ SvelteKit
caddy/ Caddyfile
docker/ Dockerfiles
docs/
git-workflow.md Trunk-based workflow
Contributing
See docs/git-workflow.md for the branching and commit conventions. TL;DR: trunk-based, short-lived branches, Conventional Commits, no force-pushing main.
License
TBD.