Security (P0):
- #169 mask FTP password in AjaxController maskSecrets()/mergeExistingSecrets()
- #187 stop leaking absolute_path in API item view (JsonapiView)
- #182 SftpUploader StrictHostKeyChecking=no -> accept-new (MITM)
Data integrity / correctness (P1):
- #179 DatabaseDumper: real newline after DROP TABLE (was literal backslash-n)
- #180 profile-picker forms ORDER BY id (the 'ordering' column is dropped on
upgraded sites, breaking the query) — run_profile.xml + plg_content xml
- #181 uninstall.mysql.sql: add missing DROP for snapshots table
- #184 CLI snapshot delete uses data_file column (file_path does not exist,
so snapshot files were never deleted)
- #186 remove duplicate pre-update backup: content plugin no longer subscribes
to onExtensionBeforeUpdate (owned by the system plugin); keeps pre-install
- #188 DatabaseImporter now collects non-fatal statement errors
(getErrors()/hasErrors()) so restores are no longer silent and callers can
surface a warning status
Also folds in the .mokogitea/CLAUDE.md mokoplatform -> mokocli fix (stale repo
name after the rename).
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
02.52.25.sql used `DROP COLUMN IF EXISTS` (MariaDB-only), which errors on
Oracle MySQL 8.x — so on MySQL 8 installs the 26 legacy remote_storage/ftp_*/
sftp_*/gdrive_*/s3_* columns were never dropped, yet Joomla recorded the schema
as applied (confirmed on suite.dev: MySQL 8.0.41, all 26 columns still present,
0 profiles with legacy remote data).
New 02.56.01 migration removes them portably: an INFORMATION_SCHEMA-gated
prepared-statement ALTER that drops all 26 columns where present (plain
DROP COLUMN, valid on MySQL 8 + MariaDB) and is a no-op where already gone
(so it's safe on already-migrated installs too). Validated non-destructively
against suite.dev (gate=1, ALTER PREPAREs cleanly).
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Two related backup-management fixes.
Retention (records/days to keep):
- The retention fieldset was defined in profile.xml but never rendered
in the profile editor, so retention_days/retention_count were invisible.
Render the retention fieldset on the Archive tab.
- retention_days/retention_count were read by nothing, so they pruned no
backups. Add RetentionManager::prune(), called from completeRecord() in
both BackupEngine and SteppedBackupEngine after a backup finishes.
Policy: delete a completed/warning backup when EITHER it is older than
retention_days OR it falls outside the newest retention_count copies
(0 = unlimited for that rule). Deleting a record also removes its
archive and log file.
- Correct misleading language/schema text ("use global default" — no such
global backup-retention setting exists) to "0 = unlimited".
Purge Old Backups button:
- The modal only opened via a fragile selector match on the toolbar
button's inline onclick, which Joomla 6's Atum toolbar does not render,
so the button did nothing. Wrap Joomla.submitbutton to open the modal
for the backups.purgeModal task, keeping the selector as a fallback.
Follow-up to the legacy remote-storage removal — three consumers still
referenced columns this branch drops:
- 02.52.25.sql: use plain DROP COLUMN instead of DROP COLUMN IF EXISTS.
IF EXISTS on DROP COLUMN is a MariaDB-only extension and errors on
Oracle MySQL 8.x (which Joomla also supports); the columns always exist
here, so the guard is unnecessary and the migration is now portable.
- AkeebaImporter::mapToMokoProfile(): stop inserting the 19 dropped
remote_storage/ftp_*/gdrive_*/s3_* columns (would fatal with "Unknown
column" on Akeeba import). Remote settings now live in the remotes
table and are re-added on the profile Remote tab after import.
- AjaxController::browseSftpDir() + SftpPathField: remove. These were the
legacy single-SFTP path picker, orphaned when the SftpPath form field
was removed; they read now-dropped sftp_* columns.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Drops the per-profile remote_storage column and all legacy FTP/SFTP/S3/
Google Drive credential columns. Remote destinations are now sourced
exclusively from #__mokosuitebackup_remotes (multi-remote), which is
created at install time — so the backward-compat fallback branches in
BackupEngine, SteppedBackupEngine and loadRemoteDestinations are removed.
- sql: drop 26 legacy columns (install.mysql.sql + 02.52.25.sql migration)
- forms/profile.xml: remove legacy remote fields and ftp/gdrive/s3 fieldsets
- tmpl/profile/edit.php: drop legacy UI, add save-first prompt, use
getOrCreateInstance for the modal, read item.params (was item.config)
- PreflightCheck: validate credentials from the remotes table; curl
warning now applies to ntfy only
- SteppedSession: drop remoteStorage property
- language: add backup-record delete-count strings
- script.php: simplify postflight license-key prompt
Previously a successful backup with a failed remote upload was marked
as "complete", hiding the upload failure. Now these records get a
"warning" status with a yellow badge so operators can see at a glance
which backups didn't reach their remote destination.
Warning-status records are treated as usable backups throughout:
- Downloadable, browsable, and restorable (the archive is intact)
- Counted in dashboard stats, storage totals, and success streaks
- Included in purge operations and differential base lookups
- Shown with yellow "warning" badge in list, detail, and cpanel module
- Filterable via the status dropdown on Backup Records
Claude-Session: https://claude.ai/code/session_01MbEjBtsSjPuTWhqqrMS2wG
The ordering column was unused in the profiles UI — profiles sort by ID.
Drops the column via migration, removes all references from model,
config query, importer, and install SQL.
Claude-Session: https://claude.ai/code/session_01MbEjBtsSjPuTWhqqrMS2wG
- install.mysql.sql: rename `config` → `params` and drop `keep_local` from remotes
table to match update file 01.41.00 and RemoteTable.php code (fixes Joomla
database maintenance "one problem")
- install.mysql.sql: fix idx_enabled index to use composite (profile_id, enabled)
- install.mysql.sql: add restore_script_name column to profiles table
- 01.43.22.sql: ALTER TABLE to add restore_script_name for existing installs
- DashboardModel: order profile dropdown by ID instead of ordering column
- SteppedBackupEngine: add stack trace logging around MokoRestore standalone
generation to debug str_replace FATAL on SFTP profiles
New #__mokosuitebackup_remotes table stores remote destinations with
JSON params per type (SFTP/S3/GDrive/FTP). Each profile can have
multiple enabled destinations — the engine uploads to all of them.
Database:
- New table with profile_id FK, type, enabled, params JSON, ordering
- Migration auto-converts existing profile remote columns to new table
- RemoteTable, RemoteModel, RemotesModel classes
Engine:
- BackupEngine: loadRemoteDestinations() + createUploaderFromParams()
iterates all enabled remotes, falls back to legacy columns
- SteppedBackupEngine: one upload step per remote destination, persisted
via session.remoteDestinations + remoteIndex
- Local copy only deleted when ALL uploads succeed
UI:
- Profile edit: "Remote Destinations" linked table with AJAX CRUD
- Add/edit modal with type selector showing dynamic fields
- Toggle enabled/disabled, delete with confirmation
- Legacy fields hidden when remotes configured, shown as fallback
- Secrets masked in responses, merged from DB on save
Closes#97
New "Data Sanitization" fieldset on profile form with four options:
- Sanitize User Passwords: replaces all bcrypt hashes with invalid sentinel
- Preserve Super Admin: keeps Super Users group passwords intact
- Sanitize User Emails: replaces with user123@sanitized.example.com
- Clear Session Data: excludes #__session table data (default: on)
DatabaseDumper sanitizes rows inline during dump — both in-memory
and file-streaming paths. Super admin detection uses group_id=8
from #__user_usergroup_map with static caching.
Use cases: sharing backups, creating demo/staging sites, GDPR compliance.
Partial #129 (Part 2 — restore script password reset — tracked separately)
All placeholders changed from lowercase to UPPERCASE:
[host] → [HOST], [site_name] → [SITE_NAME], [date] → [DATE],
[datetime] → [DATETIME], [profile_id] → [PROFILE_ID], etc.
[HOME] and [DEFAULT_DIR] were already uppercase — now consistent.
SQL migration 01.39.01 updates existing profile data in the database.
Resolution display prefixed with "EXAMPLE:" to clarify these are
example values resolved at backup time.
13 files updated across engines, fields, forms, templates, and SQL.