Aslan-stg2prodsh 10 Install and Use » History » Version 1
Kerem ERSOY, 11/05/2026 08:13 AM
| 1 | 1 | Kerem ERSOY | # aslan-stg2prod.sh 1.0: Install and Use |
|---|---|---|---|
| 2 | |||
| 3 | Copies a WordPress site (database + files) from one site stack on dmz-web |
||
| 4 | to another, e.g. staging → production. Works for every pair built by |
||
| 5 | `add-wordpress-site.sh`: you give the two site names, the script reads |
||
| 6 | everything else from the sites themselves. |
||
| 7 | |||
| 8 | Runs as **websvcmaster**. Changes nothing outside the two sites' containers |
||
| 9 | and `~websvcmaster/backups/`. |
||
| 10 | |||
| 11 | ## 1. Install |
||
| 12 | |||
| 13 | As root (file uploaded to `/root`): |
||
| 14 | |||
| 15 | ``` |
||
| 16 | install -o websvcmaster -g websvcmaster -m 0755 /root/aslan-stg2prod.sh ~websvcmaster/deploy/aslan-stg2prod.sh |
||
| 17 | ``` |
||
| 18 | |||
| 19 | As websvcmaster: |
||
| 20 | |||
| 21 | ``` |
||
| 22 | cd ~/deploy |
||
| 23 | ./aslan-stg2prod.sh --version # aslan-stg2prod.sh 1.0 |
||
| 24 | ./aslan-stg2prod.sh --help |
||
| 25 | ``` |
||
| 26 | |||
| 27 | Commit it to the dmz-web-deploy repository (root): |
||
| 28 | |||
| 29 | ``` |
||
| 30 | deploy-commit -m "aslan-stg2prod.sh 1.0: copy a WordPress site between site stacks" |
||
| 31 | ``` |
||
| 32 | |||
| 33 | ## 2. What it needs |
||
| 34 | |||
| 35 | | Item | Expected | |
||
| 36 | |---|---| |
||
| 37 | | Containers | `site-<name>` and `db-<name>` of both sites running | |
||
| 38 | | Web root | a volume mounted at `/var/www/html` in `site-<name>` (found automatically) | |
||
| 39 | | WP-CLI | `wp-cli.phar` in the source's web root (the target may not have one yet; the script lends it the source's copy) | |
||
| 40 | | Database | MariaDB image with `mariadb` / `mariadb-dump` (mariadb:11) | |
||
| 41 | | Table prefix | the same on both sites (otherwise it stops) | |
||
| 42 | | NPM | a proxy host + certificate for the target's domain, for the final check | |
||
| 43 | |||
| 44 | It does **not** create proxy hosts, certificates or DNS records, and |
||
| 45 | does not touch `nginx-proxy-manager.container` or any Quadlet file. |
||
| 46 | |||
| 47 | ## 3. Commands |
||
| 48 | |||
| 49 | ``` |
||
| 50 | aslan-stg2prod.sh <source> <target> --dry-run # read, check, show plan; changes nothing |
||
| 51 | aslan-stg2prod.sh <source> <target> # asks you to type the target name |
||
| 52 | aslan-stg2prod.sh <source> <target> -y # no question (scripts only) |
||
| 53 | aslan-stg2prod.sh <source> <target> --noindex # target: "discourage search engines" on |
||
| 54 | aslan-stg2prod.sh <source> <target> --keep-noindex # target gets the source's setting |
||
| 55 | aslan-stg2prod.sh <source> <target> --target-url=https://www.example.com |
||
| 56 | # address the copy gets (default: target's siteurl) |
||
| 57 | aslan-stg2prod.sh --restore <target> <backup-dir> # put the target back |
||
| 58 | ``` |
||
| 59 | |||
| 60 | Site names are the ones in the unit names: `site-staging` → `staging`, |
||
| 61 | `site-mdsdefense` → `mdsdefense`. |
||
| 62 | |||
| 63 | What a copy does: |
||
| 64 | |||
| 65 | 1. Reads both sites: URLs (`siteurl`, `home`), database name, user, host, |
||
| 66 | table prefix, WordPress version, active theme, search engine setting, |
||
| 67 | published content counts, file sizes. Shows them and the plan. |
||
| 68 | 2. Backs up the target to `~/backups/stg2prod/<target>-<timestamp>/` |
||
| 69 | (`db.sql`, `files.tar`, `INFO`, `SHA256SUMS`; mode 700). |
||
| 70 | 3. Puts the target in maintenance mode. |
||
| 71 | 4. Replaces the target's database with the source's. |
||
| 72 | 5. Replaces the target's files with the source's; the target's |
||
| 73 | `wp-config.php` stays (it holds the target's DB host and credentials). |
||
| 74 | 6. Rewrites the source URL to the target URL in the whole database |
||
| 75 | (serialized data and JSON-escaped `\/\/` included), sets |
||
| 76 | `siteurl`/`home`, allows search engines (`--noindex` / `--keep-noindex` |
||
| 77 | change that), |
||
| 78 | flushes transients and cache, and rebuilds the rewrite rules **with |
||
| 79 | plugins and theme loaded** (see Troubleshooting). Reports how many times the |
||
| 80 | source host is still in the database (e.g. e-mail addresses, which are |
||
| 81 | left alone on purpose). |
||
| 82 | 7. Ends maintenance mode and checks the target through NPM: home page |
||
| 83 | HTTP 200 with no source host on it, and every Polylang language root |
||
| 84 | (`/tr/`, `/ar/`...) HTTP 200 without a redirect. |
||
| 85 | |||
| 86 | If a step fails, the target stays in maintenance mode and the script |
||
| 87 | prints the exact `--restore` command. Every run is logged to |
||
| 88 | `~/.local/state/aslan-stg2prod.log`. |
||
| 89 | |||
| 90 | Database passwords are never on a command line: they go to the DB |
||
| 91 | container on stdin. |
||
| 92 | |||
| 93 | ## 4. MDS go-live: staging → www.mdsdefense.com |
||
| 94 | |||
| 95 | As websvcmaster in `~/deploy`: |
||
| 96 | |||
| 97 | 1. **Theme:** `./mds-theme.sh status` shows what staging runs. Nothing to |
||
| 98 | install on production first: the copy brings the theme files along. |
||
| 99 | 2. **Dry run.** The bare domain `mdsdefense.com` still points to the old |
||
| 100 | site, so production must run as **www**: give it explicitly, whatever |
||
| 101 | the fresh install was set up with: |
||
| 102 | |||
| 103 | ``` |
||
| 104 | ./aslan-stg2prod.sh staging mdsdefense --target-url=https://www.mdsdefense.com --dry-run |
||
| 105 | ``` |
||
| 106 | |||
| 107 | Check in the output: source `https://staging.mdsdefense.com`, target |
||
| 108 | URL `https://www.mdsdefense.com`, same table prefix, target database is |
||
| 109 | the production one (`db-mdsdefense`), counts look right. |
||
| 110 | 3. **Copy:** |
||
| 111 | |||
| 112 | ``` |
||
| 113 | ./aslan-stg2prod.sh staging mdsdefense --target-url=https://www.mdsdefense.com |
||
| 114 | ``` |
||
| 115 | |||
| 116 | Type `mdsdefense` when asked. |
||
| 117 | 4. **Check** (browser, private window): |
||
| 118 | - `https://www.mdsdefense.com/`, `/tr/`, `/ar/`: home, menu, footer, |
||
| 119 | language switcher |
||
| 120 | - Projects, Press Room, Contact (form sends), one Standard Page per |
||
| 121 | language |
||
| 122 | - wp-admin login on www (same users as staging) |
||
| 123 | - Settings > Reading: "Discourage search engines" **off** |
||
| 124 | - Media → Add New shows the 64 MB limit (`.htaccess` came along) |
||
| 125 | - `./mds-theme.sh status`: prod shows the same theme version as staging |
||
| 126 | 5. **From now on:** content is edited on **production only**. Staging is |
||
| 127 | for theme tests. |
||
| 128 | |||
| 129 | ## 5. Later: refresh staging from production |
||
| 130 | |||
| 131 | The same script works the other way; keep staging out of search engines: |
||
| 132 | |||
| 133 | ``` |
||
| 134 | ./aslan-stg2prod.sh mdsdefense staging --dry-run --noindex |
||
| 135 | ./aslan-stg2prod.sh mdsdefense staging --noindex |
||
| 136 | ``` |
||
| 137 | |||
| 138 | `--noindex` keeps staging out of search engines whatever production says. |
||
| 139 | Staging's own `wp-config.php` stays, so it keeps using its own database. |
||
| 140 | |||
| 141 | ## 6. Restore |
||
| 142 | |||
| 143 | ``` |
||
| 144 | ls -1t ~/backups/stg2prod/ # newest first |
||
| 145 | ./aslan-stg2prod.sh --restore mdsdefense ~/backups/stg2prod/mdsdefense-YYYYMMDD-HHMMSS |
||
| 146 | ``` |
||
| 147 | |||
| 148 | It checks the checksums and that the backup belongs to that site, then |
||
| 149 | puts back database and files (including that site's own `wp-config.php`). |
||
| 150 | |||
| 151 | ## 7. Troubleshooting |
||
| 152 | |||
| 153 | | Symptom | Cause | Fix | |
||
| 154 | |---|---|---| |
||
| 155 | | `/tr/`, `/ar/` (or `/projects/...`) redirect to an unrelated page, e.g. `/tr/` → `/projects/trauma-simulators/`, header `x-redirect-by: WordPress` | WordPress rewrite rules were built while Polylang (or the theme) was not loaded, so the language prefixes are missing; WordPress then "guesses" a post whose slug starts with `tr` | wp-admin > **Settings > Permalinks > Save Changes** (change nothing), or `mds-theme.sh wp <site> rewrite flush`. Never flush with `--skip-plugins`/`--skip-themes`. The script does this itself since 1.0 and warns if a language root does not answer 200. | |
||
| 156 | | The browser shows `mdsdefense.com` without `www` | Chrome/Edge hide `https://` and `www.` in the address bar | Click into the address bar, or right-click > "Always show full URLs". Check the real answer with `curl.exe -sv -o NUL https://www.mdsdefense.com/` from a PC: `Server: openresty` and no `location:` = dmz-web, no redirect. | |
||
| 157 | | `curl https://www.<domain>/` hangs **on dmz-web** | no hairpin NAT: the host cannot reach its own public IP | Test locally through NPM: `curl -skI --resolve www.<domain>:444:127.0.0.1 https://www.<domain>:444/` | |
||
| 158 | | Links point to the bare domain (old site) | the target was installed as `https://<domain>` | Copy with `--target-url=https://www.<domain>`; check `mds-theme.sh wp <site> option get home` / `siteurl` | |
||
| 159 | | Final check: HTTP 000 or 502 | NPM has no proxy host / certificate for the target domain, or the site container is down | NPM admin (:8181): proxy host + certificate; `podman ps` | |
||
| 160 | |||
| 161 | ## 8. Housekeeping |
||
| 162 | |||
| 163 | Backups are not deleted automatically. Each holds a full database dump |
||
| 164 | and the whole web root (uploads included). Remove old ones by hand when |
||
| 165 | the copy has been checked: |
||
| 166 | |||
| 167 | ``` |
||
| 168 | du -sh ~/backups/stg2prod/* |
||
| 169 | rm -r ~/backups/stg2prod/<target>-<timestamp> |
||
| 170 | ``` |
||
| 171 | |||
| 172 | ## Version history |
||
| 173 | |||
| 174 | | Version | Date | Notes | |
||
| 175 | |---|---|---| |
||
| 176 | | 1.0 | 2026-11-05 | First release: copy with backup, maintenance mode, URL rewrite, rewrite rules rebuilt with plugins + theme loaded, NPM check incl. language roots; `--dry-run`, `--noindex`, `--keep-noindex`, `--target-url`, `--restore`. | |