docs: user-facing documentation and signing key
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Portable prefix bundles
|
||||
|
||||
## Status and objective
|
||||
|
||||
The first delivery is implemented: versioned export, inspection, full verification
|
||||
and non-destructive transactional import are available from the CLI and shared
|
||||
core. Replacement, optional components and GUI integration remain future phases.
|
||||
|
||||
The Wine prefix is the primary artefact, but a useful recovery package must also
|
||||
record enough Runi configuration and runtime metadata to restore that prefix
|
||||
on another Linux installation, including a Steam Deck, without assuming identical
|
||||
filesystem paths.
|
||||
|
||||
The intended CLI is:
|
||||
|
||||
```console
|
||||
runi bundle export bionic-commando-rearmed \
|
||||
--output bionic-commando-rearmed.pfy
|
||||
|
||||
runi bundle inspect bionic-commando-rearmed.pfy
|
||||
runi bundle verify bionic-commando-rearmed.pfy
|
||||
|
||||
runi bundle import bionic-commando-rearmed.pfy \
|
||||
--exe "/path/to/bcr.exe"
|
||||
```
|
||||
|
||||
## Bundle contents
|
||||
|
||||
A bundle has this logical layout:
|
||||
|
||||
```text
|
||||
manifest.toml
|
||||
checksums.sha256
|
||||
game/config.toml
|
||||
prefix/
|
||||
notes/ # optional
|
||||
cover/ # optional
|
||||
recipe-state/ # optional, when applicable
|
||||
```
|
||||
|
||||
`manifest.toml` records:
|
||||
|
||||
- bundle format version and creating Runi version;
|
||||
- creation time, source operating system and architecture;
|
||||
- game ID and UID;
|
||||
- backend and runtime GameID;
|
||||
- Proton name, source and build fingerprint;
|
||||
- original executable path, for information only;
|
||||
- included components;
|
||||
- total uncompressed size and file count;
|
||||
- external requirements and compatibility warnings.
|
||||
|
||||
`checksums.sha256` covers every payload file. It detects corruption but is not an
|
||||
authenticity mechanism: signatures belong to a later phase if bundles are ever
|
||||
published or exchanged through an untrusted channel.
|
||||
|
||||
The following are excluded by default:
|
||||
|
||||
- the game installation;
|
||||
- Proton itself;
|
||||
- logs and caches;
|
||||
- installers or recipe assets without clear redistribution rights;
|
||||
- saves stored outside the prefix.
|
||||
|
||||
These exclusions keep the bundle focused, avoid licensing problems and prevent
|
||||
machine-local or disposable data from silently becoming portable state.
|
||||
|
||||
## Archive format
|
||||
|
||||
The payload must use TAR rather than ZIP. Wine prefixes contain symbolic links,
|
||||
permissions and other Unix filesystem metadata which ZIP does not reliably round
|
||||
trip.
|
||||
|
||||
The first format version uses:
|
||||
|
||||
- the `.pfy` extension;
|
||||
- a versioned manifest independent of its container compression;
|
||||
- TAR with gzip as the baseline available in Python 3.11's standard library.
|
||||
|
||||
Zstandard can be added later as another encoding without changing the logical
|
||||
bundle format. Import must identify the actual format from its header, not trust
|
||||
the filename alone.
|
||||
|
||||
## Current CLI
|
||||
|
||||
```console
|
||||
runi bundle export <game-id> --output game.pfy
|
||||
runi bundle inspect game.pfy
|
||||
runi bundle verify game.pfy
|
||||
runi bundle import game.pfy [--exe /new/path/game.exe] [--as new-id]
|
||||
```
|
||||
|
||||
Export refuses an existing destination and an apparently active prefix. `--force`
|
||||
only overrides the active-prefix check; it never overwrites a destination. Import
|
||||
refuses existing registrations and prefixes. There is deliberately no `--replace`
|
||||
until the preservation-and-rollback phase is implemented.
|
||||
|
||||
## Consistent export
|
||||
|
||||
Export is a snapshot operation, not an ordinary recursive copy:
|
||||
|
||||
1. Load the registered game and resolve its prefix.
|
||||
2. Confirm that the prefix exists and passes basic structural checks.
|
||||
3. Detect Wine/Proton processes using that prefix and refuse while it is active.
|
||||
`--force` can explicitly override this consistency protection.
|
||||
4. Estimate required space and create the archive as a temporary sibling of the
|
||||
requested destination.
|
||||
5. Archive filesystem entries without following symlinks.
|
||||
6. Calculate the manifest and payload hashes.
|
||||
7. Re-open and verify the completed temporary archive.
|
||||
8. Flush it and atomically rename it to the final destination.
|
||||
|
||||
A failed or interrupted export must never appear under the requested final name.
|
||||
Runi launches should take an advisory per-prefix lock; the process scan also
|
||||
covers games started outside the current Runi process. This reduces races,
|
||||
although a generic filesystem snapshot cannot guarantee application consistency
|
||||
when an uncooperative external Wine process is still writing.
|
||||
|
||||
## Inspection and verification
|
||||
|
||||
`bundle inspect` reads only metadata and reports contents, requirements, sizes and
|
||||
compatibility information without extracting the prefix.
|
||||
|
||||
`bundle verify` validates:
|
||||
|
||||
- container readability and format version;
|
||||
- required manifest fields;
|
||||
- unique, normalized archive paths;
|
||||
- declared size and file-count limits;
|
||||
- payload membership against the manifest;
|
||||
- every SHA-256 digest.
|
||||
|
||||
Both commands must work without a registered game or installed Proton version.
|
||||
|
||||
## Transactional import
|
||||
|
||||
Import is split into four explicit stages:
|
||||
|
||||
```text
|
||||
open -> validate -> stage -> publish
|
||||
```
|
||||
|
||||
1. Open and fully validate the bundle before modifying managed state.
|
||||
2. Extract into a newly-created staging directory inside the destination prefix
|
||||
root, so the eventual rename remains on the same filesystem.
|
||||
3. Validate the staged prefix and construct the destination configuration.
|
||||
4. Atomically rename the staged prefix into place.
|
||||
5. Persist the game configuration last.
|
||||
|
||||
On failure, only staging data is removed. Existing configuration and prefixes are
|
||||
left untouched.
|
||||
|
||||
Archive extraction is implemented entry by entry. It rejects absolute paths, `..`
|
||||
traversal, device nodes, FIFOs, duplicate paths, member paths descending through
|
||||
symlinks, unsafe hardlinks, unexpected entry types and excessive expansion.
|
||||
Symlink targets themselves are preserved without following them: Wine prefixes
|
||||
legitimately contain external mappings such as `dosdevices/z:` pointing at `/`.
|
||||
All directories and regular files are created before symlinks, so archive writes
|
||||
cannot traverse those mappings during extraction.
|
||||
The standard library's unrestricted `extractall()` is not an acceptable trust
|
||||
boundary.
|
||||
|
||||
## Conflict and replacement policy
|
||||
|
||||
The default is non-destructive:
|
||||
|
||||
```text
|
||||
existing registration -> abort
|
||||
existing prefix -> abort
|
||||
occupied game ID -> require --as <new-id>
|
||||
```
|
||||
|
||||
An explicit future `--replace` mode must:
|
||||
|
||||
1. validate and stage the incoming bundle completely;
|
||||
2. export or otherwise preserve the current registered state;
|
||||
3. move the current prefix aside rather than delete it;
|
||||
4. publish the incoming prefix and configuration;
|
||||
5. roll back the old state if publication fails.
|
||||
|
||||
The safety snapshot's location and retention policy must be visible to the user.
|
||||
No overwrite flag may turn validation off.
|
||||
|
||||
## Portability rules
|
||||
|
||||
Machine-specific absolute paths cannot be restored blindly:
|
||||
|
||||
- the prefix is imported under the destination machine's configured
|
||||
`prefixes_root`;
|
||||
- the imported `runtime.prefix` is cleared when the bundle represented a managed
|
||||
prefix, allowing normal XDG resolution on the destination;
|
||||
- the original executable path is treated only as a candidate;
|
||||
- `--exe` supplies the new path when the original executable is unavailable;
|
||||
- a default working directory tied to the old executable is recalculated;
|
||||
- explicitly independent working directories require confirmation or replacement;
|
||||
- the required Proton build is matched by name, source and fingerprint;
|
||||
- a missing Proton build leaves a valid imported environment in a clear
|
||||
"restored but not ready" state with concrete remediation instructions.
|
||||
|
||||
Import must not perform speculative rewrites inside the Wine registry. Any future
|
||||
registry migration must be narrow, declarative and auditable because an incorrect
|
||||
rewrite can destroy the state the bundle exists to preserve.
|
||||
|
||||
Game UID handling must be explicit. A normal recovery keeps the original UID. An
|
||||
import using `--as` creates a distinct registration and therefore needs a new UID,
|
||||
while retaining the source UID in import provenance rather than pretending both
|
||||
registrations are the same object.
|
||||
|
||||
## Core architecture
|
||||
|
||||
This is a separate subsystem from `core/backup.py`. That existing module protects
|
||||
individual files modified inside a game installation; it does not create or
|
||||
restore complete environment snapshots.
|
||||
|
||||
Proposed package:
|
||||
|
||||
```text
|
||||
src/runi/core/bundles/
|
||||
__init__.py
|
||||
models.py # BundleManifest and BundleComponent
|
||||
archive.py # safe TAR writing and extraction
|
||||
integrity.py # SHA-256 generation and verification
|
||||
compatibility.py # destination readiness and Proton matching
|
||||
exporter.py # snapshot orchestration
|
||||
importer.py # staging, publication and rollback
|
||||
|
||||
src/runi/cli/commands/bundle.py
|
||||
```
|
||||
|
||||
The core API should remain presentation-independent:
|
||||
|
||||
```python
|
||||
export_bundle(game, destination, options, on_progress=None)
|
||||
inspect_bundle(source)
|
||||
verify_bundle(source, on_progress=None)
|
||||
import_bundle(source, options, on_progress=None)
|
||||
```
|
||||
|
||||
Core functions raise typed `RuniError` subclasses and never print, prompt or
|
||||
exit. CLI and GUI adapters own user interaction. Long export, hash and extraction
|
||||
operations report through the existing progress callback abstraction.
|
||||
|
||||
## Delivery phases
|
||||
|
||||
1. ✅ Versioned manifest, safe archive primitives, export, inspect and verify.
|
||||
2. ✅ Transactional import with no replacement support.
|
||||
3. ✅ Cross-machine executable override and Proton fingerprint/readiness reporting.
|
||||
4. Explicit `--replace` with automatic preservation and rollback.
|
||||
5. Required game notes plus optional custom cover and recipe-state components.
|
||||
6. GUI integration and a visible bundle/history view.
|
||||
7. Cryptographic signing only if an untrusted sharing channel creates that need.
|
||||
|
||||
The implemented first delivery remains deliberately narrow: a reliable prefix plus
|
||||
configuration round trip through `export`, `verify` and non-destructive `import`.
|
||||
|
||||
## GUI design checkpoint (recorded, not approved for implementation)
|
||||
|
||||
Do not start the bundle GUI until its workflow and the remaining core contracts
|
||||
below have been decided. The CLI implementation is not, by itself, a sufficient
|
||||
UX specification for multi-gigabyte interactive operations.
|
||||
|
||||
### Export workflow
|
||||
|
||||
Decide whether export is exposed in the game context menu, Overview, or both; the
|
||||
default filename and destination; which optional components can be selected; how
|
||||
estimated size and free space are shown; and how an active prefix is handled. The
|
||||
GUI should probably not expose the consistency-bypassing `--force` option in its
|
||||
first version.
|
||||
|
||||
Prefix, game configuration and game notes remain mandatory for any bundle
|
||||
presented as a complete per-game backup. Custom cover and recipe state are
|
||||
candidates for explicit optional components once supported by the core. Until
|
||||
the implemented bundle format carries notes, the UI and documentation must
|
||||
describe it as a prefix/config portability bundle rather than a complete backup
|
||||
of the game's Runi knowledge.
|
||||
|
||||
### Import workflow
|
||||
|
||||
Import belongs to the library/Add Game flow because the game may not be registered
|
||||
yet. The proposed wizard is:
|
||||
|
||||
1. Select a `.pfy` file.
|
||||
2. Verify structure and integrity.
|
||||
3. Review game identity, creation date, size and required Proton build.
|
||||
4. Select or confirm the executable on the destination machine.
|
||||
5. Resolve the destination ID and paths.
|
||||
6. Review the complete operation.
|
||||
7. Import transactionally.
|
||||
8. Show readiness and any missing requirements.
|
||||
|
||||
The initial GUI should resolve an occupied ID with only **Import as Copy** or
|
||||
**Cancel**. It must not offer Replace until core replacement has automatic
|
||||
preservation and rollback.
|
||||
|
||||
### Suggested entry points
|
||||
|
||||
- Game context menu and/or Overview: **Export Bundle…**
|
||||
- Library **Add Game** menu: **Import Bundle…**
|
||||
- A global utility action: **Inspect/Verify Bundle…**
|
||||
|
||||
Inspect/Verify does not need to appear inside a registered game's Overview because
|
||||
it operates on an arbitrary bundle file, not on that registration.
|
||||
|
||||
### Progress and cancellation prerequisites
|
||||
|
||||
The current core reports phase-level progress only. Before GUI work, extend its
|
||||
contract to provide file/byte totals and cooperative cancellation for hashing,
|
||||
compression, verification and extraction. Cancellation must clean staging and
|
||||
temporary archives, and the UI must prevent application shutdown during the small
|
||||
atomic publication window.
|
||||
|
||||
All long-running work must use the existing background worker mechanism; no bundle
|
||||
operation may block the Qt event loop.
|
||||
|
||||
### Completion and readiness view
|
||||
|
||||
After import, show the restored prefix, selected executable, backend, disk usage
|
||||
and Proton state (available, missing or fingerprint mismatch). Useful next actions
|
||||
are Run, Dry-run, Open Prefix Folder, Open Game Folder and Install/Select Proton.
|
||||
|
||||
Integrity and authenticity must use different language: a bundle whose internal
|
||||
checksums pass is **integrity verified**, not a trusted or endorsed bundle. Invalid,
|
||||
incompatible and unsigned/unknown-origin states must remain distinguishable.
|
||||
|
||||
### Deliberate first-GUI scope
|
||||
|
||||
The first GUI should read and write user-selected `.pfy` files only. A managed
|
||||
bundle library/history is deferred because it introduces retention, cleanup,
|
||||
removable-storage and storage-policy decisions.
|
||||
|
||||
Before implementation, close these prerequisites in order:
|
||||
|
||||
1. Detailed core progress.
|
||||
2. Cooperative cancellation.
|
||||
3. Required notes support and decisions for optional cover and recipe-state
|
||||
components.
|
||||
4. A richer import readiness/result model.
|
||||
5. Final conflict and Import-as-Copy semantics.
|
||||
6. An agreed import-wizard wireframe.
|
||||
|
||||
## Required tests
|
||||
|
||||
- Round-trip symlinks, permissions, hidden files, Unicode names and empty dirs.
|
||||
- Export/import a synthetic prefix under different XDG roots.
|
||||
- Detect a one-byte corruption, truncated archive and manifest mismatch.
|
||||
- Reject traversal, absolute paths, escaping symlinks/hardlinks, device entries,
|
||||
duplicate members and decompression/resource abuse.
|
||||
- Leave no final archive after interrupted export.
|
||||
- Leave no registration or final prefix after interrupted import.
|
||||
- Preserve existing state on every conflict and failure path.
|
||||
- Exercise ID, UID, config and prefix conflicts, including `--as` semantics.
|
||||
- Report missing or fingerprint-mismatched Proton builds without corrupting the
|
||||
imported environment.
|
||||
- Confirm JSON CLI output and progress callbacks remain stable.
|
||||
- Use the known-good Bionic Commando Rearmed prefix as a manual acceptance test,
|
||||
including restoration onto a Steam Deck or an equivalent clean environment.
|
||||
Reference in New Issue
Block a user