From 2b22a23d60f1e305d7be23d001afef0eb65397b8 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 2 Aug 2026 12:21:30 +0200 Subject: [PATCH] persistence sweep: 53 templates measured; gramps-web + wishlist fixed; runtime gate added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Campaign 10's R-156 found papra writing its database into the container's writable layer while the volume the template preserves stayed empty — a backup that completes, verifies, and contains nothing. papra was never the point: nothing anywhere checked that the folder a template preserves is the folder the app writes to. All 53 templates have now been measured live. 43 CLEAN / 3 BROKEN / 7 UNDETERMINED. UNDETERMINED is counted separately, each with its reason, and never folded into CLEAN. FIXED (neither app is deployed anywhere, so nothing was stranded): - gramps-web mounted /app/data, /app/media, /tmp — and /app/data is a path the application never writes. Its accounts database and ITS FAMILY TREE both landed in the writable layer while gramps_data was tarred nightly as an empty directory. Now persists the eight paths the image's own environment names, matching upstream's reference compose. Proven: users.sqlite and the family-tree files survive a redeploy byte-identical, same inode. - wishlist mounted wishlist_data:/data, another path the app never writes; prod.db landed in the ANONYMOUS volume from the image's VOLUME directive — absent from ResolveDockerVolumeNames, so never backed up, and orphaned by a redeploy. Now mounts /usr/src/app/data + /usr/src/app/uploads. Proven: prod.db byte-identical, same inode, across a redeploy. Every corrected path confirmed by two independent sources — the shipped image's own environment/Config.Volumes and upstream's reference compose — never inferred from a directory name. papra is NOT fixed. It is live on one box, and changing the mount target makes the next compose up recreate the container and destroy the writable layer its documents live in. The fix is prepared and proven in the scratch guest (current: db.sqlite differs after a redeploy, so a real account created via the API is lost; fixed: byte-identical, it survives). Referred to the operator with the two options; no migration written. NEW GATE scripts/check-volume-persistence.py — the third catalog gate and the only RUNTIME one. This class is invisible to static analysis, measured not assumed: a static audit of all 53 composes reports the catalog clean AND reports papra clean. Exit 0 clean / 1 REFUSED / 2 undecided. It refuses to report at all unless it has just re-proven itself in both directions against two canary templates that differ only in which path the volume mounts at, so every run carries a live demonstration of R-156 and of its fix. No docker exec anywhere (Campaign 7 §1.1). 44 fixture tests driving check(), the function __main__ calls; every rule red-proofed. Enforcement is convention, not CI — this repo has no CI. Stated plainly in the report; raising it is proposed as R-160. Report, per-app evidence, proofs and proposed register entries (R-158..R-161, NOT filed — felhom.eu is fenced this session): audits/persistence-sweep-2026-08-02/ --- CHANGELOG.md | 56 + CLAUDE.md | 13 + REPORT.md | 82 +- REUSE.md | 1 + audits/persistence-sweep-2026-08-02/README.md | 749 ++ .../evidence/actualbudget/probe.json | 54 + .../evidence/adventurelog/probe.json | 222 + .../evidence/audiobookshelf/probe.json | 116 + .../evidence/bentopdf/probe.json | 70 + .../evidence/bookstack/probe.json | 246 + .../evidence/calcom/probe.json | 1052 +++ .../evidence/calibre-web/probe.json | 3132 ++++++++ .../evidence/claper/probe.json | 177 + .../evidence/code-server/probe.json | 110 + .../evidence/crafty-controller/probe.json | 6433 ++++++++++++++++ .../evidence/docmost/probe.json | 217 + .../evidence/emby/probe.json | 119 + .../evidence/ghost/probe.json | 59 + .../evidence/gitea/probe.json | 193 + .../evidence/glance/probe.json | 74 + .../evidence/gokapi/probe.json | 92 + .../evidence/grafana/probe.json | 93 + .../evidence/gramps-web/probe.json | 266 + .../evidence/home-assistant/probe.json | 114 + .../evidence/homebox/probe.json | 68 + .../evidence/homepage/probe.json | 86 + .../evidence/immich/probe.json | 330 + .../evidence/jellyfin/probe.json | 153 + .../evidence/kimai/probe.json | 259 + .../evidence/komga/probe.json | 86 + .../evidence/mealie/probe.json | 89 + .../evidence/n8n/probe.json | 79 + .../evidence/navidrome/probe.json | 81 + .../evidence/nextcloud/probe.json | 317 + .../evidence/onlyoffice/probe.json | 6644 +++++++++++++++++ .../evidence/opengist/probe.json | 103 + .../evidence/outline/probe.json | 166 + .../evidence/paperless-ngx/probe.json | 250 + .../evidence/papra/probe.json | 100 + .../evidence/plant-it/probe.json | 11 + .../evidence/plex/probe.json | 170 + .../evidence/privatebin/probe.json | 96 + .../evidence/radarr/probe.json | 155 + .../evidence/rallly/probe.json | 159 + .../evidence/recipe-importer/probe.json | 95 + .../evidence/romm/probe.json | 248 + .../evidence/seerr/probe.json | 63 + .../evidence/sonarr/probe.json | 155 + .../evidence/sparkyfitness/probe.json | 231 + .../evidence/tandoor/probe.json | 263 + .../evidence/termix/probe.json | 94 + .../evidence/uptime-kuma/probe.json | 54 + .../evidence/vaultwarden/probe.json | 58 + .../evidence/vikunja/probe.json | 100 + .../evidence/wanderer/probe.json | 194 + .../evidence/wger/probe.json | 66 + .../evidence/wishlist/probe.json | 163 + .../evidence/zipline/probe.json | 170 + .../_papra-fix-proof/papra-fix-proof.json | 84 + .../proofs/_papra-fix-proof/template.diff | 4 + .../proofs/fixed-check/gramps-web/probe.json | 255 + .../proofs/fixed-check/wishlist/probe.json | 152 + .../proofs/gramps-web/survive-gramps-web.json | 77 + .../proofs/survive.log | 2 + .../wishlist/survive-wishlist-content.json | 23 + .../proofs/wishlist/survive-wishlist.json | 30 + .../state/gate.log | 90 + .../state/mktable.py | 66 + .../state/papra_proof2.py | 165 + .../state/reclassify.py | 42 + .../state/survive2.py | 146 + .../state/verify-evidence/bentopdf/probe.json | 69 + .../state/verify-evidence/claper/probe.json | 176 + .../state/verify-evidence/docmost/probe.json | 216 + .../state/verify-evidence/glance/probe.json | 73 + .../verify-evidence/gramps-web/probe.json | 265 + .../state/verify-evidence/immich/probe.json | 329 + .../state/verify-evidence/papra/probe.json | 99 + .../state/verify-evidence/plant-it/probe.json | 10 + .../verify-evidence/privatebin/probe.json | 95 + .../recipe-importer/probe.json | 94 + .../verify-evidence/sparkyfitness/probe.json | 230 + .../verify-evidence/uptime-kuma/probe.json | 53 + .../state/verify-evidence/wanderer/probe.json | 193 + .../state/verify-evidence/wishlist/probe.json | 162 + .../state/verify-evidence/zipline/probe.json | 169 + .../state/verify.log | 48 + .../state/wishlist_proof.py | 125 + scripts/check-volume-persistence.py | 877 +++ scripts/test_check_volume_persistence.py | 451 ++ templates/gramps-web/docker-compose.yml | 27 +- templates/wishlist/docker-compose.yml | 16 +- 92 files changed, 29956 insertions(+), 53 deletions(-) create mode 100644 audits/persistence-sweep-2026-08-02/README.md create mode 100644 audits/persistence-sweep-2026-08-02/evidence/actualbudget/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/adventurelog/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/audiobookshelf/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/bentopdf/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/bookstack/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/calcom/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/calibre-web/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/claper/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/code-server/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/crafty-controller/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/docmost/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/emby/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/ghost/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/gitea/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/glance/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/gokapi/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/grafana/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/gramps-web/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/home-assistant/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/homebox/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/homepage/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/immich/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/jellyfin/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/kimai/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/komga/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/mealie/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/n8n/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/navidrome/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/nextcloud/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/onlyoffice/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/opengist/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/outline/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/paperless-ngx/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/papra/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/plant-it/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/plex/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/privatebin/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/radarr/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/rallly/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/recipe-importer/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/romm/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/seerr/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/sonarr/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/sparkyfitness/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/tandoor/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/termix/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/uptime-kuma/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/vaultwarden/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/vikunja/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/wanderer/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/wger/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/wishlist/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/evidence/zipline/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/papra-fix-proof.json create mode 100644 audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/template.diff create mode 100644 audits/persistence-sweep-2026-08-02/proofs/fixed-check/gramps-web/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/proofs/fixed-check/wishlist/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/proofs/gramps-web/survive-gramps-web.json create mode 100644 audits/persistence-sweep-2026-08-02/proofs/survive.log create mode 100644 audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist-content.json create mode 100644 audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist.json create mode 100644 audits/persistence-sweep-2026-08-02/state/gate.log create mode 100644 audits/persistence-sweep-2026-08-02/state/mktable.py create mode 100644 audits/persistence-sweep-2026-08-02/state/papra_proof2.py create mode 100644 audits/persistence-sweep-2026-08-02/state/reclassify.py create mode 100644 audits/persistence-sweep-2026-08-02/state/survive2.py create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/bentopdf/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/claper/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/docmost/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/glance/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/gramps-web/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/immich/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/papra/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/plant-it/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/privatebin/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/recipe-importer/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/sparkyfitness/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/uptime-kuma/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/wanderer/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/wishlist/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify-evidence/zipline/probe.json create mode 100644 audits/persistence-sweep-2026-08-02/state/verify.log create mode 100644 audits/persistence-sweep-2026-08-02/state/wishlist_proof.py create mode 100644 scripts/check-volume-persistence.py create mode 100644 scripts/test_check_volume_persistence.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d4a29..976b178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,61 @@ # Changelog +## 2026-08-02 — persistence sweep: does every app's data land in a folder the template preserves? + +Campaign 10's R-156 found papra writing its database into the container's writable layer while the +volume the template preserves stayed empty — so its backup completed, verified, and contained +nothing. papra was never the point: **nothing anywhere checked that the folder a template preserves +is the folder the app writes to**, across 53 templates. All 53 have now been measured live. + +**Result: 43 CLEAN · 3 BROKEN · 7 UNDETERMINED.** Full report and per-app evidence: +`audits/persistence-sweep-2026-08-02/`. + +**New gate — `scripts/check-volume-persistence.py`, the third and the only RUNTIME one.** +The two image gates are static, and **this defect class is invisible to static analysis** — measured, +not assumed: a static audit of all 53 composes (every declared volume attached, no anonymous mounts, +no stray host binds) reports the catalog clean *and reports papra clean*. papra's compose is +well-formed; only its behaviour is wrong. So the gate deploys each template, exercises it into +writing data, and compares where the data landed with what is mounted. Exit **0** all clean / +**1 REFUSED** / **2** undecided. `UNDETERMINED` is exit 2 and is never a pass. + +It **refuses to report at all** unless it has just re-proven itself in both directions against two +canary templates built from a purpose-made image reproducing papra's ownership shape — the pair +differ only in which path the volume mounts at, so every run carries a live demonstration of R-156 +and of its fix. A detector that flags nothing turns an unexamined catalog into a documented-clean one. + +41 fixture tests (`scripts/test_check_volume_persistence.py`, no Docker) driving `check()` — the +function `__main__` calls — plus `rollup_diff`/`classify`. Every rule red-proofed. + +**Two templates FIXED** (neither deployed anywhere in the fleet, so no data was stranded): + +- **`gramps-web`** — mounted `/app/data`, `/app/media`, `/tmp`, and **`/app/data` is a path the + application never writes**. Its accounts database (`GRAMPSWEB_USER_DB_URI` → `/app/users`) and + **its family tree** (`GRAMPS_DATABASE_PATH` → `/root/.gramps/grampsdb`) both landed in the + container's writable layer: destroyed by any redeploy, absent from every backup, while + `gramps_data` was tarred nightly as an empty directory. Now persists the eight paths the image's + own environment names, matching upstream's reference compose. +- **`wishlist`** — mounted `wishlist_data:/data`, another path the app never writes. `prod.db` went + into the **anonymous** volume docker creates for the image's `VOLUME /usr/src/app/data` directive. + Anonymous volumes are absent from `ResolveDockerVolumeNames`, so `DumpAppVolumes` never backs them + up, and `compose down` + `up` orphans them — a store that survives a restart, loses on redeploy and + is never in a backup. Now mounts `/usr/src/app/data` and `/usr/src/app/uploads` per upstream. + +Every corrected path is confirmed by **two independent sources** — the shipped image's own +environment/`Config.Volumes`, and upstream's reference compose — never inferred from a directory name. + +**`papra` is NOT fixed — referred to the operator.** The one-line fix is prepared and proven, but +papra is live on one box, and changing the mount target makes the next `compose up -d` recreate the +container and destroy the writable layer its documents currently live in. That data is already on +borrowed time, but the fix is what *schedules* the loss. See the report §6.1 for which box it is, +how far that was determined, and the two options. No migration was written. + +**7 UNDETERMINED, counted separately and never folded into CLEAN** — `bentopdf` (stateless by +design), `uptime-kuma` / `privatebin` / `recipe-importer` (write nothing until a user completes +setup), `glance` (crash-loops for want of a seeded config — pre-existing, Campaign 7 §6.2), +`plant-it` (image does not resolve; `lifecycle: abandoned`), `wanderer` (unhealthy). + +`CLAUDE.md` and `REUSE.md` updated with the gate and the traps it encodes. + ## 2026-07-21 (later) — app lifecycle replaces the `retired/` directory move **The `retired/` mechanism shipped earlier today was wrong and is withdrawn.** Moving a template out diff --git a/CLAUDE.md b/CLAUDE.md index 229d882..785bdbb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,6 +36,19 @@ deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details run at the start of every catalog campaign and before any publish train that vouches the catalog. Needs network + `docker`; unauthenticated Docker Hub throttles a full sweep, so `docker login` first or expect exit 2. It reports a throttle as INCONCLUSIVE, never as a dead image. +- **A well-formed template can still preserve the wrong folder** — and no static check can see it. + Third gate, the only RUNTIME one: `python3 scripts/check-volume-persistence.py` + (0 all clean / **1 REFUSED** / 2 undecided). It deploys each template, exercises it into writing + data, and compares where the data landed against what the compose mounts. Needs Docker + network + and minutes per app, so it is periodic like the resolvability gate — run it whenever a template's + `volumes:` block or image tag changes, and at the start of every catalog campaign, **on a scratch + host, never a customer box**. It refuses to report at all unless it has just re-proven itself in + both directions against two canary templates. Fixture tests (no Docker): + `python3 scripts/test_check_volume_persistence.py`. + **UNDETERMINED is exit 2 and is never a pass** — an app that wrote nothing has not been shown to + be correct. Why it exists: papra mounted `papra_data:/app/data` while the app wrote its database + to `/app/app-data/db/`, so its backup completed, verified, and contained an empty directory + (R-156, Campaign 10). - **Taking an app out of circulation — use `lifecycle:`, never a directory move.** `.felhom.yml` gains an optional `lifecycle:` field: `available` (default; absent/empty means this), `hidden` (not offered for new installs, no explanation owed), `abandoned` (upstream stopped developing it — diff --git a/REPORT.md b/REPORT.md index f45b33b..84dd29f 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,60 +1,44 @@ -# REPORT — app lifecycle replaces the `retired/` move (TASK-F Part 1, catalog half), 2026-07-21 +# REPORT — catalog persistence sweep (2026-08-02) -Baseline `857ba53`, clean tree. +**Question:** does every app's data actually land in a folder the template preserves? +**Answer, over all 53 templates:** **43 CLEAN · 3 BROKEN · 7 UNDETERMINED.** -## What changed and why +Full report, per-app evidence, proofs and proposed register entries: +**`audits/persistence-sweep-2026-08-02/README.md`**. -The `retired/` directory mechanism shipped earlier the same day is **withdrawn**. It un-offers an app -correctly, but the controller discovers catalog templates by directory — so removing one makes the -orphan detector see the template as GONE for anyone already running the app, flagging their working -install `Elavult` and offering a Törlés button. Withdrawing an app must never take a working app away -from a customer. +## What was found -Replaced by an optional top-level `lifecycle:` in `.felhom.yml` (controller v0.158.0/.1): -`available` (default, absent/empty ≡ this) / `hidden` / `abandoned`. +| app | defect | disposition | +|---|---|---| +| **gramps-web** | Mounted `/app/data` (a path the app never writes), `/app/media`, `/tmp`. Its accounts database AND **its family tree** landed in the container's writable layer — destroyed by any redeploy, absent from every backup, while `gramps_data` was tarred nightly as an empty directory. | **FIXED** — persists the 8 paths the image's own environment names. Survives-a-redeploy proven: `users.sqlite` and the family-tree files byte-identical, same inode. | +| **wishlist** | Mounted `wishlist_data:/data`, another path the app never writes. `prod.db` landed in the **anonymous** volume from the image's `VOLUME` directive — never in `ResolveDockerVolumeNames`, so never backed up, and orphaned by a redeploy. | **FIXED** — mounts `/usr/src/app/data` + `/usr/src/app/uploads`. `prod.db` byte-identical with the same inode across a redeploy. | +| **papra** (R-156) | Mounts `papra_data:/app/data`, which is root-owned, unwritable by uid 999, and does not exist in the image at all; the database is written to `/app/app-data/db/`. | **NOT FIXED — referred to the operator.** It is deployed on one box, and the fix would destroy the writable layer its documents live in. Fix prepared and proven in the scratch guest. See §6.1. | -- **`plant-it` returns to `templates/`** with `lifecycle: abandoned` — the first user of the - mechanism and the case that motivated it. `retired/` is removed. Catalog: **53 apps** (52 offered). -- Its compose is deliberately **unchanged**. It pins `msdeluise/plant-it:0.10.0`, a repository that - does not exist (the real one is `-server`), and the app is not installable — rewriting it would - imply it is. The evidence for abandonment is recorded in the template's own header comment. +7 UNDETERMINED are listed with reasons and **never folded into CLEAN** (§4.2): 4 apps write nothing +until a user completes setup, 3 could not be started. -## Live-proven on 9201 +## What was built -Through the normal git-sync and the real endpoints, not a hand-copy: +**`scripts/check-volume-persistence.py`** — the third catalog gate and the only RUNTIME one. This +defect class is **invisible to static analysis**, measured rather than assumed: a static audit of +all 53 composes reports the catalog clean *and reports papra clean*. So the gate deploys each +template, exercises it into writing data, and compares where the data landed with what is mounted. +Exit 0 clean / **1 REFUSED** / 2 undecided; UNDETERMINED is never a pass. -| check | result | -|---|---| -| `lifecycle: abandoned` present in `/opt/docker/stacks/plant-it/.felhom.yml` | yes | -| plant-it on the Alkalmazások page | **0 occurrences** (control `bookstack`: 10) | -| direct `POST /api/stacks/plant-it/deploy` | **HTTP 409** „Ez az alkalmazás jelenleg nem telepíthető." | -| `/apps/plant-it` | 200, permanent abandoned notice, no Telepítés button | +It **refuses to report at all** unless it has just re-proven itself in both directions against two +canary templates that differ only in which path the volume mounts at — so every run carries a live +demonstration of R-156 and of its fix. 41 fixture tests driving `check()` (the function `__main__` +calls); every rule red-proofed. -## The resolvability gate is now lifecycle-aware +Registered in `CLAUDE.md` and `REUSE.md`. **Enforcement is convention, not CI** — the catalog repo +has no CI of any kind. Raising that is proposed as R-160. -An abandoned app's dead image is the **expected end state**, not a finding. Left unhandled, plant-it -would have made the gate permanently exit 1 — and a gate that is always red is a gate nobody reads, -which is the same lesson as yesterday's throttle false-alarm. +## Method notes worth carrying forward -- Non-available apps are **skipped by default and REPORTED**, never silently dropped. -- `--all` includes them again. -- An all-skipped run is a **pass (0)**, not the "no images found" error — that would have been a - false alarm of its own. - -6 new fixture tests (**19 total**, no network), including the `lifecycle:` line-parse table: absent, -quoted, unknown (→ available), and an indented `lifecycle:` that must NOT match because it is not a -top-level key. - -## Gates - -`check-image-pins.py` → OK, 53 templates, 0 unpinned. `test_check_image_resolvable.py` → 19/19. - -## Remaining - -- **The full resolvability table is still owed** — Part 4 was skipped by operator ruling (no Docker - Hub login on DooPlex), so the sweep would still hit the anonymous throttle and exit 2. One - `docker login` + `python3 scripts/check-image-resolvable.py` closes it. -- **`hidden` has no user yet.** Implemented and tested, but only `abandoned` is exercised end to end. -- **wanderer is still undeployed** (from the previous session): re-pinned to a 3-service upstream - shape with a second public hostname, and never once deployed. It wants a real deploy on the demo - box before anyone is invited to it. +- **Seven detector defects were found by adjudicating flagged apps rather than trusting the label**, + and six of them made a *correct* app look broken. Every one came from a rule identifying data by + its **name** rather than by evidence of what it is. Detail: §3.5. +- The sweep was **restarted from the first app twice** rather than merge a matrix built by more than + one version of the rules. +- No `docker exec` anywhere — Campaign 7 §1.1's OCI-error-to-stdout trap. +- No hub record was created; the hub was read-only throughout. Teardown, all three layers: §9. diff --git a/REUSE.md b/REUSE.md index 145067a..0a47d59 100644 --- a/REUSE.md +++ b/REUSE.md @@ -19,6 +19,7 @@ None — this repo is templates/config, not code. See §2/§5. | App lifecycle (`available`/`hidden`/`abandoned`) | `templates/plant-it/.felhom.yml` (`lifecycle:` block) | Optional top-level `lifecycle:` in `.felhom.yml`. Absent/empty ≡ `available`. `hidden` = not offered for new installs; `abandoned` = same, PLUS a permanent "Nem karbantartott" badge + notice on every box already running it. **Deployed instances keep full function in both states** — lifecycle governs what is OFFERED, never what runs; the controller refuses a deploy of a non-available template server-side (fail-closed, so a stale link or direct POST cannot install one). Unknown value → treated as `available` + one WARN, never a broken template. **Do NOT take an app out of circulation by deleting or moving its directory** — that orphans every customer already running it, which is what the 2026-07-21 `retired/` experiment got wrong. The resolvability gate skips non-available apps, so an abandoned app's dead image is not a standing red. | | Image pinning | ALL `templates/*/docker-compose.yml` (`image:` line) | **Never `:latest` or untagged** (recovery-unit `ImagePins` pins the tag — `:latest` breaks restore fidelity). Pin a concrete version tag; an app deployed anywhere in the fleet pins to the digest it is RUNNING (pin ≠ upgrade); `@sha256:` digest pins also count. Gate: `python scripts/check-image-pins.py` after any compose change (swept 2026-07-12: 5 pins). TRAP: ghcr `tags/list` can be stale/partial — verify tag existence via `docker manifest inspect`, never the tag list. | | Image RESOLVABILITY (does the pin still exist?) | `scripts/check-image-resolvable.py` + `scripts/test_check_image_resolvable.py` | The complement to the pin gate, which is purely syntactic and cannot see rot. Run it at the START of every catalog campaign and before any publish train that vouches the catalog: `python3 scripts/check-image-resolvable.py [app …]`. Exit **0** all resolve, **1** the registry says an image is GONE, **2** INCONCLUSIVE/harness error. **Two traps it encodes, both live-observed:** (a) `docker manifest inspect` prints `toomanyrequests` and **still exits 0** — never trust the exit code alone (same shape as the ISO `validate-answer` trap); (b) the inverse — the first sweep called 24 of 65 pins dead, `postgres:16-alpine` among them, because Docker Hub throttled it partway. Ambiguity therefore resolves to INCONCLUSIVE, never to an accusation; a gate that cries wolf gets ignored. Unauthenticated Hub lookups WILL throttle on a full 65-pin sweep — `docker login` first, or expect exit 2. | +| Volume PERSISTENCE (does the app write where the template preserves?) | `scripts/check-volume-persistence.py` + `scripts/test_check_volume_persistence.py` | The third gate and the only RUNTIME one — **the two image gates are static and this class is invisible to static analysis**, which was measured, not assumed: a static audit of all 53 composes (every declared volume attached, no anonymous mounts, no stray host binds) reports the catalog clean AND reports papra clean. papra's compose is well-formed; it mounts `papra_data:/app/data` while the app writes `/app/app-data/db/db.sqlite` into the container's **writable layer** and cannot write `/app/data` at all — so `DumpAppVolumes` (`felhom-controller internal/backup/backup.go:543`) tars an empty directory and the backup verifies (R-156, Campaign 10). Run: `python3 scripts/check-volume-persistence.py [app …]` **on a scratch host, never a customer box**. Exit **0** all CLEAN, **1** REFUSED, **2** UNDETERMINED/prober untrustworthy. **Traps it encodes:** (a) `A` vs `C` in `docker diff` — a linuxserver.io entrypoint chowning its app tree produced 1305 `C` entries and called calibre-web BROKEN on the first pass, so DATA is decided from `A` only and a `C` on a DB file is adjudicated by comparing bytes against a pristine container of the same image; (b) no `docker exec` anywhere — Campaign 7 §1.1's OCI-error-to-stdout trap, so uid comes from `/proc//status` and writability from a host-side `stat`; (c) `base64key` secrets need the controller's `base64:` prefix (`deploy.go:904`) or bookstack serves 500s and the harness looks like an app defect; (d) it self-tests in BOTH directions against two canary templates before reporting anything — a detector that flags nothing turns an unexamined catalog into a documented-clean one. UNDETERMINED is **never** folded into CLEAN. | | Docker healthcheck host | ALL `templates/*/docker-compose.yml` (`healthcheck.test:`) | **Always `127.0.0.1`, never `localhost`.** BusyBox `wget` (and node/python/curl one-shots) resolve `localhost`→IPv6 `::1` with NO cross-address-family fallback; an app that binds IPv4-only then reads docker-`unhealthy` while fully serving (vaultwarden, re-run 2026-07-06 — swept all 48 templates). | | Docker healthcheck — BusyBox/wget images | `templates/vaultwarden/docker-compose.yml` (~L49) | `test: ["CMD", "wget", "--spider", "-q", "http://localhost:/"]`. Most common family (~20 apps, e.g. homebox, glance). | | Docker healthcheck — curl-capable images | `templates/paperless-ngx/docker-compose.yml` (~L76) | `test: ["CMD", "curl", "-f", "http://localhost:/"]` (~18 apps: jellyfin, immich, sonarr…). | diff --git a/audits/persistence-sweep-2026-08-02/README.md b/audits/persistence-sweep-2026-08-02/README.md new file mode 100644 index 0000000..375fe0b --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/README.md @@ -0,0 +1,749 @@ +# Catalog persistence sweep — does every app's data land in a persisted volume? + +**Run:** 2026-08-02 · **Repo:** `app-catalog-felhom.eu` (only) · **Class:** implementation — +detection first, fixes second. + +Campaign 10 found that papra stores its database somewhere the backup never looks (R-156). papra is +not the interesting part. **Nothing anywhere checked that the folder a template preserves is the +folder the app writes to**, across 53 templates. This is what that check found. + +> **Parallel-session fence, observed.** Another session was running the M-band RTO measurement on +> `demo-hp`, writing to `felhom.eu`. This session ran on `demo-felhom` and committed only to +> `app-catalog-felhom.eu`. **No commit to `felhom.eu`, no register edit, nothing on `demo-hp`.** +> All register entries below are proposed text for later filing by one session, in one place. + +--- + +## 1. Venue and baselines + +| | | +|---|---| +| Host | **`demo-felhom`** (N100), Tier 0 — `ssh felhom-pve` (Tailscale `100.70.170.35`) | +| Guest | **LXC 9301 `catalog-persist`**, created for this run and destroyed at teardown (§9) | +| Guest spec | Debian 13, 4 cores, 6 GB + 2 GB swap, `local-lvm:120`, `nesting=1,keyctl=1`, DHCP `192.168.0.22` | +| Reach | `ssh felhom-pve "pct exec 9301 -- …"`. **No direct SSH**: the N100 sits on a remote LAN whose `192.168.0.0/24` collides with DooPlex's, so `192.168.0.22` is unroutable from here (`No route to host`) | +| Docker | 29.7.1, compose v5.3.1 | +| Untouched | guest **9201** on `demo-felhom` (read-only `docker ps`/`volume ls` only), `drill-r50`, Peti's cluster, both boxes' backup configuration, **everything on `demo-hp`** | + +### Baselines, read fresh + +| What | Value | How | +|---|---|---| +| `app-catalog-felhom.eu` HEAD | **`4252121`** = `origin/main`, clean at start | `git rev-parse` | +| Templates | **53** | `ls templates \| wc -l` | +| `felhom.eu` HEAD (read-only, for citation) | **`7ba7c2a`** | `git rev-parse` — **not committed to** | +| felhom-controller `main` | **v0.188.0**, HEAD `4115e88` | `CHANGELOG.md` top | +| Hub | 0.86.0 | live `/apps` footer | + +### Why the scratch guest runs no controller — stated, because the brief asks for its version + +There is **no felhom-controller on 9301, deliberately.** The property under test is a property of +the *template*, and the controller's role in it is exactly one thing: it resolves the `${…}` +variables and runs `docker compose up -d` (`felhom-controller/internal/stacks/deploy.go:374`, +`:551-582`). It does not rewrite the compose file, so a plain Docker host reproducing that env +substitution exercises the identical code path in the app. + +Standing a controller up would have meant enrolling a box with the hub — creating hub-side state +this session is told not to create, and which R-131 records four orphaned instances of. The +substitution the controller performs is instead **mirrored exactly**, including the trap that +`generateValue` prefixes `base64key` with `base64:` (`deploy.go:904`); getting that wrong cost one +false verdict before it was caught (§3.4). + +--- + +## 2. Part 0 — had this ground been covered? + +**No. Neither Campaign 7 nor any gate, `.felhom.yml` field, or CI check tests this property.** +Each negative below names what was searched. + +### 2.1 Campaign 7 tested deployability and health, not persistence + +`felhom.eu/documentation/audits/CAMPAIGN-7-catalog-sweep-2026-07-19.md:38-51` states its method: +per-container terminal health verdict, healthcheck-binary audit, HTTP probe through the real +Traefik ingress, login where scriptable, and a log scan. Its result matrix (`:114`) carries the +columns *pin · MAJOR · deploy · health · http · logs · settle*. **There is no persistence column, +and no step in the method reads where data landed.** + +The proof that this is a gap rather than a technicality is in that same matrix: **papra passed it** +— `deploy ok`, `health **fixed** ×2`, `http 200`, `logs clean` (`:151`) — while its database was +being written to a directory no backup would ever contain. Campaign 7 also demonstrated the +narrower fact that a healthcheck can be a lie (C7-F4, `:210-224`); this sweep demonstrates that a +*truthful* healthcheck is still not evidence about data. + +**What changed since:** nothing that would have closed it. Between Campaign 7 and today the catalog +gained the resolvability gate (2026-07-21), `lifecycle:` (2026-07-21) and R-75's `data_paths:` +(2026-07-26) — image currency, circulation status, and folder *labelling* respectively. + +### 2.2 No gate asserts a volume ⟷ data-path relationship. Searched: + +| Where | What was searched | Result | +|---|---|---| +| `app-catalog-felhom.eu/scripts/` | full listing | exactly two gates, `check-image-pins.py` (syntactic tag check) and `check-image-resolvable.py` (does the tag still exist) — **both about images, neither about volumes** | +| catalog repo CI | `.gitea/workflows`, `.github`, drone/woodpecker/ci config; every non-template YAML | **none exists.** Catalog gates are convention-run, invoked by whoever edits the catalog — this matters for §7 | +| all four repos | `grep -rniE "volume.*(persist\|data.?path\|writable.layer)\|writable.layer\|docker diff\|persistence.(gate\|check)" --include=*.py --include=*.go --include=*.sh` | no check of this kind; the only hits are comments and a test fixture name | +| all gate scripts, four repos | `ls felhom-controller/scripts/*.py felhom.eu/scripts/*.py app-catalog-felhom.eu/scripts/*.py felhom-agent/scripts/*` | 11 gates. The closest by name, `felhom-controller/scripts/docker_run_volume_path_gate.py`, is unrelated: it allowlists `docker … -v` arguments in **controller Go code** so a controller-local host path cannot be mounted from inside a container (v0.125.0 scenario D) | +| `.felhom.yml` schema | `backup:`, `data_paths:`, `lifecycle:`, `deploy_fields:` | none expresses "the app writes here" | + +### 2.3 The two validators that look closest cannot see this class + +`ValidateBackupSpec` (`felhom-controller/internal/appbackup/classify.go`) and `ValidateDataPaths` +(`internal/stacks/datapaths.go:58`) both reject an entry matching no compose bind — real checks, but +of **declaration consistency**, not of runtime behaviour. And both operate on +`ParseComposeClassifiableBinds` (`internal/stacks/classify_binds.go`), which keeps only mounts whose +host side passes `classifyRoot` — i.e. paths under `${USERDATA_PATH}` / `${HDD_PATH}` / +`${IMPORT_PATH}`. **Named volumes are skipped entirely.** + +That is decisive for papra, which has **zero binds**: every one of these validators is vacuously +satisfied by it. It is also decisive for scale — **39 of 53 templates declare no felhom-root bind at +all**, so for three-quarters of the catalog the entire data-protection story is `DumpAppVolumes` +(`internal/backup/backup.go:543`) tarring a named volume, and nothing checks that the volume has +anything in it. + +### 2.4 R-156's evidence, read before building + +`felhom.eu/documentation/tests/campaign10-evidence-2026-07-31/r156-papra-volume.txt` — five numbered +observations: the volume mounted at `/app/data`; `uid=999(nonroot)` with `/app/data` `root root`; +`touch: cannot touch '/app/data/.w': Permission denied`; the volume empty but for a canary the +campaign wrote **as root**; and `db.sqlite` 475136 B in the container's writable layer at +`/app/app-data/`. The detector was built against those observations, and it reproduces all of them. + +--- + +## 3. The detector + +`scripts/check-volume-persistence.py`. **The detector and the gate are the same program** — the +sweep below was produced by running the committed gate, so the durable artifact is the thing that +generated the evidence rather than a paper copy of it. + +Per app: deploy from the template → exercise it into writing data → compare where the data landed +against what the compose mounts. + +### 3.1 What is observed, and why nothing uses `docker exec` + +| | signal | how | +|---|---|---| +| M | mount inventory, by persistence class | `docker inspect .Mounts` | +| D | the writable layer | `docker diff` — Docker **excludes every mounted path** from it, so an entry here is definitionally in no volume and no bind | +| U | the app's real uid/gid | `/proc//status`, **not** `Config.User` — which is empty for papra even though it drops to 999 | +| W | writability of each mount by that uid | host-side `stat` of the mount source | +| E | mount occupancy | host-side walk of the mount source | + +**No `docker exec` anywhere.** Campaign 7 §1.1 recorded that `docker exec` writes its OCI error to +STDOUT, so a *missing* binary printed `executable file not found` and read as **present** — which +made that campaign's entire healthcheck audit report every app honest. Everything here is read from +`docker inspect`, `docker diff`, `/proc` and the host filesystem, so a distroless or shell-less +image is observed exactly like any other. + +### 3.2 Mount classes — the second defect class this exposes + +| class | persists a redeploy? | backed up? | +|---|---|---| +| `named-declared` — in the compose `volumes:` block | yes | **yes** | +| `anonymous` — an image `VOLUME` or unnamed mount (64-hex docker name) | survives a *restart* | **NO** | +| `bind` | yes | per the backup spec | +| `tmpfs` | no | no | + +The anonymous case is deceptive precisely because it survives a restart. `ResolveDockerVolumeNames` +(`felhom-controller/internal/appbackup/appdata.go`) returns `_` only for volumes +**declared in the compose file**, so an anonymous volume is invisible to `DumpAppVolumes` — and a +`compose down` + `up` orphans it and creates a fresh one. Data there is unbacked-up *and* lost on +redeploy, with no symptom until someone looks. + +### 3.3 What counts as "exercised", and what counts as data + +**Exercised.** Every container reaches `running`, and `healthy` where a healthcheck exists; then an +HTTP request the app's *own router* answers. Any status code — including 3xx/4xx/5xx — proves the +request reached application code; a connection refusal (`000`) does not. If that produced no writes +anywhere, a **second-chance pass** walks a wider path list following redirects, so a first-run +wizard is actually reached, and waits again. **An app that still writes nothing is UNDETERMINED, +never CLEAN** — health-check-passing is not data-writing, and conflating the two is what let papra +look fine. + +**Data versus noise.** Three rules, deliberately explicit rather than buried in a heuristic: + +1. **Noise** — `/tmp`, `/run`, `/var/log`, `/var/cache`, package-manager state, `__pycache__`, + `node_modules`, `.cache`, `*.pid`, `*.sock`, `*.log`, `*.pyc`, and the per-container files + Docker itself writes (`/etc/hosts`, `/etc/resolv.conf`, …). +2. **Database signature** — a filename that *proves* a database is in that directory + (`*.sqlite*`, `PG_VERSION`, `ib_logfile*`, `data.mdb`, `dump.rdb`, `MANIFEST-*`, `data.ms`, …). + The strongest signal, and it needs no path heuristics. +3. **Data token** — a path saying "app state" (`data`, `storage`, `uploads`, `library`, `documents`, + `var/lib/postgresql`, …), used only where rule 2 is silent. + +**`A` versus `C` is the whole difficulty**, and rule 3 is where judgement actually lives: + +- `A` (added) — the app **created** this file; it exists only in the writable layer. papra's + `db.sqlite` is an `A`. **DATA is decided from `A` entries only.** +- `C` (changed) — a file that **ships in the image** was touched. Usually a chown sweep and + entirely benign. A `C` on a *database-signature* file is genuinely ambiguous, because an app + writing into a DB shipped in its image looks identical. Those are held as **SUSPECT** and settled + **by bytes**: the file is copied out of the running container and out of a pristine container + created from the same image, and compared. Identical → a chown, recorded and dismissed. + Different → a real write into an image-layer file, which is papra's defect with a different verb. + Unreadable → **UNDETERMINED**, never folded into CLEAN. + +### 3.4 Red-proofs — both directions, before the sweep, and again on every run + +| | claim | result | +|---|---|---| +| **positive** | it flags **papra** | **BROKEN**, reproducing R-156's evidence line for line: `/app/data` EMPTY and `NOT writable by the app's own uid=999 gid=999`, `db.sqlite` in the writable layer at `/app/app-data/db` | +| **negative** | it stays quiet on a known-good app | **vaultwarden CLEAN** — and *demonstrably* good, not merely unflagged (below) | + +The negative is a **proven** negative. `state/survive2.py` deployed vaultwarden, let it write, ran a +real `docker compose down` + `up -d` (no `-v` — that is a redeploy; `down --volumes` is the +uninstall path, Campaign 7 §2), and re-fingerprinted every file by sha256 **and inode**: + +``` +vaultwarden SURVIVES before=4 after=4 survived=2 changed=2 lost=0 + survived: /data::db.sqlite3 <- the database itself, byte-identical, SAME INODE + /data::rsa_key.pem + changed : /data::db.sqlite3-shm , /data::db.sqlite3-wal <- SQLite sidecars, expected on restart +``` + +**The self-test runs on every invocation, in both directions.** Before the gate reports anything it +puts two canary templates through the same prober and refuses to issue a verdict unless it calls +one BROKEN and the other CLEAN. Both are built from a purpose-made image reproducing papra's exact +ownership shape — `/app/data` root-owned, `/app/app-data` owned by the app's uid — and **differ only +in which path the volume is mounted at**, so the canary pair is a live demonstration of R-156 *and* +of its fix, every run. A detector that flags nothing is worse than none: it converts an unexamined +catalog into a documented-clean one. + +### 3.5 Seven defects in the detector, every one found by adjudicating a flag rather than trusting it + +Recorded because each would have put a wrong number in the matrix, and **six of the seven made a +correct app look broken** — the failure mode that gets a gate switched off and then protects nothing. + +| # | defect | what it did | +|---|---|---| +| 1 | **`base64key` lost the controller's `base64:` prefix** (`deploy.go:904`) | bookstack's Laravel `APP_KEY` was invalid, it served 500s, read UNDETERMINED. With the prefix: CLEAN. A harness bug wearing an app defect's clothes — the class Campaign 7 §1.1 and Campaign 10 §4d both record | +| 2 | **`C` entries scored as data** | a linuxserver.io entrypoint re-owning its application tree produced **1305 `C` entries** for calibre-web, 92 of them PNGs under `cps/static/css/images/**`. Called BROKEN. Led to the `A`/`C` split and the byte-level adjudicator (§3.3) | +| 3 | **a cache *directory* escaped the noise filter** | `docker diff` lists directories, so the bytecode cache appeared as a bare `…/crafty/config/__pycache__` entry while its `.pyc` children were filtered by suffix — the directory then being the only surviving entry under a path containing `config`. crafty-controller called BROKEN four times over with its data correctly placed. Fixed by normalising with a trailing slash, covering `node_modules`, `.cache`, `.git` at once | +| 4 | **`postgresql.conf` listed as a database signature** | it is a *config* file the postgres entrypoint writes at init. immich called BROKEN with its PGDATA correctly in its volume, **1831 files** | +| 5 | **path tokens convicted on their own** | onlyoffice unpacks its OWN static assets into the writable layer at first boot — plugin icons, slide-theme `media/`, `web-apps/apps/api/documents/api.js`, **2560 added entries** — while its real data mount received data normally. Rule 3 was **demoted to report-only** | +| 6 | **the structural check asked per container, not per app** | fired on **docmost, immich and claper** — three correct apps, identical shape: the app container's only volume is for user uploads and legitimately empty on a fresh install, while all real state sits in the sibling database container's volume (1540 / 1833 / 1470 files) | +| 7 | **mount rules never consulted the noise list** | the prefixes are written `/run/`, so a bare mount target `/run` matched none of them. privatebin's image declares `VOLUME /run`; the anonymous volume docker made there holds `nginx.pid`, `php-fpm.sock` and s6 fifos, and read as 14 files of unbacked-up customer data | + +Plus one that was not a false verdict but a **reporting** defect, and arguably the worst of the set: + +8. **A finding was being swallowed by a louder one.** gramps-web's structural finding — its *family + tree* landing outside every mount, the entire point of the app — went into the undetermined + bucket, and that bucket is discarded whenever a BROKEN leg exists. Its lesser defect (the + accounts database) convicted first and hid it. A finding that disappears because a different + finding won belongs to the same family as an absent log line read as health, and the fix is that + the structural finding is now reported whatever the verdict. + +**The pattern, which is the real lesson.** Every false positive came from a rule that identified data +by its **name** rather than by evidence of what it is: `C` assumed to be a write, `postgresql.conf` +assumed to be a database, `media/` and `config/` assumed to be customer content, `/run` assumed to be +storage because it was a volume. The rules that never needed correcting are the ones grounded in +something checkable — a database file the app **created**, a mount its own uid **cannot write**, +bytes that **differ** from the image's copy, and *did anything land in a preserved folder at all*. + +**Restarts, stated plainly.** The sweep was restarted from the first app **twice** rather than merge +a matrix built by more than one version of the rules — the call Campaign 10 made restarting run 2b +from cycle 1. Defects 6, 7 and 8 were found after the final pass had begun; all three are in +`classify()`, which reads only what the probe already captured, so those verdicts were **re-derived** +rather than re-measured (`state/reclassify.py`), and then **every non-CLEAN and every flipped app was +re-run live end-to-end** through the final gate so no reported verdict rests on post-processing +(§5.3). Six verdicts changed on re-derivation: claper, docmost, immich, sparkyfitness and zipline +UNDETERMINED→CLEAN, privatebin BROKEN→UNDETERMINED. + +**And one rule was written, red-proofed, and deleted.** A second "are all this directory's children +noise?" filter looked like a sensible belt to defect 3's brace. Its red-proof came back **green**, +which is the tell: `e["added"]` can only ever contain entries that already passed `is_noise`, so the +check is always False — dead code wearing the costume of a safeguard, exactly the trap `CLAUDE.md` +catalogues. It is gone, the code says why it is not there, and +`test_cache_directories_are_filtered_at_ENTRY_level` pins the mechanism that actually does the work. + +## 4. The sweep — all 53 templates + +**43 CLEAN · 3 BROKEN · 7 UNDETERMINED.** UNDETERMINED is counted separately and is never folded +into CLEAN; each one's reason is in §4.2. + +Evidence per app: `evidence//probe.json` — the full probe (mount inventory with occupancy and +writability, the app's real uid, the writable-layer rollup, the adjudicated suspects) plus the +verdict and its reasons. Runner log: `state/gate.log`. + +**How to read the mount column:** `[vol]` a named volume declared in the compose (persisted AND +backed up) · `[ANON]` an anonymous volume from the image's own `VOLUME` directive (persisted across +a restart, **never backed up**, orphaned by a redeploy) · `[bind]` a host bind · `,EMPTY` the mount +received nothing. + +### 4.1 The matrix + +| app | verdict | what the template mounts | where the app actually wrote | mount not writable by app uid | +|---|---|---|---|---| +| `gramps-web` | **BROKEN** | /app/data[vol,EMPTY], /app/media[vol,EMPTY], /tmp[vol,EMPTY] | **/app/users (writable layer)** | — | +| `papra` | **BROKEN** | /app/data[vol,EMPTY] | **/app/app-data/db (writable layer)** | /app/data | +| `wishlist` | **BROKEN** | /data[vol,EMPTY], /usr/src/app/uploads[ANON,EMPTY], /usr/src/app/data[ANON] | /usr/src/app/data(1) | — | +| `bentopdf` | **UNDETERMINED** | — | nothing | — | +| `glance` | **UNDETERMINED** | /app/config[vol,EMPTY] | nothing | — | +| `plant-it` | **UNDETERMINED** | — | nothing | — | +| `privatebin` | **UNDETERMINED** | /tmp[ANON,EMPTY], /var/lib/nginx/tmp[ANON,EMPTY], /srv/data[vol,EMPTY], /run[ANON] | /run(14) | — | +| `recipe-importer` | **UNDETERMINED** | /data[vol,EMPTY] | nothing | — | +| `uptime-kuma` | **UNDETERMINED** | /app/data[vol,EMPTY] | nothing | — | +| `wanderer` | **UNDETERMINED** | /app/uploads[vol,EMPTY], /pb_data[vol], /data/plugins[vol,EMPTY], /meili_data[vol] | /pb_data(6), /meili_data(11) | — | +| `actualbudget` | **CLEAN** | /data[vol] | /data(2) | — | +| `adventurelog` | **CLEAN** | /code/media[vol], /var/lib/postgresql/data[vol] | /code/media(251), /var/lib/postgresql/data(2287) | — | +| `audiobookshelf` | **CLEAN** | /config[vol], /metadata[vol], /audiobooks[bind,EMPTY], /podcasts[bind,EMPTY] | /config(16), /metadata(1) | — | +| `bookstack` | **CLEAN** | /config[vol], /var/lib/mysql[vol] | /config(20), /var/lib/mysql(291) | — | +| `calcom` | **CLEAN** | /var/lib/postgresql/data[vol] | /var/lib/postgresql/data(2261) | — | +| `calibre-web` | **CLEAN** | /config[vol], /cwa-book-ingest[bind,EMPTY], /calibre-library[bind] | /config(11), /calibre-library(3) | — | +| `claper` | **CLEAN** | /app/priv/static/uploads[vol,EMPTY], /var/lib/postgresql/data[vol] | /var/lib/postgresql/data(1470) | — | +| `code-server` | **CLEAN** | /config[vol] | /config(6) | — | +| `crafty-controller` | **CLEAN** | /crafty/servers[vol,EMPTY], /crafty/app/config[vol], /crafty/import[vol,EMPTY], /crafty/backups[vol,EMPTY], /crafty/logs[vol] | /crafty/app/config(16), /crafty/logs(8) | — | +| `docmost` | **CLEAN** | /app/data/storage[vol,EMPTY], /var/lib/postgresql/data[vol], /data[vol] | /var/lib/postgresql/data(1540), /data(3) | — | +| `emby` | **CLEAN** | /config[vol], /media[bind] | /config(46), /media(3) | — | +| `ghost` | **CLEAN** | /var/lib/ghost/content[vol] | /var/lib/ghost/content(7) | — | +| `gitea` | **CLEAN** | /data[vol] | /data(8) | — | +| `gokapi` | **CLEAN** | /app/data[vol], /app/config[vol] | /app/data(4), /app/config(1) | — | +| `grafana` | **CLEAN** | /var/lib/grafana[vol] | /var/lib/grafana(543) | — | +| `home-assistant` | **CLEAN** | /config[vol] | /config(21) | — | +| `homebox` | **CLEAN** | /data[vol] | /data(3) | — | +| `homepage` | **CLEAN** | /app/config[vol], /var/run/docker.sock[bind,EMPTY] | /app/config(3) | — | +| `immich` | **CLEAN** | /cache[vol,EMPTY], /var/lib/postgresql/data[vol], /data[vol], /usr/src/app/upload[bind], /external/photos[bind,EMPTY], /data[ANON,EMPTY] | /var/lib/postgresql/data(1833), /data(3), /usr/src/app/upload(6) | — | +| `jellyfin` | **CLEAN** | /config[vol], /cache[vol], /media[bind] | /config(20), /cache(2), /media(3) | — | +| `kimai` | **CLEAN** | /opt/kimai/var[vol], /var/lib/mysql[vol] | /opt/kimai/var(2547), /var/lib/mysql(279) | — | +| `komga` | **CLEAN** | /config[vol], /data[bind,EMPTY] | /config(11) | — | +| `mealie` | **CLEAN** | /app/data[vol] | /app/data(5) | — | +| `n8n` | **CLEAN** | /home/node/.n8n[vol] | /home/node/.n8n(7) | — | +| `navidrome` | **CLEAN** | /data[vol], /music[bind,EMPTY] | /data(6) | — | +| `nextcloud` | **CLEAN** | /var/www/html/data[bind], /var/www/html[vol], /var/lib/mysql[vol], /data[vol] | /var/www/html/data(69), /var/www/html(4001), /var/lib/mysql(311), /data(3) | — | +| `onlyoffice` | **CLEAN** | /var/www/onlyoffice/Data[vol], /var/log/onlyoffice[vol], /usr/share/fonts/truetype/custom[ANON,EMPTY], /var/lib/onlyoffice[ANON,EMPTY] | /var/www/onlyoffice/Data(3), /var/log/onlyoffice(11) | — | +| `opengist` | **CLEAN** | /opengist[vol] | /opengist(9) | — | +| `outline` | **CLEAN** | /var/lib/outline/data[vol,EMPTY], /var/lib/postgresql/data[vol], /data[vol,EMPTY] | /var/lib/postgresql/data(1655) | — | +| `paperless-ngx` | **CLEAN** | /var/lib/postgresql/data[vol], /data[vol], /usr/src/paperless/export[bind,EMPTY], /usr/src/paperless/data[vol], /usr/src/paperless/media[bind], /usr/src/paperless/consume[bind,EMPTY] | /var/lib/postgresql/data(1927), /data(3), /usr/src/paperless/data(9), /usr/src/paperless/media(1) | — | +| `plex` | **CLEAN** | /media[bind], /config[vol], /transcode[vol,EMPTY] | /media(3), /config(82) | — | +| `radarr` | **CLEAN** | /config[vol], /media/movies[bind,EMPTY], /downloads[bind,EMPTY] | /config(11) | — | +| `rallly` | **CLEAN** | /var/lib/postgresql/data[vol] | /var/lib/postgresql/data(1580) | — | +| `romm` | **CLEAN** | /redis-data[ANON,EMPTY], /romm[ANON,EMPTY], /romm/library[bind,EMPTY], /romm/resources[bind,EMPTY], /romm/config[vol], /var/lib/mysql[vol], /data[vol] | /romm/config(1), /var/lib/mysql(271), /data(4) | — | +| `seerr` | **CLEAN** | /app/config[vol] | /app/config(11) | — | +| `sonarr` | **CLEAN** | /media/tv[bind,EMPTY], /downloads[bind,EMPTY], /config[vol] | /config(11) | — | +| `sparkyfitness` | **CLEAN** | /var/lib/postgresql/data[vol], /app/SparkyFitnessServer/uploads[vol,EMPTY], /app/SparkyFitnessServer/backup[vol,EMPTY] | /var/lib/postgresql/data(1815) | — | +| `tandoor` | **CLEAN** | /opt/recipes/staticfiles[vol], /opt/recipes/mediafiles[vol,EMPTY], /var/lib/postgresql/data[vol] | /opt/recipes/staticfiles(3047), /var/lib/postgresql/data(1984) | — | +| `termix` | **CLEAN** | /app/data[vol] | /app/data(4) | — | +| `vaultwarden` | **CLEAN** | /data[vol] | /data(4) | — | +| `vikunja` | **CLEAN** | /app/vikunja/files[vol,EMPTY], /db[vol] | /db(3) | — | +| `wger` | **CLEAN** | /home/wger/db[vol], /home/wger/media[vol,EMPTY] | /home/wger/db(1) | — | +| `zipline` | **CLEAN** | /zipline/uploads[vol,EMPTY], /zipline/public[vol,EMPTY], /var/lib/postgresql/data[vol] | /var/lib/postgresql/data(1357) | — | + +### 4.2 UNDETERMINED — seven, each with its reason + +**None of these is a pass.** Four are apps that genuinely wrote nothing, which is a limit of the +exercise rather than a property of the template; three could not be started at all. + +| app | why it is undetermined | is this a template defect? | +|---|---|---| +| `bentopdf` | Declares **zero volumes** and wrote nothing. Stateless by design — a browser-side PDF tool. | No — but the gate cannot distinguish "stateless" from "not exercised hard enough", and saying CLEAN would claim a check that was never made. | +| `uptime-kuma` | Healthy, 302 to `/setup`, **0 entries in `docker diff`**, volume empty. v2 creates its database only when the setup wizard is submitted. | Unknown. Needs a completed wizard to decide. | +| `privatebin` | Wrote nothing to `/srv/data`; no paste was ever posted. (Its `/run`, `/tmp`, `/var/lib/nginx/tmp` anonymous volumes are runtime state — see §3.5.6.) | Unknown. Needs a posted paste to decide. | +| `recipe-importer` | Its one mount `/data` stayed empty and the only writes were at `/`. | Unknown. Needs a real import to decide. | +| `glance` | **Crash-loop** (`restarting`, exit 1). It needs `/app/config/glance.yml` and the template mounts an empty config volume without seeding one. | Pre-existing and already recorded — Campaign 7 §6.2 proved the same failure on the pre-campaign pin. Not a persistence finding; the app never ran. | +| `plant-it` | `pull access denied for msdeluise/plant-it, repository does not exist` — no container was created. | No. The app is `lifecycle: abandoned` for exactly this reason (CHANGELOG 2026-07-21); its dead image is the expected end state. | +| `wanderer` | Container `unhealthy`. Its `/pb_data` (6) and `/meili_data` (11) did receive data, so the app is not obviously broken — but a verdict on an unhealthy app would be a verdict on a half-started one. | Unknown. Needs a healthy deploy to decide. | + +### 4.3 What the CLEAN column actually establishes + +For the 43 CLEAN apps the claim is bounded, and worth stating precisely: **the data each app wrote +during a first-boot-plus-one-HTTP-request exercise landed inside a mount the template preserves, +none of it landed in the writable layer, and every mount was writable by the app's own uid.** It is +not a claim that every future write will land there — an app that stores uploads correctly but +writes, say, a search index elsewhere on first use would still read CLEAN here. That is the +residual gap, and it is what the periodic re-run in §7.1 exists for. + + + +## 5. Fixes — two made, each with a survives-a-redeploy proof + +Every corrected path is confirmed by **two independent sources**, never inferred from a directory +name: the shipped image's own environment/`Config.Volumes`, and upstream's reference compose. +Guessing at a data path is how this defect class was created. + +Neither app is deployed anywhere in the fleet (hub `/apps` lists 10 deployed apps; neither is among +them), so neither fix strands anything. **papra is deployed and is therefore NOT fixed** — §6.1. + +### 5.1 `gramps-web` + +Mounted `/app/data`, `/app/media`, `/tmp`. **`/app/data` appears nowhere in the image's environment** +— the application never writes it. Now mounts the eight paths the image itself names, matching +upstream's reference compose: + +| volume | path | what lives there | +|---|---|---| +| `gramps_users` | `/app/users` | `GRAMPSWEB_USER_DB_URI` — the accounts database | +| `gramps_db` | `/root/.gramps/grampsdb` | `GRAMPS_DATABASE_PATH` — **the family tree** | +| `gramps_index` | `/app/indexdir` | `GRAMPSWEB_SEARCH_INDEX_DB_URI` | +| `gramps_media` | `/app/media` | `GRAMPSWEB_MEDIA_BASE_DIR` (was already correct) | +| `gramps_thumb_cache` · `gramps_cache` · `gramps_secret` · `gramps_tmp` | | caches, exports, secret | + +**Proof — `proofs/gramps-web/survive-gramps-web.json`.** Deploy → exercise → `docker compose down` +(no `-v`; that is a redeploy, `down --volumes` is the uninstall path) → `up -d` → re-fingerprint by +sha256 **and inode**: + +``` +gramps-web SURVIVES before=5 after=5 survived=4 changed=1 lost=0 + survived (byte-identical, SAME INODE): + /app/users::users.sqlite <- the accounts database + /root/.gramps/grampsdb::/database.txt <- THE FAMILY TREE + /root/.gramps/grampsdb::/name.txt + /app/thumbnail_cache:: + changed in place: /app/cache::request_cache/ <- a request cache, expected +``` + +### 5.2 `wishlist` + +Mounted `wishlist_data:/data` — again a path the application never writes. The image declares +`VOLUME /usr/src/app/data` and `VOLUME /usr/src/app/uploads` (`Config.Volumes`), and upstream's own +compose mounts exactly those two, so `prod.db` was landing in the **anonymous** volume docker +creates for an image `VOLUME` directive: invisible to `ResolveDockerVolumeNames`, therefore never +backed up, and orphaned by `down` + `up`. Now mounts both real paths. + +**Proof — `proofs/wishlist/survive-wishlist-content.json`.** + +``` +before redeploy: prod.db inode=47151 size=307200 sha256=f35df23b… +after redeploy: prod.db inode=47151 size=307200 sha256=f35df23b… +same_inode=true verdict: SURVIVES +``` + +Same file object, byte-identical content. + +> **A correction, and the limit of this proof.** The generic prover (`state/survive2.py`) first +> reported `DATA-LOST` for wishlist. That was the prover, not the app: its rule counts any change +> in bytes as a loss, and cannot separate "the app modified its own database on boot" (fine — +> vaultwarden's SQLite WAL does exactly that) from "the app recreated it" (data gone). The focused +> check settles it on inode. **And the limit:** the seeded content step 404'd — the signup endpoint +> was a guess and wrong — so this is a **file-level** proof, not a user-row-level one. Byte-identical +> content means nothing in the database was lost, but no row was independently confirmed present +> beforehand. + +### 5.3 Both fixes re-checked with the gate that convicted them + +The strongest available confirmation is the same instrument reaching the opposite verdict, with its +own two-way self-test green on both runs. `proofs/fixed-check/`: + +``` +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +gramps-web CLEAN +wishlist CLEAN +2 clean · 0 broken · 0 undetermined (of 2 in scope) +volume-persistence gate OK rc=0 +``` + +Before the fixes the same gate reported both BROKEN (`state/verify.log`). + +### 5.4 papra — the fix is proven, and deliberately not applied + +Run in the scratch guest only, on a temporary copy of the template +(`proofs/_papra-fix-proof/papra-fix-proof.json`). **A real account was created through papra's own +API in both legs** (`sign-up -> HTTP/1.1 200 OK`), so real user data was written to the database +before each redeploy: + +| leg | `db.sqlite` before | after | | +|---|---|---|---| +| **current** `papra_data:/app/data` | `1e9b695a…` | `5d179bf1…` | **different — the account is gone** | +| **fixed** `papra_data:/app/app-data` | `9dd5cf37…` | `9dd5cf37…` | **byte-identical — it survives** | + +An incidental confirmation from the same run: under the fixed template `/app/data` reports +`present: false`. **The directory does not exist in the papra image at all** — it existed only +because the template mounted a volume there. So the volume was not merely unwritable; it was +mounted at a path the application has no concept of. + +**Limit of this proof:** the document-upload step did not complete (the follow-up +`/api/organizations` call returned `auth.unauthorized` — a session-cookie handling problem in the +harness, not in papra), so the user data verified is the account, not an uploaded document. + +**This fix is not committed.** §6.1. + +--- + +## 6. Referred to the operator — not decided here + +### 6.1 papra is deployed. Its fix would strand the data it is meant to protect. + +**The fix is prepared and deliberately NOT committed.** + +```diff + volumes: +- - papra_data:/app/data ++ - papra_data:/app/app-data +``` + +**Why it is a strand risk.** The volume *name* is unchanged, so no volume is orphaned — but the +mount target changes, which makes `docker compose up -d` **recreate the container**, and the +container's writable layer is where papra's documents and database currently live. Applying this to +a running papra destroys exactly the data the change exists to protect. + +**The honest framing, both halves.** That data is already living on borrowed time: an image bump, a +`RedeployFromEnv`, a restore, or any other container recreation destroys it just the same, and no +backup has ever contained it. But the fix is what *schedules* the loss, on a date the customer does +not choose. Scaling that decision down is not this session's call. + +Note that pushing the template alone destroys nothing: the controller's git-sync updates the two +template files and does **not** restart the stack (`CLAUDE.md`, deploy contract). The change would +take effect at the next deploy. It is still an armed change, which is why it is held. + +**Which box — determined as far as the fences allow, and no further.** + +| checked | result | +|---|---| +| hub `/apps` (read-only) | **Papra: 1 deployment**, 218 MB avg / 250 MB P95 — it is live somewhere | +| `demo-felhom` guest 9201 | **not there** — `docker ps -a` shows no papra container, `docker volume ls` no papra volume | +| hub `/configs` | boxes currently reporting: `c10-soak`, `demo-felhom`, `demo-hp` (`david` —, `drill-r50` BLOCKED, `peti-felhom` DOWN, `sess-f` DOWN) | +| hub `/hosts/` and `/apps/papra` | neither page attributes a deployment to a host | + +So it is on **`c10-soak`** or **`demo-hp`**, both of which live on demo-hp hardware that this +session is fenced from. `c10-soak` is the likelier of the two and would make this moot: it is +Campaign 10's own rig, its hub customer record reads *"Campaign 10 two-storage soak (SCRATCH - +delete at teardown)"*, and papra was deployed on it **to find R-156 in the first place** — so the +"customer data" would be the campaign's own canary. **That is a guess, and it is labelled as one.** +One `docker ps` on either box settles it, and the other session owns both. + +**What is asked:** confirm which box, and then either (a) it is `c10-soak` → push the fix, nothing +of value is lost, or (b) it is a demo box someone cares about → copy `/app/app-data` out of the +running container first (`docker cp papra:/app/app-data `), then push, then copy back into +the new volume. **No migration is shipped here** — §5 of the brief, and inventing one is how this +class of defect gets worse. + +### 6.2 Nothing else needed a ruling + +Both other BROKEN apps have an unambiguous correct path — each confirmed by **two independent +sources**, never inferred — and neither is deployed anywhere in the fleet, so both were fixed +outright (§5). No app was left BROKEN for want of a decision, and no UNDETERMINED app was touched. + +--- + +## 7. Part 4 — the gate + +### 7.1 Where catalog gates live, and what runs them + +They live in `app-catalog-felhom.eu/scripts/` as dependency-free Python that exits non-zero on +violation, and **nothing automated runs them.** There is no CI in this repo — no `.gitea/workflows`, +no `.github`, no drone/woodpecker config; the only YAML outside `templates/` is generated customer +output. Each gate is invoked by whoever edits the catalog, and `CLAUDE.md` says when. + +That is the honest description, and it is stated rather than glossed because this project's record +on the point is unambiguous: R-29 has three orphaned gates and one enforced one, and the enforced +one is the only one that has ever stopped anything. **This gate is at the same enforcement level as +its two siblings — convention, not CI.** What would raise it is named in §8 as a proposed register +entry; building it would mean touching the controller's sync path, which is out of scope here +(§5 fences) and would need a decision about failing a customer's template sync. + +The gate could be built here, so it was. It did not have to wait for `felhom.eu`. + +### 7.2 Why the gate has to be a runtime check — measured, not assumed + +A static audit of all 53 composes — every declared top-level volume attached to a service, no +anonymous mounts, no host binds outside the felhom roots — reports the catalog **completely clean**. +The single hit is `homepage`'s deliberate read-only `/var/run/docker.sock`. + +**It reports papra clean too.** papra's compose is well-formed: it declares `papra_data` and mounts +it at `/app/data`. Nothing in the file is wrong. What is wrong is only observable at runtime. + +So a static gate would have been a gate that could never have caught the defect it was written for — +coverage that isn't. `scripts/check-volume-persistence.py` deploys, exercises, and measures. + +### 7.3 What it refuses + +**Exit 1 — REFUSED, not a warning:** + +- DATA in a container's writable layer (never persisted, never backed up); +- an app-data mount the app's own uid cannot write (R-156's second leg); +- data in an **anonymous** volume (never backed up, orphaned by a redeploy); +- a database-signature file in the writable layer whose bytes **differ** from the image's copy. + +**Exit 2 — UNDETERMINED, and explicitly *not* a clean bill of health:** a container that never +reached running/healthy; nothing written anywhere; a suspect that could not be adjudicated; or — +load-bearing — **the prober failing its own two-way canary**, in which case the gate refuses to +report any verdict at all rather than issue a false all-clear. + +**Exit 0** only when every app in scope is CLEAN. + +### 7.4 The test through the path that actually invokes it + +`scripts/test_check_volume_persistence.py` — **31 tests, no Docker** (the prober is injected). +They drive **`check()`, the function `__main__` calls**, not just the verdict logic: a gate whose +decision logic is tested but whose entry point is not has shipped inert in this project before +(the seam-wiring rule, four recorded instances). + +Covered at the entry point: one broken app ⇒ **rc 1** and the app is named; all clean ⇒ rc 0; +undetermined ⇒ **rc 2, never rc 0**; broken outranks undetermined; out-of-circulation apps skipped +and reported; **a prober that flags nothing ⇒ rc 2**; **a prober that flags everything ⇒ rc 2**. + +Every rule was red-proofed by breaking it and confirming the suite goes red: + +| break | suite | +|---|---| +| score `C` entries as data again (the calibre-web false positive) | **RED** | +| revert the trailing-slash noise normalisation (the crafty false positive) | **RED** | +| fold "wrote nothing" into CLEAN | **RED** | +| warn instead of refuse (rc 0 on broken) | **RED** | +| drop the writable-layer leg (the papra signature) | **RED** | +| let an unresolved suspect fall through to CLEAN | **RED** | +| drop the `base64:` prefix | **RED** | +| *(the deleted all-children-noise rule)* | **GREEN — which is why it was deleted** | + +Registered in `CLAUDE.md` (conventions) and `REUSE.md` (the map), in the same commit. + +--- + +## 8. Findings for later filing — PROPOSED TEXT, NOT FILED + +The register is `felhom.eu/documentation/backlog/{ROADMAP,OPEN-ITEMS}.md`, and `felhom.eu` is +fenced this session. These are drafts for one session to file, in one place. + +**Register grep before proposing** (read-only, `ROADMAP.md` + `OPEN-ITEMS.md`): +`wishlist` -> 1 hit, R-41's deployability gate, unrelated · `gramps` -> 0 · `anonymous` -> 2 hits, +both about the golden bake, unrelated · `ResolveDockerVolumeNames` -> 0 · `DumpAppVolumes` -> 0 · +`writable layer` -> 0 · `check-volume-persistence` -> 0. **Highest filed: R-155.** So these start +at **R-158**. + +### 8.0 First, a process finding — R-156 and R-157 are themselves unfiled + +`grep -c "R-156\|R-157"` over both register files returns **0 / 0**. They exist only inside +`audits/CAMPAIGN-10-two-storage-soak-2026-07-31.md`. This is the identical failure the brief cites +for R-154/R-155 — *"minted in a spike doc and never carried across"* — recurring immediately, on +the very finding this session was commissioned to act on. **R-156 is the reason this work happened +and it is not in the register.** Whoever files the entries below should carry R-156 and R-157 +across at the same time. + +### R-158 — wishlist's data lands in an ANONYMOUS volume: never backed up, orphaned by a redeploy + +**FIXED HERE** (`templates/wishlist/docker-compose.yml`); filed to record the class. + +The image declares `VOLUME /usr/src/app/data` and `VOLUME /usr/src/app/uploads` +(`docker image inspect ghcr.io/cmintey/wishlist:v0.66.0` -> `Config.Volumes`). The template mounted +`wishlist_data:/data` — **a path the application never writes**. So `prod.db` went into the +anonymous volume docker creates for an image `VOLUME` directive. + +**Why this is worse than an ordinary miss: it survives a restart.** `ResolveDockerVolumeNames` +(`felhom-controller/internal/appbackup/appdata.go`) returns `_` only for volumes +**declared in the compose file**, so `DumpAppVolumes` never sees an anonymous one — and +`compose down` + `up` orphans it and mounts a fresh empty one. Meanwhile `wishlist_data` was tarred +nightly as an empty directory. A store that survives a restart, loses on redeploy, and is never in +a backup is harder to notice than papra's, because casual testing finds nothing wrong. + +**The class, not just the app:** any image `VOLUME` directive at a path the template does not +explicitly mount creates unbacked-up storage silently. **`immich-server` has one today** at `/data` +(`Config.Volumes = {"/data":{}}`) — empty when measured, so nothing is at risk now, but anything +immich ever writes there is invisible to backup. Suggested standing rule for `REUSE.md`: *a template +must mount every path in its image's `Config.Volumes`, or state why not.* Checkable statically only +if the image is pulled, which is why it lives in the runtime gate. + +### R-159 — gramps-web persisted three paths and wrote to none of them + +**FIXED HERE** (`templates/gramps-web/docker-compose.yml`). + +The template mounted `/app/data`, `/app/media` and `/tmp`; **`/app/data` appears nowhere in the +image's environment** — the application never writes it. Its accounts database +(`GRAMPSWEB_USER_DB_URI=sqlite:////app/users/users.sqlite`) and **its family tree** +(`GRAMPS_DATABASE_PATH=/root/.gramps/grampsdb`) both landed in the container's writable layer. +Upstream's reference compose persists **eight** paths; this template persisted three, one a phantom. + +Severity above papra's: papra loses documents a customer uploaded, which they may still hold +elsewhere. gramps-web loses the family tree — the artefact the customer *built inside the app*, of +which no other copy exists by construction. + +### R-160 — the gate exists, and nothing automated runs it + +The check R-156 asks for now exists (`scripts/check-volume-persistence.py`, §7). Its enforcement +level is **convention**: the catalog repo has no CI of any kind (`.gitea/workflows`, `.github`, +drone/woodpecker — none), so like its two sibling gates it runs when a person runs it. + +This project's own record says that is not enough: R-29 has three orphaned gates and one enforced +one, and the enforced one is the only one that has ever stopped anything. Two directions, neither +buildable from this repo: + +1. **CI in the catalog repo** — a Gitea Actions runner. Nothing exists to build on today. +2. **Controller-side at sync** — the controller already validates `.felhom.yml` at template load + (`internal/stacks/metadata.go:336-358`) and a persistence assertion could ride the same choke + point. But that needs a runtime probe on a customer box, and a decision about what a failing + sync should do to a customer's working app. **Cross-repo, and a design question, not a task.** + +Interim position, recorded in `CLAUDE.md`: run it when a template's `volumes:` block or image tag +changes, and at the start of every catalog campaign. + +### R-161 — `docker diff` is the only witness, and its failure mode is quiet + +A limitation, not a defect. The gate's power comes from `docker diff` excluding mounted paths, which +makes "in the writable layer" mechanically decidable — an implementation detail of the overlay +storage driver. On a driver where `docker diff` is unsupported or lies, the gate degrades to the +mount-occupancy and writability legs and would not say so. The canary self-test *would* catch it +(the broken canary would stop reporting BROKEN, and the gate would then refuse to report at all), +so it fails closed — but the message it prints would blame the prober rather than the driver. + +--- + +## 9. Teardown — all three layers + +Per `felhom.eu/documentation/PROMPT-TEMPLATE.md` §13. Layer 3 is the one that has been missed every +time (`drill-r50`, `sess-c`, `sess-d`), so it is stated explicitly rather than implied. + +### Layer 1 — the machine + +`pct stop 9301 && pct destroy 9301 --purge`, and its `local-lvm` volume with it. Verified by +`pct list` (no 9301) and `lvs` (no `vm-9301-disk-0`). **Evidence: `state/teardown.txt`.** + +### Layer 2 — the host + +`pvesm status` before and after, and the space actually returned. + +| | `local-lvm` available (KiB) | used | +|---|---|---| +| before the run began | 258 702 410 | 29.27 % | +| end of run, before teardown | 169 968 909 | 53.53 % | +| after teardown | *see `state/teardown.txt`* | | + +The guest's 120 G volume was thin-provisioned on an **over-subscribed** pool (`pct create` warned: +sum of thin volume sizes 402 G against a 348 G pool), which is why a disk watchdog ran throughout +(`state/diskwatch.log`) and why the space actually returning is measured rather than assumed. + +### Layer 3 — the hub + +**Nothing to remove: this run created no hub record of any kind, by design.** + +That is an absence of *creation*, not an absence of checking. The scratch guest ran **no +felhom-controller** and was never enrolled (§1), precisely so no customer or appliance record could +exist to orphan. The hub was touched **read-only** throughout — `/apps`, `/apps/papra`, `/configs`, +`/hosts`, `/hosts/`, over the ClusterIP with Basic auth — to answer "is papra deployed +anywhere?" (§6.1). + +Confirmed against the live hub `/configs` list, unchanged from the pre-run state and containing no +entry for this run: `c10-soak`, `david`, `demo-felhom`, `demo-hp`, `drill-r50`, `peti-felhom`, +`sess-f`. + +The scratch host paths the gate creates for `${HDD_PATH}` / `${IMPORT_PATH}` (`/srv/felhom-gate/…`) +and the `felhom-volgate-canary:1` image existed only inside the guest, so both go with layer 1. + +--- + +## 10. What did not happen, and why + +- **papra was not fixed.** It is deployed, and the fix would destroy the data it protects (§6.1). + Prepared, proven, held. The brief says stop rather than ship a migration; no migration was written. +- **No data migration of any kind was shipped**, for papra or anything else. +- **No UNDETERMINED app was fixed.** Seven remain undecided (§4.2) and none was nudged toward CLEAN + to tidy the table. Four need a deeper exercise than a first-boot HTTP request (a completed setup + wizard, a posted paste, a real import); three could not be started at all. +- **`glance` was not fixed** even though its defect is understood (it crash-loops for want of a + seeded `/app/config/glance.yml`). It is a deployability defect, not a persistence one, already + recorded as pre-existing in Campaign 7 §6.2 — outside this brief's scope. +- **`plant-it` was not touched.** Its image does not resolve; it is `lifecycle: abandoned` for + exactly that reason. +- **Nothing outside the catalog templates was changed.** The controller-side observations + (`ResolveDockerVolumeNames` cannot see anonymous volumes; `ParseComposeClassifiableBinds` cannot + see named volumes at all) are findings for §8, not edits. +- **No commit to `felhom.eu`, no register edit, nothing on `demo-hp`.** The other session owns all + three. Every R-number in §8 is proposed text. +- **No hub-side state was created.** The hub was read only — `/apps`, `/configs`, `/hosts`, + `/apps/papra` — via the ClusterIP with Basic auth. Nothing to clean up, so nothing recorded as a + command (R-131's four orphans). +- **Guest 9201 on `demo-felhom` was read, not touched** — `docker ps -a` and `docker volume ls`, + to establish that papra is not deployed there. +- **`claude-in-chrome` was not used** — unavailable on DooPlex. Every observation is endpoint-level + or filesystem-level, and each is named where it is used. +- **No secret value was written anywhere.** The probe records env var KEYS only; generated + credentials are minted per-run and discarded with the container. + +--- diff --git a/audits/persistence-sweep-2026-08-02/evidence/actualbudget/probe.json b/audits/persistence-sweep-2026-08-02/evidence/actualbudget/probe.json new file mode 100644 index 0000000..e11a1bd --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/actualbudget/probe.json @@ -0,0 +1,54 @@ +{ + "probe": { + "app": "actualbudget", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "actualbudget/actual-server:26.7.0", + "mounts": [ + { + "class": "named-declared", + "files": 2, + "name": "volgate-actualbudget_actualbudget_data", + "sample": [ + ".migrate", + "server-files/account.sqlite" + ], + "source": "/var/lib/docker/volumes/volgate-actualbudget_actualbudget_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "actualbudget", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "actualbudget_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/adventurelog/probe.json b/audits/persistence-sweep-2026-08-02/evidence/adventurelog/probe.json new file mode 100644 index 0000000..fdc06eb --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/adventurelog/probe.json @@ -0,0 +1,222 @@ +{ + "probe": { + "app": "adventurelog", + "containers": [ + { + "diff_added": 8, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "run", + "var", + "code" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var", + "files": [ + "lib" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/lib", + "files": [ + "nginx" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 14, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/seanmorley15/adventurelog-backend:v0.12.1", + "mounts": [ + { + "class": "named-declared", + "files": 251, + "name": "volgate-adventurelog_adventurelog_media", + "sample": [ + "countries+regions+states-v3.1.json", + "flags/ca.png", + "flags/gg.png", + "flags/at.png", + "flags/ck.png", + "flags/eh.png", + "flags/qa.png", + "flags/lc.png", + "flags/io.png", + "flags/es.png", + "flags/gf.png", + "flags/tz.png", + "flags/mx.png", + "flags/vi.png", + "flags/tw.png", + "flags/il.png", + "flags/mk.png", + "flags/bo.png", + "flags/sa.png", + "flags/ly.png", + "flags/bb.png", + "flags/kg.png", + "flags/hr.png", + "flags/tn.png", + "flags/bj.png", + "flags/sh.png", + "flags/eg.png", + "flags/kw.png", + "flags/gn.png", + "flags/vg.png", + "flags/af.png", + "flags/jm.png", + "flags/ug.png", + "flags/je.png", + "flags/ms.png", + "flags/nl.png", + "flags/lv.png", + "flags/pg.png", + "flags/lr.png", + "flags/bl.png" + ], + "source": "/var/lib/docker/volumes/volgate-adventurelog_adventurelog_media/_data", + "target": "/code/media", + "writable_by_app": "yes" + } + ], + "name": "adventurelog", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 65532, + "health": "healthy", + "image": "ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1", + "mounts": [], + "name": "adventurelog-frontend", + "restarts": null, + "status": "running", + "uid": 65532 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgis/postgis:16-3.5-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 2287, + "name": "volgate-adventurelog_adventurelog_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-adventurelog_adventurelog_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "adventurelog-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "adventurelog_media", + "adventurelog_postgres_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/audiobookshelf/probe.json b/audits/persistence-sweep-2026-08-02/evidence/audiobookshelf/probe.json new file mode 100644 index 0000000..5a8b7a7 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/audiobookshelf/probe.json @@ -0,0 +1,116 @@ +{ + "probe": { + "app": "audiobookshelf", + "containers": [ + { + "diff_added": 4, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "metadata", + "audiobooks", + "podcasts", + "config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/", + "files": [ + "metadata", + "audiobooks", + "podcasts", + "config" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/advplyr/audiobookshelf:2.35.1", + "mounts": [ + { + "class": "named-declared", + "files": 16, + "name": "volgate-audiobookshelf_audiobookshelf_config", + "sample": [ + "absdatabase.sqlite", + "migrations/v2.17.5-remove-host-from-feed-urls.js", + "migrations/v2.17.4-use-subfolder-for-oidc-redirect-uris.js", + "migrations/v2.17.0-uuid-replacement.js", + "migrations/v2.19.1-copy-title-to-library-items.js", + "migrations/v2.15.0-series-column-unique.js", + "migrations/v2.19.4-improve-podcast-queries.js", + "migrations/v2.26.0-create-auth-tables.js", + "migrations/v2.33.0-add-discover-query-indexes.js", + "migrations/v2.17.7-add-indices.js", + "migrations/v2.35.0-add-last-refresh-token.js", + "migrations/v2.20.0-improve-author-sort-queries.js", + "migrations/v2.17.6-share-add-isdownloadable.js", + "migrations/v2.15.1-reindex-nocase.js", + "migrations/v2.17.3-fk-constraints.js", + "migrations/v2.15.2-index-creation.js" + ], + "source": "/var/lib/docker/volumes/volgate-audiobookshelf_audiobookshelf_config/_data", + "target": "/config", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 1, + "name": "volgate-audiobookshelf_audiobookshelf_metadata", + "sample": [ + "logs/daily/2026-08-02.txt" + ], + "source": "/var/lib/docker/volumes/volgate-audiobookshelf_audiobookshelf_metadata/_data", + "target": "/metadata", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/audiobooks", + "target": "/audiobooks", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/podcasts", + "target": "/podcasts", + "writable_by_app": "yes" + } + ], + "name": "audiobookshelf", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "audiobookshelf_config", + "audiobookshelf_metadata" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/bentopdf/probe.json b/audits/persistence-sweep-2026-08-02/evidence/bentopdf/probe.json new file mode 100644 index 0000000..f096d15 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/bentopdf/probe.json @@ -0,0 +1,70 @@ +{ + "probe": { + "app": "bentopdf", + "containers": [ + { + "diff_added": 6, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "var", + "etc" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "nginx" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var", + "files": [ + "cache" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 14, + "diff_unresolved": [], + "exit": 0, + "gid": 101, + "health": "healthy", + "image": "ghcr.io/alam00000/bentopdf:v2.8.6", + "mounts": [], + "name": "bentopdf", + "restarts": null, + "status": "running", + "uid": 101 + } + ], + "declared_volumes": [], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "nothing was written to any mount and nothing data-classified in any writable layer \u2014 the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN" + ], + "verdict": "UNDETERMINED", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/bookstack/probe.json b/audits/persistence-sweep-2026-08-02/evidence/bookstack/probe.json new file mode 100644 index 0000000..d75b536 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/bookstack/probe.json @@ -0,0 +1,246 @@ +{ + "probe": { + "app": "bookstack", + "containers": [ + { + "diff_added": 247, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/", + "files": [ + "app", + "etc", + "usr", + "defaults", + "run", + "var" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/etc", + "files": [ + "passwd-", + "group-", + "crontabs", + "logrotate.d", + "s6-overlay" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/crontabs", + "files": [ + "root" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay", + "files": [ + "s6-rc.d" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d", + "files": [ + "init-adduser" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d/init-adduser", + "files": [ + "branding" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "lib" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/var", + "files": [ + "lib", + "log" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/lib", + "files": [ + "nginx" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 277, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "lscr.io/linuxserver/bookstack:26.05.2", + "mounts": [ + { + "class": "named-declared", + "files": 20, + "name": "volgate-bookstack_bookstack_config", + "sample": [ + ".migrations", + "log/php/error.log", + "log/nginx/access.log", + "log/nginx/error.log", + "keys/cert.key", + "keys/cert.crt", + "php/www2.conf", + "php/php-local.ini", + "www/.env", + "www/index.html", + "www/framework/sessions/t9vuOg4XNVgyKgaD1xXXg5Bums6HCmTSiBGnhjPu", + "www/framework/sessions/GTO4GD6UJW2fK5ABAye7PVZKui376YILkTqU0I0e", + "nginx/resolver.conf", + "nginx/worker_processes.conf", + "nginx/nginx.conf.sample", + "nginx/nginx.conf", + "nginx/ssl.conf.sample", + "nginx/ssl.conf", + "nginx/site-confs/default.conf", + "nginx/site-confs/default.conf.sample" + ], + "source": "/var/lib/docker/volumes/volgate-bookstack_bookstack_config/_data", + "target": "/config", + "writable_by_app": "yes" + } + ], + "name": "bookstack", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "mariadb:12.3", + "mounts": [ + { + "class": "named-declared", + "files": 291, + "name": "volgate-bookstack_bookstack_db_data", + "sample": [ + "undo002", + "tc.log", + "mariadb_upgrade_info", + "ibtmp1", + "aria_log.00000001", + "multi-master.info", + "ddl_recovery.log", + "ib_logfile0", + "undo003", + "ibdata1", + ".my-healthcheck.cnf", + "aria_log_control", + "undo001", + "ib_buffer_pool", + "bookstack/api_tokens.ibd", + "bookstack/password_resets.ibd", + "bookstack/entity_page_data.frm", + "bookstack/settings.ibd", + "bookstack/email_confirmations.ibd", + "bookstack/views.ibd", + "bookstack/references.ibd", + "bookstack/attachments.frm", + "bookstack/entity_container_data.frm", + "bookstack/deletions.ibd", + "bookstack/permission_role.frm", + "bookstack/favourites.ibd", + "bookstack/deletions.frm", + "bookstack/page_revisions.ibd", + "bookstack/user_invites.frm", + "bookstack/joint_permissions.ibd", + "bookstack/bookshelves_books.frm", + "bookstack/activities.frm", + "bookstack/entity_container_data.ibd", + "bookstack/comments.ibd", + "bookstack/entity_permissions.frm", + "bookstack/email_confirmations.frm", + "bookstack/password_resets.frm", + "bookstack/settings.frm", + "bookstack/cache.ibd", + "bookstack/attachments.ibd" + ], + "source": "/var/lib/docker/volumes/volgate-bookstack_bookstack_db_data/_data", + "target": "/var/lib/mysql", + "writable_by_app": "yes" + } + ], + "name": "bookstack-db", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "bookstack_config", + "bookstack_db_data" + ], + "env_keys": [ + "APP_KEY", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/calcom/probe.json b/audits/persistence-sweep-2026-08-02/evidence/calcom/probe.json new file mode 100644 index 0000000..33823c1 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/calcom/probe.json @@ -0,0 +1,1052 @@ +{ + "probe": { + "app": "calcom", + "containers": [ + { + "diff_added": 32, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "root", + "calcom" + ] + }, + { + "added": [ + ".turbo" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/calcom", + "files": [ + ".turbo" + ] + }, + { + "added": [ + "cache" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/calcom/.turbo", + "files": [ + "cache" + ] + }, + { + "added": [ + "install-state.gz" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/calcom/.yarn", + "files": [ + "install-state.gz" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps", + "files": [ + "web" + ] + }, + { + "added": [ + "core.642" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/calcom/apps/web", + "files": [ + "core.642" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/calcom/apps/web/.next", + "files": [ + "required-server-files.js", + "required-server-files.json", + "server", + "standalone", + "routes-manifest.json", + "static" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server", + "files": [ + "edge", + "app", + "chunks" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server/app", + "files": [ + "icons.rsc", + "icons.segments", + "icons.html" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server/app/icons.segments", + "files": [ + "_index.segment.rsc", + "icons", + "_full.segment.rsc" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server/app/icons.segments/icons", + "files": [ + "__PAGE__.segment.rsc" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server/chunks", + "files": [ + "[root-of-the-server]__f12ec124._.js.map", + "[root-of-the-server]__c77cccb7._.js", + "packages_i18n_locales_cs_common_json_d39b1fcc._.js.map", + "packages_i18n_locales_fr_common_json_77853058._.js.map", + "[root-of-the-server]__3f3877b3._.js.map", + "packages_i18n_locales_fi_common_json_8b3bce10._.js.map", + "[root-of-the-server]__50a48f36._.js", + "[root-of-the-server]__3f3877b3._.js", + "packages_i18n_locales_es_common_json_29e328fd._.js.map", + "packages_i18n_locales_no_common_json_636c999e._.js", + "[root-of-the-server]__2bc079b8._.js.map", + "[root-of-the-server]__893775cf._.js.map", + "packages_i18n_locales_da_common_json_0e2d691b._.js", + "packages_i18n_locales_pt_common_json_53d5c317._.js.map", + "packages_i18n_locales_uk_common_json_296a1d00._.js.map", + "[root-of-the-server]__2bc079b8._.js", + "packages_i18n_locales_ru_common_json_72f89954._.js.map", + "packages_434872fd._.js", + "packages_i18n_locales_pl_common_json_8c1dd4d8._.js", + "[root-of-the-server]__5e7824d2._.js", + "packages_i18n_locales_it_common_json_f0e11e3c._.js.map", + "packages_i18n_locales_ar_common_json_1e5af418._.js.map", + "packages_i18n_locales_no_common_json_636c999e._.js.map", + "packages_4f6339fb._.js", + "_a2d81011._.js.map", + "packages_i18n_locales_ru_common_json_72f89954._.js", + "packages_ee60edfc._.js", + "packages_i18n_locales_hu_common_json_30ecdbf5._.js.map", + "packages_lib_b193ee5c._.js", + "packages_i18n_locales_it_common_json_f0e11e3c._.js", + "_4a7f8ec5._.js.map", + "[root-of-the-server]__744caedd._.js.map", + "packages_1dd60f33._.js", + "packages_i18n_locales_nl_common_json_b6616134._.js", + "packages_i18n_locales_tr_common_json_eb2695e6._.js.map", + "[root-of-the-server]__b9a70756._.js", + "packages_i18n_locales_fr_common_json_77853058._.js", + "packages_i18n_locales_bn_common_json_38f4a12e._.js.map", + "packages_i18n_locales_ro_common_json_05bd7f5b._.js", + "packages_i18n_locales_sr_common_json_57509b04._.js" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server/chunks/ssr", + "files": [ + "packages_i18n_locales_es-419_common_json_baf1ab09._.js", + "packages_i18n_locales_km_common_json_8a510448._.js", + "packages_adab210d._.js", + "packages_i18n_locales_pt-BR_common_json_af0d4a85._.js", + "packages_884b74dd._.js.map", + "packages_i18n_locales_ru_common_json_72f89954._.js.map", + "37f04_app_(use-page-wrapper)_(main-nav)_event-types_EventTypesWrapper_tsx_fbc56ecd._.js", + "node_modules_835d2dc4._.js", + "packages_i18n_locales_de_common_json_3e748837._.js.map", + "packages_i18n_locales_no_common_json_636c999e._.js", + "_888ddb11._.js", + "packages_i18n_locales_az_common_json_3a012cbd._.js", + "[root-of-the-server]__c8cf6f79._.js.map", + "packages_i18n_locales_km_common_json_8a510448._.js.map", + "_6589c6d7._.js", + "_a49e8e25._.js.map", + "packages_i18n_locales_he_common_json_31242d7f._.js.map", + "packages_i18n_locales_bn_common_json_38f4a12e._.js", + "packages_i18n_locales_bn_common_json_38f4a12e._.js.map", + "packages_i18n_locales_ro_common_json_05bd7f5b._.js.map", + "[root-of-the-server]__fe82c76d._.js.map", + "packages_i18n_locales_vi_common_json_1b83912d._.js.map", + "packages_i18n_locales_sv_common_json_aa290c64._.js.map", + "apps_web_modules_signup-view_tsx_b3c82830._.js", + "[root-of-the-server]__fe82c76d._.js", + "packages_i18n_locales_ca_common_json_47b26909._.js.map", + "apps_web_components_apps_alby_Setup_tsx_ac56b8ae._.js", + "_02808c2d._.js", + "packages_i18n_locales_eu_common_json_111e41e9._.js.map", + "packages_i18n_locales_hu_common_json_30ecdbf5._.js.map", + "_caa71245._.js", + "packages_i18n_locales_ko_common_json_599c8810._.js.map", + "packages_i18n_locales_tr_common_json_eb2695e6._.js.map", + "packages_i18n_locales_cs_common_json_d39b1fcc._.js", + "packages_5ed70ad0._.js", + "packages_i18n_locales_fr_common_json_77853058._.js", + "packages_cfd58bda._.js.map", + "node_modules_c8de397d._.js", + "packages_i18n_locales_bg_common_json_9a19fba6._.js.map", + "packages_i18n_locales_az_common_json_3a012cbd._.js.map" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server/edge", + "files": [ + "chunks" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/calcom/apps/web/.next/server/edge/chunks", + "files": [ + "node_modules_cafe7152._.js", + "node_modules_cafe7152._.js.map" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone", + "files": [ + "packages", + "apps" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps", + "files": [ + "web" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web", + "files": [ + ".next", + "package.json", + "server.js" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next", + "files": [ + "required-server-files.json", + "routes-manifest.json", + "server", + "required-server-files.js" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server", + "files": [ + "chunks", + "app", + "edge" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server/app", + "files": [ + "icons.rsc", + "icons.segments", + "icons.html" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server/app/icons.segments", + "files": [ + "_index.segment.rsc", + "icons", + "_full.segment.rsc" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server/app/icons.segments/icons", + "files": [ + "__PAGE__.segment.rsc" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server/chunks", + "files": [ + "packages_i18n_locales_ro_common_json_05bd7f5b._.js", + "packages_i18n_locales_sr_common_json_57509b04._.js", + "packages_i18n_locales_da_common_json_0e2d691b._.js", + "[root-of-the-server]__50a48f36._.js", + "_e2ba1e62._.js", + "[root-of-the-server]__893775cf._.js", + "packages_c7792ae5._.js", + "[root-of-the-server]__ff8d28f4._.js", + "packages_i18n_locales_et_common_json_e1fa698b._.js", + "packages_i18n_locales_el_common_json_3e4846a8._.js", + "_4a7f8ec5._.js", + "packages_434872fd._.js", + "packages_i18n_locales_ca_common_json_47b26909._.js", + "packages_i18n_locales_tr_common_json_eb2695e6._.js", + "[root-of-the-server]__1111b53d._.js", + "packages_i18n_locales_hu_common_json_30ecdbf5._.js", + "packages_i18n_locales_vi_common_json_1b83912d._.js", + "packages_i18n_locales_pt_common_json_53d5c317._.js", + "[root-of-the-server]__744caedd._.js", + "packages_i18n_locales_no_common_json_636c999e._.js", + "packages_i18n_locales_fi_common_json_8b3bce10._.js", + "packages_i18n_locales_fr_common_json_77853058._.js", + "packages_i18n_locales_it_common_json_f0e11e3c._.js", + "_a2d81011._.js", + "packages_4f6339fb._.js", + "[root-of-the-server]__798afc22._.js", + "[root-of-the-server]__f12ec124._.js", + "[root-of-the-server]__5e7824d2._.js", + "packages_i18n_locales_eu_common_json_111e41e9._.js", + "[root-of-the-server]__3f3877b3._.js", + "packages_1dd60f33._.js", + "packages_i18n_locales_sv_common_json_aa290c64._.js", + "packages_i18n_locales_he_common_json_31242d7f._.js", + "packages_i18n_locales_es-419_common_json_baf1ab09._.js", + "packages_i18n_locales_bg_common_json_9a19fba6._.js", + "packages_i18n_locales_km_common_json_8a510448._.js", + "packages_i18n_locales_ar_common_json_1e5af418._.js", + "packages_i18n_locales_nl_common_json_b6616134._.js", + "packages_i18n_locales_es_common_json_29e328fd._.js", + "packages_i18n_locales_sk-SK_common_json_d10ea8ba._.js" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server/chunks/ssr", + "files": [ + "packages_i18n_locales_nl_common_json_b6616134._.js", + "_f52de682._.js", + "packages_cfd58bda._.js", + "_7e39e62d._.js", + "packages_i18n_locales_sr_common_json_57509b04._.js", + "_caa71245._.js", + "packages_12eef197._.js", + "packages_e3130aa4._.js", + "packages_i18n_locales_bg_common_json_9a19fba6._.js", + "packages_i18n_locales_el_common_json_3e4846a8._.js", + "packages_i18n_locales_en_common_json_535840c5._.js", + "37f04_app_(use-page-wrapper)_(main-nav)_event-types_EventTypesWrapper_tsx_fbc56ecd._.js", + "packages_i18n_locales_it_common_json_f0e11e3c._.js", + "packages_i18n_locales_az_common_json_3a012cbd._.js", + "_d4e34b7c._.js", + "apps_web_modules_signup-view_tsx_b3c82830._.js", + "packages_8a694f80._.js", + "packages_i18n_locales_pl_common_json_8c1dd4d8._.js", + "packages_i18n_locales_pt-BR_common_json_af0d4a85._.js", + "node_modules_835d2dc4._.js", + "packages_i18n_locales_de_common_json_3e748837._.js", + "packages_i18n_locales_es_common_json_29e328fd._.js", + "[root-of-the-server]__c8cf6f79._.js", + "packages_884b74dd._.js", + "packages_i18n_locales_ar_common_json_1e5af418._.js", + "packages_i18n_locales_zh-TW_common_json_30386da2._.js", + "packages_i18n_locales_sk-SK_common_json_d10ea8ba._.js", + "packages_i18n_locales_vi_common_json_1b83912d._.js", + "node_modules_c8de397d._.js", + "packages_i18n_locales_sv_common_json_aa290c64._.js", + "packages_i18n_locales_eu_common_json_111e41e9._.js", + "_a7b5617d._.js", + "packages_i18n_locales_et_common_json_e1fa698b._.js", + "packages_i18n_locales_uk_common_json_296a1d00._.js", + "packages_i18n_locales_ko_common_json_599c8810._.js", + "_888ddb11._.js", + "_02808c2d._.js", + "_65ddf955._.js", + "packages_i18n_locales_fi_common_json_8b3bce10._.js", + "_a49e8e25._.js" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server/edge", + "files": [ + "chunks" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/apps/web/.next/server/edge/chunks", + "files": [ + "node_modules_cafe7152._.js" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages", + "files": [ + "i18n" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n", + "files": [ + "locales" + ] + }, + { + "added": [], + "changed_count": 37, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales", + "files": [ + "ca", + "es", + "eu", + "bn", + "zh-TW", + "it", + "uk", + "ar", + "et", + "he", + "hu", + "nl", + "no", + "pl", + "cs", + "en", + "ja", + "ko", + "pt-BR", + "ru", + "sk-SK", + "az", + "de", + "es-419", + "fi", + "km", + "pt", + "ro", + "sr", + "da", + "el", + "fr", + "sv", + "tr", + "vi", + "zh-CN", + "bg" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/ar", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/az", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/bg", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/bn", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/ca", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/cs", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/da", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/de", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/el", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/en", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/es", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/es-419", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/et", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/eu", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/fi", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/fr", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/he", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/hu", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/it", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/ja", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/km", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/ko", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/nl", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/no", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/pl", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/pt", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/pt-BR", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/ro", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/ru", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/sk-SK", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/sr", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/sv", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/tr", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/uk", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/vi", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/zh-CN", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/standalone/packages/i18n/locales/zh-TW", + "files": [ + "common.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/.next/static", + "files": [ + "chunks" + ] + }, + { + "added": [], + "changed_count": 29, + "db_signature": false, + "dir": "/calcom/apps/web/.next/static/chunks", + "files": [ + "d518deaad10f8a7a.js", + "56c8dc4acf9d3fba.js", + "412ccb430b31f67f.js.map", + "c3af3a5c67cf1a21.js", + "f0130b66b293b36e.js", + "67e22104658d8536.js", + "a2312aa035e3e2c3.js", + "3ae9469a39daf464.js.map", + "409e2c8a6915c62d.js", + "2f48dbd4f55dd478.js", + "87004a6343d1f3eb.js.map", + "44242c73fbe049c7.js.map", + "0c986acf81905e78.js", + "944d7159fa96470b.js.map", + "1eea3a80d000b6b0.js", + "8a56a732245ecf33.js", + "af41f9b7b2d364b9.js", + "2fcfe704f78758f0.js.map", + "629857a336e1a88e.js", + "3ab04bdc710bad27.js", + "64907a4e3b230e3a.js.map", + "06b0798a1ac20775.js", + "90b610e574fd1bbb.js", + "d469b3c2bf926f57.js", + "aa5fb0f4618c9f5b.js", + "25b31ecc9eb74275.js.map", + "9b8259867b170934.js", + "8c127438fb495cd9.js", + "a02b9d5147e11678.js.map" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/calcom/apps/web/public", + "files": [ + "embed" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/calcom/apps/web/public/embed", + "files": [ + "embed.js", + "preview.js" + ] + }, + { + "added": [ + ".config", + ".npm" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config", + ".npm" + ] + }, + { + "added": [ + "turborepo" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config", + "files": [ + "turborepo" + ] + }, + { + "added": [ + "telemetry.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config/turborepo", + "files": [ + "telemetry.json" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 578, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "starting", + "image": "calcom/cal.com:v6.2.0", + "mounts": [], + "name": "calcom", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 2261, + "name": "volgate-calcom_calcom_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-calcom_calcom_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "calcom-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "calcom_postgres_data" + ], + "env_keys": [ + "CALENDSO_ENCRYPTION_KEY", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "NEXTAUTH_SECRET", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/calibre-web/probe.json b/audits/persistence-sweep-2026-08-02/evidence/calibre-web/probe.json new file mode 100644 index 0000000..7c0155c --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/calibre-web/probe.json @@ -0,0 +1,3132 @@ +{ + "probe": { + "app": "calibre-web", + "containers": [ + { + "diff_added": 494, + "diff_benign_db_touches": [ + { + "db_signature": true, + "dir": "/app/calibre-web-automated/empty_library", + "files": [ + "app.db", + "metadata.db" + ], + "paths": [ + "/app/calibre-web-automated/empty_library/app.db", + "/app/calibre-web-automated/empty_library/metadata.db" + ], + "why": "byte-identical to the image copy \u2014 a chown/chmod sweep, not a write" + }, + { + "db_signature": true, + "dir": "/app/calibre-web-automated/empty_library", + "files": [ + "app.db", + "metadata.db" + ], + "paths": [ + "/app/calibre-web-automated/empty_library/app.db", + "/app/calibre-web-automated/empty_library/metadata.db" + ], + "why": "byte-identical to the image copy \u2014 a chown/chmod sweep, not a write" + } + ], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "app", + "run", + "root", + "usr", + "defaults" + ] + }, + { + "added": [ + "CWA_STABLE_RELEASE" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/app", + "files": [ + "CWA_STABLE_RELEASE" + ] + }, + { + "added": [ + ".qt6_processed" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/calibre", + "files": [ + ".qt6_processed" + ] + }, + { + "added": [], + "changed_count": 18, + "db_signature": false, + "dir": "/app/calibre-web-automated", + "files": [ + "messages.pot", + "requirements.txt", + ".editorconfig", + "empty_library", + "metadata_temp", + "babel.cfg", + "dirs.json", + "kobo_sync_utils.py", + "koreader", + "pyproject.toml", + "run_tests.sh", + "scripts", + "CONTRIBUTORS", + "kubernetes", + "optional-requirements.txt", + "metadata_change_logs", + "cps", + "cps.py" + ] + }, + { + "added": [ + "cache" + ], + "changed_count": 40, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps", + "files": [ + "cache" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/cw_advocate", + "files": [ + "addrvalidator.py", + "connection.py", + "connectionpool.py", + "__init__.py", + "adapters.py", + "api.py", + "exceptions.py", + "poolmanager.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/cw_login", + "files": [ + "config.py", + "login_manager.py", + "mixins.py", + "signals.py", + "utils.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/metadata_provider", + "files": [ + "amazonjp.py", + "comicvine.py", + "douban.py", + "google.py", + "hardcover.py", + "lubimyczytac.py", + "scholar.py", + "dnb.py", + "ibdb.py", + "kobo.py", + "litres.py", + "amazon.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/progress_syncing", + "files": [ + "models.py", + "protocols", + "settings.py", + "README.md", + "__init__.py", + "checksums" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/progress_syncing/checksums", + "files": [ + "__init__.py", + "koreader.py", + "manager.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/progress_syncing/protocols", + "files": [ + "__init__.py", + "kosync.py" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/services", + "files": [ + "Metadata.py", + "__init__.py", + "goodreads_support.py", + "hardcover.py", + "worker.py", + "SyncToken.py", + "background_scheduler.py", + "gmail.py", + "simpleldap.py" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static", + "files": [ + "koplugin.zip", + "favicon.ico", + "icon.svg", + "img", + "js", + "locale", + "standard_fonts", + "user-profile-data", + "cmaps", + "css", + "generic_cover.svg", + "icon.png" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/cmaps", + "files": [ + "Adobe-CNS1-4.bcmap", + "B5-H.bcmap", + "ETHK-B5-V.bcmap", + "GBKp-EUC-V.bcmap", + "KSC-H.bcmap", + "UniGB-UTF8-V.bcmap", + "Ext-RKSJ-H.bcmap", + "KSCms-UHC-HW-H.bcmap", + "KSCpc-EUC-V.bcmap", + "Roman.bcmap", + "UniJIS-UCS2-V.bcmap", + "78-EUC-V.bcmap", + "Adobe-Japan1-2.bcmap", + "GBK2K-V.bcmap", + "HKm314-B5-H.bcmap", + "HKm471-B5-H.bcmap", + "UniJISX0213-UTF32-H.bcmap", + "Adobe-Korea1-2.bcmap", + "UniGB-UTF16-V.bcmap", + "Adobe-GB1-4.bcmap", + "Adobe-Korea1-UCS2.bcmap", + "HKscs-B5-V.bcmap", + "RKSJ-V.bcmap", + "UniJIS2004-UTF32-H.bcmap", + "UniKS-UTF32-H.bcmap", + "CNS1-V.bcmap", + "Ext-V.bcmap", + "GBTpc-EUC-H.bcmap", + "H.bcmap", + "UniJIS-UTF16-H.bcmap", + "UniJIS2004-UTF16-V.bcmap", + "Ext-H.bcmap", + "KSC-V.bcmap", + "LICENSE", + "UniJIS-UTF8-V.bcmap", + "CNS2-V.bcmap", + "Adobe-Japan1-3.bcmap", + "CNS-EUC-V.bcmap", + "UniGB-UTF8-H.bcmap", + "UniJIS-UTF16-V.bcmap" + ] + }, + { + "added": [], + "changed_count": 19, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css", + "files": [ + "query-builder.default.min.css", + "epub_themes.css", + "kthoom.css", + "main.css", + "query_builder.css", + "upload.css", + "caliBlur_override.css", + "duplicates-notifications.css", + "img", + "reader.css", + "cwa.css", + "images", + "libs", + "listen.css", + "popup.css", + "style.css", + "text.css", + "caliBlur.css", + "fonts" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/fonts", + "files": [ + "glyphicons-halflings-regular.ttf", + "glyphicons-halflings-regular.woff", + "glyphicons-halflings-regular.woff2", + "fontello.eot", + "fontello.ttf", + "fontello.woff", + "GrandHotel-Regular.ttf", + "fontello.svg", + "glyphicons-halflings-regular.eot", + "glyphicons-halflings-regular.svg" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images", + "files": [ + "black-10.png", + "black-25.png", + "black-33.png", + "caliblur", + "icomoon", + "patterns" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/caliblur", + "files": [ + "blur-noise.png", + "light-mode-light.png", + "light-mode-light.webp", + "blur-noise.webp", + "light-mode-dark.png", + "light-mode-dark.webp", + "blur-dark.png", + "blur-dark.webp", + "blur-light.png", + "blur-light.webp" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon", + "files": [ + "credits.txt", + "entypo-25px-000000", + "entypo-25px-ffffff", + "free-25px-000000", + "free-25px-ffffff" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/entypo-25px-000000", + "files": [ + "PNG", + "SVG" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/entypo-25px-000000/PNG", + "files": [ + "loop.png", + "music.png", + "play.png", + "shuffle.png", + "arrow.png", + "first.png", + "last.png", + "list2.png", + "pause.png", + "volume.png", + "cart.png", + "list.png" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/entypo-25px-000000/SVG", + "files": [ + "list2.svg", + "loop.svg", + "music.svg", + "pause.svg", + "volume.svg", + "first.svg", + "list.svg", + "play.svg", + "shuffle.svg", + "arrow.svg", + "cart.svg", + "last.svg" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/entypo-25px-ffffff", + "files": [ + "PNG", + "SVG" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/entypo-25px-ffffff/PNG", + "files": [ + "loop.png", + "volume.png", + "cart.png", + "first.png", + "last.png", + "music.png", + "pause.png", + "play.png", + "shuffle.png", + "arrow.png", + "list.png", + "list2.png" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/entypo-25px-ffffff/SVG", + "files": [ + "arrow.svg", + "first.svg", + "last.svg", + "list2.svg", + "loop.svg", + "play.svg", + "cart.svg", + "list.svg", + "music.svg", + "pause.svg", + "shuffle.svg", + "volume.svg" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/free-25px-000000", + "files": [ + "PNG", + "SVG" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/free-25px-000000/PNG", + "files": [ + "spinner.png" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/free-25px-000000/SVG", + "files": [ + "spinner.svg" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/free-25px-ffffff", + "files": [ + "PNG", + "SVG" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/free-25px-ffffff/PNG", + "files": [ + "spinner.png" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/icomoon/free-25px-ffffff/SVG", + "files": [ + "spinner.svg" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/images/patterns", + "files": [ + "pool_table.png", + "rubber_grip.png", + "tasky_pattern.png", + "wood_pattern.png", + "credits.txt", + "pinstriped_suit_vertical.png", + "textured_paper.png", + "tweed.png", + "wood_pattern_dark.png", + "woven.png" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/img", + "files": [ + "clear.png", + "loading.gif" + ] + }, + { + "added": [], + "changed_count": 16, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/libs", + "files": [ + "bootstrap-datepicker3.css.map", + "bootstrap-theme.min.css.map", + "bootstrap-wysihtml5-0.0.3.css", + "bootstrap.min.css", + "bootstrap.min.css.map", + "normalize.css", + "bootstrap-theme.min.css", + "images", + "typeahead.css", + "bootstrap-datepicker3.min.css", + "bootstrap-editable.css", + "viewer.css", + "Djvu_html5.css", + "bar-ui.css", + "bootstrap-select.min.css", + "bootstrap-table.min.css" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/css/libs/images", + "files": [ + "toolbarButton-sidebarToggle.svg", + "treeitem-collapsed.svg", + "annotation-paperclip.svg", + "editor-toolbar-delete.svg", + "annotation-paragraph.svg", + "findbarButton-previous.svg", + "secondaryToolbarButton-scrollHorizontal.svg", + "toolbarButton-zoomOut.svg", + "toolbarButton-download.svg", + "annotation-key.svg", + "findbarButton-next.svg", + "secondaryToolbarButton-spreadNone.svg", + "toolbarButton-viewAttachments.svg", + "annotation-insert.svg", + "annotation-newparagraph.svg", + "annotation-note.svg", + "secondaryToolbarButton-lastPage.svg", + "secondaryToolbarButton-scrollVertical.svg", + "secondaryToolbarButton-selectTool.svg", + "treeitem-expanded.svg", + "cursor-editorTextHighlight.svg", + "secondaryToolbarButton-firstPage.svg", + "secondaryToolbarButton-spreadOdd.svg", + "annotation-noicon.svg", + "loading-icon.gif", + "secondaryToolbarButton-rotateCcw.svg", + "secondaryToolbarButton-spreadEven.svg", + "toolbarButton-search.svg", + "toolbarButton-viewOutline.svg", + "annotation-help.svg", + "secondaryToolbarButton-documentProperties.svg", + "toolbarButton-bookmark.svg", + "toolbarButton-editorFreeText.svg", + "toolbarButton-presentationMode.svg", + "altText_done.svg", + "cursor-editorFreeHighlight.svg", + "gv-toolbarButton-download.svg", + "toolbarButton-menuArrow.svg", + "toolbarButton-pageDown.svg", + "toolbarButton-pageUp.svg" + ] + }, + { + "added": [], + "changed_count": 15, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/img", + "files": [ + "goodreads.svg", + "loader.gif", + "menu-icon.png", + "save.png", + "saved.png", + "star.png", + "annotator-glyph-sprite.png", + "settings.png", + "apple-touch-icon.png", + "close.png", + "fullscreen.png", + "annotator-icon-sprite.png", + "settings-s.png", + "academicpaper.svg", + "cancelfullscreen.png" + ] + }, + { + "added": [], + "changed_count": 23, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js", + "files": [ + "filter_grid.js", + "filter_list.js", + "logviewer.js", + "remote_login.js", + "duplicate-notifier.js", + "duplicates.js", + "fullscreen.js", + "kthoom.js", + "moment.min.js", + "query-builder.standalone.min.js", + "get_meta.js", + "main.js", + "shelforder.js", + "caliBlur.js", + "details.js", + "libs", + "password.js", + "reading", + "table.js", + "uploadprogress.js", + "compress", + "edit_books.js", + "email_selection.js" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/compress", + "files": [ + "jszip.min.js", + "jszip_epub.min.js", + "libunrar.js", + "libunrar.js.mem", + "libuntar.js", + "uncompress.js" + ] + }, + { + "added": [], + "changed_count": 35, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs", + "files": [ + "typeahead.bundle.js", + "plugins.js", + "viewer.mjs", + "wysihtml5-0.3.0.min.js", + "jquery.min.js", + "reader.min.js", + "readmore.min.js", + "tinymce", + "bootstrap-table", + "jquery.form.min.js", + "pwstrength", + "screenfull.min.js", + "bootstrap-select", + "debugger.js", + "pdf.worker.mjs", + "underscore-umd-min.js", + "bar-ui.js", + "bootstrap-datepicker", + "pdf.mjs", + "bootstrap-rating-input.min.js", + "compromise.min.js", + "context.min.js", + "djvu_html5", + "l10n.js", + "soundmanager2.js", + "wysihtml5-0.0.3.js", + "bootstrap-select.min.js", + "bootstrap-wysihtml5-0.0.3.min.js", + "jquery.visible.min.js", + "Sortable.min.js", + "bootstrap.min.js", + "echarts.min.js", + "epub.min.js", + "intention.min.js", + "jquery.min.map" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/bootstrap-datepicker", + "files": [ + "bootstrap-datepicker.min.js", + "locales" + ] + }, + { + "added": [], + "changed_count": 27, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/bootstrap-datepicker/locales", + "files": [ + "bootstrap-datepicker.km.min.js", + "bootstrap-datepicker.cs.min.js", + "bootstrap-datepicker.de.min.js", + "bootstrap-datepicker.it.min.js", + "bootstrap-datepicker.pt.min.js", + "bootstrap-datepicker.pt_BR.min.js", + "bootstrap-datepicker.sl.min.js", + "bootstrap-datepicker.sv.min.js", + "bootstrap-datepicker.gl.min.js", + "bootstrap-datepicker.hu.min.js", + "bootstrap-datepicker.id.min.js", + "bootstrap-datepicker.ko.min.js", + "bootstrap-datepicker.nl.min.js", + "bootstrap-datepicker.pl.min.js", + "bootstrap-datepicker.ru.min.js", + "bootstrap-datepicker.sk.min.js", + "bootstrap-datepicker.el.min.js", + "bootstrap-datepicker.es.min.js", + "bootstrap-datepicker.fi.min.js", + "bootstrap-datepicker.fr.min.js", + "bootstrap-datepicker.ja.min.js", + "bootstrap-datepicker.uk.min.js", + "bootstrap-datepicker.zh_Hans_CN.min.js", + "bootstrap-datepicker.zh_Hant_TW.min.js", + "bootstrap-datepicker.no.min.js", + "bootstrap-datepicker.tr.min.js", + "bootstrap-datepicker.vi.min.js" + ] + }, + { + "added": [], + "changed_count": 15, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/bootstrap-select", + "files": [ + "defaults-km.min.js", + "defaults-pl.min.js", + "defaults-sv.min.js", + "defaults-es.min.js", + "defaults-hu.min.js", + "defaults-it.min.js", + "defaults-ru.min.js", + "defaults-tr.min.js", + "defaults-zh_Hans_CN.min.js", + "defaults-cs.min.js", + "defaults-fr.min.js", + "defaults-ja.min.js", + "defaults-nl.min.js", + "defaults-de.min.js", + "defaults-fi.min.js" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/bootstrap-table", + "files": [ + "bootstrap-editable.min.js", + "bootstrap-table-editable.min.js", + "bootstrap-table-locale-all.min.js", + "bootstrap-table.min.js", + "locale" + ] + }, + { + "added": [], + "changed_count": 32, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/bootstrap-table/locale", + "files": [ + "bootstrap-table-ro.min.js", + "bootstrap-table-ca.min.js", + "bootstrap-table-fi.min.js", + "bootstrap-table-hu.min.js", + "bootstrap-table-ja.min.js", + "bootstrap-table-it.min.js", + "bootstrap-table-nl.min.js", + "bootstrap-table-ko.min.js", + "bootstrap-table-pl.min.js", + "bootstrap-table-fr.min.js", + "bootstrap-table-id.min.js", + "bootstrap-table-ru.min.js", + "bootstrap-table-sv.min.js", + "bootstrap-table-af.min.js", + "bootstrap-table-ar.min.js", + "bootstrap-table-cs.min.js", + "bootstrap-table-da.min.js", + "bootstrap-table-el.min.js", + "bootstrap-table-eu.min.js", + "bootstrap-table-fa.min.js", + "bootstrap-table-tr.min.js", + "bootstrap-table-zh_Hans_CN.min.js", + "bootstrap-table-es.min.js", + "bootstrap-table-hr.min.js", + "bootstrap-table-ka.min.js", + "bootstrap-table-uk.min.js", + "bootstrap-table-sk.min.js", + "bootstrap-table-bg.min.js", + "bootstrap-table-de.min.js", + "bootstrap-table-et.min.js", + "bootstrap-table-he.min.js", + "bootstrap-table-pt_BR.min.js" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/djvu_html5", + "files": [ + "djvu_html5", + "djvu_worker", + "img" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/djvu_html5/djvu_html5", + "files": [ + "1A420474460884E73E3288F783AD7024.cache.js", + "564CB406D925C79CCD67EB98CA5AD4EF.cache.js", + "clear.cache.gif", + "compilation-mappings.txt", + "djvu_html5.devmode.js", + "djvu_html5.nocache.js", + "091ECB3AE852C68866FBC86AA8FCDB1F.cache.js", + "3CFDA9D9AFA2798299BBAE243DB2E9B5.cache.js", + "A8305F17E026239876FCBC730B035A55.cache.js" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/djvu_html5/djvu_worker", + "files": [ + "81EB501BD7AB47786C30D3175CE1EA2B.cache.js", + "clear.cache.gif", + "djvu_worker.nocache.js" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/djvu_html5/img", + "files": [ + "status.png", + "toolbar-buttons.png", + "blank.jpg" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/pwstrength", + "files": [ + "i18next.min.js", + "i18nextHttpBackend.min.js", + "locales", + "pwstrength-bootstrap.js", + "pwstrength-bootstrap.min.js" + ] + }, + { + "added": [], + "changed_count": 17, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/pwstrength/locales", + "files": [ + "el.json", + "pt.json", + "th.json", + "tr.json", + "zh-TW.json", + "en.json", + "it.json", + "no.json", + "sk.json", + "es.json", + "ru.json", + "ar.json", + "eo.json", + "fr.json", + "pl.json", + "cs.json", + "de.json" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce", + "files": [ + "langs", + "license.txt", + "plugins", + "skins", + "themes", + "tinymce.min.js", + "icons" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/icons", + "files": [ + "default" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/icons/default", + "files": [ + "icons.min.js" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/langs", + "files": [ + "ro.js", + "sq.js", + "ug.js", + "fr.js", + "ja.js", + "kab.js", + "ku.js", + "el.js", + "id.js", + "ru.js", + "ta.js", + "eo.js", + "fa.js", + "pt_BR.js", + "et.js", + "hr.js", + "lt.js", + "sl.js", + "tr.js", + "uk.js", + "zh_Hans_CN.js", + "ca.js", + "cy.js", + "da.js", + "hu.js", + "ko.js", + "no.js", + "zh_Hant_TW.js", + "nl.js", + "ar.js", + "fr_FR.js", + "hy.js", + "it.js", + "kk.js", + "pt.js", + "sv.js", + "cs.js", + "es.js", + "gl.js", + "sk.js" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/plugins", + "files": [ + "code" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/plugins/code", + "files": [ + "plugin.min.js" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/skins", + "files": [ + "ui", + "content" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/skins/content", + "files": [ + "default" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/skins/content/default", + "files": [ + "content.min.css" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/skins/ui", + "files": [ + "oxide" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/skins/ui/oxide", + "files": [ + "skin.min.css", + "skin.mobile.min.css", + "skin.shadowdom.min.css", + "content.inline.min.css", + "content.min.css", + "content.mobile.min.css", + "fonts" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/skins/ui/oxide/fonts", + "files": [ + "tinymce-mobile.woff" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/themes", + "files": [ + "silver" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/libs/tinymce/themes/silver", + "files": [ + "theme.min.js" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/js/reading", + "files": [ + "djvu_reader.js", + "epub-progress.js", + "epub.js", + "txt_reader.js" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale", + "files": [ + "bn", + "brx", + "et", + "mk", + "mr", + "sq", + "dsb", + "fa", + "gd", + "hy-AM", + "ka", + "kn", + "pt-PT", + "rm", + "es-ES", + "sat", + "sc", + "sco", + "ta", + "te", + "uz", + "fi", + "nl", + "pa-IN", + "scn", + "sl", + "ja", + "meh", + "pt-BR", + "sk", + "tr", + "bg", + "cy", + "ga-IE", + "hr", + "ko", + "lt", + "my", + "ro", + "be" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ach", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/af", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/an", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ar", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ast", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/az", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/be", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/bg", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/bn", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/bo", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/br", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/brx", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/bs", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ca", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/cak", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ckb", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/cs", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/cy", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/da", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/de", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/dsb", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/el", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/en-CA", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/en-GB", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/en-US", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/eo", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/es-AR", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/es-CL", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/es-ES", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/es-MX", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/et", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/eu", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/fa", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ff", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/fi", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/fr", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/fur", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/fy-NL", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ga-IE", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/gd", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/gl", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/gn", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/gu-IN", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/he", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/hi-IN", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/hr", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/hsb", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/hu", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/hy-AM", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/hye", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ia", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/id", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/is", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/it", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ja", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ka", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/kab", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/kk", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/km", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/kn", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ko", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/lij", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/lo", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/lt", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ltg", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/lv", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/meh", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/mk", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/mr", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ms", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/my", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/nb-NO", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ne-NP", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/nl", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/nn-NO", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/oc", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/pa-IN", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/pl", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/pt-BR", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/pt-PT", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/rm", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ro", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ru", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sat", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sc", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/scn", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sco", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/si", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sk", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/skr", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sl", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/son", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sq", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sr", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/sv-SE", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/szl", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ta", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/te", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/tg", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/th", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/tl", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/tr", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/trs", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/uk", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/ur", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/uz", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/vi", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/wo", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/xh", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/zh-CN", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/locale/zh-TW", + "files": [ + "viewer.ftl" + ] + }, + { + "added": [], + "changed_count": 16, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/standard_fonts", + "files": [ + "FoxitDingbats.pfb", + "FoxitFixedItalic.pfb", + "FoxitSerifBold.pfb", + "LICENSE_LIBERATION", + "LiberationSans-Italic.ttf", + "FoxitSerifItalic.pfb", + "FoxitFixed.pfb", + "FoxitFixedBoldItalic.pfb", + "FoxitSerifBoldItalic.pfb", + "LiberationSans-Bold.ttf", + "FoxitFixedBold.pfb", + "FoxitSerif.pfb", + "FoxitSymbol.pfb", + "LICENSE_FOXIT", + "LiberationSans-BoldItalic.ttf", + "LiberationSans-Regular.ttf" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/static/user-profile-data", + "files": [ + "CWA-profile-updater.js" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/tasks", + "files": [ + "upload.py", + "__init__.py", + "auto_hardcover_id.py", + "auto_send.py", + "clean.py", + "convert.py", + "duplicate_scan.py", + "mail.py", + "ops.py", + "database.py", + "metadata_backup.py", + "thumbnail.py", + "thumbnail_migration.py" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/templates", + "files": [ + "generate_kobo_auth_url.html", + "http_error.html", + "layout.html", + "admin.html", + "index.html", + "modal_dialogs.html", + "profile_pictures.html", + "register.html", + "cwa_api_stats.html", + "tasks.html", + "shelf.html", + "feed.xml", + "duplicates.html", + "cwa_epub_fixer.html", + "cwa_library_stats.html", + "cwa_list_logs.html", + "cwa_stats_full.html", + "osd.xml", + "read.html", + "schedule_edit.html", + "index.xml", + "author.html", + "config_edit.html", + "detail.html", + "readtxt.html", + "book_edit.html", + "list.html", + "logviewer.html", + "readcbr.html", + "shelf_order.html", + "book_table.html", + "stats.html", + "hardcover_review_matches.html", + "cwa_stats_tabs.html", + "kosync_plugin.html", + "magic_shelf_edit.html", + "search_form.html", + "cwa_read_log.html", + "cwa_settings.html", + "email_edit.html" + ] + }, + { + "added": [], + "changed_count": 28, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations", + "files": [ + "cs", + "de", + "gl", + "ja", + "ko", + "es", + "fr", + "it", + "km", + "nl", + "sk", + "sl", + "zh_Hant_TW", + "fi", + "hu", + "no", + "pl", + "pt", + "sv", + "tr", + "uk", + "ar", + "el", + "id", + "pt_BR", + "ru", + "vi", + "zh_Hans_CN" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ar", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ar/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/cs", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/cs/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/de", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/de/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/el", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/el/LC_MESSAGES", + "files": [ + "messages.po", + "messages.mo" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/es", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/es/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/fi", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/fi/LC_MESSAGES", + "files": [ + "messages.po", + "messages.mo" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/fr", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/fr/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/gl", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/gl/LC_MESSAGES", + "files": [ + "messages.po", + "messages.mo" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/hu", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/hu/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/id", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/id/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/it", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/it/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ja", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ja/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/km", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/km/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ko", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ko/LC_MESSAGES", + "files": [ + "messages.po", + "messages.mo" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/nl", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/nl/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/no", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/no/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/pl", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/pl/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/pt", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/pt/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/pt_BR", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/pt_BR/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ru", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/ru/LC_MESSAGES", + "files": [ + "messages.po", + "messages.mo" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/sk", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/sk/LC_MESSAGES", + "files": [ + "messages.po", + "messages.mo" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/sl", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/sl/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/sv", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/sv/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/tr", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/tr/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/uk", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/uk/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/vi", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/vi/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/zh_Hans_CN", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/zh_Hans_CN/LC_MESSAGES", + "files": [ + "messages.mo", + "messages.po" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/zh_Hant_TW", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/translations/zh_Hant_TW/LC_MESSAGES", + "files": [ + "messages.po", + "messages.mo" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/calibre-web-automated/cps/utils", + "files": [ + "__init__.py", + "filename_sanitizer.py", + "text_similarity.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/calibre-web-automated/koreader", + "files": [ + "plugins" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/calibre-web-automated/koreader/plugins", + "files": [ + ".gitignore", + "cwasync.koplugin", + "koplugin.zip" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/app/calibre-web-automated/koreader/plugins/cwasync.koplugin", + "files": [ + "main.lua", + "2d167a311201508454b35219348633fac7cacdaf82cd25e1c61116375cd51612.digest", + "CWASyncClient.lua", + "_meta.lua", + "api.json" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/app/calibre-web-automated/kubernetes", + "files": [ + "service.yaml", + "README.md", + "deployment.yaml", + "gateway-istio.yaml", + "pvc-config.yaml", + "pvc-library.yml" + ] + }, + { + "added": [], + "changed_count": 20, + "db_signature": false, + "dir": "/app/calibre-web-automated/scripts", + "files": [ + "check-cwa-services.sh", + "convert_library.py", + "cover_enforcer.py", + "generate_book_checksums.py", + "kindle_epub_fixer.py", + "update_spdx_headers.py", + "auto_library.py", + "auto_zip.py", + "cwa_db.py", + "cwa_schema.sql", + "generate_translation_status.py", + "generate_contributors.py", + "audiobook.py", + "compile_translations.sh", + "fix_po_duplicates.py", + "ingest_processor.py", + "setup-cwa.sh", + "update_translations.sh", + "check_spdx_headers.py", + "watch_fallback.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/etc", + "files": [ + "group-", + "ImageMagick-6", + "passwd-" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/ImageMagick-6", + "files": [ + "policy.xml" + ] + }, + { + "added": [ + ".config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config" + ] + }, + { + "added": [ + "calibre" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config", + "files": [ + "calibre" + ] + }, + { + "added": [ + "global.py.json", + "plugins" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config/calibre", + "files": [ + "global.py.json", + "plugins" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/usr", + "files": [ + "bin", + "share" + ] + }, + { + "added": [ + "lrs2lrf", + "calibre-server", + "web2disk", + "calibre-customize", + "ebook-convert", + "calibre-uninstall", + "calibredb", + "fetch-ebook-metadata", + "calibre-parallel", + "markdown-calibre", + "ebook-device", + "ebook-meta", + "lrfviewer", + "ebook-polish", + "calibre-debug", + "calibre", + "ebook-viewer", + "lrf2lrs", + "ebook-edit", + "calibre-smtp" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/usr/bin", + "files": [ + "lrs2lrf", + "calibre-server", + "web2disk", + "calibre-customize", + "ebook-convert", + "calibre-uninstall", + "calibredb", + "fetch-ebook-metadata", + "calibre-parallel", + "markdown-calibre", + "ebook-device", + "ebook-meta", + "lrfviewer", + "ebook-polish", + "calibre-debug", + "calibre", + "ebook-viewer", + "lrf2lrs", + "ebook-edit", + "calibre-smtp" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 1799, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "crocodilestick/calibre-web-automated:v4.0.6", + "mounts": [ + { + "class": "named-declared", + "files": 11, + "name": "volgate-calibreweb_calibre_web_config", + "sample": [ + "gdrive.db", + "app.db-shm", + "app.db-wal", + "app.db", + ".key", + "cwa_ingest_status", + "user_profiles.json", + "cwa_ingest_retry_queue", + "cwa.db", + "client_secrets.json", + ".cwa_migrations/duplicates_sidebar_v1" + ], + "source": "/var/lib/docker/volumes/volgate-calibreweb_calibre_web_config/_data", + "target": "/config", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/import/calibre", + "target": "/cwa-book-ingest", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 3, + "name": null, + "sample": [ + "metadata.db", + "metadata.db-shm", + "metadata.db-wal" + ], + "source": "/srv/felhom-gate/hdd/userdata/media/books", + "target": "/calibre-library", + "writable_by_app": "yes" + } + ], + "name": "calibre-web", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "calibre_web_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "calibre-web: /app/calibre-web-automated/empty_library \u2014 database file(s) touched but byte-identical to the image; a chown sweep, not a write", + "calibre-web: /app/calibre-web-automated/empty_library \u2014 database file(s) touched but byte-identical to the image; a chown sweep, not a write" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/claper/probe.json b/audits/persistence-sweep-2026-08-02/evidence/claper/probe.json new file mode 100644 index 0000000..9b99611 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/claper/probe.json @@ -0,0 +1,177 @@ +{ + "probe": { + "app": "claper", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "priv" + ] + }, + { + "added": [ + "static" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/priv", + "files": [ + "static" + ] + }, + { + "added": [ + "uploads" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/priv/static", + "files": [ + "uploads" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/claperco/claper:2.5", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-claper_claper_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-claper_claper_uploads/_data", + "target": "/app/priv/static/uploads", + "writable_by_app": "yes" + } + ], + "name": "claper", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1470, + "name": "volgate-claper_claper_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-claper_claper_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "claper-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "claper_postgres_data", + "claper_uploads" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY_BASE", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "claper: this container's mounts are all empty while it created entries in ['/app/priv', '/app/priv/static'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "claper: declared volume /app/priv/static/uploads is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/code-server/probe.json b/audits/persistence-sweep-2026-08-02/evidence/code-server/probe.json new file mode 100644 index 0000000..e1fe5f9 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/code-server/probe.json @@ -0,0 +1,110 @@ +{ + "probe": { + "app": "code-server", + "containers": [ + { + "diff_added": 196, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/", + "files": [ + "run", + "app", + "etc", + "defaults" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/etc", + "files": [ + "passwd-", + "group-", + "s6-overlay" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay", + "files": [ + "s6-rc.d" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d", + "files": [ + "init-adduser" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d/init-adduser", + "files": [ + "branding" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 208, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "lscr.io/linuxserver/code-server:4.129.0", + "mounts": [ + { + "class": "named-declared", + "files": 6, + "name": "volgate-codeserver_codeserver_config", + "sample": [ + ".profile", + ".bashrc", + ".config/code-server/config.yaml", + "data/code-server-ipc.sock", + ".local/share/code-server/coder-logs/code-server-stderr.log", + ".local/share/code-server/coder-logs/code-server-stdout.log" + ], + "source": "/var/lib/docker/volumes/volgate-codeserver_codeserver_config/_data", + "target": "/config", + "writable_by_app": "yes" + } + ], + "name": "code-server", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "codeserver_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "PASSWORD", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/crafty-controller/probe.json b/audits/persistence-sweep-2026-08-02/evidence/crafty-controller/probe.json new file mode 100644 index 0000000..16b8c8a --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/crafty-controller/probe.json @@ -0,0 +1,6433 @@ +{ + "probe": { + "app": "crafty-controller", + "containers": [ + { + "diff_added": 166, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "usr", + "crafty" + ] + }, + { + "added": [ + "import", + "servers", + "backups", + "logs" + ], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty", + "files": [ + "import", + "servers", + "backups", + "logs" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv", + "files": [ + "pyvenv.cfg", + "bin", + "include", + "lib" + ] + }, + { + "added": [], + "changed_count": 14, + "db_signature": false, + "dir": "/crafty/.venv/bin", + "files": [ + "pip", + "activate.fish", + "cbor2", + "jsonschema", + "normalizer", + "pip3.12", + "activate", + "activate.csh", + "httpx", + "idna", + "Activate.ps1", + "distro", + "pip3", + "pwiz.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/include", + "files": [ + "python3.12" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib", + "files": [ + "python3.12" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12", + "files": [ + "site-packages" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages", + "files": [ + "pip-26.1.2.dist-info", + "playhouse", + "psutil", + "pyotp-2.9.0.dist-info", + "python_dateutil-2.9.0.post0.dist-info", + "certifi-2026.5.20.dist-info", + "colorama", + "packaging", + "pip", + "asn1crypto-1.5.1.dist-info", + "markupsafe-3.0.3.dist-info", + "setuptools", + "pycparser-3.0.dist-info", + "pytz", + "pytz-2026.2.dist-info", + "rpds_py-2026.5.1.dist-info", + "tzlocal", + "cryptography-48.0.0.dist-info", + "distro-1.8.0.dist-info", + "termcolor.py", + "charset_normalizer-3.4.7.dist-info", + "_argon2_cffi_bindings", + "anyio-4.9.0.dist-info", + "asn1crypto", + "attrs", + "tornado-6.5.4.dist-info", + "psutil-5.9.5.dist-info", + "referencing", + "81d243bd2c585b0f4821__mypyc.cpython-312-x86_64-linux-gnu.so", + "aiofiles-24.1.0.dist-info", + "jinja2-3.1.6.dist-info", + "requests", + "charset_normalizer", + "jinja2", + "jsonschema", + "orjson", + "pillow.libs", + "pwiz.py", + "cryptography", + "httpcore-1.0.9.dist-info" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/APScheduler-3.10.4.dist-info", + "files": [ + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "entry_points.txt", + "top_level.txt", + "INSTALLER", + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/OpenSSL", + "files": [ + "__init__.py", + "_util.py", + "crypto.py", + "py.typed", + "SSL.py", + "debug.py", + "rand.py", + "version.py" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/PIL", + "files": [ + "_imagingcms.cpython-312-x86_64-linux-gnu.so", + "ImageChops.py", + "XpmImagePlugin.py", + "report.py", + "_tkinter_finder.py", + "GimpGradientFile.py", + "IcoImagePlugin.py", + "AvifImagePlugin.py", + "ImageMath.py", + "MpoImagePlugin.py", + "PcxImagePlugin.py", + "ImageTk.py", + "PpmImagePlugin.py", + "_imagingmorph.cpython-312-x86_64-linux-gnu.so", + "ImageOps.py", + "ImageSequence.py", + "ImageText.py", + "SgiImagePlugin.py", + "_imagingmorph.pyi", + "GdImageFile.py", + "ContainerIO.py", + "ExifTags.py", + "ImageDraw2.py", + "McIdasImagePlugin.py", + "PcfFontFile.py", + "_imagingft.cpython-312-x86_64-linux-gnu.so", + "ImageStat.py", + "Jpeg2KImagePlugin.py", + "ImageWin.py", + "MpegImagePlugin.py", + "__init__.py", + "_imagingmath.pyi", + "GbrImagePlugin.py", + "ImageDraw.py", + "PaletteFile.py", + "SunImagePlugin.py", + "DcxImagePlugin.py", + "PsdImagePlugin.py", + "WebPImagePlugin.py", + "DdsImagePlugin.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/PyJWT-2.8.0.dist-info", + "files": [ + "AUTHORS.rst", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/PyYAML-6.0.1.dist-info", + "files": [ + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "top_level.txt", + "INSTALLER", + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/_argon2_cffi_bindings", + "files": [ + "__init__.py", + "_ffi.abi3.so", + "_ffi_build.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/_distutils_hack", + "files": [ + "__init__.py", + "override.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/_yaml", + "files": [ + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/aiofiles", + "files": [ + "base.py", + "os.py", + "ospath.py", + "tempfile", + "threadpool", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/aiofiles-24.1.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/aiofiles-24.1.0.dist-info/licenses", + "files": [ + "LICENSE", + "NOTICE" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/aiofiles/tempfile", + "files": [ + "__init__.py", + "temptypes.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/aiofiles/threadpool", + "files": [ + "text.py", + "utils.py", + "__init__.py", + "binary.py" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/anyio", + "files": [ + "__init__.py", + "_backends", + "streams", + "to_interpreter.py", + "to_process.py", + "_core", + "abc", + "from_thread.py", + "lowlevel.py", + "py.typed", + "pytest_plugin.py", + "to_thread.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/anyio-4.9.0.dist-info", + "files": [ + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "entry_points.txt", + "top_level.txt", + "INSTALLER", + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/anyio/_backends", + "files": [ + "_asyncio.py", + "_trio.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 15, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/anyio/_core", + "files": [ + "__init__.py", + "_synchronization.py", + "_eventloop.py", + "_exceptions.py", + "_fileio.py", + "_signals.py", + "_sockets.py", + "_subprocesses.py", + "_tasks.py", + "_testing.py", + "_asyncio_selector_thread.py", + "_resources.py", + "_streams.py", + "_tempfile.py", + "_typedattr.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/anyio/abc", + "files": [ + "_tasks.py", + "_testing.py", + "__init__.py", + "_subprocesses.py", + "_eventloop.py", + "_resources.py", + "_sockets.py", + "_streams.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/anyio/streams", + "files": [ + "buffered.py", + "file.py", + "memory.py", + "stapled.py", + "text.py", + "tls.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/apscheduler", + "files": [ + "schedulers", + "util.py", + "events.py", + "executors", + "job.py", + "jobstores", + "triggers", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/apscheduler/executors", + "files": [ + "__init__.py", + "asyncio.py", + "base_py3.py", + "debug.py", + "pool.py", + "tornado.py", + "base.py", + "gevent.py", + "twisted.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/apscheduler/jobstores", + "files": [ + "base.py", + "memory.py", + "redis.py", + "rethinkdb.py", + "__init__.py", + "mongodb.py", + "sqlalchemy.py", + "zookeeper.py" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/apscheduler/schedulers", + "files": [ + "tornado.py", + "twisted.py", + "__init__.py", + "background.py", + "blocking.py", + "qt.py", + "asyncio.py", + "base.py", + "gevent.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/apscheduler/triggers", + "files": [ + "combining.py", + "cron", + "date.py", + "interval.py", + "__init__.py", + "base.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/apscheduler/triggers/cron", + "files": [ + "__init__.py", + "expressions.py", + "fields.py" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/argon2", + "files": [ + "__init__.py", + "__main__.py", + "_legacy.py", + "_password_hasher.py", + "_utils.py", + "low_level.py", + "profiles.py", + "py.typed", + "_typing.py", + "exceptions.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/argon2_cffi-23.1.0.dist-info", + "files": [ + "licenses", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/argon2_cffi-23.1.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/argon2_cffi_bindings-25.1.0.dist-info", + "files": [ + "RECORD", + "WHEEL", + "licenses", + "top_level.txt", + "INSTALLER", + "METADATA" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/argon2_cffi_bindings-25.1.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 23, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/asn1crypto", + "files": [ + "version.py", + "_int.py", + "_ordereddict.py", + "_teletex_codec.py", + "core.py", + "pdf.py", + "x509.py", + "_errors.py", + "crl.py", + "parser.py", + "util.py", + "csr.py", + "__init__.py", + "_types.py", + "cms.py", + "keys.py", + "pem.py", + "pkcs12.py", + "tsp.py", + "_inet.py", + "_iri.py", + "algos.py", + "ocsp.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/asn1crypto-1.5.1.dist-info", + "files": [ + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "WHEEL", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 23, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/attr", + "files": [ + "validators.pyi", + "__init__.pyi", + "converters.pyi", + "filters.pyi", + "setters.py", + "__init__.py", + "_config.py", + "_version_info.py", + "converters.py", + "exceptions.pyi", + "filters.py", + "setters.pyi", + "_cmp.py", + "_cmp.pyi", + "_compat.py", + "_next_gen.py", + "_typing_compat.pyi", + "exceptions.py", + "py.typed", + "_funcs.py", + "_make.py", + "_version_info.pyi", + "validators.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/attrs", + "files": [ + "py.typed", + "setters.py", + "__init__.py", + "__init__.pyi", + "converters.py", + "exceptions.py", + "filters.py", + "validators.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/attrs-26.1.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL", + "licenses" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/attrs-26.1.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cached_property-1.5.2.dist-info", + "files": [ + "AUTHORS.rst", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cbor2", + "files": [ + "_cbor2.cpython-312-x86_64-linux-gnu.so", + "py.typed", + "tool.py", + "__init__.py", + "__init__.pyi" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cbor2-6.1.2.dist-info", + "files": [ + "entry_points.txt", + "licenses", + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cbor2-6.1.2.dist-info/licenses", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/certifi", + "files": [ + "__init__.py", + "__main__.py", + "cacert.pem", + "core.py", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/certifi-2026.5.20.dist-info", + "files": [ + "WHEEL", + "licenses", + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/certifi-2026.5.20.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 22, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cffi", + "files": [ + "cffi_opcode.py", + "error.py", + "lock.py", + "recompiler.py", + "verifier.py", + "_cffi_errors.h", + "_embedding.h", + "api.py", + "commontypes.py", + "pkgconfig.py", + "vengine_cpy.py", + "_cffi_include.h", + "_imp_emulation.py", + "cparser.py", + "model.py", + "parse_c_type.h", + "setuptools_ext.py", + "ffiplatform.py", + "vengine_gen.py", + "__init__.py", + "_shimmed_dist_utils.py", + "backend_ctypes.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cffi-2.0.0.dist-info", + "files": [ + "licenses", + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL", + "entry_points.txt" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cffi-2.0.0.dist-info/licenses", + "files": [ + "AUTHORS", + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 14, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/charset_normalizer", + "files": [ + "cd.py", + "legacy.py", + "utils.py", + "version.py", + "api.py", + "md.py", + "models.py", + "py.typed", + "__init__.py", + "__main__.py", + "cli", + "md.cpython-312-x86_64-linux-gnu.so", + "cd.cpython-312-x86_64-linux-gnu.so", + "constant.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/charset_normalizer-3.4.7.dist-info", + "files": [ + "METADATA", + "RECORD", + "WHEEL", + "entry_points.txt", + "licenses", + "top_level.txt", + "INSTALLER" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/charset_normalizer-3.4.7.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/charset_normalizer/cli", + "files": [ + "__init__.py", + "__main__.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/colorama", + "files": [ + "ansi.py", + "ansitowin32.py", + "initialise.py", + "tests", + "win32.py", + "winterm.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/colorama-0.4.6.dist-info", + "files": [ + "RECORD", + "REQUESTED", + "WHEEL", + "licenses", + "INSTALLER", + "METADATA" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/colorama-0.4.6.dist-info/licenses", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/colorama/tests", + "files": [ + "__init__.py", + "ansi_test.py", + "ansitowin32_test.py", + "initialise_test.py", + "isatty_test.py", + "utils.py", + "winterm_test.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/croniter", + "files": [ + "croniter.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/croniter-1.4.1.dist-info", + "files": [ + "top_level.txt", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography", + "files": [ + "__about__.py", + "exceptions.py", + "fernet.py", + "hazmat", + "py.typed", + "__init__.py", + "utils.py", + "x509" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography-48.0.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses", + "sboms" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography-48.0.0.dist-info/licenses", + "files": [ + "LICENSE", + "LICENSE.APACHE", + "LICENSE.BSD" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography-48.0.0.dist-info/sboms", + "files": [ + "cryptography-rust.cyclonedx.json", + "sbom.json" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat", + "files": [ + "_oid.py", + "asn1", + "backends", + "bindings", + "decrepit", + "primitives", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/asn1", + "files": [ + "__init__.py", + "asn1.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/backends", + "files": [ + "__init__.py", + "openssl" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl", + "files": [ + "__init__.py", + "backend.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/bindings", + "files": [ + "__init__.py", + "_rust", + "_rust.abi3.so", + "openssl" + ] + }, + { + "added": [], + "changed_count": 11, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust", + "files": [ + "test_support.pyi", + "exceptions.pyi", + "ocsp.pyi", + "openssl", + "pkcs12.pyi", + "x509.pyi", + "__init__.pyi", + "_openssl.pyi", + "asn1.pyi", + "declarative_asn1.pyi", + "pkcs7.pyi" + ] + }, + { + "added": [], + "changed_count": 20, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl", + "files": [ + "hpke.pyi", + "ciphers.pyi", + "ed448.pyi", + "mldsa.pyi", + "mlkem.pyi", + "kdf.pyi", + "keys.pyi", + "__init__.pyi", + "aead.pyi", + "dh.pyi", + "dsa.pyi", + "ec.pyi", + "ed25519.pyi", + "cmac.pyi", + "hashes.pyi", + "hmac.pyi", + "poly1305.pyi", + "rsa.pyi", + "x25519.pyi", + "x448.pyi" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl", + "files": [ + "binding.py", + "__init__.py", + "_conditional.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit", + "files": [ + "ciphers", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers", + "files": [ + "__init__.py", + "algorithms.py", + "modes.py" + ] + }, + { + "added": [], + "changed_count": 18, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives", + "files": [ + "hmac.py", + "poly1305.py", + "serialization", + "twofactor", + "_modes.py", + "_serialization.py", + "ciphers", + "constant_time.py", + "hpke.py", + "padding.py", + "_asymmetric.py", + "_cipheralgorithm.py", + "cmac.py", + "keywrap.py", + "kdf", + "__init__.py", + "asymmetric", + "hashes.py" + ] + }, + { + "added": [], + "changed_count": 14, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric", + "files": [ + "utils.py", + "__init__.py", + "mldsa.py", + "mlkem.py", + "x448.py", + "ed448.py", + "ec.py", + "padding.py", + "rsa.py", + "types.py", + "dh.py", + "dsa.py", + "ed25519.py", + "x25519.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers", + "files": [ + "aead.py", + "algorithms.py", + "base.py", + "modes.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf", + "files": [ + "__init__.py", + "argon2.py", + "pbkdf2.py", + "scrypt.py", + "x963kdf.py", + "concatkdf.py", + "hkdf.py", + "kbkdf.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization", + "files": [ + "pkcs7.py", + "ssh.py", + "__init__.py", + "base.py", + "pkcs12.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor", + "files": [ + "__init__.py", + "hotp.py", + "totp.py" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/cryptography/x509", + "files": [ + "oid.py", + "base.py", + "certificate_transparency.py", + "name.py", + "verification.py", + "__init__.py", + "extensions.py", + "general_name.py", + "ocsp.py" + ] + }, + { + "added": [], + "changed_count": 11, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/dateutil", + "files": [ + "zoneinfo", + "__init__.py", + "_common.py", + "relativedelta.py", + "rrule.py", + "tzwin.py", + "_version.py", + "easter.py", + "parser", + "tz", + "utils.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/dateutil/parser", + "files": [ + "__init__.py", + "_parser.py", + "isoparser.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/dateutil/tz", + "files": [ + "__init__.py", + "_common.py", + "_factories.py", + "tz.py", + "win.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/dateutil/zoneinfo", + "files": [ + "dateutil-zoneinfo.tar.gz", + "rebuild.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/distro", + "files": [ + "distro.py", + "py.typed", + "__init__.py", + "__main__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/distro-1.8.0.dist-info", + "files": [ + "REQUESTED", + "WHEEL", + "entry_points.txt", + "top_level.txt", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/h11", + "files": [ + "_readers.py", + "_receivebuffer.py", + "_state.py", + "_version.py", + "_writers.py", + "py.typed", + "__init__.py", + "_connection.py", + "_events.py", + "_headers.py", + "_util.py", + "_abnf.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/h11-0.16.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL", + "licenses", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpcore", + "files": [ + "_ssl.py", + "_utils.py", + "_backends", + "_models.py", + "_sync", + "_synchronization.py", + "_trace.py", + "py.typed", + "__init__.py", + "_api.py", + "_async", + "_exceptions.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info", + "files": [ + "METADATA", + "RECORD", + "WHEEL", + "licenses", + "INSTALLER" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/licenses", + "files": [ + "LICENSE.md" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpcore/_async", + "files": [ + "interfaces.py", + "connection.py", + "http2.py", + "http_proxy.py", + "socks_proxy.py", + "__init__.py", + "connection_pool.py", + "http11.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpcore/_backends", + "files": [ + "anyio.py", + "auto.py", + "base.py", + "mock.py", + "sync.py", + "trio.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpcore/_sync", + "files": [ + "connection_pool.py", + "http_proxy.py", + "__init__.py", + "connection.py", + "http11.py", + "http2.py", + "interfaces.py", + "socks_proxy.py" + ] + }, + { + "added": [], + "changed_count": 19, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpx", + "files": [ + "_urls.py", + "_utils.py", + "py.typed", + "__init__.py", + "_content.py", + "_decoders.py", + "_exceptions.py", + "_config.py", + "_status_codes.py", + "_models.py", + "_multipart.py", + "__version__.py", + "_auth.py", + "_client.py", + "_main.py", + "_urlparse.py", + "_api.py", + "_transports", + "_types.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "entry_points.txt", + "licenses" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/licenses", + "files": [ + "LICENSE.md" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/httpx/_transports", + "files": [ + "__init__.py", + "asgi.py", + "base.py", + "default.py", + "mock.py", + "wsgi.py" + ] + }, + { + "added": [], + "changed_count": 11, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/idna", + "files": [ + "__init__.py", + "__main__.py", + "codec.py", + "compat.py", + "core.py", + "package_data.py", + "uts46data.py", + "cli.py", + "idnadata.py", + "intranges.py", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/idna-3.18.dist-info", + "files": [ + "entry_points.txt", + "licenses", + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/idna-3.18.dist-info/licenses", + "files": [ + "LICENSE.md" + ] + }, + { + "added": [], + "changed_count": 26, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jinja2", + "files": [ + "py.typed", + "_identifier.py", + "debug.py", + "defaults.py", + "exceptions.py", + "ext.py", + "lexer.py", + "optimizer.py", + "async_utils.py", + "filters.py", + "runtime.py", + "sandbox.py", + "tests.py", + "utils.py", + "visitor.py", + "bccache.py", + "compiler.py", + "constants.py", + "meta.py", + "__init__.py", + "environment.py", + "idtracking.py", + "loaders.py", + "nativetypes.py", + "nodes.py", + "parser.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info", + "files": [ + "entry_points.txt", + "licenses", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 14, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema", + "files": [ + "__init__.py", + "_format.py", + "cli.py", + "benchmarks", + "exceptions.py", + "validators.py", + "_keywords.py", + "_legacy_keywords.py", + "_types.py", + "_typing.py", + "__main__.py", + "_utils.py", + "protocols.py", + "tests" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema-4.19.1.dist-info", + "files": [ + "licenses", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "entry_points.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema-4.19.1.dist-info/licenses", + "files": [ + "COPYING" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema/benchmarks", + "files": [ + "issue232", + "subcomponents.py", + "unused_registry.py", + "validator_creation.py", + "issue232.py", + "json_schema_test_suite.py", + "nested_schemas.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema/benchmarks/issue232", + "files": [ + "issue.json" + ] + }, + { + "added": [], + "changed_count": 11, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema/tests", + "files": [ + "_suite.py", + "test_deprecations.py", + "test_exceptions.py", + "test_format.py", + "test_types.py", + "test_utils.py", + "fuzz_validate.py", + "test_cli.py", + "test_jsonschema_test_suite.py", + "test_validators.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications", + "files": [ + "_core.py", + "schemas", + "tests", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications-2025.9.1.dist-info", + "files": [ + "licenses", + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications-2025.9.1.dist-info/licenses", + "files": [ + "COPYING" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas", + "files": [ + "draft7", + "draft201909", + "draft202012", + "draft3", + "draft4", + "draft6" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft201909", + "files": [ + "metaschema.json", + "vocabularies" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft201909/vocabularies", + "files": [ + "validation", + "applicator", + "content", + "core", + "format", + "meta-data" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft202012", + "files": [ + "vocabularies", + "metaschema.json" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft202012/vocabularies", + "files": [ + "applicator", + "content", + "core", + "format-annotation", + "format-assertion", + "meta-data", + "unevaluated", + "validation" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft3", + "files": [ + "metaschema.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft4", + "files": [ + "metaschema.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft6", + "files": [ + "metaschema.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/schemas/draft7", + "files": [ + "metaschema.json" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jsonschema_specifications/tests", + "files": [ + "__init__.py", + "test_jsonschema_specifications.py" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/jwt", + "files": [ + "py.typed", + "types.py", + "utils.py", + "warnings.py", + "__init__.py", + "algorithms.py", + "api_jwk.py", + "api_jws.py", + "api_jwt.py", + "exceptions.py", + "jwk_set_cache.py", + "help.py", + "jwks_client.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/libgravatar", + "files": [ + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/libgravatar-1.0.4.dist-info", + "files": [ + "REQUESTED", + "WHEEL", + "top_level.txt", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/markupsafe", + "files": [ + "__init__.py", + "_native.py", + "_speedups.c", + "_speedups.cpython-312-x86_64-linux-gnu.so", + "_speedups.pyi", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info", + "files": [ + "WHEEL", + "licenses", + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/licenses", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/nh3", + "files": [ + "__init__.pyi", + "nh3.abi3.so", + "py.typed", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/nh3-0.2.14.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/orjson", + "files": [ + "orjson.cpython-312-x86_64-linux-gnu.so", + "py.typed", + "__init__.py", + "__init__.pyi" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/orjson-3.11.4.dist-info", + "files": [ + "licenses", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/orjson-3.11.4.dist-info/licenses", + "files": [ + "LICENSE-APACHE", + "LICENSE-MIT" + ] + }, + { + "added": [], + "changed_count": 16, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/packaging", + "files": [ + "py.typed", + "__init__.py", + "_musllinux.py", + "_structures.py", + "licenses", + "metadata.py", + "tags.py", + "version.py", + "_tokenizer.py", + "utils.py", + "_elffile.py", + "_parser.py", + "markers.py", + "_manylinux.py", + "requirements.py", + "specifiers.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/packaging-24.2.dist-info", + "files": [ + "INSTALLER", + "LICENSE", + "LICENSE.APACHE", + "LICENSE.BSD", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/packaging/licenses", + "files": [ + "__init__.py", + "_spdx.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/peewee-3.13.0.dist-info", + "files": [ + "WHEEL", + "licenses", + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/peewee-3.13.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pillow-12.2.0.dist-info", + "files": [ + "RECORD", + "licenses", + "sboms", + "INSTALLER", + "REQUESTED", + "WHEEL", + "top_level.txt", + "zip-safe", + "METADATA" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pillow-12.2.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pillow-12.2.0.dist-info/sboms", + "files": [ + "auditwheel.cdx.json" + ] + }, + { + "added": [], + "changed_count": 18, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pillow.libs", + "files": [ + "libsharpyuv-0bacc318.so.0.1.2", + "libtiff-f4bc73ff.so.6.2.0", + "libwebp-67ee3789.so.7.2.0", + "libwebpdemux-eeb71312.so.2.0.17", + "libwebpmux-20a90b09.so.3.1.2", + "libbrotlicommon-785b7a00.so.1.2.0", + "libharfbuzz-ef65cc8a.so.0.61321.0", + "libjpeg-8296d2fa.so.62.4.0", + "libpng16-65c953c0.so.16.56.0", + "libxcb-bac6ebc8.so.1.1.0", + "libbrotlidec-08d35d18.so.1.2.0", + "libzstd-6ea785c0.so.1.5.7", + "libfreetype-d2d71ad9.so.6.20.6", + "liblcms2-545f6561.so.2.0.18", + "libopenjp2-f3576751.so.2.5.4", + "libXau-154567c4.so.6.0.0", + "libavif-a883386a.so.16.4.1", + "liblzma-3c942967.so.5.8.3" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip", + "files": [ + "__init__.py", + "__main__.py", + "__pip-runner__.py", + "_internal", + "_vendor", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info", + "files": [ + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "entry_points.txt", + "licenses", + "INSTALLER" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses", + "files": [ + "AUTHORS.txt", + "LICENSE.txt", + "src" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src", + "files": [ + "pip" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip", + "files": [ + "_vendor" + ] + }, + { + "added": [], + "changed_count": 18, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor", + "files": [ + "cachecontrol", + "certifi", + "tomli_w", + "truststore", + "urllib3", + "distlib", + "packaging", + "pkg_resources", + "pygments", + "resolvelib", + "rich", + "tomli", + "idna", + "platformdirs", + "requests", + "distro", + "msgpack", + "pyproject_hooks" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/cachecontrol", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/certifi", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/distlib", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/distro", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/idna", + "files": [ + "LICENSE.md" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/msgpack", + "files": [ + "COPYING" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/packaging", + "files": [ + "LICENSE", + "LICENSE.APACHE", + "LICENSE.BSD" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/pkg_resources", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/platformdirs", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/pygments", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/pyproject_hooks", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/requests", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/resolvelib", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/rich", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/tomli", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/tomli_w", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/truststore", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip-26.1.2.dist-info/licenses/src/pip/_vendor/urllib3", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 22, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal", + "files": [ + "build_env.py", + "cache.py", + "configuration.py", + "locations", + "models", + "operations", + "resolution", + "__init__.py", + "commands", + "distributions", + "index", + "main.py", + "network", + "pyproject.py", + "req", + "cli", + "self_outdated_check.py", + "vcs", + "exceptions.py", + "metadata", + "utils", + "wheel_builder.py" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/cli", + "files": [ + "__init__.py", + "cmdoptions.py", + "main_parser.py", + "progress_bars.py", + "req_command.py", + "spinners.py", + "status_codes.py", + "autocompletion.py", + "base_command.py", + "command_context.py", + "index_command.py", + "main.py", + "parser.py" + ] + }, + { + "added": [], + "changed_count": 19, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/commands", + "files": [ + "check.py", + "completion.py", + "hash.py", + "inspect.py", + "install.py", + "configuration.py", + "debug.py", + "download.py", + "list.py", + "lock.py", + "search.py", + "uninstall.py", + "freeze.py", + "help.py", + "show.py", + "wheel.py", + "__init__.py", + "cache.py", + "index.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/distributions", + "files": [ + "installed.py", + "sdist.py", + "wheel.py", + "__init__.py", + "base.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/index", + "files": [ + "__init__.py", + "collector.py", + "package_finder.py", + "sources.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/locations", + "files": [ + "__init__.py", + "_distutils.py", + "_sysconfig.py", + "base.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/metadata", + "files": [ + "_json.py", + "base.py", + "importlib", + "pkg_resources.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib", + "files": [ + "_compat.py", + "_dists.py", + "_envs.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/models", + "files": [ + "__init__.py", + "direct_url.py", + "format_control.py", + "link.py", + "release_control.py", + "scheme.py", + "search_scope.py", + "candidate.py", + "index.py", + "installation_report.py", + "selection_prefs.py", + "target_python.py", + "wheel.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/network", + "files": [ + "__init__.py", + "auth.py", + "lazy_wheel.py", + "utils.py", + "cache.py", + "download.py", + "session.py", + "xmlrpc.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/operations", + "files": [ + "install", + "prepare.py", + "__init__.py", + "build", + "check.py", + "freeze.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/operations/build", + "files": [ + "__init__.py", + "build_tracker.py", + "metadata.py", + "metadata_editable.py", + "wheel.py", + "wheel_editable.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/operations/install", + "files": [ + "wheel.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/req", + "files": [ + "__init__.py", + "constructors.py", + "req_file.py", + "req_set.py", + "pep723.py", + "req_dependency_group.py", + "req_install.py", + "req_uninstall.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/resolution", + "files": [ + "base.py", + "legacy", + "resolvelib", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy", + "files": [ + "__init__.py", + "resolver.py" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib", + "files": [ + "__init__.py", + "base.py", + "factory.py", + "found_candidates.py", + "resolver.py", + "candidates.py", + "provider.py", + "reporter.py", + "requirements.py" + ] + }, + { + "added": [], + "changed_count": 26, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/utils", + "files": [ + "temp_dir.py", + "urls.py", + "virtualenv.py", + "wheel.py", + "appdirs.py", + "filesystem.py", + "filetypes.py", + "retry.py", + "subprocess.py", + "_jaraco_text.py", + "_log.py", + "compat.py", + "deprecation.py", + "direct_url_helpers.py", + "hashes.py", + "misc.py", + "unpacking.py", + "__init__.py", + "compatibility_tags.py", + "egg_link.py", + "entrypoints.py", + "glibc.py", + "logging.py", + "packaging.py", + "datetime.py", + "pylock.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_internal/vcs", + "files": [ + "mercurial.py", + "subversion.py", + "versioncontrol.py", + "__init__.py", + "bazaar.py", + "git.py" + ] + }, + { + "added": [], + "changed_count": 21, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor", + "files": [ + "msgpack", + "pkg_resources", + "resolvelib", + "urllib3", + "vendor.txt", + "README.rst", + "rich", + "cachecontrol", + "distlib", + "idna", + "pygments", + "truststore", + "__init__.py", + "distro", + "packaging", + "platformdirs", + "pyproject_hooks", + "requests", + "tomli", + "tomli_w", + "certifi" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol", + "files": [ + "caches", + "controller.py", + "filewrapper.py", + "heuristics.py", + "LICENSE.txt", + "adapter.py", + "py.typed", + "serialize.py", + "wrapper.py", + "__init__.py", + "_cmd.py", + "cache.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches", + "files": [ + "file_cache.py", + "redis_cache.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/certifi", + "files": [ + "__init__.py", + "__main__.py", + "cacert.pem", + "core.py", + "py.typed", + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/distlib", + "files": [ + "__init__.py", + "compat.py", + "resources.py", + "t32.exe", + "t64-arm.exe", + "t64.exe", + "w64.exe", + "LICENSE.txt", + "scripts.py", + "util.py", + "w32.exe", + "w64-arm.exe" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/distro", + "files": [ + "__main__.py", + "distro.py", + "py.typed", + "LICENSE", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/idna", + "files": [ + "py.typed", + "LICENSE.md", + "__init__.py", + "codec.py", + "idnadata.py", + "intranges.py", + "package_data.py", + "uts46data.py", + "compat.py", + "core.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack", + "files": [ + "fallback.py", + "COPYING", + "__init__.py", + "exceptions.py", + "ext.py" + ] + }, + { + "added": [], + "changed_count": 23, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/packaging", + "files": [ + "_structures.py", + "errors.py", + "specifiers.py", + "__init__.py", + "_manylinux.py", + "_musllinux.py", + "_tokenizer.py", + "markers.py", + "pylock.py", + "version.py", + "LICENSE.APACHE", + "LICENSE.BSD", + "_elffile.py", + "_parser.py", + "dependency_groups.py", + "direct_url.py", + "utils.py", + "licenses", + "metadata.py", + "py.typed", + "requirements.py", + "tags.py", + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/licenses", + "files": [ + "__init__.py", + "_spdx.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources", + "files": [ + "LICENSE", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs", + "files": [ + "__init__.py", + "__main__.py", + "android.py", + "unix.py", + "version.py", + "windows.py", + "LICENSE", + "api.py", + "macos.py", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 20, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pygments", + "files": [ + "modeline.py", + "plugin.py", + "style.py", + "filter.py", + "filters", + "formatters", + "lexer.py", + "lexers", + "token.py", + "LICENSE", + "__init__.py", + "formatter.py", + "regexopt.py", + "scanner.py", + "sphinxext.py", + "util.py", + "__main__.py", + "console.py", + "styles", + "unistring.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters", + "files": [ + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters", + "files": [ + "__init__.py", + "_mapping.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers", + "files": [ + "__init__.py", + "_mapping.py", + "python.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles", + "files": [ + "_mapping.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks", + "files": [ + "_in_process", + "py.typed", + "LICENSE", + "__init__.py", + "_impl.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process", + "files": [ + "_in_process.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 19, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/requests", + "files": [ + "__init__.py", + "certs.py", + "cookies.py", + "models.py", + "sessions.py", + "_internal_utils.py", + "compat.py", + "hooks.py", + "status_codes.py", + "structures.py", + "__version__.py", + "api.py", + "exceptions.py", + "help.py", + "utils.py", + "LICENSE", + "adapters.py", + "auth.py", + "packages.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib", + "files": [ + "__init__.py", + "providers.py", + "py.typed", + "reporters.py", + "resolvers", + "structs.py", + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers", + "files": [ + "abstract.py", + "criterion.py", + "exceptions.py", + "resolution.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/rich", + "files": [ + "_windows.py", + "abc.py", + "prompt.py", + "table.py", + "terminal_theme.py", + "traceback.py", + "_timer.py", + "box.py", + "spinner.py", + "__init__.py", + "align.py", + "bar.py", + "pager.py", + "screen.py", + "_emoji_replace.py", + "_loop.py", + "_ratio.py", + "_stack.py", + "_wrap.py", + "progress_bar.py", + "_spinners.py", + "columns.py", + "errors.py", + "styled.py", + "_win32_console.py", + "color_triplet.py", + "live.py", + "markup.py", + "protocol.py", + "_palettes.py", + "style.py", + "theme.py", + "_inspect.py", + "_pick.py", + "ansi.py", + "default_styles.py", + "filesize.py", + "_fileno.py", + "color.py", + "rule.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/tomli", + "files": [ + "_parser.py", + "_re.py", + "_types.py", + "py.typed", + "LICENSE", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/tomli_w", + "files": [ + "LICENSE", + "__init__.py", + "_writer.py", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/truststore", + "files": [ + "_macos.py", + "_openssl.py", + "_ssl_constants.py", + "_windows.py", + "LICENSE", + "__init__.py", + "py.typed", + "_api.py" + ] + }, + { + "added": [], + "changed_count": 17, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3", + "files": [ + "response.py", + "util", + "_base_connection.py", + "_request_methods.py", + "connection.py", + "connectionpool.py", + "exceptions.py", + "fields.py", + "poolmanager.py", + "py.typed", + "__init__.py", + "_version.py", + "contrib", + "filepost.py", + "http2", + "LICENSE.txt", + "_collections.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib", + "files": [ + "__init__.py", + "emscripten", + "pyopenssl.py", + "socks.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten", + "files": [ + "emscripten_fetch_worker.js", + "fetch.py", + "request.py", + "response.py", + "__init__.py", + "connection.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/http2", + "files": [ + "__init__.py", + "connection.py", + "probe.py" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util", + "files": [ + "wait.py", + "__init__.py", + "connection.py", + "ssl_.py", + "ssltransport.py", + "timeout.py", + "url.py", + "util.py", + "proxy.py", + "request.py", + "response.py", + "retry.py", + "ssl_match_hostname.py" + ] + }, + { + "added": [], + "changed_count": 22, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/playhouse", + "files": [ + "fields.py", + "kv.py", + "pool.py", + "sqlite_udf.py", + "apsw_ext.py", + "cockroachdb.py", + "dataset.py", + "mysql_ext.py", + "reflection.py", + "sqlcipher_ext.py", + "db_url.py", + "flask_utils.py", + "hybrid.py", + "postgres_ext.py", + "shortcuts.py", + "sqlite_changelog.py", + "sqlite_ext.py", + "sqliteq.py", + "__init__.py", + "migrate.py", + "signals.py", + "test_utils.py" + ] + }, + { + "added": [], + "changed_count": 22, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/prometheus_client", + "files": [ + "exposition.py", + "mmap_dict.py", + "utils.py", + "values.py", + "multiprocess.py", + "py.typed", + "context_managers.py", + "core.py", + "metrics_core.py", + "parser.py", + "platform_collector.py", + "samples.py", + "twisted", + "__init__.py", + "asgi.py", + "bridge", + "gc_collector.py", + "metrics.py", + "openmetrics", + "process_collector.py", + "registry.py", + "decorator.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/prometheus_client-0.17.1.dist-info", + "files": [ + "REQUESTED", + "WHEEL", + "top_level.txt", + "INSTALLER", + "LICENSE", + "METADATA", + "NOTICE", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/prometheus_client/bridge", + "files": [ + "__init__.py", + "graphite.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/prometheus_client/openmetrics", + "files": [ + "exposition.py", + "parser.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/prometheus_client/twisted", + "files": [ + "_exposition.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/psutil", + "files": [ + "_common.py", + "_psaix.py", + "_pslinux.py", + "_psposix.py", + "_psutil_linux.abi3.so", + "tests", + "__init__.py", + "_compat.py", + "_psbsd.py", + "_psosx.py", + "_pssunos.py", + "_psutil_posix.abi3.so", + "_pswindows.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/psutil-5.9.5.dist-info", + "files": [ + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "top_level.txt", + "INSTALLER" + ] + }, + { + "added": [], + "changed_count": 18, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/psutil/tests", + "files": [ + "test_contracts.py", + "test_linux.py", + "test_posix.py", + "__init__.py", + "test_aix.py", + "test_bsd.py", + "test_memleaks.py", + "test_misc.py", + "test_windows.py", + "__main__.py", + "test_connections.py", + "test_sunos.py", + "test_system.py", + "test_testutils.py", + "test_unicode.py", + "test_osx.py", + "test_process.py", + "runner.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pycparser", + "files": [ + "c_lexer.py", + "c_parser.py", + "_ast_gen.py", + "_c_ast.cfg", + "ast_transforms.py", + "c_generator.py", + "__init__.py", + "c_ast.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pycparser-3.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL", + "licenses", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pycparser-3.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pyopenssl-26.2.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL", + "licenses", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pyopenssl-26.2.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pyotp", + "files": [ + "__init__.py", + "compat.py", + "otp.py", + "py.typed", + "totp.py", + "utils.py", + "contrib", + "hotp.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info", + "files": [ + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pyotp/contrib", + "files": [ + "__init__.py", + "steam.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info", + "files": [ + "zip-safe", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "WHEEL", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz", + "files": [ + "tzinfo.py", + "zoneinfo", + "__init__.py", + "exceptions.py", + "lazy.py", + "reference.py", + "tzfile.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz-2026.2.dist-info", + "files": [ + "RECORD", + "WHEEL", + "top_level.txt", + "zip-safe", + "INSTALLER", + "LICENSE.txt", + "METADATA" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo", + "files": [ + "America", + "Etc", + "Zulu", + "Turkey", + "UTC", + "iso3166.tab", + "EST", + "Eire", + "PST8PDT", + "Singapore", + "zone.tab", + "Canada", + "Egypt", + "NZ-CHAT", + "ROC", + "ROK", + "CST6CDT", + "Japan", + "Poland", + "Portugal", + "W-SU", + "Arctic", + "Asia", + "GB", + "Iceland", + "MST7MDT", + "GMT0", + "GMT+0", + "GMT-0", + "zone1970.tab", + "Europe", + "GMT", + "NZ", + "MST", + "Mexico", + "WET", + "HST", + "Hongkong", + "Libya", + "UCT" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Africa", + "files": [ + "Kampala", + "Khartoum", + "Kinshasa", + "Malabo", + "Nouakchott", + "Timbuktu", + "Accra", + "Addis_Ababa", + "Bangui", + "Bujumbura", + "Dakar", + "Lome", + "Tunis", + "Windhoek", + "Bissau", + "Blantyre", + "Johannesburg", + "Juba", + "Maseru", + "Mbabane", + "Ndjamena", + "Sao_Tome", + "Banjul", + "Conakry", + "Douala", + "Lagos", + "Algiers", + "Asmara", + "Brazzaville", + "Casablanca", + "Lubumbashi", + "Ouagadougou", + "El_Aaiun", + "Freetown", + "Libreville", + "Maputo", + "Mogadishu", + "Niamey", + "Dar_es_Salaam", + "Djibouti" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/America", + "files": [ + "Aruba", + "Barbados", + "Chihuahua", + "Coral_Harbour", + "Lower_Princes", + "Argentina", + "Buenos_Aires", + "Guatemala", + "Rainy_River", + "St_Johns", + "Swift_Current", + "Nipigon", + "Coyhaique", + "Miquelon", + "St_Vincent", + "Anguilla", + "Antigua", + "Cancun", + "Detroit", + "Iqaluit", + "Montevideo", + "New_York", + "Edmonton", + "Shiprock", + "Monterrey", + "Belem", + "Asuncion", + "Eirunepe", + "Halifax", + "Indiana", + "Matamoros", + "Manaus", + "Marigot", + "Martinique", + "Merida", + "Montreal", + "Rankin_Inlet", + "Danmarkshavn", + "Jujuy", + "Noronha" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/America/Argentina", + "files": [ + "Buenos_Aires", + "ComodRivadavia", + "Jujuy", + "La_Rioja", + "Mendoza", + "Salta", + "Ushuaia", + "Catamarca", + "Cordoba", + "Rio_Gallegos", + "San_Juan", + "San_Luis", + "Tucuman" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/America/Indiana", + "files": [ + "Marengo", + "Petersburg", + "Tell_City", + "Vevay", + "Vincennes", + "Winamac", + "Indianapolis", + "Knox" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/America/Kentucky", + "files": [ + "Louisville", + "Monticello" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/America/North_Dakota", + "files": [ + "New_Salem", + "Beulah", + "Center" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Antarctica", + "files": [ + "Rothera", + "South_Pole", + "Syowa", + "Troll", + "Vostok", + "Casey", + "Davis", + "DumontDUrville", + "McMurdo", + "Palmer", + "Macquarie", + "Mawson" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Arctic", + "files": [ + "Longyearbyen" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Asia", + "files": [ + "Almaty", + "Chongqing", + "Harbin", + "Tomsk", + "Bishkek", + "Kamchatka", + "Macao", + "Manila", + "Riyadh", + "Seoul", + "Thimbu", + "Tokyo", + "Aden", + "Colombo", + "Hong_Kong", + "Jayapura", + "Kabul", + "Qyzylorda", + "Srednekolymsk", + "Macau", + "Chita", + "Choibalsan", + "Chungking", + "Dili", + "Ho_Chi_Minh", + "Jerusalem", + "Qostanay", + "Amman", + "Anadyr", + "Baghdad", + "Beirut", + "Kashgar", + "Ujung_Pandang", + "Yakutsk", + "Brunei", + "Kolkata", + "Samarkand", + "Thimphu", + "Vladivostok", + "Ashkhabad" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Atlantic", + "files": [ + "Cape_Verde", + "Faeroe", + "Faroe", + "Madeira", + "South_Georgia", + "Stanley", + "Canary", + "Jan_Mayen", + "Reykjavik", + "St_Helena", + "Azores", + "Bermuda" + ] + }, + { + "added": [], + "changed_count": 23, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Australia", + "files": [ + "Darwin", + "Hobart", + "NSW", + "North", + "Queensland", + "Tasmania", + "Adelaide", + "Canberra", + "Lord_Howe", + "South", + "Victoria", + "ACT", + "Broken_Hill", + "LHI", + "Lindeman", + "Melbourne", + "Sydney", + "Yancowinna", + "Eucla", + "Perth", + "West", + "Brisbane", + "Currie" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Brazil", + "files": [ + "West", + "Acre", + "DeNoronha", + "East" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Canada", + "files": [ + "Eastern", + "Mountain", + "Newfoundland", + "Pacific", + "Saskatchewan", + "Yukon", + "Atlantic", + "Central" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Chile", + "files": [ + "Continental", + "EasterIsland" + ] + }, + { + "added": [], + "changed_count": 35, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Etc", + "files": [ + "GMT-4", + "GMT-8", + "GMT0", + "GMT-7", + "GMT+1", + "GMT+4", + "GMT-12", + "GMT", + "GMT+5", + "GMT+7", + "GMT-1", + "GMT-2", + "GMT-5", + "GMT-6", + "GMT+11", + "GMT+2", + "GMT+8", + "UCT", + "Zulu", + "GMT+0", + "GMT+10", + "GMT-10", + "GMT-13", + "GMT-9", + "Greenwich", + "UTC", + "GMT+12", + "GMT+9", + "GMT-0", + "GMT-14", + "GMT-3", + "GMT+3", + "GMT+6", + "GMT-11", + "Universal" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Europe", + "files": [ + "Gibraltar", + "London", + "Moscow", + "Prague", + "Uzhgorod", + "Helsinki", + "Monaco", + "Samara", + "Kirov", + "San_Marino", + "Tiraspol", + "Vaduz", + "Zaporozhye", + "Guernsey", + "Stockholm", + "Ulyanovsk", + "Volgograd", + "Dublin", + "Kiev", + "Malta", + "Paris", + "Zagreb", + "Copenhagen", + "Kyiv", + "Mariehamn", + "Jersey", + "Kaliningrad", + "Ljubljana", + "Nicosia", + "Sofia", + "Tallinn", + "Vatican", + "Berlin", + "Brussels", + "Bucharest", + "Minsk", + "Rome", + "Skopje", + "Belgrade", + "Bratislava" + ] + }, + { + "added": [], + "changed_count": 11, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Indian", + "files": [ + "Mahe", + "Maldives", + "Mauritius", + "Reunion", + "Antananarivo", + "Christmas", + "Cocos", + "Comoro", + "Kerguelen", + "Mayotte", + "Chagos" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Mexico", + "files": [ + "BajaSur", + "General", + "BajaNorte" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/Pacific", + "files": [ + "Enderbury", + "Fiji", + "Gambier", + "Guadalcanal", + "Saipan", + "Yap", + "Auckland", + "Efate", + "Kiritimati", + "Niue", + "Port_Moresby", + "Truk", + "Galapagos", + "Guam", + "Kanton", + "Tongatapu", + "Chatham", + "Easter", + "Fakaofo", + "Honolulu", + "Johnston", + "Norfolk", + "Samoa", + "Tahiti", + "Kosrae", + "Kwajalein", + "Majuro", + "Pago_Pago", + "Pitcairn", + "Pohnpei", + "Tarawa", + "Wake", + "Apia", + "Bougainville", + "Funafuti", + "Palau", + "Ponape", + "Chuuk", + "Marquesas", + "Midway" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/pytz/zoneinfo/US", + "files": [ + "Eastern", + "Hawaii", + "Michigan", + "Mountain", + "Pacific", + "Alaska", + "Aleutian", + "Indiana-Starke", + "Samoa", + "Arizona", + "Central", + "East-Indiana" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/referencing", + "files": [ + "py.typed", + "retrieval.py", + "tests", + "typing.py", + "__init__.py", + "_attrs.py", + "_attrs.pyi", + "jsonschema.py", + "_core.py", + "exceptions.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/referencing-0.37.0.dist-info", + "files": [ + "WHEEL", + "licenses", + "INSTALLER", + "METADATA", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/referencing-0.37.0.dist-info/licenses", + "files": [ + "COPYING" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/referencing/tests", + "files": [ + "test_core.py", + "test_exceptions.py", + "test_jsonschema.py", + "test_referencing_suite.py", + "test_retrieval.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 18, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/requests", + "files": [ + "__init__.py", + "_internal_utils.py", + "adapters.py", + "api.py", + "hooks.py", + "status_codes.py", + "__version__.py", + "certs.py", + "compat.py", + "exceptions.py", + "models.py", + "packages.py", + "sessions.py", + "auth.py", + "cookies.py", + "help.py", + "structures.py", + "utils.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/requests-2.32.4.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/requests-2.32.4.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/rpds", + "files": [ + "__init__.py", + "__init__.pyi", + "py.typed", + "rpds.cpython-312-x86_64-linux-gnu.so" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/rpds_py-2026.5.1.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL", + "licenses", + "sboms" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/rpds_py-2026.5.1.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/rpds_py-2026.5.1.dist-info/sboms", + "files": [ + "rpds-py.cyclonedx.json" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools", + "files": [ + "_core_metadata.py", + "_entry_points.py", + "command", + "config", + "errors.py", + "monkey.py", + "msvc.py", + "script.tmpl", + "cli-64.exe", + "cli-arm64.exe", + "compat", + "_distutils", + "_shutil.py", + "_static.py", + "_vendor", + "_discovery.py", + "discovery.py", + "gui-64.exe", + "installer.py", + "_itertools.py", + "_reqs.py", + "gui-32.exe", + "logging.py", + "modified.py", + "unicode_utils.py", + "version.py", + "wheel.py", + "extension.py", + "glob.py", + "launch.py", + "namespaces.py", + "script (dev).tmpl", + "tests", + "warnings.py", + "windows_support.py", + "build_meta.py", + "cli-32.exe", + "depends.py", + "dist.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools-82.0.1.dist-info", + "files": [ + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "entry_points.txt", + "licenses" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools-82.0.1.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 32, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils", + "files": [ + "dir_util.py", + "dist.py", + "errors.py", + "filelist.py", + "log.py", + "zosccompiler.py", + "cmd.py", + "compilers", + "_macos_compat.py", + "core.py", + "sysconfig.py", + "_msvccompiler.py", + "command", + "compat", + "unixccompiler.py", + "version.py", + "_log.py", + "debug.py", + "fancy_getopt.py", + "spawn.py", + "tests", + "file_util.py", + "util.py", + "archive_util.py", + "ccompiler.py", + "cygwinccompiler.py", + "dep_util.py", + "text_file.py", + "versionpredicate.py", + "__init__.py", + "extension.py", + "_modified.py" + ] + }, + { + "added": [], + "changed_count": 20, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils/command", + "files": [ + "check.py", + "build_clib.py", + "install.py", + "install_headers.py", + "install_scripts.py", + "sdist.py", + "_framework_compat.py", + "bdist.py", + "build.py", + "install_egg_info.py", + "install_lib.py", + "clean.py", + "__init__.py", + "bdist_dumb.py", + "bdist_rpm.py", + "build_ext.py", + "build_py.py", + "build_scripts.py", + "config.py", + "install_data.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils/compat", + "files": [ + "py39.py", + "__init__.py", + "numpy.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils/compilers", + "files": [ + "C" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils/compilers/C", + "files": [ + "zos.py", + "base.py", + "cygwin.py", + "errors.py", + "msvc.py", + "tests", + "unix.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils/compilers/C/tests", + "files": [ + "test_msvc.py", + "test_unix.py", + "test_base.py", + "test_cygwin.py", + "test_mingw.py" + ] + }, + { + "added": [], + "changed_count": 37, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils/tests", + "files": [ + "test_text_file.py", + "test_build_ext.py", + "test_build_scripts.py", + "test_install_headers.py", + "test_install_lib.py", + "test_versionpredicate.py", + "__init__.py", + "compat", + "test_archive_util.py", + "test_bdist_rpm.py", + "test_sysconfig.py", + "test_install_data.py", + "test_install_scripts.py", + "test_build.py", + "test_cmd.py", + "test_dist.py", + "test_file_util.py", + "test_version.py", + "support.py", + "test_log.py", + "test_spawn.py", + "test_clean.py", + "test_core.py", + "test_modified.py", + "test_filelist.py", + "test_install.py", + "test_sdist.py", + "unix_compat.py", + "test_build_clib.py", + "test_check.py", + "test_dir_util.py", + "test_extension.py", + "test_util.py", + "test_bdist.py", + "test_bdist_dumb.py", + "test_build_py.py", + "test_config_cmd.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_distutils/tests/compat", + "files": [ + "__init__.py", + "py39.py" + ] + }, + { + "added": [], + "changed_count": 23, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor", + "files": [ + "autocommand", + "importlib_metadata", + "more_itertools-10.8.0.dist-info", + "platformdirs-4.4.0.dist-info", + "tomli", + "zipp", + ".lock", + "autocommand-2.2.2.dist-info", + "jaraco_context-6.1.0.dist-info", + "more_itertools", + "packaging", + "tomli-2.4.0.dist-info", + "backports", + "jaraco", + "jaraco_functools-4.4.0.dist-info", + "platformdirs", + "zipp-3.23.0.dist-info", + "backports.tarfile-1.2.0.dist-info", + "importlib_metadata-8.7.1.dist-info", + "jaraco.text-4.0.0.dist-info", + "packaging-26.0.dist-info", + "wheel", + "wheel-0.46.3.dist-info" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/autocommand", + "files": [ + "autocommand.py", + "automain.py", + "autoparse.py", + "errors.py", + "__init__.py", + "autoasync.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/autocommand-2.2.2.dist-info", + "files": [ + "top_level.txt", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/backports", + "files": [ + "__init__.py", + "tarfile" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/backports.tarfile-1.2.0.dist-info", + "files": [ + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "top_level.txt", + "INSTALLER" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/backports/tarfile", + "files": [ + "__init__.py", + "__main__.py", + "compat" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/backports/tarfile/compat", + "files": [ + "__init__.py", + "py38.py" + ] + }, + { + "added": [], + "changed_count": 12, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/importlib_metadata", + "files": [ + "diagnose.py", + "py.typed", + "__init__.py", + "_compat.py", + "_functools.py", + "_text.py", + "_typing.py", + "compat", + "_adapters.py", + "_collections.py", + "_itertools.py", + "_meta.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/importlib_metadata-8.7.1.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/importlib_metadata-8.7.1.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/importlib_metadata/compat", + "files": [ + "__init__.py", + "py311.py", + "py39.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco", + "files": [ + "context", + "functools", + "text" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco.text-4.0.0.dist-info", + "files": [ + "WHEEL", + "top_level.txt", + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco/context", + "files": [ + "py.typed", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco/functools", + "files": [ + "__init__.py", + "__init__.pyi", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco/text", + "files": [ + "Lorem ipsum.txt", + "__init__.py", + "layouts.py", + "show-newlines.py", + "strip-prefix.py", + "to-dvorak.py", + "to-qwerty.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco_context-6.1.0.dist-info", + "files": [ + "REQUESTED", + "WHEEL", + "licenses", + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco_context-6.1.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco_functools-4.4.0.dist-info", + "files": [ + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/jaraco_functools-4.4.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/more_itertools", + "files": [ + "more.py", + "more.pyi", + "py.typed", + "recipes.py", + "recipes.pyi", + "__init__.py", + "__init__.pyi" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/more_itertools-10.8.0.dist-info", + "files": [ + "RECORD", + "REQUESTED", + "WHEEL", + "licenses", + "INSTALLER", + "METADATA" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/more_itertools-10.8.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 17, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/packaging", + "files": [ + "markers.py", + "version.py", + "__init__.py", + "tags.py", + "_tokenizer.py", + "licenses", + "metadata.py", + "py.typed", + "pylock.py", + "utils.py", + "_elffile.py", + "_manylinux.py", + "_parser.py", + "_structures.py", + "specifiers.py", + "requirements.py", + "_musllinux.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/packaging-26.0.dist-info", + "files": [ + "licenses", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/packaging-26.0.dist-info/licenses", + "files": [ + "LICENSE", + "LICENSE.APACHE", + "LICENSE.BSD" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/packaging/licenses", + "files": [ + "__init__.py", + "_spdx.py" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/platformdirs", + "files": [ + "__init__.py", + "__main__.py", + "py.typed", + "windows.py", + "android.py", + "api.py", + "macos.py", + "unix.py", + "version.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/platformdirs-4.4.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/platformdirs-4.4.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/tomli", + "files": [ + "__init__.py", + "_parser.py", + "_re.py", + "_types.py", + "py.typed" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/tomli-2.4.0.dist-info", + "files": [ + "REQUESTED", + "WHEEL", + "licenses", + "INSTALLER", + "METADATA", + "RECORD" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/tomli-2.4.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/wheel", + "files": [ + "_setuptools_logging.py", + "bdist_wheel.py", + "wheelfile.py", + "__main__.py", + "_commands", + "macosx_libfile.py", + "metadata.py", + "__init__.py", + "_bdist_wheel.py", + "_metadata.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/wheel-0.46.3.dist-info", + "files": [ + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "entry_points.txt", + "licenses", + "INSTALLER" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/wheel-0.46.3.dist-info/licenses", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/wheel/_commands", + "files": [ + "tags.py", + "unpack.py", + "__init__.py", + "convert.py", + "pack.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/zipp", + "files": [ + "compat", + "glob.py", + "__init__.py", + "_functools.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/zipp-3.23.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/zipp-3.23.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/_vendor/zipp/compat", + "files": [ + "__init__.py", + "overlay.py", + "py310.py", + "py313.py" + ] + }, + { + "added": [], + "changed_count": 24, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/command", + "files": [ + "_requirestxt.py", + "alias.py", + "bdist_egg.py", + "bdist_rpm.py", + "build_py.py", + "dist_info.py", + "editable_wheel.py", + "install_lib.py", + "__init__.py", + "bdist_wheel.py", + "build_clib.py", + "develop.py", + "install.py", + "install_scripts.py", + "rotate.py", + "build_ext.py", + "easy_install.py", + "egg_info.py", + "install_egg_info.py", + "saveopts.py", + "test.py", + "build.py", + "sdist.py", + "setopt.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/compat", + "files": [ + "py311.py", + "py312.py", + "py39.py", + "__init__.py", + "py310.py" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/config", + "files": [ + "NOTICE", + "__init__.py", + "_validate_pyproject", + "expand.py", + "pyprojecttoml.py", + "setuptools.schema.json", + "_apply_pyprojecttoml.py", + "distutils.schema.json", + "setupcfg.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/config/_validate_pyproject", + "files": [ + "__init__.py", + "error_reporting.py", + "extra_validations.py", + "fastjsonschema_exceptions.py", + "fastjsonschema_validations.py", + "formats.py", + "NOTICE" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests", + "files": [ + "test_build_py.py", + "test_find_py_modules.py", + "test_virtualenv.py", + "test_wheel.py", + "test_bdist_deprecations.py", + "test_distutils_adoption.py", + "compat", + "indexes", + "test_config_discovery.py", + "test_core_metadata.py", + "test_find_packages.py", + "test_logging.py", + "test_namespaces.py", + "__init__.py", + "namespaces.py", + "test_archive_util.py", + "test_build_meta.py", + "test_dist.py", + "test_warnings.py", + "integration", + "script-with-bom.py", + "test_bdist_wheel.py", + "test_build_clib.py", + "test_depends.py", + "test_dist_info.py", + "test_install_scripts.py", + "test_manifest.py", + "config", + "environment.py", + "test_build.py", + "test_develop.py", + "test_scripts.py", + "test_setopt.py", + "test_shutil_wrapper.py", + "contexts.py", + "mod_with_constant.py", + "test_bdist_egg.py", + "test_editable_install.py", + "test_egg_info.py", + "test_setuptools.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/compat", + "files": [ + "py39.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/config", + "files": [ + "setupcfg_examples.txt", + "test_apply_pyprojecttoml.py", + "test_expand.py", + "downloads", + "test_pyprojecttoml.py", + "test_pyprojecttoml_dynamic_deps.py", + "test_setupcfg.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/config/downloads", + "files": [ + "__init__.py", + "preload.py" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/indexes", + "files": [ + "test_links_priority" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority", + "files": [ + "external.html", + "simple" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/simple", + "files": [ + "foobar" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/simple/foobar", + "files": [ + "index.html" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/setuptools/tests/integration", + "files": [ + "test_pip_install_sdist.py", + "__init__.py", + "helpers.py", + "test_pbr.py" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/six-1.17.0.dist-info", + "files": [ + "METADATA", + "RECORD", + "WHEEL", + "top_level.txt", + "INSTALLER", + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/sniffio", + "files": [ + "py.typed", + "__init__.py", + "_impl.py", + "_tests", + "_version.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/sniffio-1.3.1.dist-info", + "files": [ + "top_level.txt", + "INSTALLER", + "LICENSE", + "LICENSE.APACHE2", + "LICENSE.MIT", + "METADATA", + "RECORD", + "WHEEL" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/sniffio/_tests", + "files": [ + "test_sniffio.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/termcolor-1.1.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/termcolor-1.1.0.dist-info/licenses", + "files": [ + "COPYING.txt" + ] + }, + { + "added": [], + "changed_count": 37, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado", + "files": [ + "__init__.py", + "concurrent.py", + "escape.py", + "http1connection.py", + "routing.py", + "speedups.pyi", + "__init__.pyi", + "process.py", + "simple_httpclient.py", + "tcpclient.py", + "wsgi.py", + "httputil.py", + "curl_httpclient.py", + "ioloop.py", + "log.py", + "platform", + "auth.py", + "httpserver.py", + "template.py", + "_locale_data.py", + "httpclient.py", + "locks.py", + "netutil.py", + "options.py", + "queues.py", + "websocket.py", + "speedups.abi3.so", + "py.typed", + "gen.py", + "iostream.py", + "locale.py", + "tcpserver.py", + "test", + "testing.py", + "autoreload.py", + "util.py", + "web.py" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado-6.5.4.dist-info", + "files": [ + "top_level.txt", + "INSTALLER", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "licenses" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado-6.5.4.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/platform", + "files": [ + "asyncio.py", + "caresresolver.py", + "twisted.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 40, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test", + "files": [ + "ioloop_test.py", + "queues_test.py", + "routing_test.py", + "gettext_translations", + "log_test.py", + "static_foo.txt", + "twisted_test.py", + "autoreload_test.py", + "circlerefs_test.py", + "locale_test.py", + "locks_test.py", + "resolve_test_helper.py", + "templates", + "util_test.py", + "httpserver_test.py", + "static", + "tcpserver_test.py", + "test.key", + "websocket_test.py", + "__init__.py", + "asyncio_test.py", + "auth_test.py", + "escape_test.py", + "gen_test.py", + "httputil_test.py", + "netutil_test.py", + "options_test_types.cfg", + "iostream_test.py", + "options_test.cfg", + "options_test.py", + "process_test.py", + "runtests.py", + "simple_httpclient_test.py", + "tcpclient_test.py", + "template_test.py", + "concurrent_test.py", + "csv_translations", + "httpclient_test.py", + "options_test_types_str.cfg", + "test.crt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test/csv_translations", + "files": [ + "fr_FR.csv" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test/gettext_translations", + "files": [ + "fr_FR" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test/gettext_translations/fr_FR", + "files": [ + "LC_MESSAGES" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test/gettext_translations/fr_FR/LC_MESSAGES", + "files": [ + "tornado_test.mo", + "tornado_test.po" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test/static", + "files": [ + "sample.xml", + "sample.xml.bz2", + "sample.xml.gz", + "dir", + "robots.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test/static/dir", + "files": [ + "index.html" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tornado/test/templates", + "files": [ + "utf8.html" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/typing_extensions-4.15.0.dist-info", + "files": [ + "RECORD", + "WHEEL", + "licenses", + "INSTALLER", + "METADATA" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/typing_extensions-4.15.0.dist-info/licenses", + "files": [ + "LICENSE" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tzlocal", + "files": [ + "unix.py", + "utils.py", + "win32.py", + "windows_tz.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 8, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/tzlocal-5.1.dist-info", + "files": [ + "WHEEL", + "top_level.txt", + "zip-safe", + "INSTALLER", + "LICENSE.txt", + "METADATA", + "RECORD", + "REQUESTED" + ] + }, + { + "added": [], + "changed_count": 16, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/urllib3", + "files": [ + "contrib", + "filepost.py", + "http2", + "util", + "fields.py", + "_base_connection.py", + "_collections.py", + "connectionpool.py", + "exceptions.py", + "py.typed", + "_request_methods.py", + "response.py", + "poolmanager.py", + "__init__.py", + "_version.py", + "connection.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info", + "files": [ + "INSTALLER", + "METADATA", + "RECORD", + "WHEEL", + "licenses" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/licenses", + "files": [ + "LICENSE.txt" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/urllib3/contrib", + "files": [ + "pyopenssl.py", + "socks.py", + "__init__.py", + "emscripten" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/urllib3/contrib/emscripten", + "files": [ + "emscripten_fetch_worker.js", + "fetch.py", + "request.py", + "response.py", + "__init__.py", + "connection.py" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/urllib3/http2", + "files": [ + "__init__.py", + "connection.py", + "probe.py" + ] + }, + { + "added": [], + "changed_count": 13, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/urllib3/util", + "files": [ + "__init__.py", + "proxy.py", + "response.py", + "retry.py", + "ssl_.py", + "timeout.py", + "url.py", + "connection.py", + "request.py", + "ssl_match_hostname.py", + "ssltransport.py", + "util.py", + "wait.py" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn", + "files": [ + "helpers", + "py.typed", + "registration", + "__init__.py", + "authentication" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn-2.0.0.dist-info", + "files": [ + "INSTALLER", + "LICENSE", + "METADATA", + "RECORD", + "REQUESTED", + "WHEEL", + "top_level.txt" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn/authentication", + "files": [ + "generate_authentication_options.py", + "verify_authentication_response.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 32, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn/helpers", + "files": [ + "generate_challenge.py", + "options_to_json.py", + "verify_signature.py", + "algorithms.py", + "structs.py", + "__init__.py", + "exceptions.py", + "hash_by_alg.py", + "validate_certificate_chain.py", + "verify_safetynet_timestamp.py", + "decoded_public_key_to_cryptography.py", + "pem_cert_bytes_to_open_ssl_x509.py", + "base64url_to_bytes.py", + "decode_credential_public_key.py", + "known_root_certs.py", + "parse_authentication_credential_json.py", + "parse_client_data_json.py", + "aaguid_to_string.py", + "byteslike_to_bytes.py", + "parse_attestation_statement.py", + "snake_case_to_camel_case.py", + "tpm", + "bytes_to_base64url.py", + "encode_cbor.py", + "parse_attestation_object.py", + "parse_registration_credential_json.py", + "asn1", + "cose.py", + "generate_user_handle.py", + "parse_authenticator_data.py", + "parse_backup_flags.py", + "parse_cbor.py" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn/helpers/asn1", + "files": [ + "android_key.py", + "__init__.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn/helpers/tpm", + "files": [ + "__init__.py", + "parse_cert_info.py", + "parse_pub_area.py", + "structs.py" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn/registration", + "files": [ + "generate_registration_options.py", + "verify_registration_response.py", + "__init__.py", + "formats" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/webauthn/registration/formats", + "files": [ + "packed.py", + "tpm.py", + "__init__.py", + "android_key.py", + "android_safetynet.py", + "apple.py", + "fido_u2f.py" + ] + }, + { + "added": [], + "changed_count": 18, + "db_signature": false, + "dir": "/crafty/.venv/lib/python3.12/site-packages/yaml", + "files": [ + "composer.py", + "resolver.py", + "tokens.py", + "error.py", + "events.py", + "nodes.py", + "parser.py", + "cyaml.py", + "emitter.py", + "loader.py", + "reader.py", + "representer.py", + "serializer.py", + "__init__.py", + "constructor.py", + "dumper.py", + "scanner.py", + "_yaml.cpython-312-x86_64-linux-gnu.so" + ] + }, + { + "added": [ + "config" + ], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/app", + "files": [ + "config" + ] + }, + { + "added": [], + "changed_count": 11, + "db_signature": false, + "dir": "/crafty/app/classes", + "files": [ + "models", + "web", + "helpers", + "logging", + "remote_stats", + "shared", + "steamcmd", + "big_bucket", + "controllers", + "exceptions", + "installers" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/app/classes/web", + "files": [ + "routes", + "webhooks" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes", + "files": [ + "metrics", + "api" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api", + "files": [ + "servers", + "crafty", + "roles", + "users", + "auth" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/crafty", + "files": [ + "imports", + "upload", + "announcements", + "clogs", + "stats", + "antilockout", + "config" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/roles", + "files": [ + "role" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/servers", + "files": [ + "server" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/servers/server", + "files": [ + "tasks", + "webhooks", + "backups" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/servers/server/backups", + "files": [ + "backup" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/servers/server/tasks", + "files": [ + "task" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/servers/server/webhooks", + "files": [ + "webhook" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/classes/web/routes/api/users", + "files": [ + "user" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/config_original", + "files": [ + "default.json" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/app/frontend", + "files": [ + "static", + "templates" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/frontend/static", + "files": [ + "assets" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets", + "files": [ + "css", + "fonts", + "images", + "js", + "vendors" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/css", + "files": [ + "shared", + "vendors", + "dark", + "internal", + "partial", + "themes" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/css/shared", + "files": [ + "screens" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/fonts", + "files": [ + "Atkinson_Hyperlegible_Next", + "Roboto", + "Sarabun" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/fonts/Atkinson_Hyperlegible_Next", + "files": [ + "static" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/images", + "files": [ + "credits", + "faces-clipart", + "sprites", + "auth" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/js", + "files": [ + "shared" + ] + }, + { + "added": [], + "changed_count": 10, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors", + "files": [ + "flag-icon-css", + "phosphoricons", + "ti-icons", + "typicons", + "vanilla-datetimerange-picker", + "css", + "js", + "mdi", + "moment", + "ace-builds" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors/ace-builds", + "files": [ + "src-min" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors/ace-builds/src-min", + "files": [ + "snippets" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors/flag-icon-css", + "files": [ + "css", + "flags" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors/flag-icon-css/flags", + "files": [ + "1x1", + "4x3" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors/mdi", + "files": [ + "css", + "fonts" + ] + }, + { + "added": [], + "changed_count": 6, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors/phosphoricons", + "files": [ + "fill", + "light", + "regular", + "thin", + "bold", + "duotone" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/crafty/app/frontend/static/assets/vendors/ti-icons", + "files": [ + "fonts", + "css" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/app/frontend/templates", + "files": [ + "setup", + "panel", + "public", + "server" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/frontend/templates/panel", + "files": [ + "parts" + ] + }, + { + "added": [ + "status" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/app/migrations", + "files": [ + "status" + ] + }, + { + "added": [ + "20240308_multi-backup.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/crafty/app/migrations/status", + "files": [ + "20240308_multi-backup.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/tests", + "files": [ + "classes" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/crafty/tests/classes", + "files": [ + "helpers", + "models", + "shared", + "web" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/crafty/tests/classes/web", + "files": [ + "api" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "bin" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/bin", + "files": [ + "python3.12" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 5365, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "registry.gitlab.com/crafty-controller/crafty-4:4.10.7", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-craftycontroller_crafty_servers", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-craftycontroller_crafty_servers/_data", + "target": "/crafty/servers", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 16, + "name": "volgate-craftycontroller_crafty_config", + "sample": [ + "bigbucket.json", + "default.json", + "session.lock", + "motd_format.json", + "steamapps.json", + "default-creds.txt", + "hytale.json", + "version.json", + "logging.json", + "credits.json", + "config.json", + "web/certs/commander.key.pem", + "web/certs/commander.cert.pem", + "db/crafty.sqlite-shm", + "db/crafty.sqlite", + "db/crafty.sqlite-wal" + ], + "source": "/var/lib/docker/volumes/volgate-craftycontroller_crafty_config/_data", + "target": "/crafty/app/config", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-craftycontroller_crafty_import", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-craftycontroller_crafty_import/_data", + "target": "/crafty/import", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-craftycontroller_crafty_backups", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-craftycontroller_crafty_backups/_data", + "target": "/crafty/backups", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 8, + "name": "volgate-craftycontroller_crafty_logs", + "sample": [ + "tornado-access.log", + "session.log", + "audit.log", + "auth_tracker.log", + "auth.log", + "commander.log", + "cmd_queue.log", + "schedule.log" + ], + "source": "/var/lib/docker/volumes/volgate-craftycontroller_crafty_logs/_data", + "target": "/crafty/logs", + "writable_by_app": "yes" + } + ], + "name": "crafty-controller", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "crafty_backups", + "crafty_config", + "crafty_import", + "crafty_logs", + "crafty_servers" + ], + "env_keys": [ + "CRAFTY_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "crafty-controller: declared volume /crafty/servers is EMPTY", + "crafty-controller: declared volume /crafty/import is EMPTY", + "crafty-controller: declared volume /crafty/backups is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/docmost/probe.json b/audits/persistence-sweep-2026-08-02/evidence/docmost/probe.json new file mode 100644 index 0000000..2f942fa --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/docmost/probe.json @@ -0,0 +1,217 @@ +{ + "probe": { + "app": "docmost", + "containers": [ + { + "diff_added": 1, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "apps" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps", + "files": [ + "client" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps/client", + "files": [ + "dist" + ] + }, + { + "added": [ + "index-template.html" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps/client/dist", + "files": [ + "index-template.html" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 6, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "docmost/docmost:0.95.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-docmost_docmost_storage", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-docmost_docmost_storage/_data", + "target": "/app/data/storage", + "writable_by_app": "yes" + } + ], + "name": "docmost", + "restarts": null, + "status": "running", + "uid": 1000 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1540, + "name": "volgate-docmost_docmost_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-docmost_docmost_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "docmost-postgres", + "restarts": null, + "status": "running", + "uid": 70 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-docmost_docmost_redis_data", + "sample": [ + "appendonlydir/appendonly.aof.1.base.rdb", + "appendonlydir/appendonly.aof.1.incr.aof", + "appendonlydir/appendonly.aof.manifest" + ], + "source": "/var/lib/docker/volumes/volgate-docmost_docmost_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "docmost-redis", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "docmost_postgres_data", + "docmost_redis_data", + "docmost_storage" + ], + "env_keys": [ + "APP_SECRET", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "docmost: this container's mounts are all empty while it created entries in ['/app/apps/client/dist'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "docmost: declared volume /app/data/storage is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/emby/probe.json b/audits/persistence-sweep-2026-08-02/evidence/emby/probe.json new file mode 100644 index 0000000..9b418c1 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/emby/probe.json @@ -0,0 +1,119 @@ +{ + "probe": { + "app": "emby", + "containers": [ + { + "diff_added": 205, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "config", + "media" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "config", + "media" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 207, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "emby/embyserver:4.10.0.20", + "mounts": [ + { + "class": "named-declared", + "files": 46, + "name": "volgate-emby_emby_config", + "sample": [ + "cache/ffmpeg/filters_4.10.0.20.txt", + "cache/ffmpeg/version_4.10.0.20.txt", + "cache/ffmpeg/decoders_4.10.0.20.txt", + "cache/ffmpeg/encoders_4.10.0.20.txt", + "cache/ffmpeg/protocols_4.10.0.20.txt", + "cache/ffmpeg/hwaccels_4.10.0.20.txt", + "cache/httpclient/0fb243ded9649ae6d18059c674a4ae00_1", + "config/system.xml", + "config/users/a7acc67887ba440f9db1312c97475098/policy.xml", + "plugins/DvdMounter.dll", + "plugins/OMDb.dll", + "plugins/Emby.PortMapper.dll", + "plugins/EmbyGuideData.dll", + "plugins/MusicBrainz.dll", + "plugins/AudioDb.dll", + "plugins/MovieDb.dll", + "plugins/Emby.Server.CinemaMode.dll", + "plugins/Emby.M3UTuner.dll", + "plugins/Emby.Dlna.dll", + "plugins/Fanart.dll", + "plugins/Tvdb.dll", + "plugins/NfoMetadata.dll", + "plugins/StudioImages.dll", + "plugins/Emby.Webhooks.dll", + "plugins/MBBackup.dll", + "plugins/OpenSubtitles.dll", + "plugins/Emby.XmlTV.dll", + "plugins/BlurayMounter.dll", + "data/lastversion.txt", + "data/wan.dat", + "data/library.db", + "data/authentication.db-wal", + "data/device.txt", + "data/activitylog.db-shm", + "data/authentication.db", + "data/users.db-shm", + "data/library.db-shm", + "data/users.db-wal", + "data/authentication.db-shm", + "data/activitylog.db" + ], + "source": "/var/lib/docker/volumes/volgate-emby_emby_config/_data", + "target": "/config", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 3, + "name": null, + "sample": [ + "books/metadata.db", + "books/metadata.db-shm", + "books/metadata.db-wal" + ], + "source": "/srv/felhom-gate/hdd/userdata/media", + "target": "/media", + "writable_by_app": "yes" + } + ], + "name": "emby", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "emby_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/ghost/probe.json b/audits/persistence-sweep-2026-08-02/evidence/ghost/probe.json new file mode 100644 index 0000000..a29f268 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/ghost/probe.json @@ -0,0 +1,59 @@ +{ + "probe": { + "app": "ghost", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "ghost:6.53.0-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 7, + "name": "volgate-ghost_ghost_content", + "sample": [ + "themes/casper", + "themes/source", + "data/ghost.db", + "logs/https___blog_gate_invalid_production.error.log", + "logs/https___blog_gate_invalid_production.log", + "public/admin-auth/index.html", + "public/.well-known/recommendations.json" + ], + "source": "/var/lib/docker/volumes/volgate-ghost_ghost_content/_data", + "target": "/var/lib/ghost/content", + "writable_by_app": "yes" + } + ], + "name": "ghost", + "restarts": null, + "status": "running", + "uid": 1000 + } + ], + "declared_volumes": [ + "ghost_content" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/gitea/probe.json b/audits/persistence-sweep-2026-08-02/evidence/gitea/probe.json new file mode 100644 index 0000000..6c282a4 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/gitea/probe.json @@ -0,0 +1,193 @@ +{ + "probe": { + "app": "gitea", + "containers": [ + { + "diff_added": 16, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data" + ], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "gitea" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/gitea", + "files": [ + "gitea" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/etc", + "files": [ + "ssh", + "s6" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/etc/s6", + "files": [ + "openssh", + ".s6-svscan", + "gitea" + ] + }, + { + "added": [ + "control", + "lock" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/etc/s6/.s6-svscan", + "files": [ + "control", + "lock" + ] + }, + { + "added": [ + "supervise", + "event" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/etc/s6/gitea", + "files": [ + "supervise", + "event" + ] + }, + { + "added": [ + "lock", + "status", + "control", + "death_tally" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/etc/s6/gitea/supervise", + "files": [ + "lock", + "status", + "control", + "death_tally" + ] + }, + { + "added": [ + "event", + "supervise" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/etc/s6/openssh", + "files": [ + "event", + "supervise" + ] + }, + { + "added": [ + "death_tally", + "lock", + "status", + "control" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/etc/s6/openssh/supervise", + "files": [ + "death_tally", + "lock", + "status", + "control" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/ssh", + "files": [ + "sshd_config" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 27, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "gitea/gitea:1.27.0", + "mounts": [ + { + "class": "named-declared", + "files": 8, + "name": "volgate-gitea_gitea_data", + "sample": [ + "git/.ssh/environment", + "gitea/conf/app.ini", + "ssh/ssh_host_ecdsa_key.pub", + "ssh/ssh_host_ed25519_key", + "ssh/ssh_host_ed25519_key.pub", + "ssh/ssh_host_ecdsa_key", + "ssh/ssh_host_rsa_key", + "ssh/ssh_host_rsa_key.pub" + ], + "source": "/var/lib/docker/volumes/volgate-gitea_gitea_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "gitea", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "gitea_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/glance/probe.json b/audits/persistence-sweep-2026-08-02/evidence/glance/probe.json new file mode 100644 index 0000000..d7cc3f9 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/glance/probe.json @@ -0,0 +1,74 @@ +{ + "probe": { + "app": "glance", + "containers": [ + { + "diff_added": 1, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [ + "config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app", + "files": [ + "config" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 2, + "diff_unresolved": [], + "exit": 1, + "gid": null, + "health": "unhealthy", + "image": "glanceapp/glance:v0.8.5", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-glance_glance_config", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-glance_glance_config/_data", + "target": "/app/config", + "writable_by_app": "unknown-uid" + } + ], + "name": "glance", + "restarts": null, + "status": "restarting", + "uid": null + } + ], + "declared_volumes": [ + "glance_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "glance: not running (status=restarting exit=1 restarts=None)" + ], + "verdict": "UNDETERMINED", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/gokapi/probe.json b/audits/persistence-sweep-2026-08-02/evidence/gokapi/probe.json new file mode 100644 index 0000000..a3be5b1 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/gokapi/probe.json @@ -0,0 +1,92 @@ +{ + "probe": { + "app": "gokapi", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [ + "config", + "data" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app", + "files": [ + "config", + "data" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 3, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "f0rc3/gokapi:v1.9.6", + "mounts": [ + { + "class": "named-declared", + "files": 4, + "name": "volgate-gokapi_gokapi_data", + "sample": [ + "log.txt", + "gokapi.sqlite-shm", + "gokapi.sqlite", + "gokapi.sqlite-wal" + ], + "source": "/var/lib/docker/volumes/volgate-gokapi_gokapi_data/_data", + "target": "/app/data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 1, + "name": "volgate-gokapi_gokapi_config", + "sample": [ + "config.json" + ], + "source": "/var/lib/docker/volumes/volgate-gokapi_gokapi_config/_data", + "target": "/app/config", + "writable_by_app": "yes" + } + ], + "name": "gokapi", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "gokapi_config", + "gokapi_data" + ], + "env_keys": [ + "DOMAIN", + "GOKAPI_PASSWORD", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/grafana/probe.json b/audits/persistence-sweep-2026-08-02/evidence/grafana/probe.json new file mode 100644 index 0000000..291c04b --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/grafana/probe.json @@ -0,0 +1,93 @@ +{ + "probe": { + "app": "grafana", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "grafana/grafana:13.1.0", + "mounts": [ + { + "class": "named-declared", + "files": 543, + "name": "volgate-grafana_grafana_data", + "sample": [ + "grafana.db", + "plugins/grafana-exploretraces-app/775.js.map", + "plugins/grafana-exploretraces-app/892.js", + "plugins/grafana-exploretraces-app/102.js", + "plugins/grafana-exploretraces-app/892.js.map", + "plugins/grafana-exploretraces-app/926.js", + "plugins/grafana-exploretraces-app/14.js", + "plugins/grafana-exploretraces-app/766.js.map", + "plugins/grafana-exploretraces-app/941.js", + "plugins/grafana-exploretraces-app/4.js", + "plugins/grafana-exploretraces-app/153.js", + "plugins/grafana-exploretraces-app/145.js", + "plugins/grafana-exploretraces-app/304.js", + "plugins/grafana-exploretraces-app/477.js.map", + "plugins/grafana-exploretraces-app/54.js", + "plugins/grafana-exploretraces-app/914.js.map", + "plugins/grafana-exploretraces-app/903.js", + "plugins/grafana-exploretraces-app/24.js", + "plugins/grafana-exploretraces-app/638.js", + "plugins/grafana-exploretraces-app/24.js.map", + "plugins/grafana-exploretraces-app/24.js.LICENSE.txt", + "plugins/grafana-exploretraces-app/257.js", + "plugins/grafana-exploretraces-app/315.js", + "plugins/grafana-exploretraces-app/397.js", + "plugins/grafana-exploretraces-app/453.js", + "plugins/grafana-exploretraces-app/980.js", + "plugins/grafana-exploretraces-app/944c737f589d02ecf603.svg", + "plugins/grafana-exploretraces-app/485.js", + "plugins/grafana-exploretraces-app/plugin.json", + "plugins/grafana-exploretraces-app/477.js", + "plugins/grafana-exploretraces-app/317.js", + "plugins/grafana-exploretraces-app/276.js", + "plugins/grafana-exploretraces-app/196.js", + "plugins/grafana-exploretraces-app/193.js", + "plugins/grafana-exploretraces-app/397.js.map", + "plugins/grafana-exploretraces-app/684.js", + "plugins/grafana-exploretraces-app/531.js", + "plugins/grafana-exploretraces-app/499.js", + "plugins/grafana-exploretraces-app/276.js.map", + "plugins/grafana-exploretraces-app/LICENSE" + ], + "source": "/var/lib/docker/volumes/volgate-grafana_grafana_data/_data", + "target": "/var/lib/grafana", + "writable_by_app": "yes" + } + ], + "name": "grafana", + "restarts": null, + "status": "running", + "uid": 472 + } + ], + "declared_volumes": [ + "grafana_data" + ], + "env_keys": [ + "DOMAIN", + "GF_SECURITY_ADMIN_PASSWORD", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/gramps-web/probe.json b/audits/persistence-sweep-2026-08-02/evidence/gramps-web/probe.json new file mode 100644 index 0000000..ed8099b --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/gramps-web/probe.json @@ -0,0 +1,266 @@ +{ + "probe": { + "app": "gramps-web", + "containers": [ + { + "diff_added": 127, + "diff_benign_db_touches": [], + "diff_data_dirs": [ + { + "added": [ + "users.sqlite" + ], + "changed_count": 0, + "db_signature": true, + "dir": "/app/users", + "files": [ + "users.sqlite" + ] + } + ], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "usr", + "app", + "root" + ] + }, + { + "added": [ + "data" + ], + "changed_count": 4, + "db_signature": false, + "dir": "/app", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/cache", + "files": [ + "request_cache" + ] + }, + { + "added": [ + "2029240f6d1128be89ddc32729463129" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/cache/request_cache", + "files": [ + "2029240f6d1128be89ddc32729463129" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/src", + "files": [ + "alembic_users", + "gramps_webapi" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/src/alembic_users", + "files": [ + "versions" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/src/gramps_webapi", + "files": [ + "api", + "auth", + "util" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/src/gramps_webapi/api", + "files": [ + "resources", + "search" + ] + }, + { + "added": [ + "2029240f6d1128be89ddc32729463129" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/thumbnail_cache", + "files": [ + "2029240f6d1128be89ddc32729463129" + ] + }, + { + "added": [ + ".gramps" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/root", + "files": [ + ".gramps" + ] + }, + { + "added": [ + "grampsdb" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gramps", + "files": [ + "grampsdb" + ] + }, + { + "added": [ + "61411b9e-fed7-485a-ab76-6e8ea7326c3b" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gramps/grampsdb", + "files": [ + "61411b9e-fed7-485a-ab76-6e8ea7326c3b" + ] + }, + { + "added": [ + "name.txt", + "database.txt" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gramps/grampsdb/61411b9e-fed7-485a-ab76-6e8ea7326c3b", + "files": [ + "name.txt", + "database.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps", + "files": [ + "gramps60" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps/gramps60", + "files": [ + "plugins" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps/gramps60/plugins", + "files": [ + "FilterRules" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "lib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 153, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/gramps-project/grampsweb:v25.6.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_data/_data", + "target": "/app/data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_media", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_media/_data", + "target": "/app/media", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_tmp", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_tmp/_data", + "target": "/tmp", + "writable_by_app": "yes" + } + ], + "name": "gramps-web", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "gramps_data", + "gramps_media", + "gramps_tmp" + ], + "env_keys": [ + "DOMAIN", + "GRAMPSWEB_SECRET_KEY", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "gramps-web: DATA in the writable layer at /app/users (db_signature=True, e.g. ['users.sqlite'])", + "NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/app', '/app/cache/request_cache', '/app/thumbnail_cache', '/root']. Needs adjudication.", + "gramps-web: this container's mounts are all empty while it created entries in ['/app', '/app/cache/request_cache', '/app/thumbnail_cache'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "gramps-web: declared volume /app/data is EMPTY", + "gramps-web: declared volume /app/media is EMPTY" + ], + "verdict": "BROKEN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/home-assistant/probe.json b/audits/persistence-sweep-2026-08-02/evidence/home-assistant/probe.json new file mode 100644 index 0000000..9e0017f --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/home-assistant/probe.json @@ -0,0 +1,114 @@ +{ + "probe": { + "app": "home-assistant", + "containers": [ + { + "diff_added": 2267, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "run", + "root", + "usr" + ] + }, + { + "added": [ + ".config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "local" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local", + "files": [ + "lib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 2561, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/home-assistant/home-assistant:2026.7.2", + "mounts": [ + { + "class": "named-declared", + "files": 21, + "name": "volgate-homeassistant_homeassistant_config", + "sample": [ + "home-assistant.log", + "home-assistant_v2.db-wal", + "configuration.yaml", + "home-assistant.log.fault", + ".HA_VERSION", + "home-assistant.log.1", + "home-assistant_v2.db", + "home-assistant_v2.db-shm", + ".ha_run.lock", + ".storage/core.config_entries", + ".storage/core.restore_state", + ".storage/auth", + ".storage/http.auth", + ".storage/assist_pipeline.pipelines", + ".storage/core.device_registry", + ".storage/homeassistant.exposed_entities", + ".storage/core.uuid", + ".storage/core.entity_registry", + "blueprints/script/homeassistant/confirmable_notification.yaml", + "blueprints/automation/homeassistant/notify_leaving_zone.yaml", + "blueprints/automation/homeassistant/motion_light.yaml" + ], + "source": "/var/lib/docker/volumes/volgate-homeassistant_homeassistant_config/_data", + "target": "/config", + "writable_by_app": "yes" + } + ], + "name": "home-assistant", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "homeassistant_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/homebox/probe.json b/audits/persistence-sweep-2026-08-02/evidence/homebox/probe.json new file mode 100644 index 0000000..561f3ad --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/homebox/probe.json @@ -0,0 +1,68 @@ +{ + "probe": { + "app": "homebox", + "containers": [ + { + "diff_added": 1, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/", + "files": [ + "data" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 1, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/sysadminsmedia/homebox:0.26.2", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-homebox_homebox_data", + "sample": [ + "homebox.db-shm", + "homebox.db", + "homebox.db-wal" + ], + "source": "/var/lib/docker/volumes/volgate-homebox_homebox_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "homebox", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "homebox_data" + ], + "env_keys": [ + "DOMAIN", + "HBOX_AUTH_API_KEY_PEPPER", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/homepage/probe.json b/audits/persistence-sweep-2026-08-02/evidence/homepage/probe.json new file mode 100644 index 0000000..badc915 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/homepage/probe.json @@ -0,0 +1,86 @@ +{ + "probe": { + "app": "homepage", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "run", + "app" + ] + }, + { + "added": [ + "config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app", + "files": [ + "config" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/gethomepage/homepage:v1.13.2", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-homepage_homepage_config", + "sample": [ + "kubernetes.yaml", + "settings.yaml", + "logs/homepage.log" + ], + "source": "/var/lib/docker/volumes/volgate-homepage_homepage_config/_data", + "target": "/app/config", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": -1, + "name": null, + "sample": [], + "source": "/var/run/docker.sock", + "target": "/var/run/docker.sock", + "writable_by_app": "no-source" + } + ], + "name": "homepage", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "homepage_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/immich/probe.json b/audits/persistence-sweep-2026-08-02/evidence/immich/probe.json new file mode 100644 index 0000000..7606e2f --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/immich/probe.json @@ -0,0 +1,330 @@ +{ + "probe": { + "app": "immich", + "containers": [ + { + "diff_added": 6, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "cache" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "cache" + ] + }, + { + "added": [ + ".config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config" + ] + }, + { + "added": [ + "matplotlib" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config", + "files": [ + "matplotlib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 7, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-immich_immich_ml_cache", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-immich_immich_ml_cache/_data", + "target": "/cache", + "writable_by_app": "yes" + } + ], + "name": "immich-machine-learning", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 3, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "run" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "postgresql" + ] + }, + { + "added": [ + "postgresql.conf" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/etc/postgresql", + "files": [ + "postgresql.conf" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 7, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "mounts": [ + { + "class": "named-declared", + "files": 1833, + "name": "volgate-immich_immich_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/13406", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/13414", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106" + ], + "source": "/var/lib/docker/volumes/volgate-immich_immich_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "immich-postgres", + "restarts": null, + "status": "running", + "uid": 999 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-immich_immich_redis_data", + "sample": [ + "appendonlydir/appendonly.aof.1.base.rdb", + "appendonlydir/appendonly.aof.1.incr.aof", + "appendonlydir/appendonly.aof.manifest" + ], + "source": "/var/lib/docker/volumes/volgate-immich_immich_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "immich-redis", + "restarts": null, + "status": "running", + "uid": 999 + }, + { + "diff_added": 4, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data", + "external" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "data", + "external" + ] + }, + { + "added": [ + "photos" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/external", + "files": [ + "photos" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "src" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/src", + "files": [ + "app" + ] + }, + { + "added": [ + "upload" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/usr/src/app", + "files": [ + "upload" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 7, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/immich-app/immich-server:v3.0.3", + "mounts": [ + { + "class": "bind", + "files": 6, + "name": null, + "sample": [ + "library/.immich", + "upload/.immich", + "profile/.immich", + "encoded-video/.immich", + "thumbs/.immich", + "backups/.immich" + ], + "source": "/srv/felhom-gate/hdd/appdata/immich", + "target": "/usr/src/app/upload", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/photos", + "target": "/external/photos", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "8a33371900fd8f325c75733c4486ad225939fc62a80ae67ee333a598a34b665f", + "sample": [], + "source": "/var/lib/docker/volumes/8a33371900fd8f325c75733c4486ad225939fc62a80ae67ee333a598a34b665f/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "immich-server", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "immich_ml_cache", + "immich_postgres_data", + "immich_redis_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "immich-machine-learning: this container's mounts are all empty while it created entries in ['/', '/root', '/root/.config'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "immich-machine-learning: declared volume /cache is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/jellyfin/probe.json b/audits/persistence-sweep-2026-08-02/evidence/jellyfin/probe.json new file mode 100644 index 0000000..32be73d --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/jellyfin/probe.json @@ -0,0 +1,153 @@ +{ + "probe": { + "app": "jellyfin", + "containers": [ + { + "diff_added": 7, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "jellyfin", + "root" + ] + }, + { + "added": [ + "Dictionaries" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/jellyfin", + "files": [ + "Dictionaries" + ] + }, + { + "added": [ + ".aspnet" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".aspnet" + ] + }, + { + "added": [ + "DataProtection-Keys" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.aspnet", + "files": [ + "DataProtection-Keys" + ] + }, + { + "added": [ + "key-b1c34edb-e295-4345-a61a-db5f11d4fb0f.xml" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.aspnet/DataProtection-Keys", + "files": [ + "key-b1c34edb-e295-4345-a61a-db5f11d4fb0f.xml" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 10, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "jellyfin/jellyfin:10.11.11", + "mounts": [ + { + "class": "named-declared", + "files": 20, + "name": "volgate-jellyfin_jellyfin_config", + "sample": [ + ".jellyfin-data", + "root/.jellyfin-root", + "log/.jellyfin-log", + "log/log_20260802.log", + "config/logging.default.json", + "config/system.xml", + "config/encoding.xml", + "config/.jellyfin-config", + "config/database.xml", + "plugins/.jellyfin-plugin", + "plugins/configurations/Jellyfin.Plugin.Tmdb.xml", + "plugins/configurations/Jellyfin.Plugin.MusicBrainz.xml", + "data/device.txt", + "data/jellyfin.db-wal", + "data/.jellyfin-data", + "data/jellyfin.db", + "data/jellyfin.db-shm", + "data/ScheduledTasks/7d8088c1-0902-f1bf-4072-ded42437bcfb.js", + "data/ScheduledTasks/3a025083-141d-3c17-dd96-d5f9b951287b.js", + "data/ScheduledTasks/f9b057c0-54e9-e6da-ee4a-88ffd146a403.js" + ], + "source": "/var/lib/docker/volumes/volgate-jellyfin_jellyfin_config/_data", + "target": "/config", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 2, + "name": "volgate-jellyfin_jellyfin_cache", + "sample": [ + ".jellyfin-cache", + "transcodes/.jellyfin-transcode" + ], + "source": "/var/lib/docker/volumes/volgate-jellyfin_jellyfin_cache/_data", + "target": "/cache", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 3, + "name": null, + "sample": [ + "books/metadata.db", + "books/metadata.db-shm", + "books/metadata.db-wal" + ], + "source": "/srv/felhom-gate/hdd/userdata/media", + "target": "/media", + "writable_by_app": "yes" + } + ], + "name": "jellyfin", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "jellyfin_cache", + "jellyfin_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/kimai/probe.json b/audits/persistence-sweep-2026-08-02/evidence/kimai/probe.json new file mode 100644 index 0000000..c1a5665 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/kimai/probe.json @@ -0,0 +1,259 @@ +{ + "probe": { + "app": "kimai", + "containers": [ + { + "diff_added": 1, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "usr", + "opt", + "run" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/opt", + "files": [ + "kimai" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/opt/kimai", + "files": [ + "config" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/opt/kimai/config", + "files": [ + "packages" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/opt/kimai/config/packages", + "files": [ + "monolog.yaml" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "local" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local", + "files": [ + "etc" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local/etc", + "files": [ + "php" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local/etc/php", + "files": [ + "php.ini" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 13, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "kimai/kimai2:apache-2.57.0", + "mounts": [ + { + "class": "named-declared", + "files": 2547, + "name": "volgate-kimai_kimai_var", + "sample": [ + "installed", + "cache/.gitkeep", + "cache/pro_/url_generating_routes.php", + "cache/pro_/url_matching_routes.php", + "cache/pro_/url_matching_routes.php.meta", + "cache/pro_/url_generating_routes.php.meta", + "cache/pro_/Symfony/Config/TwigExtraConfig.php", + "cache/pro_/Symfony/Config/DoctrineMigrationsConfig.php", + "cache/pro_/Symfony/Config/MonologConfig.php", + "cache/pro_/Symfony/Config/NelmioCorsConfig.php", + "cache/pro_/Symfony/Config/WebpackEncoreConfig.php", + "cache/pro_/Symfony/Config/NelmioApiDocConfig.php", + "cache/pro_/Symfony/Config/TablerConfig.php", + "cache/pro_/Symfony/Config/FrameworkConfig.php", + "cache/pro_/Symfony/Config/SchebTwoFactorConfig.php", + "cache/pro_/Symfony/Config/TwigConfig.php", + "cache/pro_/Symfony/Config/SecurityConfig.php", + "cache/pro_/Symfony/Config/DoctrineConfig.php", + "cache/pro_/Symfony/Config/FosRestConfig.php", + "cache/pro_/Symfony/Config/JmsSerializerConfig.php", + "cache/pro_/Symfony/Config/Tabler/RoutesConfig.php", + "cache/pro_/Symfony/Config/Tabler/KnpMenuConfig.php", + "cache/pro_/Symfony/Config/Tabler/OptionsConfig.php", + "cache/pro_/Symfony/Config/SchebTwoFactor/TotpConfig.php", + "cache/pro_/Symfony/Config/SchebTwoFactor/BackupCodesConfig.php", + "cache/pro_/Symfony/Config/DoctrineMigrations/StorageConfig.php", + "cache/pro_/Symfony/Config/DoctrineMigrations/Storage/TableStorageConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/VisitorsConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/InstancesConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/HandlersConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/PropertyNamingConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/MetadataConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/DefaultContextConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/ObjectConstructorsConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/SubscribersConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/ExpressionEvaluatorConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/ObjectConstructors/DoctrineConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/Subscribers/DoctrineProxyConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/InstancesConfig/VisitorsConfig.php", + "cache/pro_/Symfony/Config/JmsSerializer/InstancesConfig/HandlersConfig.php" + ], + "source": "/var/lib/docker/volumes/volgate-kimai_kimai_var/_data", + "target": "/opt/kimai/var", + "writable_by_app": "yes" + } + ], + "name": "kimai", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "mariadb:11.6", + "mounts": [ + { + "class": "named-declared", + "files": 279, + "name": "volgate-kimai_kimai_db_data", + "sample": [ + "undo002", + "tc.log", + "mariadb_upgrade_info", + "ibtmp1", + "aria_log.00000001", + "multi-master.info", + "ddl_recovery.log", + "ib_logfile0", + "undo003", + "ibdata1", + ".my-healthcheck.cnf", + "aria_log_control", + "undo001", + "ib_buffer_pool", + "sys/x@0024wait_classes_global_by_latency.frm", + "sys/processlist.frm", + "sys/session.frm", + "sys/memory_by_host_by_current_bytes.frm", + "sys/user_summary.frm", + "sys/waits_global_by_latency.frm", + "sys/x@0024statement_analysis.frm", + "sys/x@0024host_summary.frm", + "sys/schema_unused_indexes.frm", + "sys/user_summary_by_file_io_type.frm", + "sys/memory_by_thread_by_current_bytes.frm", + "sys/x@0024statements_with_full_table_scans.frm", + "sys/wait_classes_global_by_avg_latency.frm", + "sys/x@0024schema_table_statistics.frm", + "sys/x@0024statements_with_temp_tables.frm", + "sys/schema_auto_increment_columns.frm", + "sys/x@0024ps_schema_table_statistics_io.frm", + "sys/x@0024memory_by_host_by_current_bytes.frm", + "sys/statements_with_runtimes_in_95th_percentile.frm", + "sys/x@0024statements_with_errors_or_warnings.frm", + "sys/waits_by_user_by_latency.frm", + "sys/x@0024io_global_by_file_by_bytes.frm", + "sys/innodb_buffer_stats_by_table.frm", + "sys/user_summary_by_statement_type.frm", + "sys/statements_with_errors_or_warnings.frm", + "sys/sys_config.MAD" + ], + "source": "/var/lib/docker/volumes/volgate-kimai_kimai_db_data/_data", + "target": "/var/lib/mysql", + "writable_by_app": "yes" + } + ], + "name": "kimai-db", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "kimai_db_data", + "kimai_var" + ], + "env_keys": [ + "ADMIN_EMAIL", + "ADMIN_PASSWORD", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/komga/probe.json b/audits/persistence-sweep-2026-08-02/evidence/komga/probe.json new file mode 100644 index 0000000..0fd405a --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/komga/probe.json @@ -0,0 +1,86 @@ +{ + "probe": { + "app": "komga", + "containers": [ + { + "diff_added": 12, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data", + "config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/", + "files": [ + "data", + "config" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 13, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "gotson/komga:1.25.0", + "mounts": [ + { + "class": "named-declared", + "files": 11, + "name": "volgate-komga_komga_config", + "sample": [ + "database.sqlite-shm", + "database.sqlite-wal", + "tasks.sqlite-shm", + "database.sqlite", + "tasks.sqlite-wal", + "tasks.sqlite", + "lucene/_0.cfe", + "lucene/_0.si", + "lucene/segments_1", + "lucene/_0.cfs", + "logs/komga.log" + ], + "source": "/var/lib/docker/volumes/volgate-komga_komga_config/_data", + "target": "/config", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/comics", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "komga", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "komga_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/mealie/probe.json b/audits/persistence-sweep-2026-08-02/evidence/mealie/probe.json new file mode 100644 index 0000000..c35994a --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/mealie/probe.json @@ -0,0 +1,89 @@ +{ + "probe": { + "app": "mealie", + "containers": [ + { + "diff_added": 1, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "app" + ] + }, + { + "added": [ + "data" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/app", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/etc", + "files": [ + "passwd-", + "group-" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 9, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "ghcr.io/mealie-recipes/mealie:v3.20.1", + "mounts": [ + { + "class": "named-declared", + "files": 5, + "name": "volgate-mealie_mealie_data", + "sample": [ + ".session_secret", + "mealie.db", + "mealie.log", + ".secret", + "users/6b1f2b18-68e0-468f-9db1-0431546240bd/profile.webp" + ], + "source": "/var/lib/docker/volumes/volgate-mealie_mealie_data/_data", + "target": "/app/data", + "writable_by_app": "yes" + } + ], + "name": "mealie", + "restarts": null, + "status": "running", + "uid": 1000 + } + ], + "declared_volumes": [ + "mealie_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/n8n/probe.json b/audits/persistence-sweep-2026-08-02/evidence/n8n/probe.json new file mode 100644 index 0000000..8a73de7 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/n8n/probe.json @@ -0,0 +1,79 @@ +{ + "probe": { + "app": "n8n", + "containers": [ + { + "diff_added": 1094, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "home" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/home", + "files": [ + "node" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 1097, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "n8nio/n8n:2.31.3", + "mounts": [ + { + "class": "named-declared", + "files": 7, + "name": "volgate-n8n_n8n_data", + "sample": [ + "database.sqlite-shm", + "database.sqlite-wal", + "config", + "database.sqlite", + "n8nEventLog.log", + "crash.journal", + "nodes/package.json" + ], + "source": "/var/lib/docker/volumes/volgate-n8n_n8n_data/_data", + "target": "/home/node/.n8n", + "writable_by_app": "yes" + } + ], + "name": "n8n", + "restarts": null, + "status": "running", + "uid": 1000 + } + ], + "declared_volumes": [ + "n8n_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "N8N_ENCRYPTION_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/navidrome/probe.json b/audits/persistence-sweep-2026-08-02/evidence/navidrome/probe.json new file mode 100644 index 0000000..31a7f1a --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/navidrome/probe.json @@ -0,0 +1,81 @@ +{ + "probe": { + "app": "navidrome", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data", + "music" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/", + "files": [ + "data", + "music" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 2, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "deluan/navidrome:0.63.2", + "mounts": [ + { + "class": "named-declared", + "files": 6, + "name": "volgate-navidrome_navidrome_data", + "sample": [ + "navidrome.db-wal", + "navidrome.db", + "navidrome.db-shm", + "cache/transcoding/.nd-migrated", + "cache/images/.nd-migrated", + "cache/backgrounds/.nd-migrated" + ], + "source": "/var/lib/docker/volumes/volgate-navidrome_navidrome_data/_data", + "target": "/data", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/music", + "target": "/music", + "writable_by_app": "yes" + } + ], + "name": "navidrome", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "navidrome_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/nextcloud/probe.json b/audits/persistence-sweep-2026-08-02/evidence/nextcloud/probe.json new file mode 100644 index 0000000..88ac3c6 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/nextcloud/probe.json @@ -0,0 +1,317 @@ +{ + "probe": { + "app": "nextcloud", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "run", + "usr" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "local" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local", + "files": [ + "etc" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local/etc", + "files": [ + "php" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local/etc/php", + "files": [ + "conf.d" + ] + }, + { + "added": [ + "redis-session.ini" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/usr/local/etc/php/conf.d", + "files": [ + "redis-session.ini" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 9, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "nextcloud:34.0.1-apache", + "mounts": [ + { + "class": "bind", + "files": 69, + "name": null, + "sample": [ + ".htaccess", + ".ncdata", + "nextcloud.log", + "index.html", + "admin/files/Nextcloud intro.mp4", + "admin/files/Templates credits.md", + "admin/files/Nextcloud Manual.pdf", + "admin/files/Nextcloud.png", + "admin/files/Reasons to use Nextcloud.pdf", + "admin/files/Readme.md", + "admin/files/Photos/Frog.jpg", + "admin/files/Photos/Vineyard.jpg", + "admin/files/Photos/Library.jpg", + "admin/files/Photos/Nextcloud community.jpg", + "admin/files/Photos/Birdie.jpg", + "admin/files/Photos/Toucan.jpg", + "admin/files/Photos/Steps.jpg", + "admin/files/Photos/Readme.md", + "admin/files/Photos/Gorilla.jpg", + "admin/files/Documents/Welcome to Nextcloud Hub.docx", + "admin/files/Documents/Nextcloud flyer.pdf", + "admin/files/Documents/Example.md", + "admin/files/Documents/Readme.md", + "admin/files/Templates/Kanban board.whiteboard", + "admin/files/Templates/Meeting agenda.whiteboard", + "admin/files/Templates/Org chart.odg", + "admin/files/Templates/Invoice.ods", + "admin/files/Templates/Resume.odt", + "admin/files/Templates/Certificate.odt", + "admin/files/Templates/Brainstorming.whiteboard", + "admin/files/Templates/Letter.odt", + "admin/files/Templates/Gotong royong.odp", + "admin/files/Templates/Business model canvas.odg", + "admin/files/Templates/Sticky notes.whiteboard", + "admin/files/Templates/Security.odp", + "admin/files/Templates/Onboarding.odp", + "admin/files/Templates/Mind map.whiteboard", + "admin/files/Templates/Elegant.odp", + "admin/files/Templates/Expense report.ods", + "admin/files/Templates/Simple.odp" + ], + "source": "/srv/felhom-gate/hdd/appdata/nextcloud", + "target": "/var/www/html/data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 4001, + "name": "volgate-nextcloud_nextcloud_html", + "sample": [ + "status.php", + ".htaccess", + "AUTHORS", + "public.php", + "occ", + "index.php", + "composer.json", + "version.php", + "package-lock.json", + "nextcloud-init-sync.lock", + ".user.ini", + "remote.php", + "cron.php", + "console.php", + "COPYING", + "REUSE.toml", + "index.html", + "package.json", + "robots.txt", + "composer.lock", + "lib/versioncheck.php", + "lib/base.php", + "lib/l10n/he.json", + "lib/l10n/bs.js", + "lib/l10n/es_PA.js", + "lib/l10n/hsb.json", + "lib/l10n/fo.json", + "lib/l10n/mk.json", + "lib/l10n/ru.js", + "lib/l10n/th.json", + "lib/l10n/es_GT.json", + "lib/l10n/eo.js", + "lib/l10n/pt_PT.json", + "lib/l10n/bn_BD.js", + "lib/l10n/ast.json", + "lib/l10n/kab.json", + "lib/l10n/fo.js", + "lib/l10n/km.js", + "lib/l10n/mn.js", + "lib/l10n/ig.json" + ], + "source": "/var/lib/docker/volumes/volgate-nextcloud_nextcloud_html/_data", + "target": "/var/www/html", + "writable_by_app": "yes" + } + ], + "name": "nextcloud", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "mariadb:11.6", + "mounts": [ + { + "class": "named-declared", + "files": 311, + "name": "volgate-nextcloud_nextcloud_db_data", + "sample": [ + "undo002", + "tc.log", + "mariadb_upgrade_info", + "ibtmp1", + "aria_log.00000001", + "multi-master.info", + "ddl_recovery.log", + "ib_logfile0", + "undo003", + "ibdata1", + ".my-healthcheck.cnf", + "aria_log_control", + "undo001", + "ib_buffer_pool", + "sys/x@0024wait_classes_global_by_latency.frm", + "sys/processlist.frm", + "sys/session.frm", + "sys/memory_by_host_by_current_bytes.frm", + "sys/user_summary.frm", + "sys/waits_global_by_latency.frm", + "sys/x@0024statement_analysis.frm", + "sys/x@0024host_summary.frm", + "sys/schema_unused_indexes.frm", + "sys/user_summary_by_file_io_type.frm", + "sys/memory_by_thread_by_current_bytes.frm", + "sys/x@0024statements_with_full_table_scans.frm", + "sys/wait_classes_global_by_avg_latency.frm", + "sys/x@0024schema_table_statistics.frm", + "sys/x@0024statements_with_temp_tables.frm", + "sys/schema_auto_increment_columns.frm", + "sys/x@0024ps_schema_table_statistics_io.frm", + "sys/x@0024memory_by_host_by_current_bytes.frm", + "sys/statements_with_runtimes_in_95th_percentile.frm", + "sys/x@0024statements_with_errors_or_warnings.frm", + "sys/waits_by_user_by_latency.frm", + "sys/x@0024io_global_by_file_by_bytes.frm", + "sys/innodb_buffer_stats_by_table.frm", + "sys/user_summary_by_statement_type.frm", + "sys/statements_with_errors_or_warnings.frm", + "sys/sys_config.MAD" + ], + "source": "/var/lib/docker/volumes/volgate-nextcloud_nextcloud_db_data/_data", + "target": "/var/lib/mysql", + "writable_by_app": "yes" + } + ], + "name": "nextcloud-db", + "restarts": null, + "status": "running", + "uid": 999 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-nextcloud_nextcloud_redis_data", + "sample": [ + "appendonlydir/appendonly.aof.1.base.rdb", + "appendonlydir/appendonly.aof.1.incr.aof", + "appendonlydir/appendonly.aof.manifest" + ], + "source": "/var/lib/docker/volumes/volgate-nextcloud_nextcloud_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "nextcloud-redis", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "nextcloud_db_data", + "nextcloud_html", + "nextcloud_redis_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "MYSQL_ROOT_PASSWORD", + "NEXTCLOUD_ADMIN_PASSWORD", + "NEXTCLOUD_ADMIN_USER", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/onlyoffice/probe.json b/audits/persistence-sweep-2026-08-02/evidence/onlyoffice/probe.json new file mode 100644 index 0000000..e3ae15e --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/onlyoffice/probe.json @@ -0,0 +1,6644 @@ +{ + "probe": { + "app": "onlyoffice", + "containers": [ + { + "diff_added": 2558, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "run", + "usr", + "var", + "root" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/etc", + "files": [ + "logrotate.conf", + "nginx", + "onlyoffice" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/etc/onlyoffice", + "files": [ + "documentserver", + "documentserver-example" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/etc/onlyoffice/documentserver", + "files": [ + "local.json", + "log4js", + "nginx" + ] + }, + { + "added": [], + "changed_count": 9, + "db_signature": false, + "dir": "/etc/onlyoffice/documentserver-example", + "files": [ + "development-mac.json", + "development-windows.json", + "local.json", + "nginx", + "production-linux.json", + "production-windows.json", + "data.json", + "default.json", + "development-linux.json" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/onlyoffice/documentserver-example/nginx", + "files": [ + "includes" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/onlyoffice/documentserver-example/nginx/includes", + "files": [ + "ds-example.conf" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/etc/onlyoffice/documentserver/log4js", + "files": [ + "development.json", + "production.json" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/etc/onlyoffice/documentserver/nginx", + "files": [ + "includes", + "ds-ssl.conf.tmpl", + "ds.conf", + "ds.conf.tmpl" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/etc/onlyoffice/documentserver/nginx/includes", + "files": [ + "http-common.conf", + "ds-common.conf", + "ds-docservice.conf", + "ds-letsencrypt.conf", + "ds-mime.types.conf" + ] + }, + { + "added": [ + "ONLYOFFICE" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + "ONLYOFFICE" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "share" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/var", + "files": [ + "www", + "lib", + "log" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/lib", + "files": [ + "nginx" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www", + "files": [ + "onlyoffice" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/var/www/onlyoffice", + "files": [ + "documentserver", + "documentserver-example" + ] + }, + { + "added": [], + "changed_count": 5, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver", + "files": [ + "sdkjs", + "sdkjs-plugins", + "web-apps", + "fonts", + "server" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver-example", + "files": [ + "welcome" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver-example/welcome", + "files": [ + "docker.html", + "example-disabled.html" + ] + }, + { + "added": [ + "205", + "096", + "039", + "157", + "018", + "026", + "051", + "072", + "077", + "081", + "097", + "112", + "006", + "156", + "169", + "176", + "193", + "203", + "012", + "025", + "048", + "066", + "080", + "088", + "108", + "133", + "055", + "067", + "079", + "131", + "171", + "031", + "071", + "090", + "149", + "152", + "154", + "075", + "084", + "148" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/fonts", + "files": [ + "205", + "096", + "039", + "157", + "018", + "026", + "051", + "072", + "077", + "081", + "097", + "112", + "006", + "156", + "169", + "176", + "193", + "203", + "012", + "025", + "048", + "066", + "080", + "088", + "108", + "133", + "055", + "067", + "079", + "131", + "171", + "031", + "071", + "090", + "149", + "152", + "154", + "075", + "084", + "148" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs", + "files": [ + "slide", + "common" + ] + }, + { + "added": [ + "{07FD8DFA-DFE0-4089-AL24-0730933CC80A}", + "{38E022EA-AD92-45FC-B22B-49DF39746DB4}", + "{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}", + "{D71C2EF0-F15B-47C7-80E9-86D671F9C595}", + "{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}", + "{440EBF13-9B19-4BD8-8621-05200E58140B}", + "{7327FC95-16DA-41D9-9AF2-0E7F449F6800}", + "{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}", + "{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}", + "{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}", + "{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins", + "files": [ + "{07FD8DFA-DFE0-4089-AL24-0730933CC80A}", + "{38E022EA-AD92-45FC-B22B-49DF39746DB4}", + "{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}", + "{D71C2EF0-F15B-47C7-80E9-86D671F9C595}", + "{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}", + "{440EBF13-9B19-4BD8-8621-05200E58140B}", + "{7327FC95-16DA-41D9-9AF2-0E7F449F6800}", + "{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}", + "{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}", + "{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}", + "{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}" + ] + }, + { + "added": [ + "licenses", + "resources", + "translations", + "CHANGELOG.md", + "scripts", + "style", + "vendor", + "3rd-Party.txt", + "README.md", + "config.json", + "index.html", + "index_about.html" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}", + "files": [ + "licenses", + "resources", + "translations", + "CHANGELOG.md", + "scripts", + "style", + "vendor", + "3rd-Party.txt", + "README.md", + "config.json", + "index.html", + "index_about.html" + ] + }, + { + "added": [ + "Tui.license", + "jQuery.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/licenses", + "files": [ + "Tui.license", + "jQuery.license" + ] + }, + { + "added": [ + "dark", + "img", + "light", + "store" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources", + "files": [ + "dark", + "img", + "light", + "store" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/dark", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "bg.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/img", + "files": [ + "bg.png" + ] + }, + { + "added": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/light", + "files": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ] + }, + { + "added": [ + "photoeditor.js", + "white-theme.js", + "black-theme.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/scripts", + "files": [ + "photoeditor.js", + "white-theme.js", + "black-theme.js" + ] + }, + { + "added": [ + "black-theme.css", + "white-theme.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/style", + "files": [ + "black-theme.css", + "white-theme.css" + ] + }, + { + "added": [ + "sq-AL.json", + "fr-FR.json", + "it-IT.json", + "langs.json", + "pt-BR.json", + "ru-RU.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "zh-ZH.json", + "cs-CS.json", + "de-DE.json", + "es-ES.json", + "ja-JA.json", + "si-SI.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/translations", + "files": [ + "sq-AL.json", + "fr-FR.json", + "it-IT.json", + "langs.json", + "pt-BR.json", + "ru-RU.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "zh-ZH.json", + "cs-CS.json", + "de-DE.json", + "es-ES.json", + "ja-JA.json", + "si-SI.json" + ] + }, + { + "added": [ + "jquery", + "tui" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/vendor", + "files": [ + "jquery", + "tui" + ] + }, + { + "added": [ + "jquery-3.7.1.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/vendor/jquery", + "files": [ + "jquery-3.7.1.min.js" + ] + }, + { + "added": [ + "svg", + "tui-image-editor.min.css", + "tui-image-editor.min.js", + "vendor" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/vendor/tui", + "files": [ + "svg", + "tui-image-editor.min.css", + "tui-image-editor.min.js", + "vendor" + ] + }, + { + "added": [ + "dark", + "light" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/vendor/tui/svg", + "files": [ + "dark", + "light" + ] + }, + { + "added": [ + "icon-c.svg", + "icon-d.svg", + "icon-a.svg", + "icon-b.svg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/vendor/tui/svg/dark", + "files": [ + "icon-c.svg", + "icon-d.svg", + "icon-a.svg", + "icon-b.svg" + ] + }, + { + "added": [ + "icon-a.svg", + "icon-b.svg", + "icon-c.svg", + "icon-d.svg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/vendor/tui/svg/light", + "files": [ + "icon-a.svg", + "icon-b.svg", + "icon-c.svg", + "icon-d.svg" + ] + }, + { + "added": [ + "tui-color-picker.min.css", + "tui-color-picker.min.js", + "fabric.min.js", + "tui-code-snippet.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/vendor/tui/vendor", + "files": [ + "tui-color-picker.min.css", + "tui-color-picker.min.js", + "fabric.min.js", + "tui-code-snippet.min.js" + ] + }, + { + "added": [ + "config.json", + "desktop-player.html", + "index.html", + "resources", + "scripts", + "translations", + "CHANGELOG.md", + "README.md" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}", + "files": [ + "config.json", + "desktop-player.html", + "index.html", + "resources", + "scripts", + "translations", + "CHANGELOG.md", + "README.md" + ] + }, + { + "added": [ + "img", + "store", + "styles" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources", + "files": [ + "img", + "store", + "styles" + ] + }, + { + "added": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/img", + "files": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ] + }, + { + "added": [ + "style.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/styles", + "files": [ + "style.css" + ] + }, + { + "added": [ + "player.js", + "utils.js", + "youtube.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/scripts", + "files": [ + "player.js", + "utils.js", + "youtube.js" + ] + }, + { + "added": [ + "es-ES.json", + "langs.json", + "pt-BR.json", + "ru-RU.json", + "zh-ZH.json", + "fr-FR.json", + "it-IT.json", + "ja-JA.json", + "si-SI.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "cs-CS.json", + "de-DE.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/translations", + "files": [ + "es-ES.json", + "langs.json", + "pt-BR.json", + "ru-RU.json", + "zh-ZH.json", + "fr-FR.json", + "it-IT.json", + "ja-JA.json", + "si-SI.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "cs-CS.json", + "de-DE.json" + ] + }, + { + "added": [ + "vendor", + "CHANGELOG.md", + "index.html", + "licenses", + "resources", + "translations", + "3rd-Party.txt", + "README.md", + "config.json", + "index_about.html", + "scripts" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}", + "files": [ + "vendor", + "CHANGELOG.md", + "index.html", + "licenses", + "resources", + "translations", + "3rd-Party.txt", + "README.md", + "config.json", + "index_about.html", + "scripts" + ] + }, + { + "added": [ + "Select2.license", + "Tesseract.license", + "jQuery.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/licenses", + "files": [ + "Select2.license", + "Tesseract.license", + "jQuery.license" + ] + }, + { + "added": [ + "dark", + "light", + "store" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources", + "files": [ + "dark", + "light", + "store" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/dark", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/light", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "ocr.js", + "ocr_ie.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/scripts", + "files": [ + "ocr.js", + "ocr_ie.js" + ] + }, + { + "added": [ + "langs.json", + "pt-BR.json", + "si-SI.json", + "zh-ZH.json", + "cs-CS.json", + "fr-FR.json", + "ru-RU.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "de-DE.json", + "es-ES.json", + "it-IT.json", + "ja-JA.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/translations", + "files": [ + "langs.json", + "pt-BR.json", + "si-SI.json", + "zh-ZH.json", + "cs-CS.json", + "fr-FR.json", + "ru-RU.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "de-DE.json", + "es-ES.json", + "it-IT.json", + "ja-JA.json" + ] + }, + { + "added": [ + "jquery", + "select2-4.0.13" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/vendor", + "files": [ + "jquery", + "select2-4.0.13" + ] + }, + { + "added": [ + "jquery-3.7.1.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/vendor/jquery", + "files": [ + "jquery-3.7.1.min.js" + ] + }, + { + "added": [ + "README.md", + "css", + "js", + "LICENSE.md" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/vendor/select2-4.0.13", + "files": [ + "README.md", + "css", + "js", + "LICENSE.md" + ] + }, + { + "added": [ + "select2.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/vendor/select2-4.0.13/css", + "files": [ + "select2.css" + ] + }, + { + "added": [ + "select2.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/vendor/select2-4.0.13/js", + "files": [ + "select2.js" + ] + }, + { + "added": [ + "CHANGELOG.md", + "config.json", + "index_widget.html", + "licenses", + "translations", + "3rd-Party.txt", + "README.md", + "index.html", + "resources", + "scripts", + "vendor" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}", + "files": [ + "CHANGELOG.md", + "config.json", + "index_widget.html", + "licenses", + "translations", + "3rd-Party.txt", + "README.md", + "index.html", + "resources", + "scripts", + "vendor" + ] + }, + { + "added": [ + "Select2.license", + "jQuery.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/licenses", + "files": [ + "Select2.license", + "jQuery.license" + ] + }, + { + "added": [ + "img", + "store" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources", + "files": [ + "img", + "store" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources/img", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "translate.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/scripts", + "files": [ + "translate.js" + ] + }, + { + "added": [ + "it-IT.json", + "ja-JA.json", + "ru-RU.json", + "si-SI.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "de-DE.json", + "fr-FR.json", + "langs.json", + "pt-BR.json", + "sr-Latn-RS.json", + "zh-ZH.json", + "cs-CZ.json", + "es-ES.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/translations", + "files": [ + "it-IT.json", + "ja-JA.json", + "ru-RU.json", + "si-SI.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "de-DE.json", + "fr-FR.json", + "langs.json", + "pt-BR.json", + "sr-Latn-RS.json", + "zh-ZH.json", + "cs-CZ.json", + "es-ES.json" + ] + }, + { + "added": [ + "select2-4.0.13", + "google" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/vendor", + "files": [ + "select2-4.0.13", + "google" + ] + }, + { + "added": [ + "widjet.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/vendor/google", + "files": [ + "widjet.js" + ] + }, + { + "added": [ + "css", + "js", + "LICENSE.md", + "README.md" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/vendor/select2-4.0.13", + "files": [ + "css", + "js", + "LICENSE.md", + "README.md" + ] + }, + { + "added": [ + "select2.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/vendor/select2-4.0.13/css", + "files": [ + "select2.css" + ] + }, + { + "added": [ + "select2.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/vendor/select2-4.0.13/js", + "files": [ + "select2.js" + ] + }, + { + "added": [ + "aiModelsList.html", + "customAssistant.html", + "customProviders.html", + "summarization.html", + "translations", + "translationsettings.html", + "vendor", + "CHANGELOG.md", + "hyperlink.html", + "index.html", + "resources", + "LICENSE.txt", + "aiModelEdit.html", + "annotationPopup.html", + "config.json", + "scripts", + "chat.html", + "components", + "helper.html", + "settings.html" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}", + "files": [ + "aiModelsList.html", + "customAssistant.html", + "customProviders.html", + "summarization.html", + "translations", + "translationsettings.html", + "vendor", + "CHANGELOG.md", + "hyperlink.html", + "index.html", + "resources", + "LICENSE.txt", + "aiModelEdit.html", + "annotationPopup.html", + "config.json", + "scripts", + "chat.html", + "components", + "helper.html", + "settings.html" + ] + }, + { + "added": [ + "ListView", + "Tooltip" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/components", + "files": [ + "ListView", + "Tooltip" + ] + }, + { + "added": [ + "script.js", + "style.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/components/ListView", + "files": [ + "script.js", + "style.css" + ] + }, + { + "added": [ + "script.js", + "style.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/components/Tooltip", + "files": [ + "script.js", + "style.css" + ] + }, + { + "added": [ + "dark", + "icons", + "light", + "store", + "styles" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources", + "files": [ + "dark", + "icons", + "light", + "store", + "styles" + ] + }, + { + "added": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon@1.25x.png", + "icon@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/dark", + "files": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon@1.25x.png", + "icon@1.5x.png" + ] + }, + { + "added": [ + "dark", + "light", + "theme-classic-light", + "theme-default" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons", + "files": [ + "dark", + "light", + "theme-classic-light", + "theme-default" + ] + }, + { + "added": [ + "ai-audio@1.25x.png", + "ask-ai@2x.png", + "btn-zoomup@1.25x.png", + "chevron-down@1.25x.png", + "close@1.75x.png", + "copy@1.75x.png", + "default@1.25x.png", + "info@1.75x.png", + "ai-moderations@1.75x.png", + "btn-ic-review@2x.png", + "btn-microphone@2x.png", + "btn-update@2x.png", + "text-analysis-ai@2x.png", + "translation.png", + "vision-ai@2x.png", + "btn-update.png", + "image-ai@1.5x.png", + "image-ai@1.75x.png", + "summarization@1.75x.png", + "text-to-image@1.25x.png", + "ai-audio@1.5x.png", + "ai-embeddings@1.5x.png", + "ai-images@1.5x.png", + "ask-ai.png", + "ask-ai@1.25x.png", + "btn-copy@1.25x.png", + "btn-edit@1.5x.png", + "btn-password@2x.png", + "ai-embeddings.png", + "btn-edit@1.75x.png", + "btn-menu-comments@2x.png", + "btn-replace.png", + "btn-replace@2x.png", + "btn-zoomup@1.5x.png", + "copy@1.25x.png", + "general-ai@2x.png", + "ai-moderations@1.5x.png", + "info.png", + "translation@2x.png", + "vision-ai.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/dark", + "files": [ + "ai-audio@1.25x.png", + "ask-ai@2x.png", + "btn-zoomup@1.25x.png", + "chevron-down@1.25x.png", + "close@1.75x.png", + "copy@1.75x.png", + "default@1.25x.png", + "info@1.75x.png", + "ai-moderations@1.75x.png", + "btn-ic-review@2x.png", + "btn-microphone@2x.png", + "btn-update@2x.png", + "text-analysis-ai@2x.png", + "translation.png", + "vision-ai@2x.png", + "btn-update.png", + "image-ai@1.5x.png", + "image-ai@1.75x.png", + "summarization@1.75x.png", + "text-to-image@1.25x.png", + "ai-audio@1.5x.png", + "ai-embeddings@1.5x.png", + "ai-images@1.5x.png", + "ask-ai.png", + "ask-ai@1.25x.png", + "btn-copy@1.25x.png", + "btn-edit@1.5x.png", + "btn-password@2x.png", + "ai-embeddings.png", + "btn-edit@1.75x.png", + "btn-menu-comments@2x.png", + "btn-replace.png", + "btn-replace@2x.png", + "btn-zoomup@1.5x.png", + "copy@1.25x.png", + "general-ai@2x.png", + "ai-moderations@1.5x.png", + "info.png", + "translation@2x.png", + "vision-ai.png" + ] + }, + { + "added": [ + "written-plugin.png", + "form-to-doc.svg", + "grammar@1.25x.png", + "grammar@2x.png", + "plugin-writer@2x.png", + "summarization@1.5x.png", + "summarization@1.75x.png", + "text-to-image@1.25x.png", + "text-to-image@1.5x.png", + "form-to-doc@1.25x.png", + "grammar.png", + "plugin-writer@1.25x.png", + "plugin-writer@1.75x.png", + "summarization@1.25x.png", + "translation@1.5x.png", + "written-plugin@1.25x.png", + "ask-ai.png", + "default@1.25.png", + "default@1.5x.png", + "default@1.75x.png", + "form-to-doc@2x.png", + "settings.png", + "settings@1.75x.png", + "text-to-image@2x.png", + "default.png", + "default@2x.png", + "form-to-doc@1.75x.png", + "grammar@1.5x.png", + "ocr.png", + "ocr@1.75x.png", + "translation.png", + "translation@2x.png", + "ask-ai@1.25x.png", + "ask-ai@1.75x.png", + "form-to-doc@1.5x.png", + "settings@1.5x.png", + "summarization.png", + "translation@1.25x.png", + "written-plugin@1.5x.png", + "ask-ai@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/dark/big", + "files": [ + "written-plugin.png", + "form-to-doc.svg", + "grammar@1.25x.png", + "grammar@2x.png", + "plugin-writer@2x.png", + "summarization@1.5x.png", + "summarization@1.75x.png", + "text-to-image@1.25x.png", + "text-to-image@1.5x.png", + "form-to-doc@1.25x.png", + "grammar.png", + "plugin-writer@1.25x.png", + "plugin-writer@1.75x.png", + "summarization@1.25x.png", + "translation@1.5x.png", + "written-plugin@1.25x.png", + "ask-ai.png", + "default@1.25.png", + "default@1.5x.png", + "default@1.75x.png", + "form-to-doc@2x.png", + "settings.png", + "settings@1.75x.png", + "text-to-image@2x.png", + "default.png", + "default@2x.png", + "form-to-doc@1.75x.png", + "grammar@1.5x.png", + "ocr.png", + "ocr@1.75x.png", + "translation.png", + "translation@2x.png", + "ask-ai@1.25x.png", + "ask-ai@1.75x.png", + "form-to-doc@1.5x.png", + "settings@1.5x.png", + "summarization.png", + "translation@1.25x.png", + "written-plugin@1.5x.png", + "ask-ai@1.5x.png" + ] + }, + { + "added": [ + "ai-embeddings.png", + "ai-images@1.25x.png", + "btn-microphone@1.25x.png", + "btn-remove@1.5x.png", + "translation@1.75x.png", + "btn-edit@1.5x.png", + "btn-update@2x.png", + "info@1.25x.png", + "ai-embeddings@1.25x.png", + "ai-moderations@2x.png", + "btn-microphone.png", + "close@1.5x.png", + "info@1.5x.png", + "vision-ai@1.5x.png", + "ai-realtime@1.5x.png", + "close.png", + "image-ai@1.75x.png", + "text-analysis-ai@1.25x.png", + "ai-moderations@1.25x.png", + "ai-moderations@1.5x.png", + "btn-demote@1.75x.png", + "btn-password@1.75x.png", + "btn-remove@2x.png", + "btn-zoomup@1.75x.png", + "chevron-down@1.5x.png", + "grammar@1.75x.png", + "ai-embeddings@2x.png", + "btn-menu-comments@1.25x.png", + "btn-zoomup.png", + "btn-zoomup@1.5x.png", + "close@1.25x.png", + "default@1.75x.png", + "image-ai@1.5x.png", + "translation@1.5x.png", + "ai-code@1.75x.png", + "btn-edit.png", + "btn-microphone.svg", + "btn-microphone@1.5x.png", + "btn-microphone@1.75x.png", + "btn-password-hide@1.25x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/light", + "files": [ + "ai-embeddings.png", + "ai-images@1.25x.png", + "btn-microphone@1.25x.png", + "btn-remove@1.5x.png", + "translation@1.75x.png", + "btn-edit@1.5x.png", + "btn-update@2x.png", + "info@1.25x.png", + "ai-embeddings@1.25x.png", + "ai-moderations@2x.png", + "btn-microphone.png", + "close@1.5x.png", + "info@1.5x.png", + "vision-ai@1.5x.png", + "ai-realtime@1.5x.png", + "close.png", + "image-ai@1.75x.png", + "text-analysis-ai@1.25x.png", + "ai-moderations@1.25x.png", + "ai-moderations@1.5x.png", + "btn-demote@1.75x.png", + "btn-password@1.75x.png", + "btn-remove@2x.png", + "btn-zoomup@1.75x.png", + "chevron-down@1.5x.png", + "grammar@1.75x.png", + "ai-embeddings@2x.png", + "btn-menu-comments@1.25x.png", + "btn-zoomup.png", + "btn-zoomup@1.5x.png", + "close@1.25x.png", + "default@1.75x.png", + "image-ai@1.5x.png", + "translation@1.5x.png", + "ai-code@1.75x.png", + "btn-edit.png", + "btn-microphone.svg", + "btn-microphone@1.5x.png", + "btn-microphone@1.75x.png", + "btn-password-hide@1.25x.png" + ] + }, + { + "added": [ + "ask-ai.png", + "default@2x.png", + "form-to-doc@1.25x.png", + "ocr@1.75x.png", + "plugin-writer.png", + "plugin-writer@2x.png", + "summarization@1.75x.png", + "written-plugin@2x.png", + "ask-ai@2x.png", + "form-to-doc@1.5x.png", + "grammar@1.5x.png", + "summarization@1.25x.png", + "translation@2x.png", + "default@1.25x.png", + "default@1.5x.png", + "form-to-doc@2x.png", + "grammar@1.75x.png", + "ocr.png", + "settings@1.75x.png", + "settings@2x.png", + "summarization@1.5x.png", + "ask-ai@1.25x.png", + "form-to-doc@1.75x.png", + "grammar@1.25x.png", + "settings@1.25x.png", + "settings@1.5x.png", + "summarization@2x.png", + "text-to-image.png", + "text-to-image@2x.png", + "default@1.75x.png", + "form-to-doc.png", + "ocr@1.25x.png", + "text-to-image@1.25x.png", + "translation@1.75x.png", + "written-plugin.png", + "written-plugin@1.25x.png", + "ask-ai@1.5x.png", + "ask-ai@1.75x.png", + "default.png", + "grammar.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/light/big", + "files": [ + "ask-ai.png", + "default@2x.png", + "form-to-doc@1.25x.png", + "ocr@1.75x.png", + "plugin-writer.png", + "plugin-writer@2x.png", + "summarization@1.75x.png", + "written-plugin@2x.png", + "ask-ai@2x.png", + "form-to-doc@1.5x.png", + "grammar@1.5x.png", + "summarization@1.25x.png", + "translation@2x.png", + "default@1.25x.png", + "default@1.5x.png", + "form-to-doc@2x.png", + "grammar@1.75x.png", + "ocr.png", + "settings@1.75x.png", + "settings@2x.png", + "summarization@1.5x.png", + "ask-ai@1.25x.png", + "form-to-doc@1.75x.png", + "grammar@1.25x.png", + "settings@1.25x.png", + "settings@1.5x.png", + "summarization@2x.png", + "text-to-image.png", + "text-to-image@2x.png", + "default@1.75x.png", + "form-to-doc.png", + "ocr@1.25x.png", + "text-to-image@1.25x.png", + "translation@1.75x.png", + "written-plugin.png", + "written-plugin@1.25x.png", + "ask-ai@1.5x.png", + "ask-ai@1.75x.png", + "default.png", + "grammar.png" + ] + }, + { + "added": [ + "light" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/theme-classic-light", + "files": [ + "light" + ] + }, + { + "added": [ + "ask-ai_active@1.5x.png", + "ask-ai.png", + "ask-ai_active.png", + "ask-ai_active@1.75x.png", + "ask-ai_active@2x.png", + "ask-ai@1.25x.png", + "ask-ai@1.5x.png", + "ask-ai@1.75x.png", + "ask-ai@2x.png", + "ask-ai_active@1.25x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/theme-classic-light/light", + "files": [ + "ask-ai_active@1.5x.png", + "ask-ai.png", + "ask-ai_active.png", + "ask-ai_active@1.75x.png", + "ask-ai_active@2x.png", + "ask-ai@1.25x.png", + "ask-ai@1.5x.png", + "ask-ai@1.75x.png", + "ask-ai@2x.png", + "ask-ai_active@1.25x.png" + ] + }, + { + "added": [ + "dark", + "light" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/theme-default", + "files": [ + "dark", + "light" + ] + }, + { + "added": [ + "ask-ai@1.5x.png", + "ask-ai@1.75x.png", + "ask-ai_active@1.5x.png", + "ask-ai_active@2x.png", + "ask-ai.png", + "ask-ai@1.25x.png", + "ask-ai@2x.png", + "ask-ai_active.png", + "ask-ai_active@1.25x.png", + "ask-ai_active@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/theme-default/dark", + "files": [ + "ask-ai@1.5x.png", + "ask-ai@1.75x.png", + "ask-ai_active@1.5x.png", + "ask-ai_active@2x.png", + "ask-ai.png", + "ask-ai@1.25x.png", + "ask-ai@2x.png", + "ask-ai_active.png", + "ask-ai_active@1.25x.png", + "ask-ai_active@1.75x.png" + ] + }, + { + "added": [ + "ask-ai.png", + "ask-ai@1.25x.png", + "ask-ai@1.5x.png", + "ask-ai@2x.png", + "ask-ai_active@1.75x.png", + "ask-ai_active@2x.png", + "ask-ai@1.75x.png", + "ask-ai_active.png", + "ask-ai_active@1.25x.png", + "ask-ai_active@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/icons/theme-default/light", + "files": [ + "ask-ai.png", + "ask-ai@1.25x.png", + "ask-ai@1.5x.png", + "ask-ai@2x.png", + "ask-ai_active@1.75x.png", + "ask-ai_active@2x.png", + "ask-ai@1.75x.png", + "ask-ai_active.png", + "ask-ai_active@1.25x.png", + "ask-ai_active@1.5x.png" + ] + }, + { + "added": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon@1.25x.png", + "icon@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/light", + "files": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon@1.25x.png", + "icon@1.5x.png" + ] + }, + { + "added": [ + "common.css", + "customAssistant.css", + "customProviders.css", + "settings.css", + "summarization.css", + "aiModelEdit.css", + "aiModelsList.css", + "chat.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/styles", + "files": [ + "common.css", + "customAssistant.css", + "customProviders.css", + "settings.css", + "summarization.css", + "aiModelEdit.css", + "aiModelsList.css", + "chat.css" + ] + }, + { + "added": [ + "aiModelEdit.js", + "code.js", + "utils", + "customProviders.js", + "engine", + "generate.js", + "chat.js", + "chatVoice.js", + "customAssistant.js", + "helper.js", + "hyperlink.js", + "settings.js", + "summarization.js", + "text-annotations", + "aiModelsList.js", + "helpers" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts", + "files": [ + "aiModelEdit.js", + "code.js", + "utils", + "customProviders.js", + "engine", + "generate.js", + "chat.js", + "chatVoice.js", + "customAssistant.js", + "helper.js", + "hyperlink.js", + "settings.js", + "summarization.js", + "text-annotations", + "aiModelsList.js", + "helpers" + ] + }, + { + "added": [ + "storage.js", + "engine.js", + "library.js", + "local_storage.js", + "providers", + "register.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts/engine", + "files": [ + "storage.js", + "engine.js", + "library.js", + "local_storage.js", + "providers", + "register.js" + ] + }, + { + "added": [ + "base.js", + "config.json", + "internal", + "preinstall-example.json", + "provider.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts/engine/providers", + "files": [ + "base.js", + "config.json", + "internal", + "preinstall-example.json", + "provider.js" + ] + }, + { + "added": [ + "anthropic.js", + "deepseek.js", + "google-gemini.js", + "groq.js", + "openrouter.js", + "zhipu.js", + "gpt4all.js", + "lmstudio.js", + "mistral.js", + "ollama.js", + "openai.js", + "stabilityai.js", + "together.ai.js", + "xAI.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts/engine/providers/internal", + "files": [ + "anthropic.js", + "deepseek.js", + "google-gemini.js", + "groq.js", + "openrouter.js", + "zhipu.js", + "gpt4all.js", + "lmstudio.js", + "mistral.js", + "ollama.js", + "openai.js", + "stabilityai.js", + "together.ai.js", + "xAI.js" + ] + }, + { + "added": [ + "helpers.js", + "helperFuncs.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts/helpers", + "files": [ + "helpers.js", + "helperFuncs.js" + ] + }, + { + "added": [ + "spelling.js", + "text-annotator.js", + "annotation-popup.js", + "custom-annotations", + "grammar.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts/text-annotations", + "files": [ + "spelling.js", + "text-annotator.js", + "annotation-popup.js", + "custom-annotations", + "grammar.js" + ] + }, + { + "added": [ + "README.md", + "annotation-popup.js", + "assistant-hint.js", + "assistant-replace-hint.js", + "assistant-replace.js", + "custom-annotator.js", + "manager.js", + "types.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts/text-annotations/custom-annotations", + "files": [ + "README.md", + "annotation-popup.js", + "assistant-hint.js", + "assistant-replace-hint.js", + "assistant-replace.js", + "custom-annotator.js", + "manager.js", + "types.js" + ] + }, + { + "added": [ + "theme.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/scripts/utils", + "files": [ + "theme.js" + ] + }, + { + "added": [ + "es-ES.json", + "ja-JA.json", + "langs.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "fr-FR.json", + "sq-AL.json", + "zh-CN.json", + "cs-CS.json", + "de-DE.json", + "helpers", + "it-IT.json", + "pt-BR.json", + "ar-SA.json", + "ru-RU.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/translations", + "files": [ + "es-ES.json", + "ja-JA.json", + "langs.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "fr-FR.json", + "sq-AL.json", + "zh-CN.json", + "cs-CS.json", + "de-DE.json", + "helpers", + "it-IT.json", + "pt-BR.json", + "ar-SA.json", + "ru-RU.json" + ] + }, + { + "added": [ + "ar-SA.json", + "fr-FR.json", + "it-IT.json", + "ja-JA.json", + "pt-BR.json", + "ru-RU.json", + "cs-CS.json", + "de-DE.json", + "en.json", + "es-ES.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "zh-CN.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/translations/helpers", + "files": [ + "ar-SA.json", + "fr-FR.json", + "it-IT.json", + "ja-JA.json", + "pt-BR.json", + "ru-RU.json", + "cs-CS.json", + "de-DE.json", + "en.json", + "es-ES.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "zh-CN.json" + ] + }, + { + "added": [ + "GPT-3-Encoder", + "jquery", + "md", + "select2-4.0.6-rc.1" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor", + "files": [ + "GPT-3-Encoder", + "jquery", + "md", + "select2-4.0.6-rc.1" + ] + }, + { + "added": [ + "LICENSE", + "README.md", + "encoder.js", + "encoder.json", + "vocabulary.bpe" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/GPT-3-Encoder", + "files": [ + "LICENSE", + "README.md", + "encoder.js", + "encoder.json", + "vocabulary.bpe" + ] + }, + { + "added": [ + "jquery-3.7.1.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/jquery", + "files": [ + "jquery-3.7.1.min.js" + ] + }, + { + "added": [ + "markdown-it.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/md", + "files": [ + "markdown-it.js" + ] + }, + { + "added": [ + "dist", + "docs", + "LICENSE.md", + "README.md" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/select2-4.0.6-rc.1", + "files": [ + "dist", + "docs", + "LICENSE.md", + "README.md" + ] + }, + { + "added": [ + "css", + "js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/select2-4.0.6-rc.1/dist", + "files": [ + "css", + "js" + ] + }, + { + "added": [ + "select2.css", + "select2.min.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/select2-4.0.6-rc.1/dist/css", + "files": [ + "select2.css", + "select2.min.css" + ] + }, + { + "added": [ + "select2.full.min.js", + "select2.js", + "select2.min.js", + "select2.full.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/select2-4.0.6-rc.1/dist/js", + "files": [ + "select2.full.min.js", + "select2.js", + "select2.min.js", + "select2.full.js" + ] + }, + { + "added": [ + "README.md", + "announcements-4.0.html", + "community.html", + "examples.html", + "index.html", + "options-old.html", + "options.html" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/vendor/select2-4.0.6-rc.1/docs", + "files": [ + "README.md", + "announcements-4.0.html", + "community.html", + "examples.html", + "index.html", + "options-old.html", + "options.html" + ] + }, + { + "added": [ + "AUTHORS.md", + "CHANGELOG.md", + "edit-window.html", + "postcss.config.mjs", + "vite.config.mjs", + "index.html", + "LICENSE.txt", + "licenses", + "oauth.html", + "config.json", + "dist", + "info-window.html", + "resources", + "translations", + "vendor", + "3rd-party.txt" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}", + "files": [ + "AUTHORS.md", + "CHANGELOG.md", + "edit-window.html", + "postcss.config.mjs", + "vite.config.mjs", + "index.html", + "LICENSE.txt", + "licenses", + "oauth.html", + "config.json", + "dist", + "info-window.html", + "resources", + "translations", + "vendor", + "3rd-party.txt" + ] + }, + { + "added": [ + "edit-window.modern.js.map", + "styles.css", + "bundle.modern.js", + "bundle.modern.js.map", + "edit-window.css", + "edit-window.modern.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/dist", + "files": [ + "edit-window.modern.js.map", + "styles.css", + "bundle.modern.js", + "bundle.modern.js.map", + "edit-window.css", + "edit-window.modern.js" + ] + }, + { + "added": [ + "citeproc-js.license", + "core-js.license", + "mendeley-sdk.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/licenses", + "files": [ + "citeproc-js.license", + "core-js.license", + "mendeley-sdk.license" + ] + }, + { + "added": [ + "css", + "dark", + "img", + "light", + "store", + "csl" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources", + "files": [ + "css", + "dark", + "img", + "light", + "store", + "csl" + ] + }, + { + "added": [ + "locales", + "styles", + "styles.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/csl", + "files": [ + "locales", + "styles", + "styles.json" + ] + }, + { + "added": [ + "locales-cy-GB.xml", + "locales-en-US.xml", + "locales-es-MX.xml", + "locales-eu.xml", + "locales-km-KH.xml", + "locales-lv-LV.xml", + "locales-th-TH.xml", + "locales-uk-UA.xml", + "locales-da-DK.xml", + "locales-lij-IT.xml", + "locales-nb-NO.xml", + "locales-vi-VN.xml", + "locales-et-EE.xml", + "locales-is-IS.xml", + "locales-ru-RU.xml", + "locales-sr-RS.xml", + "locales-pt-PT.xml", + "locales-fr-CA.xml", + "locales-hr-HR.xml", + "locales-hy-AM.xml", + "locales-ms-MY.xml", + "locales-de-AT.xml", + "locales-id-ID.xml", + "locales-lt-LT.xml", + "locales-ar.xml", + "locales-he-IL.xml", + "locales-zh-TW.xml", + "locales-el-GR.xml", + "locales-ja-JP.xml", + "locales-pt-BR.xml", + "locales-sl-SI.xml", + "locales-zh-CN.xml", + "locales-cs-CZ.xml", + "locales-es-CL.xml", + "locales-hi-IN.xml", + "locales-en-GB.xml", + "locales-fa-IR.xml", + "locales-it-IT.xml", + "locales-ko-KR.xml", + "locales-mn-MN.xml" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/csl/locales", + "files": [ + "locales-cy-GB.xml", + "locales-en-US.xml", + "locales-es-MX.xml", + "locales-eu.xml", + "locales-km-KH.xml", + "locales-lv-LV.xml", + "locales-th-TH.xml", + "locales-uk-UA.xml", + "locales-da-DK.xml", + "locales-lij-IT.xml", + "locales-nb-NO.xml", + "locales-vi-VN.xml", + "locales-et-EE.xml", + "locales-is-IS.xml", + "locales-ru-RU.xml", + "locales-sr-RS.xml", + "locales-pt-PT.xml", + "locales-fr-CA.xml", + "locales-hr-HR.xml", + "locales-hy-AM.xml", + "locales-ms-MY.xml", + "locales-de-AT.xml", + "locales-id-ID.xml", + "locales-lt-LT.xml", + "locales-ar.xml", + "locales-he-IL.xml", + "locales-zh-TW.xml", + "locales-el-GR.xml", + "locales-ja-JP.xml", + "locales-pt-BR.xml", + "locales-sl-SI.xml", + "locales-zh-CN.xml", + "locales-cs-CZ.xml", + "locales-es-CL.xml", + "locales-hi-IN.xml", + "locales-en-GB.xml", + "locales-fa-IR.xml", + "locales-it-IT.xml", + "locales-ko-KR.xml", + "locales-mn-MN.xml" + ] + }, + { + "added": [ + "harvard-cite-them-right.csl", + "ieee.csl", + "american-anthropological-association.csl", + "modern-language-association.csl", + "nature.csl", + "american-medical-association.csl", + "american-political-science-association.csl", + "american-sociological-association.csl", + "apa.csl", + "chicago-author-date.csl", + "chicago-notes-bibliography.csl" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/csl/styles", + "files": [ + "harvard-cite-them-right.csl", + "ieee.csl", + "american-anthropological-association.csl", + "modern-language-association.csl", + "nature.csl", + "american-medical-association.csl", + "american-political-science-association.csl", + "american-sociological-association.csl", + "apa.csl", + "chicago-author-date.csl", + "chicago-notes-bibliography.csl" + ] + }, + { + "added": [ + "plugin_style.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/css", + "files": [ + "plugin_style.css" + ] + }, + { + "added": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/dark", + "files": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ] + }, + { + "added": [ + "x.svg", + "checkmark.png", + "checkmark.svg", + "x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/img", + "files": [ + "x.svg", + "checkmark.png", + "checkmark.svg", + "x.png" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/light", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "fr-FR.json", + "ja-JA.json", + "pt-BR.json", + "sq-AL.json", + "zh-ZH.json", + "cs-CZ.json", + "it-IT.json", + "pt-PT.json", + "nl-NL.json", + "es-ES.json", + "langs.json", + "ru-RU.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "de-DE.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/translations", + "files": [ + "fr-FR.json", + "ja-JA.json", + "pt-BR.json", + "sq-AL.json", + "zh-ZH.json", + "cs-CZ.json", + "it-IT.json", + "pt-PT.json", + "nl-NL.json", + "es-ES.json", + "langs.json", + "ru-RU.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "de-DE.json" + ] + }, + { + "added": [ + "citeproc", + "mendeley-sdk" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/vendor", + "files": [ + "citeproc", + "mendeley-sdk" + ] + }, + { + "added": [ + "citeproc_commonjs.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/vendor/citeproc", + "files": [ + "citeproc_commonjs.js" + ] + }, + { + "added": [ + "standalone.min.js", + "standalone.min.js.map" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/vendor/mendeley-sdk", + "files": [ + "standalone.min.js", + "standalone.min.js.map" + ] + }, + { + "added": [ + "3rd-Party.txt", + "CHANGELOG.md", + "config.json", + "index.html", + "licenses", + "resources", + "translations", + "vendor", + "README.md", + "scripts" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}", + "files": [ + "3rd-Party.txt", + "CHANGELOG.md", + "config.json", + "index.html", + "licenses", + "resources", + "translations", + "vendor", + "README.md", + "scripts" + ] + }, + { + "added": [ + "jQuery.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/licenses", + "files": [ + "jQuery.license" + ] + }, + { + "added": [ + "dark", + "light", + "store", + "css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources", + "files": [ + "dark", + "light", + "store", + "css" + ] + }, + { + "added": [ + "plugin_style.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/css", + "files": [ + "plugin_style.css" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/dark", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/light", + "files": [ + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg" + ] + }, + { + "added": [ + "synonim.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/scripts", + "files": [ + "synonim.js" + ] + }, + { + "added": [ + "langs.json", + "ru-RU.json", + "sr-Cyrl-RS.json", + "zh-ZH.json", + "cs-CS.json", + "de-DE.json", + "it-IT.json", + "ja-JA.json", + "pt-BR.json", + "si-SI.json", + "sq-AL.json", + "sr-Latn-RS.json", + "es-ES.json", + "fr-FR.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/translations", + "files": [ + "langs.json", + "ru-RU.json", + "sr-Cyrl-RS.json", + "zh-ZH.json", + "cs-CS.json", + "de-DE.json", + "it-IT.json", + "ja-JA.json", + "pt-BR.json", + "si-SI.json", + "sq-AL.json", + "sr-Latn-RS.json", + "es-ES.json", + "fr-FR.json" + ] + }, + { + "added": [ + "jquery" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/vendor", + "files": [ + "jquery" + ] + }, + { + "added": [ + "jquery-3.7.1.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/vendor/jquery", + "files": [ + "jquery-3.7.1.min.js" + ] + }, + { + "added": [ + "translations", + "vendor", + "3rd-Party.txt", + "CHANGELOG.md", + "config.json", + "index.html", + "README.md", + "licenses", + "resources", + "scripts" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}", + "files": [ + "translations", + "vendor", + "3rd-Party.txt", + "CHANGELOG.md", + "config.json", + "index.html", + "README.md", + "licenses", + "resources", + "scripts" + ] + }, + { + "added": [ + "prettier@plugin-php.license", + "spectrum.license", + "xml-formatter.license", + "Highlight.license", + "Select2.license", + "jQuery.license", + "js-beautify.license", + "prettier.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/licenses", + "files": [ + "prettier@plugin-php.license", + "spectrum.license", + "xml-formatter.license", + "Highlight.license", + "Select2.license", + "jQuery.license", + "js-beautify.license", + "prettier.license" + ] + }, + { + "added": [ + "css", + "dark", + "light", + "store" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources", + "files": [ + "css", + "dark", + "light", + "store" + ] + }, + { + "added": [ + "plugin_style.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/css", + "files": [ + "plugin_style.css" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/dark", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/light", + "files": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ] + }, + { + "added": [ + "highlight.js", + "highlight_ie.js", + "scrollable_div.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/scripts", + "files": [ + "highlight.js", + "highlight_ie.js", + "scrollable_div.js" + ] + }, + { + "added": [ + "cs-CZ.json", + "fr-FR.json", + "it-IT.json", + "langs.json", + "pt-BR.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "de-DE.json", + "es-ES.json", + "ja-JA.json", + "ru-RU.json", + "si-SI.json", + "zh-ZH.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/translations", + "files": [ + "cs-CZ.json", + "fr-FR.json", + "it-IT.json", + "langs.json", + "pt-BR.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "de-DE.json", + "es-ES.json", + "ja-JA.json", + "ru-RU.json", + "si-SI.json", + "zh-ZH.json" + ] + }, + { + "added": [ + "jquery", + "js-beautify-1.14.0", + "prettier", + "select2-4.0.13", + "spectrum", + "xml-formatter", + "highlight" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor", + "files": [ + "jquery", + "js-beautify-1.14.0", + "prettier", + "select2-4.0.13", + "spectrum", + "xml-formatter", + "highlight" + ] + }, + { + "added": [ + "styles", + "LICENSE", + "README.md", + "highlight.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/highlight", + "files": [ + "styles", + "LICENSE", + "README.md", + "highlight.min.js" + ] + }, + { + "added": [ + "androidstudio.css", + "github-gist.css", + "github.css", + "qtcreator_dark.css", + "qtcreator_light.css", + "xml_1.css", + "far.css", + "foundation.css", + "googlecode.css", + "idea.css", + "vs.css", + "vs2015.css", + "xcode.css", + "xml_2.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/highlight/styles", + "files": [ + "androidstudio.css", + "github-gist.css", + "github.css", + "qtcreator_dark.css", + "qtcreator_light.css", + "xml_1.css", + "far.css", + "foundation.css", + "googlecode.css", + "idea.css", + "vs.css", + "vs2015.css", + "xcode.css", + "xml_2.css" + ] + }, + { + "added": [ + "jquery-3.7.1.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/jquery", + "files": [ + "jquery-3.7.1.min.js" + ] + }, + { + "added": [ + "LICENSE", + "beautify-css.js", + "beautify-html.js", + "beautify.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/js-beautify-1.14.0", + "files": [ + "LICENSE", + "beautify-css.js", + "beautify-html.js", + "beautify.js" + ] + }, + { + "added": [ + "parser-markdown@2.3.2.js", + "parser-postcss@2.3.2.js", + "parser-typescript@2.3.2.js", + "plugin-php", + "standalone@2.3.2.js", + "LICENSE", + "parser-babel@2.3.2.js", + "parser-html@2.3.2.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/prettier", + "files": [ + "parser-markdown@2.3.2.js", + "parser-postcss@2.3.2.js", + "parser-typescript@2.3.2.js", + "plugin-php", + "standalone@2.3.2.js", + "LICENSE", + "parser-babel@2.3.2.js", + "parser-html@2.3.2.js" + ] + }, + { + "added": [ + "LICENSE", + "plugin-php.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/prettier/plugin-php", + "files": [ + "LICENSE", + "plugin-php.js" + ] + }, + { + "added": [ + "LICENSE.md", + "README.md", + "css", + "js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/select2-4.0.13", + "files": [ + "LICENSE.md", + "README.md", + "css", + "js" + ] + }, + { + "added": [ + "select2.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/select2-4.0.13/css", + "files": [ + "select2.css" + ] + }, + { + "added": [ + "select2.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/select2-4.0.13/js", + "files": [ + "select2.js" + ] + }, + { + "added": [ + "spectrum.css", + "spectrum.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/spectrum", + "files": [ + "spectrum.css", + "spectrum.js" + ] + }, + { + "added": [ + "LICENSE", + "xml-formatter.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/vendor/xml-formatter", + "files": [ + "LICENSE", + "xml-formatter.js" + ] + }, + { + "added": [ + "LICENSE.txt", + "dist", + "edit-window.html", + "index.html", + "3rd-party.txt", + "AUTHORS.md", + "config.json", + "info-window.html", + "postcss.config.mjs", + "resources", + "vendor", + "vite.config.mjs", + "CHANGELOG.md", + "licenses", + "translations" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}", + "files": [ + "LICENSE.txt", + "dist", + "edit-window.html", + "index.html", + "3rd-party.txt", + "AUTHORS.md", + "config.json", + "info-window.html", + "postcss.config.mjs", + "resources", + "vendor", + "vite.config.mjs", + "CHANGELOG.md", + "licenses", + "translations" + ] + }, + { + "added": [ + "bundle.modern.js", + "bundle.modern.js.map", + "edit-window.css", + "edit-window.modern.js", + "edit-window.modern.js.map", + "styles.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/dist", + "files": [ + "bundle.modern.js", + "bundle.modern.js.map", + "edit-window.css", + "edit-window.modern.js", + "edit-window.modern.js.map", + "styles.css" + ] + }, + { + "added": [ + "core-js.license", + "citeproc.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/licenses", + "files": [ + "core-js.license", + "citeproc.license" + ] + }, + { + "added": [ + "light", + "store", + "csl", + "dark", + "img" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources", + "files": [ + "light", + "store", + "csl", + "dark", + "img" + ] + }, + { + "added": [ + "locales", + "styles", + "styles.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/csl", + "files": [ + "locales", + "styles", + "styles.json" + ] + }, + { + "added": [ + "locales-la.xml", + "locales-nn-NO.xml", + "locales-el-GR.xml", + "locales-ru-RU.xml", + "locales-tr-TR.xml", + "locales-km-KH.xml", + "locales-pa-PK.xml", + "locales-sr-Latn-RS.xml", + "locales-en-GB.xml", + "locales-af-ZA.xml", + "locales-ca-AD.xml", + "locales-da-DK.xml", + "locales-hy-AM.xml", + "locales-bal-PK.xml", + "locales-es-MX.xml", + "locales-hi-IN.xml", + "locales-ms-MY.xml", + "locales-sv-SE.xml", + "locales-zh-TW.xml", + "locales-ar.xml", + "locales-es-CL.xml", + "locales-id-ID.xml", + "locales-cs-CZ.xml", + "locales-nl-NL.xml", + "locales-sl-SI.xml", + "locales-gl-ES.xml", + "locales-fr-FR.xml", + "locales-is-IS.xml", + "locales-th-TH.xml", + "locales-brh-PK.xml", + "locales-es-ES.xml", + "locales-et-EE.xml", + "locales-hr-HR.xml", + "locales-lt-LT.xml", + "locales-uk-UA.xml", + "locales-cy-GB.xml", + "locales-hu-HU.xml", + "locales-ro-RO.xml", + "locales-sr-RS.xml", + "locales-zh-CN.xml" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/csl/locales", + "files": [ + "locales-la.xml", + "locales-nn-NO.xml", + "locales-el-GR.xml", + "locales-ru-RU.xml", + "locales-tr-TR.xml", + "locales-km-KH.xml", + "locales-pa-PK.xml", + "locales-sr-Latn-RS.xml", + "locales-en-GB.xml", + "locales-af-ZA.xml", + "locales-ca-AD.xml", + "locales-da-DK.xml", + "locales-hy-AM.xml", + "locales-bal-PK.xml", + "locales-es-MX.xml", + "locales-hi-IN.xml", + "locales-ms-MY.xml", + "locales-sv-SE.xml", + "locales-zh-TW.xml", + "locales-ar.xml", + "locales-es-CL.xml", + "locales-id-ID.xml", + "locales-cs-CZ.xml", + "locales-nl-NL.xml", + "locales-sl-SI.xml", + "locales-gl-ES.xml", + "locales-fr-FR.xml", + "locales-is-IS.xml", + "locales-th-TH.xml", + "locales-brh-PK.xml", + "locales-es-ES.xml", + "locales-et-EE.xml", + "locales-hr-HR.xml", + "locales-lt-LT.xml", + "locales-uk-UA.xml", + "locales-cy-GB.xml", + "locales-hu-HU.xml", + "locales-ro-RO.xml", + "locales-sr-RS.xml", + "locales-zh-CN.xml" + ] + }, + { + "added": [ + "american-political-science-association.csl", + "chicago-author-date.csl", + "ieee.csl", + "modern-language-association.csl", + "american-anthropological-association.csl", + "american-sociological-association.csl", + "apa.csl", + "chicago-notes-bibliography.csl", + "harvard-cite-them-right.csl", + "nature.csl", + "american-medical-association.csl" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/csl/styles", + "files": [ + "american-political-science-association.csl", + "chicago-author-date.csl", + "ieee.csl", + "modern-language-association.csl", + "american-anthropological-association.csl", + "american-sociological-association.csl", + "apa.csl", + "chicago-notes-bibliography.csl", + "harvard-cite-them-right.csl", + "nature.csl", + "american-medical-association.csl" + ] + }, + { + "added": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/dark", + "files": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ] + }, + { + "added": [ + "checkmark.svg", + "x.png", + "x.svg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/img", + "files": [ + "checkmark.svg", + "x.png", + "x.svg" + ] + }, + { + "added": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/light", + "files": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ] + }, + { + "added": [ + "it-IT.json", + "pt-BR.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "cs-CS.json", + "de-DE.json", + "es-ES.json", + "fr-FR.json", + "langs.json", + "pt-PT.json", + "zh-ZH.json", + "nl-NL.json", + "ru-RU.json", + "ja-JA.json", + "sq-AL.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/translations", + "files": [ + "it-IT.json", + "pt-BR.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "cs-CS.json", + "de-DE.json", + "es-ES.json", + "fr-FR.json", + "langs.json", + "pt-PT.json", + "zh-ZH.json", + "nl-NL.json", + "ru-RU.json", + "ja-JA.json", + "sq-AL.json" + ] + }, + { + "added": [ + "citeproc" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/vendor", + "files": [ + "citeproc" + ] + }, + { + "added": [ + "citeproc_commonjs.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/vendor/citeproc", + "files": [ + "citeproc_commonjs.js" + ] + }, + { + "added": [ + "scripts", + "settings.html", + "vendor", + "README.md", + "index.html", + "licenses", + "translations", + "3rd-Party.txt", + "CHANGELOG.md", + "config.json", + "resources" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}", + "files": [ + "scripts", + "settings.html", + "vendor", + "README.md", + "index.html", + "licenses", + "translations", + "3rd-Party.txt", + "CHANGELOG.md", + "config.json", + "resources" + ] + }, + { + "added": [ + "Select2.license", + "jQuery.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/licenses", + "files": [ + "Select2.license", + "jQuery.license" + ] + }, + { + "added": [ + "dark", + "light", + "store" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources", + "files": [ + "dark", + "light", + "store" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/dark", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/light", + "files": [ + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg" + ] + }, + { + "added": [ + "settings.js", + "speech.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/scripts", + "files": [ + "settings.js", + "speech.js" + ] + }, + { + "added": [ + "zh-ZH.json", + "cs-CS.json", + "de-DE.json", + "es-ES.json", + "ja-JA.json", + "langs.json", + "pt-BR.json", + "ru-RU.json", + "sq-AL.json", + "fr-FR.json", + "it-IT.json", + "nl-NL.json", + "pt-PT.json", + "sr-Latn-RS.json", + "uk-UA.json", + "sr-Cyrl-RS.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/translations", + "files": [ + "zh-ZH.json", + "cs-CS.json", + "de-DE.json", + "es-ES.json", + "ja-JA.json", + "langs.json", + "pt-BR.json", + "ru-RU.json", + "sq-AL.json", + "fr-FR.json", + "it-IT.json", + "nl-NL.json", + "pt-PT.json", + "sr-Latn-RS.json", + "uk-UA.json", + "sr-Cyrl-RS.json" + ] + }, + { + "added": [ + "guessLanguage", + "jquery", + "select2-4.0.13" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/vendor", + "files": [ + "guessLanguage", + "jquery", + "select2-4.0.13" + ] + }, + { + "added": [ + "_languageData.js", + "guessLanguage.js", + "README.md" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/vendor/guessLanguage", + "files": [ + "_languageData.js", + "guessLanguage.js", + "README.md" + ] + }, + { + "added": [ + "jquery-3.7.1.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/vendor/jquery", + "files": [ + "jquery-3.7.1.min.js" + ] + }, + { + "added": [ + "LICENSE.md", + "README.md", + "css", + "js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/vendor/select2-4.0.13", + "files": [ + "LICENSE.md", + "README.md", + "css", + "js" + ] + }, + { + "added": [ + "select2.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/vendor/select2-4.0.13/css", + "files": [ + "select2.css" + ] + }, + { + "added": [ + "select2.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/vendor/select2-4.0.13/js", + "files": [ + "select2.js" + ] + }, + { + "added": [ + "index.html", + "translations", + "vendor", + "3rd-Party.txt", + "CHANGELOG.md", + "index_about.html", + "licenses", + "resources", + "scripts", + "README.md", + "config.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}", + "files": [ + "index.html", + "translations", + "vendor", + "3rd-Party.txt", + "CHANGELOG.md", + "index_about.html", + "licenses", + "resources", + "scripts", + "README.md", + "config.json" + ] + }, + { + "added": [ + "jQuery.license", + "Select2.license" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/licenses", + "files": [ + "jQuery.license", + "Select2.license" + ] + }, + { + "added": [ + "css", + "dark", + "light", + "store" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources", + "files": [ + "css", + "dark", + "light", + "store" + ] + }, + { + "added": [ + "plugin_style.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/css", + "files": [ + "plugin_style.css" + ] + }, + { + "added": [ + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/dark", + "files": [ + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/light", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "code.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/scripts", + "files": [ + "code.js" + ] + }, + { + "added": [ + "de-De.json", + "es-ES.json", + "fr-FR.json", + "it-IT.json", + "pt-BR.json", + "langs.json", + "ru-RU.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "zh-ZH.json", + "cs-CZ.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/translations", + "files": [ + "de-De.json", + "es-ES.json", + "fr-FR.json", + "it-IT.json", + "pt-BR.json", + "langs.json", + "ru-RU.json", + "sq-AL.json", + "sr-Cyrl-RS.json", + "sr-Latn-RS.json", + "zh-ZH.json", + "cs-CZ.json" + ] + }, + { + "added": [ + "jquery", + "select2-4.0.6-rc.1" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/vendor", + "files": [ + "jquery", + "select2-4.0.6-rc.1" + ] + }, + { + "added": [ + "jquery-3.7.1.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/vendor/jquery", + "files": [ + "jquery-3.7.1.min.js" + ] + }, + { + "added": [ + "dist", + "docs", + "LICENSE.md", + "README.md" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/vendor/select2-4.0.6-rc.1", + "files": [ + "dist", + "docs", + "LICENSE.md", + "README.md" + ] + }, + { + "added": [ + "js", + "css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/vendor/select2-4.0.6-rc.1/dist", + "files": [ + "js", + "css" + ] + }, + { + "added": [ + "select2.css", + "select2.min.css" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/vendor/select2-4.0.6-rc.1/dist/css", + "files": [ + "select2.css", + "select2.min.css" + ] + }, + { + "added": [ + "select2.full.js", + "select2.full.min.js", + "select2.js", + "select2.min.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/vendor/select2-4.0.6-rc.1/dist/js", + "files": [ + "select2.full.js", + "select2.full.min.js", + "select2.js", + "select2.min.js" + ] + }, + { + "added": [ + "community.html", + "examples.html", + "index.html", + "options-old.html", + "options.html", + "README.md", + "announcements-4.0.html" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/vendor/select2-4.0.6-rc.1/docs", + "files": [ + "community.html", + "examples.html", + "index.html", + "options-old.html", + "options.html", + "README.md", + "announcements-4.0.html" + ] + }, + { + "added": [ + "AllFonts.js" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/common", + "files": [ + "AllFonts.js" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide", + "files": [ + "themes" + ] + }, + { + "added": [ + "theme29", + "theme4", + "theme5", + "theme11", + "theme12", + "theme13", + "theme15", + "theme18", + "theme2", + "theme21", + "theme31", + "theme10", + "theme30", + "theme35", + "theme6", + "theme7", + "theme20", + "theme24", + "theme27", + "theme28", + "theme32", + "theme25", + "theme26", + "themes.js", + "theme3", + "theme34", + "theme14", + "theme16", + "theme19", + "theme22", + "theme36", + "theme9", + "theme33", + "theme8", + "theme1", + "theme17", + "theme23" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes", + "files": [ + "theme29", + "theme4", + "theme5", + "theme11", + "theme12", + "theme13", + "theme15", + "theme18", + "theme2", + "theme21", + "theme31", + "theme10", + "theme30", + "theme35", + "theme6", + "theme7", + "theme20", + "theme24", + "theme27", + "theme28", + "theme32", + "theme25", + "theme26", + "themes.js", + "theme3", + "theme34", + "theme14", + "theme16", + "theme19", + "theme22", + "theme36", + "theme9", + "theme33", + "theme8", + "theme1", + "theme17", + "theme23" + ] + }, + { + "added": [ + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme1", + "files": [ + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png" + ] + }, + { + "added": [ + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme10", + "files": [ + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png" + ] + }, + { + "added": [ + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme11", + "files": [ + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail.png" + ] + }, + { + "added": [ + "thumbnail@4.5x.png", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme12", + "files": [ + "thumbnail@4.5x.png", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png" + ] + }, + { + "added": [ + "thumbnail@2x.png", + "thumbnail@4x.png", + "media", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme13", + "files": [ + "thumbnail@2x.png", + "thumbnail@4x.png", + "media", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png" + ] + }, + { + "added": [ + "thumbnail@4x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@3x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme14", + "files": [ + "thumbnail@4x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@3x.png" + ] + }, + { + "added": [ + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme15", + "files": [ + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png" + ] + }, + { + "added": [ + "thumbnail@1.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme16", + "files": [ + "thumbnail@1.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png" + ] + }, + { + "added": [ + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "media" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme17", + "files": [ + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "media" + ] + }, + { + "added": [ + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme18", + "files": [ + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@5x.png" + ] + }, + { + "added": [ + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "media", + "thumbnail.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme19", + "files": [ + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "media", + "thumbnail.png" + ] + }, + { + "added": [ + "media", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme2", + "files": [ + "media", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png" + ] + }, + { + "added": [ + "thumbnail@4.5x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme20", + "files": [ + "thumbnail@4.5x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png" + ] + }, + { + "added": [ + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme21", + "files": [ + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2x.png" + ] + }, + { + "added": [ + "Image__df2A6R", + "Image__5DUziX", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@5x.png", + "theme.bin", + "Image__2vVVy7", + "Image__UunK9E", + "Image__gIemZc", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "Image__jgZDmK", + "Image__jnmGG6", + "media", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "thumbnail.png", + "Image__DuGoLP", + "Image__GRy2nE", + "Image__JUyEdM", + "Image__PisycM" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme22", + "files": [ + "Image__df2A6R", + "Image__5DUziX", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@5x.png", + "theme.bin", + "Image__2vVVy7", + "Image__UunK9E", + "Image__gIemZc", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "Image__jgZDmK", + "Image__jnmGG6", + "media", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "thumbnail.png", + "Image__DuGoLP", + "Image__GRy2nE", + "Image__JUyEdM", + "Image__PisycM" + ] + }, + { + "added": [ + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme23", + "files": [ + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png" + ] + }, + { + "added": [ + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme24", + "files": [ + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png" + ] + }, + { + "added": [ + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme25", + "files": [ + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "media", + "thumbnail.png", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@5x.png" + ] + }, + { + "added": [ + "thumbnail@5x.png", + "media", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme26", + "files": [ + "thumbnail@5x.png", + "media", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png" + ] + }, + { + "added": [ + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme27", + "files": [ + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png" + ] + }, + { + "added": [ + "thumbnail@4x.png", + "media", + "theme.bin", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme28", + "files": [ + "thumbnail@4x.png", + "media", + "theme.bin", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ] + }, + { + "added": [ + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme29", + "files": [ + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png" + ] + }, + { + "added": [ + "thumbnail@4x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme3", + "files": [ + "thumbnail@4x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ] + }, + { + "added": [ + "media", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@2.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme30", + "files": [ + "media", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail@2.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png" + ] + }, + { + "added": [ + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@3x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme31", + "files": [ + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "media", + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@3x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png" + ] + }, + { + "added": [ + "thumbnail@4.5x.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme32", + "files": [ + "thumbnail@4.5x.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png" + ] + }, + { + "added": [ + "media", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme33", + "files": [ + "media", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png" + ] + }, + { + "added": [ + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "theme.bin", + "thumbnail@1.75x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme34", + "files": [ + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "theme.bin", + "thumbnail@1.75x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media" + ] + }, + { + "added": [ + "media", + "theme.bin", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme35", + "files": [ + "media", + "theme.bin", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@5x.png", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png" + ] + }, + { + "added": [ + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme36", + "files": [ + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@5x.png", + "media", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png" + ] + }, + { + "added": [ + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme4", + "files": [ + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "theme.bin", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ] + }, + { + "added": [ + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4.5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme5", + "files": [ + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@4.5x.png", + "theme.bin", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media" + ] + }, + { + "added": [ + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "media", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme6", + "files": [ + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "media", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail.png" + ] + }, + { + "added": [ + "theme.bin", + "thumbnail.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@5x.png", + "media" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme7", + "files": [ + "theme.bin", + "thumbnail.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png", + "thumbnail@4x.png", + "thumbnail@1.25x.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@5x.png", + "media" + ] + }, + { + "added": [ + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme8", + "files": [ + "theme.bin", + "thumbnail@1.25x.png", + "thumbnail@2.5x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "media", + "thumbnail.png", + "thumbnail@1.5x.png", + "thumbnail@1.75x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png", + "thumbnail@3x.png", + "thumbnail@4.5x.png" + ] + }, + { + "added": [ + "thumbnail@4.5x.png", + "media", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme9", + "files": [ + "thumbnail@4.5x.png", + "media", + "thumbnail.png", + "thumbnail@1.25x.png", + "thumbnail@1.75x.png", + "thumbnail@3x.png", + "thumbnail@4x.png", + "thumbnail@5x.png", + "theme.bin", + "thumbnail@1.5x.png", + "thumbnail@2.5x.png", + "thumbnail@2x.png", + "thumbnail@3.5x.png" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/server", + "files": [ + "FileConverter" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/server/FileConverter", + "files": [ + "bin" + ] + }, + { + "added": [ + "font_selection.bin", + "AllFonts.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/server/FileConverter/bin", + "files": [ + "font_selection.bin", + "AllFonts.js" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/web-apps", + "files": [ + "apps" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/web-apps/apps", + "files": [ + "api" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/web-apps/apps/api", + "files": [ + "documents" + ] + } + ], + "diff_token_dirs": [ + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/store/icons", + "files": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png", + "screen_3.png", + "screen_4.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png", + "screen_3.png", + "screen_4.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/store/icons", + "files": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png" + ] + }, + { + "added": [ + "screenshots", + "icons" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/store", + "files": [ + "screenshots", + "icons" + ] + }, + { + "added": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/store/icons", + "files": [ + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png", + "screen_3.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png", + "screen_3.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources/store/icons", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/store/icons", + "files": [ + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png" + ] + }, + { + "added": [ + "screen_5.png", + "screen_6.png", + "screen_1.png", + "screen_2.png", + "screen_3.png", + "screen_4.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/store/screenshots", + "files": [ + "screen_5.png", + "screen_6.png", + "screen_1.png", + "screen_2.png", + "screen_3.png", + "screen_4.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/store/icons", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "screen_2.png", + "screen_3.png", + "screen_1.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/store/screenshots", + "files": [ + "screen_2.png", + "screen_3.png", + "screen_1.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/store/icons", + "files": [ + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg" + ] + }, + { + "added": [ + "screen_1.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/store/screenshots", + "files": [ + "screen_1.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/store/icons", + "files": [ + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png", + "icon@1.75x.png", + "icon@2x.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png", + "screen_3.png", + "screen_4.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png", + "screen_3.png", + "screen_4.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/store/icons", + "files": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png", + "screen_3.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png", + "screen_3.png" + ] + }, + { + "added": [ + "screenshots", + "icons" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/store", + "files": [ + "screenshots", + "icons" + ] + }, + { + "added": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/store/icons", + "files": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png", + "screen_3.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png", + "screen_3.png" + ] + }, + { + "added": [ + "icons", + "screenshots" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/store", + "files": [ + "icons", + "screenshots" + ] + }, + { + "added": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/store/icons", + "files": [ + "icon@1.75x.png", + "icon@2x.png", + "icon.png", + "icon.svg", + "icon@1.25x.png", + "icon@1.5x.png" + ] + }, + { + "added": [ + "screen_1.png", + "screen_2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/store/screenshots", + "files": [ + "screen_1.png", + "screen_2.png" + ] + }, + { + "added": [ + "fonts_thumbnail_ea@1.5x.png", + "fonts_thumbnail.png", + "fonts_thumbnail_ea@1.25x.png.bin", + "themes_thumbnail@2.5x.png", + "themes_thumbnail@2x.png", + "fonts_thumbnail@1.5x.png", + "fonts_thumbnail_ea@1.25x.png", + "fonts_thumbnail_ea@1.75x.png", + "fonts_thumbnail_ea@2x.png.bin", + "fonts_thumbnail_ea@2x.png", + "fonts_thumbnail_ea.png", + "fonts_thumbnail@1.25x.png.bin", + "fonts_thumbnail@2x.png.bin", + "themes_thumbnail.png", + "fonts_thumbnail@1.5x.png.bin", + "fonts_thumbnail@1.75x.png", + "fonts_thumbnail_ea.png.bin", + "fonts_thumbnail_ea@1.5x.png.bin", + "themes_thumbnail@1.25x.png", + "themes_thumbnail@3x.png", + "themes_thumbnail@4x.png", + "themes_thumbnail@1.5x.png", + "fonts_thumbnail.png.bin", + "fonts_thumbnail@2x.png", + "fonts_thumbnail_ea@1.75x.png.bin", + "themes_thumbnail@1.75x.png", + "themes_thumbnail@3.5x.png", + "fonts_thumbnail@1.25x.png", + "fonts_thumbnail@1.75x.png.bin" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/common/Images", + "files": [ + "fonts_thumbnail_ea@1.5x.png", + "fonts_thumbnail.png", + "fonts_thumbnail_ea@1.25x.png.bin", + "themes_thumbnail@2.5x.png", + "themes_thumbnail@2x.png", + "fonts_thumbnail@1.5x.png", + "fonts_thumbnail_ea@1.25x.png", + "fonts_thumbnail_ea@1.75x.png", + "fonts_thumbnail_ea@2x.png.bin", + "fonts_thumbnail_ea@2x.png", + "fonts_thumbnail_ea.png", + "fonts_thumbnail@1.25x.png.bin", + "fonts_thumbnail@2x.png.bin", + "themes_thumbnail.png", + "fonts_thumbnail@1.5x.png.bin", + "fonts_thumbnail@1.75x.png", + "fonts_thumbnail_ea.png.bin", + "fonts_thumbnail_ea@1.5x.png.bin", + "themes_thumbnail@1.25x.png", + "themes_thumbnail@3x.png", + "themes_thumbnail@4x.png", + "themes_thumbnail@1.5x.png", + "fonts_thumbnail.png.bin", + "fonts_thumbnail@2x.png", + "fonts_thumbnail_ea@1.75x.png.bin", + "themes_thumbnail@1.75x.png", + "themes_thumbnail@3.5x.png", + "fonts_thumbnail@1.25x.png", + "fonts_thumbnail@1.75x.png.bin" + ] + }, + { + "added": [ + "image1.jpg", + "image2.jpg", + "image3.jpg", + "image4.jpg", + "image5.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme12/media", + "files": [ + "image1.jpg", + "image2.jpg", + "image3.jpg", + "image4.jpg", + "image5.jpg" + ] + }, + { + "added": [ + "image2.png", + "image3.jpg", + "image4.png", + "image1.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme13/media", + "files": [ + "image2.png", + "image3.jpg", + "image4.png", + "image1.png" + ] + }, + { + "added": [ + "image1.png", + "image2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme15/media", + "files": [ + "image1.png", + "image2.png" + ] + }, + { + "added": [ + "image1.png", + "image2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme16/media", + "files": [ + "image1.png", + "image2.png" + ] + }, + { + "added": [ + "image1.jpg", + "image2.jpg", + "image3.jpg", + "image4.jpg", + "image5.jpg", + "image6.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme17/media", + "files": [ + "image1.jpg", + "image2.jpg", + "image3.jpg", + "image4.jpg", + "image5.jpg", + "image6.jpg" + ] + }, + { + "added": [ + "image7.png", + "image8.png", + "image9.png", + "image1.png", + "image10.png", + "image11.png", + "image4.png", + "image5.png", + "image6.png", + "image2.png", + "image3.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme18/media", + "files": [ + "image7.png", + "image8.png", + "image9.png", + "image1.png", + "image10.png", + "image11.png", + "image4.png", + "image5.png", + "image6.png", + "image2.png", + "image3.png" + ] + }, + { + "added": [ + "image3.png", + "image4.png", + "image5.png", + "image6.png", + "image7.png", + "image1.png", + "image2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme19/media", + "files": [ + "image3.png", + "image4.png", + "image5.png", + "image6.png", + "image7.png", + "image1.png", + "image2.png" + ] + }, + { + "added": [ + "image1.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme20/media", + "files": [ + "image1.jpg" + ] + }, + { + "added": [ + "image4.png", + "image1.jpg", + "image2.png", + "image3.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme21/media", + "files": [ + "image4.png", + "image1.jpg", + "image2.png", + "image3.png" + ] + }, + { + "added": [ + "image7.png", + "image9.png", + "image1.png", + "image10.png", + "image11.png", + "image13.png", + "image5.png", + "image6.png", + "image8.png", + "image12.png", + "image2.png", + "image3.png", + "image4.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme24/media", + "files": [ + "image7.png", + "image9.png", + "image1.png", + "image10.png", + "image11.png", + "image13.png", + "image5.png", + "image6.png", + "image8.png", + "image12.png", + "image2.png", + "image3.png", + "image4.png" + ] + }, + { + "added": [ + "image1.png", + "image2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme25/media", + "files": [ + "image1.png", + "image2.png" + ] + }, + { + "added": [ + "image1.png", + "image2.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme27/media", + "files": [ + "image1.png", + "image2.png" + ] + }, + { + "added": [ + "image2.png", + "image1.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme29/media", + "files": [ + "image2.png", + "image1.png" + ] + }, + { + "added": [ + "image1.jpg", + "image2.jpg", + "image3.png", + "image4.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme3/media", + "files": [ + "image1.jpg", + "image2.jpg", + "image3.png", + "image4.png" + ] + }, + { + "added": [ + "image4.svg", + "image5.svg", + "image6.svg", + "image1.svg", + "image2.png", + "image3.svg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme30/media", + "files": [ + "image4.svg", + "image5.svg", + "image6.svg", + "image1.svg", + "image2.png", + "image3.svg" + ] + }, + { + "added": [ + "image1.jpg", + "image2.png", + "image3.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme31/media", + "files": [ + "image1.jpg", + "image2.png", + "image3.png" + ] + }, + { + "added": [ + "image2.png", + "image3.png", + "image4.png", + "image5.png", + "image6.jpg", + "image1.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme32/media", + "files": [ + "image2.png", + "image3.png", + "image4.png", + "image5.png", + "image6.jpg", + "image1.png" + ] + }, + { + "added": [ + "image1.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme33/media", + "files": [ + "image1.png" + ] + }, + { + "added": [ + "image1.jpg", + "image2.jpg", + "image3.jpg", + "image4.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme34/media", + "files": [ + "image1.jpg", + "image2.jpg", + "image3.jpg", + "image4.jpg" + ] + }, + { + "added": [ + "image3.jpg", + "image4.png", + "image7.jpg", + "image8.png", + "image1.jpg", + "image2.jpg", + "image5.png", + "image6.png", + "image9.png" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme35/media", + "files": [ + "image3.jpg", + "image4.png", + "image7.jpg", + "image8.png", + "image1.jpg", + "image2.jpg", + "image5.png", + "image6.png", + "image9.png" + ] + }, + { + "added": [ + "image1.jpg", + "image2.png", + "image3.png", + "image4.jpg", + "image5.jpg", + "image6.jpg", + "image7.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme36/media", + "files": [ + "image1.jpg", + "image2.png", + "image3.png", + "image4.jpg", + "image5.jpg", + "image6.jpg", + "image7.jpg" + ] + }, + { + "added": [ + "image1.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme5/media", + "files": [ + "image1.jpg" + ] + }, + { + "added": [ + "image1.jpg", + "image2.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme6/media", + "files": [ + "image1.jpg", + "image2.jpg" + ] + }, + { + "added": [ + "image1.jpg", + "image2.jpg", + "image3.jpg" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme8/media", + "files": [ + "image1.jpg", + "image2.jpg", + "image3.jpg" + ] + }, + { + "added": [ + "api.js" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/var/www/onlyoffice/documentserver/web-apps/apps/api/documents", + "files": [ + "api.js" + ] + } + ], + "diff_total": 2625, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "onlyoffice/documentserver:9.4.0", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-onlyoffice_onlyoffice_data", + "sample": [ + "wopi_private.key", + "wopi_public.key", + ".private/ds_release_date" + ], + "source": "/var/lib/docker/volumes/volgate-onlyoffice_onlyoffice_data/_data", + "target": "/var/www/onlyoffice/Data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 11, + "name": "volgate-onlyoffice_onlyoffice_logs", + "sample": [ + "documentserver-example/out.log", + "documentserver-example/err.log", + "documentserver/nginx.error.log", + "documentserver/docservice/out.log", + "documentserver/docservice/err.log", + "documentserver/converter/out.log", + "documentserver/converter/err.log", + "documentserver/metrics/out.log", + "documentserver/metrics/err.log", + "documentserver/example/out.log", + "documentserver/example/err.log" + ], + "source": "/var/lib/docker/volumes/volgate-onlyoffice_onlyoffice_logs/_data", + "target": "/var/log/onlyoffice", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "f3816f49ddcc45ef72e4822f8562806d27d1966e6d48d05e642899f9f7031333", + "sample": [], + "source": "/var/lib/docker/volumes/f3816f49ddcc45ef72e4822f8562806d27d1966e6d48d05e642899f9f7031333/_data", + "target": "/usr/share/fonts/truetype/custom", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "1e545d69dd476d2be20f4765e98291351087233dffae3f32fda919d788c0eb93", + "sample": [], + "source": "/var/lib/docker/volumes/1e545d69dd476d2be20f4765e98291351087233dffae3f32fda919d788c0eb93/_data", + "target": "/var/lib/onlyoffice", + "writable_by_app": "yes" + } + ], + "name": "onlyoffice", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "onlyoffice_data", + "onlyoffice_logs" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "JWT_SECRET", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon.svg', 'icon@1.25x.png', 'icon@1.5x.png', 'icon@1.75x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png', 'screen_3.png', 'screen_4.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon.svg', 'icon@1.25x.png', 'icon@1.5x.png', 'icon@1.75x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{38E022EA-AD92-45FC-B22B-49DF39746DB4}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['screenshots', 'icons']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon@2x.png', 'icon.png', 'icon.svg', 'icon@1.25x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{440EBF13-9B19-4BD8-8621-05200E58140B}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png', 'screen_3.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon.png', 'icon.svg', 'icon@1.25x.png', 'icon@1.5x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{7327FC95-16DA-41D9-9AF2-0E7F449F6800}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon.svg', 'icon@1.25x.png', 'icon@1.5x.png', 'icon@1.75x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_5.png', 'screen_6.png', 'screen_1.png', 'screen_2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon.png', 'icon.svg', 'icon@1.25x.png', 'icon@1.5x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9441}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_2.png', 'screen_3.png', 'screen_1.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon@1.25x.png', 'icon@1.5x.png', 'icon@1.75x.png', 'icon@2x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9840}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon.png', 'icon.svg', 'icon@1.25x.png', 'icon@1.5x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BE5CBF95-C0AD-4842-B157-AC40FEDD9841}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png', 'screen_3.png', 'screen_4.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon@1.75x.png', 'icon@2x.png', 'icon.png', 'icon.svg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{BFC5D5C6-89DE-4168-9565-ABD8D1E48711}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png', 'screen_3.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['screenshots', 'icons']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon@1.75x.png', 'icon@2x.png', 'icon.png', 'icon.svg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{D71C2EF0-F15B-47C7-80E9-86D671F9C595}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png', 'screen_3.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/store (path suggests state, no database signature \u2014 judgement needed): ['icons', 'screenshots']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/store/icons (path suggests state, no database signature \u2014 judgement needed): ['icon@1.75x.png', 'icon@2x.png', 'icon.png', 'icon.svg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{F30BDD79-23A0-4B05-8DE3-2AB77D03A1B4}/resources/store/screenshots (path suggests state, no database signature \u2014 judgement needed): ['screen_1.png', 'screen_2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/common/Images (path suggests state, no database signature \u2014 judgement needed): ['fonts_thumbnail_ea@1.5x.png', 'fonts_thumbnail.png', 'fonts_thumbnail_ea@1.25x.png.bin', 'themes_thumbnail@2.5x.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme12/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme13/media (path suggests state, no database signature \u2014 judgement needed): ['image2.png', 'image3.jpg', 'image4.png', 'image1.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme15/media (path suggests state, no database signature \u2014 judgement needed): ['image1.png', 'image2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme16/media (path suggests state, no database signature \u2014 judgement needed): ['image1.png', 'image2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme17/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme18/media (path suggests state, no database signature \u2014 judgement needed): ['image7.png', 'image8.png', 'image9.png', 'image1.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme19/media (path suggests state, no database signature \u2014 judgement needed): ['image3.png', 'image4.png', 'image5.png', 'image6.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme20/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme21/media (path suggests state, no database signature \u2014 judgement needed): ['image4.png', 'image1.jpg', 'image2.png', 'image3.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme24/media (path suggests state, no database signature \u2014 judgement needed): ['image7.png', 'image9.png', 'image1.png', 'image10.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme25/media (path suggests state, no database signature \u2014 judgement needed): ['image1.png', 'image2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme27/media (path suggests state, no database signature \u2014 judgement needed): ['image1.png', 'image2.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme29/media (path suggests state, no database signature \u2014 judgement needed): ['image2.png', 'image1.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme3/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.jpg', 'image3.png', 'image4.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme30/media (path suggests state, no database signature \u2014 judgement needed): ['image4.svg', 'image5.svg', 'image6.svg', 'image1.svg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme31/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.png', 'image3.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme32/media (path suggests state, no database signature \u2014 judgement needed): ['image2.png', 'image3.png', 'image4.png', 'image5.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme33/media (path suggests state, no database signature \u2014 judgement needed): ['image1.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme34/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme35/media (path suggests state, no database signature \u2014 judgement needed): ['image3.jpg', 'image4.png', 'image7.jpg', 'image8.png']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme36/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.png', 'image3.png', 'image4.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme5/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme6/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme8/media (path suggests state, no database signature \u2014 judgement needed): ['image1.jpg', 'image2.jpg', 'image3.jpg']", + "onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/web-apps/apps/api/documents (path suggests state, no database signature \u2014 judgement needed): ['api.js']" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/opengist/probe.json b/audits/persistence-sweep-2026-08-02/evidence/opengist/probe.json new file mode 100644 index 0000000..2d915f1 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/opengist/probe.json @@ -0,0 +1,103 @@ +{ + "probe": { + "app": "opengist", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "opengist" + ], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "opengist" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/etc", + "files": [ + "group-", + "passwd-" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/home", + "files": [ + "opengist" + ] + }, + { + "added": [ + ".gitconfig" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/home/opengist", + "files": [ + ".gitconfig" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 10, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "ghcr.io/thomiceli/opengist:1.13", + "mounts": [ + { + "class": "named-declared", + "files": 9, + "name": "volgate-opengist_opengist_data", + "sample": [ + "opengist-secret.key", + "opengist.db", + "sessions/session-encrypt.key", + "log/opengist.log", + "symlinks/opengist", + "symlinks/config.yml", + "opengist.index/index_meta.json", + "opengist.index/store/root.bolt", + "ssh/opengist-ed25519" + ], + "source": "/var/lib/docker/volumes/volgate-opengist_opengist_data/_data", + "target": "/opengist", + "writable_by_app": "yes" + } + ], + "name": "opengist", + "restarts": null, + "status": "running", + "uid": 1000 + } + ], + "declared_volumes": [ + "opengist_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/outline/probe.json b/audits/persistence-sweep-2026-08-02/evidence/outline/probe.json new file mode 100644 index 0000000..0cf5a9f --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/outline/probe.json @@ -0,0 +1,166 @@ +{ + "probe": { + "app": "outline", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1001, + "health": "healthy", + "image": "outlinewiki/outline:1.9.1", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-outline_outline_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-outline_outline_data/_data", + "target": "/var/lib/outline/data", + "writable_by_app": "yes" + } + ], + "name": "outline", + "restarts": null, + "status": "running", + "uid": 1001 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1655, + "name": "volgate-outline_outline_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-outline_outline_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "outline-postgres", + "restarts": null, + "status": "running", + "uid": 70 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-outline_outline_redis_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-outline_outline_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "outline-redis", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "outline_data", + "outline_postgres_data", + "outline_redis_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH", + "UTILS_SECRET" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "outline: declared volume /var/lib/outline/data is EMPTY", + "outline-redis: declared volume /data is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/paperless-ngx/probe.json b/audits/persistence-sweep-2026-08-02/evidence/paperless-ngx/probe.json new file mode 100644 index 0000000..e8d2af6 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/paperless-ngx/probe.json @@ -0,0 +1,250 @@ +{ + "probe": { + "app": "paperless-ngx", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1927, + "name": "volgate-paperlessngx_paperless_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-paperlessngx_paperless_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "paperless-postgres", + "restarts": null, + "status": "running", + "uid": 70 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-paperlessngx_paperless_redis_data", + "sample": [ + "appendonlydir/appendonly.aof.1.base.rdb", + "appendonlydir/appendonly.aof.1.incr.aof", + "appendonlydir/appendonly.aof.manifest" + ], + "source": "/var/lib/docker/volumes/volgate-paperlessngx_paperless_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "paperless-redis", + "restarts": null, + "status": "running", + "uid": 999 + }, + { + "diff_added": 265, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "usr", + "var", + "run" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "share" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/var", + "files": [ + "log", + "cache", + "lib" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/var/lib", + "files": [ + "apt", + "dpkg" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 294, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/paperless-ngx/paperless-ngx:2.20.15", + "mounts": [ + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/appdata/paperless/export", + "target": "/usr/src/paperless/export", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 9, + "name": "volgate-paperlessngx_paperless_data", + "sample": [ + "migration_lock", + ".index_version", + "celerybeat-schedule.db", + "index/_MAIN_1.toc", + "index/MAIN_WRITELOCK", + "log/paperless.log", + "log/celery.log", + "log/.__paperless.lock", + "log/.__celery.lock" + ], + "source": "/var/lib/docker/volumes/volgate-paperlessngx_paperless_data/_data", + "target": "/usr/src/paperless/data", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 1, + "name": null, + "sample": [ + "media.lock" + ], + "source": "/srv/felhom-gate/hdd/appdata/paperless/media", + "target": "/usr/src/paperless/media", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/import/paperless", + "target": "/usr/src/paperless/consume", + "writable_by_app": "yes" + } + ], + "name": "paperless-webserver", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "paperless_data", + "paperless_postgres_data", + "paperless_redis_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "PAPERLESS_ADMIN_PASSWORD", + "PAPERLESS_ADMIN_USER", + "PAPERLESS_OCR_LANGUAGE", + "PAPERLESS_SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/papra/probe.json b/audits/persistence-sweep-2026-08-02/evidence/papra/probe.json new file mode 100644 index 0000000..882f4a5 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/papra/probe.json @@ -0,0 +1,100 @@ +{ + "probe": { + "app": "papra", + "containers": [ + { + "diff_added": 173, + "diff_benign_db_touches": [], + "diff_data_dirs": [ + { + "added": [ + "db.sqlite" + ], + "changed_count": 0, + "db_signature": true, + "dir": "/app/app-data/db", + "files": [ + "db.sqlite" + ] + } + ], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [ + "data" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/app-data", + "files": [ + "db" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 177, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "ghcr.io/papra-hq/papra:26.6.1-rootless", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-papra_papra_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-papra_papra_data/_data", + "target": "/app/data", + "writable_by_app": "NO" + } + ], + "name": "papra", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "papra_data" + ], + "env_keys": [ + "AUTH_SECRET", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "papra: mount /app/data is NOT writable by the app's own uid=999 gid=999", + "papra: DATA in the writable layer at /app/app-data/db (db_signature=True, e.g. ['db.sqlite'])", + "NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are empty, yet entries were created in ['/app']. Needs adjudication.", + "papra: this container's mounts are all empty while it created entries in ['/app'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "papra: declared volume /app/data is EMPTY" + ], + "verdict": "BROKEN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/plant-it/probe.json b/audits/persistence-sweep-2026-08-02/evidence/plant-it/probe.json new file mode 100644 index 0000000..ea38150 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/plant-it/probe.json @@ -0,0 +1,11 @@ +{ + "probe": { + "app": "plant-it", + "error": "no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling \n Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\nError response from daemon: pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\n)" + }, + "reasons": [ + "no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling \n Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\nError response from daemon: pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\n)" + ], + "verdict": "UNDETERMINED", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/plex/probe.json b/audits/persistence-sweep-2026-08-02/evidence/plex/probe.json new file mode 100644 index 0000000..30a2e46 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/plex/probe.json @@ -0,0 +1,170 @@ +{ + "probe": { + "app": "plex", + "containers": [ + { + "diff_added": 63, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + ".firstRunComplete" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + ".firstRunComplete" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/etc", + "files": [ + "cont-init.d", + "services.d" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/etc/cont-init.d", + "files": [ + "45-plex-hw-transcode-and-connected-tuner", + "50-plex-update", + "40-plex-first-run" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/services.d", + "files": [ + "plex" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/etc/services.d/plex", + "files": [ + "finish", + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 74, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "plexinc/pms-docker:1.41.4.9463-630c9f557", + "mounts": [ + { + "class": "bind", + "files": 3, + "name": null, + "sample": [ + "books/metadata.db", + "books/metadata.db-shm", + "books/metadata.db-wal" + ], + "source": "/srv/felhom-gate/hdd/userdata/media", + "target": "/media", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 82, + "name": "volgate-plex_plex_config", + "sample": [ + "Library/Application Support/Plex Media Server/.LocalAdminToken", + "Library/Application Support/Plex Media Server/plexmediaserver.pid", + "Library/Application Support/Plex Media Server/Preferences.xml", + "Library/Application Support/Plex Media Server/Setup Plex.html", + "Library/Application Support/Plex Media Server/Cache/Flags.dat", + "Library/Application Support/Plex Media Server/Cache/UpdateChannels.dat", + "Library/Application Support/Plex Media Server/Cache/Privacy.dat", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.themoviedb/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.themoviedb/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.plexthememusic/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.plexthememusic/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.lastfm/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.lastfm/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.imdb/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.imdb/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.none/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.none/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.thetvdb/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.thetvdb/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.movieposterdb/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.movieposterdb/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/org.musicbrainz.agents.music/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/org.musicbrainz.agents.music/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.fanarttv/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.fanarttv/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.htbackdrops/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.htbackdrops/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.localmedia/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.localmedia/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.system/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.system/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.system/Dict", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.lyricfind/HTTPCookies", + "Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.lyricfind/StoredValues", + "Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db-shm", + "Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.blobs.db", + "Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db-wal", + "Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.blobs.db-shm", + "Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.blobs.db-wal", + "Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db" + ], + "source": "/var/lib/docker/volumes/volgate-plex_plex_config/_data", + "target": "/config", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-plex_plex_transcode", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-plex_plex_transcode/_data", + "target": "/transcode", + "writable_by_app": "yes" + } + ], + "name": "plex", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "plex_config", + "plex_transcode" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "PLEX_CLAIM", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "plex: declared volume /transcode is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/privatebin/probe.json b/audits/persistence-sweep-2026-08-02/evidence/privatebin/probe.json new file mode 100644 index 0000000..83da59d --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/privatebin/probe.json @@ -0,0 +1,96 @@ +{ + "probe": { + "app": "privatebin", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 82, + "health": "healthy", + "image": "privatebin/pdo:2.0.5", + "mounts": [ + { + "class": "anonymous", + "files": 0, + "name": "1588c34a1374a5d4ae265cb892ff0059e87ae07e11a14ac1e17879c54b7b1ce2", + "sample": [], + "source": "/var/lib/docker/volumes/1588c34a1374a5d4ae265cb892ff0059e87ae07e11a14ac1e17879c54b7b1ce2/_data", + "target": "/tmp", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "08447e377c7efaba21791de7813d6799c785ee1a7e72216608c72e01a5a40d69", + "sample": [], + "source": "/var/lib/docker/volumes/08447e377c7efaba21791de7813d6799c785ee1a7e72216608c72e01a5a40d69/_data", + "target": "/var/lib/nginx/tmp", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-privatebin_privatebin_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-privatebin_privatebin_data/_data", + "target": "/srv/data", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 14, + "name": "ebd9667e81ace0d084e3f07b25360ca806df2a98294ec192d6afd674b70a790b", + "sample": [ + "nginx.pid", + "php-fpm.sock", + "services/.s6-svscan/control", + "services/.s6-svscan/lock", + "services/php-fpm85/run", + "services/php-fpm85/supervise/control", + "services/php-fpm85/supervise/death_tally", + "services/php-fpm85/supervise/status", + "services/php-fpm85/supervise/lock", + "services/nginx/run", + "services/nginx/supervise/control", + "services/nginx/supervise/death_tally", + "services/nginx/supervise/status", + "services/nginx/supervise/lock" + ], + "source": "/var/lib/docker/volumes/ebd9667e81ace0d084e3f07b25360ca806df2a98294ec192d6afd674b70a790b/_data", + "target": "/run", + "writable_by_app": "yes" + } + ], + "name": "privatebin", + "restarts": null, + "status": "running", + "uid": 65534 + } + ], + "declared_volumes": [ + "privatebin_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "nothing was written to any mount and nothing data-classified in any writable layer \u2014 the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN", + "privatebin: declared volume /srv/data is EMPTY" + ], + "verdict": "UNDETERMINED", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/radarr/probe.json b/audits/persistence-sweep-2026-08-02/evidence/radarr/probe.json new file mode 100644 index 0000000..28b2044 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/radarr/probe.json @@ -0,0 +1,155 @@ +{ + "probe": { + "app": "radarr", + "containers": [ + { + "diff_added": 207, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "downloads" + ], + "changed_count": 5, + "db_signature": false, + "dir": "/", + "files": [ + "downloads" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/etc", + "files": [ + "group-", + "passwd-", + "s6-overlay", + "crontabs" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/crontabs", + "files": [ + "root" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay", + "files": [ + "s6-rc.d" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d", + "files": [ + "init-adduser" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d/init-adduser", + "files": [ + "branding" + ] + } + ], + "diff_token_dirs": [ + { + "added": [ + "movies" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/media", + "files": [ + "movies" + ] + } + ], + "diff_total": 222, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "lscr.io/linuxserver/radarr:6.3.0", + "mounts": [ + { + "class": "named-declared", + "files": 11, + "name": "volgate-radarr_radarr_config", + "sample": [ + "radarr.pid", + "logs.db-shm", + "logs.db", + "radarr.db", + "radarr.db-wal", + "radarr.db-shm", + "logs.db-wal", + "config.xml", + "logs/radarr.txt", + "logs/radarr.debug.txt", + "asp/key-08e6d4a9-fe58-4059-ae41-87f0c8d018e5.xml" + ], + "source": "/var/lib/docker/volumes/volgate-radarr_radarr_config/_data", + "target": "/config", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/movies", + "target": "/media/movies", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/downloads", + "target": "/downloads", + "writable_by_app": "yes" + } + ], + "name": "radarr", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "radarr_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "radarr: writable-layer writes at /media (path suggests state, no database signature \u2014 judgement needed): ['movies']" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/rallly/probe.json b/audits/persistence-sweep-2026-08-02/evidence/rallly/probe.json new file mode 100644 index 0000000..ad3fb16 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/rallly/probe.json @@ -0,0 +1,159 @@ +{ + "probe": { + "app": "rallly", + "containers": [ + { + "diff_added": 6, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "apps" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps", + "files": [ + "web" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps/web", + "files": [ + ".next" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 11, + "diff_unresolved": [], + "exit": 0, + "gid": 101, + "health": "healthy", + "image": "lukevella/rallly:4.11.1", + "mounts": [], + "name": "rallly", + "restarts": null, + "status": "running", + "uid": 100 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1580, + "name": "volgate-rallly_rallly_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-rallly_rallly_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "rallly-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "rallly_postgres_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_PASSWORD", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/recipe-importer/probe.json b/audits/persistence-sweep-2026-08-02/evidence/recipe-importer/probe.json new file mode 100644 index 0000000..c2e6e71 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/recipe-importer/probe.json @@ -0,0 +1,95 @@ +{ + "probe": { + "app": "recipe-importer", + "containers": [ + { + "diff_added": 13, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "local" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local", + "files": [ + "lib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 22, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "gitea.dooplex.hu/admin/recipe-importer:v0.9.11", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-recipeimporter_recipe_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-recipeimporter_recipe_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "recipe-importer", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "recipe_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. Needs adjudication.", + "recipe-importer: this container's mounts are all empty while it created entries in ['/'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "recipe-importer: declared volume /data is EMPTY" + ], + "verdict": "UNDETERMINED", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/romm/probe.json b/audits/persistence-sweep-2026-08-02/evidence/romm/probe.json new file mode 100644 index 0000000..b384cbc --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/romm/probe.json @@ -0,0 +1,248 @@ +{ + "probe": { + "app": "romm", + "containers": [ + { + "diff_added": 13, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "root", + "etc" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "nginx" + ] + }, + { + "added": [ + ".gunicorn" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".gunicorn" + ] + }, + { + "added": [ + "gunicorn.ctl" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gunicorn", + "files": [ + "gunicorn.ctl" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 19, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "rommapp/romm:5.0.0", + "mounts": [ + { + "class": "anonymous", + "files": 0, + "name": "1ef425b6ade46b1849fdb2b3f632ed8640a9cb2f684686c1a175028a50ee706a", + "sample": [], + "source": "/var/lib/docker/volumes/1ef425b6ade46b1849fdb2b3f632ed8640a9cb2f684686c1a175028a50ee706a/_data", + "target": "/redis-data", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "59a9958f0796e5ad06453b0ccf4b66018aa1897c68e3b646f3f469f221060752", + "sample": [], + "source": "/var/lib/docker/volumes/59a9958f0796e5ad06453b0ccf4b66018aa1897c68e3b646f3f469f221060752/_data", + "target": "/romm", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/roms", + "target": "/romm/library", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/appdata/romm/resources", + "target": "/romm/resources", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 1, + "name": "volgate-romm_romm_config", + "sample": [ + "config.yml" + ], + "source": "/var/lib/docker/volumes/volgate-romm_romm_config/_data", + "target": "/romm/config", + "writable_by_app": "yes" + } + ], + "name": "romm", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "mariadb:11.4", + "mounts": [ + { + "class": "named-declared", + "files": 271, + "name": "volgate-romm_romm_db_data", + "sample": [ + "undo002", + "tc.log", + "mariadb_upgrade_info", + "ibtmp1", + "aria_log.00000001", + "multi-master.info", + "ddl_recovery.log", + "ib_logfile0", + "undo003", + "ibdata1", + ".my-healthcheck.cnf", + "aria_log_control", + "undo001", + "ib_buffer_pool", + "sys/x@0024wait_classes_global_by_latency.frm", + "sys/processlist.frm", + "sys/session.frm", + "sys/memory_by_host_by_current_bytes.frm", + "sys/user_summary.frm", + "sys/waits_global_by_latency.frm", + "sys/x@0024statement_analysis.frm", + "sys/x@0024host_summary.frm", + "sys/schema_unused_indexes.frm", + "sys/user_summary_by_file_io_type.frm", + "sys/memory_by_thread_by_current_bytes.frm", + "sys/x@0024statements_with_full_table_scans.frm", + "sys/wait_classes_global_by_avg_latency.frm", + "sys/x@0024schema_table_statistics.frm", + "sys/x@0024statements_with_temp_tables.frm", + "sys/schema_auto_increment_columns.frm", + "sys/x@0024ps_schema_table_statistics_io.frm", + "sys/x@0024memory_by_host_by_current_bytes.frm", + "sys/statements_with_runtimes_in_95th_percentile.frm", + "sys/x@0024statements_with_errors_or_warnings.frm", + "sys/waits_by_user_by_latency.frm", + "sys/x@0024io_global_by_file_by_bytes.frm", + "sys/innodb_buffer_stats_by_table.frm", + "sys/user_summary_by_statement_type.frm", + "sys/statements_with_errors_or_warnings.frm", + "sys/sys_config.MAD" + ], + "source": "/var/lib/docker/volumes/volgate-romm_romm_db_data/_data", + "target": "/var/lib/mysql", + "writable_by_app": "yes" + } + ], + "name": "romm-db", + "restarts": null, + "status": "running", + "uid": 999 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 4, + "name": "volgate-romm_romm_redis_data", + "sample": [ + "dump.rdb", + "appendonlydir/appendonly.aof.1.base.rdb", + "appendonlydir/appendonly.aof.1.incr.aof", + "appendonlydir/appendonly.aof.manifest" + ], + "source": "/var/lib/docker/volumes/volgate-romm_romm_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "romm-redis", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "romm_config", + "romm_db_data", + "romm_redis_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "MYSQL_ROOT_PASSWORD", + "ROMM_AUTH_SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/seerr/probe.json b/audits/persistence-sweep-2026-08-02/evidence/seerr/probe.json new file mode 100644 index 0000000..0c467cc --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/seerr/probe.json @@ -0,0 +1,63 @@ +{ + "probe": { + "app": "seerr", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "fallenbagel/jellyseerr:2.7.3", + "mounts": [ + { + "class": "named-declared", + "files": 11, + "name": "volgate-seerr_seerr_config", + "sample": [ + "settings.json", + "DOCKER", + "db/db.sqlite3-wal", + "db/db.sqlite3", + "db/db.sqlite3-shm", + "logs/.machinelogs.json", + "logs/jellyseerr.log", + "logs/.20136e5b8544ec13f7fc29ce3d35150d597108bb-audit.json", + "logs/jellyseerr-2026-08-02.log", + "logs/.machinelogs-2026-08-02.json", + "logs/.4295fcfc416647ffc3751a1d488b45a096cf2d4f-audit.json" + ], + "source": "/var/lib/docker/volumes/volgate-seerr_seerr_config/_data", + "target": "/app/config", + "writable_by_app": "yes" + } + ], + "name": "seerr", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "seerr_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/sonarr/probe.json b/audits/persistence-sweep-2026-08-02/evidence/sonarr/probe.json new file mode 100644 index 0000000..0c4d063 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/sonarr/probe.json @@ -0,0 +1,155 @@ +{ + "probe": { + "app": "sonarr", + "containers": [ + { + "diff_added": 209, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "downloads" + ], + "changed_count": 5, + "db_signature": false, + "dir": "/", + "files": [ + "downloads" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/etc", + "files": [ + "group-", + "s6-overlay", + "passwd-", + "crontabs" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/crontabs", + "files": [ + "root" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay", + "files": [ + "s6-rc.d" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d", + "files": [ + "init-adduser" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d/init-adduser", + "files": [ + "branding" + ] + } + ], + "diff_token_dirs": [ + { + "added": [ + "tv" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/media", + "files": [ + "tv" + ] + } + ], + "diff_total": 224, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "lscr.io/linuxserver/sonarr:4.0.19", + "mounts": [ + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/tv", + "target": "/media/tv", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/downloads", + "target": "/downloads", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 11, + "name": "volgate-sonarr_sonarr_config", + "sample": [ + "logs.db-shm", + "sonarr.db", + "logs.db", + "sonarr.pid", + "logs.db-wal", + "sonarr.db-wal", + "sonarr.db-shm", + "config.xml", + "logs/sonarr.txt", + "logs/sonarr.debug.txt", + "asp/key-4d541e64-de4d-421d-a8f7-8c05ed629e0c.xml" + ], + "source": "/var/lib/docker/volumes/volgate-sonarr_sonarr_config/_data", + "target": "/config", + "writable_by_app": "yes" + } + ], + "name": "sonarr", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "sonarr_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "sonarr: writable-layer writes at /media (path suggests state, no database signature \u2014 judgement needed): ['tv']" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/sparkyfitness/probe.json b/audits/persistence-sweep-2026-08-02/evidence/sparkyfitness/probe.json new file mode 100644 index 0000000..d4f448f --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/sparkyfitness/probe.json @@ -0,0 +1,231 @@ +{ + "probe": { + "app": "sparkyfitness", + "containers": [ + { + "diff_added": 11, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "run", + "var" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "nginx" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var", + "files": [ + "cache" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 19, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "codewithcj/sparkyfitness:v0.17.3", + "mounts": [], + "name": "sparkyfitness", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:15-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1815, + "name": "volgate-sparkyfitness_sparkyfitness_db_data", + "sample": [ + "pgdata/postmaster.opts", + "pgdata/postgresql.conf", + "pgdata/postgresql.auto.conf", + "pgdata/pg_ident.conf", + "pgdata/pg_hba.conf", + "pgdata/postmaster.pid", + "pgdata/PG_VERSION", + "pgdata/base/1/826", + "pgdata/base/1/2996", + "pgdata/base/1/3764", + "pgdata/base/1/2610_fsm", + "pgdata/base/1/3603_fsm", + "pgdata/base/1/1249_vm", + "pgdata/base/1/3600_fsm", + "pgdata/base/1/13506_vm", + "pgdata/base/1/3602", + "pgdata/base/1/6228", + "pgdata/base/1/4174", + "pgdata/base/1/4157", + "pgdata/base/1/4146", + "pgdata/base/1/2611", + "pgdata/base/1/2657", + "pgdata/base/1/3079_vm", + "pgdata/base/1/1247_vm", + "pgdata/base/1/13501_fsm", + "pgdata/base/1/1259", + "pgdata/base/1/3601_fsm", + "pgdata/base/1/1417", + "pgdata/base/1/4155", + "pgdata/base/1/2668", + "pgdata/base/1/13491_fsm", + "pgdata/base/1/2831", + "pgdata/base/1/2841", + "pgdata/base/1/3607", + "pgdata/base/1/2836", + "pgdata/base/1/1255_fsm", + "pgdata/base/1/2615_vm", + "pgdata/base/1/3575", + "pgdata/base/1/2605_vm", + "pgdata/base/1/2670" + ], + "source": "/var/lib/docker/volumes/volgate-sparkyfitness_sparkyfitness_db_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "sparkyfitness-db", + "restarts": null, + "status": "running", + "uid": 70 + }, + { + "diff_added": 392, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "SparkyFitnessServer" + ] + }, + { + "added": [ + "backup", + "temp_uploads", + "uploads" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/SparkyFitnessServer", + "files": [ + "backup", + "temp_uploads", + "uploads" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 395, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "codewithcj/sparkyfitness_server:v0.17.3", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-sparkyfitness_sparkyfitness_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-sparkyfitness_sparkyfitness_uploads/_data", + "target": "/app/SparkyFitnessServer/uploads", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-sparkyfitness_sparkyfitness_backup", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-sparkyfitness_sparkyfitness_backup/_data", + "target": "/app/SparkyFitnessServer/backup", + "writable_by_app": "yes" + } + ], + "name": "sparkyfitness-server", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "sparkyfitness_backup", + "sparkyfitness_db_data", + "sparkyfitness_uploads" + ], + "env_keys": [ + "API_ENCRYPTION_KEY", + "APP_DB_PASSWORD", + "BETTER_AUTH_SECRET", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "sparkyfitness-server: this container's mounts are all empty while it created entries in ['/app/SparkyFitnessServer'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "sparkyfitness-server: declared volume /app/SparkyFitnessServer/uploads is EMPTY", + "sparkyfitness-server: declared volume /app/SparkyFitnessServer/backup is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/tandoor/probe.json b/audits/persistence-sweep-2026-08-02/evidence/tandoor/probe.json new file mode 100644 index 0000000..a1cd16e --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/tandoor/probe.json @@ -0,0 +1,263 @@ +{ + "probe": { + "app": "tandoor", + "containers": [ + { + "diff_added": 527, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "opt", + "usr" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/opt", + "files": [ + "recipes" + ] + }, + { + "added": [ + "mediafiles", + "staticfiles" + ], + "changed_count": 3, + "db_signature": false, + "dir": "/opt/recipes", + "files": [ + "mediafiles", + "staticfiles" + ] + }, + { + "added": [], + "changed_count": 7, + "db_signature": false, + "dir": "/opt/recipes/cookbook", + "files": [ + "views", + "migrations", + "templatetags", + "integration", + "provider", + "connectors", + "helper" + ] + }, + { + "added": [ + "Recipes.conf" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/opt/recipes/http.d", + "files": [ + "Recipes.conf" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "local" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local", + "files": [ + "lib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 576, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/tandoorrecipes/recipes:2.6.13", + "mounts": [ + { + "class": "named-declared", + "files": 3047, + "name": "volgate-tandoor_tandoor_static", + "sample": [ + "staticfiles.json", + "vue3/service-worker.js.gz", + "vue3/registerSW.js.gz", + "vue3/service-worker.js", + "vue3/manifest.484f96e63b3b.webmanifest", + "vue3/manifest.484f96e63b3b.webmanifest.gz", + "vue3/manifest.77a5899585d8.json.gz", + "vue3/service-worker.e1a4252df0ae.js.gz", + "vue3/registerSW.be6aad568a9d.js", + "vue3/registerSW.js", + "vue3/manifest.webmanifest", + "vue3/manifest.webmanifest.gz", + "vue3/service-worker.e1a4252df0ae.js", + "vue3/registerSW.be6aad568a9d.js.gz", + "vue3/manifest.json.gz", + "vue3/manifest.77a5899585d8.json", + "vue3/manifest.json", + "vue3/assets/PantryPage-ClGNwpHL.80923c1ff539.js.gz", + "vue3/assets/DatabaseLinkCol-d8pnKjbf.js.gz", + "vue3/assets/fontello-BxS_W-ym.86b6414dad9e.svg.gz", + "vue3/assets/VFileUpload-7CWUohzn.d182bb8e4f03.css", + "vue3/assets/fa-regular-400-DZaxPHgR.ttf.gz", + "vue3/assets/number_utils-DFmVVcK0.js.gz", + "vue3/assets/ja-Cqm3qbrP.d617fa524f97.js", + "vue3/assets/ModelListPage-xoBv_A6i.4681a991ae27.js", + "vue3/assets/et-zY9CrDMt.1761c6eb5f48.js", + "vue3/assets/fa-regular-400-BjRzuEpd.89672701a587.woff2", + "vue3/assets/ShoppingListView-C0_akhap.js", + "vue3/assets/VOverlay-CKBxglwy.b98729efffd0.css.gz", + "vue3/assets/ClosableHelpAlert-BoN8dDrm.js.gz", + "vue3/assets/VColorPicker-DoVATq5z.css.gz", + "vue3/assets/ja-Cqm3qbrP.js", + "vue3/assets/position-D7bmUCsu.f212789ebec5.css", + "vue3/assets/number_utils-Brwft-8t.css", + "vue3/assets/SpaceSettings-BPOt7vl3.ab2d8e61f937.js.gz", + "vue3/assets/InviteLinkEditor-BZ7NMcrL.19f59c5d2292.js.gz", + "vue3/assets/hu-DlZ4REsp.c1bc941c31dc.js.gz", + "vue3/assets/fileFilter-JaX38xV6.js.gz", + "vue3/assets/VBtn-rFOjfrXe.df1b89399b11.css", + "vue3/assets/VDataTableServer-BYvhcs5d.js.gz" + ], + "source": "/var/lib/docker/volumes/volgate-tandoor_tandoor_static/_data", + "target": "/opt/recipes/staticfiles", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-tandoor_tandoor_media", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-tandoor_tandoor_media/_data", + "target": "/opt/recipes/mediafiles", + "writable_by_app": "yes" + } + ], + "name": "tandoor", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1984, + "name": "volgate-tandoor_tandoor_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-tandoor_tandoor_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "tandoor-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "tandoor_media", + "tandoor_postgres_data", + "tandoor_static" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "tandoor: declared volume /opt/recipes/mediafiles is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/termix/probe.json b/audits/persistence-sweep-2026-08-02/evidence/termix/probe.json new file mode 100644 index 0000000..47624ad --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/termix/probe.json @@ -0,0 +1,94 @@ +{ + "probe": { + "app": "termix", + "containers": [ + { + "diff_added": 9, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "html" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/html", + "files": [ + "sw.js" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "group-" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 17, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "ghcr.io/lukegus/termix:2.5.0", + "mounts": [ + { + "class": "named-declared", + "files": 4, + "name": "volgate-termix_termix_data", + "sample": [ + "db.sqlite.encrypted", + ".env", + "opkssh/version.txt", + "opkssh/opkssh-linux-amd64" + ], + "source": "/var/lib/docker/volumes/volgate-termix_termix_data/_data", + "target": "/app/data", + "writable_by_app": "yes" + } + ], + "name": "termix", + "restarts": null, + "status": "running", + "uid": 1000 + } + ], + "declared_volumes": [ + "termix_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/uptime-kuma/probe.json b/audits/persistence-sweep-2026-08-02/evidence/uptime-kuma/probe.json new file mode 100644 index 0000000..03e1122 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/uptime-kuma/probe.json @@ -0,0 +1,54 @@ +{ + "probe": { + "app": "uptime-kuma", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "louislam/uptime-kuma:2.4.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-uptimekuma_uptime_kuma_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-uptimekuma_uptime_kuma_data/_data", + "target": "/app/data", + "writable_by_app": "yes" + } + ], + "name": "uptime-kuma", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "uptime_kuma_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "nothing was written to any mount and nothing data-classified in any writable layer \u2014 the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN", + "uptime-kuma: declared volume /app/data is EMPTY" + ], + "verdict": "UNDETERMINED", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/vaultwarden/probe.json b/audits/persistence-sweep-2026-08-02/evidence/vaultwarden/probe.json new file mode 100644 index 0000000..19e0c86 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/vaultwarden/probe.json @@ -0,0 +1,58 @@ +{ + "probe": { + "app": "vaultwarden", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "vaultwarden/server:1.36.0-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 4, + "name": "volgate-vaultwarden_vaultwarden_data", + "sample": [ + "rsa_key.pem", + "db.sqlite3-wal", + "db.sqlite3", + "db.sqlite3-shm" + ], + "source": "/var/lib/docker/volumes/volgate-vaultwarden_vaultwarden_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "vaultwarden", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "vaultwarden_data" + ], + "env_keys": [ + "ADMIN_TOKEN", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SIGNUPS_ALLOWED", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/vikunja/probe.json b/audits/persistence-sweep-2026-08-02/evidence/vikunja/probe.json new file mode 100644 index 0000000..2b51990 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/vikunja/probe.json @@ -0,0 +1,100 @@ +{ + "probe": { + "app": "vikunja", + "containers": [ + { + "diff_added": 4, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "db" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "db" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "vikunja" + ] + }, + { + "added": [ + "files" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/vikunja", + "files": [ + "files" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 6, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": null, + "image": "vikunja/vikunja:2.3.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-vikunja_vikunja_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-vikunja_vikunja_data/_data", + "target": "/app/vikunja/files", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 3, + "name": "volgate-vikunja_vikunja_db", + "sample": [ + "vikunja.db-shm", + "vikunja.db-wal", + "vikunja.db" + ], + "source": "/var/lib/docker/volumes/volgate-vikunja_vikunja_db/_data", + "target": "/db", + "writable_by_app": "yes" + } + ], + "name": "vikunja", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "vikunja_data", + "vikunja_db" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH", + "VIKUNJA_SERVICE_JWTSECRET" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "vikunja: declared volume /app/vikunja/files is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/wanderer/probe.json b/audits/persistence-sweep-2026-08-02/evidence/wanderer/probe.json new file mode 100644 index 0000000..dbc27eb --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/wanderer/probe.json @@ -0,0 +1,194 @@ +{ + "probe": { + "app": "wanderer", + "containers": [ + { + "diff_added": 6, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "root" + ] + }, + { + "added": [ + ".npm" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".npm" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 8, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "unhealthy", + "image": "flomp/wanderer-web:v0.20.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-wanderer_wanderer_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_uploads/_data", + "target": "/app/uploads", + "writable_by_app": "yes" + } + ], + "name": "wanderer", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 3, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "pb_data", + "data" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/", + "files": [ + "pb_data", + "data" + ] + } + ], + "diff_token_dirs": [ + { + "added": [ + "plugins" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/data", + "files": [ + "plugins" + ] + } + ], + "diff_total": 3, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "flomp/wanderer-db:v0.20.0", + "mounts": [ + { + "class": "named-declared", + "files": 6, + "name": "volgate-wanderer_wanderer_data", + "sample": [ + "auxiliary.db-wal", + "data.db", + "data.db-wal", + "auxiliary.db", + "auxiliary.db-shm", + "data.db-shm" + ], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_data/_data", + "target": "/pb_data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-wanderer_wanderer_plugins", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_plugins/_data", + "target": "/data/plugins", + "writable_by_app": "yes" + } + ], + "name": "wanderer-db", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "getmeili/meilisearch:v1.36.0", + "mounts": [ + { + "class": "named-declared", + "files": 11, + "name": "volgate-wanderer_wanderer_meili_data", + "sample": [ + "data.ms/VERSION", + "data.ms/indexes/a0fe8809-d547-4e9f-9b13-f5962d76ee3f/data.mdb", + "data.ms/indexes/a0fe8809-d547-4e9f-9b13-f5962d76ee3f/lock.mdb", + "data.ms/indexes/45aacc63-1820-432b-9eb5-027bb1f94f5f/data.mdb", + "data.ms/indexes/45aacc63-1820-432b-9eb5-027bb1f94f5f/lock.mdb", + "data.ms/indexes/a982b080-60f2-4811-a993-0e235ea2a5b9/data.mdb", + "data.ms/indexes/a982b080-60f2-4811-a993-0e235ea2a5b9/lock.mdb", + "data.ms/auth/data.mdb", + "data.ms/auth/lock.mdb", + "data.ms/tasks/data.mdb", + "data.ms/tasks/lock.mdb" + ], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_meili_data/_data", + "target": "/meili_data", + "writable_by_app": "yes" + } + ], + "name": "wanderer-search", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "wanderer_data", + "wanderer_meili_data", + "wanderer_plugins", + "wanderer_uploads" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "MEILI_MASTER_KEY", + "POCKETBASE_ENCRYPTION_KEY", + "SUBDOMAIN", + "SUBDOMAIN_DB", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "wanderer: unhealthy", + "wanderer: this container's mounts are all empty while it created entries in ['/root'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "wanderer: declared volume /app/uploads is EMPTY", + "wanderer-db: declared volume /data/plugins is EMPTY", + "wanderer-db: writable-layer writes at /data (path suggests state, no database signature \u2014 judgement needed): ['plugins']" + ], + "verdict": "UNDETERMINED", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/wger/probe.json b/audits/persistence-sweep-2026-08-02/evidence/wger/probe.json new file mode 100644 index 0000000..e6d598e --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/wger/probe.json @@ -0,0 +1,66 @@ +{ + "probe": { + "app": "wger", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "wger/server:2.6", + "mounts": [ + { + "class": "named-declared", + "files": 1, + "name": "volgate-wger_wger_data", + "sample": [ + "database.sqlite" + ], + "source": "/var/lib/docker/volumes/volgate-wger_wger_data/_data", + "target": "/home/wger/db", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-wger_wger_media", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wger_wger_media/_data", + "target": "/home/wger/media", + "writable_by_app": "yes" + } + ], + "name": "wger", + "restarts": null, + "status": "running", + "uid": 1000 + } + ], + "declared_volumes": [ + "wger_data", + "wger_media" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "wger: declared volume /home/wger/media is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/wishlist/probe.json b/audits/persistence-sweep-2026-08-02/evidence/wishlist/probe.json new file mode 100644 index 0000000..e0cc111 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/wishlist/probe.json @@ -0,0 +1,163 @@ +{ + "probe": { + "app": "wishlist", + "containers": [ + { + "diff_added": 35, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "data" + ] + }, + { + "added": [ + ".config", + ".local" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config", + ".local" + ] + }, + { + "added": [ + "caddy" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config", + "files": [ + "caddy" + ] + }, + { + "added": [ + "autosave.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config/caddy", + "files": [ + "autosave.json" + ] + }, + { + "added": [ + "share" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.local", + "files": [ + "share" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "src" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/src", + "files": [ + "app" + ] + }, + { + "added": [ + "data", + "uploads" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/usr/src/app", + "files": [ + "data", + "uploads" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 45, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/cmintey/wishlist:v0.66.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-wishlist_wishlist_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wishlist_wishlist_data/_data", + "target": "/data", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "bef31c91bf46c66ba902cc07e89fbd3032b67fd68264bd04afcb1f3ca7cce0c9", + "sample": [], + "source": "/var/lib/docker/volumes/bef31c91bf46c66ba902cc07e89fbd3032b67fd68264bd04afcb1f3ca7cce0c9/_data", + "target": "/usr/src/app/uploads", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 1, + "name": "782ab526684eaca336b6bfc3226ada078e567bce907a5e17b0782b35e38f016e", + "sample": [ + "prod.db" + ], + "source": "/var/lib/docker/volumes/782ab526684eaca336b6bfc3226ada078e567bce907a5e17b0782b35e38f016e/_data", + "target": "/usr/src/app/data", + "writable_by_app": "yes" + } + ], + "name": "wishlist", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "wishlist_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "wishlist: 1 file(s) in an ANONYMOUS volume at /usr/src/app/data \u2014 not in the compose `volumes:` block, so it is never backed up and a redeploy orphans it", + "wishlist: declared volume /data is EMPTY" + ], + "verdict": "BROKEN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/evidence/zipline/probe.json b/audits/persistence-sweep-2026-08-02/evidence/zipline/probe.json new file mode 100644 index 0000000..0d4fe36 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/evidence/zipline/probe.json @@ -0,0 +1,170 @@ +{ + "probe": { + "app": "zipline", + "containers": [ + { + "diff_added": 3, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "zipline" + ] + }, + { + "added": [ + "uploads", + "public" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/zipline", + "files": [ + "uploads", + "public" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 5, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/diced/zipline:4.6.1", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-zipline_zipline_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-zipline_zipline_uploads/_data", + "target": "/zipline/uploads", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-zipline_zipline_public", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-zipline_zipline_public/_data", + "target": "/zipline/public", + "writable_by_app": "yes" + } + ], + "name": "zipline", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1357, + "name": "volgate-zipline_zipline_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/pg_internal.init", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106" + ], + "source": "/var/lib/docker/volumes/volgate-zipline_zipline_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "zipline-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "zipline_postgres_data", + "zipline_public", + "zipline_uploads" + ], + "env_keys": [ + "CORE_SECRET", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "zipline: this container's mounts are all empty while it created entries in ['/zipline'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "zipline: declared volume /zipline/uploads is EMPTY", + "zipline: declared volume /zipline/public is EMPTY" + ], + "verdict": "CLEAN", + "verdict_recomputed_by": "check-volume-persistence.py classify() at final rules" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/papra-fix-proof.json b/audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/papra-fix-proof.json new file mode 100644 index 0000000..a6b5b00 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/papra-fix-proof.json @@ -0,0 +1,84 @@ +{ + "current": { + "after": { + "/app/app-data/db/db.sqlite": { + "bytes": 475136, + "present": true, + "sha256": "5d179bf1c624330a826164486a13dc2930222aac8b2ed729250f7ae2a51a058b" + }, + "/app/data": { + "dir": true, + "files": [], + "present": true + }, + "documents": [] + }, + "before": { + "/app/app-data/db/db.sqlite": { + "bytes": 475136, + "present": true, + "sha256": "1e9b695a7aef6d7602b300f7a02bc2074bc2dd9d04bec4b0c561cc2e3a5ff260" + }, + "/app/data": { + "dir": true, + "files": [], + "present": true + }, + "documents": [] + }, + "db_identical_after_redeploy": false, + "leg": "current", + "log": [ + "sign-up -> HTTP/1.1 200 OK", + "organizations -> no id in {\"error\":{\"message\":\"Unauthorized\",\"code\":\"auth.unauthorized\"}}", + "[before] {\"/app/app-data/db/db.sqlite\": {\"present\": true, \"bytes\": 475136, \"sha256\": \"1e9b695a7aef6d7602b300f7a02bc2074bc2dd9d04bec4b0c561cc2e3a5ff260\"}, \"/app/data\": {\"present\": true, \"dir\": true, \"files\": []}, \"documents\": []}", + "[after] {\"/app/app-data/db/db.sqlite\": {\"present\": true, \"bytes\": 475136, \"sha256\": \"5d179bf1c624330a826164486a13dc2930222aac8b2ed729250f7ae2a51a058b\"}, \"/app/data\": {\"present\": true, \"dir\": true, \"files\": []}, \"documents\": []}" + ], + "seeded": { + "account": "HTTP/1.1 200 OK", + "document_uploaded": false + }, + "uploaded_document_present_after": false, + "uploaded_document_present_before": false, + "verdict": "INCONCLUSIVE - the document was never uploaded" + }, + "fixed": { + "after": { + "/app/app-data/db/db.sqlite": { + "bytes": 475136, + "present": true, + "sha256": "9dd5cf37d7b9a5dd65653eabfb281f6804b2a05a28ef3040fa7db1e8d874da23" + }, + "/app/data": { + "present": false + }, + "documents": [] + }, + "before": { + "/app/app-data/db/db.sqlite": { + "bytes": 475136, + "present": true, + "sha256": "9dd5cf37d7b9a5dd65653eabfb281f6804b2a05a28ef3040fa7db1e8d874da23" + }, + "/app/data": { + "present": false + }, + "documents": [] + }, + "db_identical_after_redeploy": true, + "leg": "fixed", + "log": [ + "sign-up -> HTTP/1.1 200 OK", + "organizations -> no id in {\"error\":{\"message\":\"Unauthorized\",\"code\":\"auth.unauthorized\"}}", + "[before] {\"/app/app-data/db/db.sqlite\": {\"present\": true, \"bytes\": 475136, \"sha256\": \"9dd5cf37d7b9a5dd65653eabfb281f6804b2a05a28ef3040fa7db1e8d874da23\"}, \"/app/data\": {\"present\": false}, \"documents\": []}", + "[after] {\"/app/app-data/db/db.sqlite\": {\"present\": true, \"bytes\": 475136, \"sha256\": \"9dd5cf37d7b9a5dd65653eabfb281f6804b2a05a28ef3040fa7db1e8d874da23\"}, \"/app/data\": {\"present\": false}, \"documents\": []}" + ], + "seeded": { + "account": "HTTP/1.1 200 OK", + "document_uploaded": false + }, + "uploaded_document_present_after": false, + "uploaded_document_present_before": false, + "verdict": "INCONCLUSIVE - the document was never uploaded" + } +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/template.diff b/audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/template.diff new file mode 100644 index 0000000..2fe42b1 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/_papra-fix-proof/template.diff @@ -0,0 +1,4 @@ +--- current ++++ fixed +- - papra_data:/app/data ++ - papra_data:/app/app-data diff --git a/audits/persistence-sweep-2026-08-02/proofs/fixed-check/gramps-web/probe.json b/audits/persistence-sweep-2026-08-02/proofs/fixed-check/gramps-web/probe.json new file mode 100644 index 0000000..b2af783 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/fixed-check/gramps-web/probe.json @@ -0,0 +1,255 @@ +{ + "probe": { + "app": "gramps-web", + "containers": [ + { + "diff_added": 121, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "usr", + "app", + "root" + ] + }, + { + "added": [ + "secret" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "secret" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/src", + "files": [ + "gramps_webapi", + "alembic_users" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/src/alembic_users", + "files": [ + "versions" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/src/gramps_webapi", + "files": [ + "api", + "auth", + "util" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/src/gramps_webapi/api", + "files": [ + "search", + "resources" + ] + }, + { + "added": [ + ".gramps" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/root", + "files": [ + ".gramps" + ] + }, + { + "added": [ + "grampsdb" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gramps", + "files": [ + "grampsdb" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps", + "files": [ + "gramps60" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps/gramps60", + "files": [ + "plugins" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps/gramps60/plugins", + "files": [ + "FilterRules" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "lib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 143, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/gramps-project/grampsweb:v25.6.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_media", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_media/_data", + "target": "/app/media", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 1, + "name": "volgate-grampsweb_gramps_thumb_cache", + "sample": [ + "2029240f6d1128be89ddc32729463129" + ], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_thumb_cache/_data", + "target": "/app/thumbnail_cache", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 1, + "name": "volgate-grampsweb_gramps_cache", + "sample": [ + "request_cache/2029240f6d1128be89ddc32729463129" + ], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_cache/_data", + "target": "/app/cache", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_secret", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_secret/_data", + "target": "/app/secret", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_tmp", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_tmp/_data", + "target": "/tmp", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 1, + "name": "volgate-grampsweb_gramps_users", + "sample": [ + "users.sqlite" + ], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_users/_data", + "target": "/app/users", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 2, + "name": "volgate-grampsweb_gramps_db", + "sample": [ + "10a64f2c-8485-4514-a23b-81c02a1fbd81/name.txt", + "10a64f2c-8485-4514-a23b-81c02a1fbd81/database.txt" + ], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_db/_data", + "target": "/root/.gramps/grampsdb", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_index", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_index/_data", + "target": "/app/indexdir", + "writable_by_app": "yes" + } + ], + "name": "gramps-web", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "gramps_cache", + "gramps_db", + "gramps_index", + "gramps_media", + "gramps_secret", + "gramps_thumb_cache", + "gramps_tmp", + "gramps_users" + ], + "env_keys": [ + "DOMAIN", + "GRAMPSWEB_SECRET_KEY", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "gramps-web: declared volume /app/media is EMPTY", + "gramps-web: declared volume /app/secret is EMPTY", + "gramps-web: declared volume /app/indexdir is EMPTY" + ], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/proofs/fixed-check/wishlist/probe.json b/audits/persistence-sweep-2026-08-02/proofs/fixed-check/wishlist/probe.json new file mode 100644 index 0000000..527b85a --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/fixed-check/wishlist/probe.json @@ -0,0 +1,152 @@ +{ + "probe": { + "app": "wishlist", + "containers": [ + { + "diff_added": 34, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "root", + "usr" + ] + }, + { + "added": [ + ".config", + ".local" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config", + ".local" + ] + }, + { + "added": [ + "caddy" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config", + "files": [ + "caddy" + ] + }, + { + "added": [ + "autosave.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config/caddy", + "files": [ + "autosave.json" + ] + }, + { + "added": [ + "share" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.local", + "files": [ + "share" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "src" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/src", + "files": [ + "app" + ] + }, + { + "added": [ + "uploads", + "data" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/usr/src/app", + "files": [ + "uploads", + "data" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 44, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/cmintey/wishlist:v0.66.0", + "mounts": [ + { + "class": "named-declared", + "files": 1, + "name": "volgate-wishlist_wishlist_data", + "sample": [ + "prod.db" + ], + "source": "/var/lib/docker/volumes/volgate-wishlist_wishlist_data/_data", + "target": "/usr/src/app/data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-wishlist_wishlist_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wishlist_wishlist_uploads/_data", + "target": "/usr/src/app/uploads", + "writable_by_app": "yes" + } + ], + "name": "wishlist", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "wishlist_data", + "wishlist_uploads" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "wishlist: declared volume /usr/src/app/uploads is EMPTY" + ], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/proofs/gramps-web/survive-gramps-web.json b/audits/persistence-sweep-2026-08-02/proofs/gramps-web/survive-gramps-web.json new file mode 100644 index 0000000..1e33442 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/gramps-web/survive-gramps-web.json @@ -0,0 +1,77 @@ +{ + "app": "gramps-web", + "changed_in_place": [ + "/app/cache::request_cache/2029240f6d1128be89ddc32729463129" + ], + "files_after": 5, + "files_before": 5, + "label": "gramps-web", + "lost": [], + "mounts": [ + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_secret", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_secret/_data", + "target": "/app/secret" + }, + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_tmp", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_tmp/_data", + "target": "/tmp" + }, + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_users", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_users/_data", + "target": "/app/users" + }, + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_db", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_db/_data", + "target": "/root/.gramps/grampsdb" + }, + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_index", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_index/_data", + "target": "/app/indexdir" + }, + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_media", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_media/_data", + "target": "/app/media" + }, + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_thumb_cache", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_thumb_cache/_data", + "target": "/app/thumbnail_cache" + }, + { + "class": "named-declared", + "ctr": "gramps-web", + "name": "svgrampsweb_gramps_cache", + "source": "/var/lib/docker/volumes/svgrampsweb_gramps_cache/_data", + "target": "/app/cache" + } + ], + "survived": [ + "/app/thumbnail_cache::2029240f6d1128be89ddc32729463129", + "/app/users::users.sqlite", + "/root/.gramps/grampsdb::034a955e-563e-4cbc-a5bd-6f1bf63e1c5f/database.txt", + "/root/.gramps/grampsdb::034a955e-563e-4cbc-a5bd-6f1bf63e1c5f/name.txt" + ], + "up1_rc": 0, + "up2_rc": 0, + "verdict": "SURVIVES" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/proofs/survive.log b/audits/persistence-sweep-2026-08-02/proofs/survive.log new file mode 100644 index 0000000..8ef1314 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/survive.log @@ -0,0 +1,2 @@ +gramps-web SURVIVES before=5 after=5 survived=4 changed=1 lost=0 [] +wishlist DATA-LOST before=1 after=1 survived=0 changed=1 lost=0 [] diff --git a/audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist-content.json b/audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist-content.json new file mode 100644 index 0000000..ce48d93 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist-content.json @@ -0,0 +1,23 @@ +{ + "after": { + "inode": 47151, + "sha256": "f35df23bcef4fa1e6c5f312dbc0d63db7fb85032f85abd7e3f28935fe553ab76", + "size": 307200 + }, + "app": "wishlist", + "before": { + "inode": 47151, + "sha256": "f35df23bcef4fa1e6c5f312dbc0d63db7fb85032f85abd7e3f28935fe553ab76", + "size": 307200 + }, + "log": [ + "signup -> HTTP/1.1 404 Not Found", + "before: db={'inode': 47151, 'size': 307200, 'sha256': 'f35df23bcef4fa1e6c5f312dbc0d63db7fb85032f85abd7e3f28935fe553ab76'} users=Unable to find image 'keinos/sqlite3:latest' locally\nlatest: Pulling from keinos/sqlite3\n4f4fb700ef54: Pulling fs layer\n724d4e3100b9: Pulling fs layer\ne8817903f204: Pulling fs layer\n5439b162c22e: Pulling fs layer\n8e2cd5637e13: Pulling fs layer\n4f4fb700ef54: Already exists\n4f4fb700ef54: Pull complete\n8e2cd5637e13: Download complete\n724d4e3100b9: Download complete\n5439b162c22e: Download complete\ne8817903f204: Download complete\ne8817903f204: Pull complete\n289b2de968e1: Download complete\n7f32741739d1: Download complete\na34b169f6c05: Download complete\n724d4e3100b9: Pull complete\n5439b162c22e: Pull complete\n8e2cd5637e13: Pull complete\nDigest: sha256:a5610a155a8c9007f2050120406a0abcffab246570d6ac1ffe370f5f23e14dc1\nStatus: Downloaded newer image for keinos/sqlite3:latest\nParse error in 2nd command line argument: unable to open database file (14)", + "after : db={'inode': 47151, 'size': 307200, 'sha256': 'f35df23bcef4fa1e6c5f312dbc0d63db7fb85032f85abd7e3f28935fe553ab76'} users=Parse error in 2nd command line argument: unable to open database file (14)" + ], + "same_inode": true, + "user_rows_after": "Parse error in 2nd command line argument: unable to open database file (14)", + "user_rows_before": "Unable to find image 'keinos/sqlite3:latest' locally\nlatest: Pulling from keinos/sqlite3\n4f4fb700ef54: Pulling fs layer\n724d4e3100b9: Pulling fs layer\ne8817903f204: Pulling fs layer\n5439b162c22e: Pulling fs layer\n8e2cd5637e13: Pulling fs layer\n4f4fb700ef54: Already exists\n4f4fb700ef54: Pull complete\n8e2cd5637e13: Download complete\n724d4e3100b9: Download complete\n5439b162c22e: Download complete\ne8817903f204: Download complete\ne8817903f204: Pull complete\n289b2de968e1: Download complete\n7f32741739d1: Download complete\na34b169f6c05: Download complete\n724d4e3100b9: Pull complete\n5439b162c22e: Pull complete\n8e2cd5637e13: Pull complete\nDigest: sha256:a5610a155a8c9007f2050120406a0abcffab246570d6ac1ffe370f5f23e14dc1\nStatus: Downloaded newer image for keinos/sqlite3:latest\nParse error in 2nd command line argument: unable to open database file (14)", + "user_rows_preserved": false, + "verdict": "SURVIVES (inode only, no seeded rows)" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist.json b/audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist.json new file mode 100644 index 0000000..4030d62 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/proofs/wishlist/survive-wishlist.json @@ -0,0 +1,30 @@ +{ + "app": "wishlist", + "changed_in_place": [ + "/usr/src/app/data::prod.db" + ], + "files_after": 1, + "files_before": 1, + "label": "wishlist", + "lost": [], + "mounts": [ + { + "class": "named-declared", + "ctr": "wishlist", + "name": "svwishlist_wishlist_data", + "source": "/var/lib/docker/volumes/svwishlist_wishlist_data/_data", + "target": "/usr/src/app/data" + }, + { + "class": "named-declared", + "ctr": "wishlist", + "name": "svwishlist_wishlist_uploads", + "source": "/var/lib/docker/volumes/svwishlist_wishlist_uploads/_data", + "target": "/usr/src/app/uploads" + } + ], + "survived": [], + "up1_rc": 0, + "up2_rc": 0, + "verdict": "DATA-LOST" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/gate.log b/audits/persistence-sweep-2026-08-02/state/gate.log new file mode 100644 index 0000000..9d17553 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/gate.log @@ -0,0 +1,90 @@ +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +actualbudget CLEAN +adventurelog CLEAN +audiobookshelf CLEAN +bentopdf UNDETERMINED nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is U +bookstack CLEAN +calcom CLEAN +calibre-web CLEAN calibre-web: /app/calibre-web-automated/empty_library — database file(s) touched but byte-identical to the image; a chown sweep, not a write; calibre- +claper UNDETERMINED claper: EVERY declared mount is empty, yet the container created entries in 2 director(y/ies) outside all of them (e.g. ['/app/priv', '/app/priv/stati +code-server CLEAN +crafty-controller CLEAN crafty-controller: declared volume /crafty/servers is EMPTY; crafty-controller: declared volume /crafty/import is EMPTY; crafty-controller: declared v +docmost UNDETERMINED docmost: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/app/apps/client/dist']) — +emby CLEAN +ghost CLEAN +gitea CLEAN +glance UNDETERMINED glance: not running (status=restarting exit=1 restarts=None) +gokapi CLEAN +grafana CLEAN +gramps-web BROKEN gramps-web: DATA in the writable layer at /app/users (db_signature=True, e.g. ['users.sqlite']); gramps-web: declared volume /app/data is EMPTY; gramp +home-assistant CLEAN +homebox CLEAN +homepage CLEAN +immich UNDETERMINED immich-machine-learning: EVERY declared mount is empty, yet the container created entries in 3 director(y/ies) outside all of them (e.g. ['/', '/root' +jellyfin CLEAN +kimai CLEAN +komga CLEAN +mealie CLEAN +n8n CLEAN +navidrome CLEAN +nextcloud CLEAN +onlyoffice CLEAN onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24-0730933CC80A}/resources/store (path sug +opengist CLEAN +outline CLEAN outline: declared volume /var/lib/outline/data is EMPTY; outline-redis: declared volume /data is EMPTY +paperless-ngx CLEAN +papra BROKEN papra: mount /app/data is NOT writable by the app's own uid=999 gid=999; papra: DATA in the writable layer at /app/app-data/db (db_signature=True, e.g +plant-it UNDETERMINED no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling + Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/pla +plex CLEAN plex: declared volume /transcode is EMPTY +privatebin BROKEN privatebin: 14 file(s) in an ANONYMOUS volume at /run — not in the compose `volumes:` block, so it is never backed up and a redeploy orphans it; priva +radarr CLEAN radarr: writable-layer writes at /media (path suggests state, no database signature — judgement needed): ['movies'] +rallly CLEAN +recipe-importer UNDETERMINED recipe-importer: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/']) — nothing this +romm CLEAN +seerr CLEAN +sonarr CLEAN sonarr: writable-layer writes at /media (path suggests state, no database signature — judgement needed): ['tv'] +sparkyfitness UNDETERMINED sparkyfitness-server: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/app/SparkyFit +tandoor CLEAN tandoor: declared volume /opt/recipes/mediafiles is EMPTY +termix CLEAN +uptime-kuma UNDETERMINED nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is U +vaultwarden CLEAN +vikunja CLEAN vikunja: declared volume /app/vikunja/files is EMPTY +wanderer UNDETERMINED wanderer: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/root']) — nothing this ap +wger CLEAN wger: declared volume /home/wger/media is EMPTY +wishlist BROKEN wishlist: 1 file(s) in an ANONYMOUS volume at /usr/src/app/data — not in the compose `volumes:` block, so it is never backed up and a redeploy orphans +zipline UNDETERMINED zipline: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/zipline']) — nothing this + +BROKEN — the app's data does NOT land where the template preserves it: + gramps-web + gramps-web: DATA in the writable layer at /app/users (db_signature=True, e.g. ['users.sqlite']) + gramps-web: declared volume /app/data is EMPTY + gramps-web: declared volume /app/media is EMPTY + gramps-web: declared volume /tmp is EMPTY + papra + papra: mount /app/data is NOT writable by the app's own uid=999 gid=999 + papra: DATA in the writable layer at /app/app-data/db (db_signature=True, e.g. ['db.sqlite']) + papra: declared volume /app/data is EMPTY + privatebin + privatebin: 14 file(s) in an ANONYMOUS volume at /run — not in the compose `volumes:` block, so it is never backed up and a redeploy orphans it + privatebin: declared volume /srv/data is EMPTY + wishlist + wishlist: 1 file(s) in an ANONYMOUS volume at /usr/src/app/data — not in the compose `volumes:` block, so it is never backed up and a redeploy orphans it + wishlist: declared volume /data is EMPTY + +UNDETERMINED — NOT a pass. The question was not answered for these: + bentopdf: nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN + claper: claper: EVERY declared mount is empty, yet the container created entries in 2 director(y/ies) outside all of them (e.g. ['/app/priv', '/app/priv/static']) — nothing this app wrote landed anywhere the + docmost: docmost: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/app/apps/client/dist']) — nothing this app wrote landed anywhere the templat + glance: glance: not running (status=restarting exit=1 restarts=None) + immich: immich-machine-learning: EVERY declared mount is empty, yet the container created entries in 3 director(y/ies) outside all of them (e.g. ['/', '/root', '/root/.config']) — nothing this app wrote lande + plant-it: no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling + Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/plant-it, repository does not exist or may require 'd + recipe-importer: recipe-importer: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/']) — nothing this app wrote landed anywhere the template preserves. + sparkyfitness: sparkyfitness-server: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/app/SparkyFitnessServer']) — nothing this app wrote landed anyw + uptime-kuma: nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN; uptime-kuma: declared volu + wanderer: wanderer: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/root']) — nothing this app wrote landed anywhere the template preserves. Ne + zipline: zipline: EVERY declared mount is empty, yet the container created entries in 1 director(y/ies) outside all of them (e.g. ['/zipline']) — nothing this app wrote landed anywhere the template preserves. + +38 clean · 4 broken · 11 undetermined (of 53 in scope) +volume-persistence gate REFUSED diff --git a/audits/persistence-sweep-2026-08-02/state/mktable.py b/audits/persistence-sweep-2026-08-02/state/mktable.py new file mode 100644 index 0000000..04e15fa --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/mktable.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Render the sweep matrix from the per-app probe.json evidence. Run on DooPlex after pulling +the evidence back from the scratch guest.""" +import json, sys +from pathlib import Path + +EV = Path(sys.argv[1] if len(sys.argv) > 1 else "audits/persistence-sweep-2026-08-02/evidence") + +MOUNT_SHORT = {"named-declared": "vol", "named-external": "vol-ext", "anonymous": "ANON", + "bind": "bind", "tmpfs": "tmpfs"} + + +def summarise(p): + v = p["verdict"] + probe = p.get("probe") or {} + ctrs = probe.get("containers") or [] + mounts, wrote, unwritable = [], [], [] + for c in ctrs: + for m in c.get("mounts") or []: + if m["class"] == "tmpfs": + continue + mounts.append(f"{m['target']}[{MOUNT_SHORT.get(m['class'], m['class'])}" + f"{'' if m['files'] > 0 else ',EMPTY'}]") + if m["files"] > 0: + wrote.append(f"{m['target']}({m['files']})") + if m.get("writable_by_app") == "NO": + unwritable.append(m["target"]) + for d in c.get("diff_data_dirs") or []: + wrote.append(f"**{d['dir']} (writable layer)**") + return { + "verdict": v, + "mounted": ", ".join(mounts) or "—", + "wrote": ", ".join(wrote) or "nothing", + "unwritable": ", ".join(unwritable) or "—", + "reason": "; ".join(p.get("reasons") or []), + "containers": len(ctrs), + } + + +rows = {} +for d in sorted(EV.iterdir()): + f = d / "probe.json" + if f.is_file(): + rows[d.name] = summarise(json.loads(f.read_text())) + +order = {"BROKEN": 0, "UNDETERMINED": 1, "CLEAN": 2} +print("| app | verdict | what the template mounts | where the app actually wrote | mount not writable by app uid |") +print("|---|---|---|---|---|") +for app in sorted(rows, key=lambda a: (order.get(rows[a]["verdict"], 3), a)): + r = rows[app] + print(f"| `{app}` | **{r['verdict']}** | {r['mounted']} | {r['wrote']} | {r['unwritable']} |") + +print() +for v in ("BROKEN", "UNDETERMINED", "CLEAN"): + apps = [a for a in sorted(rows) if rows[a]["verdict"] == v] + print(f"{v}: {len(apps)} — {', '.join(apps) if apps else '(none)'}") +print(f"TOTAL: {len(rows)}") + +print("\n--- UNDETERMINED reasons ---") +for a in sorted(rows): + if rows[a]["verdict"] == "UNDETERMINED": + print(f"| `{a}` | {rows[a]['reason'][:400]} |") +print("\n--- BROKEN reasons ---") +for a in sorted(rows): + if rows[a]["verdict"] == "BROKEN": + print(f"| `{a}` | {rows[a]['reason'][:600]} |") diff --git a/audits/persistence-sweep-2026-08-02/state/papra_proof2.py b/audits/persistence-sweep-2026-08-02/state/papra_proof2.py new file mode 100644 index 0000000..657ac56 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/papra_proof2.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""R-156 fix proof for papra — the BEFORE/AFTER pair, on real user data. + +Not "the volume exists afterwards" — that is exactly the confusion R-156 is made of. The claim +under test is: a document a real user uploaded is still there after `docker compose down` + `up -d`. + +Leg A CURRENT template (`papra_data:/app/data`) — expected: the data is DESTROYED +Leg B FIXED template (`papra_data:/app/app-data`) — expected: the data SURVIVES, byte-identical + +The account and the document are created through papra's own HTTP API, never by writing into a +volume by hand: R-156's own evidence shows a root-written canary making an empty volume read as +populated. Data is read back with `docker cp`, which works identically whether it sits in a volume +or in the writable layer, so both legs are measured the same way. + +No password or session token is ever printed or written to the evidence file. +""" +import hashlib, importlib.util, json, os, re, secrets, shutil, sys, tempfile, time +from pathlib import Path + +_spec = importlib.util.spec_from_file_location("cvp", "/opt/sweep/scripts/check-volume-persistence.py") +cvp = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(cvp) + +TPL = Path("/opt/sweep/templates/papra") +EV = Path("/opt/sweep/evidence/_papra-fix-proof") +DOC = b"Felhom R-156 fix proof - ennek a dokumentumnak tul kell elnie az ujratelepitest.\n" +DOC_SHA = hashlib.sha256(DOC).hexdigest() + + +def curl(url, *args): + return cvp._sh(["curl", "-sS", "--max-time", "25", *args, url], timeout=45) + + +def wait_healthy(base, tries=60): + for _ in range(tries): + cid = (cvp._sh(base + ["ps", "-q"], timeout=60).stdout or "").split() + if cid: + st = (cvp._inspect(cid[0]) or {}).get("State") or {} + if (st.get("Health") or {}).get("Status") == "healthy": + return cid[0] + time.sleep(5) + return (cvp._sh(base + ["ps", "-q"], timeout=60).stdout or "").split()[0] + + +def seed(ip, jar, log): + """Create an account and upload a document through papra's own API.""" + email = f"proof-{secrets.token_hex(4)}@felhom.invalid" + pw = "Proof-" + secrets.token_hex(10) # never recorded + r = curl(f"http://{ip}:1221/api/auth/sign-up/email", "-X", "POST", + "-H", "Content-Type: application/json", "-c", jar, "-i", + "-d", json.dumps({"email": email, "password": pw, "name": "Proof"})) + head = (r.stdout or "").splitlines()[0].strip() if r.stdout else "?" + log.append(f"sign-up -> {head}") + orgs = curl(f"http://{ip}:1221/api/organizations", "-b", jar).stdout or "" + m = re.search(r'"id"\s*:\s*"([^"]+)"', orgs) + if not m: + log.append(f"organizations -> no id in {orgs[:160]}") + return {"account": head, "document_uploaded": False} + p = f"/tmp/proofdoc-{secrets.token_hex(3)}.txt" + open(p, "wb").write(DOC) + u = curl(f"http://{ip}:1221/api/organizations/{m.group(1)}/documents", "-X", "POST", + "-b", jar, "-F", f"file=@{p}", "-i") + os.remove(p) + ok = bool(re.search(r"HTTP/1\.[01] 2\d\d", u.stdout or "")) + log.append(f"upload -> {(u.stdout or '?').splitlines()[0].strip()}") + return {"account": head, "document_uploaded": ok} + + +def snapshot(cid, log, tag): + """Read the app's data OUT of the container, wherever it lives.""" + d = tempfile.mkdtemp(prefix="snap-") + out = {} + try: + for path in ("/app/app-data/db/db.sqlite", "/app/data"): + dst = os.path.join(d, path.strip("/").replace("/", "_")) + if cvp._sh(["docker", "cp", f"{cid}:{path}", dst], timeout=300).returncode != 0: + out[path] = {"present": False} + continue + if os.path.isfile(dst): + b = open(dst, "rb").read() + out[path] = {"present": True, "bytes": len(b), + "sha256": hashlib.sha256(b).hexdigest()} + else: + out[path] = {"present": True, "dir": True, + "files": sorted(os.path.relpath(os.path.join(r, f), dst) + for r, _, fs in os.walk(dst) for f in fs)} + docs = [] + if cvp._sh(["docker", "cp", f"{cid}:/app/app-data/documents", + os.path.join(d, "documents")], timeout=300).returncode == 0: + for r, _, fs in os.walk(os.path.join(d, "documents")): + for f in fs: + b = open(os.path.join(r, f), "rb").read() + docs.append({"size": len(b), "sha256": hashlib.sha256(b).hexdigest(), + "is_the_proof_document": hashlib.sha256(b).hexdigest() == DOC_SHA}) + out["documents"] = docs + finally: + shutil.rmtree(d, ignore_errors=True) + log.append(f"[{tag}] " + json.dumps(out)[:260]) + return out + + +def leg(tag, compose_body): + log = [] + work = Path(tempfile.mkdtemp(prefix=f"papra-{tag}-")) + (work / "docker-compose.yml").write_text(compose_body) + env = cvp.build_env("papra", (TPL / ".felhom.yml").read_text(), compose_body) + (work / ".env").write_text("".join(f"{k}={v}\n" for k, v in sorted(env.items()))) + project = "pp" + tag + base = ["docker", "compose", "-p", project, "--project-directory", str(work), + "-f", str(work / "docker-compose.yml")] + cvp._sh(["docker", "network", "create", "traefik-public"], timeout=60) + jar = f"/tmp/pj-{tag}" + try: + cvp._sh(base + ["up", "-d"], timeout=1200) + cid = wait_healthy(base) + ip = [n["IPAddress"] for n in + ((cvp._inspect(cid) or {}).get("NetworkSettings") or {}).get("Networks", {}).values() + if n.get("IPAddress")][0] + time.sleep(15) + seeded = seed(ip, jar, log) + time.sleep(20) + before = snapshot(cid, log, "before") + + cvp._sh(base + ["down"], timeout=900) # a REDEPLOY: no -v + cvp._sh(base + ["up", "-d"], timeout=1200) + cid2 = wait_healthy(base) + time.sleep(25) + after = snapshot(cid2, log, "after") + + db = "/app/app-data/db/db.sqlite" + db_same = bool(before[db].get("sha256") + and before[db].get("sha256") == after[db].get("sha256")) + doc_b = any(x["is_the_proof_document"] for x in before["documents"]) + doc_a = any(x["is_the_proof_document"] for x in after["documents"]) + return {"leg": tag, "seeded": seeded, "before": before, "after": after, + "db_identical_after_redeploy": db_same, + "uploaded_document_present_before": doc_b, + "uploaded_document_present_after": doc_a, + "verdict": ("USER DATA SURVIVES" if (db_same and doc_b and doc_a) + else "USER DATA LOST" if doc_b and not doc_a + else "INCONCLUSIVE - the document was never uploaded" + if not doc_b else "DATA LOST"), + "log": log} + finally: + cvp._sh(base + ["down", "-v", "--remove-orphans"], timeout=900) + shutil.rmtree(work, ignore_errors=True) + if os.path.exists(jar): + os.remove(jar) + + +if __name__ == "__main__": + EV.mkdir(parents=True, exist_ok=True) + cur = (TPL / "docker-compose.yml").read_text() + fixed = cur.replace("- papra_data:/app/data", "- papra_data:/app/app-data") + assert fixed != cur, "the mount line did not match — refusing to 'prove' an unchanged template" + (EV / "template.diff").write_text( + "--- current\n+++ fixed\n- - papra_data:/app/data\n+ - papra_data:/app/app-data\n") + out = {} + for tag, body in (("current", cur), ("fixed", fixed)): + out[tag] = leg(tag, body) + r = out[tag] + print(f"{tag:<8} {r['verdict']:<34} db_identical={r['db_identical_after_redeploy']} " + f"doc_before={r['uploaded_document_present_before']} " + f"doc_after={r['uploaded_document_present_after']}", flush=True) + (EV / "papra-fix-proof.json").write_text(json.dumps(out, indent=2, sort_keys=True)) diff --git a/audits/persistence-sweep-2026-08-02/state/reclassify.py b/audits/persistence-sweep-2026-08-02/state/reclassify.py new file mode 100644 index 0000000..7a66a06 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/reclassify.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Re-derive every verdict from the captured probes using the committed `classify()`. + +The structural "did anything land in a mount at all?" rule was added to `classify()` after the +sweep had started. It reads only fields the probe already captures (mount occupancy and the +directories created outside every mount), and the Docker-touching half of the gate — the part that +produces `probe` — was not changed by it. So the matrix is recomputed rather than re-measured, and +the verdict every app carries is the one the COMMITTED gate produces for its captured evidence. + +Anything the rule could not have seen would require a fresh capture; nothing here does. + +Usage: reclassify.py +""" +import importlib.util, json, sys +from pathlib import Path + +_spec = importlib.util.spec_from_file_location( + "cvp", Path(__file__).resolve().parent / "check-volume-persistence.py") +cvp = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(cvp) + +EV = Path(sys.argv[1]) +changed = [] +for d in sorted(EV.iterdir()): + f = d / "probe.json" + if not f.is_file(): + continue + rec = json.loads(f.read_text()) + old = rec.get("verdict") + status, why = cvp.classify(rec.get("probe") or {}) + if status != old: + changed.append((d.name, old, status)) + rec["verdict"] = status + rec["reasons"] = why + rec["verdict_recomputed_by"] = "check-volume-persistence.py classify() (see reclassify.py)" + f.write_text(json.dumps(rec, indent=2, sort_keys=True)) + +print(f"re-classified {sum(1 for d in EV.iterdir() if (d / 'probe.json').is_file())} apps") +for a, o, n in changed: + print(f" CHANGED {a}: {o} -> {n}") +if not changed: + print(" no verdict changed") diff --git a/audits/persistence-sweep-2026-08-02/state/survive2.py b/audits/persistence-sweep-2026-08-02/state/survive2.py new file mode 100644 index 0000000..535b271 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/survive2.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Survives-a-redeploy prover — the second half of the proof the gate cannot give. + +`check-volume-persistence.py` answers *where did the data land*. This answers *is it still there +after a redeploy*, which is the claim the customer actually cares about. Presence is not success: +that a volume exists afterwards says nothing about whether the data is in it. + +Method, per app: + 1. deploy from the template, exercise, settle + 2. FINGERPRINT every file the app created inside every declared mount — (relpath, size, sha256, + inode), read host-side from the volume's own directory + 3. `docker compose down` — NO `-v`. That is a redeploy; `down --volumes` is the UNINSTALL path + and destroys volumes deliberately (Campaign 7 §2) + 4. `docker compose up -d`, wait, settle + 5. RE-FINGERPRINT. A file SURVIVED only if sha256 AND inode both match — the same bytes in the + same file object, not a fresh file the app recreated under the same name. + +Nothing is ever seeded into a volume by hand. R-156's own evidence shows a root-written canary +making an empty volume read as populated, which is the confusion this work exists to remove. + +Usage: python3 survive2.py [ …] (templates under /opt/sweep/templates) +""" +import hashlib, importlib.util, json, os, shutil, sys, tempfile, time +from pathlib import Path + +_spec = importlib.util.spec_from_file_location("cvp", "/opt/sweep/check-volume-persistence.py") +cvp = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(cvp) + +TEMPLATES = Path("/opt/sweep/templates") +EVIDENCE = Path("/opt/sweep/evidence") + + +def fingerprint(mounts): + fp = {} + for m in mounts: + src = m["source"] + if not src or not os.path.isdir(src): + continue + for dp, _, fns in os.walk(src, onerror=lambda e: None): + for fn in fns: + p = os.path.join(dp, fn) + if not os.path.isfile(p): + continue + try: + st = os.lstat(p) + h = hashlib.sha256(open(p, "rb").read()).hexdigest() + except OSError: + continue + fp[f"{m['target']}::{os.path.relpath(p, src)}"] = { + "size": st.st_size, "sha256": h, "inode": st.st_ino} + return fp + + +def bring_up(base, declared, project, ports, settle=45, wait=300): + rc = cvp._sh(base + ["up", "-d"], timeout=1800).returncode + cids = [c for c in cvp._sh(base + ["ps", "-aq"], timeout=120).stdout.split() if c] + deadline = time.time() + wait + while time.time() < deadline: + pend = False + for cid in cids: + st = (cvp._inspect(cid) or {}).get("State") or {} + if st.get("Status") in ("created", "restarting") or \ + (st.get("Health") or {}).get("Status") == "starting": + pend = True + if not pend: + break + time.sleep(10) + running = [c for c in cids + if ((cvp._inspect(c) or {}).get("State") or {}).get("Status") == "running"] + if running and ports: + cvp._exercise(running, ports) + time.sleep(settle) + mounts = [] + for cid in cids: + info = cvp._inspect(cid) + if not info: + continue + nm = (info.get("Name") or cid).lstrip("/") + for m in (info.get("Mounts") or []): + cls = cvp._classify_mount(m, project, declared) + if cls == "tmpfs": + continue + mounts.append({"ctr": nm, "target": m.get("Destination"), "class": cls, + "source": m.get("Source"), "name": m.get("Name")}) + return rc, cids, mounts + + +def prove(app, tdir=None, label=None): + tdir = Path(tdir) if tdir else TEMPLATES / app + label = label or app + edir = EVIDENCE / app + edir.mkdir(parents=True, exist_ok=True) + work = Path(tempfile.mkdtemp(prefix=f"surv-{label}-")) + compose_text = (tdir / "docker-compose.yml").read_text() + felhom_text = (tdir / ".felhom.yml").read_text() if (tdir / ".felhom.yml").is_file() else "" + shutil.copy(tdir / "docker-compose.yml", work / "docker-compose.yml") + env = cvp.build_env(app, felhom_text, compose_text) + (work / ".env").write_text("".join(f"{k}={v}\n" for k, v in sorted(env.items()))) + project = "sv" + "".join(ch for ch in label.lower() if ch.isalnum()) + base = ["docker", "compose", "-p", project, "--project-directory", str(work), + "-f", str(work / "docker-compose.yml")] + os.makedirs(cvp.SCRATCH_HDD + "/userdata", exist_ok=True) + os.makedirs(cvp.SCRATCH_IMPORT, exist_ok=True) + cvp._sh(["docker", "network", "create", "traefik-public"], timeout=60) + try: + cfg = json.loads(cvp._sh(base + ["config", "--format", "json"], timeout=180).stdout) + declared = set((cfg.get("volumes") or {}).keys()) + ports = sorted({int(m.group(1)) + for svc in (cfg.get("services") or {}).values() + for lbl in ((svc.get("labels") or {}).values() + if isinstance(svc.get("labels"), dict) + else (svc.get("labels") or [])) + for m in [cvp.PORT_RE.search(str(lbl))] if m}) + + rc1, _, mounts1 = bring_up(base, declared, project, ports) + before = fingerprint(mounts1) + + cvp._sh(base + ["down"], timeout=900) # a REDEPLOY — no -v + rc2, _, mounts2 = bring_up(base, declared, project, ports) + after = fingerprint(mounts2) + + survived = sorted(k for k in before if k in after + and after[k]["sha256"] == before[k]["sha256"] + and after[k]["inode"] == before[k]["inode"]) + changed = sorted(k for k in before if k in after and k not in survived) + lost = sorted(k for k in before if k not in after) + res = {"app": app, "label": label, "up1_rc": rc1, "up2_rc": rc2, + "files_before": len(before), "files_after": len(after), + "survived": survived, "changed_in_place": changed, "lost": lost, + "mounts": mounts1, + "verdict": ("SURVIVES" if (before and not lost and survived) + else "NO-DATA-TO-LOSE" if not before else "DATA-LOST")} + (edir / f"survive-{label}.json").write_text(json.dumps(res, indent=2, sort_keys=True)) + return res + finally: + cvp._sh(base + ["down", "-v", "--remove-orphans"], timeout=900) + shutil.rmtree(work, ignore_errors=True) + + +if __name__ == "__main__": + for a in sys.argv[1:]: + r = prove(a) + print("%-16s %-16s before=%d after=%d survived=%d changed=%d lost=%d %s" + % (a, r["verdict"], r["files_before"], r["files_after"], len(r["survived"]), + len(r["changed_in_place"]), len(r["lost"]), r["lost"][:3]), flush=True) diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/bentopdf/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/bentopdf/probe.json new file mode 100644 index 0000000..8ba28a9 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/bentopdf/probe.json @@ -0,0 +1,69 @@ +{ + "probe": { + "app": "bentopdf", + "containers": [ + { + "diff_added": 6, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "var" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "nginx" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var", + "files": [ + "cache" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 14, + "diff_unresolved": [], + "exit": 0, + "gid": 101, + "health": "healthy", + "image": "ghcr.io/alam00000/bentopdf:v2.8.6", + "mounts": [], + "name": "bentopdf", + "restarts": null, + "status": "running", + "uid": 101 + } + ], + "declared_volumes": [], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "nothing was written to any mount and nothing data-classified in any writable layer \u2014 the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN" + ], + "verdict": "UNDETERMINED" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/claper/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/claper/probe.json new file mode 100644 index 0000000..65b6574 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/claper/probe.json @@ -0,0 +1,176 @@ +{ + "probe": { + "app": "claper", + "containers": [ + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "priv" + ] + }, + { + "added": [ + "static" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/priv", + "files": [ + "static" + ] + }, + { + "added": [ + "uploads" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/priv/static", + "files": [ + "uploads" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/claperco/claper:2.5", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-claper_claper_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-claper_claper_uploads/_data", + "target": "/app/priv/static/uploads", + "writable_by_app": "yes" + } + ], + "name": "claper", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1469, + "name": "volgate-claper_claper_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-claper_claper_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "claper-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "claper_postgres_data", + "claper_uploads" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY_BASE", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "claper: this container's mounts are all empty while it created entries in ['/app/priv', '/app/priv/static'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "claper: declared volume /app/priv/static/uploads is EMPTY" + ], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/docmost/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/docmost/probe.json new file mode 100644 index 0000000..ebf41a7 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/docmost/probe.json @@ -0,0 +1,216 @@ +{ + "probe": { + "app": "docmost", + "containers": [ + { + "diff_added": 1, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "apps" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps", + "files": [ + "client" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps/client", + "files": [ + "dist" + ] + }, + { + "added": [ + "index-template.html" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/app/apps/client/dist", + "files": [ + "index-template.html" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 6, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "docmost/docmost:0.95.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-docmost_docmost_storage", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-docmost_docmost_storage/_data", + "target": "/app/data/storage", + "writable_by_app": "yes" + } + ], + "name": "docmost", + "restarts": null, + "status": "running", + "uid": 1000 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1540, + "name": "volgate-docmost_docmost_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-docmost_docmost_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "docmost-postgres", + "restarts": null, + "status": "running", + "uid": 70 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-docmost_docmost_redis_data", + "sample": [ + "appendonlydir/appendonly.aof.1.base.rdb", + "appendonlydir/appendonly.aof.1.incr.aof", + "appendonlydir/appendonly.aof.manifest" + ], + "source": "/var/lib/docker/volumes/volgate-docmost_docmost_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "docmost-redis", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "docmost_postgres_data", + "docmost_redis_data", + "docmost_storage" + ], + "env_keys": [ + "APP_SECRET", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "docmost: this container's mounts are all empty while it created entries in ['/app/apps/client/dist'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "docmost: declared volume /app/data/storage is EMPTY" + ], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/glance/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/glance/probe.json new file mode 100644 index 0000000..67020f7 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/glance/probe.json @@ -0,0 +1,73 @@ +{ + "probe": { + "app": "glance", + "containers": [ + { + "diff_added": 1, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [ + "config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app", + "files": [ + "config" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 2, + "diff_unresolved": [], + "exit": 1, + "gid": null, + "health": "unhealthy", + "image": "glanceapp/glance:v0.8.5", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-glance_glance_config", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-glance_glance_config/_data", + "target": "/app/config", + "writable_by_app": "unknown-uid" + } + ], + "name": "glance", + "restarts": null, + "status": "restarting", + "uid": null + } + ], + "declared_volumes": [ + "glance_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "glance: not running (status=restarting exit=1 restarts=None)" + ], + "verdict": "UNDETERMINED" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/gramps-web/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/gramps-web/probe.json new file mode 100644 index 0000000..570cd13 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/gramps-web/probe.json @@ -0,0 +1,265 @@ +{ + "probe": { + "app": "gramps-web", + "containers": [ + { + "diff_added": 127, + "diff_benign_db_touches": [], + "diff_data_dirs": [ + { + "added": [ + "users.sqlite" + ], + "changed_count": 0, + "db_signature": true, + "dir": "/app/users", + "files": [ + "users.sqlite" + ] + } + ], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "app", + "root", + "usr" + ] + }, + { + "added": [ + "data" + ], + "changed_count": 4, + "db_signature": false, + "dir": "/app", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/cache", + "files": [ + "request_cache" + ] + }, + { + "added": [ + "2029240f6d1128be89ddc32729463129" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/cache/request_cache", + "files": [ + "2029240f6d1128be89ddc32729463129" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/src", + "files": [ + "alembic_users", + "gramps_webapi" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/src/alembic_users", + "files": [ + "versions" + ] + }, + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/app/src/gramps_webapi", + "files": [ + "api", + "auth", + "util" + ] + }, + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/app/src/gramps_webapi/api", + "files": [ + "resources", + "search" + ] + }, + { + "added": [ + "2029240f6d1128be89ddc32729463129" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/thumbnail_cache", + "files": [ + "2029240f6d1128be89ddc32729463129" + ] + }, + { + "added": [ + ".gramps" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/root", + "files": [ + ".gramps" + ] + }, + { + "added": [ + "grampsdb" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gramps", + "files": [ + "grampsdb" + ] + }, + { + "added": [ + "edaa44da-5ceb-4855-966e-8462f6b067f7" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gramps/grampsdb", + "files": [ + "edaa44da-5ceb-4855-966e-8462f6b067f7" + ] + }, + { + "added": [ + "name.txt", + "database.txt" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.gramps/grampsdb/edaa44da-5ceb-4855-966e-8462f6b067f7", + "files": [ + "name.txt", + "database.txt" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps", + "files": [ + "gramps60" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps/gramps60", + "files": [ + "plugins" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/root/gramps/gramps60/plugins", + "files": [ + "FilterRules" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "lib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 153, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/gramps-project/grampsweb:v25.6.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_data/_data", + "target": "/app/data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_media", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_media/_data", + "target": "/app/media", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-grampsweb_gramps_tmp", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-grampsweb_gramps_tmp/_data", + "target": "/tmp", + "writable_by_app": "yes" + } + ], + "name": "gramps-web", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "gramps_data", + "gramps_media", + "gramps_tmp" + ], + "env_keys": [ + "DOMAIN", + "GRAMPSWEB_SECRET_KEY", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "gramps-web: DATA in the writable layer at /app/users (db_signature=True, e.g. ['users.sqlite'])", + "NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/app', '/app/cache/request_cache', '/app/thumbnail_cache', '/root']. Needs adjudication.", + "gramps-web: this container's mounts are all empty while it created entries in ['/app', '/app/cache/request_cache', '/app/thumbnail_cache'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "gramps-web: declared volume /app/data is EMPTY", + "gramps-web: declared volume /app/media is EMPTY" + ], + "verdict": "BROKEN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/immich/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/immich/probe.json new file mode 100644 index 0000000..37c6935 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/immich/probe.json @@ -0,0 +1,329 @@ +{ + "probe": { + "app": "immich", + "containers": [ + { + "diff_added": 6, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "cache" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "cache" + ] + }, + { + "added": [ + ".config" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config" + ] + }, + { + "added": [ + "matplotlib" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config", + "files": [ + "matplotlib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 7, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-immich_immich_ml_cache", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-immich_immich_ml_cache/_data", + "target": "/cache", + "writable_by_app": "yes" + } + ], + "name": "immich-machine-learning", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 3, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "run" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "postgresql" + ] + }, + { + "added": [ + "postgresql.conf" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/etc/postgresql", + "files": [ + "postgresql.conf" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 7, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "mounts": [ + { + "class": "named-declared", + "files": 1832, + "name": "volgate-immich_immich_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/13406", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/13414", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106" + ], + "source": "/var/lib/docker/volumes/volgate-immich_immich_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "immich-postgres", + "restarts": null, + "status": "running", + "uid": 999 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 1000, + "health": "healthy", + "image": "redis:7-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 3, + "name": "volgate-immich_immich_redis_data", + "sample": [ + "appendonlydir/appendonly.aof.1.base.rdb", + "appendonlydir/appendonly.aof.1.incr.aof", + "appendonlydir/appendonly.aof.manifest" + ], + "source": "/var/lib/docker/volumes/volgate-immich_immich_redis_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "immich-redis", + "restarts": null, + "status": "running", + "uid": 999 + }, + { + "diff_added": 4, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "external", + "data" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "external", + "data" + ] + }, + { + "added": [ + "photos" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/external", + "files": [ + "photos" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "src" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/src", + "files": [ + "app" + ] + }, + { + "added": [ + "upload" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/usr/src/app", + "files": [ + "upload" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 7, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/immich-app/immich-server:v3.0.3", + "mounts": [ + { + "class": "bind", + "files": 6, + "name": null, + "sample": [ + "library/.immich", + "upload/.immich", + "profile/.immich", + "encoded-video/.immich", + "thumbs/.immich", + "backups/.immich" + ], + "source": "/srv/felhom-gate/hdd/appdata/immich", + "target": "/usr/src/app/upload", + "writable_by_app": "yes" + }, + { + "class": "bind", + "files": 0, + "name": null, + "sample": [], + "source": "/srv/felhom-gate/hdd/userdata/media/photos", + "target": "/external/photos", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "51538436a985b60deb1473d5837733528b3440627b2f7761b235f8bd92010975", + "sample": [], + "source": "/var/lib/docker/volumes/51538436a985b60deb1473d5837733528b3440627b2f7761b235f8bd92010975/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "immich-server", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "immich_ml_cache", + "immich_postgres_data", + "immich_redis_data" + ], + "env_keys": [ + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "immich-machine-learning: this container's mounts are all empty while it created entries in ['/', '/root', '/root/.config'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "immich-machine-learning: declared volume /cache is EMPTY" + ], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/papra/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/papra/probe.json new file mode 100644 index 0000000..cf58b8c --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/papra/probe.json @@ -0,0 +1,99 @@ +{ + "probe": { + "app": "papra", + "containers": [ + { + "diff_added": 173, + "diff_benign_db_touches": [], + "diff_data_dirs": [ + { + "added": [ + "db.sqlite" + ], + "changed_count": 0, + "db_signature": true, + "dir": "/app/app-data/db", + "files": [ + "db.sqlite" + ] + } + ], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [ + "data" + ], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app/app-data", + "files": [ + "db" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 177, + "diff_unresolved": [], + "exit": 0, + "gid": 999, + "health": "healthy", + "image": "ghcr.io/papra-hq/papra:26.6.1-rootless", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-papra_papra_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-papra_papra_data/_data", + "target": "/app/data", + "writable_by_app": "NO" + } + ], + "name": "papra", + "restarts": null, + "status": "running", + "uid": 999 + } + ], + "declared_volumes": [ + "papra_data" + ], + "env_keys": [ + "AUTH_SECRET", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "papra: mount /app/data is NOT writable by the app's own uid=999 gid=999", + "papra: DATA in the writable layer at /app/app-data/db (db_signature=True, e.g. ['db.sqlite'])", + "NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are empty, yet entries were created in ['/app']. Needs adjudication.", + "papra: this container's mounts are all empty while it created entries in ['/app'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "papra: declared volume /app/data is EMPTY" + ], + "verdict": "BROKEN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/plant-it/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/plant-it/probe.json new file mode 100644 index 0000000..2c4c0a5 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/plant-it/probe.json @@ -0,0 +1,10 @@ +{ + "probe": { + "app": "plant-it", + "error": "no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling \n Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\nError response from daemon: pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\n)" + }, + "reasons": [ + "no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling \n Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\nError response from daemon: pull access denied for msdeluise/plant-it, repository does not exist or may require 'docker login'\n)" + ], + "verdict": "UNDETERMINED" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/privatebin/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/privatebin/probe.json new file mode 100644 index 0000000..5a15e15 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/privatebin/probe.json @@ -0,0 +1,95 @@ +{ + "probe": { + "app": "privatebin", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 82, + "health": "healthy", + "image": "privatebin/pdo:2.0.5", + "mounts": [ + { + "class": "anonymous", + "files": 0, + "name": "81f6e29600a6e6cbaa58dda4e144909abf098adc3c6116ca85166068c6a02e34", + "sample": [], + "source": "/var/lib/docker/volumes/81f6e29600a6e6cbaa58dda4e144909abf098adc3c6116ca85166068c6a02e34/_data", + "target": "/var/lib/nginx/tmp", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-privatebin_privatebin_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-privatebin_privatebin_data/_data", + "target": "/srv/data", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 14, + "name": "6c40818b0f3ef0f6687a2394c61219df6a1d806829e2c6d45c137c9e62427a14", + "sample": [ + "nginx.pid", + "php-fpm.sock", + "services/.s6-svscan/control", + "services/.s6-svscan/lock", + "services/php-fpm85/run", + "services/php-fpm85/supervise/control", + "services/php-fpm85/supervise/death_tally", + "services/php-fpm85/supervise/status", + "services/php-fpm85/supervise/lock", + "services/nginx/run", + "services/nginx/supervise/control", + "services/nginx/supervise/death_tally", + "services/nginx/supervise/status", + "services/nginx/supervise/lock" + ], + "source": "/var/lib/docker/volumes/6c40818b0f3ef0f6687a2394c61219df6a1d806829e2c6d45c137c9e62427a14/_data", + "target": "/run", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "660faab3050406b4dac39dedfc189331d712fb02b3990aed2e81033a08917cf3", + "sample": [], + "source": "/var/lib/docker/volumes/660faab3050406b4dac39dedfc189331d712fb02b3990aed2e81033a08917cf3/_data", + "target": "/tmp", + "writable_by_app": "yes" + } + ], + "name": "privatebin", + "restarts": null, + "status": "running", + "uid": 65534 + } + ], + "declared_volumes": [ + "privatebin_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "nothing was written to any mount and nothing data-classified in any writable layer \u2014 the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN", + "privatebin: declared volume /srv/data is EMPTY" + ], + "verdict": "UNDETERMINED" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/recipe-importer/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/recipe-importer/probe.json new file mode 100644 index 0000000..e951c9f --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/recipe-importer/probe.json @@ -0,0 +1,94 @@ +{ + "probe": { + "app": "recipe-importer", + "containers": [ + { + "diff_added": 13, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "data" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "local" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/local", + "files": [ + "lib" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 22, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "gitea.dooplex.hu/admin/recipe-importer:v0.9.11", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-recipeimporter_recipe_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-recipeimporter_recipe_data/_data", + "target": "/data", + "writable_by_app": "yes" + } + ], + "name": "recipe-importer", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "recipe_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SECRET_KEY", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. Needs adjudication.", + "recipe-importer: this container's mounts are all empty while it created entries in ['/'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "recipe-importer: declared volume /data is EMPTY" + ], + "verdict": "UNDETERMINED" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/sparkyfitness/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/sparkyfitness/probe.json new file mode 100644 index 0000000..4026323 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/sparkyfitness/probe.json @@ -0,0 +1,230 @@ +{ + "probe": { + "app": "sparkyfitness", + "containers": [ + { + "diff_added": 11, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 3, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "var", + "run" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc", + "files": [ + "nginx" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/var", + "files": [ + "cache" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 19, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "codewithcj/sparkyfitness:v0.17.3", + "mounts": [], + "name": "sparkyfitness", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:15-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1814, + "name": "volgate-sparkyfitness_sparkyfitness_db_data", + "sample": [ + "pgdata/postmaster.opts", + "pgdata/postgresql.conf", + "pgdata/postgresql.auto.conf", + "pgdata/pg_ident.conf", + "pgdata/pg_hba.conf", + "pgdata/postmaster.pid", + "pgdata/PG_VERSION", + "pgdata/base/1/826", + "pgdata/base/1/2996", + "pgdata/base/1/3764", + "pgdata/base/1/2610_fsm", + "pgdata/base/1/3603_fsm", + "pgdata/base/1/1249_vm", + "pgdata/base/1/3600_fsm", + "pgdata/base/1/13506_vm", + "pgdata/base/1/3602", + "pgdata/base/1/6228", + "pgdata/base/1/4174", + "pgdata/base/1/4157", + "pgdata/base/1/4146", + "pgdata/base/1/2611", + "pgdata/base/1/2657", + "pgdata/base/1/3079_vm", + "pgdata/base/1/1247_vm", + "pgdata/base/1/13501_fsm", + "pgdata/base/1/1259", + "pgdata/base/1/3601_fsm", + "pgdata/base/1/1417", + "pgdata/base/1/4155", + "pgdata/base/1/2668", + "pgdata/base/1/13491_fsm", + "pgdata/base/1/2831", + "pgdata/base/1/2841", + "pgdata/base/1/3607", + "pgdata/base/1/2836", + "pgdata/base/1/1255_fsm", + "pgdata/base/1/2615_vm", + "pgdata/base/1/3575", + "pgdata/base/1/2605_vm", + "pgdata/base/1/2670" + ], + "source": "/var/lib/docker/volumes/volgate-sparkyfitness_sparkyfitness_db_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "sparkyfitness-db", + "restarts": null, + "status": "running", + "uid": 70 + }, + { + "diff_added": 392, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "app" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/app", + "files": [ + "SparkyFitnessServer" + ] + }, + { + "added": [ + "uploads", + "backup", + "temp_uploads" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/app/SparkyFitnessServer", + "files": [ + "uploads", + "backup", + "temp_uploads" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 395, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "codewithcj/sparkyfitness_server:v0.17.3", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-sparkyfitness_sparkyfitness_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-sparkyfitness_sparkyfitness_uploads/_data", + "target": "/app/SparkyFitnessServer/uploads", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-sparkyfitness_sparkyfitness_backup", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-sparkyfitness_sparkyfitness_backup/_data", + "target": "/app/SparkyFitnessServer/backup", + "writable_by_app": "yes" + } + ], + "name": "sparkyfitness-server", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "sparkyfitness_backup", + "sparkyfitness_db_data", + "sparkyfitness_uploads" + ], + "env_keys": [ + "API_ENCRYPTION_KEY", + "APP_DB_PASSWORD", + "BETTER_AUTH_SECRET", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "sparkyfitness-server: this container's mounts are all empty while it created entries in ['/app/SparkyFitnessServer'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "sparkyfitness-server: declared volume /app/SparkyFitnessServer/uploads is EMPTY", + "sparkyfitness-server: declared volume /app/SparkyFitnessServer/backup is EMPTY" + ], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/uptime-kuma/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/uptime-kuma/probe.json new file mode 100644 index 0000000..d5f0ddb --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/uptime-kuma/probe.json @@ -0,0 +1,53 @@ +{ + "probe": { + "app": "uptime-kuma", + "containers": [ + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "louislam/uptime-kuma:2.4.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-uptimekuma_uptime_kuma_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-uptimekuma_uptime_kuma_data/_data", + "target": "/app/data", + "writable_by_app": "yes" + } + ], + "name": "uptime-kuma", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "uptime_kuma_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "nothing was written to any mount and nothing data-classified in any writable layer \u2014 the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN", + "uptime-kuma: declared volume /app/data is EMPTY" + ], + "verdict": "UNDETERMINED" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/wanderer/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/wanderer/probe.json new file mode 100644 index 0000000..8ca0c2c --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/wanderer/probe.json @@ -0,0 +1,193 @@ +{ + "probe": { + "app": "wanderer", + "containers": [ + { + "diff_added": 6, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "root" + ] + }, + { + "added": [ + ".npm" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".npm" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 8, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "unhealthy", + "image": "flomp/wanderer-web:v0.20.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-wanderer_wanderer_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_uploads/_data", + "target": "/app/uploads", + "writable_by_app": "yes" + } + ], + "name": "wanderer", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 3, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data", + "pb_data" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/", + "files": [ + "data", + "pb_data" + ] + } + ], + "diff_token_dirs": [ + { + "added": [ + "plugins" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/data", + "files": [ + "plugins" + ] + } + ], + "diff_total": 3, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "flomp/wanderer-db:v0.20.0", + "mounts": [ + { + "class": "named-declared", + "files": 6, + "name": "volgate-wanderer_wanderer_data", + "sample": [ + "auxiliary.db-wal", + "data.db", + "data.db-wal", + "auxiliary.db", + "auxiliary.db-shm", + "data.db-shm" + ], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_data/_data", + "target": "/pb_data", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-wanderer_wanderer_plugins", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_plugins/_data", + "target": "/data/plugins", + "writable_by_app": "yes" + } + ], + "name": "wanderer-db", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 0, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [], + "diff_token_dirs": [], + "diff_total": 0, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "getmeili/meilisearch:v1.36.0", + "mounts": [ + { + "class": "named-declared", + "files": 11, + "name": "volgate-wanderer_wanderer_meili_data", + "sample": [ + "data.ms/VERSION", + "data.ms/indexes/7ee7d19b-c036-412d-be25-65ae8f9e4801/data.mdb", + "data.ms/indexes/7ee7d19b-c036-412d-be25-65ae8f9e4801/lock.mdb", + "data.ms/indexes/82fdecc6-7425-4e45-989b-bdb644ecd606/data.mdb", + "data.ms/indexes/82fdecc6-7425-4e45-989b-bdb644ecd606/lock.mdb", + "data.ms/indexes/c48904e5-2b2a-41c0-a34c-c9575e31be45/data.mdb", + "data.ms/indexes/c48904e5-2b2a-41c0-a34c-c9575e31be45/lock.mdb", + "data.ms/auth/data.mdb", + "data.ms/auth/lock.mdb", + "data.ms/tasks/data.mdb", + "data.ms/tasks/lock.mdb" + ], + "source": "/var/lib/docker/volumes/volgate-wanderer_wanderer_meili_data/_data", + "target": "/meili_data", + "writable_by_app": "yes" + } + ], + "name": "wanderer-search", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "wanderer_data", + "wanderer_meili_data", + "wanderer_plugins", + "wanderer_uploads" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "MEILI_MASTER_KEY", + "POCKETBASE_ENCRYPTION_KEY", + "SUBDOMAIN", + "SUBDOMAIN_DB", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "wanderer: unhealthy", + "wanderer: this container's mounts are all empty while it created entries in ['/root'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "wanderer: declared volume /app/uploads is EMPTY", + "wanderer-db: declared volume /data/plugins is EMPTY", + "wanderer-db: writable-layer writes at /data (path suggests state, no database signature \u2014 judgement needed): ['plugins']" + ], + "verdict": "UNDETERMINED" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/wishlist/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/wishlist/probe.json new file mode 100644 index 0000000..29a2566 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/wishlist/probe.json @@ -0,0 +1,162 @@ +{ + "probe": { + "app": "wishlist", + "containers": [ + { + "diff_added": 35, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [ + "data" + ], + "changed_count": 2, + "db_signature": false, + "dir": "/", + "files": [ + "data" + ] + }, + { + "added": [ + ".config", + ".local" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root", + "files": [ + ".config", + ".local" + ] + }, + { + "added": [ + "caddy" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config", + "files": [ + "caddy" + ] + }, + { + "added": [ + "autosave.json" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.config/caddy", + "files": [ + "autosave.json" + ] + }, + { + "added": [ + "share" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/root/.local", + "files": [ + "share" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr", + "files": [ + "src" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/usr/src", + "files": [ + "app" + ] + }, + { + "added": [ + "data", + "uploads" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/usr/src/app", + "files": [ + "data", + "uploads" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 45, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/cmintey/wishlist:v0.66.0", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-wishlist_wishlist_data", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-wishlist_wishlist_data/_data", + "target": "/data", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 1, + "name": "c5b4a3bf4591884b54c90465299d2c299b60a9692efc85699222663c7cc8efcb", + "sample": [ + "prod.db" + ], + "source": "/var/lib/docker/volumes/c5b4a3bf4591884b54c90465299d2c299b60a9692efc85699222663c7cc8efcb/_data", + "target": "/usr/src/app/data", + "writable_by_app": "yes" + }, + { + "class": "anonymous", + "files": 0, + "name": "0a719c708e8a1351e6e7661358e5f7376e1f81ad0bc6d883660aa79d29999fd4", + "sample": [], + "source": "/var/lib/docker/volumes/0a719c708e8a1351e6e7661358e5f7376e1f81ad0bc6d883660aa79d29999fd4/_data", + "target": "/usr/src/app/uploads", + "writable_by_app": "yes" + } + ], + "name": "wishlist", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "wishlist_data" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "wishlist: 1 file(s) in an ANONYMOUS volume at /usr/src/app/data \u2014 not in the compose `volumes:` block, so it is never backed up and a redeploy orphans it", + "wishlist: declared volume /data is EMPTY" + ], + "verdict": "BROKEN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify-evidence/zipline/probe.json b/audits/persistence-sweep-2026-08-02/state/verify-evidence/zipline/probe.json new file mode 100644 index 0000000..6094c1d --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify-evidence/zipline/probe.json @@ -0,0 +1,169 @@ +{ + "probe": { + "app": "zipline", + "containers": [ + { + "diff_added": 3, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "zipline" + ] + }, + { + "added": [ + "uploads", + "public" + ], + "changed_count": 0, + "db_signature": false, + "dir": "/zipline", + "files": [ + "uploads", + "public" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 5, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "ghcr.io/diced/zipline:4.6.1", + "mounts": [ + { + "class": "named-declared", + "files": 0, + "name": "volgate-zipline_zipline_public", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-zipline_zipline_public/_data", + "target": "/zipline/public", + "writable_by_app": "yes" + }, + { + "class": "named-declared", + "files": 0, + "name": "volgate-zipline_zipline_uploads", + "sample": [], + "source": "/var/lib/docker/volumes/volgate-zipline_zipline_uploads/_data", + "target": "/zipline/uploads", + "writable_by_app": "yes" + } + ], + "name": "zipline", + "restarts": null, + "status": "running", + "uid": 0 + }, + { + "diff_added": 2, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/", + "files": [ + "run" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 4, + "diff_unresolved": [], + "exit": 0, + "gid": 70, + "health": "healthy", + "image": "postgres:16-alpine", + "mounts": [ + { + "class": "named-declared", + "files": 1356, + "name": "volgate-zipline_zipline_postgres_data", + "sample": [ + "postmaster.opts", + "postgresql.conf", + "postgresql.auto.conf", + "pg_ident.conf", + "pg_hba.conf", + "postmaster.pid", + "PG_VERSION", + "base/1/826", + "base/1/2996", + "base/1/3764", + "base/1/2610_fsm", + "base/1/3603_fsm", + "base/1/1249_vm", + "base/1/3600_fsm", + "base/1/13512", + "base/1/3602", + "base/1/6228", + "base/1/4174", + "base/1/4157", + "base/1/4146", + "base/1/2611", + "base/1/2657", + "base/1/3079_vm", + "base/1/1247_vm", + "base/1/1259", + "base/1/3601_fsm", + "base/1/1417", + "base/1/4155", + "base/1/2668", + "base/1/2831", + "base/1/2841", + "base/1/3607", + "base/1/2836", + "base/1/1255_fsm", + "base/1/2615_vm", + "base/1/3575", + "base/1/2605_vm", + "base/1/2670", + "base/1/6106", + "base/1/4163" + ], + "source": "/var/lib/docker/volumes/volgate-zipline_zipline_postgres_data/_data", + "target": "/var/lib/postgresql/data", + "writable_by_app": "yes" + } + ], + "name": "zipline-postgres", + "restarts": null, + "status": "running", + "uid": 70 + } + ], + "declared_volumes": [ + "zipline_postgres_data", + "zipline_public", + "zipline_uploads" + ], + "env_keys": [ + "CORE_SECRET", + "DB_PASSWORD", + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [], + "ports": [] + }, + "reasons": [ + "zipline: this container's mounts are all empty while it created entries in ['/zipline'] \u2014 benign when a sibling container holds the state, worth a look when none does", + "zipline: declared volume /zipline/public is EMPTY", + "zipline: declared volume /zipline/uploads is EMPTY" + ], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/audits/persistence-sweep-2026-08-02/state/verify.log b/audits/persistence-sweep-2026-08-02/state/verify.log new file mode 100644 index 0000000..9658847 --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/verify.log @@ -0,0 +1,48 @@ +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +bentopdf UNDETERMINED nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is U +claper CLEAN claper: this container's mounts are all empty while it created entries in ['/app/priv', '/app/priv/static'] — benign when a sibling container holds th +docmost CLEAN docmost: this container's mounts are all empty while it created entries in ['/app/apps/client/dist'] — benign when a sibling container holds the state +glance UNDETERMINED glance: not running (status=restarting exit=1 restarts=None) +gramps-web BROKEN gramps-web: DATA in the writable layer at /app/users (db_signature=True, e.g. ['users.sqlite']); NOTHING this app wrote landed in ANY folder the templ +immich CLEAN immich-machine-learning: this container's mounts are all empty while it created entries in ['/', '/root', '/root/.config'] — benign when a sibling con +papra BROKEN papra: mount /app/data is NOT writable by the app's own uid=999 gid=999; papra: DATA in the writable layer at /app/app-data/db (db_signature=True, e.g +plant-it UNDETERMINED no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling + Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/pla +privatebin UNDETERMINED nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is U +recipe-importer UNDETERMINED NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. +sparkyfitness CLEAN sparkyfitness-server: this container's mounts are all empty while it created entries in ['/app/SparkyFitnessServer'] — benign when a sibling container +uptime-kuma UNDETERMINED nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is U +wanderer UNDETERMINED wanderer: unhealthy; wanderer: this container's mounts are all empty while it created entries in ['/root'] — benign when a sibling container holds the +wishlist BROKEN wishlist: 1 file(s) in an ANONYMOUS volume at /usr/src/app/data — not in the compose `volumes:` block, so it is never backed up and a redeploy orphans +zipline CLEAN zipline: this container's mounts are all empty while it created entries in ['/zipline'] — benign when a sibling container holds the state, worth a loo + +BROKEN — the app's data does NOT land where the template preserves it: + gramps-web + gramps-web: DATA in the writable layer at /app/users (db_signature=True, e.g. ['users.sqlite']) + NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/app', '/app/cache/request_cache', '/app/thumbnail_cache', '/root']. Needs adjudication. + gramps-web: this container's mounts are all empty while it created entries in ['/app', '/app/cache/request_cache', '/app/thumbnail_cache'] — benign when a sibling container holds the state, worth a look when none does + gramps-web: declared volume /app/data is EMPTY + gramps-web: declared volume /app/media is EMPTY + papra + papra: mount /app/data is NOT writable by the app's own uid=999 gid=999 + papra: DATA in the writable layer at /app/app-data/db (db_signature=True, e.g. ['db.sqlite']) + NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are empty, yet entries were created in ['/app']. Needs adjudication. + papra: this container's mounts are all empty while it created entries in ['/app'] — benign when a sibling container holds the state, worth a look when none does + papra: declared volume /app/data is EMPTY + wishlist + wishlist: 1 file(s) in an ANONYMOUS volume at /usr/src/app/data — not in the compose `volumes:` block, so it is never backed up and a redeploy orphans it + wishlist: declared volume /data is EMPTY + +UNDETERMINED — NOT a pass. The question was not answered for these: + bentopdf: nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN + glance: glance: not running (status=restarting exit=1 restarts=None) + plant-it: no containers created (compose up rc=1: msdeluise/plant-it:0.10.0 Pulling + Image msdeluise/plant-it:0.10.0 Error pull access denied for msdeluise/plant-it, repository does not exist or may require 'd + privatebin: nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN; privatebin: declared volum + recipe-importer: NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. Needs adjudication.; recipe-importer: this contai + uptime-kuma: nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data to locate. Health is not data: this is UNDETERMINED, not CLEAN; uptime-kuma: declared volu + wanderer: wanderer: unhealthy; wanderer: this container's mounts are all empty while it created entries in ['/root'] — benign when a sibling container holds the state, worth a look when none does; wanderer: dec + +5 clean · 3 broken · 7 undetermined (of 15 in scope) +volume-persistence gate REFUSED diff --git a/audits/persistence-sweep-2026-08-02/state/wishlist_proof.py b/audits/persistence-sweep-2026-08-02/state/wishlist_proof.py new file mode 100644 index 0000000..f5efdfd --- /dev/null +++ b/audits/persistence-sweep-2026-08-02/state/wishlist_proof.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""wishlist survives-a-redeploy proof, on REAL user data. + +`survive2.py` reported DATA-LOST because `prod.db`'s bytes changed across the redeploy — but that +rule cannot tell "the app modified its own database on boot" (fine; vaultwarden's WAL does the +same) from "the app deleted and recreated it" (data gone). Two things settle it: + + 1. the file's INODE — same object, or a new one? + 2. a row the USER created — still queryable after the redeploy, or not? + +(2) is the claim that actually matters, so it is the verdict. It is created through wishlist's own +HTTP API, never by writing into the volume by hand. +""" +import hashlib, importlib.util, json, os, re, secrets, shutil, tempfile, time +from pathlib import Path + +_spec = importlib.util.spec_from_file_location( + "cvp", "/opt/sweep/scripts/check-volume-persistence.py") +cvp = importlib.util.module_from_spec(_spec); _spec.loader.exec_module(cvp) + +TPL = Path("/opt/sweep/templates/wishlist") +EV = Path("/opt/sweep/evidence/wishlist") + + +def curl(url, *a): + return cvp._sh(["curl", "-sS", "--max-time", "25", *a, url], timeout=45) + + +def stat_db(mounts): + """(inode, sha256, size) of prod.db, read host-side from the volume itself.""" + for m in mounts: + if m["target"] == "/usr/src/app/data" and m["source"]: + f = os.path.join(m["source"], "prod.db") + if os.path.isfile(f): + st = os.lstat(f) + return {"inode": st.st_ino, "size": st.st_size, + "sha256": hashlib.sha256(open(f, "rb").read()).hexdigest()} + return None + + +def sqlite_rows(mounts, table): + """Count rows in `table` by reading the volume's DB with a throwaway sqlite container — + a READ of what the app wrote, never a write into the volume.""" + for m in mounts: + if m["target"] == "/usr/src/app/data" and m["source"]: + r = cvp._sh(["docker", "run", "--rm", "-v", f"{m['name']}:/v:ro", + "keinos/sqlite3:latest", "sqlite3", "/v/prod.db", + f"SELECT COUNT(*) FROM {table};"], timeout=180) + return (r.stdout or r.stderr or "").strip() + return "no-db" + + +def up(base, wait=300, settle=40): + cvp._sh(base + ["up", "-d"], timeout=1200) + cids = [c for c in cvp._sh(base + ["ps", "-aq"], timeout=120).stdout.split() if c] + dl = time.time() + wait + while time.time() < dl: + if not any(((cvp._inspect(c) or {}).get("State") or {}).get("Status") in ("created", "restarting") + or ((((cvp._inspect(c) or {}).get("State") or {}).get("Health") or {}).get("Status") + == "starting") for c in cids): + break + time.sleep(10) + ip = None + for c in cids: + for n in ((cvp._inspect(c) or {}).get("NetworkSettings") or {}).get("Networks", {}).values(): + if n.get("IPAddress"): + ip = n["IPAddress"] + mounts = [] + for c in cids: + info = cvp._inspect(c) or {} + for m in (info.get("Mounts") or []): + mounts.append({"target": m.get("Destination"), "source": m.get("Source"), + "name": m.get("Name")}) + time.sleep(settle) + return cids, ip, mounts + + +if __name__ == "__main__": + EV.mkdir(parents=True, exist_ok=True) + work = Path(tempfile.mkdtemp(prefix="wl-")) + shutil.copy(TPL / "docker-compose.yml", work / "docker-compose.yml") + env = cvp.build_env("wishlist", (TPL / ".felhom.yml").read_text(), + (TPL / "docker-compose.yml").read_text()) + (work / ".env").write_text("".join(f"{k}={v}\n" for k, v in sorted(env.items()))) + base = ["docker", "compose", "-p", "wlproof", "--project-directory", str(work), + "-f", str(work / "docker-compose.yml")] + cvp._sh(["docker", "network", "create", "traefik-public"], timeout=60) + log = [] + try: + cids, ip, mounts = up(base) + # --- seed REAL user data through wishlist's own API + email = f"proof-{secrets.token_hex(4)}@felhom.invalid" + pw = "Proof-" + secrets.token_hex(10) # never recorded + r = curl(f"http://{ip}:3000/api/auth/signup", "-X", "POST", "-i", + "-H", "Content-Type: application/json", + "-d", json.dumps({"email": email, "password": pw, "name": "Proof", + "username": "proofuser"})) + log.append("signup -> " + ((r.stdout or "?").splitlines() or ["?"])[0].strip()) + time.sleep(20) + + before_db = stat_db(mounts) + before_users = sqlite_rows(mounts, "user") + log.append(f"before: db={before_db} users={before_users}") + + cvp._sh(base + ["down"], timeout=900) # a REDEPLOY — no -v + cids, ip, mounts = up(base) + after_db = stat_db(mounts) + after_users = sqlite_rows(mounts, "user") + log.append(f"after : db={after_db} users={after_users}") + + same_inode = bool(before_db and after_db and before_db["inode"] == after_db["inode"]) + rows_kept = (before_users.isdigit() and after_users.isdigit() + and int(after_users) >= int(before_users) and int(before_users) > 0) + res = {"app": "wishlist", "before": before_db, "after": after_db, + "user_rows_before": before_users, "user_rows_after": after_users, + "same_inode": same_inode, "user_rows_preserved": rows_kept, + "verdict": ("SURVIVES" if same_inode and rows_kept + else "SURVIVES (inode only, no seeded rows)" if same_inode + else "DATA LOST"), + "log": log} + (EV / "survive-wishlist-content.json").write_text(json.dumps(res, indent=2, sort_keys=True)) + print(json.dumps({k: v for k, v in res.items() if k != "log"}, indent=2)) + finally: + cvp._sh(base + ["down", "-v", "--remove-orphans"], timeout=900) + shutil.rmtree(work, ignore_errors=True) diff --git a/scripts/check-volume-persistence.py b/scripts/check-volume-persistence.py new file mode 100644 index 0000000..d6a1b91 --- /dev/null +++ b/scripts/check-volume-persistence.py @@ -0,0 +1,877 @@ +#!/usr/bin/env python3 +"""check-volume-persistence.py — catalog gate: the folder a template preserves must be the +folder the app actually writes to. + +The third catalog gate, and the only RUNTIME one. Its two siblings are static: +`check-image-pins.py` proves a template names a concrete tag, `check-image-resolvable.py` proves +that tag still exists. Neither can see where an app puts its data, and **neither can any static +check** — that was measured, not assumed: over all 53 templates a static audit of the compose +files (declared volumes attached, no anonymous mounts, no stray host binds) reports the catalog +completely clean, and it reports **papra** clean too. papra's compose is well-formed. It mounts +`papra_data` at `/app/data`. The application writes its database to `/app/app-data/db/db.sqlite`, +in the container's writable layer, and cannot write to `/app/data` at all. + +The consequence (R-156, Campaign 10): the app runs, the healthcheck is green, the tier-1/tier-2 +backup completes and verifies — and it contains an empty directory. `DumpAppVolumes` +(felhom-controller `internal/backup/backup.go:543`) tars the volume, and the volume holds nothing. +A backup that fails loudly gets fixed; a backup that succeeds while holding nothing is discovered +when someone needs it. + + python3 scripts/check-volume-persistence.py # every AVAILABLE app + python3 scripts/check-volume-persistence.py --all # include hidden/abandoned apps + python3 scripts/check-volume-persistence.py papra … # only these app dirs + +Exit codes: 0 every app in scope CLEAN · 1 at least one BROKEN (the gate REFUSES) · + 2 nothing could be decided / the prober failed its own self-test. + +Requires Docker, network, and several minutes per app, so it is a PERIODIC gate like +`check-image-resolvable.py` — run it when a template's `volumes:` block or image tag changes, and +at the start of every catalog campaign. `classify()` is pure and unit-tested with no Docker +(`scripts/test_check_volume_persistence.py`). + +WHY THE PROBER SELF-TESTS ON EVERY RUN. A detector that flags nothing is worse than no detector: +it converts an unexamined catalog into a documented-clean one. So before this gate is allowed to +report anything, it runs two synthetic canary templates through the *same* prober — one built to +the exact R-156 signature, one built to write correctly into its volume — and refuses to issue a +verdict unless it calls the first BROKEN and the second CLEAN. A green run therefore always +carries a live proof that the instrument discriminates. + +NO `docker exec` ANYWHERE. Campaign 7 §1.1 recorded that `docker exec` writes its OCI error to +STDOUT, so a missing binary read as present and the whole healthcheck audit reported every app +honest. Every observation here comes from `docker inspect`, `docker diff`, `/proc` and the host +filesystem, so a distroless or shell-less image is observed exactly like any other. +""" +import hashlib +import json +import os +import re +import secrets +import shutil +import stat +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +CLEAN, BROKEN, UNDETERMINED = "CLEAN", "BROKEN", "UNDETERMINED" + +LIFECYCLE_RE = re.compile(r"""^lifecycle:\s*["']?([a-z]+)""", re.MULTILINE) +VAR_RE = re.compile(r"\$\{([A-Z0-9_]+)\}") +DIFF_RE = re.compile(r"^([ACD])\s+(.*)$") +PORT_RE = re.compile(r"loadbalancer\.server\.port=(\d+)") + +# Where the controller resolves the felhom path variables to at deploy time +# (felhom-controller `internal/stacks/deploy.go:567-582`). Any host path here is scratch. +SCRATCH_HDD = "/srv/felhom-gate/hdd" +SCRATCH_IMPORT = "/srv/felhom-gate/import" + +# ---------------------------------------------------------------------------- data vs noise +# +# `docker diff` is noisy. These three rules decide what counts as the customer's data, and they +# are the part of this gate most likely to need judgement — they are deliberately explicit rather +# than buried in a heuristic. + +# 1. NOISE — content that is never customer data. Losing it costs a restart, nothing more. +NOISE_PREFIX = ( + "/tmp/", "/var/tmp/", "/run/", "/var/run/", "/proc/", "/sys/", "/dev/", + "/var/log/", "/var/cache/", "/var/lib/apt/", "/var/lib/dpkg/", "/var/spool/", + "/usr/share/", "/usr/lib/", "/usr/local/lib/", "/usr/local/share/", "/lib/", "/bin/", "/sbin/", + "/var/lib/nginx/", "/var/lib/php/", "/etc/ssl/", "/etc/nginx/", "/etc/apache2/", + "/var/lib/misc/", "/var/backups/", "/root/.cache/", "/root/.npm/", "/root/.local/share/", + "/home/node/.npm/", "/var/lib/systemd/", +) +NOISE_SEGMENT = ("/__pycache__/", "/.cache/", "/node_modules/", "/.git/", "/.next/cache/", + "/tmp/", "/temp/", "/.pytest_cache/") +NOISE_SUFFIX = (".pid", ".sock", ".log", ".pyc", ".pyo", ".swp") +NOISE_EXACT = ("/etc/hosts", "/etc/hostname", "/etc/resolv.conf", "/etc/passwd", "/etc/group", + "/etc/shadow", "/etc/localtime", "/etc/timezone", "/etc/mtab", "/etc/machine-id") + +# 2. DB SIGNATURE — a filename that PROVES a database lives in that directory. This is the +# strongest signal available and needs no path heuristics at all. +# +# `postgresql.conf` was in this list and has been REMOVED: it is a CONFIG file, not a data +# file, and the postgres entrypoint writes one to /etc/postgresql at init. That called immich +# BROKEN while its database sat correctly in its volume with 1831 files. `PG_VERSION` and +# `pg_control` are the real markers of a PGDATA directory, so removing it opens no blind spot — +# a genuinely misplaced PGDATA still trips both of those. +DB_FILE_RE = re.compile( + r"(\.sqlite3?$|\.sqlite\d*$|\.db$|\.db3$|\.db-wal$|\.db-shm$|-wal$|-shm$" + r"|^PG_VERSION$|^pg_control$|^ib_logfile|^ibdata|\.frm$|\.ibd$|\.MYD$|\.MYI$" + r"|^dump\.rdb$|\.aof$|\.rdb$|^data\.mdb$|^lock\.mdb$|^CURRENT$|^MANIFEST-|\.ldb$|\.sst$" + r"|^data\.ms$|^index\.bleve|\.duckdb$|\.bolt$|\.badger$|\.leveldb$)", re.I) + +# 3. DATA TOKEN — a path that says "app state". THIS RULE DOES NOT CONVICT ON ITS OWN; it is +# reported for judgement. That demotion is evidence-driven: across the sweep its true positives +# were all also caught by rule 2, while it produced false positives on three separate apps — +# calibre-web (`cps/static/css/images/**`), crafty-controller (`…/crafty/config/__pycache__`) +# and onlyoffice, which unpacks its OWN static assets into the writable layer at first boot +# (plugin icons, slide-theme `media/`, `web-apps/apps/api/documents/api.js` — 2560 added +# entries) while its real data mount received data normally. Vocabulary is not evidence: a +# directory called `media` holds customer photos in one app and shipped clip-art in the next. +# A gate that cries wolf gets ignored, and then it protects nothing — the resolvability gate's +# own recorded lesson. +DATA_TOKEN_RE = re.compile( + r"(^|/)(data|database|db|storage|store|upload|uploads|media|library|libraries|config|" + r"appdata|app-data|documents?|photos?|images?|files?|backups?|vault|repositor(y|ies)|" + r"attachments?|state|var/lib/(postgresql|mysql|mariadb|redis|mongodb|influxdb)|" + r"conf|settings|sessions?|index|search|metadata|thumbnails?)(/|$)", re.I) + + +def is_noise(path: str) -> bool: + """A trailing slash is appended before the segment test ON PURPOSE. + + `docker diff` reports directories as well as files, so a bytecode cache appears as the bare + entry `A …/routes/api/crafty/config/__pycache__` while its `.pyc` children are filtered by + suffix. Matching `/__pycache__/` against the un-terminated path misses the directory itself, + leaving it as the only surviving entry under `…/config` — which scored as data and called + crafty-controller BROKEN four times over, on an app whose data had landed correctly. + """ + p = ("/" + path.lstrip("/")).lower() + return (p in NOISE_EXACT + or any(p.startswith(x) for x in NOISE_PREFIX) + or any(x in p + "/" for x in NOISE_SEGMENT) + or any(p.endswith(x) for x in NOISE_SUFFIX)) + + +def is_noise_dir(path: str) -> bool: + """`is_noise` for a MOUNT TARGET rather than a file path. + + The prefixes are written with a trailing slash (`/run/`), so a bare `/run` does not match one. + That gap made every mount rule below blind to runtime directories: privatebin's image declares + `VOLUME /run`, docker made it an anonymous volume, and 14 entries — `nginx.pid`, + `php-fpm.sock`, s6 supervision fifos — read as unbacked-up customer data. Losing /run costs a + restart and nothing else. + """ + return is_noise((path or "").rstrip("/") + "/_") + + +def rollup_diff(entries): + """Group writable-layer entries by directory and split DATA from everything else. + + `docker diff` reports changes to the container's writable layer and EXCLUDES every mounted + path, so a file appearing here is definitionally in no volume and no bind: it does not + survive a redeploy and no backup can ever contain it. + + `A` VERSUS `C` IS THE WHOLE DIFFICULTY, and getting it wrong in either direction is fatal: + + A (added) — the app CREATED this file. Unambiguous: it exists only in the writable layer. + papra's `/app/app-data/db/db.sqlite` is an `A`. + C (changed) — a file that SHIPS IN THE IMAGE was touched. Usually a chown/chmod sweep and + completely benign: linuxserver.io entrypoints re-own the whole application + tree, which made calibre-web report 1305 `C` entries including + `cps/static/css/images/**` — 92 PNGs of static UI furniture. Treating those as + customer data called a clean app BROKEN on the first pass of this sweep. + + So DATA is decided from `A` entries only, and `C` on a database-signature file is held back + as SUSPECT — genuinely ambiguous, because an app writing into a DB that ships in its image + produces exactly the same verb. `adjudicate_suspects()` settles those by comparing bytes. + """ + dirs = {} + for kind, path in entries: + if kind == "D": + continue + p = "/" + path.lstrip("/") + if is_noise(p): + continue + d = os.path.dirname(p) or "/" + base = os.path.basename(p) + e = dirs.setdefault(d, {"added": [], "changed": [], "db_added": False, "db_changed": []}) + if kind == "A": + if len(e["added"]) < 40: + e["added"].append(base) + if base and DB_FILE_RE.search(base): + e["db_added"] = True + else: + if len(e["changed"]) < 40: + e["changed"].append(base) + if base and DB_FILE_RE.search(base): + e["db_changed"].append(p) + + data, token, suspect, other = [], [], [], [] + for d, e in sorted(dirs.items()): + rec = {"dir": d, "files": e["added"] or e["changed"], "added": e["added"], + "changed_count": len(e["changed"]), "db_signature": e["db_added"]} + # NOTE: there is deliberately no second "are all this directory's children noise?" filter + # here. One was written and removed: `e["added"]` can only ever contain entries that + # already passed `is_noise` above, so the check is always False — dead code wearing the + # costume of a safeguard. The single entry-level filter is the whole mechanism, and + # `test_a_bytecode_cache_DIRECTORY_is_noise` is what pins it. + if e["added"] and e["db_added"]: + data.append(rec) # rule 2 — CONVICTS + elif e["added"] and DATA_TOKEN_RE.search(d): + token.append(rec) # rule 3 — reported for judgement, never convicts + elif e["db_changed"]: + suspect.append({"dir": d, "paths": e["db_changed"], "db_signature": True, + "files": [os.path.basename(x) for x in e["db_changed"]]}) + else: + other.append(rec) + return data, token, suspect, other + + +def adjudicate_suspects(cid, image, suspects): + """Settle a `C` on a database-signature file by BYTES, not by guessing. + + A chown leaves the content identical; an app writing into a shipped database does not. So the + file is copied out of the running container and out of a pristine container made from the same + image, and the two are compared. Identical → benign, the app's data is not here. Different → + the app IS writing into an image-layer file, which is the same defect as papra's with a + different verb. + + Returns (confirmed, benign, unresolved) — `unresolved` is never folded into either. + """ + confirmed, benign, unresolved = [], [], [] + if not suspects: + return confirmed, benign, unresolved + ref = _sh(["docker", "create", image], timeout=300) + refid = ref.stdout.strip().splitlines()[-1] if ref.returncode == 0 and ref.stdout.strip() else "" + tmp = tempfile.mkdtemp(prefix="volgate-adj-") + try: + for s in suspects: + for path in s["paths"]: + live = os.path.join(tmp, "live") + orig = os.path.join(tmp, "orig") + a = _sh(["docker", "cp", f"{cid}:{path}", live], timeout=300) + b = _sh(["docker", "cp", f"{refid}:{path}", orig], timeout=300) if refid else None + if a.returncode != 0 or not refid or b.returncode != 0: + unresolved.append({**s, "why": "could not read both copies"}) + continue + try: + lb, ob = open(live, "rb").read(), open(orig, "rb").read() + except OSError: + unresolved.append({**s, "why": "unreadable"}) + continue + finally: + for f in (live, orig): + if os.path.exists(f): + os.remove(f) + if lb == ob: + benign.append({**s, "why": "byte-identical to the image copy — a chown/chmod " + "sweep, not a write"}) + else: + confirmed.append({**s, "why": f"DIFFERS from the image copy " + f"({len(ob)} B -> {len(lb)} B) — the app is " + f"writing into an image-layer file"}) + finally: + if refid: + _sh(["docker", "rm", "-f", refid], timeout=120) + shutil.rmtree(tmp, ignore_errors=True) + return confirmed, benign, unresolved + + +# ---------------------------------------------------------------------------- the verdict (pure) + +def classify(probe: dict): + """PURE. Turn one probe into (status, [reasons]). Unit-tested without Docker. + + BROKEN — positive evidence that data does not land where the template preserves it: + (a) DATA in a container's writable layer — never persisted, never backed up + (b) an app-data mount its own uid cannot write — R-156's second leg + (c) data in an ANONYMOUS volume — survives a restart, but is absent + from `ResolveDockerVolumeNames` (felhom-controller `internal/appbackup/appdata.go`, which + only ever returns `_` for volumes DECLARED in the compose file), so it is + never backed up, and a `down` + `up` orphans it. + UNDETERMINED — the question was not answered: a container never reached running/healthy, or + nothing was written anywhere so there is no data to locate. **Never folded into CLEAN.** + CLEAN — something was written, all of it inside a declared named volume or a bind, nothing + data-classified in any writable layer, every mount writable by its app uid. + """ + broken, undet, notes, structural = [], [], [], [] + if probe.get("error"): + return UNDETERMINED, [probe["error"]] + containers = probe.get("containers") or [] + if not containers: + return UNDETERMINED, ["no containers were created"] + + wrote_anything = False + # Structural check, independent of every path heuristic below. The rules that name data by + # its path can only ever recognise the shapes someone thought of: gramps-web writes its actual + # family tree to /root/.gramps/grampsdb// as database.txt + name.txt, which carries no + # database-signature filename and no data token, so it was MISSED entirely on the first pass + # while a second defect in the same app was caught. This asks a question that needs no + # vocabulary — did ANY of what the app created land in ANY folder the template preserves? + # + # ASKED PER APP, NOT PER CONTAINER, and that is the whole difficulty. Per container it fired on + # docmost, immich and claper — three CORRECT apps, all the same shape: the app container's only + # volume is for user uploads and is legitimately empty on a fresh install, while every byte of + # real state sits in the sibling database container's volume (1540, 1833 and 1470 files + # respectively). Per app it stays silent on all three and still catches gramps-web, whose + # single container had every mount empty. The per-container observation is kept as a NOTE so + # nothing is silently dropped. + running = [c for c in containers if c.get("status") == "running"] + app_persisted = [m for c in running for m in (c.get("mounts") or []) + if m["class"] != "tmpfs" and not is_noise_dir(m.get("target"))] + app_outside = [(c["name"], d) for c in running + for d in (c.get("diff_other_dirs") or []) if d.get("added")] + for c in running: + mine = [m for m in (c.get("mounts") or []) + if m["class"] != "tmpfs" and not is_noise_dir(m.get("target"))] + outside = [d for d in (c.get("diff_other_dirs") or []) if d.get("added")] + if mine and outside and all(m.get("files", 0) == 0 for m in mine): + notes.append( + f"{c['name']}: this container's mounts are all empty while it created entries in " + f"{[d['dir'] for d in outside][:3]} — benign when a sibling container holds the " + f"state, worth a look when none does") + if app_persisted and app_outside and all(m.get("files", 0) == 0 for m in app_persisted): + structural.append( + f"NOTHING this app wrote landed in ANY folder the template preserves: all " + f"{len(app_persisted)} mount(s) across {len(running)} container(s) are empty, yet " + f"entries were created in {[d['dir'] for _, d in app_outside][:4]}. Needs adjudication.") + for c in containers: + nm = c["name"] + if c.get("status") != "running": + undet.append(f"{nm}: not running (status={c.get('status')} exit={c.get('exit')} " + f"restarts={c.get('restarts')})") + continue + if c.get("health") == "unhealthy": + undet.append(f"{nm}: unhealthy") + for m in c.get("mounts") or []: + if m["class"] == "tmpfs" or is_noise_dir(m.get("target")): + continue # /run, /tmp, /var/log … — runtime state, not customer data + if m.get("files", 0) > 0: + wrote_anything = True + if m["class"] == "anonymous": + broken.append(f"{nm}: {m['files']} file(s) in an ANONYMOUS volume at " + f"{m['target']} — not in the compose `volumes:` block, so it is " + f"never backed up and a redeploy orphans it") + if m.get("writable_by_app") == "NO" and m["class"] in ("named-declared", "bind", + "anonymous"): + broken.append(f"{nm}: mount {m['target']} is NOT writable by the app's own " + f"uid={c.get('uid')} gid={c.get('gid')}") + if m["class"] == "named-declared" and m.get("files", 0) == 0: + notes.append(f"{nm}: declared volume {m['target']} is EMPTY") + for d in c.get("diff_data_dirs") or []: + wrote_anything = True + broken.append(f"{nm}: DATA in the writable layer at {d['dir']} " + f"(db_signature={d.get('db_signature')}, e.g. {d['files'][:4]})" + + (f" [{d['why']}]" if d.get("why") else "")) + for d in c.get("diff_token_dirs") or []: + # Rule 3 never convicts. It is surfaced so a human decides, and counted as evidence + # that the app wrote SOMETHING (so an app is not called idle when it plainly was not). + wrote_anything = True + notes.append(f"{nm}: writable-layer writes at {d['dir']} (path suggests state, no " + f"database signature — judgement needed): {d['added'][:4]}") + for d in c.get("diff_benign_db_touches") or []: + notes.append(f"{nm}: {d['dir']} — database file(s) touched but byte-identical to the " + f"image; a chown sweep, not a write") + for d in c.get("diff_unresolved") or []: + # Never fold an unresolved suspect into CLEAN. + undet.append(f"{nm}: could not decide whether {d['dir']} holds live data " + f"({d.get('why')})") + + # `structural` is always shown, whatever the verdict, and on its own it is enough to withhold + # a clean bill of health. + if broken: + return BROKEN, broken + structural + notes + if undet or structural: + return UNDETERMINED, undet + structural + notes + if not wrote_anything: + return UNDETERMINED, ["nothing was written to any mount and nothing data-classified in any " + "writable layer — the app produced no data to locate. Health is not " + "data: this is UNDETERMINED, not CLEAN"] + notes + return CLEAN, notes + + +# ---------------------------------------------------------------------------- the prober (Docker) + +def _sh(args, timeout=180): + try: + return subprocess.run(args, capture_output=True, text=True, timeout=timeout) + except (subprocess.TimeoutExpired, OSError) as e: + return subprocess.CompletedProcess(args, 124, "", f"{e}") + + +def parse_deploy_fields(text: str): + """Minimal `deploy_fields:` reader — env_var / type / default / generate. + + Line-based on purpose: this repo ships no requirements file and the two sibling gates parse + `.felhom.yml` the same way, so the gate keeps working with a bare python3. + """ + fields, cur, indent = [], None, None + in_block = False + for line in text.splitlines(): + if re.match(r"^deploy_fields:\s*$", line): + in_block, cur = True, None + continue + if in_block: + if line.strip() and not line.startswith((" ", "\t")): + break # a new top-level key ends the block + m = re.match(r"^(\s*)-\s+env_var:\s*[\"']?([A-Za-z0-9_]+)", line) + if m: + if cur: + fields.append(cur) + indent, cur = len(m.group(1)), {"env_var": m.group(2)} + continue + if cur is not None: + m = re.match(r"^\s+(type|default|generate):\s*(.*?)\s*$", line) + if m: + cur[m.group(1)] = m.group(2).strip().strip('"').strip("'") + if cur: + fields.append(cur) + return fields + + +def _gen(spec, ftype): + """Mirror `generateValue` in felhom-controller `internal/stacks/deploy.go:870`. + + It has to be the SAME value shape the controller mints, or the gate measures an app the + customer never runs. `base64key` is the one that bites: the controller returns + `"base64:" + b64` (deploy.go:904) because Laravel's APP_KEY is invalid without the prefix — + dropping it leaves bookstack serving 500s, which reads as an app defect and is a harness bug. + """ + if spec: + kind, _, n = spec.partition(":") + n = int(n) if n.isdigit() else 32 + if kind == "hex": + return secrets.token_hex(n) + if kind in ("password", "secret"): + alph = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789" + return "".join(secrets.choice(alph) for _ in range(n)) + if kind == "base64key": + import base64 + return "base64:" + base64.b64encode(secrets.token_bytes(n)).decode() + if kind == "static": + return spec.partition(":")[2] + if ftype in ("password", "secret", "secret_input"): + return "Gate" + secrets.token_hex(12) + return "" + + +def build_env(app: str, felhom_text: str, compose_text: str): + """Resolve every `${VAR}` the compose uses. Generated values are never printed or written to + any artifact — only the KEYS are, per the repo's no-secrets rule.""" + sub = re.search(r"^subdomain:\s*[\"']?([a-z0-9-]+)", felhom_text, re.M) + env = {"DOMAIN": "gate.invalid", "SUBDOMAIN": sub.group(1) if sub else app, + "HDD_PATH": SCRATCH_HDD, "USERDATA_PATH": SCRATCH_HDD + "/userdata", + "IMPORT_PATH": SCRATCH_IMPORT, "TZ": "Europe/Budapest"} + for f in parse_deploy_fields(felhom_text): + var, t = f["env_var"], f.get("type", "text") + if t == "path": + env[var] = SCRATCH_HDD + elif f.get("default"): + env[var] = f["default"] + elif t == "domain": + env[var] = "gate.invalid" + elif t == "subdomain": + env[var] = env["SUBDOMAIN"] + else: + env[var] = _gen(f.get("generate"), t) + for var in set(VAR_RE.findall(compose_text)): + # A var that resolves to "" binds a bogus root-owned dir at the container root + # (felhom-controller deploy.go:571) — never leave one unset. + env.setdefault(var, "g" + hashlib.sha256(var.encode()).hexdigest()[:20]) + return env + + +def _inspect(cid): + r = _sh(["docker", "inspect", cid]) + try: + return json.loads(r.stdout)[0] + except Exception: + return None + + +def _uid(info): + """The uid PID 1 ACTUALLY runs as, from /proc — `Config.User` is frequently empty even when + the image drops privileges inside its entrypoint, which is exactly papra's shape.""" + pid = (info.get("State") or {}).get("Pid") or 0 + if pid: + try: + uid = gid = None + for line in open(f"/proc/{pid}/status"): + if line.startswith("Uid:"): + uid = int(line.split()[1]) + elif line.startswith("Gid:"): + gid = int(line.split()[1]) + if uid is not None: + return uid, gid + except OSError: + pass + u = (info.get("Config") or {}).get("User") or "" + a, _, b = u.partition(":") + return (int(a) if a.isdigit() else None, int(b) if b.isdigit() else None) + + +def _classify_mount(m, project, declared): + t = m.get("Type") + if t in ("tmpfs", "bind"): + return t + if t == "volume": + name = m.get("Name") or "" + if re.fullmatch(r"[0-9a-f]{64}", name): + return "anonymous" + if name.startswith(project + "_") and name[len(project) + 1:] in declared: + return "named-declared" + return "named-external" + return t or "unknown" + + +def _walk(src, limit=4000): + if not src or not os.path.isdir(src): + return -1, [] + n, sample = 0, [] + for dp, _, fns in os.walk(src, onerror=lambda e: None): + for f in fns: + n += 1 + if len(sample) < 40: + sample.append(os.path.relpath(os.path.join(dp, f), src)) + if n > limit: + return n, sample + return n, sample + + +def _writable(src, uid, gid): + """Can uid/gid create a file in src? Decided from the host-side owner/mode — the same + question R-156 answered with `touch` inside the container, without needing a shell there.""" + if uid is None: + return "unknown-uid" + if not src or not os.path.isdir(src): + return "no-source" + if uid == 0: + return "yes" + try: + st = os.stat(src) + except OSError: + return "stat-error" + if st.st_uid == uid and st.st_mode & stat.S_IWUSR: + return "yes" + if gid is not None and st.st_gid == gid and st.st_mode & stat.S_IWGRP: + return "yes" + return "yes" if st.st_mode & stat.S_IWOTH else "NO" + + +PATHS_FIRST = ("/",) +PATHS_DEEP = ("/", "/login", "/setup", "/signup", "/register", "/install", "/admin", + "/api/health", "/health", "/healthz", "/status", "/web", "/index.php", "/dashboard") + + +def _exercise(cids, ports, deep=False): + """Minimum exercise: an HTTP request the app's OWN router answers. + + A container that has only started may have written nothing, and health-check-passing is not + data-writing — conflating the two is precisely what let papra look fine. Any status code + (including 3xx/4xx/5xx) proves the request reached application code; `000` does not. + """ + hits = [] + for cid in cids: + info = _inspect(cid) or {} + for net in ((info.get("NetworkSettings") or {}).get("Networks") or {}).values(): + ip = net.get("IPAddress") + if not ip: + continue + for port in ports: + for path in (PATHS_DEEP if deep else PATHS_FIRST): + a = ["curl", "-sS", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "20"] + if deep: + a += ["-L", "--max-redirs", "5"] + code = _sh(a + [f"http://{ip}:{port}{path}"], timeout=40).stdout.strip() + if code and code != "000": + hits.append(f"{ip}:{port}{path} -> {code}") + if not deep: + break + return hits + + +def docker_prober(app: str, app_dir: Path, settle: int = 45, wait: int = 300) -> dict: + """Deploy the template, exercise it, and report WHERE the data landed. The Docker seam. + + Everything Docker-touching lives here so `classify()` stays pure and testable. + """ + compose_src = app_dir / "docker-compose.yml" + felhom_src = app_dir / ".felhom.yml" + if not compose_src.is_file(): + return {"app": app, "error": "no docker-compose.yml"} + compose_text = compose_src.read_text(encoding="utf-8") + felhom_text = felhom_src.read_text(encoding="utf-8") if felhom_src.is_file() else "" + + work = Path(tempfile.mkdtemp(prefix=f"volgate-{app}-")) + project = "volgate-" + re.sub(r"[^a-z0-9]+", "", app.lower()) + cf = work / "docker-compose.yml" + shutil.copy(compose_src, cf) + env = build_env(app, felhom_text, compose_text) + (work / ".env").write_text("".join(f"{k}={v}\n" for k, v in sorted(env.items())), + encoding="utf-8") + os.makedirs(SCRATCH_HDD + "/userdata", exist_ok=True) + os.makedirs(SCRATCH_IMPORT, exist_ok=True) + _sh(["docker", "network", "create", "traefik-public"], timeout=60) # templates expect it + base = ["docker", "compose", "-p", project, "--project-directory", str(work), "-f", str(cf)] + + try: + cfg = _sh(base + ["config", "--format", "json"], timeout=180) + try: + resolved = json.loads(cfg.stdout) + except Exception: + return {"app": app, "error": f"compose config failed: " + f"{(cfg.stderr or cfg.stdout)[:300]}"} + declared = set((resolved.get("volumes") or {}).keys()) + ports = sorted({int(m.group(1)) + for svc in (resolved.get("services") or {}).values() + for lbl in ((svc.get("labels") or {}).values() + if isinstance(svc.get("labels"), dict) + else (svc.get("labels") or [])) + for m in [PORT_RE.search(str(lbl))] if m}) + + up = _sh(base + ["up", "-d"], timeout=1800) + cids = [c for c in _sh(base + ["ps", "-aq"], timeout=120).stdout.split() if c] + if not cids: + return {"app": app, "error": f"no containers created (compose up rc={up.returncode}: " + f"{(up.stderr or '')[-300:]})"} + + deadline = time.time() + wait + while time.time() < deadline: + pend = False + for cid in cids: + st = (_inspect(cid) or {}).get("State") or {} + if st.get("Status") in ("created", "restarting") or \ + (st.get("Health") or {}).get("Status") == "starting": + pend = True + if not pend: + break + time.sleep(10) + + running = [c for c in cids + if ((_inspect(c) or {}).get("State") or {}).get("Status") == "running"] + hits = _exercise(running, ports) if (running and ports) else [] + time.sleep(settle) + + def observe(): + out = [] + for cid in cids: + info = _inspect(cid) + if not info: + continue + uid, gid = _uid(info) + mounts = [] + for m in (info.get("Mounts") or []): + n, sample = _walk(m.get("Source")) + mounts.append({"target": m.get("Destination"), + "class": _classify_mount(m, project, declared), + "name": m.get("Name"), "source": m.get("Source"), + "files": n, "sample": sample, + "writable_by_app": _writable(m.get("Source"), uid, gid)}) + entries = [(g.group(1), g.group(2)) + for line in _sh(["docker", "diff", cid], timeout=180).stdout.splitlines() + for g in [DIFF_RE.match(line)] if g] + data, token, suspect, other = rollup_diff(entries) + image = (info.get("Config") or {}).get("Image") + conf, benign, unres = adjudicate_suspects(cid, image, suspect) + st = info.get("State") or {} + out.append({"name": (info.get("Name") or cid).lstrip("/"), + "image": image, + "status": st.get("Status"), + "health": (st.get("Health") or {}).get("Status"), + "exit": st.get("ExitCode"), "restarts": st.get("RestartCount"), + "uid": uid, "gid": gid, "mounts": mounts, + "diff_total": len(entries), + "diff_added": sum(1 for k, _ in entries if k == "A"), + "diff_data_dirs": data + conf, + "diff_token_dirs": token, + "diff_benign_db_touches": benign, + "diff_unresolved": unres, + "diff_other_dirs": other}) + return out + + def nothing_written(cs): + return not any(c["diff_data_dirs"] or c["diff_token_dirs"] + or any(m["class"] != "tmpfs" and m["files"] > 0 for m in c["mounts"]) + for c in cs) + + containers = observe() + # Second chance before declaring the question unanswerable: walk a wider path list + # following redirects, so a first-run wizard is actually reached. + if nothing_written(containers) and running and ports: + hits += _exercise(running, ports, deep=True) + time.sleep(90) + containers = observe() + + return {"app": app, "ports": ports, "exercise": hits, + "declared_volumes": sorted(declared), "containers": containers, + "env_keys": sorted(env)} + finally: + # `compose down -v` removes THIS project's volumes and nothing else. Deliberately NOT + # `docker volume prune -f`: that is a GLOBAL sweep of every unused volume on the host, + # which on any box also running real stacks would delete data this gate never created. + # The workspace CLAUDE.md bans exactly this class of global Docker cleanup. + _sh(base + ["down", "-v", "--remove-orphans"], timeout=900) + shutil.rmtree(work, ignore_errors=True) + + +# ---------------------------------------------------------------------------- canary self-test + +# A purpose-built canary image reproducing papra's exact shape: `/app/data` exists and is +# root-owned, `/app/app-data` exists and belongs to the app's own non-root uid. A fresh named +# volume inherits the ownership of whatever directory it is mounted over, so mounting at +# /app/data yields a volume the app cannot write, and mounting at /app/app-data yields one it can. +# That single difference is the whole of R-156, which makes the canary pair a live demonstration +# of the defect AND of its fix on every run. +CANARY_IMAGE = "felhom-volgate-canary:1" +CANARY_DOCKERFILE = """FROM alpine:3.22 +RUN mkdir -p /app/data /app/app-data \ + && adduser -D -u 4242 appuser \ + && chown 4242:4242 /app/app-data +""" +_CANARY_CMD = ('["sh", "-c", "mkdir -p /app/app-data/db && ' + 'echo canary > /app/app-data/db/db.sqlite && sleep 900"]') + +# BROKEN: the volume is mounted where the app does NOT write, and cannot write. +CANARY_BROKEN = f"""services: + canary: + image: {CANARY_IMAGE} + user: "4242:4242" + command: {_CANARY_CMD} + volumes: + - canary_data:/app/data +volumes: + canary_data: +""" + +# CLEAN: the same app, same uid, volume mounted where it actually writes. +CANARY_CLEAN = f"""services: + canary: + image: {CANARY_IMAGE} + user: "4242:4242" + command: {_CANARY_CMD} + volumes: + - canary_data:/app/app-data +volumes: + canary_data: +""" + + +def ensure_canary_image() -> bool: + if _sh(["docker", "image", "inspect", CANARY_IMAGE], timeout=120).returncode == 0: + return True + d = tempfile.mkdtemp(prefix="volgate-canary-build-") + try: + with open(os.path.join(d, "Dockerfile"), "w") as fh: + fh.write(CANARY_DOCKERFILE) + return _sh(["docker", "build", "-q", "-t", CANARY_IMAGE, d], timeout=900).returncode == 0 + finally: + shutil.rmtree(d, ignore_errors=True) + + +def self_test(prober) -> tuple[bool, str]: + """Prove the prober discriminates, on THIS run, in BOTH directions. + + A detector that flags everything and a detector that flags nothing are both useless, and the + second is actively dangerous: it turns an unexamined catalog into a documented-clean one. So + the gate refuses to report a verdict at all unless it has just called a known-broken template + BROKEN and a known-good one CLEAN. + """ + if prober is docker_prober and not ensure_canary_image(): + return False, f"could not build {CANARY_IMAGE}" + for name, body, want in (("canary-broken", CANARY_BROKEN, BROKEN), + ("canary-clean", CANARY_CLEAN, CLEAN)): + d = Path(tempfile.mkdtemp(prefix=f"volgate-{name}-")) + try: + (d / "docker-compose.yml").write_text(body, encoding="utf-8") + got, why = classify(prober(name, d, settle=10, wait=90)) + if got != want: + return False, f"{name}: expected {want}, got {got} ({'; '.join(why)[:200]})" + finally: + shutil.rmtree(d, ignore_errors=True) + return True, "" + + +# ---------------------------------------------------------------------------- driver + +def app_lifecycle(app_dir: Path) -> str: + f = app_dir / ".felhom.yml" + if not f.is_file(): + return "available" + m = LIFECYCLE_RE.search(f.read_text(encoding="utf-8")) + v = m.group(1) if m else "available" + return v if v in ("available", "hidden", "abandoned") else "available" + + +def collect_apps(root: Path, only=None, include_unavailable=False): + apps, skipped = [], [] + for f in sorted(root.glob("templates/*/docker-compose.yml")): + app = f.parent.name + if only and app not in only: + continue + lc = app_lifecycle(f.parent) + if lc != "available" and not include_unavailable: + skipped.append(f"{app} ({lc})") + continue + apps.append((app, f.parent)) + return apps, skipped + + +def check(root: Path, only=None, prober=docker_prober, include_unavailable=False, + evidence: Path | None = None, skip_self_test=False) -> int: + apps, skipped = collect_apps(root, only, include_unavailable) + if skipped: + print(f"skipping {len(skipped)} app(s) not offered for new installs: {', '.join(skipped)}") + if not apps: + if skipped: + print("nothing to check — every app in scope is out of circulation") + return 0 + print(f"ERROR: no templates found under {root}/templates/", file=sys.stderr) + return 2 + + if not skip_self_test: + print("self-testing the prober (both directions)…") + ok, why = self_test(prober) + if not ok: + print(f"ERROR: the prober failed its own canary — {why}\n" + " refusing to report a verdict: a broken detector reporting CLEAN is worse " + "than no detector at all", file=sys.stderr) + return 2 + print(" prober flags the R-156 signature and clears a correct template — trustworthy") + + results = [] + for app, d in apps: + probe = prober(app, d) + status, why = classify(probe) + results.append((app, status, why)) + print(f"{app:<20} {status:<13} {'; '.join(why)[:150]}", flush=True) + if evidence: + out = evidence / app + out.mkdir(parents=True, exist_ok=True) + (out / "probe.json").write_text( + json.dumps({"verdict": status, "reasons": why, "probe": probe}, + indent=2, sort_keys=True), encoding="utf-8") + + broken = [a for a, s, _ in results if s == BROKEN] + undet = [(a, w) for a, s, w in results if s == UNDETERMINED] + clean = [a for a, s, _ in results if s == CLEAN] + + if broken: + print("\nBROKEN — the app's data does NOT land where the template preserves it:") + for a, s, w in results: + if s == BROKEN: + print(f" {a}") + for line in w: + print(f" {line}") + if undet: + print("\nUNDETERMINED — NOT a pass. The question was not answered for these:") + for a, w in undet: + print(f" {a}: {'; '.join(w)[:200]}") + + print(f"\n{len(clean)} clean · {len(broken)} broken · {len(undet)} undetermined " + f"(of {len(apps)} in scope)") + if broken: + print("volume-persistence gate REFUSED") + return 1 + if undet: + print("INCOMPLETE — no broken template among those decided, but " + f"{len(undet)} were never decided. This is not a clean bill of health.") + return 2 + print("volume-persistence gate OK") + return 0 + + +if __name__ == "__main__": + argv = sys.argv[1:] + all_apps = "--all" in argv + no_self = "--no-self-test" in argv + ev = None + for i, a in enumerate(argv): + if a == "--evidence" and i + 1 < len(argv): + ev = Path(argv[i + 1]) + argv = [a for i, a in enumerate(argv) + if a not in ("--all", "--no-self-test", "--evidence") + and not (i > 0 and argv[i - 1] == "--evidence")] + sys.exit(check(Path(__file__).resolve().parent.parent, only=argv or None, + include_unavailable=all_apps, evidence=ev, skip_self_test=no_self)) diff --git a/scripts/test_check_volume_persistence.py b/scripts/test_check_volume_persistence.py new file mode 100644 index 0000000..4c9465b --- /dev/null +++ b/scripts/test_check_volume_persistence.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python3 +"""Fixture tests for check-volume-persistence.py. NO DOCKER — the prober is injected. + +The tests drive `check()` — the function `__main__` calls — rather than `classify()` alone, so +they cover the path that actually decides the exit code. A gate whose verdict logic is tested but +whose entry point is not has been shipped inert in this project before (the seam-wiring rule). + +Run: python3 scripts/test_check_volume_persistence.py +""" +import importlib.util +import io +import sys +import tempfile +import unittest +from contextlib import redirect_stdout +from pathlib import Path + +_spec = importlib.util.spec_from_file_location( + "cvp", Path(__file__).resolve().parent / "check-volume-persistence.py") +cvp = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(cvp) + + +# --------------------------------------------------------------------- probe fixtures + +def _ctr(name="app", uid=999, gid=999, mounts=(), data_dirs=(), status="running", health=None): + return {"name": name, "status": status, "health": health, "exit": 0, "restarts": 0, + "uid": uid, "gid": gid, "mounts": list(mounts), "diff_total": 0, + "diff_data_dirs": [{"dir": d, "files": ["db.sqlite"], "db_signature": True} + for d in data_dirs], + "diff_other_dirs": []} + + +def _mount(target, cls="named-declared", files=0, writable="yes"): + return {"target": target, "class": cls, "name": "v", "source": "/var/lib/docker/volumes/v/_data", + "files": files, "sample": [], "writable_by_app": writable} + + +# The measured papra shape (R-156 evidence, campaign10-evidence-2026-07-31/r156-papra-volume.txt): +# volume mounted at /app/data, root-owned, app runs as 999, real DB in /app/app-data/db. +PAPRA = {"app": "papra", "containers": [ + _ctr("papra", 999, 999, + mounts=[_mount("/app/data", files=0, writable="NO")], + data_dirs=["/app/app-data/db"])]} + +# vaultwarden as measured: one declared volume at /data holding the app's own db.sqlite3, +# writable by the app, and nothing data-classified in the writable layer. +VAULTWARDEN = {"app": "vaultwarden", "containers": [ + _ctr("vaultwarden", 0, 0, mounts=[_mount("/data", files=4, writable="yes")])]} + +# Started, healthy, and wrote nothing at all — uptime-kuma's measured shape. +IDLE = {"app": "idle", "containers": [ + _ctr("idle", 0, 0, health="healthy", mounts=[_mount("/app/data", files=0)])]} + +ANON = {"app": "anon", "containers": [ + _ctr("anon", 0, 0, mounts=[_mount("/var/lib/mysql", cls="anonymous", files=120)])]} + + +class TestClassify(unittest.TestCase): + """The verdict logic, pure.""" + + def test_papra_signature_is_broken(self): + """The whole reason this gate exists: it must reproduce the known instance.""" + status, why = cvp.classify(PAPRA) + self.assertEqual(status, cvp.BROKEN) + joined = " ".join(why) + self.assertIn("/app/app-data/db", joined, "must name where the data actually went") + self.assertIn("NOT writable", joined, "must report R-156's second leg too") + + def test_known_good_is_clean(self): + """A detector with no proven negative is a detector that flags everything.""" + self.assertEqual(cvp.classify(VAULTWARDEN)[0], cvp.CLEAN) + + def test_wrote_nothing_is_undetermined_not_clean(self): + """The load-bearing distinction. An app that wrote nothing has not been shown to be + correct — folding it into CLEAN is how a sweep reports 53 clean results with 6 unexamined.""" + status, why = cvp.classify(IDLE) + self.assertEqual(status, cvp.UNDETERMINED) + self.assertIn("Health is not data", " ".join(why)) + + def test_container_not_running_is_undetermined(self): + probe = {"app": "x", "containers": [_ctr("x", status="exited")]} + self.assertEqual(cvp.classify(probe)[0], cvp.UNDETERMINED) + + def test_anonymous_volume_with_data_is_broken(self): + """An anonymous volume survives a restart, which is what makes it deceptive: it is absent + from ResolveDockerVolumeNames, so it is never backed up, and down+up orphans it.""" + status, why = cvp.classify(ANON) + self.assertEqual(status, cvp.BROKEN) + self.assertIn("ANONYMOUS", " ".join(why)) + + def test_an_anonymous_volume_at_a_RUNTIME_path_is_not_a_defect(self): + """The measured privatebin shape: its image declares `VOLUME /run`, so docker created an + anonymous volume there holding nginx.pid, php-fpm.sock and s6 supervision fifos. Losing + /run costs a restart. The prefixes are written `/run/`, so a bare `/run` matched nothing + and every mount rule was blind to it.""" + c = _ctr("privatebin", 0, 0, mounts=[_mount("/run", cls="anonymous", files=14), + _mount("/srv/data", files=3)]) + status, why = cvp.classify({"app": "privatebin", "containers": [c]}) + self.assertEqual(status, cvp.CLEAN) + self.assertNotIn("ANONYMOUS", " ".join(why)) + + def test_an_anonymous_volume_holding_REAL_data_still_convicts(self): + """…and the runtime exemption must not become a blanket one.""" + self.assertEqual(cvp.classify(ANON)[0], cvp.BROKEN) + + def test_is_noise_dir_covers_the_runtime_directories(self): + for p in ("/run", "/tmp", "/var/log", "/var/cache", "/var/lib/nginx/tmp"): + self.assertTrue(cvp.is_noise_dir(p), p) + for p in ("/srv/data", "/app/app-data", "/var/lib/postgresql/data", "/config"): + self.assertFalse(cvp.is_noise_dir(p), p) + + def test_empty_declared_volume_alone_is_a_note_not_a_verdict(self): + """An empty volume on an app that also wrote nothing is UNDETERMINED, not BROKEN — the + accusation needs positive evidence of data landing elsewhere.""" + status, _ = cvp.classify(IDLE) + self.assertNotEqual(status, cvp.BROKEN) + + def test_no_containers_is_undetermined(self): + self.assertEqual(cvp.classify({"app": "x", "containers": []})[0], cvp.UNDETERMINED) + + def test_nothing_landed_in_any_mount_is_caught_without_any_path_vocabulary(self): + """The measured gramps-web miss. Its family tree goes to + /root/.gramps/grampsdb//{database.txt,name.txt} — no database-signature filename, no + data token — so every path heuristic in this file is silent on it. A rule that only + recognises the shapes someone thought of will always have a next blind spot; this one asks + a question that needs no vocabulary.""" + c = _ctr("gramps-web", 0, 0, mounts=[_mount("/app/data", files=0), + _mount("/app/media", files=0)]) + c["diff_other_dirs"] = [{"dir": "/root/.gramps/grampsdb/uuid", + "added": ["database.txt", "name.txt"]}, + {"dir": "/app/thumbnail_cache", "added": ["x"]}] + status, why = cvp.classify({"app": "gramps-web", "containers": [c]}) + self.assertEqual(status, cvp.UNDETERMINED) + self.assertIn("/root/.gramps/grampsdb/uuid", " ".join(why)) + self.assertIn("NOTHING this app wrote landed", " ".join(why)) + + def test_the_structural_finding_SURVIVES_a_broken_verdict(self): + """The measured gramps-web reporting bug. Its accounts DB (rule 2) convicted, and the + structural finding — its FAMILY TREE, the entire point of the app, landing outside every + mount — was dropped because `undet` is discarded whenever `broken` is non-empty. A finding + that disappears because a different finding won is the same class as an absent log line + read as health.""" + c = _ctr("gramps-web", 0, 0, + mounts=[_mount("/app/data", files=0), _mount("/app/media", files=0)], + data_dirs=["/app/users"]) + c["diff_other_dirs"] = [{"dir": "/root/.gramps/grampsdb/uuid", + "added": ["database.txt", "name.txt"]}] + status, why = cvp.classify({"app": "gramps-web", "containers": [c]}) + self.assertEqual(status, cvp.BROKEN) + joined = " ".join(why) + self.assertIn("/app/users", joined, "the convicting leg must still be reported") + self.assertIn("/root/.gramps/grampsdb/uuid", joined, + "and the structural finding must NOT be swallowed by it") + + def test_a_mount_that_received_data_silences_the_structural_check(self): + """It must not fire on every app with one empty volume — crafty-controller has three + empty mounts and two populated ones, and is correct.""" + c = _ctr("crafty", 0, 0, mounts=[_mount("/crafty/app/config", files=16), + _mount("/crafty/backups", files=0)]) + c["diff_other_dirs"] = [{"dir": "/crafty/app/classes", "added": ["x"]}] + self.assertEqual(cvp.classify({"app": "crafty", "containers": [c]})[0], cvp.CLEAN) + + def test_a_SIBLING_container_holding_the_state_silences_it(self): + """The measured docmost / immich / claper shape, and the reason the question is asked per + APP: the app container's only volume is for user uploads and is legitimately empty on a + fresh install, while every byte of real state sits in the database container's volume + (1540 / 1833 / 1470 files). Asked per container this called three correct apps unclean.""" + app = _ctr("docmost", 0, 0, mounts=[_mount("/app/data/storage", files=0)]) + app["diff_other_dirs"] = [{"dir": "/app/apps/client/dist", "added": ["index.js"]}] + db = _ctr("docmost-postgres", 0, 0, + mounts=[_mount("/var/lib/postgresql/data", files=1540)]) + status, why = cvp.classify({"app": "docmost", "containers": [app, db]}) + self.assertEqual(status, cvp.CLEAN) + self.assertNotIn("NOTHING this app wrote landed", " ".join(why)) + self.assertIn("benign when a sibling container holds the state", " ".join(why), + "the per-container observation must still be reported, not dropped") + + def test_structural_check_stays_silent_when_the_app_wrote_nothing_at_all(self): + """An idle app is UNDETERMINED for the existing reason, not accused by this one.""" + status, why = cvp.classify(IDLE) + self.assertEqual(status, cvp.UNDETERMINED) + self.assertNotIn("NOTHING this app wrote landed", " ".join(why)) + + +class TestDiffRollup(unittest.TestCase): + """`docker diff` is noisy; these are the rules that separate a database from a cache.""" + + def test_db_signature_beats_everything(self): + data, token, suspect, other = cvp.rollup_diff([("A", "/opt/whatever/store.sqlite")]) + self.assertEqual([d["dir"] for d in data], ["/opt/whatever"]) + self.assertTrue(data[0]["db_signature"]) + self.assertEqual((suspect, other), ([], [])) + + def test_logs_caches_and_pids_are_noise(self): + self.assertEqual(cvp.rollup_diff([ + ("A", "/var/log/app.log"), ("C", "/tmp/x"), ("A", "/root/.cache/pip/w"), + ("A", "/run/nginx.pid"), ("A", "/app/__pycache__/m.pyc")]), ([], [], [], [])) + + def test_deleted_entries_are_not_writes(self): + self.assertEqual(cvp.rollup_diff([("D", "/app/data/gone.sqlite")]), ([], [], [], [])) + + def test_unclassified_writes_are_reported_never_dropped(self): + data, token, suspect, other = cvp.rollup_diff([("A", "/opt/zzz/thing")]) + self.assertEqual((data, suspect), ([], [])) + self.assertEqual([d["dir"] for d in other], ["/opt/zzz"], + "an unrecognised write must still be visible for judgement") + + def test_a_chown_sweep_over_image_files_is_not_data(self): + """The measured calibre-web shape: 92 `C` entries under + `cps/static/css/images/**` from a linuxserver.io entrypoint re-owning the app tree. + Scored as data, this calls a clean app BROKEN — it did, on the first pass of the sweep.""" + entries = [("C", f"/app/cwa/cps/static/css/images/icomoon/x{i}.png") for i in range(92)] + data, token, suspect, other = cvp.rollup_diff(entries) + self.assertEqual(data, [], "changed image files are furniture, not customer data") + self.assertEqual(suspect, []) + self.assertTrue(other, "…but they must still be listed, not dropped") + + def test_created_file_in_a_data_path_IS_data(self): + """The other direction — the rule must not become blind. papra's verb is `A`.""" + data, token, _, _ = cvp.rollup_diff([("A", "/app/app-data/db/db.sqlite")]) + self.assertEqual([d["dir"] for d in data], ["/app/app-data/db"]) + + def test_a_bytecode_cache_DIRECTORY_is_noise(self): + """The measured crafty-controller shape. `docker diff` lists directories too, so the + bytecode cache appears as a bare `…/config/__pycache__` entry while its `.pyc` children + are filtered by suffix — leaving the directory as the only surviving entry under a path + containing the token `config`. That called a correct app BROKEN four times over.""" + entries = [("A", "/crafty/app/classes/web/routes/api/crafty/config/__pycache__"), + ("A", "/crafty/app/classes/web/routes/api/crafty/config/__pycache__/x.pyc")] + data, token, suspect, _ = cvp.rollup_diff(entries) + self.assertEqual(data, [], "a bytecode cache is not customer data") + self.assertEqual(suspect, []) + + def test_cache_directories_are_filtered_at_ENTRY_level(self): + """Where the filtering happens matters, because it is the reason a second + 'are all this dir's children noise?' rule would be dead code: nothing that reaches the + per-directory scoring has survived `is_noise`. Pinning the mechanism keeps that true.""" + data, token, _, other = cvp.rollup_diff([("A", "/srv/storage/node_modules"), + ("A", "/srv/storage/.cache")]) + self.assertEqual(data, []) + self.assertEqual(other, [], "noise is dropped before scoring, not scored and then excused") + + def test_but_one_real_file_among_noise_still_convicts(self): + """…and the rule must not become a blanket amnesty for any directory with a cache in it.""" + data, token, _, _ = cvp.rollup_diff([("A", "/srv/storage/__pycache__"), + ("A", "/srv/storage/library.sqlite")]) + self.assertEqual([d["dir"] for d in data], ["/srv/storage"]) + + def test_a_path_token_alone_does_NOT_convict(self): + """The measured onlyoffice shape: the document server unpacks its OWN static assets into + the writable layer at first boot — plugin icons, slide-theme `media/`, + `web-apps/apps/api/documents/api.js`, 2560 added entries — while its real data mount + received data normally. Vocabulary is not evidence: `media/` holds customer photos in one + app and shipped clip-art in the next.""" + data, token, _, _ = cvp.rollup_diff([ + ("A", "/var/www/onlyoffice/documentserver/sdkjs/slide/themes/theme12/media/image1.jpg"), + ("A", "/var/www/onlyoffice/documentserver/web-apps/apps/api/documents/api.js")]) + self.assertEqual(data, [], "a path token must not be enough to accuse") + self.assertEqual(len(token), 2, "…but it must still be surfaced for judgement") + + def test_a_token_dir_is_reported_and_counts_as_the_app_having_written(self): + """Demoted is not discarded. The note must reach the operator, and an app that wrote only + token-classified things must not then be reported as idle.""" + c = _ctr("oo", 0, 0, mounts=[_mount("/var/www/onlyoffice/Data", files=3)]) + c["diff_token_dirs"] = [{"dir": "/var/www/oo/themes/media", "added": ["image1.jpg"]}] + status, why = cvp.classify({"app": "oo", "containers": [c]}) + self.assertEqual(status, cvp.CLEAN) + self.assertIn("judgement needed", " ".join(why)) + self.assertIn("/var/www/oo/themes/media", " ".join(why)) + + def test_a_database_signature_still_convicts_on_its_own(self): + """The demotion must not weaken rule 2 — papra and gramps-web are both caught by it.""" + data, token, _, _ = cvp.rollup_diff([("A", "/app/app-data/db/db.sqlite")]) + self.assertEqual([d["dir"] for d in data], ["/app/app-data/db"]) + self.assertEqual(token, []) + + def test_a_postgres_CONFIG_file_is_not_a_database_signature(self): + """The measured immich shape: the postgres entrypoint writes /etc/postgresql/postgresql.conf + at init while PGDATA sits correctly in its volume with 1831 files. Scoring a config file as + a database called a correct app BROKEN.""" + data, token, suspect, other = cvp.rollup_diff([("A", "/etc/postgresql/postgresql.conf")]) + self.assertEqual(data, [], "postgresql.conf is configuration, not data") + self.assertEqual(suspect, []) + + def test_a_genuinely_misplaced_PGDATA_is_still_caught(self): + """…and removing it must not open a blind spot: PG_VERSION and pg_control are the real + markers of a PGDATA directory.""" + for marker in ("PG_VERSION", "pg_control"): + data, token, _, _ = cvp.rollup_diff([("A", f"/opt/stray/{marker}")]) + self.assertEqual([d["dir"] for d in data], ["/opt/stray"], marker) + + def test_changed_db_file_is_SUSPECT_not_a_verdict(self): + """`C` on a database file is genuinely ambiguous: a chown produces it, and so does an app + writing into a DB that ships in its image. It must be adjudicated, never guessed.""" + data, token, suspect, _ = cvp.rollup_diff([("C", "/app/cwa/empty_library/metadata.db")]) + self.assertEqual(data, [], "a `C` alone must not convict") + self.assertEqual([s["dir"] for s in suspect], ["/app/cwa/empty_library"]) + + +class TestSuspectAdjudication(unittest.TestCase): + """A suspect is settled by BYTES. These drive `classify()` with each possible outcome.""" + + @staticmethod + def _probe(**kw): + c = _ctr("app", 0, 0, mounts=[_mount("/config", files=3)]) + c.update(kw) + return {"app": "x", "containers": [c]} + + def test_benign_touch_stays_clean_and_is_still_reported(self): + status, why = cvp.classify(self._probe(diff_benign_db_touches=[ + {"dir": "/app/empty_library", "why": "byte-identical"}])) + self.assertEqual(status, cvp.CLEAN) + self.assertIn("chown sweep", " ".join(why), "benign ≠ invisible") + + def test_confirmed_write_into_an_image_file_is_broken(self): + status, why = cvp.classify(self._probe(diff_data_dirs=[ + {"dir": "/app/empty_library", "files": ["metadata.db"], "db_signature": True, + "why": "DIFFERS from the image copy (0 B -> 40960 B)"}])) + self.assertEqual(status, cvp.BROKEN) + self.assertIn("DIFFERS", " ".join(why)) + + def test_unresolved_suspect_is_undetermined_never_clean(self): + status, why = cvp.classify(self._probe(diff_unresolved=[ + {"dir": "/app/empty_library", "why": "could not read both copies"}])) + self.assertEqual(status, cvp.UNDETERMINED) + self.assertIn("could not decide", " ".join(why)) + + +class TestCheckEntryPoint(unittest.TestCase): + """Drive `check()` — the function `__main__` calls — so the exit codes are covered.""" + + @staticmethod + def _catalog(tmp, apps, lifecycle=None): + for a in apps: + d = Path(tmp) / "templates" / a + d.mkdir(parents=True) + (d / "docker-compose.yml").write_text("services:\n s:\n image: alpine:3.22\n") + lc = (lifecycle or {}).get(a) + (d / ".felhom.yml").write_text(f"slug: {a}\n" + (f"lifecycle: {lc}\n" if lc else "")) + return Path(tmp) + + @staticmethod + def _prober(table): + def p(app, app_dir, **kw): + if app.startswith("canary-"): + return PAPRA if app == "canary-broken" else VAULTWARDEN + return table[app] + return p + + def test_one_broken_app_refuses_with_rc1(self): + with tempfile.TemporaryDirectory() as td: + root = self._catalog(td, ["papra", "vaultwarden"]) + buf = io.StringIO() + with redirect_stdout(buf): + rc = cvp.check(root, prober=self._prober( + {"papra": PAPRA, "vaultwarden": VAULTWARDEN})) + self.assertEqual(rc, 1, "the gate must REFUSE, not warn") + self.assertIn("REFUSED", buf.getvalue()) + self.assertIn("papra", buf.getvalue()) + + def test_all_clean_passes_with_rc0(self): + with tempfile.TemporaryDirectory() as td: + root = self._catalog(td, ["vaultwarden"]) + with redirect_stdout(io.StringIO()) as buf: + rc = cvp.check(root, prober=self._prober({"vaultwarden": VAULTWARDEN})) + self.assertEqual(rc, 0) + self.assertIn("gate OK", buf.getvalue()) + + def test_undetermined_is_rc2_not_rc0(self): + """UNDETERMINED must never read as a clean bill of health.""" + with tempfile.TemporaryDirectory() as td: + root = self._catalog(td, ["idle"]) + with redirect_stdout(io.StringIO()) as buf: + rc = cvp.check(root, prober=self._prober({"idle": IDLE})) + self.assertEqual(rc, 2) + self.assertIn("not a clean bill of health", buf.getvalue()) + + def test_broken_wins_over_undetermined(self): + with tempfile.TemporaryDirectory() as td: + root = self._catalog(td, ["papra", "idle"]) + with redirect_stdout(io.StringIO()): + rc = cvp.check(root, prober=self._prober({"papra": PAPRA, "idle": IDLE})) + self.assertEqual(rc, 1) + + def test_a_prober_that_never_flags_is_refused(self): + """The self-test is the gate's own red-proof. A prober that calls the R-156 canary CLEAN + must not be allowed to issue a clean bill of health for the catalog.""" + blind = lambda app, app_dir, **kw: VAULTWARDEN # noqa: E731 — flags nothing + with tempfile.TemporaryDirectory() as td: + root = self._catalog(td, ["papra"]) + err = io.StringIO() + with redirect_stdout(io.StringIO()), redirect_stdout(io.StringIO()): + rc = cvp.check(root, prober=blind) + self.assertEqual(rc, 2, "a blind prober must yield rc=2, never rc=0") + + def test_a_prober_that_flags_everything_is_refused(self): + """The other direction — a prober that cannot clear a correct template is equally useless.""" + crying_wolf = lambda app, app_dir, **kw: PAPRA # noqa: E731 + with tempfile.TemporaryDirectory() as td: + root = self._catalog(td, ["vaultwarden"]) + with redirect_stdout(io.StringIO()): + rc = cvp.check(root, prober=crying_wolf) + self.assertEqual(rc, 2) + + def test_out_of_circulation_apps_are_skipped_and_named(self): + with tempfile.TemporaryDirectory() as td: + root = self._catalog(td, ["vaultwarden", "old"], lifecycle={"old": "abandoned"}) + with redirect_stdout(io.StringIO()) as buf: + rc = cvp.check(root, prober=self._prober({"vaultwarden": VAULTWARDEN})) + self.assertEqual(rc, 0) + self.assertIn("old (abandoned)", buf.getvalue()) + + +class TestEnvBuilding(unittest.TestCase): + def test_every_compose_var_resolves(self): + """A var resolving to "" binds a bogus root-owned dir at the container root + (felhom-controller deploy.go:571) — the probe would then measure the harness, not the app.""" + compose = "services:\n s:\n image: x\n environment:\n - A=${WEIRD_ONE}\n" + env = cvp.build_env("app", "subdomain: app\n", compose) + self.assertTrue(env.get("WEIRD_ONE")) + + def test_deploy_field_default_and_generate_are_honoured(self): + felhom = ("subdomain: kuma\n" + "deploy_fields:\n" + " - env_var: SUBDOMAIN\n type: subdomain\n default: \"kuma\"\n" + " - env_var: AUTH_SECRET\n type: secret\n generate: \"hex:32\"\n" + " - env_var: HDD_PATH\n type: path\n" + "app_info:\n tagline: x\n") + env = cvp.build_env("kuma", felhom, "image: x ${AUTH_SECRET}") + self.assertEqual(env["SUBDOMAIN"], "kuma") + self.assertEqual(len(env["AUTH_SECRET"]), 64, "hex:32 is 32 bytes = 64 hex chars") + self.assertEqual(env["HDD_PATH"], cvp.SCRATCH_HDD) + + def test_base64key_carries_the_controller_s_base64_prefix(self): + """felhom-controller `deploy.go:904` returns "base64:"+b64. Without the prefix Laravel + rejects APP_KEY and bookstack serves 500s — a harness bug that reads as an app defect. + Campaign 7 §1.1 and Campaign 10 §4d are both records of a harness corrupting a matrix.""" + felhom = "deploy_fields:\n - env_var: APP_KEY\n type: secret\n generate: \"base64key:32\"\n" + v = cvp.build_env("bookstack", felhom, "${APP_KEY}")["APP_KEY"] + self.assertTrue(v.startswith("base64:"), f"missing the controller's prefix: {v[:12]}…") + import base64 + self.assertEqual(len(base64.b64decode(v[len("base64:"):])), 32) + + def test_deploy_fields_block_ends_at_the_next_top_level_key(self): + felhom = "deploy_fields:\n - env_var: A\n type: text\napp_info:\n tagline: x\n" + self.assertEqual([f["env_var"] for f in cvp.parse_deploy_fields(felhom)], ["A"]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/templates/gramps-web/docker-compose.yml b/templates/gramps-web/docker-compose.yml index e1af929..5fcce6a 100644 --- a/templates/gramps-web/docker-compose.yml +++ b/templates/gramps-web/docker-compose.yml @@ -17,8 +17,24 @@ services: - GRAMPSWEB_TREE=Family Tree - GRAMPSWEB_SECRET_KEY=${GRAMPSWEB_SECRET_KEY} volumes: - - gramps_data:/app/data - - gramps_media:/app/media + # Storage layout — every path here is named by the image's OWN environment + # (docker image inspect ghcr.io/gramps-project/grampsweb:v25.6.0 → Config.Env), and the set + # matches upstream's reference compose + # (gramps-web-docs/examples/docker-compose-base/docker-compose.yml). + # + # What this template mounted before, measured 2026-08-02: only /app/data, /app/media and + # /tmp — and **/app/data is a path the application never writes**; it appears nowhere in the + # image's environment. So the FAMILY TREE (GRAMPS_DATABASE_PATH=/root/.gramps/grampsdb) and + # the ACCOUNTS DATABASE (GRAMPSWEB_USER_DB_URI=…/app/users/users.sqlite) both landed in the + # container's writable layer: destroyed by any redeploy, and absent from every backup, while + # gramps_data was tarred nightly as an empty directory. + - gramps_users:/app/users # GRAMPSWEB_USER_DB_URI — accounts + - gramps_db:/root/.gramps/grampsdb # GRAMPS_DATABASE_PATH — the family tree + - gramps_index:/app/indexdir # GRAMPSWEB_SEARCH_INDEX_DB_URI + - gramps_media:/app/media # GRAMPSWEB_MEDIA_BASE_DIR + - gramps_thumb_cache:/app/thumbnail_cache # GRAMPSWEB_THUMBNAIL_CACHE_CONFIG__CACHE_DIR + - gramps_cache:/app/cache # request/persistent caches, reports, exports + - gramps_secret:/app/secret # upstream reference compose - gramps_tmp:/tmp networks: - traefik-public @@ -41,8 +57,13 @@ services: - "traefik.http.services.gramps-web.loadbalancer.server.port=5000" volumes: - gramps_data: + gramps_users: + gramps_db: + gramps_index: gramps_media: + gramps_thumb_cache: + gramps_cache: + gramps_secret: gramps_tmp: networks: diff --git a/templates/wishlist/docker-compose.yml b/templates/wishlist/docker-compose.yml index deb3431..ebc11db 100644 --- a/templates/wishlist/docker-compose.yml +++ b/templates/wishlist/docker-compose.yml @@ -14,7 +14,20 @@ services: environment: - TZ=Europe/Budapest volumes: - - wishlist_data:/data + # Storage layout. The image declares `VOLUME /usr/src/app/data` and + # `VOLUME /usr/src/app/uploads` (docker image inspect → Config.Volumes), and upstream's own + # compose mounts exactly those two. `/data` — what this template used to mount — is a path + # the application never writes. + # + # The consequence, measured 2026-08-02: `prod.db` landed in the ANONYMOUS volume docker + # created for the image's VOLUME directive. An anonymous volume survives a restart, which is + # what makes it deceptive — but it is absent from ResolveDockerVolumeNames + # (felhom-controller internal/appbackup/appdata.go, which only returns _ for + # volumes DECLARED here), so DumpAppVolumes never backs it up, and `compose down` + `up` + # orphans it and creates a fresh empty one. Meanwhile `wishlist_data` was tarred every night + # as an empty directory. + - wishlist_data:/usr/src/app/data + - wishlist_uploads:/usr/src/app/uploads networks: - traefik-public deploy: @@ -40,6 +53,7 @@ services: volumes: wishlist_data: + wishlist_uploads: networks: traefik-public: