Skip to content

Commit 6890f5c

Browse files
committed
Improved MySQL migration system
no ref - Currently we prompt a user for a username to dump their Ghost database with which can be confusing if users can't remember their database details - The Ghost user _likely_ has the required permissions to dump the database itself so we can first try that without prompting the user and _then_ prompt them if it doesn't work - We added `--no-tablespaces` to the dump command to try and reduce the likelyhood of hitting users not having the `PROCESS` permission since most hosted DBs don't let this and Ghost's DB setup doesn't require DBs be dumped with that anyway
1 parent fb8703d commit 6890f5c

2 files changed

Lines changed: 126 additions & 25 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ The repository includes comprehensive migration tools:
6666

6767
- `scripts/migrate.sh` - Main migration script that:
6868
- Backs up existing Ghost installation
69+
- Automatically tries Ghost's database credentials first
70+
- Only prompts for alternative credentials if needed
71+
- Uses `--no-tablespaces` flag to avoid PROCESS privilege requirements
6972
- Converts config.json to environment variables
7073
- Preserves content and database
71-
- Creates recovery script
74+
- Creates recovery script with clear restoration instructions
7275
- Sets up Docker Compose environment
7376

7477
- `scripts/config-to-env.js` - Converts Ghost JSON config to .env format

scripts/migrate.sh

Lines changed: 122 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,17 @@ cleanup() {
5252

5353
if [[ $exit_code -ne 0 && -f "$RECOVERY_SCRIPT" ]]; then
5454
echo ""
55-
echo "ERROR: Migration failed!"
55+
echo "════════════════════════════════════════════════════════════"
56+
echo "❌ MIGRATION FAILED!"
57+
echo "════════════════════════════════════════════════════════════"
58+
echo ""
59+
echo "Don't worry - your data is safe!"
60+
echo ""
5661
echo "To restore your original Ghost installation, run:"
5762
echo " bash $RECOVERY_SCRIPT"
5863
echo ""
64+
echo "Need help? Check the migration logs above for error details."
65+
echo "════════════════════════════════════════════════════════════"
5966
fi
6067

6168
exit $exit_code
@@ -66,6 +73,7 @@ trap cleanup EXIT INT TERM
6673

6774
# Create recovery script
6875
create_recovery_script() {
76+
local service_name="$1"
6977
cat > "$RECOVERY_SCRIPT" << EOF
7078
#!/usr/bin/env bash
7179
# Recovery script generated by Ghost migration on $(date)
@@ -79,15 +87,19 @@ echo "Restoring original Ghost installation..."
7987
docker compose down 2>/dev/null || true
8088
8189
# Re-enable and start the original Ghost service
82-
systemctl enable "${ghost_service_name}"
83-
systemctl start "${ghost_service_name}"
84-
85-
echo "Original Ghost installation has been restored."
86-
echo "You can check the status with: systemctl status ${ghost_service_name}"
90+
if [[ -n "${service_name}" ]]; then
91+
systemctl enable "${service_name}" 2>/dev/null || true
92+
systemctl start "${service_name}" 2>/dev/null || true
93+
echo "Original Ghost installation has been restored."
94+
echo "You can check the status with: systemctl status ${service_name}"
95+
else
96+
echo "Note: Ghost service was not yet stopped, so no restoration needed."
97+
echo "Your original installation should still be running."
98+
fi
8799
EOF
88100

89101
chmod +x "$RECOVERY_SCRIPT"
90-
echo "Recovery script created at: $RECOVERY_SCRIPT"
102+
echo "Recovery script created at: $RECOVERY_SCRIPT"
91103
}
92104

93105
# Validate MySQL connection
@@ -188,6 +200,21 @@ migrate_content() {
188200
echo "✓ Content migration completed"
189201
}
190202

203+
# Test if we can dump database with given credentials
204+
test_mysql_dump() {
205+
local host=$1
206+
local database=$2
207+
local user=$3
208+
local password=$4
209+
210+
# Try a minimal dump to test permissions
211+
if mysqldump --no-tablespaces --no-data -h"$host" -u"$user" -p"$password" "$database" >/dev/null 2>&1; then
212+
return 0
213+
else
214+
return 1
215+
fi
216+
}
217+
191218
# Export and import database
192219
migrate_database() {
193220
local mysql_host
@@ -197,9 +224,38 @@ migrate_database() {
197224

198225
echo "Exporting database from $mysql_host..."
199226

200-
# Export database
201-
if ! mysqldump -h"$mysql_host" -u"$mysql_user" -p"$mysql_password" "$mysql_database" > "$TEMP_SQL_FILE"; then
227+
# Export database with proper error handling
228+
local dump_output
229+
local dump_status
230+
dump_output=$(mysqldump --no-tablespaces -h"$mysql_host" -u"$mysql_user" -p"$mysql_password" "$mysql_database" 2>&1 > "$TEMP_SQL_FILE")
231+
dump_status=$?
232+
233+
# Check for errors in output (mysqldump may return 0 even with some errors)
234+
if [[ $dump_status -ne 0 ]] || [[ "$dump_output" =~ "Error:" ]]; then
235+
echo ""
202236
echo "ERROR: Failed to export database"
237+
if [[ "$dump_output" =~ "PROCESS privilege" ]]; then
238+
echo ""
239+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
240+
echo "The MySQL user '$mysql_user' needs the PROCESS privilege."
241+
echo ""
242+
echo "To fix this, connect to MySQL as a privileged user and run:"
243+
echo " GRANT PROCESS ON *.* TO '$mysql_user'@'%';"
244+
echo " FLUSH PRIVILEGES;"
245+
echo ""
246+
echo "Or retry with a user that has sufficient privileges (e.g., root)."
247+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
248+
elif [[ -n "$dump_output" ]]; then
249+
echo "Error details: $dump_output"
250+
fi
251+
exit 1
252+
fi
253+
254+
# Verify the dump file exists and has content
255+
if [[ ! -f "$TEMP_SQL_FILE" ]] || [[ ! -s "$TEMP_SQL_FILE" ]]; then
256+
echo ""
257+
echo "ERROR: Database dump file is empty or missing"
258+
echo "This might indicate insufficient disk space or permissions issues."
203259
exit 1
204260
fi
205261

@@ -286,8 +342,12 @@ main() {
286342
# Get database configuration
287343
local mysql_host
288344
local mysql_database
345+
local ghost_mysql_user
346+
local ghost_mysql_password
289347
mysql_host=$(jq -r < "${current_location}/config.production.json" '.database.connection.host')
290348
mysql_database=$(jq -r < "${current_location}/config.production.json" '.database.connection.database')
349+
ghost_mysql_user=$(jq -r < "${current_location}/config.production.json" '.database.connection.user')
350+
ghost_mysql_password=$(jq -r < "${current_location}/config.production.json" '.database.connection.password')
291351

292352
# Check disk space
293353
echo ""
@@ -321,23 +381,52 @@ main() {
321381
echo "✓ Disk space check passed"
322382
echo ""
323383

324-
# Get MySQL credentials and validate
325-
read -rp "MySQL user for database export (default: root): " mysql_user
326-
mysql_user=${mysql_user:-root}
384+
# Try Ghost's own credentials first
385+
echo "Testing database export with Ghost's credentials..."
386+
if test_mysql_dump "$mysql_host" "$mysql_database" "$ghost_mysql_user" "$ghost_mysql_password"; then
387+
echo "✓ Ghost's credentials have sufficient privileges"
388+
mysql_user="$ghost_mysql_user"
389+
mysql_password="$ghost_mysql_password"
390+
else
391+
echo "Ghost's database user doesn't have sufficient privileges for export."
392+
echo "Please provide credentials for a MySQL user with dump privileges."
393+
echo ""
327394

328-
# Get password securely
329-
echo -n "MySQL password for ${mysql_user}: "
330-
read -rs mysql_password
331-
echo ""
395+
# Get MySQL credentials and validate
396+
read -rp "MySQL user for database export (default: root): " mysql_user
397+
mysql_user=${mysql_user:-root}
332398

333-
# Validate connection
334-
if ! validate_mysql_connection "$mysql_host" "$mysql_database" "$mysql_user" "$mysql_password"; then
335-
echo "Please check your MySQL credentials and try again."
336-
exit 1
399+
# Get password securely
400+
echo -n "MySQL password for ${mysql_user}: "
401+
read -rs mysql_password
402+
echo ""
403+
404+
# Validate connection
405+
if ! validate_mysql_connection "$mysql_host" "$mysql_database" "$mysql_user" "$mysql_password"; then
406+
echo ""
407+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
408+
echo "Could not connect to MySQL database."
409+
echo ""
410+
echo "Please verify:"
411+
echo " • MySQL service is running"
412+
echo " • Credentials are correct"
413+
echo " • User has access from this host"
414+
echo " • Database name is correct"
415+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
416+
exit 1
417+
fi
418+
419+
# Test dump permissions
420+
if ! test_mysql_dump "$mysql_host" "$mysql_database" "$mysql_user" "$mysql_password"; then
421+
echo ""
422+
echo "ERROR: The provided user doesn't have sufficient privileges for database export."
423+
echo "Please ensure the user has the necessary privileges or try a different user."
424+
exit 1
425+
fi
337426
fi
338427

339-
# Create recovery script
340-
create_recovery_script
428+
# Create recovery script (with empty service name since we haven't stopped anything yet)
429+
create_recovery_script ""
341430

342431
# Final confirmation before stopping Ghost
343432
echo ""
@@ -365,8 +454,17 @@ main() {
365454
# Stop Ghost service
366455
echo ""
367456
echo "Stopping Ghost service..."
368-
systemctl stop "$ghost_service_name"
369-
systemctl disable "$ghost_service_name"
457+
458+
# Update recovery script with actual service name before stopping
459+
create_recovery_script "$ghost_service_name"
460+
461+
if ! systemctl stop "$ghost_service_name"; then
462+
echo "ERROR: Failed to stop Ghost service"
463+
echo "Please check: systemctl status $ghost_service_name"
464+
exit 1
465+
fi
466+
467+
systemctl disable "$ghost_service_name" 2>/dev/null || true
370468
echo "✓ Ghost service stopped"
371469

372470
# Migrate content

0 commit comments

Comments
 (0)