/
ghe-backup
executable file
·241 lines (198 loc) · 7.87 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
#!/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.
#/
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
;;
-*)
echo "Error: invalid argument: '$1'" 1>&2
exit 1
;;
*)
break
;;
esac
done
# 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"
# Used to record failed backup steps
failures=
# 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 src
touch src/testfile
if ! ln -s /data/does/not/exist/hooks/ src/ >/dev/null 2>&1; then
echo "Error: the filesystem containing $GHE_DATA_DIR does not support symbolic links." 1>&2
echo "Git 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
echo "Error: rsync encountered an error that could indicate a problem with permissions," 1>&2
echo "hard links, symbolic links, or another issue that may affect backups." 1>&2
echo "$output"
exit 1
fi
if [ "$(ls -il dest1/testfile | awk '{ print $1 }')" != "$(ls -il dest2/testfile | awk '{ print $1 }')" ]; then
echo "Error: the filesystem containing $GHE_DATA_DIR does not support hard links." 1>&2
echo "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
# Cleanup SSH multiplexing
ghe-ssh --clean "$GHE_HOSTNAME"
}
# Setup exit traps
trap 'cleanup' EXIT
trap 'exit $?' INT # ^C always terminate
if [ -h ../in-progress ]; then
echo "Error: detected a backup already in progress from a previous version of ghe-backup." 1>&2
echo "If there is no backup in progress anymore, please remove" 1>&2
echo "the $GHE_DATA_DIR/in-progress file." 1>&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 | grep ghe-backup; then
# We can safely remove in-progress, ghe-prune-snapshots
# will clean up the failed backup.
unlink ../in-progress
else
echo "Error: backup process $pid of $GHE_HOSTNAME already in progress in snapshot $snapshot. Aborting." 1>&2
exit 1
fi
fi
echo "$GHE_SNAPSHOT_TIMESTAMP $$" > ../in-progress
echo "Starting backup of $GHE_HOSTNAME with backup-utils v$BACKUP_UTILS_VERSION in snapshot $GHE_SNAPSHOT_TIMESTAMP"
# 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
if [ -n "$GHE_ALLOW_REPLICA_BACKUP" ]; then
echo "Warning: backing up a high availability replica may result in inconsistent or unreliable backups."
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 ||
echo "Warning: storing backup-utils version remotely failed."
echo "Backing up GitHub settings ..."
ghe-backup-settings || failures="$failures settings"
echo "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"
echo "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"
echo "Backing up MySQL database ..."
bm_start "ghe-export-mysql"
echo 'set -o pipefail; ghe-export-mysql | gzip' |
ghe-ssh "$GHE_HOSTNAME" -- /bin/bash > mysql.sql.gz || failures="$failures mysql"
bm_end "ghe-export-mysql"
echo "Backing up Redis database ..."
ghe-backup-redis > redis.rdb || failures="$failures redis"
echo "Backing up audit log ..."
ghe-backup-es-audit-log || failures="$failures audit-log"
echo "Backing up hookshot logs ..."
ghe-backup-es-hookshot || failures="$failures hookshot"
echo "Backing up Git repositories ..."
ghe-backup-repositories || failures="$failures repositories"
echo "Backing up GitHub Pages ..."
ghe-backup-pages || failures="$failures pages"
echo "Backing up storage data ..."
ghe-backup-storage || failures="$failures storage"
echo "Backing up custom Git hooks ..."
ghe-backup-git-hooks || failures="$failures git-hooks"
if [ "$GHE_BACKUP_STRATEGY" = "rsync" ]; then
echo "Backing up Elasticsearch indices ..."
ghe-backup-es-rsync || failures="$failures elasticsearch"
fi
# git fsck repositories after the backup
if [ "$GHE_BACKUP_FSCK" = "yes" ]; then
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"
ghe-prune-snapshots
fi
echo "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="$(echo $failures | sed 's/ /, /g')"
ghe_remote_logger "Completed backup from $(hostname) / snapshot $GHE_SNAPSHOT_TIMESTAMP with failures: ${steps}."
echo "Error: Snapshot incomplete. Some steps failed: ${steps}. "
exit 1
fi
# Detect if the created backup contains any leaked ssh keys
echo "Checking for leaked ssh keys ..."
ghe-detect-leaked-ssh-keys -s "$GHE_SNAPSHOT_DIR" || true
# Make sure we exit zero after the conditional
true