117 lines
5.4 KiB
Markdown
117 lines
5.4 KiB
Markdown
# Backup archive format
|
|
|
|
Backups are uncompressed tar archives with a versioned TOML manifest. Version 1
|
|
uses only fixed logical member names and is parsed without extracting files.
|
|
The archive is an opaque saved-state copy; its tar checksum detects accidental
|
|
header damage but does not authenticate contents or make the archive tamper-proof.
|
|
|
|
## Members and limits
|
|
|
|
The first member is always `manifest.toml`. The only permitted payload names are
|
|
`settings.toml`, `server.conf`, `internal-state.toml`, and `vault.toml`, in that
|
|
order when present. Settings and server configuration are required. Internal
|
|
state and vault are optional components: a missing component is recorded as
|
|
`present = false` and `length = none`, which means restore removes that owned
|
|
component. Absence is not represented by an empty placeholder file.
|
|
|
|
| Member | Maximum bytes | Presence |
|
|
| --- | ---: | --- |
|
|
| `manifest.toml` | 64 KiB | Required, first |
|
|
| `settings.toml` | 1 MiB | Required |
|
|
| `server.conf` | 4 MiB | Required |
|
|
| `internal-state.toml` | 2 MiB | Optional |
|
|
| `vault.toml` | 16 MiB | Optional |
|
|
|
|
The archive is limited to 26 MiB and five entries. Only checksummed UStar regular
|
|
files are accepted. Absolute names, path separators, traversal, links, device or
|
|
special entries, sparse entries, GNU/PAX extension headers, nonzero path prefixes,
|
|
unexpected metadata fields, duplicate/missing/unexpected members, size mismatches,
|
|
truncation, nonzero padding, and nonzero trailing data are rejected. The reader
|
|
checks declared sizes before allocating payload buffers and never follows a
|
|
tar-supplied path. It does not restore files or validate their application
|
|
semantics; restoration must run settings, configuration, state, and vault
|
|
validators before mutation.
|
|
|
|
## Manifest v1
|
|
|
|
The manifest is UTF-8 TOML with unknown fields denied:
|
|
|
|
```toml
|
|
version = 1
|
|
created_at_utc = "2026-10-19T12:30:00Z"
|
|
reason = "pre-commit"
|
|
|
|
[historical_apply]
|
|
status = "unknown"
|
|
|
|
[[members]]
|
|
name = "settings.toml"
|
|
present = true
|
|
length = 420
|
|
|
|
[[members]]
|
|
name = "server.conf"
|
|
present = true
|
|
length = 640
|
|
|
|
[[members]]
|
|
name = "internal-state.toml"
|
|
present = false
|
|
|
|
[[members]]
|
|
name = "vault.toml"
|
|
present = true
|
|
length = 8192
|
|
```
|
|
|
|
`reason` is one of `manual`, `pre-commit`, `pre-restore`, or `pre-restart`.
|
|
Timestamps are canonical RFC 3339 UTC values with whole-second precision. The
|
|
historical apply record is either `status = "unknown"` or
|
|
`status = "verified"` with a `verified_at_utc` timestamp. It is historical
|
|
metadata only: a pre-operation reason or a verified marker does not establish
|
|
that these bytes match the currently running interface. Application-created
|
|
backups derive this record from the timestamp stored in the internal-state bytes
|
|
captured with the saved set. A successful Commit or restore clears that timestamp
|
|
for its new saved generation; a failed operation leaves it intact. Reopening may
|
|
show the saved generation's historical verification time, but runtime observation
|
|
always begins as unknown.
|
|
|
|
The inventory always has exactly four records in the specified order. Required
|
|
members have `present = true` and an exact byte `length`; optional absence has
|
|
`present = false` and omits `length` (TOML has no null value). Present optional members follow the required
|
|
members in fixed order. The manifest itself is excluded from its payload
|
|
inventory. Readers reject unsupported versions and any discrepancy between
|
|
inventory and tar entries.
|
|
|
|
## Publication and trust boundary
|
|
|
|
Creation receives exact saved bytes from a trusted caller. It does not discover
|
|
files, inspect the current working directory, recurse into the archive directory,
|
|
or read client source/output directories. The caller must supply installed bytes,
|
|
never a proposed draft or decrypted vault records. New archives are fully
|
|
serialized and synced in a private temporary file, then published without
|
|
replacement under a timestamped collision-safe name; the archive directory is
|
|
synced after publication. The archive file is mode `0600` from temporary-file
|
|
creation. A write or file-sync failure removes the unpublished temporary file;
|
|
a publication or directory-sync failure returns an error and blocks the protected
|
|
operation. A complete archive may already be visible if failure occurs after its
|
|
atomic publication, and a failed temporary cleanup may leave a hidden `0600`
|
|
temporary file. Neither condition is reported as backup success. Readers should
|
|
expose the list and preview without passwords, but must keep payload bytes out of
|
|
Debug output and logs.
|
|
|
|
The application restore preview validates both the archive structure and its
|
|
settings, server document, optional internal state, and opaque vault before it
|
|
returns an opaque confirmation value. That value owns the validated payload
|
|
bytes, so confirmation applies the exact content shown during preview even if
|
|
the selected archive path changes afterward. Confirmation first completes a
|
|
`pre-restore` archive and then uses the saved-file transaction journal for the
|
|
full replacement. Dropping the preview is cancellation and does not discard a
|
|
draft. Historical verification metadata is informational; opening or restoring
|
|
an archive always leaves current runtime state unknown.
|
|
|
|
This module only creates, lists, and validates archives. It does not write saved
|
|
state, apply WireGuard changes, or restore content. A backup is not encrypted:
|
|
it can contain the server's private configuration and the encrypted vault, so
|
|
filesystem access must remain restricted to root.
|