Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ DATABASE_ROOT_PASSWORD=reallysecurerootpassword
DATABASE_USER=optionalusername
DATABASE_PASSWORD=ghostpassword

# Port Ghost should listen on
# You should only need to edit this if you want to host
# multiple sites on the same server
# GHOST_PORT=2368

# Developer Experiences must be enabled for
# both Traffic Analytics and ActivityPub
ENABLE_DEVELOPER_EXPERIMENTS=false
Expand Down
2 changes: 1 addition & 1 deletion compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ services:
image: ghost:${GHOST_VERSION:-5-alpine}
restart: always
expose:
- "2368"
- "127.0.0.1:${GHOST_PORT:-2368}:2368"
# This is required to import current config when migrating
env_file:
- .env
Expand Down
48 changes: 48 additions & 0 deletions help
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash

cat << 'EOF'
════════════════════════════════════════════════════════════════════
GHOST DOCKER HELP & COMMANDS
════════════════════════════════════════════════════════════════════

COMMON COMMANDS:
docker compose logs -f ghost # View real-time logs
docker compose logs -f caddy # View Caddy webserver logs
docker compose ps # Check service status
docker compose down # Stop all services
docker compose up -d # Start all services
docker compose restart ghost # Restart Ghost container

TROUBLESHOOTING:
docker compose exec ghost sh # Access Ghost container shell
docker compose logs --tail=100 # View last 100 log lines
docker stats # Monitor resource usage

DATABASE ACCESS:
docker compose exec mysql mysql -u root -p
# Use the DATABASE_ROOT_PASSWORD from your .env file

UPGRADES:
git pull # Pull latest updates
docker compose pull # Get latest container images
docker compose up -d # Upgrade to the latest version

For major upgrades, always:
1. Backup your data first
2. Read Ghost's release notes
3. Test in a staging environment

CONFIGURATION:
- Edit .env file for environment variables
- Restart Ghost after changes: docker compose up -d

USEFUL PATHS:
Content: ./data/ghost/
Database: ./data/mysql/
Logs: docker compose logs
Config: ./.env

MORE HELP:
Ghost Docs: https://ghost.org/docs/
Ghost Community: https://forum.ghost.org/
EOF
176 changes: 139 additions & 37 deletions scripts/migrate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ WHAT WILL HAPPEN:
✓ Copy content directory to Docker mount
✓ Export and import your database to a Docker based MySQL instance
✓ Start Ghost in Docker container
✓ Optionally configure Caddy for HTTPS
✓ Optionally configure Caddy Webserver for HTTPS

WHAT WONT HAPPEN:
✓ No data will be deleted
Expand Down Expand Up @@ -191,7 +191,8 @@ migrate_content() {
mkdir -p "$dest"

# Copy with progress
rsync --info=progress2 -aHv "$source" "$dest"
echo "Copying files..."
rsync --info=progress2 -aH "$source" "$dest"

echo ""
echo "Setting permissions for Ghost container (UID: $GHOST_UID, GID: $GHOST_GID)..."
Expand All @@ -208,7 +209,7 @@ test_mysql_dump() {
local password=$4

# Try a minimal dump to test permissions
if mysqldump --no-tablespaces --no-data -h"$host" -u"$user" -p"$password" "$database" >/dev/null 2>&1; then
if MYSQL_PWD="$password" mysqldump --no-tablespaces --no-data -h"$host" -u"$user" "$database" >/dev/null 2>&1; then
return 0
else
return 1
Expand All @@ -227,7 +228,7 @@ migrate_database() {
# Export database with proper error handling
local dump_output
local dump_status
dump_output=$(mysqldump --no-tablespaces -h"$mysql_host" -u"$mysql_user" -p"$mysql_password" "$mysql_database" 2>&1 > "$TEMP_SQL_FILE")
dump_output=$(MYSQL_PWD="$mysql_password" mysqldump --no-tablespaces -h"$mysql_host" -u"$mysql_user" "$mysql_database" 2>&1 > "$TEMP_SQL_FILE")
dump_status=$?

# Check for errors in output (mysqldump may return 0 even with some errors)
Expand Down Expand Up @@ -263,10 +264,6 @@ migrate_database() {
dump_size=$(human_readable "$(stat -c%s "$TEMP_SQL_FILE")")
echo "✓ Database exported successfully ($dump_size)"

# Start MySQL container
echo "Starting MySQL container..."
docker compose up db -d

# Wait for MySQL to be ready
echo -n "Waiting for MySQL container to be ready"
local counter=0
Expand All @@ -286,7 +283,9 @@ migrate_database() {

# Import database
echo "Importing database into Docker MySQL..."
if ! docker compose exec -T db sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" $MYSQL_DATABASE' < "$TEMP_SQL_FILE"; then
local MYSQL_ROOT_PASSWORD
MYSQL_ROOT_PASSWORD=$(grep DATABASE_ROOT_PASSWORD "$PWD/.env" | cut -d '=' -f 2-)
if ! docker compose exec -e MYSQL_PWD="$MYSQL_ROOT_PASSWORD" -T db sh -c 'exec mysql -uroot $MYSQL_DATABASE' < "$TEMP_SQL_FILE"; then
echo "ERROR: Failed to import database"
exit 1
fi
Expand All @@ -297,6 +296,32 @@ migrate_database() {
rm -f "$TEMP_SQL_FILE"
}

# Find Ghost installations in /var/www/
find_ghost_installations() {
local installations=()

# Search one level deep in /var/www/
if [[ -d "/var/www" ]]; then
for dir in /var/www/*/; do
# Skip if not a directory
[[ ! -d "$dir" ]] && continue

# Remove trailing slash
dir="${dir%/}"

# Check if it's a valid Ghost installation
if [[ -f "${dir}/.ghost-cli" ]] && [[ -d "${dir}/content" ]]; then
# Additional validation - check if config file exists
if [[ -f "${dir}/config.production.json" ]]; then
installations+=("$dir")
fi
fi
done
fi

printf '%s\n' "${installations[@]}"
}

# Main script starts here
main() {
check_prerequisites
Expand All @@ -311,8 +336,54 @@ main() {
exit 0
fi

# Search for Ghost installations
echo ""
echo "Searching for Ghost installations in /var/www/..."

local ghost_installations=()
while IFS= read -r line; do
[[ -n "$line" ]] && ghost_installations+=("$line")
done < <(find_ghost_installations)

# Get installation location
read -rp 'Enter your current Ghost installation path: ' current_location
if [[ ${#ghost_installations[@]} -gt 0 ]]; then
echo ""
echo "Found ${#ghost_installations[@]} Ghost installation(s) in /var/www/:"
echo ""

# Display found installations with numbers
local i=1
for installation in "${ghost_installations[@]}"; do
local site_name
site_name=$(basename "$installation")
echo " $i) $site_name (${installation})"
((i++))
done
echo " $i) Enter a different path"
echo ""

read -rp "Select an installation (1-$i): " selection

# Validate selection
if [[ "$selection" =~ ^[0-9]+$ ]] && [[ $selection -ge 1 ]] && [[ $selection -le $i ]]; then
if [[ $selection -eq $i ]]; then
# User wants to enter a different path
read -rp 'Enter your current Ghost installation path: ' current_location
else
# User selected a found installation
current_location="${ghost_installations[$((selection-1))]}"
echo "Selected: $current_location"
fi
else
echo "Invalid selection"
exit 1
fi
else
# No installations found, ask for path directly
echo ""
echo "No Ghost installations found in /var/www/"
read -rp 'Enter your current Ghost installation path: ' current_location
fi

if [[ -z "$current_location" ]]; then
echo "ERROR: Installation path is required"
Expand Down Expand Up @@ -431,19 +502,19 @@ main() {
# Final confirmation before stopping Ghost
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "⚠️ YOUR SITE WILL NOW GO OFFLINE FOR MIGRATION"
echo "⚠️ YOUR SITE WILL BE UNAVAILABLE DURING MIGRATION"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "The next steps will:"
echo " 1. Stop your Ghost service"
echo " 2. Migrate your content and database"
echo " a. Your content directory will now be at ${PWD}/data/ghost/"
echo " b. Your MySQL database will now be at ${PWD}/data/mysql/"
echo " a. Your new content directory will be at ${PWD}/data/ghost/"
echo " b. Your new MySQL database will be at ${PWD}/data/mysql/"
echo " 3. Start Ghost in Docker"
echo ""
echo "If anything goes wrong, run: bash $RECOVERY_SCRIPT"
echo ""
read -rp 'Continue with migration? This will take your site offline. (y/n): ' confirm
read -rp 'Continue with migration? This will make your site unavailable. (y/n): ' confirm

if [[ "${confirm,,}" != "y" ]]; then
echo "Migration cancelled."
Expand All @@ -467,6 +538,10 @@ main() {
systemctl disable "$ghost_service_name" 2>/dev/null || true
echo "✓ Ghost service stopped"

# Start MySQL container
echo "Starting MySQL container for migration..."
docker compose up db -d

# Migrate content
echo ""
migrate_content
Expand All @@ -478,17 +553,13 @@ main() {
# Import configuration
echo ""
echo "Importing configuration from existing installation..."
echo ""
node "${PWD}/scripts/config-to-env.js" "${current_location}/config.production.json"
echo ""

read -rp 'Import these settings to .env? (y/n): ' confirm
if [[ "${confirm,,}" == "y" ]]; then
echo -e '\n# Configuration imported from existing Ghost install' >> "${PWD}/.env"
node "${PWD}/scripts/config-to-env.js" "${current_location}/config.production.json" >> "${PWD}/.env"
echo "✓ Configuration imported"
else
echo "Skipped configuration import"
echo "Note: You'll need to manually configure mail settings if required"
fi
echo -e "\n# Configuration imported from existing Ghost install at ${current_location}" >> "${PWD}/.env"
node "${PWD}/scripts/config-to-env.js" "${current_location}/config.production.json" >> "${PWD}/.env"
echo "✓ Configuration imported"

# Start Ghost
echo ""
Expand All @@ -498,24 +569,26 @@ main() {

# Caddy setup
echo ""
read -rp 'Start Caddy for automatic HTTPS? This will stop Nginx. (y/n): ' confirm
read -rp 'Start Caddy Webserver for automatic HTTPS? This will stop Nginx. (y/n): ' confirm
if [[ "${confirm,,}" == "y" ]]; then
echo "Stopping Nginx..."
systemctl stop nginx || true
systemctl disable nginx || true
systemctl stop nginx -q || true
systemctl disable nginx -q || true

echo "Starting Caddy..."
docker compose up caddy -d

local domain
domain=$(grep 'DOMAIN' "${PWD}/.env" | cut -d '=' -f 2)
domain=$(grep 'DOMAIN' "${PWD}/.env" | cut -d '=' -f 2-)
echo ""
echo "✓ Caddy is running!"
echo "✓ Caddy Webserver is running!"
echo "✓ Your site is available at: https://${domain}"
else
local ghost_port
ghost_port=$(grep 'GHOST_PORT' "${PWD}/.env" | cut -d '=' -f 2-)
echo ""
echo "✓ Ghost is running on port 2368"
echo " Configure your reverse proxy to forward traffic to it"
echo "✓ Ghost is now running"
echo " To finish migration, configure your webserver to forward traffic to 127.0.0.1:${ghost_port}"
fi

# Success! Remove recovery script
Expand All @@ -526,15 +599,44 @@ main() {
echo "✓ MIGRATION COMPLETED SUCCESSFULLY!"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "Your Ghost site is now running in Docker."
echo "Original installation files remain at: $current_location"
echo "Your existing MySQL instance is still running at: $mysql_host"
echo "Your Ghost site is now running in Docker!"
echo ""
echo "IMPORTANT INFORMATION:"
echo " • Original files: $current_location"
echo " • Original database: $mysql_database on $mysql_host"
echo " • New content location: ${PWD}/data/ghost/"
echo " • Configuration: ${PWD}/.env"
echo ""
echo "QUICK START COMMANDS:"
echo " View logs: docker compose logs -f ghost"
echo " Check status: docker compose ps"
echo " Stop Ghost: docker compose down"
echo " Start Ghost: docker compose up -d"
echo ""
echo "TROUBLESHOOTING:"
echo " • If site is unreachable, check: docker compose logs caddy"
echo " • For 502 errors, Ghost may still be starting (check logs)"
echo " • Database issues: docker compose logs db"
echo ""
echo "UPGRADES:"
echo " 1. git pull"
echo " 2. docker compose pull"
echo " 3. docker compose up -d"
echo " Always backup before major upgrades!"
echo ""
echo "Next steps:"
echo " - Monitor logs: docker compose logs -f ghost"
echo " - View status: docker compose ps"
echo " - Stop services: docker compose down"
echo "HELP GUIDE:"
echo " For a comprehensive list of commands and troubleshooting tips:"
echo " ./help"
echo ""
echo "CLEANUP:"
echo "Once you're checked over the migration you can remove the old installation files and database by running:"
echo ""
echo " rm -r $current_location/"
echo " mysql -h$mysql_host -u$mysql_user -p -e 'DROP DATABASE IF EXISTS ${mysql_database}'"
echo ""
echo "This will remove the old Ghost CLI and Ghost 5.x installation"
echo ""
echo "════════════════════════════════════════════════════════════"
}

# Run main function
Expand Down