ddidderr cd50f78854 fix(peer): bound local library ingestion
Cap index reads, games, archive fingerprints, selected-root entries, recursive depth and bytes, scan time, and version.ini reads. Failed rescans retain the prior complete index and revision instead of publishing partial state.

Test Plan:
- just test
- just clippy
- focused oversize index, traversal, and prior-snapshot tests
- git diff --check
2026-09-12 13:00:04 +02:00
2026-09-02 22:08:37 +02:00
2026-09-02 22:08:37 +02:00
2026-09-02 22:08:37 +02:00

lanspread

Peer-to-peer game library sharing for LAN parties. Peers discover each other on the local network via mDNS, exchange library metadata over QUIC, and let users browse and download games from each other. Ships as a Tauri desktop app.

Peer identity and protocol status

Each desktop installation creates and reuses one Ed25519 identity in its Tauri app-data directory. Its PeerId is lowercase unpadded RFC 4648 base32 of BLAKE3 over the certificate's complete canonical DER SubjectPublicKeyInfo; certificates and private keys are never distributed from the repository. Outbound QUIC connections authenticate the responder against the exact expected PeerId. Wire protocol 8 (lanspread/8) is the only supported protocol. Peers exchange responder-owned, revisioned snapshots over identity-pinned endpoints; file and streamed-install requests name the catalog's exact ContentId. There are no legacy wire fallbacks or repository-shared identity keys.

First-time setup

Install Rust, Deno, and just first, then bootstrap the project:

just setup

That installs the Tauri CLI and the locked frontend dependencies. All routine repository work should go through just; the recipes carry the catalog mode and Tauri resource configuration needed to keep fixture authority out of production builds.

Daily development

Run the desktop app with the checked-in test catalog:

just run

Build a launcher from the already-published production catalog without creating an installer:

just build

Generate the production catalog from a package tree when necessary, then build the same no-bundle launcher:

just build-production /srv/lanspread/packages

To make the default just command use a real package tree, set the directory before invoking it. The command generates or reuses the production catalog and then runs the launcher with the production resource map:

LANSPREAD_GAMES_DIR=/srv/lanspread/packages just
# Equivalent Just-variable form:
just --set GAMES_DIR /srv/lanspread/packages

The fixture-only paths remain explicit:

just run-fixture
just build-fixture

Run the standard checks before committing:

just fmt
just clippy
just test
just frontend-test

just run uses the fixture path only when LANSPREAD_GAMES_DIR is unset. just build always uses the production resource map and production profile; use just build-fixture when a fixture build is intended. Tauri's build script still rejects fixture resources unless the explicit fixture opt-in is present.

Catalog checksums and production manifests

Lanspread does not trust file descriptions received from peers. The release catalog contains the expected bytes for each game version. The catalog publisher derives that authority from the canonical game packages and writes:

  • one <game_id>.json manifest containing every ordinary file's size, full-file BLAKE3, and BLAKE3 for each 128 MiB transfer chunk;
  • BLAKE3 and size for every file obtained by extracting the root-level .eti archives, which authorizes Stream Install output; and
  • catalog-content-index-v1.jsonl, which binds every catalog game/version to its complete manifest ContentId and Stream Install capability.

The ContentId is a digest of the canonical manifest transcript, not merely a hash of one archive. Never calculate or edit these values by hand.

Prepare the package tree

Place the canonical packages under one directory, with each direct child named exactly like the game_id in the production game.db:

/srv/lanspread/packages/
├── alienswarm/
│   ├── version.ini
│   ├── alienswarm.eti
│   └── ...
├── bf1942/
│   ├── version.ini
│   ├── bf1942.eti
│   └── ...
└── ...

Each version.ini must match that game's game_version in game.db. Package entries must be regular files/directories with canonical portable names; links, reparse points, special files, aliases, unsafe archive output, or changing bytes make generation fail closed.

The default publisher uses the checked-in Linux unrar sidecar. Select another trusted executable when generating on a different supported host:

LANSPREAD_UNRAR=/absolute/path/to/unrar \
  just catalog-generate-production /srv/lanspread/packages

Generate and verify all checksums

Generate the complete production manifest set:

just catalog-generate-production /srv/lanspread/packages

The --all recipe keeps a local metadata-only stamp in .lanspread/catalog-cache/production.json. It compares the package tree's paths, entry types, sizes, and nanosecond mtimes, plus the catalog database and unrar metadata. If those values and the published output still match, the expensive package hashing and extraction step is skipped. This is only a performance cache: catalog-check-production and every production build still validate the catalog authority. Force a refresh when needed:

LANSPREAD_CATALOG_FORCE=1 just catalog-generate-production /srv/lanspread/packages

The publisher first checks every selected package's version.ini against game.db, then independently reads and hashes each selected package twice before publication. Under a durable publication marker, it atomically replaces each manifest and writes the complete content index last. Interruption leaves the marker in place so later checks and builds fail closed instead of accepting a mixed generation.

Verify the published database/index/manifest set without rereading the package corpus:

just catalog-check-production

This is a packaging integrity check, not a substitute for retaining the canonical packages and the successful generation log as provenance. The output prints the game_id, game_version, and content_id for every checked game.

Regenerate one changed game

After a complete manifest set exists, update one game without rehashing every package:

just catalog-generate-production-game /srv/lanspread/packages alienswarm
just catalog-check-production

Incremental generation refuses to bootstrap a partial catalog. Under its durable publication marker it revalidates every unselected manifest/index identity, rebuilds the complete index, and then publishes the selected game. Use the full generation command for a new catalog or any intentionally broad catalog change.

Build a production bundle

Only after the production check succeeds, create the installer/bundle:

just bundle

just bundle repeats the production catalog gate before Tauri packaging. It never falls back to fixture manifests. This checkout does not include the canonical 186-game package corpus, so the command intentionally fails until that external corpus has been processed into crates/lanspread-tauri-deno-ts/src-tauri/manifests/.

Fixture catalogs and peer-CLI acceptance

The committed fixture profiles are test authority only:

just fixture-catalog-check       # default GUI/CLI profile
just fixture-catalogs-check      # default + solid + multi + unknown profiles
just fixture-catalogs            # explicitly regenerate all committed profiles

The solid, multi-archive, and unknown-game profiles intentionally describe different package bytes, so the peer-CLI matrix checks all of them before building its Docker image. Normal GUI development needs only the default profile.

Build or run the JSONL peer harness:

just peer-cli-build
just peer-cli-image
just peer-cli-tests S1 S2 S3
just peer-cli-run alpha

Run the entire S1-S49 acceptance matrix by omitting scenario arguments:

just peer-cli-tests

For an interactive three-peer session, start these in separate terminals:

just peer-cli-alpha
just peer-cli-bravo
just peer-cli-charlie

Important just commands

Command Purpose
just setup Install the Tauri CLI and frontend dependencies.
just / just run Run fixtures, or real data when LANSPREAD_GAMES_DIR is set.
just run-fixture Check the default fixture catalog and run the GUI.
just build Validate production authority and build without bundling.
just build-production PACKAGES_DIR Generate/reuse production authority and build without bundling.
just build-fixture Check the default fixture catalog and build without bundling.
just bundle Check production authority and build production bundles.
just catalog-generate-production PACKAGES_DIR Hash, cache, and publish the complete production package corpus.
just catalog-generate-production-game DIR GAME_ID Safely regenerate one game in an already complete catalog.
just catalog-check-production Validate all production database/index/manifest artifacts.
just fixture-catalog-check Validate the default development fixture catalog.
just fixture-catalogs-check Validate every peer-CLI fixture profile.
just fixture-catalogs Regenerate all committed fixture profiles from their packages.
just fmt Format Rust, TOML, Markdown, and the Justfile.
just clippy Lint every Rust workspace target with warnings denied.
just test Run every Rust workspace test target.
just frontend-test Run the frontend reducer/lifecycle tests.
just peer-cli-build Build the JSONL peer harness.
just peer-cli-image Check fixture authority and build the harness Docker image.
just peer-cli-tests [SCENARIO ...] Run selected scenarios, or the complete matrix when no IDs are supplied.
just peer-cli-run NAME Start one persistent-state interactive harness peer.
S
Description
No description provided
Readme
157 MiB
Languages
Rust 73.8%
TypeScript 10.8%
Python 5.4%
JavaScript 5.2%
CSS 3.7%
Other 1%