-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackup_ghost.sh
More file actions
executable file
·160 lines (135 loc) · 6.65 KB
/
Copy pathbackup_ghost.sh
File metadata and controls
executable file
·160 lines (135 loc) · 6.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/bin/bash
set -euo pipefail
##### INSTRUCTION
#
# Sauvegarde d'une instance Ghost vers un depot borgwarehouse (BWH).
#
# Principe : mysqldump coherent depose dans le dossier de la stack, puis TOUT le
# dossier part dans UNE archive borg. L'archive contient donc le dump, le content
# (images, themes), le .env et le docker-compose.yml : tout ce qu'il faut pour
# remonter le site de zero, et le tout coherent au meme instant.
#
# Exclusions :
# db/ fichiers InnoDB — les copier a chaud donnerait une base corrompue,
# le dump SQL est la seule forme fiable (et ils sont illisibles
# sans root de toute facon)
# ghost/logs/ volumineux, sans valeur
# scripts/.ssh/ la cle privee du depot : on ne l'archive pas dans le depot
# qu'elle protege. La garder au coffre (voir --init ci-dessous).
# .git/ deja sur GitHub
#
# CONFIGURATION : ce script ne contient AUCUN secret. Il lit BORG_PREFIX,
# BORG_REPO et BORG_PASSPHRASE dans le .env de la stack — le seul fichier que
# git ignore. Ce script, lui, est versionne : y ecrire une passphrase, c'est
# la pousser sur GitHub au premier `git add -A` distrait.
#
# NB : le .env part donc dans l'archive, passphrase du depot comprise. C'est
# sans consequence : il faut deja connaitre cette passphrase pour ouvrir
# l'archive qui la contient.
#
# 1- Ce script vit dans le dossier scripts/ de la stack Ghost, sur le serveur.
# 2- Choisir un BORG_PREFIX, puis generer une cle SSH DEDIEE a ce depot
# (une cle = un depot sur borgwarehouse) :
# mkdir -p ./.ssh && chmod 700 ./.ssh
# ssh-keygen -t ed25519 -N '' -f ./.ssh/ghost_upop_ed25519 # nom = $BORG_PREFIX
# 3- Creer un depot dans borgwarehouse, y coller ./.ssh/ghost_upop_ed25519.pub
# 4- Renseigner le bloc SAUVEGARDE du .env (voir env_example).
# 5- Initier le depot : ./backup_ghost.sh --init
# 6- Sauvegarder : ./backup_ghost.sh
# 7- Cron (a lancer par un utilisateur membre du groupe docker, ou root) :
# @daily bash /opt/ghost/scripts/backup_ghost.sh >> /var/log/backup_ghost.log 2>&1
#
# Le mot de passe root MySQL n'est PAS recopie ici non plus : il est lu dans le
# conteneur au moment du dump.
#####
## Surveillance optionnelle via Sentry :
## Installez : curl -sL https://sentry.io/get-cli/ | bash (maj : sentry-cli update)
# export SENTRY_DSN=''
# eval "$(sentry-cli bash-hook)"
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
# Dossier de la stack Ghost (celui qui contient docker-compose.yml et .env).
# Par defaut : le dossier parent de scripts/.
GHOST_DIR="${GHOST_DIR:-$(dirname "$SCRIPT_DIR")}"
COMPOSE_FILE="$GHOST_DIR/docker-compose.yml"
CONTENT_DIR="$GHOST_DIR/ghost"
ENV_FILE="$GHOST_DIR/.env"
# Les secrets du depot borg viennent du .env de la stack (ignore par git).
[ -f "$ENV_FILE" ] || { echo "[ghost-borg] .env introuvable : $ENV_FILE" >&2; exit 1; }
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
PREFIX="${BORG_PREFIX:?BORG_PREFIX doit etre renseigne dans le .env}"
# Cle SSH DEDIEE a ce depot (une cle = un depot sur borgwarehouse).
SSH_KEY="$SCRIPT_DIR/.ssh/${PREFIX}_ed25519"
# Dossier de travail pour le dump (supprime apres l'archivage)
DUMP_DIR="${DUMP_DIR:-$SCRIPT_DIR/mysql-dump-$PREFIX}"
#### PREPARATION ####
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes
# Un seul backup a la fois pour cette stack. Sans ce verrou, le cron qui tombe
# pendant un `make update` (qui rejoue une sauvegarde) partagerait le meme
# DUMP_DIR : le premier a finir le supprime — trap EXIT — pendant que l'autre
# archive encore, et on obtient une archive SANS DUMP, silencieusement.
exec 9>"$SCRIPT_DIR/.backup-$PREFIX.lock"
flock -n 9 || { echo "[ghost-borg] une sauvegarde est deja en cours — abandon." >&2; exit 1; }
: "${BORG_REPO:?BORG_REPO doit etre renseigne dans le .env}"
: "${BORG_PASSPHRASE:?BORG_PASSPHRASE doit etre renseigne dans le .env}"
command -v borg >/dev/null || { echo "[ghost-borg] borg introuvable dans le PATH" >&2; exit 1; }
command -v docker >/dev/null || { echo "[ghost-borg] docker introuvable dans le PATH" >&2; exit 1; }
command -v flock >/dev/null || { echo "[ghost-borg] flock introuvable dans le PATH" >&2; exit 1; }
[ -f "$COMPOSE_FILE" ] || { echo "[ghost-borg] compose introuvable : $COMPOSE_FILE" >&2; exit 1; }
DATE_NOW=$(date +%Y-%m-%d-%H-%M)
# Force l'usage de CETTE cle (et pas une autre de l'agent SSH).
if [ -f "$SSH_KEY" ]; then
chmod 600 "$SSH_KEY" 2>/dev/null || true
export BORG_RSH="/usr/bin/ssh -oStrictHostKeyChecking=accept-new -oIdentitiesOnly=yes -i $SSH_KEY"
else
echo "[ghost-borg] [INFO] aucune cle a $SSH_KEY — SSH utilisera la config systeme." >&2
fi
# Mode init : initialise le depot puis sort.
if [ "${1:-}" = "--init" ]; then
echo "$DATE_NOW initialisation du depot borg ($BORG_REPO) avec repokey-blake2"
/usr/bin/borg init -e repokey-blake2 "$BORG_REPO"
exit 0
fi
[ -d "$CONTENT_DIR" ] || { echo "[ghost-borg] dossier content introuvable : $CONTENT_DIR" >&2; exit 1; }
#### DUMP MYSQL ####
# Nettoyage garanti du dump meme en cas d'erreur.
trap 'rm -rf "$DUMP_DIR"' EXIT
rm -rf "$DUMP_DIR"
mkdir -p "$DUMP_DIR"
DUMP_FILE="$DUMP_DIR/ghost.sql"
echo "$DATE_NOW dump de la base 'ghost' (mysqldump, transaction unique)"
# --single-transaction : dump coherent sans verrouiller le site (InnoDB).
# Le mot de passe root est lu DANS le conteneur, il ne transite pas par ce script.
docker compose -f "$COMPOSE_FILE" exec -T ghost-mysql \
sh -c 'exec mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" \
--single-transaction --quick --routines --triggers ghost' \
> "$DUMP_FILE"
# Garde-fou : un dump vide = on n'archive pas.
if [ ! -s "$DUMP_FILE" ]; then
echo "[ghost-borg] dump vide ($DUMP_FILE) — abandon" >&2
exit 1
fi
#### BORG CREATE ####
# On archive TOUT le dossier de la stack : le dump (depose dans scripts/), le
# content, mais aussi le .env et le docker-compose.yml. Une archive contient
# ainsi tout ce qu'il faut pour remonter le site de zero.
echo "$DATE_NOW on cree l'archive borg (dossier complet de la stack)"
/usr/bin/borg create -vs --compression lz4 \
--exclude "$GHOST_DIR/db" \
--exclude "$GHOST_DIR/ghost/logs" \
--exclude "$GHOST_DIR/scripts/.ssh" \
--exclude "$GHOST_DIR/.git" \
"$BORG_REPO::$PREFIX-$DATE_NOW" \
"$GHOST_DIR"
echo "$DATE_NOW on prune les vieux borg :"
# --glob-archives : le prune ne touche QUE les archives de cette stack. Si un
# jour deux instances partagent le meme depot, chacune garde sa retention —
# sans ce filtre, elles se rognent mutuellement, en silence.
/usr/bin/borg prune -v --list \
--glob-archives "$PREFIX-*" \
--keep-within=7d --keep-daily=30 --keep-weekly=12 --keep-monthly=-1 --keep-yearly=-1 \
"$BORG_REPO"
echo "$DATE_NOW termine"