Opt-in ------ Nested-dataset snapshot support changes how backups read their source data, so it is now off by default and gated behind a marker file: install.sh --enable-nested-snapshots install.sh --disable-nested-snapshots With neither flag install.sh preserves the current setting, so a routine `git pull && bash install.sh` can never silently flip it. When disabled, apply.sh skips the patch entirely and the stock guard remains. uninstall.sh tears down staging mounts and removes the marker. Snapshot lifecycle ------------------ zfs.snapshot.delete defaults to recursive=False and stock restic_backup() calls it with no options. Stock is safe only because its validation means recursive is never True in the field. Enabling nested datasets makes recursive snapshots real: the parent then has one child snapshot per descendant dataset (160+ on an Apps pool), so stock's delete would orphan every child on EVERY successful run. The patch now owns the lifecycle end to end: - delete_snapshot_tree() sweeps the parent and all children, and is idempotent against stock's finally winning the race once our mounts are released - on a staging failure the tree is deleted here, because sync.py never completes `snapshot, local_path = await create_snapshot(...)` and so its finally deletes nothing at all - the snapshot is recorded in a sidecar file before anything is mounted, so a middlewared restart mid-backup cannot orphan it - a crashed run's snapshot tree is reclaimed on the next run instead of being overwritten and leaked Silent-omission fix ------------------- The dataset list is now enumerated AFTER the snapshot. Read beforehand it could miss a dataset created in the gap, which the recursive snapshot would capture but the staging plan would not -- silently omitting its data. Read afterwards, an unsnapshotted dataset trips the staging check and fails the run loudly. Also from the audit ------------------- - plan_staging scopes by dataset name, so skipped-dataset warnings no longer include every mountpoint-less dataset on the box, which buried the ones that matter - staging_root_for rejects "." / ".." components that would escape the staging base, and resolves STAGING_BASE at call time rather than freezing it into a default argument - uninstall.sh no longer `rm -rf`s a tree that may still contain live bind mounts, and unmounts by path depth rather than string length - apply_plan takes an injectable isdir; verify_staged drops an unused parameter - pin the shellcheck action instead of tracking @master 61 tests, ruff and shellcheck clean.
63 lines
1.7 KiB
YAML
63 lines
1.7 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main, "feat/**", "fix/**"]
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
shell:
|
|
name: shell (shellcheck + syntax)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: bash syntax check
|
|
run: |
|
|
fail=0
|
|
while IFS= read -r f; do
|
|
bash -n "$f" || { echo "::error file=$f::bash syntax error"; fail=1; }
|
|
done < <(find . -name '*.sh' -not -path './.git/*')
|
|
exit $fail
|
|
|
|
# Pinned to a release tag, not @master: a third-party action on a moving
|
|
# branch runs whatever that branch contains at the time CI fires.
|
|
- name: shellcheck
|
|
uses: ludeeus/action-shellcheck@2.0.0
|
|
env:
|
|
SHELLCHECK_OPTS: -S warning -e SC1091
|
|
|
|
python:
|
|
name: python ${{ matrix.python }}
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# TrueNAS SCALE middleware runs 3.11+; keep the patch importable across
|
|
# the versions it may be injected into.
|
|
python: ["3.11", "3.12", "3.13"]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: ${{ matrix.python }}
|
|
|
|
- name: install dev deps
|
|
run: python -m pip install --upgrade pip pytest ruff
|
|
|
|
- name: ruff
|
|
run: ruff check patch tests
|
|
|
|
- name: pytest
|
|
run: pytest tests -v
|
|
|
|
- name: verify injected middleware blocks compile
|
|
# Belt-and-braces: the *_BLOCK strings are appended into live middlewared
|
|
# modules. A syntax error there would break the box at boot.
|
|
run: pytest tests/test_apply_blocks.py -v
|