/
setup.sh
executable file
·402 lines (357 loc) · 11.6 KB
/
setup.sh
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
394
395
396
397
398
399
400
401
402
#! /bin/bash
# This file sets up the directories, scripts, and tasks needed for BUNS backups.
# It should be run if the paths for any of the Rulesets, the monitor
# directory name, the base script directory, or the config file location
# itself has changed. It must be run from the same directory that contains
# the read_config script unless the -r option is used. It also requires the
# presence of the user script template directory ('user').
# It does not need to be run if only the rules in the Rulesets change.
#
# The -c option indicates the config file to use. If it is not present, the
# default file '/etc/buns/conf' is used, unless the -i (install) option is used.
# When the install option is used, the -c indicates which config file to install
# to the default location.
#
# The -i option (install) will copy the config file to the default location
# '/etc/buns.conf', and install BUNS scripts from the local directory to the
# SCRIPT_DIR indicated by the config file. If a config file is not indicated
# by the -c option, the local file 'buns.conf' is used.
#
# The -u option (user) will determine where the user scripts (those that run the
# actual backup called by the remote machine) are installed. It will also set
# the selected user as the owner of the backup monitor files.
#
# The -d option (directory) indicates the directory to install the user scripts to.
# This option overrides -u as the destination for user scripts.
# If neither -u nor -d is specified, and the BACKUP_USER is not set in the config
# file, the user scripts will be installed to the current directory.
#
#
# Note that running this script will clear the log file.
#
config_filename="buns.conf"
default_config="/etc/${config_filename}"
function exit_bad_argument {
cat >&2 <<EOF
Usage: $0 [-i] [-c CONFIG FILE ] [-d USER_DIR] [-u USER] [-r READ_CONFIG SCRIPT ]
Default config $default_config is used if not specified (unless using -i).
The -i option will install the config file to $default_config, and also install
BUNS scripts to the directory indicated in the config file. (User scripts are
always installed.)
The -d option will install user scripts to the directory provided, overriding
the config file USER_SCRIPT_DIR setting.
The -u option will set the indicated user to handle backup scripts, overriding
the config file BACKUP_USER setting.
The -r option indicates the location of the script to read the config file.
When not specified, the current directory is used as the default location.
EOF
exit 1
}
# Given a variable name, will produce an output line setting that variable
function resolve_line {
local lv=$1
line_output="${lv}="
if [[ -z "${!lv}" ]]; then
return 1
fi
local indx=0
local contents=""
local lv_ra="${lv}[@]"
for rv in "${!lv_ra}"; do
contents+="\"${rv}\" "
indx+=1
done
if [[ $indx -gt 1 ]]; then
line_output+="(${contents})"
else
line_output+="${contents}"
fi
return 0
}
# Test if a BTRFS subvolume already exists
function isBTRFSsub {
btrfs subvolume show "$1" &>/dev/null && return 0
return 1
}
# Set defaults
install_enabled=false
uscript_dir="."
script_dir="."
user_provided=false
user_dir_provided=false
config_script="read_config.sh"
config_file="$default_config"
while getopts "ic:d:u:r:" options; do
case "$options" in
i)
install_enabled=true
;;
c)
config_file="$OPTARG"
;;
d)
uscript_dir="$OPTARG"
user_dir_provided=true
;;
u)
script_user="$OPTARG"
user_provided=true
;;
r)
config_script="$OPTARG"
;;
:)
printf "Error: -%s requires an argument.\n" "$OPTARG" >&2
exit_bad_argument
;;
*)
exit_bad_argument
;;
esac
done
config_error_msg="ERROR: Could not install config file to ${default_config}."
if [[ "$install_enabled" = true ]]; then
if [[ "$config_file" = "$default_config" ]]; then
config_source="$config_filename" # Use local
else
config_source="$config_file" # Use command-specified
fi
cp "$config_source" $default_config
[[ $? = 0 ]] || { echo $config_error_msg >&2; exit 1; }
chown $(/usr/bin/id -run) $default_config
[[ $? = 0 ]] || { echo $config_error_msg >&2; exit 1; }
config_file="$default_config" # Use the installed one for remainder
fi
# Load the config file now
[[ -e $config_script ]] || { echo "ERROR: Script to read config file not present." >&2; exit 1; }
source $config_script "$config_file"
# Reset the log file
cat >"$LOG_FILE" <<EOF
BUNS log file
Setup initialized on $(date)
EOF
install_error_msg="ERROR: Could not install files to $SCRIPT_DIR."
# Copy BUNS scripts to script root (if install option enabled)
iscripts=("cull.sh" "file_response.sh" "snapshot.sh" "setup.sh")
if [[ "$install_enabled" = true ]]; then
echo "Create directory ${SCRIPT_DIR}" >> "$LOG_FILE"
mkdir -p $SCRIPT_DIR
[[ $? = 0 ]] || { echo $install_error_msg >&2; exit 1; }
fi
if ! [[ -d $SCRIPT_DIR ]]; then
printf "ERROR: Script install directory not found: %s.\n" "$SCRIPT_DIR" >&2
exit_bad_argument
fi
if [[ "$install_enabled" = true ]]; then
echo "Installing scripts to $SCRIPT_DIR" >> "$LOG_FILE"
for bscript in ${iscripts[@]}; do
[[ -e "$bscript" ]] || { echo "Script missing: $bscript" >&2; exit 1; }
cp "$bscript" "$SCRIPT_DIR"
[[ $? = 0 ]] || { echo $install_error_msg >&2; exit 1; }
done
cp "$config_script" "$SCRIPT_DIR"
[[ $? = 0 ]] || { echo $install_error_msg >&2; exit 1; }
# Copy templates (user & uninstall)
cp -r './templates' "$SCRIPT_DIR"
[[ $? = 0 ]] || { echo $install_error_msg >&2; exit 1; }
fi
# Create user scripts from templates
if [[ "$user_provided" = false ]]; then
script_user="$BACKUP_USER"
fi
if [[ "$user_dir_provided" = false ]]; then
uscript_dir=$(eval echo "~${script_user}/${USER_SCRIPT_DIR}")
fi
if [[ "$install_enabled" = true ]]; then
echo "Create directory ${uscript_dir}" >> "$LOG_FILE"
mkdir -p $uscript_dir >&2
[[ $? = 0 ]] || { echo "ERROR: Could not create user script directory.">&2; exit 1; }
fi
if ! [[ -d $uscript_dir ]]; then
printf "ERROR: User script directory not found: %s.\n" "$uscript_dir" >&2
exit_bad_argument
fi
echo "Writing user scripts to ${uscript_dir}" >>"$LOG_FILE"
user_script_source="${SCRIPT_DIR}/templates/user"
user_script_error="ERROR: Could not install user scripts to ${uscript_dir}."
declare -a USER_SCRIPTS # Track installed user scripts (for uninstall)
for uscript in $user_script_source/*.template; do
[[ -e "$uscript" ]] || continue
config_mode=false
ch_result=0
output_basename=$(basename "$uscript")
output_filename=${output_basename%%.template}
output_file="${uscript_dir}/${output_filename}.sh"
while IFS=$'\n' read line; do
if [[ "$config_mode" = true ]]; then
if [[ $line =~ ^#DONE ]]; then
config_mode=false
continue
else
resolve_line $line
if [[ $? = 1 ]]; then
echo $user_script_error >&2
printf "Required config variable %s in %s is null.\n" "$line" "$uscript" >&2
exit 1
else
# Add variable value after its name
echo $line_output
fi
fi
else
if [[ $line =~ ^#CONFIG ]]; then
config_mode=true
continue
else
# Just copy the line
printf '%s\n' "$line"
fi
fi
done <"$uscript" >"$output_file"
# Change permissions/ownership for user script files
chmod ug+x "$output_file"
ch_result=$(($ch_result && $?))
chmod go-w "$output_file"
ch_result=$(($ch_result && $?))
chmod o-x "$output_file"
ch_result=$(($ch_result && $?))
if ! [[ -z $script_user ]]; then
chown "$script_user":"$BACKUP_GROUP" "$output_file"
ch_result=$(($ch_result && $?))
USER_SCRIPTS+=("$output_file")
else
chown :"$BACKUP_GROUP" "$output_file"
ch_result=$(($ch_result && $?))
fi
[[ $ch_result -eq 0 ]] || { echo $user_script_error >&2; exit 1; }
done
# Create start-up cleanup script
cleanup_script="${SCRIPT_DIR}/cleanup.sh"
cat > $cleanup_script <<EOF
#! /bin/bash
# Automatically generated file for BUNS directory cleanup on startup.
EOF
# Get the current incrontab for temporary editing
temp_incron=/tmp/bunsincron
temp_swap=/tmp/buns_scratch
incrontab -l >"$temp_incron"
incron_edited=false
declare -a INCRON_ENTRIES
declare -a MONITOR_PATHS
# Do all for each path in the Ruleset
for path in "${!RULESETS[@]}"; do
# Create/edit incron entry
incron_partial="${path}/${MONITOR_DIR} IN_CLOSE_WRITE ${SCRIPT_DIR}/file_response.sh" # Used for matching when removing entries
incronline="${incron_partial} \$@ \$# ${config_file}"
editregex="^${path}/${MONITOR_DIR}\s"
while read line; do
if [[ "$line" =~ $editregex ]]; then
line="$incronline"
incron_edited=true
fi
echo "$line"
done <"$temp_incron" >"$temp_swap"
if [[ "$incron_edited" == false ]]; then
echo "$incronline" >>"$temp_swap"
fi
INCRON_ENTRIES+=("$incron_partial")
mv "$temp_swap" "$temp_incron"
incron_edited=false
editregex=""
# Create directories if necessary
# Make Monitor & Status directory accessible to group
mod="${path}/${MONITOR_DIR}"
echo "Create directory ${mod}" >>"$LOG_FILE"
mkdir -p "${mod}" >>"$LOG_FILE"
chown :"$BACKUP_GROUP" "$mod" >>"$LOG_FILE"
chmod g+w "$mod" >>"$LOG_FILE"
bud="${path}/${BACKUP_DIR}"
case "$SNAPSHOT_METHOD" in
btrfs | BTRFS )
echo "Create BTRFS subvolume ${bud}" >>"$LOG_FILE"
if isBTRFSsub "$bud"; then
echo "Subvolume exists, skipping creation." >>"$LOG_FILE"
else
btrfs subvol create "${bud}" >>"$LOG_FILE"
fi
;;
cp | COPY | copy )
echo "Create directory ${bud}" >>"$LOG_FILE"
mkdir -p "${bud}" >>"$LOG_FILE"
;;
*)
printf "Unrecognized snapshot method %s.\n" "$SNAPSHOT_METHOD"
exit 1
;;
esac
chown :"$BACKUP_GROUP" "$bud"
chmod g+w "$bud"
# Repository not modifiable by group
repd="${path}/${BACKUP_REPOSITORY}"
echo "Create directory ${repd}" >>"$LOG_FILE"
mkdir -p "$repd" >>"$LOG_FILE"
# Add entry to cleanup script
echo "rm -rf ${path}/${MONITOR_DIR}/*" >>"$cleanup_script"
MONITOR_PATHS+=("$mod")
done
# Load modified incron file
incrontab "$temp_incron" >>"$LOG_FILE" 2>&1
# Add the cleanup script to systemd to run at startup
chmod +x $cleanup_script >>"$LOG_FILE" 2>&1
chmod a-w $cleanup_script >>"$LOG_FILE" 2>&1
servicename=buns_cleanup
servicefile=/etc/systemd/system/$servicename.service
echo "Adding systemd service ${servicename} to run at startup" >>"$LOG_FILE"
cat >$servicefile <<EOF
[Unit]
Description=BUNS File cleanup
[Service]
ExecStart=$cleanup_script
[Install]
WantedBy=default.target
EOF
systemctl daemon-reload >>"$LOG_FILE" 2>&1
systemctl enable $servicename >>"$LOG_FILE" 2>&1
# Create uninstall script
uninstaller="${SCRIPT_DIR}/templates/buns_uninstall.template"
config_mode=false
output_basename=$(basename "$uninstaller")
output_filename=${output_basename%%.template}
uninstall_script="${output_filename}.sh"
uninstall_script_error="ERROR: Could not create uninstaller script"
if [[ "$install_enabled" = true ]]; then
uninstall_script="${SCRIPT_DIR}/${uninstall_script}"
fi
echo "Creating uninstall script ${uninstall_script}" >> "$LOG_FILE"
CLEANUP_SCRIPT=$cleanup_script
SYSTEM_SERVICE=$servicename
CONFIG_FILE=$config_file
while IFS=$'\n' read line; do
if [[ "$config_mode" = true ]]; then
if [[ $line =~ ^#DONE ]]; then
config_mode=false
continue
else
resolve_line $line
if [[ $? = 1 ]]; then
echo $uninstall_script_error >&2
printf "Required config variable %s in %s is null.\n" "$line" "$uninstaller" >&2
exit 1
else
echo $line_output
fi
fi
else
if [[ $line =~ ^#CONFIG ]]; then
config_mode=true
continue
else
# Just copy the line
printf '%s\n' "$line"
fi
fi
done <"$uninstaller" >"$uninstall_script"
chmod +x $uninstall_script >> "$LOG_FILE" 2>&1
chmod a-w $uninstall_script >> "$LOG_FILE" 2>&1
echo "Setup complete." >>"$LOG_FILE"
echo "Setup complete. Refer to ${LOG_FILE} for details."