Skip to content

Commit 90d573c

Browse files
authored
Merge branch 'master' into hao/fix-host-check
2 parents 5b1cd03 + 78974b6 commit 90d573c

File tree

8 files changed

+232
-29
lines changed

8 files changed

+232
-29
lines changed

.github/workflows/main.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ jobs:
2121
- name: Install Dependencies (macOS)
2222
run: |
2323
brew install gnu-tar shellcheck jq pigz coreutils gnu-sed gnu-getopt
24-
brew unlink parallel
2524
brew install moreutils gawk
2625
if: matrix.os == 'macos-latest'
2726
- name: Get Sources

docs/requirements.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ be running GitHub Enterprise Server 2.12.x or 2.13.x. You can't restore a snapsh
6363

6464
**Note**: You _cannot_ restore a backup created from a newer verison of GitHub Enterprise Server to an older version. For example, an attempt to restore a snapshot of GitHub Enterprise Server 2.21 to a GitHub Enterprise Server 2.20 environment will fail with an error of `Error: Snapshot can not be restored to an older release of GitHub Enterprise Server.`.
6565

66+
## Multiple backup hosts
67+
68+
Using multiple backup hosts or backup configurations is not currently recommended.
69+
70+
Due to how some components of Backup Utiltiies (e.g. MSSQL) take incremental backups, running another instance of Backup Utilities may result in unrestorable snapshots as data may be split across backup hosts. If you still wish to have multiple instances of Backup Utilties for redundancy purposes or to run at different frequencies, ensure that they share the same `GHE_DATA_DIR` backup directory.
71+
6672
[1]: https://www.gnu.org/software/bash/
6773
[2]: https://git-scm.com/
6874
[3]: https://www.openssh.com/

share/github-backup-utils/ghe-backup-mssql

Lines changed: 152 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ set -e
1414
backup_dir="$GHE_SNAPSHOT_DIR/mssql"
1515
last_mssql=
1616
backup_command=
17-
take_full=
18-
take_diff=
17+
backup_type=
1918
full_expire=
2019
diff_expire=
2120
tran_expire=
@@ -53,23 +52,23 @@ find_timestamp() {
5352
echo $datetime_part
5453
}
5554

56-
ensure_same_dbs() {
55+
actions_dbs() {
5756
all_dbs=$(echo 'set -o pipefail; ghe-mssql-console -y -n -q "SET NOCOUNT ON; SELECT name FROM sys.databases"' | ghe-ssh "$GHE_HOSTNAME" /bin/bash)
58-
59-
remotes=()
6057
for db in $all_dbs; do
6158
if [[ ! "$db" =~ ^(master|tempdb|model|msdb)$ ]] && [[ "$db" =~ ^[a-zA-Z0-9_-]+$ ]]; then
62-
remotes+=("$db")
59+
echo "$db"
6360
fi
6461
done
62+
}
6563

64+
ensure_same_dbs() {
6665
locals=()
6766
while read -r file; do
6867
filename=$(basename "$file")
6968
locals+=("$filename")
7069
done < <(find "$1" \( -name "*.bak" -o -name "*.diff" -o -name "*.log" \))
7170

72-
for remote in "${remotes[@]}"; do
71+
for remote in $(actions_dbs); do
7372
remaining_locals=()
7473
for local in "${locals[@]}"; do
7574
if ! [[ "$local" == "$remote"* ]]; then
@@ -90,13 +89,67 @@ ensure_same_dbs() {
9089
fi
9190
}
9291

92+
run_query() {
93+
echo "set -o pipefail; ghe-mssql-console -y -n -q \"SET NOCOUNT ON; $1\"" | ghe-ssh "$GHE_HOSTNAME" /bin/bash | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
94+
}
95+
96+
get_latest_backup_file() {
97+
backups_dir=$1
98+
db=$2
99+
ext=$3
100+
101+
latest_full_backup=$(find "$backups_dir" -type f -name "$db*.$ext" | egrep '[0-9]{8}T[0-9]{6}' | sort | tail -n 1)
102+
latest_full_backup_file="${latest_full_backup##*/}"
103+
echo "$latest_full_backup_file"
104+
}
105+
106+
get_backup_val() {
107+
db=$1
108+
filename=$2
109+
column=$3
110+
run_query "
111+
SELECT s.$column
112+
FROM msdb.dbo.backupset s
113+
JOIN msdb.dbo.backupmediafamily f
114+
ON s.media_set_id = f.media_set_id
115+
WHERE s.database_name = '$db' AND f.physical_device_name LIKE '%$filename'"
116+
}
117+
118+
get_backup_checkpoint_lsn() {
119+
get_backup_val "$1" "$2" "checkpoint_lsn"
120+
}
121+
122+
get_backup_last_lsn() {
123+
get_backup_val "$1" "$2" "last_lsn"
124+
}
125+
126+
get_next_log_backup_starting_lsn() {
127+
db=$1
128+
# last_log_backup_lsn: The starting log sequence number of the next log backup
129+
# https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-recovery-status-transact-sql
130+
run_query "
131+
SELECT last_log_backup_lsn
132+
FROM sys.database_recovery_status drs
133+
JOIN sys.databases db on drs.database_id = db.database_id
134+
WHERE db.name = '$db'"
135+
}
136+
137+
get_next_diff_backup_base_lsn() {
138+
db=$1
139+
# differential_base_lsn: Base for differential backups. Data extents changed after this LSN will be included in a differential backup.
140+
# https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-master-files-transact-sql
141+
run_query "
142+
SELECT differential_base_lsn
143+
FROM sys.master_files mf
144+
WHERE mf.name = '$db'"
145+
}
146+
93147
last_mssql=$GHE_DATA_DIR/current/mssql
94148

95149
if [ ! -d $last_mssql ] \
96150
|| [ -z "$(find $last_mssql -type f -name '*.bak' | head -n 1)" ]; then
97151
ghe_verbose "Taking first full backup"
98-
take_full=1
99-
backup_command='ghe-export-mssql'
152+
backup_type="full"
100153
else
101154
ensure_same_dbs "$last_mssql"
102155

@@ -128,25 +181,89 @@ else
128181
ghe_verbose "current $current, full expire $full_expire, \
129182
diff expire $diff_expire, tran expire $tran_expire"
130183

131-
# Determine the type of backup to take
184+
# Determine the type of backup to take based on expiry time
132185
if [ $current -gt $full_expire ]; then
133-
ghe_verbose "Taking full backup"
134-
take_full=1
135-
backup_command='ghe-export-mssql'
186+
backup_type='full'
136187
elif [ $current -gt $diff_expire ]; then
137-
ghe_verbose "Taking diff backup"
138-
take_diff=1
139-
backup_command='ghe-export-mssql -d'
188+
backup_type='diff'
140189
elif [ $current -gt $tran_expire ]; then
141-
ghe_verbose "Taking transaction backup"
142-
backup_command='ghe-export-mssql -t'
190+
backup_type='transaction'
143191
fi
192+
193+
# Upgrade to a full backup if the diff/transaction backup might not be restorable due to other backup mechanisms interfering
194+
# with the transaction LSN chain or differential base LSN.
195+
if [ "$backup_type" == 'diff' ] || [ "$backup_type" == 'transaction' ]; then
196+
ghe_verbose "Checking for conflicting backups to ensure a $backup_type backup is sufficient"
197+
198+
for db in $(actions_dbs); do
199+
# Ensure that a diff backup will be based on the full backup file we have (rather than one another backup mechanism took)
200+
if [ "$backup_type" == 'diff' ]; then
201+
full_backup_file=$(get_latest_backup_file "$last_mssql" "$db" "bak")
202+
if [[ "$full_backup_file" == "" ]]; then
203+
ghe_verbose "Taking a full backup instead of a diff backup because for $db a full backup file wasn't found"
204+
backup_type="full"
205+
break
206+
fi
207+
208+
full_backup_file_checkpoint_lsn=$(get_backup_checkpoint_lsn "$db" "$full_backup_file")
209+
if [[ "$full_backup_file_checkpoint_lsn" = "NULL" ]] || [[ "$full_backup_file_checkpoint_lsn" == "" ]]; then
210+
ghe_verbose "Taking a full backup instead of a diff backup because for $db the checkpoint LSN for $full_backup_file couldn't be determined"
211+
backup_type="full"
212+
break
213+
fi
214+
215+
next_diff_backup_base_lsn=$(get_next_diff_backup_base_lsn "$db")
216+
if [[ "$next_diff_backup_base_lsn" = "NULL" ]] || [[ "$next_diff_backup_base_lsn" == "" ]]; then
217+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the base LSN for the next diff backup couldn't be determined"
218+
backup_type="full"
219+
break
220+
fi
221+
222+
# The base of the diff backup we're about to take must exactly match the checkpoint LSN of the full backup file we have
223+
if [[ "$next_diff_backup_base_lsn" -ne "$full_backup_file_checkpoint_lsn" ]]; then
224+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the diff would have base LSN $next_diff_backup_base_lsn yet our full backup has checkpoint LSN $full_backup_file_checkpoint_lsn"
225+
backup_type="full"
226+
break
227+
fi
228+
fi
229+
230+
# Ensure that a transaction log backup will immediately follow the previous one
231+
latest_log_backup_file=$(get_latest_backup_file "$last_mssql" "$db" "log")
232+
if [[ "$latest_log_backup_file" == "" ]]; then
233+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db a previous transaction log backup wasn't found"
234+
backup_type="full"
235+
break
236+
fi
237+
238+
latest_log_backup_last_lsn=$(get_backup_last_lsn "$db" "$latest_log_backup_file")
239+
if [[ "$latest_log_backup_last_lsn" = "NULL" ]] || [[ "$latest_log_backup_last_lsn" == "" ]]; then
240+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the LSN range for $latest_log_backup_file couldn't be determined"
241+
backup_type="full"
242+
break
243+
fi
244+
245+
next_log_backup_starting_lsn=$(get_next_log_backup_starting_lsn "$db")
246+
if [[ "$next_log_backup_starting_lsn" = "NULL" ]] || [[ "$next_log_backup_starting_lsn" == "" ]]; then
247+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the starting LSN for the next log backup couldn't be determined"
248+
backup_type="full"
249+
break
250+
fi
251+
252+
# The starting LSN of the backup we're about to take must be equal to (or before) the last LSN from the last backup,
253+
# otherwise there'll be a gap and the logfiles won't be restorable
254+
if [[ "$next_log_backup_starting_lsn" -gt "$latest_log_backup_last_lsn" ]]; then
255+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db a gap would exist between the last backup ending at LSN $latest_log_backup_last_lsn and next backup starting at $next_log_backup_starting_lsn"
256+
backup_type="full"
257+
break
258+
fi
259+
done
260+
fi
144261
fi
145262

146263
# Make sure root backup dir exists if this is the first run
147264
mkdir -p "$backup_dir"
148265

149-
# Create hard links to save disk space and time
266+
# Use hard links to "copy" over previous applicable backups to the new snapshot folder to save disk space and time
150267
if [ -d $last_mssql ]; then
151268
for p in $last_mssql/*
152269
do
@@ -156,15 +273,18 @@ if [ -d $last_mssql ]; then
156273
extension="${filename##*.}"
157274
transfer=
158275

159-
if [ $extension = "bak" ] && [ -z $take_full ]; then
276+
# Copy full backups unless we're taking a new full backup
277+
if [ $extension = "bak" ] && [ "$backup_type" != 'full' ]; then
160278
transfer=1
161279
fi
162280

163-
if [ $extension = "diff" ] && [ -z $take_full ] && [ -z $take_diff ]; then
281+
# Copy diff backups unless we're taking a new full or diff backup
282+
if [ $extension = "diff" ] && [ "$backup_type" != 'full' ] && [ "$backup_type" != 'diff' ]; then
164283
transfer=1
165284
fi
166285

167-
if [ $extension = "log" ] && [ -z $take_full ] && [ -z $take_diff ]; then
286+
# Copy transaction log backups unless we're taking a new full or diff backup
287+
if [ $extension = "log" ] && [ "$backup_type" != 'full' ] && [ "$backup_type" != 'diff' ]; then
168288
transfer=1
169289
fi
170290

@@ -175,7 +295,16 @@ if [ -d $last_mssql ]; then
175295
done
176296
fi
177297

178-
if [ -n "$backup_command" ]; then
298+
if [ -n "$backup_type" ]; then
299+
ghe_verbose "Taking $backup_type backup"
300+
301+
backup_command='ghe-export-mssql'
302+
if [ "$backup_type" = "diff" ]; then
303+
backup_command='ghe-export-mssql -d'
304+
elif [ "$backup_type" = "transaction" ]; then
305+
backup_command='ghe-export-mssql -t'
306+
fi
307+
179308
bm_start "$(basename $0)"
180309
ghe-ssh "$GHE_HOSTNAME" -- "$backup_command" || failures="$failures mssql"
181310
bm_end "$(basename $0)"

test/bin/ghe-mssql-console

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
#!/usr/bin/env bash
2-
# Returns environment variable REMOTE_DBS, the variable should be set as space-delimited string
32
# Tests use this to emulate ghe-mssql-console from a remote GitHub Enterprise Server
4-
echo $REMOTE_DBS
3+
if [[ "$*" == *"SELECT name FROM sys.databases"* ]]; then
4+
# REMOTE_DBS should be set as space-delimited string with the test database names
5+
echo "$REMOTE_DBS"
6+
elif [[ "$*" == *"last_log_backup_lsn"* ]]; then
7+
# Starting log sequence number of the next log backup.
8+
# Should match the most recent log backup last_lsn.
9+
echo "$NEXT_LOG_BACKUP_STARTING_LSN"
10+
elif [[ "$*" == *"last_lsn"* ]]; then
11+
# Last LSN of the transaction log backup file being looked up.
12+
echo "$LOG_BACKUP_FILE_LAST_LSN"
13+
elif [[ "$*" == *"differential_base_lsn"* ]]; then
14+
# Next diff backup base LSN. Should match full checkpoint LSN.
15+
echo "$DIFFERENTIAL_BASE_LSN"
16+
elif [[ "$*" == *"checkpoint_lsn"* ]]; then
17+
# Checkpoint LSN of the full backup file being looked up.
18+
echo "$FULL_BACKUP_FILE_LSN"
19+
else
20+
echo "UNKNOWN QUERY: ghe-mssql-console test stub failed on: $*"
21+
fi

test/bin/systemctl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ghe-fake-true

test/test-ghe-backup.sh

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ begin_test "ghe-backup takes full backup upon expiration"
376376
set -e
377377
enable_actions
378378
enable_minio
379-
export REMOTE_DBS="full_mssql"
379+
setup_mssql_stubs
380380

381381
setup_mssql_backup_file "full_mssql" 11 "bak"
382382

@@ -391,7 +391,7 @@ begin_test "ghe-backup takes diff backup upon expiration"
391391
set -e
392392
enable_actions
393393
enable_minio
394-
export REMOTE_DBS="full_mssql"
394+
setup_mssql_stubs
395395

396396
setup_mssql_backup_file "full_mssql" 7 "bak"
397397

@@ -406,7 +406,7 @@ begin_test "ghe-backup takes transaction backup upon expiration"
406406
(
407407
set -e
408408
enable_actions
409-
export REMOTE_DBS="full_mssql"
409+
setup_mssql_stubs
410410

411411
setup_mssql_backup_file "full_mssql" 3 "bak"
412412

@@ -425,6 +425,7 @@ begin_test "ghe-backup warns if database names mismatched"
425425
rm -rf "$GHE_DATA_DIR/current/mssql"
426426
mkdir -p "$GHE_DATA_DIR/current/mssql"
427427

428+
setup_mssql_stubs
428429
export REMOTE_DBS="full_mssql_1 full_mssql_2 full_mssql_3"
429430

430431
add_mssql_backup_file "full_mssql_1" 3 "bak"
@@ -437,6 +438,38 @@ begin_test "ghe-backup warns if database names mismatched"
437438
)
438439
end_test
439440

441+
begin_test "ghe-backup upgrades diff backup to full if diff base mismatch"
442+
(
443+
set -e
444+
enable_actions
445+
setup_mssql_stubs
446+
export FULL_BACKUP_FILE_LSN=100
447+
export DIFFERENTIAL_BASE_LSN=101 # some other full backup interfered and moved up the diff base!
448+
449+
setup_mssql_backup_file "full_mssql" 7 "bak"
450+
451+
output=$(ghe-backup -v)
452+
echo "$output" | grep "Taking a full backup instead of a diff backup"
453+
echo "$output" | grep "Taking full backup"
454+
)
455+
end_test
456+
457+
begin_test "ghe-backup upgrades transaction backup to full if LSN chain break"
458+
(
459+
set -e
460+
enable_actions
461+
setup_mssql_stubs
462+
export LOG_BACKUP_FILE_LAST_LSN=100
463+
export NEXT_LOG_BACKUP_STARTING_LSN=101 # some other log backup interfered and stole 1 LSN!
464+
465+
setup_mssql_backup_file "full_mssql" 3 "bak"
466+
467+
output=$(ghe-backup -v)
468+
echo "$output" | grep "Taking a full backup instead of a transaction backup"
469+
echo "$output" | grep "Taking full backup"
470+
)
471+
end_test
472+
440473
begin_test "ghe-backup takes backup of Actions settings"
441474
(
442475
set -e

test/test-ghe-restore-parallel.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
# ghe-restore command tests run in parallel
33
set -e
44

5+
# Currently disabled on macOS due to rsync issues
6+
if [[ "$OSTYPE" == "darwin"* ]]; then
7+
echo "Skipping $(basename "$0") tests as they are temporarily disabled on macOS: https://github.com/github/backup-utils/issues/841"
8+
exit 0
9+
fi
10+
511
export GHE_PARALLEL_ENABLED=yes
612

713
# use temp dir to fix rsync file issues in parallel execution:

0 commit comments

Comments
 (0)