=== seljeBackup ===
Contributors: seljenes
Tags: backup, database backup, google drive, onedrive, s3
Donate link: https://buy.stripe.com/3cI7sL4tagAt4xr7ACbQY0n
Requires at least: 6.2
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.9.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Simple, reliable backups of your database and files to this server, Google Drive, OneDrive, Dropbox, S3-compatible storage or FTP.

== Description ==

seljeBackup backs up your WordPress database and files with one click, or automatically every day, week or month. Every backup is two ordinary files, a .sql.gz for the database and a .zip for your files, that you can open without this plugin.

Find this free plugin useful? Please support my work with a <a href="https://buy.stripe.com/3cI7sL4tagAt4xr7ACbQY0n">one-time donation</a> (50% of the proceeds go to Ukraine).

Key features:

- "Back up now" button and a 3-question quick setup
- Daily, weekly or monthly backups; keeps the last N backups
- Off-site copies in Google Drive, OneDrive, Dropbox, S3-compatible storage (Amazon S3, Backblaze B2, Wasabi, Cloudflare R2, …) and FTP/FTPS
- Built for shared hosting: short, resumable steps that survive PHP time limits
- Handles large sites (ZIP64, chunked database dump)
- "What takes up space?" with tips and one-click "Skip in backups"
- Email alerts when something goes wrong
- Remote API: connect your own dashboard with one connection string; signed webhook; cron URL
- Encrypted credentials and a protected backup folder
- Translation-ready

== Privacy ==

This plugin does not send any data to the plugin developer. Nothing is sent anywhere unless you add a cloud storage destination, a webhook, or use the donate link.

Backups contain your site's database and files, including personal data and the database password in wp-config.php (the security keys and salts are never included). Store them only in places you trust.

Google Drive is a trademark of Google LLC. OneDrive is a trademark of Microsoft Corporation. Dropbox is a trademark of Dropbox, Inc. Amazon S3 is a trademark of Amazon.com, Inc. This plugin is not affiliated with or endorsed by any of them.

WordPress is a trademark of the WordPress Foundation. This plugin is not affiliated with or sponsored by the WordPress Foundation.

== Third Party Services ==

The plugin connects to a third-party service only when you configure that service as a storage destination (or webhook). Each site owner uses their own app/account credentials; there is no relay server between your site and the provider.

= Google (Google OAuth + Google Drive API) =

What it is used for: uploading backups to your Google Drive and deleting old backups there.

When data is sent:

- When an administrator clicks **Connect** and completes authorization
- When a backup is uploaded, an old backup is deleted (retention), or **Test connection** is clicked
- When the plugin refreshes an expired access token

What data is sent: OAuth client ID/secret, redirect URI, state and authorization code (during connection); the OAuth access token, the backup folder name, and the backup files themselves (during uploads).

Endpoints: https://accounts.google.com/o/oauth2/v2/auth, https://oauth2.googleapis.com/token, https://www.googleapis.com/drive/v3, https://www.googleapis.com/upload/drive/v3

OAuth scope: https://www.googleapis.com/auth/drive.file (the plugin can only see files it created itself)

Google Privacy Policy: https://policies.google.com/privacy — Google Terms of Service: https://policies.google.com/terms — Google APIs Terms of Service: https://developers.google.com/terms

= Microsoft (Microsoft identity platform + Microsoft Graph / OneDrive) =

What it is used for: uploading backups to your OneDrive and deleting old backups there.

When data is sent: when an administrator clicks **Connect**, when a backup is uploaded or deleted, when **Test connection** is clicked, and when tokens are refreshed.

What data is sent: OAuth client ID/secret, redirect URI, state and authorization code; the OAuth access token, the backup folder path, and the backup files.

Endpoints: https://login.microsoftonline.com, https://graph.microsoft.com/v1.0 and the pre-authenticated upload URLs Microsoft returns for each upload.

OAuth scopes: Files.ReadWrite, offline_access, User.Read

Microsoft Privacy Statement: https://privacy.microsoft.com/privacystatement — Microsoft Services Agreement: https://www.microsoft.com/servicesagreement

= Dropbox (Dropbox OAuth + Dropbox API) =

What it is used for: uploading backups to your Dropbox app folder and deleting old backups there.

When data is sent: when an administrator clicks **Connect**, when a backup is uploaded or deleted, when **Test connection** is clicked, and when tokens are refreshed.

What data is sent: the app key, redirect URI, state, PKCE code challenge/verifier and authorization code; the OAuth access token, the backup folder path, and the backup files.

Endpoints: https://www.dropbox.com/oauth2/authorize, https://api.dropboxapi.com, https://content.dropboxapi.com

Dropbox Privacy Policy: https://www.dropbox.com/privacy — Dropbox Terms of Service: https://www.dropbox.com/terms

= Amazon S3 or S3-compatible storage (provider chosen by you) =

What it is used for: uploading backups to your bucket and deleting old backups there.

When data is sent: when a backup is uploaded or deleted, or **Test connection** is clicked.

What data is sent: requests signed with your access key (the secret key itself is never sent), the bucket and folder name, and the backup files.

Endpoint: the endpoint you enter (for Amazon S3: https://s3.<region>.amazonaws.com). Please see your provider's privacy policy and terms, for example Amazon Web Services: https://aws.amazon.com/privacy/ and https://aws.amazon.com/service-terms/, Backblaze: https://www.backblaze.com/company/policy/privacy, Wasabi: https://wasabi.com/legal/privacy-policy, Cloudflare: https://www.cloudflare.com/privacypolicy/

= FTP / FTPS server (chosen by you) =

What it is used for: uploading backups to your own FTP server or NAS.

When data is sent: when a backup is uploaded or deleted, or **Test connection** is clicked.

What data is sent: your FTP username and password, and the backup files. With FTPS (default) the connection is encrypted; with plain FTP it is not.

= Webhook (URL chosen by you) =

If you enter a webhook URL under Remote access, the plugin sends a JSON message to that URL when a backup finishes or fails: site address and name, backup ID, type, status, times, error/warnings, backup file names and sizes, and storage names. No backup contents are sent.

= Stripe (Donate link) =

What it is used for: optional donations via a Stripe-hosted checkout page.

When data is sent: only when an administrator clicks the **Donate** button in the plugin admin page. The plugin itself does not send payment details; Stripe receives the data needed to load and process the checkout (such as your IP address, browser/user agent and anything you enter on the checkout page).

Stripe Privacy Policy: https://stripe.com/privacy — Stripe Terms / Legal: https://stripe.com/legal

== Installation ==

1. In WordPress admin, go to **Plugins -> Add New**.
2. Search for **seljeBackup**.
3. Click **Install Now** and then **Activate**.
4. Open **seljeBackup** in the admin menu and answer the three quick-setup questions.

== Configuration ==

=== Quick setup ===

The first time you open the plugin, three questions set everything up: what to back up (everything, or database only), how often (daily, weekly, or manual), and where (this server, or a cloud storage you connect next). Click **Back up now** to make your first backup immediately.

=== Google Drive ===

1. Go to https://console.cloud.google.com and create a project.
2. **APIs & Services -> Library**: enable **Google Drive API**.
3. **OAuth consent screen**: choose **External**, fill in an app name and your email, and add the scope `.../auth/drive.file`.
4. **Important:** publish the app (**Audience -> Publish app**, status "In production"). Apps left in "Testing" lose access after 7 days, which stops scheduled backups. `drive.file` is a non-sensitive scope, so Google does not need to review the app.
5. **Credentials -> Create credentials -> OAuth client ID**, type **Web application**. Add the **redirect URI** shown in the plugin (your `/wp-admin/` address) under Authorized redirect URIs.
6. In the plugin: **Storage -> Google Drive**, paste the Client ID and Client secret, click **Save**, then **Connect**, then **Test connection**.

=== OneDrive ===

1. Go to https://entra.microsoft.com (or https://portal.azure.com) -> **App registrations -> New registration**.
2. Supported account types: "Accounts in any organizational directory and personal Microsoft accounts" (or personal accounts only).
3. Redirect URI: platform **Web**, value = the redirect URI shown in the plugin.
4. **API permissions -> Add a permission -> Microsoft Graph -> Delegated**: `Files.ReadWrite` and `offline_access`.
5. **Certificates & secrets -> New client secret**. Copy the **Value** (not the Secret ID) and note the expiry date. Before it expires, create a new secret, paste it into the plugin and click **Connect** again.
6. In the plugin: **Storage -> OneDrive**, paste the Application (client) ID and the secret value, choose the account type, **Save**, **Connect**, **Test connection**.

=== Dropbox ===

Requirement: Dropbox only accepts redirect URIs that start with `https://` (except `localhost`). Enable HTTPS on the site first. The plugin shows a warning if it is missing.

**1. Create the app** at https://www.dropbox.com/developers/apps -> **Create app**

- Choose an API: **Scoped access**
- Choose the type of access you need: **App folder** (seljeBackup can then only reach its own folder, never the rest of your Dropbox)
- Name your app: any unique name, for example "seljeBackup-yourname" -> **Create app**

**2. Settings tab (top to bottom)**

- Status: leave **Development**. Only you connect to the app, so "Apply for production" is not needed.
- Development users: leave **Only you**.
- Permission type: must be **Scoped App (App Folder)**.
- App folder name: the folder under Dropbox -> Apps where backups are stored. Change it if you like.
- App key: copy it into seljeBackup. The **App secret is not used**; keep it private.
- OAuth 2 -> Redirect URIs: paste the redirect URI shown in seljeBackup and click **Add**.
- Allow public clients (Implicit Grant & PKCE): must be **Allow** (the default). seljeBackup connects without the App secret, which requires this.
- Generated access token, Chooser / Saver / Embedder domains, Webhooks: leave empty.

**3. Permissions tab**

- Tick only `files.content.write` (used to upload backups and delete old ones). Leave everything else as it is; `account_info.read` is already ticked and is used by "Test connection". Click **Submit**.
- If you change permissions after connecting, click **Reconnect** in seljeBackup.
- The **Branding** and **Analytics** tabs are optional and not needed.

**4. In seljeBackup:** Storage -> Dropbox, paste the **App key**, **Save**, **Connect** (approve in Dropbox), **Test connection**.

One app is enough for all your sites: add the redirect URI of every site to the same app and use the same App key everywhere. Each site stores its backups in its own folder. The same applies to Google Drive and OneDrive.

=== Amazon S3 and S3-compatible storage ===

1. Create a private bucket with your provider.
2. Create an access key limited to that bucket (list, read, write, delete).
3. In the plugin: **Storage -> S3 Compatible**, enter the endpoint (leave empty for Amazon S3), region, bucket and keys, **Save**, **Test connection**.

Endpoint examples: `s3.eu-central-003.backblazeb2.com` (Backblaze B2), `s3.eu-central-1.wasabisys.com` (Wasabi), `<account-id>.r2.cloudflarestorage.com` with region `auto` (Cloudflare R2), `fra1.digitaloceanspaces.com` (DigitalOcean Spaces). Turn on "path-style URLs" for MinIO.

Tip: add a lifecycle rule to the bucket that aborts incomplete multipart uploads after a few days.

=== FTP / FTPS ===

Enter host, port, username, password and a remote folder, **Save**, **Test connection**. FTPS (explicit TLS) is on by default; plain FTP sends your password unencrypted.

=== Remote access (connect your own dashboard) ===

1. **Remote access -> Create API key**.
2. Copy the **connection string** (it contains the API address and the key) and paste it into your dashboard. The key is shown only once. You can create a new one at any time, which disables the old one.
3. Your dashboard sends the key in the header `Authorization: Bearer <key>` (or `X-SWPB-Key: <key>` if your host strips Authorization headers).

Endpoints (under `/wp-json/seljebackup/v1`):

- `GET /status`: health (ok / warning / error), last backup, running job, next scheduled run, storage status
- `POST /backups`: start a backup; optional JSON body `{"type": "full" | "database" | "files"}`. Returns 202, or 409 if a backup is already running.
- `GET /job`: progress of the current or last backup
- `GET /backups`: list of backups with sizes and storage locations

The connection string is `swpb1:` followed by base64url-encoded JSON: `{"url": "<API address>", "key": "<API key>", "site": "<site name>"}`.

A WordPress Application Password of an administrator also works instead of the API key.

Webhook: if you enter a webhook URL, the plugin POSTs JSON (`event` = `backup.completed` or `backup.failed`) with the header `X-SWPB-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the signing secret>`. Compare it in constant time before trusting the message.

=== Cron URL ===

WordPress runs scheduled tasks only when someone visits the site. On quiet sites (or if `DISABLE_WP_CRON` is set), add the **Cron URL** from the Remote access tab to a free service such as cron-job.org, or to your host's cron jobs, every 5–15 minutes. It only starts backups that are due and continues running ones.

== Plugin settings ==

=== Automatic backups ===

**How often / at**
Daily, weekly, monthly, or off (manual only), at a time in the site's timezone (Settings -> General).

**Day (weekly) / Day of month (monthly)**
Which day weekly or monthly backups run. Days 29–31 are not offered so that monthly backups run every month.

**Automatic backups include**
Database + files, database only, or files only.

=== What to back up ===

**Files**
"wp-content + wp-config.php and .htaccess" (recommended) contains your themes, plugins and uploads. WordPress core files can always be downloaded again. "The entire WordPress folder" also includes core files and anything else in that folder. With both options, wp-config.php (and copies such as wp-config.php.bak) is saved without its security keys and salts, which are replaced with WordPress's placeholder text (WP_CACHE_KEY_SALT, used by object caches, gets a new random value instead). If a file cannot be cleaned with certainty, it is left out and the backup log says so.

**Skip: cache folders and other backup plugins' backups**
Leaves out cache folders (cache, et-cache, litespeed, …) and backup folders of other plugins (UpdraftPlus, Duplicator, All-in-One WP Migration, WPvivid, BackWPup, …) so backups don't contain other backups.

**Skip: developer folders**
Leaves out .git, .svn, .hg and node_modules folders.

**Also skip**
Your own exclusions, one path per line relative to the WordPress folder, for example `wp-content/uploads/videos`. Wildcards (*) are allowed.

**Skip files larger than**
Leave out single files above this size (MB). 0 = no limit.

**Database: include tables that do not start with the table prefix**
Off by default, so only this WordPress site's tables are backed up. Turn on if other applications share the database and you want their tables too.

**Skip tables**
Full table names to leave out, one per line (for example large log tables).

=== Keeping backups and notifications ===

**Keep on this server**
How many of the most recent backups stay on this server. 0 removes the local copy as soon as it is safely uploaded to a cloud storage. How many to keep in each cloud storage is set per storage ("Keep").

**Email me / Email address**
Only when something goes wrong (default), after every backup, or never. Leave the address empty to use the site admin email; separate several addresses with commas.

=== Advanced ===

**Low-impact mode**
Off (default), or a 5, 15 or 30 second pause between backup steps. The pause gives the server a break so busy or low-cost hosting stays responsive and CPU limits are not hit. Large backups take longer; small sites that finish in one step are not affected. Waiting uses no CPU.

**Time per step**
Backups run in short steps (default 20 seconds) so hosting time limits are never hit. After a failed step the plugin automatically halves the step size. Lower this if backups repeatedly fail with "stopped by the server".

**Split files archive every**
0 = one single .zip (recommended). Set a size in MB if your storage has a maximum file size; each part is then a complete, separate .zip file.

=== Storage (per destination) ===

**Name**: label shown in the plugin. **Keep**: how many recent backups to keep in this storage (0 = keep all). **Active**: untick to pause uploads without removing the storage. Removing a storage never deletes the backups already stored there.

== Frequently Asked Questions ==

= What does seljeBackup not do (yet)? =

- One-click restore or site migration. Backups are standard .sql.gz and .zip files and are restored by hand (see "How do I restore or rebuild my site from a backup?" below).
- Incremental backups
- Official multisite support

= How do I restore or rebuild my site from a backup? =

Every backup is two ordinary files, so no plugin is needed: `…_database.sql.gz` (posts, pages, settings, users, orders) and `…_files.zip` (wp-content with themes, plugins and uploads, plus wp-config.php without its security keys, and .htaccess). The same guide is in the plugin under **Backups -> How do I restore or rebuild my site?**

You need: both backup files (download them in the plugin or from your cloud storage), a file manager or FTP/SFTP, and phpMyAdmin or your host's database tool. For a full rebuild you also need WordPress itself from wordpress.org: the core folders `wp-admin` and `wp-includes` are not in the backup by default, because they are identical for every site and can always be downloaded again.

**A. Quick restore (WordPress still runs on the same site)**

1. Make a new backup first, so you can go back.
2. Database: in phpMyAdmin select the site's database, choose **Import**, pick the `.sql.gz` file (no need to unzip) and run it. The WordPress tables are replaced.
3. Files (only if they are broken or missing): unzip `…_files.zip` and upload the `wp-content` folder, replacing the existing files.
4. Log in (users and passwords are as they were in the backup), click **Save** under Settings -> Permalinks and clear any cache.

**B. Full rebuild (empty hosting, new server or new address)**

1. Unzip `…_files.zip` and open `seljebackup-manifest.json`: note the WordPress version, table prefix and old site address.
2. Download WordPress (preferably the same version: https://wordpress.org/download/releases/) and upload the contents of its `wordpress` folder to your web folder (often `public_html`). Do not run the installer.
3. Upload from the backup: `wp-content` (replace the one from WordPress), `wp-config.php` and `.htaccess`.
4. Create an empty database and user in your hosting panel. If they differ from the old site, change `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DB_HOST` in `wp-config.php`. Do not change `$table_prefix`. Backups never contain the real security keys: replace the eight lines that say "put your unique phrase here" with new ones from https://api.wordpress.org/secret-key/1.1/salt/ (if you skip this, WordPress creates keys by itself).
5. Import `…_database.sql.gz` with phpMyAdmin. Too big for phpMyAdmin? Use your host's import tool or SSH: `gunzip < backup_database.sql.gz | mysql -u USER -p DATABASE`
6. New address? Run `wp search-replace 'https://old-site.com' 'https://new-site.com' --all-tables` (WP-CLI) or use a plugin such as Better Search Replace. Never use find/replace on the `.sql` file itself; it breaks serialized settings.
7. Log in at `/wp-admin` (everyone has to log in again, because the security keys are new), click **Save** under Settings -> Permalinks, clear caches and check some pages and images.
8. In **seljeBackup -> Storage**, connect each storage again (or enter its password again): saved connections are protected with the old security keys.

If "Files" was set to "The entire WordPress folder", WordPress core is already in the zip: skip step 2.

**If something goes wrong**

- "Error establishing a database connection": the database details in `wp-config.php` are wrong (step B4).
- Only the front page works: Settings -> Permalinks -> Save, or upload `.htaccess` again.
- Missing images: `wp-content/uploads` was not uploaded completely.
- Redirects to the old address: the search-and-replace (B6) was not done for all tables.
- Import stops or times out: use the host's import tool or the SSH command in B5.

= Do I need to back up the wp-content folder? =

Yes, it is the most important folder. WordPress core (wp-admin, wp-includes) can always be downloaded again, which is why it is left out by default, but wp-content holds everything that is unique to your site:

- `uploads`: your images and documents. Usually irreplaceable, and usually the largest part.
- `themes` and `plugins`: can be reinstalled from WordPress.org, but not premium or custom-made ones, edited files, or the exact versions you run. Restoring from a backup is much faster and brings back a working site in one step.
- `mu-plugins`, `languages` and other plugin folders: small, often forgotten, and needed for the site to work the same way.

Parts of wp-content that are not needed are skipped automatically: cache folders, other plugins' backups, upgrade leftovers and logs.

When a smaller backup makes sense: if your files rarely change but your database does (orders, comments, form entries), you can schedule **Database only** backups and click **Back up now** with "Database + files" after uploading media or updating plugins. Separate schedules for database and files are planned. Very large, rarely changing folders (for example an old video archive that is stored elsewhere too) can be skipped under "What takes up space?" on the Dashboard.

= Why is my site (and backup) getting bigger? =

Open **Dashboard -> What takes up space?** and click **Analyze now**. It measures folders, the largest files and database tables, shows what goes into the backup, and gives tips. Common causes: several resized copies of every uploaded image, old backups from other plugins, cache folders, large log files, many post revisions and WooCommerce/Action Scheduler logs.

= Where are backups stored on my server? =

In `wp-content/seljebackup-<random>/`. The folder is blocked from web access (`.htaccess`, `web.config`, index files) and has an unguessable name, because some servers (nginx) ignore `.htaccess`. To store backups outside the web root, add `define('SWPB_BACKUP_DIR', '/path/outside/public_html/backups');` to `wp-config.php`.

= Will backups work on my cheap shared hosting? =

Yes, that is what it is built for. Backups run in short steps (a few seconds each) that continue in the background. If the host kills a step, the plugin notices, retries with smaller steps, and explains in plain language if it still cannot continue.

= The system check says my server "blocks requests to itself". Is that a problem? =

No. Backups still work while the admin page is open, and scheduled backups continue via WP-Cron every 5 minutes. For faster scheduled backups, add the Cron URL to a cron service.

= Why do I need to create my own Google / Microsoft / Dropbox app? =

So that your backups and access tokens go directly between your site and your own account, with no third-party server in between, and so that the plugin does not depend on a service run by the developer. It takes about 10 minutes, once, and the setup guide in the plugin walks you through it.

= Can my Google Drive connection stop working? =

Only if the Google app is left in "Testing" mode (access then expires after 7 days; publish the app as described in the guide), if you remove the app's access in your Google account, or if you change the client secret. The plugin emails you when an upload fails.

= What happens when I delete the plugin? =

Its settings, credentials and backup list are removed. Backup files are deliberately left on the server and in your cloud storage. Delete the `wp-content/seljebackup-<random>/` folder yourself if you no longer need them.

= Does it support multisite? =

Not officially yet. On a multisite network the whole network (all sites) is backed up together.

= What PHP version is required? =

PHP 7.4 or newer with the zlib extension (almost always available). FTP storage additionally needs the PHP FTP extension.

= Does it support Norwegian language? =

The plugin is translation-ready. A Norwegian Bokmål (`nb_NO`) translation is planned.

== Screenshots ==

1. Dashboard with "Back up now", progress and status.
2. Backups list with downloads.
3. Storage setup with step-by-step guide.
4. Backup settings.
5. Remote access: API key and connection string for your dashboard.
6. Example of a remote web page that runs backups for several sites from one place.
7. Remote backups with a pause in between, since both sites are on the same server.

== Changelog ==

= 0.9.0 =

- First version: database + files backups as two standard files, schedules, retention, Google Drive, OneDrive, Dropbox, S3-compatible and FTP storage, remote API with API key / connection string, webhook, cron URL, email notifications, system check
- "What takes up space?" panel: folder sizes, largest files and tables, tips, one-click skip/include
- Database backups are written as one standard gzip stream (readable by every tool, including those that only read the first part of a .gz file)
- Step-by-step guide for restoring or rebuilding a site from a backup
- Lower load on every page view: plugin code is only loaded when it is needed (admin, REST, cron, backups)
- Low-impact mode: optional pause between backup steps
- Duplicator 5's backup folder (wp-content/duplicator-backups) is skipped automatically
- Security: wp-config.php is backed up without its authentication keys and salts, and the database backup leaves out keys and salts stored in the database, so they never leave the server

== Upgrade Notice ==

= 0.9.0 =

First version.
