forked from github/backup-utils
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathghe-backup
executable file
·393 lines (321 loc) · 12.5 KB
/
ghe-backup
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#!/usr/bin/env bash
#/ Usage: ghe-backup [-hv] [--version]
#/
#/ Take snapshots of all GitHub Enterprise data, including Git repository data,
#/ the MySQL database, instance settings, GitHub Pages data, etc.
#/
#/ OPTIONS:
#/ -v | --verbose Enable verbose output.
#/ -h | --help Show this message.
#/ --version Display version information.
#/ -i | --incremental Incremental backup
#/ --skip-checks Skip storage/sw version checks
#/
set -e
# Parse arguments
while true; do
case "$1" in
-h|--help)
export GHE_SHOW_HELP=true
shift
;;
--version)
export GHE_SHOW_VERSION=true
shift
;;
-v|--verbose)
export GHE_VERBOSE=true
shift
;;
-i|--incremental)
export GHE_INCREMENTAL=true
shift
;;
--skip-checks)
export GHE_SKIP_CHECKS=true
shift
;;
-*)
echo "Error: invalid argument: '$1'" 1>&2
exit 1
;;
*)
break
;;
esac
done
export CALLING_SCRIPT="ghe-backup"
# Bring in the backup configuration
# shellcheck source=share/github-backup-utils/ghe-backup-config
. "$( dirname "${BASH_SOURCE[0]}" )/../share/github-backup-utils/ghe-backup-config"
# Setup progress tracking
init-progress
export PROGRESS_TOTAL=14 # Minimum number of steps in backup is 14
echo "$PROGRESS_TOTAL" > /tmp/backup-utils-progress-total
export PROGRESS_TYPE="Backup"
echo "$PROGRESS_TYPE" > /tmp/backup-utils-progress-type
export PROGRESS=0 # Used to track progress of backup
echo "$PROGRESS" > /tmp/backup-utils-progress
OPTIONAL_STEPS=0
# Backup actions+mssql
if ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config --true app.actions.enabled'; then
OPTIONAL_STEPS=$((OPTIONAL_STEPS + 2))
fi
# Backup fsck
if [ "$GHE_BACKUP_FSCK" = "yes" ]; then
OPTIONAL_STEPS=$((OPTIONAL_STEPS + 1))
fi
# Backup minio
if ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config --true app.minio.enabled'; then
OPTIONAL_STEPS=$((OPTIONAL_STEPS + 1))
fi
# Backup pages
if [ "$GHE_BACKUP_PAGES" != "no" ]; then
OPTIONAL_STEPS=$((OPTIONAL_STEPS + 1))
fi
PROGRESS_TOTAL=$((OPTIONAL_STEPS + PROGRESS_TOTAL)) # Minimum number of steps in backup is 14
echo "$PROGRESS_TOTAL" > /tmp/backup-utils-progress-total
# Check to make sure moreutils parallel is installed and working properly
ghe_parallel_check
# Used to record failed backup steps
failures=
failures_file="$(mktemp -t backup-utils-backup-failures-XXXXXX)"
# CPU and IO throttling to keep backups from thrashing around.
export GHE_NICE=${GHE_NICE:-"nice -n 19"}
export GHE_IONICE=${GHE_IONICE:-"ionice -c 3"}
# Create the timestamped snapshot directory where files for this run will live,
# change into it, and mark the snapshot as incomplete by touching the
# 'incomplete' file. If the backup succeeds, this file will be removed
# signifying that the snapshot is complete.
mkdir -p "$GHE_SNAPSHOT_DIR"
cd "$GHE_SNAPSHOT_DIR"
touch "incomplete"
# Exit early if the snapshot filesystem doesn't support hard links, symlinks and
# if rsync doesn't support hardlinking of dangling symlinks
trap 'rm -rf src dest1 dest2' EXIT
mkdir -p src
touch src/testfile
if ! ln -s /data/does/not/exist/hooks/ src/ >/dev/null 2>&1; then
log_error "Error: the filesystem containing $GHE_DATA_DIR does not support symbolic links. \nGit repositories contain symbolic links that need to be preserved during a backup." 1>&2
exit 1
fi
if ! output=$(rsync -a src/ dest1 2>&1 && rsync -av src/ --link-dest=../dest1 dest2 2>&1); then
log_error "Error: rsync encountered an error that could indicate a problem with permissions,\n hard links, symbolic links, or another issue that may affect backups." 1>&2
echo "$output"
exit 1
fi
if [ "$(stat -c %i dest1/testfile)" != "$(stat -c %i dest2/testfile)" ]; then
log_error "Error: the filesystem containing $GHE_DATA_DIR does not support hard links.\n Backup Utilities use hard links to store backup data efficiently." 1>&2
exit 1
fi
rm -rf src dest1 dest2
# To prevent multiple backup runs happening at the same time, we create a
# in-progress file with the timestamp and pid of the backup process,
# giving us a form of locking.
#
# Set up a trap to remove the in-progress file if we exit for any reason but
# verify that we are the same process before doing so.
#
# The cleanup trap also handles disabling maintenance mode on the appliance if
# it was automatically enabled.
cleanup () {
if [ -f ../in-progress ]; then
progress=$(cat ../in-progress)
snapshot=$(echo "$progress" | cut -d ' ' -f 1)
pid=$(echo "$progress" | cut -d ' ' -f 2)
if [ "$snapshot" = "$GHE_SNAPSHOT_TIMESTAMP" ] && [ "$$" = "$pid" ]; then
unlink ../in-progress
fi
fi
rm -rf "$failures_file"
rm -f "${GHE_DATA_DIR}/in-progress-backup"
# Cleanup SSH multiplexing
ghe-ssh --clean
}
# Setup exit traps
trap 'cleanup' EXIT
trap 'exit $?' INT # ^C always terminate
# Check to see if there is a running restore
ghe_restore_check
# Check to see if there is a running backup
if [ -h ../in-progress ]; then
log_error "Detected a backup already in progress from a previous version of ghe-backup. \nIf there is no backup in progress anymore, please remove \nthe $GHE_DATA_DIR/in-progress file." >&2
exit 1
fi
if [ -f ../in-progress ]; then
progress=$(cat ../in-progress)
snapshot=$(echo "$progress" | cut -d ' ' -f 1)
pid=$(echo "$progress" | cut -d ' ' -f 2)
if ! ps -p "$pid" >/dev/null 2>&1; then
# We can safely remove in-progress, ghe-prune-snapshots
# will clean up the failed backup.
unlink ../in-progress
else
log_error "Error: A backup of $GHE_HOSTNAME may still be running on PID $pid. \nIf PID $pid is not a process related to the backup utilities, please remove \nthe $GHE_DATA_DIR/in-progress file and try again." 1>&2
exit 1
fi
fi
# Perform a host connection check and establish the remote appliance version.
# The version is available in the GHE_REMOTE_VERSION variable and also written
# to a version file in the snapshot directory itself.
ghe_remote_version_required
echo "$GHE_REMOTE_VERSION" > version
# check that incremental settings are valid if set
is_inc=$(is_incremental_backup_feature_on)
if [ "$is_inc" = true ]; then
if [ "$GHE_VERSION_MAJOR" -lt 3 ]; then
log_error "Can only perform incremental backups on enterprise version 3.10 or higher"
exit 1
fi
if [ "$GHE_VERSION_MINOR" -lt 10 ]; then
log_error "Can only perform incremental backups on enterprise version 3.10 or higher"
exit 1
fi
incremental_backup_check
# If everything is ok, check if we have hit GHE_MAX_INCREMENTAL_BACKUPS, performing pruning actions if necessary
check_for_incremental_max_backups
# initialize incremental backup if it hasn't been done yet
incremental_backup_init
fi
echo "$GHE_SNAPSHOT_TIMESTAMP $$" > ../in-progress
echo "$GHE_SNAPSHOT_TIMESTAMP $$" > "${GHE_DATA_DIR}/in-progress-backup"
START_TIME=$(date +%s)
log_info "Starting backup of $GHE_HOSTNAME with backup-utils v$BACKUP_UTILS_VERSION in snapshot $GHE_SNAPSHOT_TIMESTAMP"
if [ -n "$GHE_ALLOW_REPLICA_BACKUP" ]; then
echo "Warning: backing up a high availability replica may result in inconsistent or unreliable backups."
fi
# Output system information of the backup host
# If /etc/issue.net exists, use it to get the OS version
if [ -f /etc/issue.net ]; then
echo "Running on: $(cat /etc/issue.net)"
else
echo "Running on: Unknown OS"
fi
# If nproc command exists, use it to get the number of CPUs
if command -v nproc >/dev/null 2>&1; then
echo "CPUs: $(nproc)"
else
echo "CPUs: Unknown"
fi
# If the free command exists, use it to get the memory details
if command -v free >/dev/null 2>&1; then
echo "Memory $(free -m | grep '^Mem:' | awk '{print "total/used/free+share/buff/cache: " $2 "/" $3 "/" $4 "+" $5 "/" $6 "/" $7}')"
else
echo "Memory: Unknown"
fi
# Log backup start message in /var/log/syslog on remote instance
ghe_remote_logger "Starting backup from $(hostname) with backup-utils v$BACKUP_UTILS_VERSION in snapshot $GHE_SNAPSHOT_TIMESTAMP ..."
export GHE_BACKUP_STRATEGY=${GHE_BACKUP_STRATEGY:-$(ghe-backup-strategy)}
# Record the strategy with the snapshot so we will know how to restore.
echo "$GHE_BACKUP_STRATEGY" > strategy
# Create benchmark file
bm_init > /dev/null
ghe-backup-store-version ||
log_warn "Warning: storing backup-utils version remotely failed."
log_info "Backing up GitHub settings ..."
ghe-backup-settings || failures="$failures settings"
log_info "Backing up SSH authorized keys ..."
bm_start "ghe-export-authorized-keys"
ghe-ssh "$GHE_HOSTNAME" -- 'ghe-export-authorized-keys' > authorized-keys.json ||
failures="$failures authorized-keys"
bm_end "ghe-export-authorized-keys"
log_info "Backing up SSH host keys ..."
bm_start "ghe-export-ssh-host-keys"
ghe-ssh "$GHE_HOSTNAME" -- 'ghe-export-ssh-host-keys' > ssh-host-keys.tar ||
failures="$failures ssh-host-keys"
bm_end "ghe-export-ssh-host-keys"
ghe-backup-mysql || failures="$failures mysql"
if ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config --true app.actions.enabled'; then
log_info "Backing up MSSQL databases ..."
ghe-backup-mssql 1>&3 || failures="$failures mssql"
log_info "Backing up Actions data ..."
ghe-backup-actions 1>&3 || failures="$failures actions"
fi
if ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config --true app.minio.enabled'; then
log_info "Backing up Minio data ..."
ghe-backup-minio 1>&3 || failures="$failures minio"
fi
cmd_title=$(log_info "Backing up Redis database ...")
commands=("
echo \"$cmd_title\"
ghe-backup-redis > redis.rdb || printf %s \"redis \" >> \"$failures_file\"")
cmd_title=$(log_info "Backing up audit log ...")
commands+=("
echo \"$cmd_title\"
ghe-backup-es-audit-log || printf %s \"audit-log \" >> \"$failures_file\"")
cmd_title=$(log_info "Backing up Git repositories ...")
commands+=("
echo \"$cmd_title\"
ghe-backup-repositories || printf %s \"repositories \" >> \"$failures_file\"")
# Pages backups are skipped only if GHE_BACKUP_PAGES is explicitly set to 'no' to guarantee backward compatibility.
# If a customer upgrades backup-utils but keeps the config file from a previous version, Pages backups still work as expected.
if [ "$GHE_BACKUP_PAGES" != "no" ]; then
cmd_title=$(log_info "Backing up GitHub Pages artifacts ...")
commands+=("
echo \"$cmd_title\"
ghe-backup-pages || printf %s \"pages \" >> \"$failures_file\"")
fi
cmd_title=$(log_info "Backing up storage data ...")
commands+=("
echo \"$cmd_title\"
ghe-backup-storage || printf %s \"storage \" >> \"$failures_file\"")
cmd_title=$(log_info "Backing up custom Git hooks ...")
commands+=("
echo \"$cmd_title\"
ghe-backup-git-hooks || printf %s \"git-hooks \" >> \"$failures_file\"")
if [ "$GHE_BACKUP_STRATEGY" = "rsync" ]; then
increment-progress-total-count 1
cmd_title=$(log_info "Backing up Elasticsearch indices ...")
commands+=("
echo \"$cmd_title\"
ghe-backup-es-rsync || printf %s \"elasticsearch \" >> \"$failures_file\"")
fi
if [ "$GHE_PARALLEL_ENABLED" = "yes" ]; then
"$GHE_PARALLEL_COMMAND" "${GHE_PARALLEL_COMMAND_OPTIONS[@]}" -- "${commands[@]}"
else
for c in "${commands[@]}"; do
eval "$c"
done
fi
if [ -s "$failures_file" ]; then
failures="$failures $(cat "$failures_file")"
fi
# git fsck repositories after the backup
if [ "$GHE_BACKUP_FSCK" = "yes" ]; then
log_info "Running git fsck on repositories ..."
ghe-backup-fsck "$GHE_SNAPSHOT_DIR" || failures="$failures fsck"
fi
# If everything was successful, mark the snapshot as complete, update the
# current symlink to point to the new snapshot and prune expired and failed
# snapshots.
if [ -z "$failures" ]; then
rm "incomplete"
rm -f "../current"
ln -s "$GHE_SNAPSHOT_TIMESTAMP" "../current"
if [[ $GHE_PRUNING_SCHEDULED != "yes" ]]; then
ghe-prune-snapshots
else
log_info "Expired and incomplete snapshots to be pruned separately"
fi
else
log_info "Skipping pruning snapshots, since some backups failed..."
fi
END_TIME=$(date +%s)
log_info "Runtime: $((END_TIME - START_TIME)) seconds"
log_info "Completed backup of $GHE_HOSTNAME in snapshot $GHE_SNAPSHOT_TIMESTAMP at $(date +"%H:%M:%S")"
# Exit non-zero and list the steps that failed.
if [ -z "$failures" ]; then
ghe_remote_logger "Completed backup from $(hostname) / snapshot $GHE_SNAPSHOT_TIMESTAMP successfully."
else
steps="${failures// /, }"
ghe_remote_logger "Completed backup from $(hostname) / snapshot $GHE_SNAPSHOT_TIMESTAMP with failures: ${steps}."
log_error "Error: Snapshot incomplete. Some steps failed: ${steps}. "
ghe_backup_finished
exit 1
fi
# Detect if the created backup contains any leaked ssh keys
log_info "Checking for leaked ssh keys ..."
ghe-detect-leaked-ssh-keys -s "$GHE_SNAPSHOT_DIR" || true
log_info "Backup of $GHE_HOSTNAME finished."
# Remove in-progress file
ghe_backup_finished