Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
persist-credentials: false

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}

Expand Down Expand Up @@ -96,7 +96,7 @@ jobs:
persist-credentials: false

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}

Expand Down Expand Up @@ -131,7 +131,7 @@ jobs:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

Expand Down Expand Up @@ -171,7 +171,7 @@ jobs:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/release-notes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: "3.12"

Expand Down Expand Up @@ -154,7 +154,7 @@ jobs:
fetch-depth: 1

- name: Download validated source wheel
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
with:
name: release-source
path: source
Expand Down Expand Up @@ -254,13 +254,13 @@ jobs:
persist-credentials: false

- name: Download source archives
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
with:
name: release-source
path: release-inputs

- name: Download target package outputs
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
with:
pattern: "bbackup-deb-*"
merge-multiple: true
Expand Down Expand Up @@ -342,7 +342,7 @@ jobs:
actual_sha="$(jq -r '.head_sha' "$RUNNER_TEMP/build-run.json")"
test "$actual_sha" = "$expected_sha"
- name: Download exact successful build artifact
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
with:
name: release-inputs
run-id: ${{ inputs.build_run_id }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,41 @@ current uv-based install, development, and release instructions.

## [Unreleased]

### Added

- Reviewed repository lock inspection and stale-lock removal through the native
production dispatcher. Unlock requires an exact review token, explicit
approval, a confirmed quiet window, and only own-host, same-user locks whose
processes are absent. Changed or uncertain lock inventories block removal.
- Optional `local_last` and `local_weekly` retention counts, disabled by default,
alongside daily retention. Counts are part of the reviewed retention policy.

### Fixed

- Keep dashboard forms and setup navigation reachable in short terminals, and
preserve resource summaries, failure status and cancel hints in the compact
legacy dashboard.
- Put getting-started instructions before detailed support history and keep
the documentation index readable on narrow screens.
- Accept Restic's explicit `remove: null` keep-all selection only when its keep
group contains valid full snapshot IDs; malformed selections remain errors.
- Recognize B2's read-only `readBucketLifecycleRules` capability without allowing
administrative or unknown permissions or qualifying a permanent-delete key as
a restricted source writer.
- Preserve the resolved Restic cache policy in nested repository queries,
including execution without `HOME` or `XDG_CACHE_HOME`.

These source changes address issue [#30](https://github.com/CruxExperts/BestBackup/issues/30).
They do not publish a new release or expand cloud or machine-recovery qualification.

### Changed

- Refresh the reviewed Python maintenance dependencies from pull request #28
and the immutable GitHub Actions references from #20. Keep the established
Ruff rule profile explicit across its default-rule migration. See the
[dependency evidence](docs/development/release-dependencies.json) for exact
versions, source timestamps and limits.

## [2.0.5] - 2026-10-22

### Fixed
Expand Down
60 changes: 60 additions & 0 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,66 @@ qualify complete-machine disaster recovery.

Persistent timers can catch up after an idle interruption. An interrupted mutation remains uncertain and blocks automatic replay until explicit reconciliation. Retention remains a separate reviewed action; scheduling does not silently prune data.

## Review local retention and stale locks (unreleased)

The current source includes the following maintenance features for issue
[#30](https://github.com/CruxExperts/BestBackup/issues/30). They are not part of
the published v2.0.5 artifacts. Use an explicitly enrolled policy and bindings;
the examples below use the default configuration paths.

The unreleased dashboard layout keeps forms and setup navigation reachable in
compact terminals. Use `Tab` and `Shift+Tab` to move between controls; the focused
control scrolls into view. Use `F1` for field help. The legacy live dashboard
summarizes resource categories and keeps failures and cancel hints visible when
there is not enough room for its full layout.

For a job's `retention` object, optional `local_last` and `local_weekly` counts
default to zero (disabled). For example:

```json
{"local_last": 12, "local_daily": 14, "local_weekly": 8}
```

The optional counts must be nonnegative integers; booleans and null are rejected.
`local_daily` retains its positive-integer requirement. Restic
keeps a snapshot when any enabled rule matches, within the existing host/job
group. Review the resulting exact snapshot selection before applying it:

```bash
uv run bbackup production retention plan --job daily
uv run bbackup production retention apply --input-json \
'{"job":"daily","plan_token":"EXACT_PLAN_TOKEN","approved":true}'
```

The plan token includes the counts, snapshot inventory and replica evidence.
Changing them requires a new plan. Required replica obligations recorded at
capture still apply after editing a job. This operation forgets selected local
snapshots; it does not prune repository data or apply cloud retention.

For a suspected interrupted Restic lock, inspect first:

```bash
uv run bbackup production repositories locks --name local
```

Review the returned lock inventory and eligibility. Stop every external
repository writer and keep them stopped throughout inspection and unlock.
Only an exact, bounded set of this host's same-user locks with demonstrably
absent processes can qualify. Then explicitly approve the current token:

```bash
uv run bbackup production repositories unlock-stale --input-json \
'{"name":"local","plan_token":"EXACT_REVIEW_TOKEN","approved":true,"quiescent_window_confirmed":true}'
```

The service reinspects under its mutation lease, uses ordinary repository-wide
Restic unlock (which cannot target individual lock IDs), and verifies no locks
remain. It cannot prevent an external writer from
starting, so the quiet window is essential. Live, foreign, changed, malformed
or incomplete locks block removal. Never repeat an uncertain unlock; use the
[administrator reconciliation workflow](docs/AGENT_INTEGRATION.md#reconcile-uncertain-mutations).
This is a deliberate maintenance action, not automatic lock cleanup.

## Existing Docker and YAML users

The retained 1.x commands use their own configuration. Legacy Docker restores require unused container/volume names; bind-mounted container restoration is refused. SFTP requires a previously trusted host key. See [legacy management](docs/management.md) and [encryption compatibility](docs/encryption.md).
Expand Down
64 changes: 40 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,13 @@ Use bbackup when you want to know what a job captured, whether each repository c

![Real bbackup dashboard with disposable example data and illustrative capacity](docs/assets/bbackup-dashboard-preview.svg)

## v2.0.5 tested scope

The qualified scope is unchanged from v2.0.4: local file capture and restore; live capture and restore for PostgreSQL 17.11, MySQL 8.4.12, and MariaDB 11.8.9; headless dashboard happy paths; and AMD64 Ubuntu 24.04/26.04 packages. A separate real-guest user-timer reboot drill covers only the components and conditions recorded in the [qualification evidence](docs/development/version-2.md#development-candidate-rebootable-systemd-qualification).

B2/AWS, native Docker, ARM64 backup/restore, cross-application-version upgrades and rollback, root-owned systemd package templates, and cloud or off-host protection remain unqualified. ARM64 packages may be built, but build or package-install smoke checks alone do not qualify backup/restore. Keep an independently retrievable copy of repository passwords; a second repository on the same host does not protect against host loss. See [installation](INSTALL.md) for prerequisites and release install instructions.

Recent live B2 evidence covers a small interrupted maintenance run
whose prune outcome remained uncertain, and a separate lifecycle-overlap guard
that made central retention refuse the configured bucket. Neither test proves
prune completion, elapsed lifecycle expiry, physical reclamation, or provider
protection. These workflows ship in v2.0.5 as experimental and remain
unqualified. See the [maintenance interruption record](docs/development/version-2.md#live-b2-central-maintenance-interruption-trial)
and [lifecycle guard evidence](docs/cloud-storage.md#b2-lifecycle-overlap-guard-experimental).
Issues #23–#26 were canceled and closed as not planned. Cancellation does not
qualify the experimental B2 workflow. This release adds no new live provider
qualification; the existing workstation results remain historical.
The qualified core scope is unchanged from v2.0.4.

## Start with the guided workflow

Experimental restricted cloud credentials, central recovery and retention,
bounded remote-primary capture, and reviewed Linux coverage ship in v2.0.5 and
are [documented with their limits](docs/development/version-2.md#experimental-cloud-and-machine-recovery-features).
The reviewed Linux coverage work includes an experimental manual PostgreSQL
path. These features do not expand the tested stable scope.

For packaged releases, start with the [latest release downloads](https://github.com/CruxExperts/BestBackup/releases/latest) and [installation guide](INSTALL.md).

Check the [tested scope](#v205-tested-scope) before choosing a workflow. Cloud
protection and complete-machine recovery remain unqualified.

From a development checkout with Python 3.12+, uv, and restic installed:

```bash
Expand All @@ -61,6 +40,43 @@ uv run bbackup production tui

Choose a source folder, encrypted storage, and a schedule. Review and enroll once. The dashboard then guides you through initializing storage, running the first backup, and restoring into a new folder. Advanced settings and the command line remain available. The [quick start](QUICKSTART.md) covers migration, password custody, replica setup, and deliberate schedule activation.

## v2.0.5 tested scope

The qualified scope is unchanged from v2.0.4: local file capture and restore;
live capture and restore for PostgreSQL 17.11, MySQL 8.4.12, and MariaDB 11.8.9;
headless dashboard happy paths; and AMD64 Ubuntu 24.04/26.04 packages. User-timer
reboot qualification covers only the components and conditions in the
[recorded guest drill](docs/development/version-2.md#development-candidate-rebootable-systemd-qualification).

B2/AWS protection, native Docker, ARM64 backup/restore, cross-application-version
upgrades and rollback, root-owned systemd package templates, and off-host recovery
remain unqualified. Package-build or install checks alone do not qualify those
paths. Keep an independently retrievable copy of repository passwords; a second
repository on the same host does not protect against host loss.

Experimental cloud credentials, central recovery and retention, remote-primary
capture, and reviewed Linux coverage ship with
[documented limits](docs/development/version-2.md#experimental-cloud-and-machine-recovery-features).
Issues #23–#26 were canceled and closed as not planned. Historical B2
[maintenance interruption](docs/development/version-2.md#live-b2-central-maintenance-interruption-trial)
and [lifecycle guard](docs/cloud-storage.md#b2-lifecycle-overlap-guard-experimental)
trials do not prove prune completion, elapsed expiry, physical reclamation or
provider protection. The [qualification record](docs/development/version-2.md)
owns the complete evidence and support limits.

### Current source changes

The source checkout also includes [unreleased issue #30 maintenance](docs/development/version-2.md#unreleased-maintenance-issue-30):
reviewed stale-lock handling, optional local last/weekly retention, strict
keep-all parsing, B2 lifecycle-read compatibility and nested cache handling.
These changes are separate from the v2.0.5 downloads. Follow the
[maintenance quick start](QUICKSTART.md#review-local-retention-and-stale-locks-unreleased)
for the new commands.

The [current maintenance batch](docs/development/version-2.md#unreleased-layout-and-dependency-maintenance)
also repairs compact-terminal layouts and refreshes reviewed Python and CI
dependencies. Those changes are likewise unreleased.

## Choose a guide

- [Install](INSTALL.md): prerequisites, release packages, and development setup.
Expand Down
2 changes: 1 addition & 1 deletion bbackup/b2.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class B2Error(RuntimeError):
"listBuckets", "listAllBucketNames", "listFiles", "readFiles", "writeFiles",
"readBuckets", "readBucketEncryption", "readBucketRetentions",
"readFileRetentions", "readFileLegalHolds", "readBucketReplications",
"readBucketNotifications",
"readBucketNotifications", "readBucketLifecycleRules",
}
_INSPECTION_CAPABILITIES = {"listBuckets", "listFiles", "readFiles"}
_PROFILE_CAPABILITIES = _INSPECTION_CAPABILITIES | {"writeFiles"}
Expand Down
5 changes: 5 additions & 0 deletions bbackup/cli_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,8 @@ class ProductionCommand(TypedDict):
"repositories list": {"summary": "List declared repositories without host credentials.", "fields": {}},
"repositories init": {"summary": "Initialize an encrypted repository.", "fields": {"name": "string", "source": "optional-string"}},
"repositories check": {"summary": "Read and verify repository data.", "fields": {"name": "string"}},
"repositories locks": {"summary": "Inspect a bounded Restic lock inventory and issue a review token without changing the repository.", "fields": {"name": "string"}},
"repositories unlock-stale": {"summary": "Remove only reviewed same-host, same-user Restic locks whose PIDs are demonstrably absent, after explicit approval and a confirmed quiet window.", "fields": {"name": "string", "plan_token": "string", "approved": "boolean", "quiescent_window_confirmed": "boolean"}},
"snapshots list": {"summary": "List successful local snapshot receipts.", "fields": {"limit": "limit", "offset": "offset"}},
"snapshots copy": {"summary": "Copy a successful snapshot into an independent repository.", "fields": {"source": "string", "destination": "string", "snapshot": "string"}},
"retention plan": {"summary": "Review a read-only local retention plan bound to current snapshot and replica evidence.", "fields": {"job": "string"}},
Expand Down Expand Up @@ -2051,6 +2053,7 @@ def production_schema(command: str) -> dict:
"target": "Choose a new absolute destination that does not overlap protected data.",
"executable": "Use the absolute path to the installed bbackup executable.",
"approved": "Approve only the exact retention plan already reviewed.",
"plan_token": "Use the complete token returned by the matching read-only review command.",
"activate": "Enable and start this job's exact user timer after installing its units.",
"operation_id": "Use the complete operation ID from the run ledger.",
"outcome": "Record the outcome already verified on the target; this does not replay work.",
Expand Down Expand Up @@ -2133,6 +2136,8 @@ def production_schema(command: str) -> dict:
"jobs run": "Run a backup",
"repositories init": "Initialize storage",
"repositories check": "Verify repository data",
"repositories locks": "Review repository locks",
"repositories unlock-stale": "Unlock reviewed stale locks",
"snapshots copy": "Make an additional copy",
"retention plan": "Preview retention",
"retention apply": "Apply reviewed retention",
Expand Down
Loading
Loading