diff --git a/.env.example b/.env.example index 67cad11..6931f9f 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/compose.yml b/compose.yml index 030c51d..4319bae 100644 --- a/compose.yml +++ b/compose.yml @@ -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 diff --git a/help b/help new file mode 100755 index 0000000..2bd4c4c --- /dev/null +++ b/help @@ -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 diff --git a/scripts/migrate.sh b/scripts/migrate.sh index 3cf2f84..fa29c8a 100644 --- a/scripts/migrate.sh +++ b/scripts/migrate.sh @@ -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 @@ -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)..." @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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" @@ -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." @@ -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 @@ -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 "" @@ -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 @@ -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