diff --git a/blog.knravish.me.conf b/blog.knravish.me.conf new file mode 100644 index 0000000..c574fe1 --- /dev/null +++ b/blog.knravish.me.conf @@ -0,0 +1,16 @@ +server { + server_name blog.knravish.me; + index index.html index.htm; + + include /etc/nginx/snippets/authelia-location.conf; + + set $upstream http://127.0.0.1:2368; + + location / { + include /etc/nginx/snippets/proxy.conf; + include /etc/nginx/snippets/authelia-authrequest.conf; + proxy_pass $upstream; + } + + listen 80; +} diff --git a/ghost_server-backup b/ghost_server-backup index c19b5a6..9c42791 100644 --- a/ghost_server-backup +++ b/ghost_server-backup @@ -8,29 +8,30 @@ logFile=${HOME}/backup_logs/$(date +%y_%m).log { echo -e "\n[+] ghost backup\n" - cd "${BLOG_PATH}" || exit + TMP_DIR="/tmp/${USER}-backup" + mkdir -p "${TMP_DIR}" - if ! /usr/bin/expect "${HOME}"/"${USER}"-credentials.exp; then - curl -Ss \ - -H "Title: Ghost Blog" \ - -H "Priority: 3" \ - -H "Tags: warning,backup" \ - -d "Backup not completed - ghost backup failure" \ - "${NOTIF_URL}" - rm -r "${BLOG_PATH}"/backup* - exit 1 - fi + sudo docker compose -f "${HOME}"/"${USER}"-compose.yaml stop ghost - echo "[+] local backup taken" + # Dump database + # shellcheck disable=SC2024 + sudo docker exec ghost-mysql mysqldump \ + -u root --password="${DB_ROOT_PASSWORD}" \ + --no-tablespaces ghost >"${TMP_DIR}/db.sql" - if ! rclone copyto "${BLOG_PATH}"/backup*.zip "${BUCKET_PATH}" -v; then + # Backup content (exclude logs) + rsync -a --exclude='logs/' "${VOLUME_PATH}/content/" "${TMP_DIR}/content/" + + sudo docker compose -f "${HOME}"/"${USER}"-compose.yaml start ghost + + if ! rclone copy "${TMP_DIR}" "${BUCKET_PATH}" -v; then curl -Ss \ -H "Title: Ghost Blog" \ -H "Priority: 3" \ -H "Tags: warning,backup" \ -d "Backup not completed - rclone failure" \ "${NOTIF_URL}" - rm -r "${BLOG_PATH}"/backup* + rm -rf "${TMP_DIR}" exit 1 fi @@ -40,6 +41,6 @@ logFile=${HOME}/backup_logs/$(date +%y_%m).log -H "Tags: heavy_check_mark,backup" \ -d "Backup completed" \ "${NOTIF_URL}" - rm -r "${BLOG_PATH}"/backup* + rm -rf "${TMP_DIR}" } &>>"$logFile" diff --git a/ghost_server-compose_template.yaml b/ghost_server-compose_template.yaml new file mode 100644 index 0000000..9b41f5c --- /dev/null +++ b/ghost_server-compose_template.yaml @@ -0,0 +1,42 @@ +--- +services: + ghost: + image: ghost:6 + container_name: ghost + pull_policy: always + restart: unless-stopped + ports: + - 127.0.0.1:${PORT}:2368 + environment: + - url=${BASE_URL} + - database__client=mysql + - database__connection__host=db + - database__connection__user=ghost + - database__connection__password=${DB_PASSWORD} + - database__connection__database=ghost + - mail__transport=Direct + - logging__transports=["stdout"] + volumes: + - ${VOLUME_PATH}/content:/var/lib/ghost/content + depends_on: + db: + condition: service_healthy + + db: + image: mysql:8 + container_name: ghost-mysql + pull_policy: always + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD} + - MYSQL_USER=ghost + - MYSQL_PASSWORD=${DB_PASSWORD} + - MYSQL_DATABASE=ghost + volumes: + - ${VOLUME_PATH}/mysql:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "--password=${DB_ROOT_PASSWORD}"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s diff --git a/ghost_server-config.production copy.json b/ghost_server-config.production copy.json deleted file mode 100644 index c8f3d0b..0000000 --- a/ghost_server-config.production copy.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "url": "https://blog.knravish.me", - "server": { - "port": 2368, - "host": "127.0.0.1" - }, - "database": { - "client": "mysql", - "connection": { - "host": "postgres_hostname", - "user": "postgres_username", - "password": "postgres_password", - "database": "defaultdb", - "port": , - "ssl": { - "ca": "", - "rejectUnauthorized": true - } - } - }, - "mail": { - "transport": "Direct" - }, - "logging": { - "transports": [ - "file", - "stdout" - ] - }, - "process": "systemd", - "paths": { - "contentPath": "/var/www/blog.knravish.me/content" - }, - "bootstrap-socket": { - "port": 8000, - "host": "localhost" - } -} diff --git a/ghost_server-credentials copy.exp b/ghost_server-credentials copy.exp deleted file mode 100644 index 75db144..0000000 --- a/ghost_server-credentials copy.exp +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/expect - -set stafftoken "" - -spawn ghost backup - -expect "Ghost Staff access token" -send "$stafftoken\r" - -expect eof diff --git a/ghost_server-cronjob b/ghost_server-cronjob index 5fb9c40..e94e064 100644 --- a/ghost_server-cronjob +++ b/ghost_server-cronjob @@ -1 +1,2 @@ 3 10 * * * /home/ghost_server/ghost_server-backup +3 11 * * 2 /home/ghost_server/ghost_server-update diff --git a/ghost_server-setup b/ghost_server-setup index e6df5f0..5d54555 100644 --- a/ghost_server-setup +++ b/ghost_server-setup @@ -1,52 +1,63 @@ #!/bin/bash +# Sets up Ghost (Docker-based) from a backup stored in B2. +# Assumes B2 at ${BUCKET_PATH} contains: +# db.sql — mysqldump of the ghost database +# content/ — Ghost content directory +# +# Run ghost_server-migrate first to populate B2, then run this. + +set -euo pipefail # shellcheck source=ghost_server-env . "${HOME}"/"${USER}"-env -email_address=hello@knravish.me echo -e "\n[+] setting up ghost\n\n-------\n" -echo "[+] node and companions" -# ghost doesn't play well with nvm for some reason, probably because of installation location and sudo access -# Download and import the Nodesource GPG key -sudo mkdir -p /etc/apt/keyrings -curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -NODE_MAJOR=20 # Use a supported version -echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list -sudo apt-get update -sudo apt-get install nodejs -y +TMP_DIR="/tmp/${USER}-restore" -echo "[+] getting ready..." +mkdir -p "${VOLUME_PATH}"/{content,mysql} +mkdir -p "${TMP_DIR}" -ghost_cli_ver="1.28.4" -sudo npm i -g ghost-cli@${ghost_cli_ver} +envsubst <"${HOME}"/"${USER}"-compose_template.yaml >"${HOME}"/"${USER}"-compose.yaml -sudo mkdir -p "${BLOG_PATH}" -sudo chown "${USER}":"${USER}" "${BLOG_PATH}" -sudo chmod 775 "${BLOG_PATH}" +# --------------------------------------------------------------------------- +# 1. Start MySQL; Ghost stays down until data is restored +# --------------------------------------------------------------------------- +echo "[+] starting MySQL..." +sudo docker compose -f "${HOME}"/"${USER}"-compose.yaml up -d db -# ghost really needs to update to newer nginx versions and conventions... -sudo mkdir -p /etc/nginx/sites-available/ /etc/nginx/sites-enabled/ /etc/nginx/snippets/ +echo "[+] waiting for MySQL to be healthy..." +until sudo docker exec ghost-mysql mysqladmin ping -h localhost -u root --password="${DB_ROOT_PASSWORD}" --silent 2>/dev/null; do + sleep 3 +done -echo "[+] ooh, interactive stuff" +# --------------------------------------------------------------------------- +# 2. Restore database from B2 +# --------------------------------------------------------------------------- +echo "[+] downloading database dump from B2..." +rclone copyto "${BUCKET_PATH}/db.sql" "${TMP_DIR}/db.sql" -# currently track manually, maybe automate -ghost_ver="5.130.6" +echo "[+] restoring database..." +cat "${TMP_DIR}/db.sql" | sudo docker exec -i ghost-mysql mysql -u root --password="${DB_ROOT_PASSWORD}" ghost -cd "${BLOG_PATH}" && ghost install ${ghost_ver} --no-setup +echo "[+] database restored" -sudo cp "${HOME}"/"${USER}"-config.production.json "${BLOG_PATH}"/ -sudo chown "${USER}":"${USER}" "${BLOG_PATH}"/"${USER}"-config.production.json -mv "${BLOG_PATH}"/"${USER}"-config.production.json "${BLOG_PATH}"/config.production.json +# --------------------------------------------------------------------------- +# 3. Restore content directory from B2 +# --------------------------------------------------------------------------- +echo "[+] downloading content from B2..." +rclone copy "${BUCKET_PATH}/content/" "${VOLUME_PATH}/content/" -cd "${BLOG_PATH}" && ghost setup --auto --sslemail ${email_address} +echo "[+] content restored" -echo "[+] restoring backup data" +# --------------------------------------------------------------------------- +# 4. Start Ghost — it will run schema migrations (V5 -> V6) automatically +# --------------------------------------------------------------------------- +echo "[+] starting Ghost..." +sudo docker compose -f "${HOME}"/"${USER}"-compose.yaml up -d ghost -sudo rm -r "${BLOG_PATH}"/content/* +rm -rf "${TMP_DIR}" -rclone copyto "${BUCKET_PATH}" "${BLOG_PATH}"/ghostBackup.zip -sudo unzip "${BLOG_PATH}"/ghostBackup.zip -d "${BLOG_PATH}"/content/ -sudo chown -R ghost:ghost "${BLOG_PATH}"/content/ - -echo -e "\n-----\nIMPORTANT\n-----\n[X] modify the nginx default config file to include the sites-enabled directory\n" +echo -e "\n-----\nINFO\n-----" +echo "[i] Ghost is starting and running database migrations. This may take a minute." +echo "[i] Check logs: sudo docker compose -f ~/ghost_server-compose.yaml logs -f ghost" diff --git a/ghost_server-teardown b/ghost_server-teardown new file mode 100644 index 0000000..ed33eaa --- /dev/null +++ b/ghost_server-teardown @@ -0,0 +1,14 @@ +#!/bin/bash + +username=ghost_server + +# application +sudo docker compose -f /home/${username}/${username}-compose.yaml down -v + +uid_num=$(id -u $username) +sudo killall -9 -v -g -u $username +sudo crontab -r -u $username +sudo deluser --remove-all-files $username + +# clean-up +sudo find / -user "$uid_num" -delete diff --git a/ghost_server-update b/ghost_server-update new file mode 100644 index 0000000..67553ce --- /dev/null +++ b/ghost_server-update @@ -0,0 +1,11 @@ +#!/bin/bash + +mkdir -p "${HOME}"/update_logs +logFile=${HOME}/update_logs/$(date +%y_%m).log +{ + echo -e "\n[+] updating ghost\n" + + sudo docker compose -f "${HOME}"/"${USER}"-compose.yaml pull && + sudo docker compose -f "${HOME}"/"${USER}"-compose.yaml up -d --always-recreate-deps --remove-orphans && + yes | sudo docker image prune -af +} &>>"$logFile"