Project

General

Profile

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`. |