-
Notifications
You must be signed in to change notification settings - Fork 78
/
grub-btrfsd
executable file
·340 lines (314 loc) · 11.5 KB
/
grub-btrfsd
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
#!/usr/bin/env bash
# Copyright 2023 Pascal Jaeger
# Distributed under the terms of the GNU General Public License v3
# Update GRUB when new BTRFS snapshots are created.
sighandler() {
trap '""' SIGINT SIGTERM
vlog "Parent $$: Received signal SIGINT/ SIGTERM"
kill 0
wait
exit 0
}
setcolors() {
if [ "${1}" = true ]; then
GREEN=$'\033[0;32m'
RED=$'\033[0;31m'
CYAN=$'\033[;36m'
RESET=$'\033[0m'
fi
if [ "${1}" = false ]; then
GREEN=$'\033[0;0m'
RED=$'\033[0;0m'
CYAN=$'\033[;0m'
RESET=$'\033[0m'
fi
}
print_help() {
echo "${CYAN}[?] Usage:"
echo "${0##*/} [-h, --help] [-c, --no-color] [-l, --log-file LOG_FILE] [-r, --recursive] [-s, --syslog] [-t, --timeshift-auto] [-v, --verbose] SNAPSHOTS_DIRS"
echo
echo "SNAPSHOTS_DIRS Snapshot directories to watch, without effect when --timeshift-auto"
echo
echo "Optional arguments:"
echo "-c, --no-color Disable colors in output"
echo "-l, --log-file Specify a logfile to write to"
echo "-r, --recursive Watch snapshots directory recursively"
echo "-s, --syslog Write to syslog"
echo "-o, --timeshift-old Look for snapshots in directory of Timeshift <v22.06 (requires --timeshift-auto)"
echo "-t, --timeshift-auto Automatically detect Timeshifts snapshot directory"
echo "-v, --verbose Let the log of the daemon be more verbose"
echo "-h, --help Display this message"
echo
echo "Version ${GRUB_BTRFS_VERSION}${RESET}"
}
log() {
echo "${2}"$1"${RESET}"
if [ ${syslog} = true ]; then
logger -p user.notice -t ${0##*/}"["$$"]" "$1"
fi
if [ ${#logfile} -gt 1 ]; then
echo "$(date) ${1}" >> "${logfile}"
fi
}
vlog() {
if [ ${verbose} = true ]; then
echo "${2}"$1"${RESET}"
if [ ${syslog} = true ]; then
logger -p user.notice -t ${0##*/} "$1"
fi
if [ ${#logfile} -gt 1 ]; then
echo "$(date) ${1}" >> "${logfile}"
fi
fi
}
err() {
echo "${2}"${1}"${RESET}" >&2
if [ ${syslog} = true ]; then
logger -p user.error -t ${0##*/} "$1"
fi
if [ ${#logfile} -gt 1 ]; then
echo "$(date) error: ${1}" >> "${logfile}"
fi
}
parse_arguments() {
while getopts :l:ctvrsh-: opt; do
case "$opt" in
-)
case "${OPTARG}" in
no-color)
setcolors false
;;
log-file)
logfile="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
;;
timeshift-auto)
timeshift_auto=true
;;
timeshift-old)
timeshift_old=true
;;
verbose)
verbose=true
;;
recursive)
recursive=true
;;
syslog)
syslog=true
;;
help)
print_help
exit 0
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
err "[!] Unknown option --${OPTARG}" "${RED}" >&2
echo
fi
print_help
exit 1
;;
esac;;
c)
setcolors false
;;
l)
logfile="${OPTARG}"
;;
t)
timeshift_auto=true
;;
o)
timeshift_old=true
;;
v)
verbose=true
;;
r)
recursive=true
;;
s)
syslog=true
;;
h)
print_help
exit 0
;;
*)
if [ "$OPTERR" = 1 ] || [ "${optspec:0:1}" = ":" ]; then
err "[!] Non-option argument: '-${OPTARG}'" "${RED}" >&2
echo
fi
print_help
exit 1
;;
esac
done
}
checks() {
# check if inotify exists, see issue #227
if ! command -v inotifywait >/dev/null 2>&1; then
err "[!] inotifywait was not found, exiting. Is inotify-tools installed?" "${RED}" >&2
exit 1
fi
if [ ${timeshift_auto} = false ] && [ ${timeshift_old} = true ]; then
err "[!] Flag --timeshift-old requires flag --timeshift-auto" "${RED}" >&2
exit 1
fi
if ! [ ${timeshift_auto} = true ]; then
for snapdir in "${snapdirs[@]}"
do
if ! [ -d ${snapdir} ]; then
err "[!] No directory found at ${snapdir}" "${RED}" >&2
err "[!] Please specify a valid snapshot directory" "${RED}" >&2
exit 1
fi
done
fi
}
setup() {
if [ ${#logfile} -gt 1 ]; then
touch "${logfile}"
echo "GRUB-BTRFSD log $(date)" >> "${logfile}"
fi
if [ ${verbose} = true ]; then
inotify_qiet_flag=""
else
inotify_qiet_flag=" -q -q "
fi
if [ ${recursive} = true ]; then
inotify_recursive_flag=" -r "
else
inotify_recursive_flag=""
fi
if [ ${timeshift_auto} = true ]; then
watchtime=15
[ -d /run/timeshift ] || mkdir /run/timeshift
else
watchtime=0
fi
}
create_grub_menu() {
# create the grub submenu of the whole grub menu, depending on whether the submenu already exists
# and gives feedback if it worked
if grep "snapshots-btrfs" "${GRUB_BTRFS_GRUB_DIRNAME:-/boot/grub}"/grub.cfg; then
if /etc/grub.d/41_snapshots-btrfs; then
log "Grub submenu recreated" "${GREEN}"
else
err "[!] Error during grub submenu creation (grub-btrfs error)" "${RED}"
fi
else
if ${GRUB_BTRFS_MKCONFIG:-grub-mkconfig} -o "${GRUB_BTRFS_GRUB_DIRNAME:-/boot/grub}"/grub.cfg; then
log "Grub menu recreated" "${GREEN}"
else
err "[!] Error during grub menu creation (grub/ grub-btrfs error)" "${RED}"
fi
fi
}
set_snapshot_dir() {
# old timeshift has it's snapshot dir in a different location
if [ "${timeshift_old}" = true ]; then
snapdir="/run/timeshift/backup/timeshift-btrfs/snapshots"
else
snapdir="/run/timeshift/${timeshift_pid}/backup/timeshift-btrfs/snapshots"
fi
}
daemon_function() {
trap 'vlog "$BASHPID: Received SIGINT/ SIGTERM"; exit 0' SIGINT SIGTERM
# start the actual daemon
snapdir=$1
vlog "Subdaemon function started, PID: $BASHPID" "${GREEN}"
vlog "${BASHPID}: Entering infinite while for $snapdir" "${GREEN}"
vlog "${BASHPID}: Snapshot dir watchtimeout: $watchtime"
while true; do
runs=false
if [ ${timeshift_auto} = true ] && ! [ "${timeshift_pid}" -gt 0 ] ; then
# watch the timeshift folder for a folder that is created when timeshift starts up
sleep 1 # for safety so the outer while is not going crazy
if [ "${timeshift_pid}" -eq -2 ]; then
log "${BASHPID}: detected timeshift shutdown"
fi
timeshift_pid=$(ps ax | awk '{sub(/.*\//, "", $5)} $5 ~ /timeshift/ {print $1}')
if [ "${#timeshift_pid}" -gt 0 ]; then
set_snapshot_dir
log "${BASHPID}: detected running Timeshift at daemon startup, PID is: $timeshift_pid"
vlog "${BASHPID}: new snapshots directory is $snapdir"
else
log "Watching /run/timeshift for timeshift to start"
inotifywait ${inotify_qiet_flag} -e create -e delete /run/timeshift && {
sleep 1
timeshift_pid=$(ps ax | awk '{sub(/.*\//, "", $5)} $5 ~ /timeshift/ {print $1}')
set_snapshot_dir
log "${BASHPID}: detected Timeshift startup, PID is: $timeshift_pid" "${CYAN}"
vlog "${BASHPID}: new snapshots directory is $snapdir" "${CYAN}"
(create_grub_menu) # create the grub menu once immediately in a forking process. Snapshots from commandline using timeshift --create need this
}
fi
runs=false
else
while [ -d "$snapdir" ]; do
# watch the actual snapshots folder for a new snapshot or a deletion of a snapshot
if [ ${runs} = false ] && [ ${verbose} = false ]; then
log "${BASHPID}: Watching $snapdir for new snapshots..." "${CYAN}"
else
vlog "${BASHPID}: Watching $snapdir for new snapshots..." "${CYAN}"
fi
runs=true
inotifywait ${inotify_qiet_flag} ${inotify_recursive_flag} -e create -e delete -e unmount -t "$watchtime" "$snapdir" && {
log "${BASHPID}: Detected snapshot creation/ deletion, recreating Grub menu" "${CYAN}"
sleep 5
create_grub_menu
}
sleep 1
done
timeshift_pid=-2
fi
if ! [ ${timeshift_auto} = true ] && ! [ -d "${snapdir}" ] ; then # in case someone deletes the snapshots folder (in snapper mode) to prevent the while loop from going wild
break
fi
done
}
main() {
# init
timeshift_pid=-1
watchtime=0
logfile=0
snapshots=-1
timeshift_auto=false
timeshift_old=false
verbose=false
syslog=false
recursive=false
trap sighandler SIGINT SIGTERM
setcolors true # normally we want colors
sysconfdir="/etc"
grub_btrfs_config="${sysconfdir}/default/grub-btrfs/config"
# source config file
[ -f "$grub_btrfs_config" ] && . "$grub_btrfs_config"
[ -f "${sysconfdir}/default/grub" ] && . "${sysconfdir}/default/grub"
parse_arguments "${@}"
shift $(( OPTIND - 1 ))
snapdirs=( "${@}" )
vlog "Arguments:"
vlog "Snapshot directories: ${snapdirs[*]}"
vlog "Timestift autodetection: $timeshift_auto"
vlog "Timeshift old: $timeshift_old"
vlog "Logfile: $logfile"
vlog "Recursive: $recursive"
checks
setup
log "grub-btrfsd starting up..." "${GREEN}"
if [ ${timeshift_auto} = true ] ; then
daemon_function "timeshift" &
else
# for all dirs that got passed to the script, start a new fork with that dir
for snapdir in "${snapdirs[@]}"
do
vlog "starting daemon watching $snapdir..."
daemon_function "${snapdir}" &
done
fi
wait # wait for forks to finish, kill child forks if SIGTERM is sent
exit 0
}
main "${@}"