wordmove-ng 6.0.0 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/pages.yml +52 -0
- data/.github/workflows/release.yml +15 -2
- data/.gitignore +2 -0
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +15 -1
- data/CLAUDE.md +9 -1
- data/README.md +26 -9
- data/contrib/wordmove-sync.sh +73 -0
- data/docs/Gemfile +4 -0
- data/docs/README.md +10 -0
- data/docs/_config.yml +47 -0
- data/docs/assets/images/wordmove-ng.png +0 -0
- data/docs/automation.md +166 -0
- data/docs/configuration.md +345 -0
- data/docs/contributing.md +55 -0
- data/docs/database-sync.md +91 -0
- data/docs/environments.md +65 -0
- data/docs/hooks.md +103 -0
- data/docs/index.md +59 -0
- data/docs/installation.md +104 -0
- data/docs/quick-start.md +81 -0
- data/docs/troubleshooting.md +141 -0
- data/docs/upgrading.md +61 -0
- data/docs/usage.md +129 -0
- data/lib/wordmove/version.rb +1 -1
- data/wordmove-ng.gemspec +1 -1
- metadata +20 -2
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Configuration
|
|
3
|
+
nav_order: 5
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Configuration
|
|
7
|
+
{: .no_toc }
|
|
8
|
+
|
|
9
|
+
`movefile.yml` describes your local install and every remote. It is a YAML file, evaluated as an ERB template first, so it can read environment variables.
|
|
10
|
+
|
|
11
|
+
1. TOC
|
|
12
|
+
{:toc}
|
|
13
|
+
|
|
14
|
+
## File name and location
|
|
15
|
+
|
|
16
|
+
wordmove-ng looks for `movefile.yml`, `movefile.yaml`, `movefile` or `Movefile` in the current directory and then in each parent up to the one containing `wp-config.php`. `-c other.yml` selects another file inside the project.
|
|
17
|
+
|
|
18
|
+
{: .note }
|
|
19
|
+
Indentation defines the structure. Two spaces per level, no tabs. `wordmove-ng doctor` validates the file against a schema and tells you what is wrong.
|
|
20
|
+
|
|
21
|
+
## Complete example
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
global:
|
|
25
|
+
maintenance_mode: false
|
|
26
|
+
|
|
27
|
+
local:
|
|
28
|
+
vhost: http://site.test
|
|
29
|
+
wordpress_path: /home/john/sites/site
|
|
30
|
+
database:
|
|
31
|
+
name: site
|
|
32
|
+
user: root
|
|
33
|
+
password: root
|
|
34
|
+
host: 127.0.0.1
|
|
35
|
+
# port: 3306
|
|
36
|
+
# socket: /path/to/mysqld.sock
|
|
37
|
+
|
|
38
|
+
staging:
|
|
39
|
+
vhost: https://staging.example.com
|
|
40
|
+
wordpress_path: /var/www/staging
|
|
41
|
+
database:
|
|
42
|
+
name: staging
|
|
43
|
+
user: staging
|
|
44
|
+
password: "<%= ENV['STAGING_DB_PASS'] %>"
|
|
45
|
+
host: localhost
|
|
46
|
+
ssh:
|
|
47
|
+
host: staging.example.com
|
|
48
|
+
user: deploy
|
|
49
|
+
exclude:
|
|
50
|
+
- ".git/"
|
|
51
|
+
- ".env"
|
|
52
|
+
- "node_modules/"
|
|
53
|
+
- "wp-config.php"
|
|
54
|
+
- "wp-content/*.sql.gz"
|
|
55
|
+
|
|
56
|
+
production:
|
|
57
|
+
vhost: https://example.com
|
|
58
|
+
wordpress_path: /var/www/example
|
|
59
|
+
database:
|
|
60
|
+
name: example
|
|
61
|
+
user: example
|
|
62
|
+
password: "<%= ENV['PROD_DB_PASS'] %>"
|
|
63
|
+
host: localhost
|
|
64
|
+
# mysqldump_options: --max_allowed_packet=50MB
|
|
65
|
+
# mysql_options: --protocol=TCP
|
|
66
|
+
ssh:
|
|
67
|
+
host: example.com
|
|
68
|
+
user: deploy
|
|
69
|
+
# port: 22
|
|
70
|
+
# password: only with sshpass installed; prefer keys
|
|
71
|
+
# rsync_options: --verbose
|
|
72
|
+
# gateway:
|
|
73
|
+
# host: bastion.example.com
|
|
74
|
+
# user: jump
|
|
75
|
+
exclude:
|
|
76
|
+
- ".git/"
|
|
77
|
+
- ".env"
|
|
78
|
+
- "node_modules/"
|
|
79
|
+
- "wp-config.php"
|
|
80
|
+
- "wp-content/*.sql.gz"
|
|
81
|
+
forbid:
|
|
82
|
+
push:
|
|
83
|
+
db: true # never push the database to production
|
|
84
|
+
hooks:
|
|
85
|
+
push:
|
|
86
|
+
after:
|
|
87
|
+
- command: wp cache flush
|
|
88
|
+
where: remote
|
|
89
|
+
raise: false
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## First level keys
|
|
93
|
+
|
|
94
|
+
`local` is mandatory and must keep that name. `global` is optional. Every other first level key is a remote environment, selected with `-e`. See [Multiple environments]({{ site.baseurl }}/environments/).
|
|
95
|
+
|
|
96
|
+
## `global`
|
|
97
|
+
|
|
98
|
+
| Key | Default | Meaning |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `maintenance_mode` | `false` | Wrap the target's database import and adaptation in `wp maintenance-mode activate`/`deactivate`. `WORDMOVE_MAINTENANCE_MODE=1` in the environment forces it on for one run. |
|
|
101
|
+
| `collation_fallbacks` | built-in table | Collations rewritten before import, e.g. `utf8mb3_uca1400_ai_ci: utf8mb4_unicode_ci`. Setting the key replaces the default table. |
|
|
102
|
+
| `charset_fallbacks` | `utf8mb3: utf8mb4` | Charsets rewritten before import. |
|
|
103
|
+
| `sql_adapter` | ignored | Accepted with a warning for 5.x movefiles. |
|
|
104
|
+
|
|
105
|
+
## `local` and each remote
|
|
106
|
+
|
|
107
|
+
### `vhost`
|
|
108
|
+
{: .no_toc }
|
|
109
|
+
|
|
110
|
+
> mandatory
|
|
111
|
+
|
|
112
|
+
The URL you use to reach the site, including any subdirectory WordPress is installed in. It should match `wp option get home`. Omit the trailing slash. `init` pre-fills it from `wp-config.php` or `wp option get home`.
|
|
113
|
+
|
|
114
|
+
### `wordpress_path`
|
|
115
|
+
{: .no_toc }
|
|
116
|
+
|
|
117
|
+
> mandatory
|
|
118
|
+
|
|
119
|
+
The absolute path of the installation. Spaces and other shell characters are handled; no manual escaping. On a remote it is the path **on that host**.
|
|
120
|
+
|
|
121
|
+
{: .warning }
|
|
122
|
+
Avoid values where one environment's `vhost` or `wordpress_path` is a prefix of another's (`https://site.test` and `https://site.test.example.com`): the search-replace of the shorter value would also rewrite the longer one. The doctor reports this.
|
|
123
|
+
|
|
124
|
+
### `database`
|
|
125
|
+
{: .no_toc }
|
|
126
|
+
|
|
127
|
+
> mandatory keys: `name`, `user`, `password`, `host`
|
|
128
|
+
|
|
129
|
+
| Key | Meaning |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `name`, `user`, `password`, `host` | as in `wp-config.php`. For a remote, as seen **from the remote host**: wordmove-ng connects over SSH first and runs `mysqldump`/`mysql`/`wp` there. |
|
|
132
|
+
| `port` | optional; `init` splits `DB_HOST` values like `localhost:3307`. |
|
|
133
|
+
| `socket` | optional unix socket; `init` splits `DB_HOST` values like `localhost:/path/mysqld.sock`. |
|
|
134
|
+
| `mysqldump_options` | passed verbatim to `mysqldump`/`mariadb-dump`, e.g. `--max_allowed_packet=50MB --ignore-table=db.wp_bigtable`. |
|
|
135
|
+
| `mysql_options` | passed verbatim to `mysql`/`mariadb` on import, e.g. `--protocol=TCP`. |
|
|
136
|
+
|
|
137
|
+
`mariadb`/`mariadb-dump` are preferred when installed, falling back to `mysql`/`mysqldump`. Imports run with `--binary-mode` (unless `mysql_options` already sets it), `SET FOREIGN_KEY_CHECKS=0` and a trailing `COMMIT;`; a MariaDB sandbox header is stripped.
|
|
138
|
+
|
|
139
|
+
### `paths`
|
|
140
|
+
{: .no_toc }
|
|
141
|
+
|
|
142
|
+
> optional
|
|
143
|
+
|
|
144
|
+
Customise WordPress internal paths when they are not the defaults, relative to `wordpress_path`. Configure it on every environment whose layout differs.
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
paths:
|
|
148
|
+
wp_content: app
|
|
149
|
+
wp_config: config/wp-config.php
|
|
150
|
+
uploads: app/uploads
|
|
151
|
+
plugins: app/plugins
|
|
152
|
+
mu_plugins: app/mu-plugins
|
|
153
|
+
themes: app/themes
|
|
154
|
+
languages: app/languages
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Remote-only keys
|
|
158
|
+
|
|
159
|
+
### `ssh`
|
|
160
|
+
{: .no_toc }
|
|
161
|
+
|
|
162
|
+
> mandatory key: `host`
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
ssh:
|
|
166
|
+
host: host
|
|
167
|
+
user: user
|
|
168
|
+
port: 22 # optional
|
|
169
|
+
password: secret # optional; needs sshpass, prefer keys
|
|
170
|
+
rsync_options: "--verbose" # optional, appended to rsync
|
|
171
|
+
gateway: # optional jump host, becomes `ssh -J`
|
|
172
|
+
host: bastion
|
|
173
|
+
user: jump
|
|
174
|
+
port: 22
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Every operation uses the **system `ssh` client**: rsync for directories, `ssh` for remote commands and hooks, `scp` for dump files. Whatever works for `ssh` on your command line works here: ssh-agent, `~/.ssh/config`, `ProxyJump`, hardware keys, modern key types. Only `host` is mandatory because the rest can live in `~/.ssh/config`:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
Host client_x_staging
|
|
181
|
+
Hostname 79.99.8.94
|
|
182
|
+
User welaika
|
|
183
|
+
Port 1337
|
|
184
|
+
ProxyJump bastion
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
staging:
|
|
189
|
+
ssh:
|
|
190
|
+
host: client_x_staging
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- Without `password`, connections run with `BatchMode=yes`: a failing key authentication is an error naming the failed command, never a prompt.
|
|
194
|
+
- `password` is passed through `sshpass -p` to `ssh`, `scp` and `rsync`. It requires `sshpass` locally and exposes the password in the process list.
|
|
195
|
+
- `rsync_options` are appended to the defaults `-rlpt --compress --omit-dir-times --delete`. See [rsync options for CI]({{ site.baseurl }}/usage/#rsync-options-in-a-continuous-delivery-context).
|
|
196
|
+
- `gateway` becomes `ssh -J [user@]host[:port]`. A `gateway.password` cannot be honoured and is ignored; the doctor warns.
|
|
197
|
+
- Remote commands always run through `sh -c`, so the remote login shell can be bash, zsh, fish or csh. Programs only need to be in the `$PATH` of a non interactive login.
|
|
198
|
+
|
|
199
|
+
### `exclude`
|
|
200
|
+
{: .no_toc }
|
|
201
|
+
|
|
202
|
+
> optional
|
|
203
|
+
|
|
204
|
+
Patterns ignored during `push` and `pull`, which also means matching files are **not deleted** on the destination when missing at the source. Each entry becomes an rsync `--exclude`, relative to `wordpress_path`, so all rsync pattern syntax applies; see [working on specific plugins]({{ site.baseurl }}/usage/#work-only-on-specific-plugins-or-themes).
|
|
205
|
+
|
|
206
|
+
```yaml
|
|
207
|
+
exclude:
|
|
208
|
+
- ".git/"
|
|
209
|
+
- ".gitignore"
|
|
210
|
+
- ".env"
|
|
211
|
+
- "node_modules/"
|
|
212
|
+
- "tmp/*"
|
|
213
|
+
- "movefile.yml"
|
|
214
|
+
- "wp-config.php"
|
|
215
|
+
- "wp-content/*.sql.gz"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `hooks`
|
|
219
|
+
{: .no_toc }
|
|
220
|
+
|
|
221
|
+
> optional
|
|
222
|
+
|
|
223
|
+
Commands run before or after a push or pull, locally or on the remote. See [Hooks]({{ site.baseurl }}/hooks/).
|
|
224
|
+
|
|
225
|
+
### `forbid`
|
|
226
|
+
{: .no_toc }
|
|
227
|
+
|
|
228
|
+
> optional
|
|
229
|
+
|
|
230
|
+
Blocks actions by configuration; forbidden tasks ignore command line flags and are skipped with a warning. Values must be booleans.
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
forbid:
|
|
234
|
+
push:
|
|
235
|
+
db: true
|
|
236
|
+
uploads: false
|
|
237
|
+
plugins: false
|
|
238
|
+
themes: false
|
|
239
|
+
languages: false
|
|
240
|
+
mu_plugins: false
|
|
241
|
+
pull:
|
|
242
|
+
db: false
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Removed: `ftp`
|
|
246
|
+
{: .no_toc }
|
|
247
|
+
|
|
248
|
+
FTP, FTPS and SFTP transports were removed in 6.0. A remote with an `ftp` block fails validation with a clear message. See [Upgrading]({{ site.baseurl }}/upgrading/#4-ftp-and-sftp-are-gone).
|
|
249
|
+
|
|
250
|
+
## Environment variables and `.env`
|
|
251
|
+
|
|
252
|
+
Because the movefile is an ERB template, secrets can come from the environment:
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
production:
|
|
256
|
+
database:
|
|
257
|
+
user: "<%= ENV['PROD_DB_USER'] %>"
|
|
258
|
+
password: "<%= ENV['PROD_DB_PASS'] %>"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Set them in the shell (`export PROD_DB_PASS="…"`) or in a `.env` file next to the movefile:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
PROD_DB_USER="username"
|
|
265
|
+
PROD_DB_PASS="password"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`.env.<environment>` (for example `.env.production`) is loaded when that environment is selected with `-e`. Add `.env*` to `exclude` and to `.gitignore`.
|
|
269
|
+
|
|
270
|
+
System variables work too: `wordpress_path: "<%= ENV['HOME'] %>/sites/example"`.
|
|
271
|
+
|
|
272
|
+
## YAML and ERB tips
|
|
273
|
+
|
|
274
|
+
### Anchors to avoid repetition
|
|
275
|
+
{: .no_toc }
|
|
276
|
+
|
|
277
|
+
```yaml
|
|
278
|
+
global:
|
|
279
|
+
default: &default
|
|
280
|
+
vhost: "http://example.com"
|
|
281
|
+
wordpress_path: "/var/www/site"
|
|
282
|
+
database: &db
|
|
283
|
+
host: localhost
|
|
284
|
+
user: username
|
|
285
|
+
password: password
|
|
286
|
+
name: database
|
|
287
|
+
ssh: &ssh
|
|
288
|
+
host: server
|
|
289
|
+
user: foo
|
|
290
|
+
|
|
291
|
+
staging:
|
|
292
|
+
<<: *default
|
|
293
|
+
vhost: http://staging.example.com
|
|
294
|
+
database:
|
|
295
|
+
<<: *db
|
|
296
|
+
name: db_staging
|
|
297
|
+
ssh:
|
|
298
|
+
<<: *ssh
|
|
299
|
+
user: foobar
|
|
300
|
+
|
|
301
|
+
production:
|
|
302
|
+
<<: *default
|
|
303
|
+
vhost: https://www.example.com
|
|
304
|
+
database:
|
|
305
|
+
<<: *db
|
|
306
|
+
name: db_production
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The `default` anchor lives under `global` because every other first level key is treated as an environment.
|
|
310
|
+
|
|
311
|
+
### Variables inside the template
|
|
312
|
+
{: .no_toc }
|
|
313
|
+
|
|
314
|
+
```yaml
|
|
315
|
+
production:
|
|
316
|
+
<% prod_wp_path = "/home/site/public" %>
|
|
317
|
+
vhost: "https://example.com"
|
|
318
|
+
wordpress_path: <%= prod_wp_path %>
|
|
319
|
+
hooks:
|
|
320
|
+
pull:
|
|
321
|
+
after:
|
|
322
|
+
- command: 'cd <%= prod_wp_path %> && wp core version'
|
|
323
|
+
where: remote
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Conditional sections
|
|
327
|
+
{: .no_toc }
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
hooks:
|
|
331
|
+
pull:
|
|
332
|
+
after:
|
|
333
|
+
<% if ENV.fetch('NOTIFY', nil) %>
|
|
334
|
+
- command: 'bash ./scripts/notify.sh'
|
|
335
|
+
where: local
|
|
336
|
+
<% end %>
|
|
337
|
+
- command: 'wp cache flush'
|
|
338
|
+
where: remote
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`ENV.fetch('NOTIFY', nil)` returns the variable or `nil`; you can also compare against values (`<% if ENV.fetch('TARGET', '') == 'blue' %>`).
|
|
342
|
+
|
|
343
|
+
## Schema validation
|
|
344
|
+
|
|
345
|
+
`wordmove-ng doctor` validates every section against the schemas in [`lib/wordmove/assets`](https://github.com/tekgnosis-net/wordmove-ng/tree/master/lib/wordmove/assets), reports unknown or mistyped keys, rejects `ftp` blocks and flags prefix collisions between search terms.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Contributing and releasing
|
|
3
|
+
nav_order: 12
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Contributing and releasing
|
|
7
|
+
{: .no_toc }
|
|
8
|
+
|
|
9
|
+
1. TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
## Reporting a bug
|
|
13
|
+
|
|
14
|
+
Run `wordmove-ng doctor` first and include its output. Then open an issue with the command you ran, the relevant part of `movefile.yml` with secrets removed, the full error output, and your OS, Ruby (`ruby --version`) and wordmove-ng (`wordmove-ng --version`) versions. Only the latest release is supported; bugs in the legacy `wordmove` 5.x gem belong to its own tracker.
|
|
15
|
+
|
|
16
|
+
## Development
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/tekgnosis-net/wordmove-ng.git
|
|
20
|
+
cd wordmove-ng
|
|
21
|
+
bundle install
|
|
22
|
+
bundle exec rake # specs + rubocop, what CI runs
|
|
23
|
+
bundle exec rspec spec/deployer/ssh_db_spec.rb # one file
|
|
24
|
+
bin/wordmove-ng --help # run from the checkout, from any directory
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
CI runs the suite and rubocop on Ruby 3.0 through 4.0. Code must run on all of them. New rubocop offences are fixed rather than added to `.rubocop_todo.yml`, which only grandfathers the pre-6.0 backlog.
|
|
28
|
+
|
|
29
|
+
Repository layout, request flow and conventions are described in [`CLAUDE.md`](https://github.com/tekgnosis-net/wordmove-ng/blob/master/CLAUDE.md).
|
|
30
|
+
|
|
31
|
+
## Commit messages
|
|
32
|
+
|
|
33
|
+
Write [Conventional Commits](https://www.conventionalcommits.org): `fix: …` (patch), `feat: …` (minor), `feat!: …` or a `BREAKING CHANGE:` footer (major), and `docs:`, `chore:`, `ci:`, `test:`, `refactor:` for everything that does not ship a change. The version number and `CHANGELOG.md` are generated from them; never edit `lib/wordmove/version.rb` or the changelog by hand.
|
|
34
|
+
|
|
35
|
+
## Releasing
|
|
36
|
+
|
|
37
|
+
Releases are automated by [release-please](https://github.com/googleapis/release-please) and published to [rubygems.org](https://rubygems.org/gems/wordmove-ng) with [trusted publishing](https://guides.rubygems.org/trusted-publishing/), so no API key is stored anywhere.
|
|
38
|
+
|
|
39
|
+
1. Every push to `master` updates a "release PR" that bumps the version and the changelog from the commits since the last release. release-please proposes a patch release for any Conventional Commit, `ci:` and `docs:` included, so merge the PR only when there is something worth shipping.
|
|
40
|
+
2. Merging it creates the `vX.Y.Z` tag and the GitHub release.
|
|
41
|
+
3. The `publish` job in `.github/workflows/release.yml` re-runs the suite on the tagged commit, builds the gem, pushes it to rubygems.org and attaches the `.gem` file to the GitHub release.
|
|
42
|
+
|
|
43
|
+
Pushing a `v*` tag by hand at the head of `master` triggers the same publish job.
|
|
44
|
+
|
|
45
|
+
`master` is protected: changes land through pull requests whose `test` checks pass, and the rules apply to administrators too. release-please opens its PR with a fine-grained personal access token (secret `RELEASE_PLEASE_TOKEN`) because PRs opened with the built-in Actions token never trigger the checks.
|
|
46
|
+
|
|
47
|
+
## This documentation
|
|
48
|
+
|
|
49
|
+
The site is built from the `docs/` folder on `master` with Jekyll and the just-the-docs theme by `.github/workflows/pages.yml`. Preview locally with
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd docs && bundle install && bundle exec jekyll serve
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Please keep it, and the README, updated when changing user-facing behaviour.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Database sync
|
|
3
|
+
nav_order: 7
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Database sync
|
|
7
|
+
{: .no_toc }
|
|
8
|
+
|
|
9
|
+
1. TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
## What happens on `-d`
|
|
13
|
+
|
|
14
|
+
Both directions have the same shape. The **source** database is only ever read.
|
|
15
|
+
|
|
16
|
+
| Step | `pull -e production -d` | `push -e production -d` |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| 1. Prerequisites | probe local for `gzip`, `mysql`, `wp`; remote for `gzip`, `mysqldump` | probe local for `gzip`, `mysqldump`; remote for `gzip`, `mysql`, `wp` |
|
|
19
|
+
| 2. Backup the target | local DB to `wp-content/local-backup-<ts>.sql.gz` | remote DB downloaded to `wp-content/production-backup-<ts>.sql.gz` |
|
|
20
|
+
| 3. Dump the source | remote `mysqldump`, gzip, `scp` down | local `mysqldump`, normalise collations, gzip, `scp` up |
|
|
21
|
+
| 4. Import on the target | `mysql` locally | `mysql` on the remote over SSH |
|
|
22
|
+
| 5. Adapt on the target | `wp search-replace` locally for `vhost`, then `wordpress_path` | `wp search-replace` on the remote for `vhost`, then `wordpress_path` |
|
|
23
|
+
| 6. Clean up | temp dumps removed | temp dumps removed |
|
|
24
|
+
|
|
25
|
+
Step 1 aborts with a list of missing programs before anything else happens, so a misconfigured host never leaves a half-done sync.
|
|
26
|
+
|
|
27
|
+
`wp search-replace` runs with `--all-tables --skip-columns=guid --allow-root`, so every table in the target database is adapted, including non-WordPress tables sharing it, and post GUIDs are left alone as WordPress recommends. wp-cli unserializes PHP data properly, so serialized options, widgets and page-builder data survive. `--no-adapt` skips step 5.
|
|
28
|
+
|
|
29
|
+
## Backups and recovery
|
|
30
|
+
|
|
31
|
+
Backups are gzipped SQL files in the local `wp-content/` folder, named after the target environment and a Unix timestamp. To restore one:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gunzip < wp-content/production-backup-1757400000.sql.gz | mysql -u user -p example # on the remote
|
|
35
|
+
gunzip < wp-content/local-backup-1757400000.sql.gz | mysql -u root -p site # locally
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If the remote `wp search-replace` fails after the import, the log names the backup file and the remote is left with the local URLs. Either restore, or re-run `wordmove-ng push -d`, or run the search-replace by hand on the remote:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
wp search-replace 'http://site.test' 'https://example.com' --all-tables --skip-columns=guid --path=/var/www/example
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Add `wp-content/*.sql.gz` to `exclude` so backups are never synced.
|
|
45
|
+
|
|
46
|
+
## Maintenance mode
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
global:
|
|
50
|
+
maintenance_mode: true
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
wraps steps 4 and 5 on the target in `wp maintenance-mode activate` and `deactivate`, so visitors see the WordPress maintenance page rather than a site pointing at the other environment's URLs for a few seconds. Deactivation runs even when the adaptation fails. `WORDMOVE_MAINTENANCE_MODE=1` forces it on for a single run. Default: off.
|
|
54
|
+
|
|
55
|
+
## MariaDB and MySQL compatibility
|
|
56
|
+
|
|
57
|
+
- `mariadb` and `mariadb-dump` are preferred when present, falling back to `mysql` and `mysqldump`, on each side independently.
|
|
58
|
+
- Dumps starting with the MariaDB sandbox header `/*!999999- enable the sandbox mode */` import cleanly on servers that do not understand it.
|
|
59
|
+
- Imports run with `--binary-mode`, `SET FOREIGN_KEY_CHECKS=0` and a trailing `COMMIT;`, unless `mysql_options` already sets binary mode.
|
|
60
|
+
- `database.socket` and `database.port` are first-class; `--socket` inside `mysql_options` or `mysqldump_options` still works.
|
|
61
|
+
- Newer `utf8mb3` collations (`utf8mb3_uca1400_*`, `utf8mb3_unicode_520_ci`) are rewritten to `utf8mb4_unicode_ci`, and `utf8mb3` to `utf8mb4`, before import. Override or extend the mappings:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
global:
|
|
65
|
+
collation_fallbacks:
|
|
66
|
+
utf8mb3_uca1400_ai_ci: utf8mb4_unicode_ci
|
|
67
|
+
charset_fallbacks:
|
|
68
|
+
utf8mb3: utf8mb4
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `wp` is always called with `--allow-root`, so root-owned Docker installs work.
|
|
72
|
+
|
|
73
|
+
## Large databases
|
|
74
|
+
|
|
75
|
+
Pass options straight to the dump and import tools:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
database:
|
|
79
|
+
mysqldump_options: "--max_allowed_packet=1G --single-transaction --quick --ignore-table=example.wp_actionscheduler_logs"
|
|
80
|
+
mysql_options: "--max_allowed_packet=1G"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`--hex-blob` in `mysqldump_options` makes binary columns safe to move between servers with different defaults.
|
|
84
|
+
|
|
85
|
+
## Table prefixes
|
|
86
|
+
|
|
87
|
+
Local and remote must use the same table prefix. `wp search-replace --all-tables` rewrites values, not table names.
|
|
88
|
+
|
|
89
|
+
## Prefix collisions
|
|
90
|
+
|
|
91
|
+
If one of the four search terms (local and remote `vhost` and `wordpress_path`) is a prefix of another, for example `https://site.test` and `https://site.test.example.com`, the replacement of the shorter value also rewrites the longer one. The doctor reports this as an error and the database step warns before running. Use distinct values.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Multiple environments
|
|
3
|
+
nav_order: 9
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Multiple environments
|
|
7
|
+
|
|
8
|
+
`local` is always your development environment and must keep that name. Every other first level key of `movefile.yml`, at the same indentation level, declares a remote. Here two remotes, `test` and `live`:
|
|
9
|
+
|
|
10
|
+
```yaml
|
|
11
|
+
local:
|
|
12
|
+
vhost: "http://site.test"
|
|
13
|
+
wordpress_path: "/Users/me/Sites/site"
|
|
14
|
+
database:
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
test:
|
|
18
|
+
vhost: "https://test.site.net"
|
|
19
|
+
wordpress_path: "/srv/test/www"
|
|
20
|
+
database:
|
|
21
|
+
...
|
|
22
|
+
ssh:
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
live:
|
|
26
|
+
vhost: "https://site.net"
|
|
27
|
+
wordpress_path: "/srv/live/www"
|
|
28
|
+
database:
|
|
29
|
+
...
|
|
30
|
+
ssh:
|
|
31
|
+
...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pick the remote with `-e`. With a single remote it is used by default and `-e` can be omitted; with several, omitting it is an error.
|
|
35
|
+
|
|
36
|
+
- `pull` always lands in `local`; `-e` names where the data comes from: `wordmove-ng pull -t -e live` copies themes from `live` to `local`.
|
|
37
|
+
- `push` always starts from `local`; `-e` names the destination: `wordmove-ng push -t -e test` copies themes from `local` to `test`.
|
|
38
|
+
|
|
39
|
+
You cannot sync two remotes directly; go through `local`.
|
|
40
|
+
|
|
41
|
+
## Backups per environment
|
|
42
|
+
|
|
43
|
+
Before a database import the target's database is saved locally, named after the target: `wp-content/local-backup-<timestamp>.sql.gz` on a pull, `wp-content/live-backup-<timestamp>.sql.gz` or `wp-content/test-backup-<timestamp>.sql.gz` on a push.
|
|
44
|
+
|
|
45
|
+
## Protecting an environment
|
|
46
|
+
|
|
47
|
+
Use `forbid` to make some operations impossible on a given remote regardless of flags, typically the production database:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
live:
|
|
51
|
+
forbid:
|
|
52
|
+
push:
|
|
53
|
+
db: true
|
|
54
|
+
uploads: true
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Sharing configuration between remotes
|
|
58
|
+
|
|
59
|
+
Use YAML anchors under `global` to avoid repeating blocks; see [Configuration]({{ site.baseurl }}/configuration/#yaml-and-erb-tips).
|
|
60
|
+
|
|
61
|
+
## Distinct values
|
|
62
|
+
|
|
63
|
+
Keep each environment's `vhost` and `wordpress_path` distinct and never a prefix of another's (`https://site.net` versus `https://site.net.staging`), otherwise the search-replace of the shorter value rewrites the longer one. `wordmove-ng doctor` reports such collisions.
|
|
64
|
+
|
|
65
|
+
Thanks to **@charmcat**, who shared her troubleshooting with the original authors and suggested this page.
|
data/docs/hooks.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hooks
|
|
3
|
+
nav_order: 8
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hooks
|
|
7
|
+
{: .no_toc }
|
|
8
|
+
|
|
9
|
+
Hooks run arbitrary commands before or after a `push` or `pull`, locally or on the remote.
|
|
10
|
+
|
|
11
|
+
1. TOC
|
|
12
|
+
{:toc}
|
|
13
|
+
|
|
14
|
+
## Syntax
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
production:
|
|
18
|
+
hooks:
|
|
19
|
+
push:
|
|
20
|
+
before:
|
|
21
|
+
- command: 'echo "do something"'
|
|
22
|
+
where: local
|
|
23
|
+
raise: false # raise is true by default
|
|
24
|
+
after:
|
|
25
|
+
- command: 'echo "do something"'
|
|
26
|
+
where: remote
|
|
27
|
+
pull:
|
|
28
|
+
before:
|
|
29
|
+
- command: 'echo "do something"'
|
|
30
|
+
where: local
|
|
31
|
+
after:
|
|
32
|
+
- command: 'echo "do something"'
|
|
33
|
+
where: remote
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `hooks` is optional and is configured per remote environment, never under `local`.
|
|
37
|
+
- Two groups, `push` and `pull`, each with `before` and `after`, each a sequence of command objects.
|
|
38
|
+
- `command`: the command to run. Quote it, preferably with single quotes, since double quotes are often used inside commands.
|
|
39
|
+
- `where`: `local` or `remote`. `remote` is always the environment given with `-e`, or the only remote when there is one.
|
|
40
|
+
- `raise`: default `true`. With `false`, a failing command (exit status > 0) is logged and the operation continues.
|
|
41
|
+
|
|
42
|
+
Hooks run regardless of which components you selected: `push --all` and `push -t` trigger the same hooks. `wordmove-ng doctor` validates them.
|
|
43
|
+
|
|
44
|
+
## Execution
|
|
45
|
+
|
|
46
|
+
- Both local and remote hooks run inside the respective `wordpress_path`. Change directory inside the command if needed: `cd tools && ./build.sh`.
|
|
47
|
+
- Hooks run in order, synchronously. What you read is what you get.
|
|
48
|
+
- Remote hooks use the same `ssh` settings as everything else (host, user, port, password, gateway) through the system `ssh` binary, and run inside `sh -c`, so the remote login shell does not matter. Programs must be in the `$PATH` of a non interactive login; if `wp` is not found, add its directory to `PATH` in `~/.bashrc` or `~/.profile` on the remote, or use an absolute path.
|
|
49
|
+
- Hook output is logged with movefile secrets masked as `[secret]`.
|
|
50
|
+
|
|
51
|
+
## Error handling
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
hooks:
|
|
55
|
+
push:
|
|
56
|
+
before:
|
|
57
|
+
- command: 'exit 1'
|
|
58
|
+
where: remote
|
|
59
|
+
raise: false
|
|
60
|
+
- command: 'echo "Still working"'
|
|
61
|
+
where: local
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The first command fails, the second still runs, and since these are before-push hooks the push happens too. The error is visible in the log. Without `raise: false` the failure stops everything.
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
hooks:
|
|
70
|
+
push:
|
|
71
|
+
before:
|
|
72
|
+
- command: 'npm run build' # build assets before pushing
|
|
73
|
+
where: local
|
|
74
|
+
- command: 'rm -rf ./tmp/*'
|
|
75
|
+
where: local
|
|
76
|
+
after:
|
|
77
|
+
- command: 'wp rewrite flush'
|
|
78
|
+
where: remote
|
|
79
|
+
- command: 'wp cache flush'
|
|
80
|
+
where: remote
|
|
81
|
+
- command: 'wp option set blog_public 0' # keep staging out of search engines
|
|
82
|
+
where: remote
|
|
83
|
+
- command: 'find . -type f -exec chmod 664 {} +' # fix permissions
|
|
84
|
+
where: remote
|
|
85
|
+
- command: 'find . -type d -exec chmod 755 {} +'
|
|
86
|
+
where: remote
|
|
87
|
+
- command: 'bash ./scripts/notify.sh' # tell the team
|
|
88
|
+
where: local
|
|
89
|
+
raise: false
|
|
90
|
+
pull:
|
|
91
|
+
after:
|
|
92
|
+
- command: 'wp option update siteurl http://site.test'
|
|
93
|
+
where: local
|
|
94
|
+
- command: 'wp plugin deactivate wordfence' # no security plugin locally
|
|
95
|
+
where: local
|
|
96
|
+
raise: false
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Maintenance mode around the database replacement is built in (`global.maintenance_mode`), so hooks are not needed for that.
|
|
100
|
+
|
|
101
|
+
## ERB inside hooks
|
|
102
|
+
|
|
103
|
+
Because the movefile is an ERB template, hooks can be conditional or share variables with the rest of the file. See [Configuration]({{ site.baseurl }}/configuration/#yaml-and-erb-tips).
|