=== Sitecarry ===
Contributors: dotance
Tags: backup, migration, clone, database backup, restore
Requires at least: 5.9
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.15.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Package your whole WordPress site — every file plus the database — into one archive you can download, store, or move to another server.

== Description ==

Sitecarry builds a single package containing your site's files and a full database
export, then lets you download it from the WordPress admin.

The build runs in short, resumable slices, so it works on shared hosting where a
single PHP request is capped at 30 seconds. If a request times out, the next one
picks up exactly where the last one stopped.

Each package comes with a standalone installer that restores it onto any server —
the same file works for moving a site to new hosting, cloning live to staging, or
rolling a site back.

**What it does**

* Full site package: files plus a complete SQL export
* Scheduled backups — hourly, twice daily, daily or weekly, with retention
* Optional incremental file backups, with automatic full backups in between
* Upload to Amazon S3, any S3-compatible storage, FTP or Google Drive
* Restore straight from storage when the archives are no longer on the server
* Email or Slack/Discord notification when a backup fails
* Verify that a package is intact, from the admin or WP-CLI
* WP-CLI commands for backing up, listing, verifying and checking status
* Backups run in the background — start one and close the tab
* Resumable build and resumable restore — both survive short PHP execution limits
* Live progress throughout
* Standalone `installer.php`: no WordPress needed on the target server
* Serialization-safe URL rewriting, so page builder content survives the move
* Skips caches, `node_modules`, `.git`, and other backup plugins' folders, which is
  usually the difference between a 400MB package and a 40GB one
* Records a manifest describing the origin site (URLs, table prefix, versions)

**Why URL rewriting is the hard part**

WordPress stores a lot of settings as PHP serialized data, where every string
carries its own byte length. Replacing an old domain with a longer one using a
plain search and replace breaks those lengths, and WordPress can then no longer
read the value — which is how a migration quietly wipes widget settings and page
builder layouts. Sitecarry rewrites the serialized form directly and repairs the
lengths, and it also handles the JSON-escaped form (`https:\/\/`) that page
builders such as Elementor store their content in.

**Security**

Packages contain a complete copy of your database. Sitecarry stores them in a
protected folder with an unguessable filename, blocks direct web access via
`.htaccess` and `web.config`, and serves downloads only through an authenticated
admin request — never a public URL.

Every package has its own restore passphrase, shown in the admin and kept out of
the archive. The installer refuses to do anything without it, so an installer
left behind on a server cannot be used by anyone else to overwrite the site. It
also deletes itself and the archive when the restore finishes.

== External services ==

Out of the box Sitecarry talks to nothing but your own server. It contacts an
outside service only when you switch one on, and only with what that service needs
to do its job. It never sends usage data, analytics or licence checks anywhere.

**Amazon S3, or any S3-compatible storage** — used only when you set Storage to S3.
Sitecarry sends your backup archives, and the requests needed to list and delete
them, to the endpoint you enter (by default `https://s3.<region>.amazonaws.com`, or
your own for Backblaze B2, Wasabi, DigitalOcean Spaces, MinIO and so on). What is
sent: the archive itself — which contains your site's files and a full database
export — plus the access key you configured, used to sign each request. Nothing is
sent until you save S3 credentials and run a backup.
Amazon S3 terms: https://aws.amazon.com/service-terms/ — privacy policy:
https://aws.amazon.com/privacy/. For any other provider, the terms are that
provider's own.

**FTP / FTPS server** — used only when you set Storage to FTP. Sitecarry connects to
the host, port and path you enter and uploads the same archives, authenticating with
the username and password you configured. The server is one you choose, so its terms
are your host's. Plain FTP sends that password and the whole archive unencrypted;
the settings screen recommends FTP over TLS for this reason.

**Google Drive** — used only when you set Storage to Google Drive and complete the
connection. Sitecarry sends you to `https://accounts.google.com` to authorise,
exchanges the code at `https://oauth2.googleapis.com/token`, and uploads, lists and
deletes archives through `https://www.googleapis.com/drive/v3` and
`https://www.googleapis.com/upload/drive/v3`. What is sent: the archives, and an
OAuth token belonging to the Google account you authorise. The connection uses an
OAuth client you create yourself, so the traffic is between your site and Google —
it is not routed through us. Only the `drive.file` scope is requested, which limits
Sitecarry to files it created itself.
Google terms: https://policies.google.com/terms — privacy policy:
https://policies.google.com/privacy.

**Slack, Discord, or any incoming webhook** — used only when you enter a webhook URL
on the Notifications screen. Sitecarry posts a short message to that URL when a
backup finishes or fails: the site name, the outcome, the step it stopped on and the
error text. No archive and no credentials are sent. The destination is the URL you
supply, commonly `https://hooks.slack.com/...`.
Slack terms: https://slack.com/terms-of-service — privacy policy:
https://slack.com/trust/privacy/privacy-policy. Discord terms:
https://discord.com/terms — privacy policy: https://discord.com/privacy.

**Your own site** — Sitecarry sends an HTTP request to your site's own
`admin-ajax.php` to start and continue a background backup. That is a loopback
request to your server, not a third party.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/`.
2. Activate it through the **Plugins** screen.
3. Open **Sitecarry** in the admin menu and click **Create Backup**.

== Frequently Asked Questions ==

= Where are packages stored? =

In `wp-content/uploads/sitecarry/`, protected from direct web access.

= What happens if I delete the plugin? =

Sitecarry removes its settings and its scheduled events from the database, and stops
scheduling anything. Your packages in `wp-content/uploads/sitecarry/` are left
exactly where they are, on purpose — deleting a plugin should never be the thing
that destroys your last copy of the site. Delete that folder yourself once you are
sure you no longer need what is in it.

= It says to keep the tab open. Why? =

Background backups need the site to be able to send an HTTP request to itself. Some
hosts block that. When Sitecarry detects it cannot, it falls back to running the
backup from your browser instead, which works everywhere but needs the tab left
open. Ask your host to allow loopback requests if you want unattended backups.

= How do I roll this site back to a package? =

Press **Restore** next to the package. Sitecarry places a restore installer in the
WordPress folder and sends you to it. You will need the package passphrase and this
site's database password. Your stored packages are not touched.

= A backup seems stuck. What do I do? =

While a backup is running there is a **Stop backup** button under the progress bar,
and it will tell you if that backup has stopped making progress. Stopping clears
away the half-built package. From the command line, `wp sitecarry stop` does the
same — useful if the admin screen is not loading.

Nothing is lost by stopping: a partial package cannot be restored from anyway.

= Can I run backups from the command line? =

Yes. `wp sitecarry backup` builds one and waits for it, with no execution limit and
nothing to keep open. `wp sitecarry verify --all` checks every package and exits
non-zero if any fails, so it works in a monitoring script. `wp sitecarry status`
prints how the site is protected.

= Does verifying prove the backup will restore? =

No, and nothing short of restoring it does. Verifying proves the archive is the one
that was written, still opens, and holds the files a restore starts from. That
catches truncated uploads, disks that filled mid-write and silent corruption. It
cannot tell you the database inside will import cleanly on a different server.

= The server is gone. How do I restore? =

Upload `installer.php` into the target folder on the new server, open it, and enter
the package passphrase. If it cannot find the archives it will offer to download
them from your storage — enter a read-only key for that bucket when it asks. Keep a
copy of `installer.php` somewhere other than the site it protects; it is small, and
without it you would have only the archives.

= What do I need to restore an incremental backup? =

Every archive from the last full backup up to the one you are restoring. The
Sitecarry screen tells you how many that is, and the installer refuses to start
while any of them is missing rather than restoring half a site. If that sounds
fragile, leave incremental off — every package is then complete on its own.

= How do I move a site to a different server? =

Download both the Archive and the Installer from the Sitecarry screen, upload them
into the target folder on the other server, create an empty database there, then
open `installer.php` in a browser and enter the package's restore passphrase.

= Why does the restore not run inside WordPress? =

Because it replaces the very files WordPress is running from. Anything doing that
from inside WordPress would be pulling the floor out from under itself partway
through. The installer is a standalone program that never loads WordPress.

= Can it restore into a database that already has a WordPress install? =

Only if it uses the same table prefix, and it will replace those tables. Changing
the table prefix during a restore is not supported yet — use an empty database.

= How large a site can it handle? =

Comfortably into the tens of gigabytes. Sitecarry writes its own archive format
rather than using PHP's zip extension, appending each file to the end and never
rewriting what is already there — so the work grows with the size of the site
instead of with the square of it. ZIP64 is used automatically, so neither the
archive nor any file in it is capped at 4GB.

You still need free disk space for the package, and enough for the database export
while it is being written.

= Can I make backups faster? =

Three things matter, in order.

Compression is the big one, and Sitecarry already avoids most of the cost: images,
video, audio, fonts and archives are stored as they are, whatever level you pick,
because compressing them again is pure waste. If the server is slow and disk is
cheap, set compression to None.

Second, exclude what you do not need. A folder of raw video in uploads will
dominate everything else.

Third, let it run in the background rather than from a browser tab, or use
`wp sitecarry backup`, which has no execution limit at all.

== Screenshots ==

1. Every package, with the passphrase its restore needs, and one line at the top for the only question that matters: is this site protected right now.
2. When backups run, how many are kept, and what goes into them.
3. Sending finished packages to Amazon S3, or anything that speaks the same protocol — Backblaze B2, Wasabi, DigitalOcean Spaces, MinIO, Google Cloud Storage.
4. Being told when a backup fails, by email or into Slack or Discord.

== Changelog ==

= 1.15.0 =
* Deleting the plugin now clears its settings and scheduled events out of the
  database. Your packages in `wp-content/uploads/sitecarry/` are deliberately left
  alone — they are backups, and deleting a plugin should not throw away the only
  copy of someone's site.
* Housekeeping for the WordPress.org plugin directory, none of it visible in use:
  byte order marks removed from the folder guard files, a missing guard file added,
  template variables renamed so they cannot shadow WordPress globals, and the readme
  now documents every outside service the plugin can be configured to contact.
* The restore installer is assembled slightly differently. The files shared between
  the plugin and the installer now carry the plain guard WordPress expects, and the
  builder removes it as it inlines them, rather than each file carrying a guard
  written to suit both places at once.

= 1.14.0 =
This release fixes ten bugs found in a full review, most of them cases where a
backup could be silently corrupt or a restore could fail — the worst kind, because
you only find out when you need the backup.

* A backup interrupted partway and resumed no longer risks a corrupt archive. The
  archive, its index and the database export are each cut back to their last good
  point before more is written, so nothing is ever duplicated or left in a hole.
* The database export now pages through large tables by their primary key. This is
  both correct — the old method could skip or double rows on tables like term
  relationships — and dramatically faster on tables with millions of rows.
* A single very large file no longer lets a second copy of the backup start on top
  of the first.
* Switching the storage destination off mid-backup no longer leaves a backup
  spinning forever; it stops with a clear message.
* The compression setting now actually changes the compression. "Light" is now
  genuinely lighter on the processor, and "Maximum" genuinely smaller.
* Stopping a backup mid-upload now cancels the upload at the other end too.
* A restore interrupted mid-statement now resumes correctly instead of wedging.

= 1.13.2 =
* A finished backup can no longer be written back into existence as a half-done one.
  Anything still holding an older copy of a job — a request that was killed and
  restarted, a second driver that began before the job ended — is now ignored once
  the job on disk has finished.

= 1.13.1 =
* Fixed the FTP and Google Drive settings being impossible to find. Each
  destination's settings were rendered hidden and only revealed by a script, so if
  that script did not run they stayed invisible with no way to reach them. The
  chosen destination's settings are now shown by the page itself, and the script
  only handles switching.
* The Storage screen now says that choosing a destination is what reveals its
  settings, rather than showing a lone dropdown and nothing else.

= 1.13.0 =
* A Stop button for a backup that is running, or one that has hung. It appears only
  while a backup is in progress, and says so plainly when that backup has stopped
  making progress.
* Stopping removes the half-built package. A partial package cannot be restored
  from, so leaving it would only take up room and look like a backup.
* An upload in progress is cancelled at the far end too, rather than left as an
  unfinished multipart upload that storage keeps charging for.
* `wp sitecarry stop` does the same from the command line, which is the way out if
  the admin screen itself cannot be reached.

= 1.12.1 =
* Fixed background backups never starting. The request that tells the site to begin
  work sent the job details in its body, and because that request is deliberately
  abandoned the instant it is made, the body was often never delivered — so the
  backup sat at the first step for ever, looking busy. The details now travel in the
  address, which is how WordPress sends its own equivalent.
* If the server says it will carry on by itself and then does not, the open tab now
  notices after a few seconds and takes over, instead of watching a bar that will
  never move.
* Scanning no longer shows a percentage that falls as more folders are found. A site
  with a deep folder tree used to sit at a low number looking stuck; it now reports
  how many files it has found.

= 1.12.0 =
* Large sites are now genuinely practical. Sitecarry writes its own archive instead
  of using PHP's zip extension: every file is appended to the end and nothing already
  written is touched again. Previously each batch of files rewrote the whole archive,
  so a build got slower the bigger it grew — at five gigabytes, dramatically so.
* ZIP64 throughout, so neither the archive nor any single file inside it stops at 4GB.
* A compression setting: None, Light, Normal or Maximum. Already-compressed files —
  images, video, audio, fonts, archives — are stored untouched at every level,
  because deflating them costs processor time and saves nothing.
* Faster, and not only because of the above: the old limit of 200 files per request
  existed to avoid running out of file handles, and no longer applies.
* Building a package no longer needs PHP's zip extension at all.

= 1.11.0 =
* Two more destinations: FTP and Google Drive. Uploads to all three divide into
  resumable chunks the same way, so a large package survives short execution limits
  wherever it is going.
* FTP resumes at a byte offset in both directions, uploads under a temporary name so
  an interrupted transfer is never mistaken for a finished archive, and offers FTP
  over TLS — which the settings screen recommends, plainly, because plain FTP sends
  the password and the whole database in clear.
* Google Drive uses an OAuth client of your own rather than routing authorisation
  through anyone else's servers, and asks only for the scope that lets it see the
  files it created itself. A restore cannot pull archives back from Drive: that
  would mean putting a Google token into a file that can end up at a public URL, so
  those archives are downloaded from Drive by hand instead.
* Google Cloud Storage also works through the S3 setting — point Endpoint at
  `https://storage.googleapis.com`.

= 1.10.0 =
* Restore from storage. If an archive is no longer on the server — pruned by
  retention, or because the server it lived on is gone — the installer downloads it
  from S3 before restoring. This is the situation a backup exists for, and until now
  there was no way back from it.
* Downloads are ranged and resumable, into a `.part` file, so a large archive
  survives the same short execution limits as everything else and a partial one is
  never mistaken for a complete one.
* Storage keys are never written into the installer. It carries the bucket and
  region; the operator types the credentials when a download is actually needed.

= 1.9.0 =
* Verify a package: checks its size and checksum against what was recorded, that the
  archive still opens and is structurally sound, and that the two files a restore
  cannot start without are inside it. For an incremental package it checks every
  archive in the set, not just the newest.
* WP-CLI: `wp sitecarry backup`, `list`, `verify`, `delete`, `status` and `stage`.
  A backup on the command line has no execution limit and no tab to keep open, and
  `verify` exits non-zero on failure so it can run from a script.

= 1.8.0 =
* Split the admin into four screens — Backups, Settings, Storage, Notifications —
  instead of one page carrying everything.
* The Backups screen now opens with a summary: when the last backup ran, when the
  next one is due, where packages are kept, and whether anyone gets told if one
  fails.
* Each screen saves only its own settings, so changing storage cannot disturb the
  schedule.
* Packages are labelled full or incremental, and show whether they were uploaded.

= 1.7.0 =
* Incremental backups: package only the files that changed since the last backup.
  The database is still exported in full every time, so a restore never depends on
  reassembling it.
* A full backup is taken automatically every so often, capping how many archives a
  restore has to replay.
* Files deleted from the site are recorded, so they do not reappear on restore.
* Retention now counts complete sets. A set is deleted whole or not at all, because
  removing its full backup would leave the rest unrestorable.
* The restore installer replays a whole set in order, and refuses to start while any
  of its archives is missing — naming the ones it cannot find.

= 1.6.0 =
* Notifications by email and to Slack or Discord. The default is failures only,
  because that is the message you need to still be reading in six months.
* A failure names the step it stopped on and quotes the error, and says plainly
  that the partial package must not be relied on.
* Send a test, so notifications can be proved before they are needed.
* Every way a backup can end now runs through one place, so a job abandoned by the
  watchdog reports itself exactly like one that failed outright.

= 1.5.0 =
* Send packages to Amazon S3, or anything S3-compatible — Backblaze B2, Wasabi,
  DigitalOcean Spaces, MinIO — by setting a custom endpoint.
* Uploading is part of the backup job, using S3 multipart, so a large package
  survives short execution limits and a resumed run continues at the next part
  rather than starting the transfer again.
* Deleting a package, including by retention, removes the remote copy too.
* Test connection writes a small file and deletes it again, so it proves the upload
  and delete permissions a backup actually needs.
* Credentials can be defined in wp-config.php as SITECARRY_S3_KEY and
  SITECARRY_S3_SECRET, keeping them out of the database — and therefore out of the
  packages the database is dumped into.

= 1.4.0 =
* Scheduled backups: hourly, twice daily, daily or weekly, at an hour you choose in
  your site's own timezone.
* Retention — keep the last N packages and let older ones go, so a schedule cannot
  quietly fill the disk.
* A settings screen for what goes into a package: skip transients, include tables
  belonging to other applications, and your own list of paths to exclude. Manual and
  scheduled backups now build packages identically.
* A scheduled run is skipped while a backup is already running, so a tight schedule
  cannot stack jobs on top of each other.

= 1.3.0 =
* Backups now run in the background. Start one and close the tab — the server keeps
  working, and the Sitecarry screen re-attaches to a running backup when you return.
* A watchdog restarts a backup whose background run is interrupted, picking up from
  where it stopped rather than starting over.
* Sites that cannot call themselves over HTTP fall back to the previous
  browser-driven mode automatically, and the screen says which one is in use.

= 1.2.0 =
* Restore onto the current site with one button, no manual upload. WordPress cannot
  overwrite the files it is running from, so Sitecarry places the restore installer
  in the WordPress folder and hands off to it; the restore runs outside WordPress.
* The staged installer knows which site it is on and pre-fills everything except
  the database password.
* An in-place restore never deletes the stored package it was started from.
* A staged installer is flagged on every admin screen until it is used or removed,
  with a one-click Remove.

= 1.1.0 =
* Restore. Every package now comes with a standalone `installer.php` that rebuilds
  the site on any server: unpacks the export, imports the database, extracts the
  files, rewrites URLs and paths, and updates `wp-config.php`.
* Serialization-safe search and replace that repairs string lengths, walks
  doubly-serialized values, and covers the JSON-escaped URLs page builders store.
* Per-package restore passphrase; the installer refuses to run without it and
  removes itself and the archive when it finishes.
* Archive entries whose paths point outside the target folder are refused.

= 1.0.0 =
* First release: resumable full-site packaging (files + database), progress UI,
  package list, authenticated download and delete.

== Upgrade Notice ==

= 1.15.0 =
Housekeeping and a proper uninstall. Deleting the plugin now clears its settings and
schedules; your backup packages are left where they are.

= 1.14.0 =
Fixes ten bugs, most of them cases where an interrupted backup could resume into a
corrupt archive. Worth taking.
