Project

General

Profile

Actions

aslan-stg2prod.sh 1.0: Install and Use

Copies a WordPress site (database + files) from one site stack on dmz-web
to another, e.g. staging → production. Works for every pair built by
add-wordpress-site.sh: you give the two site names, the script reads
everything else from the sites themselves.

Runs as websvcmaster. Changes nothing outside the two sites' containers
and ~websvcmaster/backups/.

1. Install

As root (file uploaded to /root):

install -o websvcmaster -g websvcmaster -m 0755 /root/aslan-stg2prod.sh ~websvcmaster/deploy/aslan-stg2prod.sh

As websvcmaster:

cd ~/deploy
./aslan-stg2prod.sh --version        # aslan-stg2prod.sh 1.0
./aslan-stg2prod.sh --help

Commit it to the dmz-web-deploy repository (root):

deploy-commit -m "aslan-stg2prod.sh 1.0: copy a WordPress site between site stacks"

2. What it needs

Item Expected
Containers site-<name> and db-<name> of both sites running
Web root a volume mounted at /var/www/html in site-<name> (found automatically)
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)
Database MariaDB image with mariadb / mariadb-dump (mariadb:11)
Table prefix the same on both sites (otherwise it stops)
NPM a proxy host + certificate for the target's domain, for the final check

It does not create proxy hosts, certificates or DNS records, and
does not touch nginx-proxy-manager.container or any Quadlet file.

3. Commands

aslan-stg2prod.sh <source> <target> --dry-run      # read, check, show plan; changes nothing
aslan-stg2prod.sh <source> <target>                # asks you to type the target name
aslan-stg2prod.sh <source> <target> -y             # no question (scripts only)
aslan-stg2prod.sh <source> <target> --noindex      # target: "discourage search engines" on
aslan-stg2prod.sh <source> <target> --keep-noindex # target gets the source's setting
aslan-stg2prod.sh <source> <target> --target-url=https://www.example.com
                                                   # address the copy gets (default: target's siteurl)
aslan-stg2prod.sh --restore <target> <backup-dir>  # put the target back

Site names are the ones in the unit names: site-staging → staging,
site-mdsdefense → mdsdefense.

What a copy does:

  1. Reads both sites: URLs (siteurl, home), database name, user, host,
    table prefix, WordPress version, active theme, search engine setting,
    published content counts, file sizes. Shows them and the plan.
  2. Backs up the target to ~/backups/stg2prod/<target>-<timestamp>/
    (db.sql, files.tar, INFO, SHA256SUMS; mode 700).
  3. Puts the target in maintenance mode.
  4. Replaces the target's database with the source's.
  5. Replaces the target's files with the source's; the target's
    wp-config.php stays (it holds the target's DB host and credentials).
  6. Rewrites the source URL to the target URL in the whole database
    (serialized data and JSON-escaped \/\/ included), sets
    siteurl/home, allows search engines (--noindex / --keep-noindex
    change that),
    flushes transients and cache, and rebuilds the rewrite rules with
    plugins and theme loaded
    (see Troubleshooting). Reports how many times the
    source host is still in the database (e.g. e-mail addresses, which are
    left alone on purpose).
  7. Ends maintenance mode and checks the target through NPM: home page
    HTTP 200 with no source host on it, and every Polylang language root
    (/tr/, /ar/...) HTTP 200 without a redirect.

If a step fails, the target stays in maintenance mode and the script
prints the exact --restore command. Every run is logged to
~/.local/state/aslan-stg2prod.log.

Database passwords are never on a command line: they go to the DB
container on stdin.

4. MDS go-live: staging → www.mdsdefense.com

As websvcmaster in ~/deploy:

  1. Theme: ./mds-theme.sh status shows what staging runs. Nothing to
    install on production first: the copy brings the theme files along.

  2. Dry run. The bare domain mdsdefense.com still points to the old
    site, so production must run as www: give it explicitly, whatever
    the fresh install was set up with:

    ./aslan-stg2prod.sh staging mdsdefense --target-url=https://www.mdsdefense.com --dry-run
    

    Check in the output: source https://staging.mdsdefense.com, target
    URL https://www.mdsdefense.com, same table prefix, target database is
    the production one (db-mdsdefense), counts look right.

  3. Copy:

    ./aslan-stg2prod.sh staging mdsdefense --target-url=https://www.mdsdefense.com
    

    Type mdsdefense when asked.

  4. Check (browser, private window):

    • https://www.mdsdefense.com/, /tr/, /ar/: home, menu, footer,
      language switcher
    • Projects, Press Room, Contact (form sends), one Standard Page per
      language
    • wp-admin login on www (same users as staging)
    • Settings > Reading: "Discourage search engines" off
    • Media → Add New shows the 64 MB limit (.htaccess came along)
    • ./mds-theme.sh status: prod shows the same theme version as staging
  5. From now on: content is edited on production only. Staging is
    for theme tests.

5. Later: refresh staging from production

The same script works the other way; keep staging out of search engines:

./aslan-stg2prod.sh mdsdefense staging --dry-run --noindex
./aslan-stg2prod.sh mdsdefense staging --noindex

--noindex keeps staging out of search engines whatever production says.
Staging's own wp-config.php stays, so it keeps using its own database.

6. Restore

ls -1t ~/backups/stg2prod/                 # newest first
./aslan-stg2prod.sh --restore mdsdefense ~/backups/stg2prod/mdsdefense-YYYYMMDD-HHMMSS

It checks the checksums and that the backup belongs to that site, then
puts back database and files (including that site's own wp-config.php).

7. Troubleshooting

Symptom Cause Fix
/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.
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.
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/
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
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

8. Housekeeping

Backups are not deleted automatically. Each holds a full database dump
and the whole web root (uploads included). Remove old ones by hand when
the copy has been checked:

du -sh ~/backups/stg2prod/*
rm -r ~/backups/stg2prod/<target>-<timestamp>

Version history

Version Date Notes
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.

Updated by Kerem ERSOY about 23 hours ago · 1 revisions