Skip to content

fix(selfhost): mount observability/backup scripts as directories, not single files#5238

Merged
JSONbored merged 1 commit into
mainfrom
claude/fix-stale-script-bind-mounts
Jul 12, 2026
Merged

fix(selfhost): mount observability/backup scripts as directories, not single files#5238
JSONbored merged 1 commit into
mainfrom
claude/fix-stale-script-bind-mounts

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Found while verifying the Reviews & PRs dashboard fix (#5234): the reporting-exporter service bind-mounted export-grafana-reporting-db.sh as a single file. Docker pins a single-file bind mount to that file's inode at container-creation time — git pull replaces a file by writing a new blob then renaming it over the old path, so the running container kept reading the pre-pull script indefinitely with no error. Confirmed live: a script edit had zero effect across several git pulls on the deployment server until the container was force-recreated (docker compose up -d --force-recreate --no-deps reporting-exporter) — verified via md5sum mismatch between the host file and what the running container actually saw.

The same mount shape existed on the backup and backup-exporter services, for backup.sh, verify-backup.sh, selfhost-pg-url.sh, and backup-metrics.sh — meaning a backup-script fix would silently never take effect either, without anyone noticing until a real restore was needed.

Fix

Switched all of these to directory bind mounts (./scripts:/scripts:ro), which resolve paths fresh on every access instead of pinning an inode — the same pattern grafana/dashboards already used (a directory mount), which is exactly why the dashboard JSON itself always updated correctly on this same deployment while the exporter script silently didn't.

Bonus: the directory mount also naturally satisfies backup.sh/verify-backup.sh's existing . "$(dirname "$0")/selfhost-pg-url.sh" dirname-relative sourcing (invoked as /scripts/backup.sh, dirname resolves to /scripts, finding /scripts/selfhost-pg-url.sh right alongside it) — removing the need for the old comment explaining why that file had to be mounted at container root.

Scope

  • Conventional Commit title.
  • Focused to the mount-shape bug across the 2 affected services; no unrelated changes.
  • Follows CONTRIBUTING.md.
  • Linked issue — not applicable; owner-authored fix discovered during live verification of a prior PR.

Validation

  • git diff --check
  • docker compose -f docker-compose.yml config --quiet
  • node scripts/validate-observability-configs.mjs
  • sh -n scripts/backup.sh / sh -n scripts/verify-backup.sh
  • npx vitest run test/unit/selfhost-observability-config.test.ts — 4/4 pass (updated the one assertion on the old mount/command shape)
  • npm run ui:typecheck / ui:lint (for the one docs page's usage-comment update)
  • npm run docs:drift-check
  • Live-verified on the deployment server: force-recreated reporting-exporter, confirmed via md5sum that the container now sees the current script, confirmed a fresh export completes successfully and the new issues table populates correctly.

If any required check was skipped, explain why:

  • No src/** production logic changed — this is entirely docker-compose.yml, shell-script comments, one doc page's code-block text, and one test's fixture expectations.

Safety

  • No secrets, tokens, or private values exposed.
  • Public GitHub text stays sanitized.
  • Public docs updated (docs.self-hosting-backup-scaling.tsx's usage examples).

UI Evidence

Not applicable — a docs-page code-sample text change only, no visual/layout difference.

Notes

This is a real, previously-unnoticed operational gap: any self-hoster who edited export-grafana-reporting-db.sh, backup.sh, verify-backup.sh, or backup-metrics.sh and expected git pull alone to pick it up would have been silently running stale logic indefinitely, with the container reporting healthy the whole time.

… single files

Found while verifying the Reviews & PRs dashboard fix: the
reporting-exporter service bind-mounted export-grafana-reporting-db.sh
as a single file. Docker pins a single-file bind mount to that file's
inode at container-creation time -- `git pull` replaces a file by
writing a new blob then renaming it over the old path, so the running
container kept reading the pre-pull script indefinitely with no error,
confirmed live (a script edit had zero effect across several git pulls
until the container was force-recreated). The same shape existed on
the backup and backup-exporter services for backup.sh, verify-backup.sh,
selfhost-pg-url.sh, and backup-metrics.sh.

Switched all of these to directory bind mounts (./scripts:/scripts:ro),
which resolve paths fresh on every access instead of pinning an inode --
the same pattern grafana/dashboards already used, which is why the
dashboard JSON itself always updated correctly while the exporter
script silently didn't. Updated the doc/usage comments and the one
test asserting the old mount shape to match.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui b4a795c Commit Preview URL

Branch Preview URL
Jul 12 2026, 10:35 AM

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.34%. Comparing base (7a423be) to head (b4a795c).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5238   +/-   ##
=======================================
  Coverage   94.34%   94.34%           
=======================================
  Files         473      473           
  Lines       39982    39982           
  Branches    14576    14576           
=======================================
  Hits        37722    37722           
  Misses       1585     1585           
  Partials      675      675           
Flag Coverage Δ
shard-1 46.41% <ø> (ø)
shard-2 34.48% <ø> (ø)
shard-3 30.99% <ø> (ø)
shard-4 33.30% <ø> (ø)
shard-5 33.68% <ø> (ø)
shard-6 44.87% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored
JSONbored merged commit 8cb5e74 into main Jul 12, 2026
20 checks passed
@JSONbored
JSONbored deleted the claude/fix-stale-script-bind-mounts branch July 12, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant