Release history
Borg Backup Server releases
Centrally manage BorgBackup across endpoints
All releases
100 shown
- Docker image will be rebuilt automatically by GitHub Actions; no manual reinstall needed.
- Migration script `084_apprise_mailto_mode.sql` backfills missing `mode=` on existing Apprise email services.
- Deprecation: Dropbear‑specific SSH options (`UserKnownHostsFile`, `LogLevel`) are stripped at agent startup to avoid warnings.
- Agent version jumped from 2.29.13 to 2.53.0 to match the server; existing agents auto‑update on next poll.
- Auto‑retry offline‑induced backup failures (default 3 attempts) with configurable hysteresis and reconnect logging.
- Apprise wizard now always emits explicit `mode=` for mailto URLs to enforce STARTTLS or SSL encryption.
- Shell hook environment variables (`BBS_*`) are injected and optional repository credentials can be exposed via `BORG_PASSCOMMAND`.
Full changelog
Notable change: synchronized versioning
The agent and server now share the same version number. The agent jumped from 2.29.13 to 2.53.0 to match the server. Going forward both bump together. Existing agents will auto-update on their next poll.
Bug fixes
- Apprise email STARTTLS — the wizard was emitting
mailto://…:587URLs without an explicitmode=parameter, and apprise's default formailto://falls through to insecure SMTP. AWS SES (and any RFC-compliant submission server on 587) rejects AUTH-before-STARTTLS with530 Must issue a STARTTLS command first. The wizard now always emits?mode=ssl|starttls|insecureso the encryption mode is unambiguous regardless of scheme. Migration084backfills existing services using a port-based heuristic. Reported as a "forward slash in password" issue (#250 follow-up); the slash was a red herring — apprise correctly URL-decodes%2F→/. - Apprise dispatch failures no longer flood the dashboard — push delivery problems now log at
warninginstead oferror, so they don't conflate with backup-job failures in the Errors tile and Jobs (24h) chart.last_emailed_atis now stamped on every email attempt, not just success, so a misconfigured mailer can't loop and retry on every occurrence. - Recent Activity rows on client detail pages are now clickable — they navigate to the queue detail for that job, matching how
/queuealready worked. - Repository size for remote SSH repos — chained fallback now tries
du -skover SSH first (most accurate, captures FS overhead, segment files, indexes, and uncompacted data), falls back toborg info'scache.stats.unique_csizefor borg-only shells like BorgBase, and finally toSUM(deduplicated_size). Combines #245 (credit @c0dr1ver) with the v2.52.2 fix from #258 (#259).
#249 — laptop offline mid-backup
A laptop that disconnected mid-backup used to leave the user in a bad spot: the in-flight backup got marked failed silently, the agent's reconnect wasn't visible in the log, no email arrived (dedup), and there was no retry. This release addresses all of it:
- Auto-retry on offline-induced failure (default on, 3 attempts max) — when the offline sweep marks a backup failed because the agent went offline, the same plan is re-queued automatically. The agent picks it up on the next poll after reconnect. Real errors (borg path missing, repo locked, encryption failure) come in via a different path and are not retried, so a genuinely broken backup can't loop forever. Configurable in Settings → General → Agent.
- Hysteresis on offline notifications (default 5 minutes) — the agent's status still flips to offline at the 90s threshold (so dashboards and queues react quickly), but the user-visible notification, email, and push wait until the agent has been continuously offline for at least 5 minutes. Brief network blips and short laptop suspends never become alerts. Configurable in Settings → General → Agent.
- Reconnect log line — the offline → online transition now writes a
server_loginfo row alongside the existing notification, so the log timeline shows the recovery, not just the outage. - Dedup-escalation re-email — if a
backup_failednotification accumulates new occurrences and 6+ hours have passed since the last email, the email re-fires. Previously dedup silently swallowed every repeat. - SMTP-not-configured banner on Settings → Email — surfaces when an
email_on_*toggle is enabled but SMTP isn't configured, so users aren't silently un-notified.
#247 — Dropbear SSH on agents
Enigma2, BusyBox, and embedded Linux clients use Dropbear's dbclient, which doesn't recognize OpenSSH-only options like UserKnownHostsFile and LogLevel and complains for each one. Backups completed fine but the warnings were loud. The agent now detects Dropbear at startup (parses ssh -V), strips the OpenSSH -o flags from BORG_RSH, and uses -y -y for hostkey bypass instead.
#250 — Shell hook context vars + opt-in credential exposure
The wiki documented BBS_* env vars but the agent code never set them — subprocess.run() was called without an env= parameter, so hooks only inherited the agent's plain environment.
BBS_ARCHIVE_NAME,BBS_REPO_PATH,BBS_BACKUP_PLAN,BBS_CLIENT_NAME,BBS_BACKUP_STATUS,BBS_DIRECTORIES,BBS_JOB_IDare now actually injected.- New per-config opt-in "Expose repository credentials to script (advanced)" sets
BORG_PASSCOMMAND(pointing at a mode-0600 temp file) andBORG_REPO. We useBORG_PASSCOMMANDrather thanBORG_PASSPHRASEbecause env vars are inherited by every subprocess the script spawns —BORG_PASSCOMMANDis only consulted when something explicitly shells out to read it (which is borg itself by design), so the leak surface is much smaller. The temp file is deleted as soon as the script exits. The wiki has full details and a streamingpg_dumpexample.
Migrations
Run automatically via bbs-update:
083_offline_retry_and_notify_escalation.sql— addsretry_count,parent_job_idtobackup_jobs;last_emailed_attonotifications084_apprise_mailto_mode.sql— backfillsmode=on existing Apprise email services
Notes
- Docker image will be rebuilt automatically by GitHub Actions.
- The agent will auto-update from
2.29.xto2.53.0on the next poll. No manual reinstall required.
- Admin summary API (`GET /api/v1/summary`) returns plans, enabled flag, repo ID/name, and last terminal backup result
- Queue and Schedule pages visual refresh
Full changelog
Bug fixes
- Dashboard Errors (24h) tile now picks up backups that failed because the agent went offline. The scheduler's offline-job sweep was logging at
level=warningwhile the dashboard counted onlylevel=errorrows, so those failures were silently invisible. (#243) - Repository size for catalog-synced and remote SSH repos now matches
borg info <repo>(cache.stats.unique_csize) instead of summing each archive's incrementaldeduplicated_size— the old approach drifted to nonsense values after pruning or compacting. Local repos continue to usedu. (#258) - Job detail page: the empty dark "black box" below the Stats card on completed jobs is gone. The two cards no longer force equal heights, so each sizes to its own content. (#244)
- Schedules week view: when a backup failure happened in a previous week, the failed-block marker no longer silently disappears once the week rolls over. Failed runs in today's lane also stay clickable instead of shrinking to a 1-pixel sliver as time elapses.
- Queue page header counters and metric tiles now refresh in sync with the live tables every 10 seconds. Previously the slot counters, 24h totals, and average duration drifted to stale values within seconds of page load.
Hardening
- ClickHouse log growth in Docker installs is now capped. A Poco log-rotation glitch could cause
stderr.logto grow ~12 GB/day on some installs (regression of #79); the container now self-heals on startup if either log is over 100 MB and a cron entry truncates them every 5 minutes when over 50 MB. The fix bounds disk usage without re-introducing the silent-startup-failure regression from #88. (#252) - Admin summary API (
GET /api/v1/summary) now reports the last terminal backup result instead of occasionally returning a queued/running job, exposesenabled,repository_id, andrepository_nameper plan, and uses a windowed query that scales linearly with job history. - Schedule view queries are bounded to the last 30 days. Previously the page scanned every historical job for every plan on each load.
New
GET /api/v1/summaryadmin API — single token-authenticated call that returns each client with its plans and last backup result. (#254)- Queue and Schedule pages received a visual refresh aligning their styling. (#251, #253)
Notes
- No agent changes in this release —
AGENT_VERSIONstays at 2.29.11. - The Docker image will be rebuilt automatically by GitHub Actions; the ClickHouse log cap is only present in the rebuilt image, not via in-place server update.
Fixed dashboard activity chart erroneously showing error bars when no log entries exist and corrected upgrade page top cropping.
Full changelog
Bug fixes
- Dashboard activity chart still showed phantom error bars (#240) — the v2.52.0 fix matched the tile count to
/logentries, but the "Jobs (Last 24h)" chart was still drawing red bars fromfailed_jobs+ unresolved alerts, so the chart could show error bars at hours where the Log page was empty. Chart now reads from the sameserver_logquery the tile and Log page use. - Upgrade page top cropped, can't scroll up (#239) — the auth layout used
align-items: center, which works for the login form (small, fits in viewport) but pushed the top of a tall upgrade page (steps + release notes) above the visible area with no way to scroll up to it. Override toflex-startfor the upgrade page only; login pages keep their centered layout.
Other
- Enigma2 set-top-box detection in the agent installer (#230) —
install.sh detect_os()now recognizes Dreambox / OpenATV / OE-Alliance / OpenPLi devices via/proc/stb/info/modeland falls back through three image-info sources (OE-Alliance/etc/image-version, OpenATV/usr/lib/enigma.info, Python boxbranding). Detection only — Enigma2 still hits "Unsupported OS" in the package-install step (manual borg + python3 install required), but the OS is now correctly named in the install summary. Thanks @MegaV0lt.
Versions
- Server: 2.52.1
- Agent: 2.29.11 (unchanged)
- settings.value column type changed from TEXT to MEDIUMTEXT in migration 082_settings_value_mediumtext.sql
Full changelog
What's new
Schedules page redesign (#225)
Replaces the per-hour load histogram with a duration-driven horizontal timeline, where each block's width tracks the estimated backup duration. Day view gets a "concrete" treatment for the already-completed portion of each job, a bright current-time line, larger tooltips, uppercase job/client labels, and a unified blue palette to match the dashboard. Big thanks to @c0dr1ver for the design and implementation.
Bug fixes
- Branding upload broken (#238) — uploading a navbar icon, login logo, or app icon failed with
Data too long for column 'value'. Migratedsettings.valuefromTEXT(65 KiB) toMEDIUMTEXT(16 MiB). - CentOS / RHEL agent stuck offline (#237) — Cloudflare's bot rules block Python's default
Python-urllibUser-Agent, so a BBS install fronted by Cloudflare returned 403 to every agent request. Agent now sendsUser-Agent: BBS-Agent/<version>. If you have stricter Cloudflare WAF/Bot-Fight rules, add a Page Rule (or WAF skip rule) for/api/agent/*. - Windows / macOS backups always flagged "completed with warnings" (#236) — agents tripped
had_warningson every backup because OS-level lockouts on system files (NTUSER.DAT, SIP-protected caches, iCloud Mobile Documents) were treated as actionable warnings. Added the locale-independent error codes ([WinError 5],[Errno 13],[Errno 1],[Errno 11]) to the agent's routine-warnings list. - Dashboard "Errors (24h)" tile out of sync with linked log page (#235) — the count summed log errors + failed jobs + alerts but the linked
/logpage only shows server-log entries, so the count and the linked page could disagree. Count now matches what/logshows. Operational alerts remain visible on/notificationsand the navbar bell.
Other
- Server-side job finalize-race logging (#227 follow-up) — when the agent's stall detector marks a running server-side job as failed mid-flight, the scheduler now writes a
warningrow toserver_logso admins can see it in the activity log, not just injournalctl. Credit @SAY-5 (PR #228) for the diagnostic improvement. - Trimmed dead histogram computation from
ScheduleControllerafter the timeline view replaced it.
Versions
- Server: 2.52.0
- Agent: 2.29.11
Migration
Adds migration 082_settings_value_mediumtext.sql. Runs automatically via bbs-update.
Fixed backup progress display, long path overflow, OIDC login on PHP 8.4, archive delete stall handling.
Full changelog
Bug-fix release. Server 2.51.0 → 2.51.1, agent 2.29.8 → 2.29.9.
Fixes
- Backup progress showed nonsense like "83 GB of 17 B" (#234): borg 1.4 fires
progress_percentevents during backup for internal phases where current/total are item counts, not bytes. The agent was forwarding those asbytes_total. Now restore-only. - Long file paths still overflowed the queue-detail card (#233): earlier #108 / #209 fixes wrapped text inside the progress card, but the page column itself (
.main-content, a flex child) lackedmin-width: 0, so unbreakable strings could blow past the viewport regardless. Site-wide fix. - Dashboard "Errors (24h)" tile linked to all errors (#232): the link had no time filter, so clicking the 24h count showed errors days old. LogController now accepts
?hours=Nand the tile passeshours=24. - OIDC login broken on PHP 8.4 (#231):
jumbojett/openid-connect-phpdeprecation warnings (implicit nullable params) hit the response body before the redirect Location header, triggering "headers already sent". DropE_DEPRECATEDfor the OIDC call so upstream's noise stays out of our output. - Archive delete marked completed despite agent stall-abandon (#227):
archive_deletewas missing from the agent's stall-detection exclusion list, so agents flagged it abandoned ~30 min into a long delete; then scheduler's unconditional finalize UPDATE clobbered the resulting 'failed' with 'completed'. Both fixed (exclusion list + finalize scoped toWHERE status='running'). - Upgrade page stretched to full viewport on wide displays (#226): wrapped in a 1100px max-width centered container so steps and release-notes paragraphs stay readable.
- AGENT_VERSION bumped to 2.29.8; agents auto‑update.
- If custom Branding is used, note the updated navbar‑icon/login‑logo limits and restored Login Page Theme override.
- Consistent card headers across all UI pages
- Tighter type sizes site‑wide (tables, headers, topbar)
- New Branding settings: single App Icon upload and static‑asset caching
Full changelog
The brand-cleanup work continues. This release is primarily UI polish — consistent visual language across the app, smaller and tighter type, less noise in the activity log, plus a few real bug fixes. No database changes; safe in-place upgrade.
UI overhaul
- Card headers — one consistent style. Settings (light blue), Clients (transparent), and Dashboard (dark gradient) used three different palettes for what should be the same thing. Standard
.card-headeris now a subtle neutral surface in both themes; the dark navy gradient on Dashboard / Schedules / Archive Detail collapses to the same neutral in light mode so a light page is never broken up by a dark block. - Tighter type, site-wide. Card headers shrunk from ~16px to 14px. Table headers switched from ALL-CAPS 13.6px to mixed-case 14px. Table body cells capped at 14px (was Bootstrap's 16px default). Topbar page title and username dropdown nudged down ~2px each.
- Storage Locations page — three floating
<h5>section titles ("Local Storage", "Remote SSH", "S3 Offsite Sync") replaced with proper card-headers. Each section now has a card frame with the action button (Add Location / Add SSH Host) inside the header, matching the rest of the app. - Schedules page colors toned down. Per-agent block colors went from
hsl(h, 55%, 45%)tohsl(h, 32%, 38%)so they sit in the dark-navy palette instead of looking like a saturated bar chart. - Server Health card (dashboard) — fixed-width label and value columns so every progress bar starts and ends at the same x-coordinate across rows. Smaller, tighter type. No more dead whitespace between bar and short values like "0.48".
- Recent Activity on the client status tab — list reformatted as a proper table (Status / Task / Repo / Time / Date), 14px text, 20 rows instead of 10.
table-responsiveso it scrolls horizontally on phones. - Sidebar version pill — was illegible in light mode (gray-on-dark-navy). Now uses light-on-dark colors hardcoded since the sidebar is always dark.
- Mobile login gets a small header logo above the form (uses your branded navbar icon if set, otherwise the favicon mascot). Form pane fills the screen on phones.
- Light theme login is back. Auth layout now respects the Login Page Theme override in Branding settings (which is also re-enabled — was temporarily disabled while the light variant was missing).
Branding system
- New "App Icon / Favorite Icon" upload in Branding settings. Single 512×512 transparent PNG; BBS resizes it on demand to every favicon / Apple touch / PWA size needed. No more uploading half a dozen sizes manually. Bundled mascot is the default fallback.
- Navbar Icon description now mentions it doubles as the small mobile-login header logo.
- Topbar mascot image was 1536×1024 / 2.4MB shipping for a 115px display. Resized to 400×266 / 82KB — ~30× smaller. (#223)
- Static-asset caching added via
mod_headersinpublic/.htaccess: 7-day cache for images and fonts, 1-day for CSS/JS. Previous behavior issued a conditional GET on every page load.
Bug fixes
- Activity log noise filtered. Routine borg warnings (
Permanently added <host> to known hosts,file changed while we backed it up,stat: [Errno 2]on transient files) no longer flag a backup as "completed with warnings" or fire a notification. Added-o LogLevel=ERRORtoBORG_RSHto suppress the SSH known-hosts notice at the source. Agents pick this up via the auto-update mechanism. (#225 / #223) - Upgrade page no longer cropped by the login mascot artwork. The post-upgrade result page now suppresses the split-pane art and uses the full frame for the upgrade log. (#222)
- Long file paths in
/notificationsnow wrap properly instead of overflowing the message column off the right edge. - Queue page duration (PR #207 by @c0dr1ver) — durations ≥ 1 hour now format as
Hh Mm Ssinstead of dropping the hour. Completed-row durations also get a colored progress bar showing relative job length. - Files in Archive count (#192) — tile now headlines the catalog-derived count (what's actually navigable in the restore browser); borg's
nfilesshown as a small caption only when it differs by more than 1% (typical on systems with heavy hardlinks where borg over-counts).
Heads-up
If you've customized the Branding section, the navbar-icon and login-logo upload limits changed in v2.50.0. The new App Icon section is additive — your existing logos still work. The Login Page Theme override is functional again in light mode.
AGENT_VERSION bumped to 2.29.8; existing agents will pick this up via the server-pushed update mechanism. No manual reinstall needed.
- Mobile login screen displays a header logo on phones
Full changelog
Patch release with two fixes and one small UI improvement on top of v2.50.0.
Fixes
-
Backup jobs no longer fail when borg's cache lock cleanup hiccups (#214). Borg occasionally raises
borg.locking.NotLockedat the very end of a successful backup when releasing its local cache lock — the archive is already written and intact, but the cleanup error caused BBS to mark the job as failed. The agent now detects this specific case and reports the job as completed with warnings instead of failed. The full traceback is preserved in the job log so the underlying cause is still visible. BumpsAGENT_VERSIONto 2.29.7; existing agents will pick this up on the next server-pushed update. -
Smaller PNG payloads across the UI (#220). All bundled PNG assets compressed via
pngquant— no visual change, just smaller files and faster page loads. Thanks to the contributor for the PR.
UI
- Mobile login screen now shows a header logo. The split-pane art is hidden on phones, so the form pane was previously untethered from any branding cue. Phone widths now show an 88px logo above the form. Falls back through: branded navbar icon (if set in Branding settings) → bundled mascot favicon as default. The larger login-page logo is intentionally not used on mobile because it's sized for a 500px column and would crowd a phone-width pane.
Heads-up
If you saw the SSH lockout from the brief chown root:root authorized_keys change earlier today: that was reverted before this release was tagged. v2.50.1 ships the corrected behavior — authorized_keys is back to user-owned mode 600, which is the supported configuration.
- Custom branding users should test on non‑production first due to changed upload limits and disabled Login Page Theme override
- Login page is dark-only temporarily while design settles
- Redesigned login page with split-pane layout, glassy ribbon, dark theme across all auth screens
- Files-in-archive count headlines catalog-derived total; shows borg `nfiles` caption when differing
Full changelog
A fresh start on branding
This release kicks off a brand cleanup that's been a long time coming. There's a new mascot logo, a redesigned login screen, and rebuilt favicon/PWA icons — all wired through a new branding system that lets you upload larger custom logos and see the new defaults in the preview pane.
What's new
- New mascot logo in the topbar, on the login screen, and across favicons (browser tab, Apple home-screen, PWA install).
- Redesigned login page — split-pane layout with the mascot artwork on the left, a glassy feature ribbon, drifting binary-stream background, and a unified dark theme across login / forgot-password / 2FA / reset-password.
- Branding settings rework — bigger upload limits (Navbar Icon up to 360×200, Login Logo up to 800×800), preview pane that mocks the actual rendering, and the default-fallback previews now show what the app actually ships with rather than a stale legacy icon.
- Files-in-archive count (#192) now headlines the catalog-derived total (what's actually navigable in the restore browser), with borg's
nfilesshown as a small caption when it differs noticeably — it can run 2-3× higher on systems with heavy hardlinks.
Heads-up if you rely on custom branding
The branding system still needs more work. Upload limits and preview rendering have changed, the login page is dark-only for now (the Login Page Theme override is temporarily disabled while the design settles), and not every screen has been re-checked against custom logos yet.
If you've customized BBS with your own logo or login theme, please test this release on a non-production install first to see how things look before rolling it out on a production server. If the new defaults work for you, you'll have the smoothest upgrade path.
What's coming
There's still a lot of interface work ahead. Expect more polish landing in the coming weeks — small fixes, larger redesigns, and more thoughtful defaults across the dashboard, schedules, and settings screens. This release is the starting point, not the finish line.
I hope you like the new logo. It's here to stay.
- Telegram thread/topic support via optional Thread field in Apprise notifications
- Dropbear SSH key conversion for embedded systems
Full changelog
Server bumps from 2.29.1 → 2.29.5 to re-align with the agent (also at 2.29.5).
Fixes
- Queue list / detail badges (#208): Running, Sent, and Queued now use Bootstrap 5.3
text-bg-*tokens to match the Dashboard — no more cyan-on-white or white-on-yellow. - Queue detail overflow (#209): when borg's
status_messagecontains a long file path, the running progress card no longer stretches past the viewport. The original #108 fix only covered Directories / Borg Options / log content. - Import existing repository (#210): the local-storage branch had been dropped from the dispatch in an earlier refactor, so POSTing the import form for a local repo returned no body and the browser sat on a blank
/repositories/import. Restored theelsebranch that callsimportLocal(). - Queued agent updates blocking backups (#206): management tasks (update_borg / update_agent) bypassed the slot-count check on the way in but still incremented the counter on the way out, so a batch of queued updates for offline agents could fill the imaginary slot budget and block backups. Counter now stays in sync with the bypass.
- Schedules view (#202, thanks @c0dr1ver): current-time indicators on the schedule view, dashboard duration formatting fixed for jobs over an hour, and missed-schedule alerts surfaced on the dashboard.
Features
- Telegram thread/topic support (#205, thanks @c0dr1ver): Apprise notification setup now has an optional Thread field that emits
tgram://bot_token/chat_id:thread, for groups with topics enabled. - Dropbear SSH key conversion (#201, thanks @MegaV0lt): install.sh now detects a Dropbear
sshand runsdropbearconvertso the server-issued OpenSSH key works on embedded systems (OpenATV / Enigma2 receivers, etc.). Plus a follow-up agent change so the same conversion runs whenever the agent re-downloads the key after an auth failure (e.g. after a server-side rotation).
- Containerized agent image (Docker) supporting multi-arch and self‑updates
Full changelog
Highlights
🐋 New: Containerized agent image
The BBS agent now ships as a Docker image for hosts where the native installer isn't practical — TrueNAS Scale, Synology DSM, unRAID, UGreen NAS, and any appliance OS where you can't persist a system-installed package.
docker hub: marcpope/borgbackupserver-agent:latest
- Multi-arch (
linux/amd64andlinux/arm64) - Includes borg, ssh, and clients for MySQL, PostgreSQL, and MongoDB so one image covers every backup type
- Self-updates from the server on protocol bumps; reset by pulling a new image tag
- Fully documented in the Docker Agent Setup wiki page
Bring it up with a docker-compose.yml that mirror-mounts the host paths you want backed up — see the wiki for examples. Addresses #193.
Bug fixes
- Restore jobs now report failure correctly. Previously
borg extractwarnings (exit code 1) were treated as success and a path-filter mismatch returned 0 with no error — both showed a green check on the queue page. Restores now fail loudly when borg reports warnings or extracts nothing. - Restore stats are accurate. The Stats panel for restore jobs (Files Total / Files Processed / Bytes Total / Bytes Processed) used to be empty or show byte counts in the file fields. All four are now populated correctly using
borg extract --listoutput. - Restore triggers land on the queue detail page. Previously, manually triggering a file/MySQL/Postgres/Mongo restore redirected back to the client page, forcing you to navigate to Queue and find the new job. Now restores land directly on
/queue/<job_id>, matching the existing manual-backup behavior.
- Agent and server versions now match at 2.29.0
Full changelog
Fixes
- Server Health layout (#199): label column now content-sized (left-aligned, ellipsis past 140px) so short labels like CPU/Memory hug the card edge and the progress bars keep the rest of the row on narrow cards.
- Badge consistency (#200): solid-color badges across the app now use Bootstrap 5.3's
text-bg-*tokens so text contrast is automatic and uniform. Semantic "info" (log-level badge, Upgrade Agents pill, notification Info/New pills) switches from cyan to blue where the cyan+white combo was hard to read. - Manual borg updates now register (#198): the agent re-reports system info hourly, so clients that can't use auto-update (e.g. armv7l without pip) will have their version picked up on the server within an hour of a manual install. AGENT_VERSION bumped to 2.29.0 to match.
Polish
- Footer: "Open Source & Made with ❤ by Marc Pope — Sponsor" replaces the standalone heart/Sponsor link; footer text size trimmed a notch.
Agent and server versions now match at 2.29.0.
Fixed Windows installer directory creation, clamped dedup savings display at 99.9%, increased Borg lock wait time to 10 minutes, and added encryption control for mail settings.
Full changelog
Bug Fixes
- Windows installer (#195): Fresh installs failed with a
DirectoryNotFoundExceptionwhen writing the SSH-path marker file. The installer now createsC:\ProgramData\bbs-agent\up front before any writes. - Dedup savings display (#191): Rounding lifted 99.95%+ to
100%even when the repo still held bytes on disk. The dashboard, storage cards, email reports, and archive detail now clamp at99.9%when dedupe size is non-zero. - borg lock timeout (#194): Agent-side
borg createandborg extractran without--lock-wait, so borg's 1-second default turned any brief lock contention into an immediateFailed to create/acquire the lock ... (timeout)failure. Agent commands now pass--lock-wait=600(10 minutes), matching the server-side operations. - Mail settings (#197): The Email Settings UI had no encryption control and the Mailer hardcoded STARTTLS for port 587 only, so SMTP servers on port 465 (implicit TLS) just hung. Added an Encryption dropdown (STARTTLS / SSL/TLS / None) with a port-based default, and taught the Mailer and Test SMTP button to honor it. Also fixed Test SMTP passing the still-encrypted password to AUTH LOGIN.
- CachyOS support in agent installer
Full changelog
Fixes
- ClickHouse self-heal (#189): container start now also repairs mode bits (not just ownership) and stops hiding chown errors. Resolves
directory_iterator: Permission denied/ASYNC_LOAD_WAIT_FAILEDon thefile_catalogtable and surfaces real problems on FUSE filesystems (Unraid/mnt/usershfs) where ownership changes are silently dropped. - Failure email mislabels the task (#185): every failed task (update_borg, check, prune, compact, catalog_rebuild, …) now sends an email with the correct label instead of always saying "Backup Failed".
- Email time in user's timezone (#186): the
Time:line in notification emails is now formatted in each recipient's configured timezone with the zone abbreviation (e.g.CEST) instead of always UTC. - Upgrade gate blocked by offline agents (#184): the server-upgrade precheck now ignores jobs stuck
sentto an offline agent, and skips management/server-side task types — matching the filter the queue scheduler already uses. - Daily borg-update spam on unsupported arches (#187): agents whose architecture has no GitHub or server-hosted binary (armv7l, some BSDs) are no longer auto-queued for daily updates they can't complete. Manual Update Borg from the client page still works.
- Backup duration formatting (#190, thanks @c0dr1ver): completion logs and notifications now show
1h 12m/27m 5sinstead of raw seconds. - CachyOS support (#188, thanks @ChrSchu90): agent installer recognizes CachyOS alongside Arch/Manjaro/EndeavourOS.
- Auto-update skips agents already at the target version
- Per-user low‑storage alert thresholds (set per profile)
- Activity icons on Dashboard Recently Completed page
Full changelog
Bug fixes, enhancements, and UI polish. Agent bumped to v2.25.1 for the restore-progress change.
Fixes
- Daily email report counted only one repo per client (#175): the report picked a single "last backup" per client, so clients with two or more backup plans reported only one plan's size and file count. It now aggregates the latest completed/failed backup per plan per client and sums size and files across all of them. Clients with a mix of ok and failed plans now show as "Partial" (orange) so it's visible at a glance.
- "Last generated" timestamp always showed the current time (#176): the scheduler regenerates the day's report every minute to keep numbers fresh, and an earlier fix was bumping
created_aton every regenerate. Now only the manual "Generate Report Now" button updates the timestamp; scheduled refreshes leave it alone. - Restore stuck on "Starting task..." (#168):
borg extractnow runs with--progressand the agent forwardsprogress_percentevents so the UI shows a live progress bar during restore, matching the backup experience. - Schedule page descenders clipped on hover (#171): the
:hovertransform was scaling the block, which clipped the bottom pixel row of letters like g/p/y. Replaced with a stronger box-shadow — same lift cue without the clipping. - Repo detail status badges hard to read (#169): warning/info badges now use
text-darkfor contrast instead of default white-on-light.
Enhancements
- Auto-update stops re-queueing up-to-date agents (#174): the daily
update_borgsweep compares each agent's current borg version against the target and skips the ones already at or past it, instead of re-installing the same binary every day. Log line now breaks out queued vs already-current vs incompatible counts. - Per-user low-storage alert thresholds were already added in 2.28.5 — if you missed it, each user now sets their own trigger (percent used or free GB) under Profile → Account → Low-Storage Alerts.
- Activity icons on Dashboard Recently Completed (#172): same task-type icon vocabulary as the Queue page — backup / prune / compact / restore / update / etc. — so the two pages read consistently.
- Server Health drops redundant root row (#178): when
/var/bbssits on the root filesystem, the widget no longer shows/and/var/bbsas two identical rows. - Clients sorted alphabetically (#182): the Clients list is now sorted by name instead of creation order.
- Version prefix consistency (#170): the Clients page showed raw version numbers where the rest of the app said
v<num>; aligned them.
Fixed Windows installer failure when borg.exe moved to archive root.
Full changelog
Windows installer hotfix.
Fix
- Windows install fails with "borg.exe not found" (#180): borg-windows v1.4.4-win6 changed its zip layout —
borg.exenow lives at the archive root instead of under aborg/subdirectory. The installer was hardcoded to the old layout and bailed out. Installer now locatesborg.exewherever the release puts it and derives thePATHaddition from that location. Pre-install cleanup also wipes everything under$BorgDirexceptssh\so upgrades from the old layout can't leave stale files behind.
UI
- Server Health panel label column widened and right-aligned; inter-column gap tightened.
- Per-user low‑storage alert thresholds with percentage used or free space in GB, plus a Disabled option; notifications are scoped per user.
Full changelog
Bug fixes + new feature.
New
- Per-user low-storage alert thresholds (#156): each user now picks their own trigger under Profile → Account → Low-Storage Alerts. Two modes — percentage used or free space in GB — plus a Disabled option. Notifications and emails are scoped per-user; everyone gets the alert tuned to their own preference rather than sharing one server-wide number. Existing values backfill from the old server-wide threshold on upgrade so nobody's alert behavior changes unexpectedly.
Fixes
- Windows restore to original location (#167): Borg archives from Windows clients store paths with the drive letter as the first segment (
C/Users/...). The catalog indexer prepends a leading slash for display (/C/Users/...), so paths reaching the restore endpoint look like/C/Users/.... The drive-letter detection only matched the no-slash form, fell through, and the agent extracted files relative to its working directory (C:\ProgramData\bbs-agent\) instead of the original drive. Regex now accepts an optional leading slash and routes correctly back to the drive root. - Dashboard Server Health label column (#166): partition names like
/var/bbswere clipped by a 64px label column. Widened to 110px so names fit and the bars align further right.
- Upgrade to v2.28.4 (or later) – migration 079 ensures the `api_key` column is nullable with explicit NULL default, resolving PDOException on client addition.
Full changelog
Hotfix.
Fix
- Adding a new client fails with "Field 'api_key' doesn't have a default value" (#173): migration 077 (part of the API-key hardening in 2.28.0) used SQL syntax that some MariaDB/MySQL versions interpret as "preserve the existing
NOT NULL, just change the default." On those installs the column never actually became nullable, so every attempt to add a new client after the hardening rewrote the INSERT to useapi_key_hashinstead throws a fatal PDOException.
Migration 079 re-runs the change with explicit NULL DEFAULT NULL syntax that's unambiguous across versions and SQL modes. Idempotent on installs where 077 already took effect.
All v2.28.x Docker users should upgrade.
- Clients page UI: renamed "Backup Activity" to "Activity", split failed jobs into red/grey categories
- Archive detail page redesign with metric-tile stats, improved readability and performance
Full changelog
Bug fixes and UI polish.
Fixes
- Windows installer failed with "Missing closing brace" parse errors (#165): the
.ps1script contained UTF-8 em-dashes inside double-quoted strings. Windows PowerShell reading the file without a BOM defaults to the legacy code page (usually cp1252), where the UTF-8 byte sequence for—includes0x94— interpreted there as a right curly quote — which prematurely terminated strings and derailed parsing into the brace errors users saw from lines much later in the file. Bothinstall-windows.ps1anduninstall-windows.ps1are now pure ASCII. - Borg/agent updates failed on offline clients (#144): scheduled
update_borgandupdate_agentjobs were being auto-failed the moment the agent's heartbeat timed out. Those task types now stay queued until the client next polls, so laptops asleep at the scheduled update hour quietly receive the update when they come back. A 7-day safety valve expires management jobs that have been abandoned. - Missed-schedule spam (no issue): each cron tick was incrementing
occurrence_countand resettingread_aton the same unresolved missed-schedule row, so agents offline for days accumulated thousands of occurrences and the notification refused to stay marked read. Deduped at the scheduler level, plus a one-time cleanup migration clamps any existing inflated counts back to 1.
UI
- Clients page (#141): "Backup Activity" renamed to "Activity". Failed jobs split into red "Backup Failed" (real data risk) and amber "Other Failed" (update jobs, plugin tests, etc.), so a laptop that was offline at update time no longer paints the chart the same color as an actual backup failure.
- Archive detail page redesign (#132, #133): stat row rebuilt with the
metric-tilepattern from the dashboard and card headers use the signature blue gradient. "Files" is now "Files in Archive" with a tooltip explaining the pre-scan vs manifest difference. Status Breakdown footer is now readable with a proper "Grand Total" row that includes directories/symlinks. Excluded-file badges swapped to a readable subtle variant. Largest Files no longer shows excluded entries (node_modules etc. that weren't backed up). - Archive detail page load time: deferred the "deleted vs previous archive" anti-join to a background fetch, and rewrote it as
LEFT ANTI JOIN— page renders immediately even on multi-million-file archives. - Dashboard tweaks: Server Health progress bars switched to Bootstrap's standard
.progresswith an always-visible centered % overlay. File Catalog stats table uses hairline row separators, gets a transparent background, and expands to full width on mobile. Doughnut tooltips escape the 110×110 canvas as HTML overlays on<body>and show size + rows. - Notifications page: tightened row heights, removed the redundant "Notifications" H4 (already labeled in the nav).
- Mobile bottom nav: 5-tab layout (Home / Clients / Queue / Settings / More) with a bottom-sheet submenu for Schedules, Log, Users, Storage, Logout. Active state highlights More when the current page is one of the inner items. Lighter top border + stronger upward shadow for definition.
- Badge styling: site-wide consistency — medium weight (not bold), Title Case.
- Client detail header: hostname uses a signpost icon so the globe icon clearly belongs to the IP.
❤️ Sponsor this project if you find it useful.
Fixed ClickHouse start failures on Docker by repairing ownership and making errors visible.
Full changelog
Bug fixes and UI polish.
Fixes
- ClickHouse silently fails to start on Docker (#158): upgrade-bitten volumes had
/var/bbs/clickhouse/*owned bywww-dataleft over from an older image build. ClickHouse refuses to run when the process user doesn't match the data owner and the error was being swallowed. Entrypoint now scans service data directories on each start and repairs ownership if it finds a mismatch. Also stops suppressing ClickHouse's stderr and adds an explicit post-start health check so the next time anything like this happens it's visible. - Server-side jobs could execute twice (#163): a long-running compact/prune could be re-picked by the next cron-fired scheduler instance before the first one marked the row
running. Thesent → runningtransition is now a conditional UPDATE; if the row was already claimed by another scheduler, the iteration is skipped. - Missing compact output in task log (#162):
borg compactnow runs with--verboseso the "compaction freed about X GB" summary is captured into the task log. - Dashboard doughnut tooltip clipped (#164): the File Catalog canvas is only 110×110, so the default canvas-rendered tooltip got cut off. Switched to an external HTML tooltip appended to
<body>that escapes the canvas entirely, and the body now shows size and rows instead of duplicating the name.
UI
- Server Health progress bars on the dashboard switched to Bootstrap's
.progress/.progress-barwith an absolute-positioned percentage overlay — always centered, always visible regardless of bar width. - File Catalog card: stats table uses hairline row separators instead of striped backgrounds; Top Repositories list tightened up; table background made transparent so it matches the card body.
- Client detail header: hostname uses a
signposticon so theglobeicon is unambiguously the IP address.
- Bare metal: run `sudo /var/www/bbs/bin/bbs-update /var/www/bbs`
- Docker: pull the `v2.28.1` image tag
- Perform a hard refresh (Cmd/Ctrl+Shift+R) after upgrade to apply the dropdown CSS changes
Full changelog
Bug fixes.
Fixes
- Backup report (#150, #152): the Server section now aggregates disk usage across all configured storage locations instead of only the default partition, and the on-disk footprint is computed from per-repo sizes instead of a JOIN-inflated archive sum. Regenerating a same-day report now bumps its timestamp, so the viewer header reflects the refresh.
- Server-side compact/prune lock timeout (#159): server-side borg operations (compact, prune, check, info, list, delete) now wait up to 10 minutes for the repo lock instead of failing immediately when an agent backup or another task is still holding it. Previously any overlap produced
Failed to create/acquire the lock ... (timeout). - Dropdown and tooltip clipping (#161, and a long list of predecessors): replaced the per-element z-index patches that kept getting rediscovered with a global fix. Every dropdown toggle now gets
data-bs-strategy="fixed"and every tooltip getscontainer=body+boundary=viewportautomatically. Also removed thetransform: translateY(-1px)hover effect on card elements — the transform was creating a stacking context that trappedposition: fixeddescendants, defeating the z-index escape entirely. Replaced with box-shadow intensification for the same visual feedback.
Upgrade
Bare metal: sudo /var/www/bbs/bin/bbs-update /var/www/bbs
Docker: pull the v2.28.1 image tag.
Hard-refresh (Cmd/Ctrl+Shift+R) after upgrade to bypass the CSS cache for the dropdown fix.
- Proactive hardening: tighter input validation for internal APIs/UI, restricted privileged helper inputs, and canonical filesystem path checks prevent traversal‑style attacks (no CVE ID provided)
- Automatic migration of agent authentication tokens to a stricter at‑rest storage scheme on next heartbeat
Full changelog
Claude Opus 4.7 (Anthropic's top model) was used to audit the codebase for security issues. Several low-to-medium findings have been addressed in this release. No confirmed exploits in the wild — this is proactive hardening.
Highlights:
- Agent credentials — authentication tokens now use a stricter at-rest storage scheme. Migration is automatic on each agent's next heartbeat; no intervention needed.
- Server-side privileged operations — the internal helper used for borg and SSH maintenance now accepts a narrower set of inputs, reducing the blast radius of a hypothetical admin-account compromise.
- Input validation — several gaps in internal APIs and UI handlers have been closed against crafted input.
- Filesystem boundaries — paths passed to privileged operations are validated against canonical forms to prevent traversal-style bypasses.
No breaking changes. No reinstall required.
Recommended: update promptly.
Bare metal: Settings → Updates → Update.
Docker:
```
docker compose pull
docker compose up -d
```
(Docker Hub multi-arch build takes ~15 min after tag.)
❤️ Sponsor this project if you find it useful.
- Dockerfile added `apt-get dist-upgrade` and a second upgrade pass after ClickHouse repo install to reduce vulnerability scan findings.
Full changelog
We are making a set of security updates. This first change helps keep your installed packages more up-to-date.
Patch release — base-image CVE hygiene. No functional changes.
- Dockerfile:
apt-get dist-upgradeadded and a second upgrade pass after ClickHouse repo install. Should meaningfully reduce the Docker Hub vulnerability scan count.
Pull:
docker compose pull
docker compose up -d
Bare metal: Settings → Updates → Update.
- Docker users: run `docker compose pull` then `docker compose up -d`. Bare‑metal installations should use Settings → Updates → Update.
- Automatic UID/GID migration now runs when PUID/PGID/MYSQL_PUID/CH_PUID change; tracked via /var/bbs/config/.ownership and logs timestamped migrations.
- Fully redesigned homepage with hero tiles, job charts, storage location cards, recently completed table, server health auto‑refresh, and demoted ClickHouse/MariaDB panels.
- New Schedules page featuring hour‑of‑day histograms, day‑view timeline, day pills, block context menus for editing time or plan, and inline validation.
Full changelog
Stable release of the v2.27.0 series. :latest is now updated — docker compose pull / bbs-update will upgrade.
We are relasing this for progress, in no way complete. Just wanted to get a bunch of these changes out there while we can. More to come this weekend.
🎉 New dashboard
Fully redesigned homepage addressing #146, #98, #104, #116, and #128.
- Hero tiles — Clients, Running, Recovery Points (replaces the Clickhouse-rows emphasis), Errors. Colored tinted backgrounds for quick visual parsing.
- Jobs (24h) chart + Backup Summary (original data, on-disk footprint, dedup savings %, last backup time) + Server Health (CPU/memory/partition horizontal bars).
- Storage Locations — every configured local location AND every remote SSH host as its own card with per-location disk %, used/free, repo count, and on-disk size. Auto-sizes to a single card at 50% width or a grid of N columns matching your configured locations.
- Recently Completed table with a filter dropdown — checkboxes for Backup / Restore / Prune / Compact / S3 Sync / Other, saved in localStorage, filters via AJAX.
- Server Health auto-refreshes every 15 seconds (CPU / memory / partitions via a lightweight endpoint).
- File Catalog (ClickHouse) and MariaDB panels demoted to compact cards at the bottom. ClickHouse shows Top Repositories list + donut; MariaDB shows a 3×2 stat grid.
- Old dashboard preserved at
/dashboard-legacy.
📅 Schedules page (#145)
New Schedules item in the sidebar with:
- Hour-of-day histogram (1h buckets) stacked per client, segments hoverable/clickable.
- Day-view timeline with half-hour labels, median-duration block heights from backup history.
- Day pills (Mon–Sun) that swap both views at once.
- Click a block or histogram segment → context menu: Change Time / Edit Plan / Disable.
- Inline Change Time modal with add/remove time rows and server-side validation.
- Edit Plan deep-links into the client page with the edit panel pre-expanded.
🐳 Docker bind-mount automation (#143, #121)
- Automatic UID/GID migration when
PUID/PGID/MYSQL_PUID/CH_PUIDchange — no manualchownrequired. - State tracked in
/var/bbs/config/.ownershipso migrations only run when values actually change. - Preflight guards reject
PUID=0, collisions with other services, and collisions with existing SSH-client UIDs. - Detailed timestamped migration logs with file counts and elapsed time.
- Validated on Synology btrfs.
- Thanks @addvanced for the original PR.
✨ Other improvements
- #151 Non-breaking space between numbers and units throughout —
7 GBnot7GB, no wrapping on narrow screens. - #153 Success events no longer clutter the in-app notification bell. Email/push settings unchanged. Opt back in at Settings → Notifications.
- #155 Bare-metal installer now installs
cron(missing on ubuntu-minimal) and fixes prompt/output ordering issues. - #157
/api/v1/storageincludes disk capacity and usage metrics per location. - #142 Daily report email subject prefixed with
[BBS]for consistency. - Page title in the top navbar bar with per-page icons; version pill above sidebar Logout.
MySQLlabel →MariaDB; File Catalog shows(ClickHouse).- Schedules page edge-label clipping, timezone honoring, subdued navy gradient card headers.
📦 Pull
Docker:
docker compose pull
docker compose up -d
Bare metal: Settings → Updates → Update.
❤️ Sponsor this project if you find it useful.
Fixed dashboard breakage, chart sizing issue, and prevented exposure of remote storage details to non-admin users.
Full changelog
Fixes (non-admin users)
- Dashboard was silently broken for non-admin users. An undefined `$chStats` variable (only set inside the admin-only Row 3 block) was referenced further down in the pie-chart JS guard. On PHP 8 that triggered a Whoops error page injection right into the middle of an inline `` tag — the stray `` and `` tags broke JS parsing, which killed Chart.js, the Bootstrap dropdown handlers, and all polling loops. Non-admins saw an empty Jobs (24h) chart and a non-working profile dropdown.
- Jobs (24h) chart rendered at 0px for non-admin users even after the above fix. Non-admins get the chart alone at `col-12` with no siblings to define the row height, so `h-100` + `maintainAspectRatio: false` collapsed the canvas to 0. Wrapped it in a `min-height: 200px` container.
- Daily backup report exposed Remote Storage section (Hetzner / rsync.net quotas) to non-admin users. That's infrastructure detail — now gated behind the admin check that was already computed in the renderer.
❤️ Sponsor this project if you find it useful.
Repo size scan now only occurs when the repository is modified, preventing idle disks from being awakened.
Full changelog
Fixes
- Repo size scan no longer keeps idle disks awake (#135) — previously a scheduler loop ran
duon every local repository every 5 minutes. That's gone. Size now only gets measured when BBS itself modifies the repo (backup, prune, compact, or archive delete). On an idle home server with no active jobs, disks are never touched. A one-time bootstrap still runsdufor any repo whose size hasn't been measured yet (fresh installs and newly-added repos).
❤️ Sponsor this project if you find it useful.
- Archive detail page redesigned to match repo detail layout
- Queue detail now shows prune-specific stats from borg output
Full changelog
Fixes
- Template selector broken in backup plan form (#139) — a previous fix for empty session files was too aggressive and logged out browser-initiated API calls. Sessions are now only skipped for agent Bearer-token endpoints.
- Push notification log entries now include the client (#136) — Log page's Client filter now works correctly for per-client push notification events.
- Dashboard counts no longer full-scan ClickHouse —
uniqExact(agent_id)over the entire file catalog was running every 60 seconds on every dashboard load, pegging ClickHouse. Replaced with cheap MySQL queries andsystem.partsmetadata. - S3 manifest generator no longer pegs ClickHouse — OFFSET-based pagination was O(N²) on large catalogs. Switched to keyset pagination.
Improvements
- Archive detail page redesigned to match the repo detail page — same breadcrumb/header style, reordered stat tiles (Total Size, Dedup Size with savings %, Files, Duration), dark-mode-friendly badges, and Restore Files / Restore Databases deep links that pre-select the archive and mode.
- Queue detail shows prune-specific stats — Archives kept/pruned parsed from the borg output, instead of the generic backup stats card.
❤️ Sponsor this project if you find it useful.
Fixed client storage size reporting to match actual disk usage and cleaned up excessive empty PHP session files.
Full changelog
If you find BBS useful, please consider sponsoring this project. It takes significant work to build and maintain — your support helps keep it going.
Bug Fixes
-
Client detail page showed wrong storage size — Reverse of #118. The recalculation from
SUM(archives.deduplicated_size)undercounted actual disk usage (excluded borg metadata and uncompacted chunks). Local repos now use the scheduler's 5-minuteduscan as the source of truth, matching whatdu -hreports on the filesystem. (#135) -
Hundreds of thousands of empty PHP session files — Every API request from an agent created a new empty session file, and Docker installs had no cleanup mechanism. Sessions now only start for UI requests (agents use Bearer token auth), and the scheduler prunes old session files hourly. (#131)
-
Upgrade progress step count — Fixed off-by-one: was showing
[1/10]then[2/9]through[9/9]. Now consistently/9. (#136, thanks @MegaV0lt)
- Redesigned upgrade progress screen with collapsible stepped list and status icons
- OIDC Redirect URL override setting for reverse‑proxy deployments
Full changelog
If you find BBS useful, please consider sponsoring this project. It takes significant work to build and maintain — your support helps keep it going.
New
- Redesigned upgrade page — The upgrade progress screen now shows a clean stepped list with status icons instead of a raw terminal log. Each step is collapsible to show details if needed.
- OIDC Redirect URL override — New optional setting under Settings → SSO. Use when BBS sits behind a reverse proxy and agents use an internal URL but SSO must use a different public hostname. Leave blank for auto-detection (existing behavior). (#125)
Bug Fixes
- False "upgrade failed" messages — When PHP-FPM restarts mid-update the
=== Update complete ===marker could get dropped from the log, making successful upgrades appear as failures. Now detects completion based on reaching the final step marker. (#128) - Can't delete archive from recovery points list — The row-level click handler was swallowing the trash button's click, navigating to the archive detail page instead of opening the delete confirmation. (#126)
- Update step labels — Changed "Setting up" / "Fixing" to "Checking" since these steps are idempotent verification rather than repair.
Fixed root filesystem backup hangs caused by the pre-count scanner traversing cross‑filesystem directories.
Full changelog
If you find BBS useful, please consider sponsoring this project. It takes significant work to build and maintain — your support helps keep it going.
Bug Fixes
-
Root filesystem backups hanging — The pre-count file scanner was walking into
/proc,/sys, NFS mounts, and other cross-filesystem directories even though borg skips them with--one-file-system. A hung or slow mount would stall the count indefinitely and borg would never start. The pre-count now checks filesystem device IDs and stays on the same mount, matching borg's behavior. (#129) -
pip3 borg updates on modern Python — Added
--break-system-packagesflag for PEP 668 compliance (required by Homebrew Python 3.11+ and Debian 12+). Also validates pip3 is functional before attempting the install — broken pip now reports a clear error instead of a raw traceback. -
Agent auto-update not pushing pip fixes — Agent version was not bumped after adding the pip fixes, so the server thought agents were already current and never pushed the update.
Agent
- bbs-agent v2.25.0
count_files()respects--one-file-systemviast_devchecks- pip3 validated before use,
--break-system-packagesfor PEP 668 - Cleaner error messages for pip failures
- Progress bar redesign: bytes moved to upper‑right, file name left‑justified, trailing slash fixed in PHP and JS
Full changelog
If you find BBS useful, please consider sponsoring this project. It takes significant work to build and maintain — your support helps keep it going.
Bug Fixes
-
Stall detection actually works now — The heartbeat stall query used
INTERVAL ? MINUTEwhich MySQL doesn't support with prepared statement placeholders. The query silently returned no results, so stalled jobs were never killed. Also now detects jobs that hang before any progress is reported. (#130) -
Progress bar redesign — Bytes processed moved to upper right, current file left-justified below the bar as a single truncated line. Trailing slash on file paths finally fixed in both PHP and JS rendering paths.
- Archive Detail Page with summary cards, file changes table, largest files list, tabbed file browser, deleted‑files detection, and on‑demand ClickHouse analytics
- Plan names displayed above raw archive names in the recovery points list
Full changelog
New Features
-
Archive Detail Page — Click any recovery point in the repo view to see a full breakdown of that backup:
- Summary cards: file count, original/dedup size, dedup savings %
- File Changes: stacked progress bar + table showing Added, Modified, Unchanged counts with proper labels for all borg status codes (directories, symlinks, hardlinks, etc. shown separately)
- Largest Files: top 20 files by size with full path
- File Browser: tabbed interface (All / Added / Modified / Deleted / Unchanged) with search and server-side pagination via ClickHouse — handles backups with millions of files
- Deleted files detection: compared against the previous archive
- Backup directories and database backup info
-
Plan names in recovery points list — The repo detail page now shows the backup plan name above the raw archive name, making it easier to identify which plan created each recovery point.
All file stats are computed on-demand from ClickHouse, which handles analytical queries over millions of rows near-instantly.
- Server-hosted Borg 1.4.4 binary (GPG‑signed) replaces the 1.4.3 version
Full changelog
New
- Server-hosted borg 1.4.4 binary — GPG-signed standalone binary for glibc 2.17+ (CentOS 6/7, Ubuntu 14.04+, Debian 8+). Available under Settings → Borg Management when "Use Server Binaries" is enabled. Replaces the 1.4.3 binary.
Bug Fixes
- Progress bar trailing slash — The current file path in the backup progress bar no longer shows a trailing
/on directory entries.
Fixed Docker tar.gz download failures and OIDC redirect_uri protocol handling behind TLS‑terminating proxies.
Full changelog
Bug Fixes
- Download .tar.gz failed in Docker — PHP's
sys_get_temp_dir()returns/var/bbs/tmp/in some Docker setups, but the ssh-helper only accepts/tmp/. Borg extract was silently rejected, producing "No files were extracted." Now uses/tmpexplicitly. (#81) - OIDC redirect_uri behind reverse proxies — The OAuth redirect_uri used
http://when BBS runs behind a TLS-terminating reverse proxy (nginx, Traefik, Kubernetes ingress). Now respects the standardX-Forwarded-Protoheader. (#125)
- Agents affected by the Python 3.4 incident require a one‑time manual re‑run of the install script from the BBS dashboard to apply the self‑recovery wrapper.
- Configurable stall timeout under Settings → Agent (10 min–24 h, default 2 h)
- Report frequency option: daily or weekly with day‑of‑week picker
- Log page client filter dropdown and server log auto‑prune at 30 days
Full changelog
Bug Fixes
- Stall detection — Fixed a structural bug where stall detection never fired during running jobs because the agent's main loop was blocked. The heartbeat channel now carries stall signals and can kill a hung borg process. Configurable timeout under Settings → Agent, default 2 hours. (#117)
- Dashboard storage mismatch — Repo
size_bytesis now refreshed after prune and archive delete, so the dashboard matches the client detail page without needing to visit it first (#118) - Imported repos show 0 files —
catalog_syncnow readsnfilesfromborg infoduring import. Existing repos with 0 file counts are auto-backfilled from ClickHouse on next page view (#115) - Repo name sanitization on import — Import now sanitizes names the same way as create, preventing leading slashes and special characters (#114)
- Same-path rename — Renaming
/home→homeno longer fails with "target already exists" (#114) - False "update failed" — PHP-FPM restart failure no longer aborts the update script (#93)
- Agent offline severity — Downgraded from critical/error to warning (#111)
New Features
- Report frequency — Users can choose daily or weekly report emails with a day-of-week picker (#120)
- Log page client filter — Dropdown to filter log entries by client alongside the existing level filter
- Log retention — Server log auto-pruned at 30 days, backup jobs at 90 days
- Stall timeout setting — Configurable under Settings → Agent (10 min – 24 hours, default 2 hours)
Agent
- bbs-agent v2.24.3
- Heartbeat thread now processes stall detection and cancel signals from the server
- Stalled borg processes are killed automatically when the server detects no progress
- Cancel via UI now works even when borg is completely frozen (relayed through heartbeat)
Agent Update Safety
v2.24.10 included the self-recovery wrapper (bbs-agent-start.sh) and syntax validation. Agents bricked by the Python 3.4 incident need a one-time manual fix (re-run the install script from the BBS dashboard). After that, the wrapper prevents this class of failure permanently.
- Agents previously bricked by v2.24.4–v2.24.9 require a one‑time manual re‑run of the install script; after that the new startup wrapper provides auto‑recovery.
- Python 3.4 compatibility fix added for bbs-agent.
- Log page now includes client filter dropdown alongside level filter
- Backup jobs and server logs are auto‑pruned (jobs 90 days, logs 30 days)
- Agent offline notifications downgraded from critical to warning
Full changelog
Bug Fixes
- Job cancellation hang — Killing borg on cancel now kills the entire process tree (including the SSH transport child) so the agent doesn't hang waiting on a dead pipe (#91)
- Catalog import on FUSE/shfs — Catalog files are now explicitly chgrp'd to www-data with a chmod 644 fallback, plus diagnostic logging to trace permission issues on Unraid and similar FUSE mounts (#84)
- Daily catalog rebuild loop — Archives with 0 indexable files no longer trigger a rebuild every 24 hours (#112)
- Import repo first-click failure — Removed
2>&1from verify-repo helper so borg's cache initialization messages don't corrupt the JSON output (#99) - Shell hook script arguments — Pre/post script paths now support arguments (e.g.
/path/script.sh before) via shell-style parsing (#107) - Queue detail overflow — Long file paths and command lines now wrap instead of overflowing the layout (#108)
- Prune imported archives — Prune can now clean up imported archives on single-plan repos (#109)
- False "update failed" — PHP-FPM restart failure during update no longer aborts the script before printing the completion marker (#93)
- Repo name sanitization on import — Import now sanitizes repo names the same way as create, preventing leading slashes and special characters (#114)
- Same-path rename — Renaming a repo where old/new paths resolve to the same directory (e.g.
/home→home) now just updates the DB name (#114) - Download .tar.gz diagnostics — "No files were extracted" now includes borg output, exit code, and a root-level view of the tmp dir in the server log (#81)
- Last Backup stat tile — Font size now matches the other stat tiles (#103)
- OIDC SSO — Fixed
addScopeTypeError when configuring multiple scopes (#95)
Agent
- bbs-agent v2.24.2
- Self-recovery from bad updates: syntax validation (ast.parse) before replacing the running script, automatic .bak backup, and a new startup wrapper (
bbs-agent-start.sh) that downloads a fresh copy from the server if the .py is broken - Shell hook scripts support command-line arguments
- Cancel kills the entire borg process group (not just borg) to prevent orphaned SSH children
- Windows: bundled Git-for-Windows SSH to avoid the built-in ssh.exe stdin forwarding hang
- Python 3.4 compatibility fix for
**kwargsunpacking
- Self-recovery from bad updates: syntax validation (ast.parse) before replacing the running script, automatic .bak backup, and a new startup wrapper (
Server / UI
- Log page — Added client filter dropdown alongside the existing level filter
- Log retention — server_log auto-pruned at 30 days, backup_jobs at 90 days
- Borg Management page — Windows/macOS agents no longer show a false "incompatible" warning; tooltip improved for Linux agents without a matching server binary
- Agent offline notifications — Downgraded from critical to warning severity (#111)
Agent Update Safety
A Python 3.4 syntax incompatibility in v2.24.4–v2.24.9 caused agents on CentOS 6/7 to crash on update with no auto-recovery path. This release adds three layers of defense:
- Syntax validation —
ast.parsechecks downloaded code before replacing the running script - Backup —
.bakcopy saved before every replacement - Startup wrapper —
bbs-agent-start.shruns before the Python agent, downloads a fresh copy from the server if the .py is broken, and restores from backup if the server is unreachable
Agents that were bricked by the earlier update need a one-time manual fix: re-run the install script from the BBS dashboard on the affected machine. After that, the wrapper prevents this class of failure permanently.
Fixed first‑click repository import failure caused by cache initialization corrupting JSON output.
Full changelog
Bug Fixes
- Repository import: Fixed first-click import failure caused by borg cache initialization messages corrupting the JSON output (#99)
- Script plugin: Pre/post script paths now support arguments (e.g.
/path/script.sh before) via shell-style parsing (#107) - Queue detail page: Long file paths and command lines now wrap inside table cells instead of overflowing the layout (#108)
- Prune: Imported archives on single-plan repositories are now eligible for prune cleanup (#109)
- Client detail page: Recent Activity error messages now show an ellipsis when truncated and tooltip shows up to 500 chars (#107)
- Last Backup stat tile: Font size now matches the other stat tiles (#103)
- OIDC SSO: Fixed
addScopeTypeError when configuring multiple scopes (#95)
Agent
- bbs-agent v2.24.0: Shell hook scripts now support command-line arguments
- Generic OpenID Connect (OIDC) single sign‑on with provider list, config UI, new user handling options and optional IdP logout
- All JavaScript timestamps now use the BBS profile timezone for consistent display
Full changelog
What's New
OIDC Single Sign-On (#70)
Generic OpenID Connect SSO support — works with Keycloak, Authentik, Azure AD, Google Workspace, Okta, Auth0, Authelia, and any OIDC-compliant provider.
- Settings > Authentication tab for configuration
- SSO button on login page alongside existing password login
- New user handling: deny access, pending admin approval, or copy permissions from a template user
- SSO users skip 2FA (rely on identity provider for auth strength)
- Optional OIDC logout (sign out of IdP when logging out of BBS)
- Documentation
Job Cancellation (#91)
Cancel now properly stops running backups. The server signals the agent via the progress response, the agent kills the borg subprocess within ~5 seconds, and a break-lock job is automatically queued to clean up any stale locks.
JS Timezone Consistency (#87)
All JavaScript time formatting now uses the BBS profile timezone instead of the browser's local timezone. Prevents times from jumping after AJAX refresh when the browser timezone differs from the profile setting.
Fixes
ClickHouse Stability (#88)
Reverted thread pool limits and stderr redirect that were causing ClickHouse to fail to start on some installations. Logging cap (warning level, 10MB rotation) remains.
ClickHouse Docker Startup (#83)
Increased startup health check wait from 15 to 30 seconds for slower VMs.
Installer Fix (#86)
ClickHouse table creation deferred until after BBS code is downloaded — was failing because it referenced schema files before git clone.
Borg Update Retry Loop (#79)
Auto-update no longer re-queues failed borg updates every 30 seconds. Waits 24 hours before retrying.
- Settings > Branding tab allows uploading custom navbar icon, login page logo (max 475x100px), and forcing dark or light theme on the login screen
- System-wide default theme (dark/light) configurable in Settings > General applies to login page and new users
Full changelog
What's New
Branding (#85)
New Settings > Branding tab for customizing the BBS interface:
- Navbar Icon — upload a square PNG to replace the default BBS logo in the top-left corner (resized to 120x120px max)
- Login Page Logo — upload a wide PNG for the login screen (resized to 475x100px max)
- Login Page Theme — force dark or light mode on the login page regardless of user preferences (useful when your logo only works on one background)
Images are stored in the database and persist across Docker rebuilds. Client-side resizing with live preview before saving.
System-Wide Default Theme (#85)
Admins can set the default theme (dark/light) in Settings > General. Applies to the login page and new users. Users can still override in their profile.
Fixes
Borg Update Retry Loop (#79)
When a borg update failed (e.g. disk full), the auto-update logic re-queued a new attempt every 30 seconds — creating hundreds of failed jobs and error notification emails. Now waits 24 hours before retrying a failed update.
ClickHouse Thread Count (#83)
ClickHouse thread pools capped to reasonable limits (max 32 threads, background pools 1-2 each). Default ClickHouse spawned ~800 threads regardless of workload. Should drop to ~50-80 threads.
Force MySQL session timezone to UTC, fixing timestamp display inconsistencies.
Full changelog
Fixes
Timezone: Force MySQL Sessions to UTC
Completes the timezone fix from v2.21.2. PHP was already forced to UTC, but MySQL's NOW() and CURRENT_TIMESTAMP were still using the server's local timezone on non-UTC systems. Now sets time_zone = '+00:00' on every MySQL connection so all timestamps are consistent. This should fully resolve time display issues on servers in non-UTC timezones (e.g. Europe/Prague).
New jobs after updating will show correct times. Old timestamps stored before this fix may still be offset.
- Recovery Points table on repo detail page showing archive metadata and delete via job queue
- Restore dropdowns now display the backup plan that created each archive
Full changelog
What's New
Archive Management
The repo detail page now shows a Recovery Points table listing all archives with name, date, file count, original and deduplicated sizes. Individual archives can be deleted via a confirmation modal — deletions are queued as jobs and wait their turn in the normal job queue.
Restore Point Context
Restore point dropdowns on the Restore tab now show which backup plan created each archive (e.g. "Thursday, Apr 2 at 8:16 PM — daily-full"), making it easy to distinguish archives when multiple plans back up to the same repo.
Fixes
Shell Hook Post-Scripts Always Run (#79)
Post-scripts now run regardless of whether the backup succeeded or failed. Previously, if a backup failed, the post-script was skipped — leaving services that were stopped by the pre-script in a down state. Other plugin cleanup (dump file deletion) still only runs on success.
ClickHouse Logging Capped (#79)
ClickHouse's file-based logging is now limited to warning level, 10MB max file size with 2 rotations, and stderr redirected to /dev/null. Prevents ClickHouse from filling the container disk with unbounded stderr logs (7+ GB observed in the wild) when its own log rotation fails.
Fixed PHP timezone mismatch with MySQL timestamps.
Full changelog
Fixes
Timezone: Force PHP to UTC
PHP's date() function was using the server's local timezone (e.g. Europe/Prague = UTC+2) while MySQL stores timestamps in UTC. This caused all timestamps written by PHP — started_at, completed_at, heartbeats, log entries — to be offset by the server's timezone. Now forces date_default_timezone_set('UTC') at both the web entry point and scheduler, so all PHP timestamps match MySQL.
Scheduler: Fix PHP warnings on SSH repo paths
is_dir() was being called on raw SSH-format repo paths (ssh://user@host/./repo), triggering PHP "unable to find ssh wrapper" warnings. Now uses the proper local path resolver.
ReportService: Fix undefined variable warnings
$dayStart and $dayEnd were used in the daily report error log query without being defined. Now properly initialized from the report date.
- Added S3 Storage Class, Server‑Side Encryption (SSE‑S3/KMS), and Bandwidth Limit options to global settings
Full changelog
Fixes
Non-Default Storage Location Fix (#76)
Fixed repos failing to create on non-default storage locations (NFS mounts, external drives, etc.) with "repo path must be under /var/bbs/ or a registered storage location". The local path resolver was returning a double-slash prefix (e.g. //var/TB10/... instead of /var/TB10/...) which failed the security check.
S3 Offsite Sync Options (#77)
Added missing options to the global S3 settings page:
- Storage Class — Standard, Standard-IA, Intelligent-Tiering, Glacier, Deep Archive
- Server-Side Encryption — AES-256 (SSE-S3) or AWS KMS (SSE-KMS) with optional KMS Key ID
- Bandwidth Limit — Throttle upload speed (e.g. 50M for 50 MB/s)
Per-plugin custom S3 configs can also override storage class and encryption.
Storage Page Layout Fix
Fixed broken card layout on the Storage Locations page caused by a leftover HTML element.
- Storage locations become immutable after creation; changes require deletion and recreation.
- Setup wizard storage path locked to `/var/bbs/home` and no longer editable.
- Removed `storage_path` from the editable settings POST whitelist.
- InterWorx Control Panel backup plugin with Structure Only, Full Backup, and Partial Backup modes.
- Plugin form enhancements: `select` dropdown fields and `show_when` conditional visibility.
Full changelog
What's New
InterWorx Control Panel Backup Plugin
New pre-backup plugin for InterWorx hosting control panel servers. Runs backup.pex before borg archives to capture domain configurations, websites, databases, and email.
Three backup modes:
- Structure Only — Manifest XML only, no data files (fastest, ideal when borg already backs up the data directories)
- Full Backup — All website files, databases, and email
- Partial Backup — Select individual components (web, db, mail) with options to exclude logs and stats
Configurable domains, output directory, compression level, and automatic cleanup after borg archiving.
Storage Location Security (#71)
- Storage locations are now immutable once created — delete and recreate to change. Prevents accidental path changes that would break existing repos.
- Storage path in setup wizard is locked to
/var/bbs/homeand no longer editable. External storage should be added as Storage Locations after setup. - Removed
storage_pathfrom the editable settings POST whitelist.
Plugin Form Improvements
- Generic plugin forms now support
selectdropdown fields andshow_whenconditional visibility. Any plugin can use dropdowns with fields that appear/hide based on the selected value.
Fixed timezone‑related duration calculations and timestamp handling across the server.
Full changelog
Fixes
Timezone-Safe Duration Calculation
On servers where PHP's timezone differs from MySQL's (e.g. PHP set to Europe/Prague while MySQL uses UTC), job durations were calculated incorrectly — off by the timezone offset (e.g. showing 2h for an instant operation). Now uses MySQL's TIMESTAMPDIFF() for all duration calculations to keep everything in the same timezone. Also fixed completed_at timestamps being written in PHP local time instead of MySQL time.
24-Hour Time Format Fixes
- Added missing conversion patterns for
g:i:s A(with seconds) andg:ia(lowercase am/pm) used on queue detail and chart pages - Job list on client Status tab now shows completion time instead of queue time for finished jobs
Dashboard Elapsed Time
Fixed running job elapsed time display on the dashboard to correctly treat started_at as UTC.
- MongoDB backup/restore plugin with UI integration, per‑database dumps, gzip compression, and one‑click restore
- Per‑user configurable 24‑hour time display across timestamps, charts, queues, and email reports
Full changelog
What's New
MongoDB Plugin (#68)
Community-contributed MongoDB backup/restore plugin by @sainf. Supports per-database dumps with mongodump, gzip compression, automatic cleanup, and one-click restore via mongorestore.
- Full backup/restore workflow integrated into the UI
- Plugin configuration with auth database, database selection, and exclusions
- Documentation
24-Hour Time Format (#69)
Per-user preference to switch between 12-hour (1:30 PM) and 24-hour (13:30) time display. Set in Profile > Time Format. Applies to all timestamps, chart labels, queue pages, and email reports.
Fixes
Database Restore Improvements (All Plugins)
- Targeted extraction — Restores now extract only the requested database files from archives instead of the entire dump directory. Dramatically faster on large archives. (MySQL was fixed in v2.19.1, PostgreSQL and MongoDB fixed in this release)
- Safety backup before replace — Before overwriting a database during restore, the current version is dumped as a safety net (MySQL, PostgreSQL, and MongoDB)
- Accurate job timing — All database restore types now report running status immediately so Started At and Duration are accurate
- PostgreSQL restore dispatch fix —
restore_pgtasks were missing fromgetTasksForAgent(), potentially causing PG restores to get stuck in "sent" status
Other Fixes
- Job list on client Status tab now shows completion time instead of queue time for finished jobs
Fixed MySQL restore performance by extracting only requested databases and added a safety backup before replacement.
Full changelog
Fixes
MySQL Restore Performance (#67)
- Extract only requested databases —
borg extractnow targets specific dump files instead of the entire dump directory which caused unnecessary overhead and disk I/O. - Safety backup before replace — Before overwriting a database during a replace restore, the agent dumps the current DB to
dump_dir/dbname_pre_restore.sql.gzas a safety net. - Accurate job timing — Agent now reports running status immediately when the restore begins (during borg extract), so Started At and Duration reflect actual elapsed time.
Path Resolution Fix
getLocalRepoPath()now parses the directory name from the SSH path URL instead of relying on$repo['name'], fixing prune, compact, catalog, restore, and S3 sync failures after repository renames.
- Full Admin REST API supporting Clients, Repositories, Backup Plans, Jobs, Queue, Plugins, and Storage resources
Full changelog
What's New
Admin REST API (#64)
A full provisioning API for automated infrastructure-as-code workflows with Ansible, Terraform, or CI pipelines.
Authentication: Bearer token via Authorization: Bearer bbs_tok_...
- Token management in Settings > API tab or via
bin/bbs-tokenCLI
Endpoints:
| Resource | Endpoints |
|----------|-----------|
| Clients | GET/POST list/create, GET/PUT/DELETE detail/edit/delete |
| Repositories | GET/POST list/create, PUT rename, DELETE delete (local + remote SSH) |
| Backup Plans | GET/POST list/create, PUT edit, DELETE, POST pause/resume/trigger |
| Jobs | GET list (paginated, filterable), GET detail |
| Queue | GET global active queue |
| Plugins | GET list, GET schema, GET/POST configs per client |
| Storage | GET local + remote SSH locations |
Full documentation: API Wiki
Fixes
- Fix
getLocalRepoPath()to parse directory name from the SSH path URL instead of relying on$repo['name']— prevents path mismatches after renames for prune, compact, catalog, restore, and S3 sync operations
- Rename local repositories via the detail page (blocked during active jobs), with on-disk directory renamed and database path updated.
- New repository names are sanitized to remove special characters, replacing spaces with hyphens for filesystem safety.
- Backup plan templates now support Borg options such as compression, exclude caches, one file system, noatime, numeric IDs, skip xattrs, and skip ACLs.
Full changelog
What's New
Repository Rename (#61)
- Rename local repositories from the repo detail page (pencil icon next to name). Renaming remote repos is not possible in some services, so naming is limited to local (and NFS) type repos.
- Blocked while jobs are active on the repo
- Directory renamed on disk, path updated in database — borg handles relocation seamlessly
Repository Name Sanitization
- New repo names are sanitized for filesystem safety (special characters stripped, spaces become hyphens)
- Prevents problematic characters like brackets, spaces, and symbols in directory names
Backup Plan Template Options (#63)
- Templates now support borg options (compression, exclude caches, one file system, noatime, numeric IDs, skip xattrs, skip ACLs)
- Options are applied automatically when selecting a template during plan creation or editing
Fixes
- Fix dropdown menus getting clipped on schedule and repo cards
- Fix template options not applying to plan form checkboxes when template has no options saved
- Default prune hourly retention set to 0 for new plans (only useful when backing up more than once per hour)
- SMTP username/password now optional for notification services (#57)
- Fix installer header box alignment (#62)
- Bare metal/VM installs must restart ClickHouse (sudo systemctl restart clickhouse-server) after update to apply reduced idle CPU metrics polling interval.
- Remote SSH storage monitoring shows disk usage via df -k over SSH
- Low storage notifications now monitor remote SSH repository space
Full changelog
New Features
- Remote SSH storage monitoring (#49) — The Storage Locations page now shows disk usage for remote SSH repositories (rsync.net, Hetzner Storage Box, etc.) with progress bars. Polled every 15 minutes via
df -kover SSH. Providers that don't support disk queries (BorgBase) show "Quota unavailable." - Low storage notifications for remote hosts — Storage alert threshold now also monitors remote SSH storage. You'll receive notifications when remote hosts are running low on space.
- Per-agent server host and SSH port overrides (#54) — Agents connecting from outside the local network can use a different hostname and SSH port. Set in Edit Client modal.
Bug Fixes
- Fix bbs-update crashing on some PHP installations —
php_ini_scanned_dir()doesn't exist on all PHP builds, which caused the update to abort mid-way (steps 5-9 never ran). Users who updated to v2.18.3-v2.18.5 should re-run the update to complete the remaining steps. - Fix race condition: stall detection abandoning jobs being delivered (#55)
- Fix daily report showing 0 completed/failed backups — Now counts backups since the last report instead of a timezone-dependent "today" window.
- Fix plugin test timeout (#50) — Test was using a hardcoded 30s timeout instead of the configured value (default 300s).
- Fix repo card dropdown menu clipped by parent container
- Fix PHP warnings on malformed request URIs (#53)
- Fix schedule showing Overdue while backup is running
Improvements
- Increase PHP max_execution_time to 300s — Default 30s was too short for large catalog imports and API operations under load. Set for both Docker and bare metal installs.
- Increase agent API request timeout to 60s — Was 30s, could silently fail under server load.
- Reduce ClickHouse idle CPU usage — Internal async metrics polling reduced from 1s to 60s. Note: Bare metal/VM installs need to restart ClickHouse (
sudo systemctl restart clickhouse-server) after updating.
- Per-agent server host and SSH port overrides for external connections
Full changelog
New Features
- Per-agent server host and SSH port overrides (#54) — Agents connecting from outside the local network can now use a different hostname and SSH port. Set in Edit Client → Server Host Override / SSH Port Override. Empty = use global settings. No agent code changes needed. See #54 for detailed setup instructions with SSL considerations.
Bug Fixes
- Fix race condition: stall detection abandoning jobs being delivered (#55) — The tasks endpoint delivered a job and asked the agent "are you running this job?" in the same response. Jobs being delivered are now excluded from stall detection.
- Fix container disk full from temp files (#52) — Catalog TSV files, MySQL temp tables, and ClickHouse processing temps now write to
/var/bbs/tmpon the persistent volume. - Fix PHP warnings on malformed request URIs (#53)
- Fix schedule showing Overdue while backup is running
Fixed race condition causing abandoned jobs to not record archives during delivery.
Full changelog
Bug Fixes
- Fix race condition: stall detection abandoning jobs being delivered (#55) — The tasks endpoint delivered a job and asked the agent "are you running this job?" in the same response. The agent reported "not running" (it just received it), the server marked it abandoned, and the backup completed successfully but the archive was never recorded. Jobs being delivered are now excluded from stall detection.
- Fix container disk full from temp files (#52) — Catalog TSV files, MySQL temp tables, and ClickHouse processing temps now write to
/var/bbs/tmpon the persistent volume instead of the container's overlay filesystem. - Fix PHP warnings on malformed request URIs (#53) —
parse_urlreturned null for network-path URIs, causing deprecation warnings and a broken 404 response. - Fix schedule showing Overdue while backup is running — The scheduler now advances
next_runeven when skipping a duplicate, so the dashboard no longer shows "Overdue" for plans with an active backup.
- Management tasks (update_borg/update_agent) bypass queue slots
- Stale job detection now only fails sent/running jobs, leaving queued jobs intact
- Shell script plugin test reports exit code and output
Full changelog
Bug Fixes
- Fix long-running backups killed at 24 hours despite active progress (#51) — PHP and MySQL timezone mismatch on Docker caused
last_progress_atto always appear stale. All database timestamps now use MySQL's clock directly. - Fix queue blocking: offline agents no longer block all backups — Two stuck jobs for offline agents could fill the 4-slot queue and prevent all other agents from running backups for hours. Slot counting now excludes jobs for offline agents and management tasks.
- Fix Rebuild Full not recovering wiped archives (#47) — Now properly re-reads all recovery points from the borg repository with sizes, then rebuilds the file catalog.
- Fix borg warnings wiping all archive records (#47) — Separated stderr from JSON stdout in bbs-ssh-helper, added JSON validation guards.
- Fix client creation on NAS/NFS storage paths (#48) — SSH home directories are created on local filesystem when storage_path points to a NAS mount.
- Fix allowed-storage-paths lost on Docker container restart (#47) — The file is now regenerated from the database on every container start.
- Fix auto-catalog-rebuild infinite loop — Added 24-hour cooldown.
Improvements
- update_borg/update_agent bypass queue slots — Management tasks run freely without waiting for backup slots. They only wait if the agent has an active backup.
- Stale job detection no longer fails queued jobs — Only sent/running jobs are failed when an agent goes offline. Queued jobs wait for the agent to come back.
- Shell script plugin test now runs scripts (#46) — Reports exit code and output instead of just checking file existence.
- FreeBSD borg updater support — Added
pkg install borgbackupand pip3→pip fallback. - Better error diagnostics — Catalog sync, SSH provisioning, and borg list failures now report actual errors.
Fixed catalog sync failures on NAS/non-default storage locations and improved error diagnostics.
Full changelog
Bug Fixes
- Fix catalog sync failing on NAS/non-default storage locations (#47) — The
/etc/bbs/allowed-storage-pathsfile (which authorizes bbs-ssh-helper to access repo paths outside/var/bbs/) lives inside the container filesystem and was lost on every container recreation. Now regenerated from the database on container start. This was the cause of "repo path must be under /var/bbs/ or a registered storage location" errors after upgrading. - Better error diagnostics for catalog sync failures — Error messages now include the actual borg output instead of a generic "not valid JSON" message.
Fixed "Rebuild Full" not recovering wiped archives, enabling proper restoration of lost backup data.
Full changelog
Bug Fixes
- Fix "Rebuild Full" not recovering wiped archives (#47) — Rebuild Full now properly re-reads all recovery points from the borg repository, repopulates archive records with sizes, then rebuilds the file catalog. Previously it only rebuilt the file catalog from an already-empty archives table.
- Fix client creation on NAS/NFS storage paths (#48) — SSH home directories are now created on the local filesystem when storage_path points to a NAS mount.
- Better error diagnostics — Catalog sync failures now report the actual borg error instead of a generic "not valid JSON" message.
- Fix auto-catalog-rebuild loop — Added 24-hour cooldown to prevent infinite re-queuing.
- Shell script plugin test now executes scripts and reports exit code + output
- Server logs list attached plugins per backup job for debugging
Full changelog
We apologize for the issues in recent releases. This release addresses several bugs that were introduced and fixes recovery tooling that should have been working correctly from the start.
Bug Fixes
- Fix "Rebuild Full" not recovering wiped archives (#47) — The Rebuild Full button was only rebuilding the ClickHouse file catalog from the MySQL archives table. If the archives table was empty (from the bug in v2.17.9), it had nothing to work with. Rebuild Full now properly re-reads all recovery points from the actual borg repository, repopulates archive records with correct sizes, then rebuilds the file catalog.
- Fix borg warnings wiping all archive records from database (#47) — The
bbs-ssh-helperborg-listcommand merged stderr into stdout, corrupting JSON output when borg emitted warnings. Both the post-prune archive sync and catalog sync deleted all archive records when JSON parsing failed. Fixed by separating stderr and adding JSON validation guards. - Fix client creation failing on NAS/NFS storage paths (#48) — SSH home directories require
chownwhich fails on NAS mounts. Client creation now detects when storage_path points to a NAS storage location and creates SSH home dirs on the local filesystem instead. - Fix auto-catalog-rebuild infinite loop — Added 24-hour cooldown to prevent the scheduler from re-queuing catalog rebuilds every minute when some archives can't be indexed.
Improvements
- Shell script plugin test now runs scripts (#46) — Previously only checked file existence/permissions. Now actually executes scripts and reports exit code + output.
- Server log shows attached plugins per backup job for easier debugging.
- Better error messages — SSH provisioning failures now log the actual error from bbs-ssh-helper.
- Agent inhibits system sleep during backup tasks
Full changelog
Bug Fixes
- Fix backup paths with spaces — Directory paths containing spaces (e.g.
/var/lib/.../Application Support/...) were being split on whitespace instead of newlines, breaking the borg command. Fixed in both the server-side command builder and the agent's file counter. (#45)
New Features (since v2.17.7)
- Prevent OS sleep during backups — The agent now inhibits system sleep while a task is running, preventing Windows laptops (and macOS/Linux) from going to sleep mid-backup. (#43)
Timezone Fixes (since v2.17.6)
- Fix schedule timezone not persisted on plan update, causing duplicate daily backups
- Fix timezone not propagated to schedules on profile change
- Force MariaDB to UTC to fix restore point timestamp display on non-UTC Docker hosts (#42)
Enjoying Borg Backup Server? Consider sponsoring this project on GitHub.
Fixed backup schedule timezone persistence and MariaDB timestamp handling bugs.
Full changelog
Bug Fixes
- Fix schedule timezone not persisted on plan update — editing a backup plan now saves the user's timezone to the schedule, preventing SchedulerService from recalculating next_run with a stale timezone (which caused backups to run twice per day)
- Fix timezone not propagated to schedules on profile change — changing timezone in /profile now updates all matching schedules and recalculates next_run immediately
- Fix MariaDB using host timezone for CURRENT_TIMESTAMP — Docker entrypoint now sets MariaDB default-time-zone to UTC via config file, fixing restore point timestamps displaying hours off on non-UTC hosts
Thanks to @erycsonero for reporting and diagnosing these issues (#42).
- SSH port setting is no longer overwritten on every container restart (entrypoint fix)
- Docker first-run setup modal auto-detects containers and prompts admin for hostname, web port, SSH port
- Cleaner docker-compose.yml with variable substitution in .env and added .env.example
- Podman support added alongside Docker container detection
Full changelog
What's New
- Docker first-run setup modal — Auto-detects Docker/Podman containers and prompts admin to configure hostname, web port, and SSH port on first login
- Cleaner docker-compose.yml — Uses variable substitution so ports are defined once in
.env; added.env.example - Entrypoint fix — SSH port setting no longer overwritten on every container restart
- Full borg compression support — Backup plans now support all borg compression specs (lzma, auto, obfuscate, etc.) via free-text input with suggestions (thanks @faultoverload)
- Podman support — Container detection works for both Docker and Podman
- FreeBSD agent support via `pkg` installer and `rc.d` service (tested on FreeBSD 15.0‑RELEASE)
- SSH port configurable in Settings > Server Host for Docker deployments
Full changelog
Bug Fixes
- Add SSH port setting to settings page — Docker setups that map SSH to a non-standard external port (e.g. 2222) had no way to configure this, causing all agent SSH connections to fail with "Permission denied." The SSH port field was hardcoded to 22 with no UI to change it. Now editable in Settings > Server Host section.
New Features
- FreeBSD agent support — The agent installer now supports FreeBSD via
pkg, with anrc.dservice script. Tested on FreeBSD 15.0-RELEASE.
- FreeBSD agent installer via pkg, rc.d service and fetch support (tested on FreeBSD 15.0-RELEASE)
Full changelog
New Features
- FreeBSD support — The agent installer now supports FreeBSD. Installs borg and python3 via
pkg, creates anrc.dservice usingdaemon(8), and supports FreeBSD's built-infetchfor downloads. Tested on FreeBSD 15.0-RELEASE.- Prerequisite:
pkg install bash, then:fetch -o - https://server/get-agent | /usr/local/bin/bash -s -- --server URL --key KEY
- Prerequisite:
Bug Fixes
- Fix Windows SSH key permissions (PowerShell approach) — Previous icacls-based fixes failed on some Windows configurations. Now uses PowerShell to build a completely fresh ACL from scratch with only SYSTEM and Administrators read access. Falls back to icacls with well-known SIDs if PowerShell is unavailable.
- Fix SSH key parsing on FreeBSD — The installer's JSON parser now correctly finds versioned Python (e.g.
python3.11) whenpython3symlink doesn't exist.
Fixed Windows SSH key permission issues causing OpenSSH to reject keys due to leftover ACEs.
Full changelog
Bug Fixes
- Fix Windows SSH key permissions (take 3) — The previous icacls-based approach failed to fully clean up inherited ACEs on some Windows configurations, causing OpenSSH to still reject the key with "Permission denied." Now uses PowerShell to build a completely fresh ACL from scratch with only SYSTEM and Administrators read access — no leftover ACEs are possible. Falls back to icacls with SIDs if PowerShell is unavailable.
- Added diagnostic logging — After setting key permissions, the agent now verifies the key is readable and logs the actual ACL if it's not, making future debugging much easier.
Fixed Windows SSH key permission handling for non‑English systems and auto‑remediated misconfigured keys on agent start.
Full changelog
Bug Fixes
- Fix Windows SSH key permissions on non-English systems — The agent's SSH key lockdown used English group names (
Users,BUILTIN\Users, etc.) in icacls commands, which fail on non-English Windows installations (e.g. Spanish). Now uses well-known SIDs for locale independence. Also removes leftover CREATOR OWNER ACE that caused OpenSSH to reject the key as "too open." - Re-apply key permissions on every agent startup — Keys created by older agent versions with incorrect permissions are now automatically fixed on startup, without requiring manual intervention.
- Progressive Web App (PWA) support prompting mobile users to add BBS to home screen
- Mobile‑only Settings tab linking directly to Storage Locations
- Option in Settings to opt out of anonymous usage statistics
Full changelog
Bug Fixes
- Fix Windows SSH host key mismatch after server_host update — After the v2.17.0 fix that stripped the web port from
server_host, stale entries in the Windowsknown_hostsfile caused SSH connections to fail with "REMOTE HOST IDENTIFICATION HAS CHANGED." All SSH connections (borg, catalog pipe, SSH test) now useUserKnownHostsFile=/dev/null(NUL on Windows) to prevent stale host key issues after Docker rebuilds or server configuration changes. - Fix dashboard slow loading — ClickHouse and server health stats are now cached with 60s TTL and refreshed in the background, keeping the dashboard snappy.
- Fix queue detail cancel button — Cancel button no longer loses its form reference during AJAX status polling.
- Optimize queue/dashboard queries — Truncate large
error_logTEXT columns in list views to prevent MySQL temp table bloat.
Improvements
- Add to Home Screen (PWA) — Mobile users are prompted to add BBS to their home screen for app-like access.
- Storage Locations link on mobile — Settings page now includes a mobile-only tab linking to Storage Locations.
- Added option to opt out of anonymous usage statistics in Settings.
- Added PWA manifest and mobile 'Add to Home Screen' banner
- Added Storage Locations link on settings page for mobile users
- Stripped web port from SSH commands in docker setups
Full changelog
- Fix cancel button on queue detail page losing form reference during AJAX poll
- Improve dashboard performance: split fast/slow AJAX endpoints, ClickHouse stats cached 60s
- Add PWA manifest and mobile "Add to Home Screen" banner
- Add Storage Locations link on settings page for mobile users
- Stripped web port from host in SSH commands (was appending it causing an error in docker setups)
- Removed redundant storage section from Settings
- New UI toggle in Settings > General to manually enable/disable maintenance mode
- Top bar badge indicates Maintenance Mode status with link to Settings
- Auto‑enable maintenance mode after CLI (`bbs-restore`) and S3 web restores to pause new backup jobs
Full changelog
Maintenance Mode
- New UI toggle in Settings > General to manually enable/disable maintenance mode
- Top bar badge shows "Maintenance Mode: On" with link to Settings when active
- Auto-enabled after restore — both CLI (
bbs-restore) and S3 web restore automatically enable maintenance mode to prevent the scheduler from queuing new backup jobs before repositories are restored - Server-side jobs (catalog rebuild, prune, compact, etc.) still run during maintenance mode — only new backup/restore jobs are paused
Restore Improvements
bbs-restorenow clears stale job queue and enables maintenance mode after database import- S3 web restore enables maintenance mode after successful restore
- Generate random MySQL password for new Docker installs
Performance & Stability
- Streaming catalog rebuild for remote SSH repos —
borg listoutput is now streamed line-by-line instead of buffered into memory, fixing out-of-memory crashes on large repositories (tested with 850K+ files per archive) - Streaming ClickHouse inserts — TSV data is streamed to ClickHouse via curl instead of loading entire files into memory
- Slimmed down
bbs-update-runfor faster server updates - Fix Windows agent going offline after self-update (agent v2.14.1)
UI Cleanup
- Removed redundant storage section from Settings (now fully managed in Storage page)
- If the Windows agent went offline after updating, manually restart the service with `sc start BorgBackupAgent` or reboot the machine.
- Catalog rebuild now syncs archives from borg before rebuilding
- 'Include beta versions' checkbox added to update checker
Full changelog
What's New
NFS & Multiple Storage Location Fixes (#25)
- Fix borg init on NFS storage: Repository initialization now runs through
bbs-ssh-helperas root, resolving "Permission denied" errors when creating repos on NFS-mounted storage locations (Synology NAS, TrueNAS, generic Linux NFS) - Fix repo operations on non-default storage paths:
borg-list,borg-list-archive,borg-cmd(prune, compact, check, etc.) now support repositories on any registered storage location, not just/var/bbs/ - Fix chown failures on NFS: Ownership operations are now non-fatal — NFS with user mapping doesn't support chown, and it's not needed when all users map to the same NFS admin user
- Fix storage-paths not written on init failure:
.storage-pathsfile is now updated before borg init so SSH access works even if initialization needs to be retried - Fix NFS owner detection:
bbs-ssh-helpernow handles "UNKNOWN" UIDs from NFS stat by looking up the correct user from/etc/passwd - Extracted
is_allowed_path()helper inbbs-ssh-helperfor consistent path validation across all commands
Windows Agent Fix
- Fix Windows agent going offline after self-update: The agent update mechanism used
sc stop/sc startto restart the Windows service, which raced with the launcher's own restart loop and could leave the service permanently stopped. The agent now exits cleanly and lets the launcher handle the restart automatically.
⚠️ Windows Agent Users: If your Windows agent went offline after updating, the service has stopped and cannot pull the fix on its own. You need to restart the service manually by running
sc start BorgBackupAgentin an elevated command prompt, or restart the Windows machine. Once the service is running again, it will automatically update to the fixed agent version.
Catalog Rebuild Improvement
- Sync archives from borg before catalog rebuild: Catalog rebuild now runs
borg listfirst to discover any archives in borg that aren't yet in the database, ensuring rebuilds always work with fresh data
Documentation
- Updated Storage Setup wiki with comprehensive NFS setup instructions for Synology NAS, TrueNAS, and generic Linux — including Docker NFS volume configuration and troubleshooting
- Updated Docker Installation wiki with NFS/multi-storage setup, ARM/ClickHouse notes, and
.envpersistence details
Previous Beta Changes (since v2.14.0)
- Fix storage-paths not updated when default location differs from SSH home
- Add manual path restore/download when ClickHouse is unavailable (#30)
- Add 'Include beta versions' checkbox to update checker
- Fix Windows agent not restarting after sleep/wake
- Configure multiple storage locations via the new **Storage** page, consolidating Remote SSH and S3 Sync settings
- Import existing Borg repositories without re‑running full backups through the Repositories tab
- Windows Agent now handles sleep/wake events and fixes a stop‑request race condition (bumped to version 2.13.4)
Full changelog
What's New
Storage Locations
Configure multiple storage paths so different repositories can live on separate disks or mount points. Manage all storage from the new Storage page, which also consolidates Remote Storage (SSH) and S3 Sync configuration.
Import Existing Repositories
Bring previously created borg repositories under management without re-running a full backup. Available from the Repositories tab on any client.
Restore Without ClickHouse (#30)
When ClickHouse is unavailable (e.g., ARMv8.0 hardware), the Restore tab now shows a manual path entry UI instead of failing silently. Users can type file/directory paths or select "Restore entire archive" — no catalog browsing required.
Update Checker Improvements
- The update checker now shows only stable releases by default, with an Include beta versions checkbox for those who want to opt in
- Pre-releases and drafts are filtered out of the upgrade prompt
Windows Agent: Sleep/Wake Fix
- Fixed a race condition in the Windows service launcher where a stop request during the restart delay was ignored, causing the agent to restart then immediately get killed
- Added power event handling so the service detects wake-from-sleep and restarts the agent subprocess automatically
- Agent version bumped to 2.13.4
Bug Fixes
- SSH storage path stability — Added
ssh_home_dirto the agents table so changing the default storage location no longer breaks existing SSH access to repos - SSH repo path port leak — Fixed non-default storage locations leaking the SSH port into the repository path
- Download permission errors — Fixed "permission denied" on extracted files during server-side downloads by running a separate sudo permission fix after extract
- Non-default storage locations — Fixed server-side borg extract, prune, compact, and repo size calculations using the wrong path for repos on non-default storage locations
- Repository verification — Switched from
borg infotoborg list --jsonfor more reliable repo verification during import - S3 endpoint validation — Validate S3 endpoint URLs before saving and testing
- S3/rclone timeouts — Added timeouts to S3 rclone commands to prevent Apache worker hangs
- UI fixes — Fixed dropdown menus clipped on repo and schedule cards; fixed catalog log files not cleaned up after import
- Download handler — Cleared output buffer before sending download headers to prevent corrupt archives
Fixed catalog rebuild deleting other repositories' data and treating their archives as orphaned on clients with multiple repos.
Full changelog
Bug Fixes
- Fix catalog rebuild deleting other repos' data on same client — full rebuild was dropping the entire agent partition in ClickHouse, wiping catalog data for all repos on that client. Incremental rebuild also treated other repos' archives as orphaned. Both now scope operations to the specific repository's archive IDs, fixing the infinite rebuild loop when a client has multiple repositories (#27)
- Allow queuing different maintenance actions on a repository
- Maintenance buttons remain enabled during active jobs
Full changelog
Bug Fixes
- Fix server-side jobs failing when agent is offline — catalog rebuild, prune, compact, and other server-side tasks no longer fail just because the agent is offline (#21)
- Fix queue detail showing "Waiting for progress data from agent" for server-side tasks — now correctly shows "Running on server..."
- Fix JS isServerSide list — was missing most server-side task types, causing incorrect UI behavior for catalog rebuild, repo check, repair, etc.
Improvements
- Allow queuing different maintenance actions — previously any active job on a repo blocked all other maintenance buttons; now you can queue a compact while a check is running, etc.
- Maintenance buttons no longer disabled during active jobs — the controller prevents duplicate actions of the same type server-side
Fixed a stall detection loop that prevented catalog rebuilds from completing.
Full changelog
Bug Fixes
- Fix catalog rebuild abandon loop — stall detection no longer asks the agent about server-side tasks (catalog rebuild, prune, compact, etc.), preventing a fail/retry loop that made catalog rebuilds impossible to complete (#21)
- Fix zombie job cleanup for server-side tasks — the 24h timeout safety net now also excludes server-side task types
- Track progress for server-side catalog jobs — catalog sync and catalog rebuild now update
last_progress_atduring processing for accurate progress tracking
Fixed Windows SSH key permission handling to prevent "UNPROTECTED PRIVATE KEY FILE" errors.
Full changelog
Bug Fixes
- Fix Windows SSH key permissions — the agent now properly strips all ACLs (including explicit BUILTIN\Users entries) before setting read-only access for SYSTEM and Administrators, fixing "UNPROTECTED PRIVATE KEY FILE" errors on Windows (#23)
- Duplicate backup plan option creates a paused copy via the plan's 3‑dot menu
- Repository passphrase can be revealed and copied to clipboard on the repository detail page
Full changelog
New Features
- Duplicate backup plan — new "Duplicate" option in the plan's 3-dot menu; the copy is created paused to prevent accidental duplicate schedules
- Repository passphrase reveal — click-to-reveal and copy-to-clipboard for the repo passphrase on the repository detail page
Bug Fixes
- Detect pipx/pip borg installs — the agent now checks
~/.local/bin/borgso borg installed via pipx or pip is detected even when running as a systemd service (#15)
Fixed UID collisions on fresh installs and prevented backups from failing due to catalog streaming errors.
Full changelog
Bug Fixes
-
Fix UID collision with system groups on fresh install — New SSH users were allocated UIDs in the system range (100–999) via
useradd --system, causing the first user on a fresh container to collide with thecrontabgroup (UID 997). Users now get UIDs ≥ 1000. Existing installs with bad UIDs will self-heal on next container restart. (#13) -
Don't fail backups when only catalog streaming fails — Catalog streaming is a non-critical UI feature; a SSH pipe failure no longer marks the entire backup as failed. The job stays completed with a warning note that includes the actual SSH error for easier diagnosis. (#12)
- Pull the latest image (marcpope/borgbackupserver:latest) and restart the compose stack to apply the ownership fix.
Full changelog
Fix: Per-user cache directory ownership reset on container restart
Fixes server-side prune failing with Permission denied after container restart.
What happened
The entrypoint ran chown -R www-data:www-data /var/bbs/cache, which reset per-user borg cache directories (e.g., /var/bbs/cache/bbs-batam) to www-data ownership. Since prune runs as the SSH user, it lost access to its own cache directory.
Fix
/var/bbs/cacheis now only chown'd at the top level (not recursive), matching the fix already applied to/var/bbs/home- Per-user cache directory ownership is restored during the SSH user recreation loop on startup
Upgrade
docker pull marcpope/borgbackupserver:latest
docker compose up -d
- Pull the latest image and restart the compose stack: `docker pull marcpope/borgbackupserver:latest && docker compose up -d`
Full changelog
Hotfix: Container crash on startup after upgrading from pre-2.12.0
Fixes a startup crash introduced in v2.12.0 where the container would restart in a loop with:
chown: invalid group: 'bbs-XXXXX:bbs-XXXXX'
What happened
Older versions ran chown -R www-data:www-data /var/bbs/home, changing all home directory ownership to www-data (UID 33). When v2.12.0's new user recreation logic ran, it detected UID 33 from the directory and tried to create SSH users with that UID — which silently failed because UID 33 already belongs to www-data. The subsequent chown with the non-existent username crashed the entrypoint.
Fix
- Detects when a directory's UID already belongs to another system user and allocates a fresh UID instead
- Verifies user creation succeeded before attempting ownership changes
- Uses numeric IDs for
.sshownership to avoid group name lookup failures
Upgrade
docker pull marcpope/borgbackupserver:latest
docker compose up -d
- Included `bbs-ssh-gate` binary in the Docker image
Full changelog
SSH Authentication Fix for Docker Deployments
This release fixes a critical bug where all SSH agents stopped authenticating after a Docker container restart (#11).
What was broken
Four issues in the container entrypoint combined to break SSH authentication on every restart:
- User recreation failed silently — the entrypoint searched for directories matching
bbs-*but home directories are named by agent ID (e.g.,1,2). SSH users were never recreated in the container's/etc/passwd, so sshd rejected all connections. - File ownership was clobbered — a recursive
chownreset all.ssh/authorized_keysfiles towww-dataownership before users were recreated. OpenSSH requires these files to be owned by the connecting user. - sshd started too early — the SSH server launched before users existed in the system, guaranteeing auth failures during the startup window.
- Legacy SSH config was lost — the
PubkeyAcceptedAlgorithms +ssh-rsasetting (required by OpenSSH 10 in the container) was only written during updates, not during container startup.
What's fixed
- SSH users are now recreated from the database with correct UID mapping
- File ownership is set correctly per-user (home dir
user:www-data,.ssh/diruser:user) - sshd now starts after all users and SSH config are in place
- Legacy SSH compatibility config is written on every container start
bbs-ssh-gateis now included in the Docker image (was previously missing)
Upgrade
Pull the latest image and restart your container. No agent reconnection needed — existing agents will authenticate automatically once the container starts with the fix.
docker pull marcpope/borgbackupserver:latest
docker compose up -d
- Server‑Agent stall recovery automatically marks stuck jobs as failed with clear explanation
- Resilient status reporting with exponential backoff retries for completion reports
- Idempotent status endpoint safely handles duplicate reports
Full changelog
Welcome to Update v2.11.0
[!NOTE]
Still looking for feedback testing Windows Clients: See update v2.10.0
Stall Detection & Self-Healing Job Queue
Backups should never silently disappear. In v2.11.0, BBS gains intelligent stall detection that ensures every job reaches a definitive outcome — even when things go wrong between the agent and server.
What's New
Server-Agent Stall Recovery — If a job completion report is lost due to a transient server error (database restart, network blip, etc.), BBS now detects and recovers automatically. The server monitors job progress and asks the agent directly: "Are you still working on this?" If the agent confirms it's moved on, the job is marked as failed with a clear explanation — no more zombie jobs sitting in "running" forever.
Resilient Status Reporting — The agent now retries completion reports with exponential backoff. A momentary server hiccup no longer means a lost backup result. Five retry attempts over ~2.5 minutes ensure the report gets through.
Idempotent Status Endpoint — Status reports are now safe to retry. Duplicate reports (from retries where the response was lost) are detected and handled gracefully — no duplicate archives, notifications, or prune jobs.
24-Hour Safety Net — As a last resort, the scheduler automatically fails any job that's been running for over 24 hours with no progress. This catches edge cases even on agents that haven't updated yet.
Multi-Client Access Controls
Filtered Notifications — The notification bell and notification list now respect user-level client access. Users only see alerts for the clients they manage.
Filtered Logs — The server log page is filtered by accessible clients, keeping multi-tenant views clean and scoped.
Agent v2.11.1
- Retry logic for all status reports (exponential backoff, 5 attempts)
- Responds to server stall checks via
check_jobspolling mechanism - Reports
abandonedfor jobs no longer in progress
*To update: go to Settings > Updates in the Borg Backup Server Software.
- Native Windows PowerShell installer for BBS agent (zero‑dependency, automatically installs borg-windows)
- Cross‑platform BBS agent (`bbs-agent.py`) runs natively on Windows with full backup, restore, and self‑update support
- Windows Service integration (`BorgBackupAgent`) with auto‑start and failure recovery
Full changelog
Windows Support (Pre-Release)
[!CAUTION]
Windows support is currently pre-release software. We've built a special, native version of BorgBackup for Windows
and have been testing it. Please report any issues.
Native Windows you ask?
Borg Backup Server (BBS for Short) can now back up Windows machines! This release adds preliminary Windows agent support with a zero-dependency PowerShell installer. No Python, WSL, or Cygwin needed.
What's included:
- One-line PowerShell installer — automatically downloads and installs Borg for Windows, the BBS agent, and configures a Windows Service
- Tabbed Install UI — the client Install tab now has Linux/macOS and Windows sub-tabs with platform-specific install commands
- Cross-platform agent — the BBS agent (
bbs-agent.py) now runs natively on Windows with full backup, restore, and self-update support - Windows Service — the agent runs as a proper Windows Service (
BorgBackupAgent) with auto-start and failure recovery - Catalog & file browsing — Windows backup archives are fully browsable in the Restore tab with correct directory trees
- File download — download individual files or folders from Windows backups directly from the dashboard
Additional changes:
- Portable archive paths — Windows backups use forward slashes and drive letter prefixes (
C/Users/...) for cross-platform compatibility - In-place restore support with
--strip-componentsto correctly restore files back to their original drive locations - Multi-drive restore warning when selected files span multiple drives without a custom destination
Known limitations:
- Windows support is pre-release — recommended for non-production workloads
- Requires borg-windows (installed automatically by the agent installer)
We'd love your feedback on how Windows support works for you! Please report any issues or share your experience at https://github.com/marcpope/borgbackupserver/issues
Fix .env persistence across container recreation preventing decryption of encrypted data.
Full changelog
Bug Fixes
-
Fix .env not persisted across Docker container recreation (#10) — The
.envfile (containing theAPP_KEYused to encrypt SSH keys and S3 credentials) was stored inside the container's ephemeral filesystem. Afterdocker compose down/up, a newAPP_KEYwas generated, making all encrypted data undecryptable. The.envis now stored on the/var/bbspersistent volume with a symlink back to the expected path. Existing containers are migrated automatically on next start. -
Fix rclone test connection failures in Docker — The S3 test connection was missing essential environment variables (
HOME,PATH,RCLONE_CONFIG), causing rclone to fail withgetent not foundand.rclone.conf not founderrors.
- Removed one-time migration restore script; use bbs-restore for similar functionality
- Install rclone from official binary on bare metal installs to fix Go runtime CVEs in Debian package
- Rsync.net Remote Storage Wizard with SSH key instructions, borg version selector (borg12/borg14), and connection testing
- Configurable Server Backups section to enable/disable daily backups, set retention count, and optionally include ClickHouse catalog data
- Comprehensive Backup & Restore now includes SSH host keys, sudoers config, and cron jobs with backward‑compatible restore
Full changelog
New Features
- rsync.net Remote Storage Wizard — Setup wizard for rsync.net with SSH key instructions, borg version selector (borg12/borg14), and connection testing
- Configurable Server Backups — New "Server Backups" section in Settings > General to enable/disable daily backups, set retention count, and optionally include ClickHouse catalog data
- Comprehensive Backup & Restore — Server backups now include SSH host keys, sudoers config, and cron jobs in addition to database and .env. Restore script handles all new contents with backward compatibility
Improvements
- Install rclone from official binary on bare metal installs (fixes Go runtime CVEs in Debian package)
- Backup script accepts
--keep Nand--with-catalogsflags - Settings General tab layout reorganized (Agent moved under Server)
Housekeeping
- Removed one-time migration restore script (bbs-restore covers this use case)
- CVE‑2026‑24049 — Upgraded Python wheel package to v0.46.3 (CVSS 7.1)
- CVE-2026-24049
- Added HTTP security headers: X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- Session cookies now include the Secure flag automatically when accessed over HTTPS
Full changelog
Security Hardening
- Added HTTP security headers — X-Frame-Options, X-Content-Type-Options, and Referrer-Policy headers are now set on all responses to protect against clickjacking and MIME-sniffing attacks
- Secure session cookies on HTTPS — Session cookies now include the
Secureflag automatically when the server is accessed over HTTPS (HTTP/LAN deployments are unaffected) - Fixed wheel CVE-2026-24049 (CVSS 7.1) — Upgraded Python
wheelpackage to v0.46.3 in Docker image
- CVE-2026-24049 (CVSS 7.1) — pinned wheel>=0.46.2 to mitigate vulnerability
- Replaced Parsedown with league/commonmark for GitHub Flavored Markdown support
Full changelog
Bug Fixes
- Fixed "Rebuild Full Catalog" — resolved a database error when triggering a full catalog rebuild (
catalog_rebuild_fullwas missing from the task_type ENUM)
Improvements
- Replaced Parsedown with league/commonmark — fixes PHP 8.4 deprecation warnings in release notes display; uses the actively-maintained CommonMark library with GitHub Flavored Markdown support
Security & Dependency Updates
- Pinned
wheel>=0.46.2to address CVE-2026-24049 (CVSS 7.1)
- rclone upgraded to v1.73.1 — addresses multiple CVEs from previous version
Full changelog
Security & Dependency Updates
- Upgraded rclone to v1.73.1 (from Debian-packaged v1.65), installed from official binary to address multiple CVEs noted in docker hub
- Bind mount documentation added to
docker-compose.ymlfor users storing data on external disks
- Upgraded Docker base image to PHP 8.4 (Debian Trixie) and added `apt-get upgrade` in the build process to apply latest system security patches.
- Updated Apache to version 2.4.66 and OpenSSL to the latest Debian Trixie release.
- ClickHouse updated to the latest stable release with a patched Go runtime.
Full changelog
Security Updates
- Upgraded Docker base image from PHP 8.1 to PHP 8.4 (Debian Trixie)
- Added
apt-get upgradeto Docker build to pull latest security patches for all system packages - Updated Apache to 2.4.66 and OpenSSL to latest Trixie release
- ClickHouse updated to latest stable release with patched Go runtime
- Full Disk Access integration for macOS agent via .app bundle
- Dynamic launchd plist generation detecting python3 paths on Apple Silicon and Intel
- Modern launchctl usage with bootstrap/bootout commands
Full changelog
Improved macOS Agent Support
- Full Disk Access integration — compiled wrapper binary packaged as a
.appbundle so macOS properly displays it in System Settings → Privacy & Security → Full Disk Access - Dynamic launchd plist generation — installer detects the correct python3 path (supports both Apple Silicon
/opt/homebrewand Intel/usr/local) - Fixed duplicate log lines — agent no longer double-logs when running under launchd or systemd
- Fixed hostname detection — macOS agents now report the correct hostname instead of IPv6 reverse DNS
- Modern launchctl commands — uses
bootstrap/bootoutinstead of deprecatedload/unload - Step-by-step FDA instructions — installer output guides users through granting Full Disk Access
Docker Improvements
Docker support is still in beta, changes may break the installation, it's getting close to stable.
- Docker Updates — We've added many improvements to the Docker Setup. Seeking feedback.
- ClickHouse data persistence — ClickHouse data now stored on the persistent Docker volume at
/var/bbs/clickhouse/, surviving container recreation. Previously restarting or upgrading could lose catalog data (not backup data) if not stored on a persistent volume. >2.6 and higher moved this to /var/bbs volume which is mapped to a persistent volume.
- Run `docker compose pull` followed by `docker compose up -d` when upgrading from previous versions to apply the new persistent storage layout.
- Existing ClickHouse data at /var/lib/clickhouse/ inside containers will no longer be used; migrate any critical files to the new volume path (/var/bbs/clickhouse/) before upgrade.
- ClickHouse catalog data stored at /var/bbs/clickhouse/ on the Docker volume for persistence
- Docker-specific upgrade instructions shown in Settings > Updates page (docker compose pull/up)
- Application code and dependencies baked into the Docker image, eliminating runtime Git clone
Full changelog
Docker Improvements
-
ClickHouse catalog data now persists on the Docker volume — Previously, ClickHouse stored data at
/var/lib/clickhouse/inside the container, meaning the entire file catalog was lost on container recreation. Data is now stored at/var/bbs/clickhouse/on the persistent volume, matching the existing pattern for MariaDB at/var/bbs/mysql/. -
Docker-specific upgrade instructions — The Settings > Updates page now detects Docker environments and shows
docker compose pull/docker compose up -dinstructions instead of the bare-metal upgrade button. -
Application code baked into Docker image — Code and dependencies are now included in the image at build time. Containers no longer clone from GitHub at runtime, eliminating silent upgrades on restart.
-
ClickHouse idle disk I/O fix — Disabled ClickHouse system log tables that were causing ~13 GB of disk writes and constant CPU usage on idle installs.
- Old ClickHouse system log tables are automatically dropped on upgrade, reclaiming disk space
- Application code baked into Docker image at build time; runtime no longer clones from GitHub
- Container restarts stop silent software upgrades; users must pull new image to upgrade
- Settings page displays Docker‑specific upgrade instructions when running in Docker
Full changelog
What's New in 2.7.0
Docker Improvements
- Application code is now baked into the Docker image at build time — no more cloning from GitHub at runtime
- Container restarts no longer silently upgrade the software
- Users upgrade by pulling a new image:
docker compose pull && docker compose up -d - Settings page shows Docker-specific upgrade instructions when running in Docker
- Developer Sync hidden in Docker (no git repo available)
ClickHouse Performance Fix
- Disabled ClickHouse system log tables to eliminate idle disk I/O and CPU usage
- Old system log tables automatically dropped on upgrade to reclaim disk space