2 # Copyright (C) 2016 Ian Kelling
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 script=$
(readlink
-f -- "$BASH_SOURCE")
18 [[ $EUID == 0 ]] ||
exec sudo
-E "$script" "$@"
20 source /usr
/local
/lib
/err
24 Usage: ${0##*/} [OPTIONS]
26 -h|--help Print help and exit.
27 -f|--force Use kill -9 to try fixing unmount errors
28 -v|--verbose Be more verbose
31 Note, at source location, intentionally not executable, run and read
34 Note: Uses util-linux getopt option parsing: spaces between args and
35 options, short options can be combined, options before args.
42 while read -r line
; do
44 grep -xFq "$line" "$file" ||
tee -a "$file"<<<"$line"
65 if ! mountpoint
-q $dir; then
71 while read -r start mpoint end
; do
72 l
="$start $mpoint $end"
73 # kill off any lines that duplicate the mount point.
74 sed --follow-symlinks -ri "\%$l%b;\%^\s*\S+\s+$mpoint\s%d" /etc
/fstab
80 for m
in ${my_pids[@]}; do
82 echo "$0: error: pids to kill includes our pid or a parent. ps output:" >&2
93 if pids
=$
(timeout
4 lsof
-t $dir); then
95 timeout
4 lsof
-w $dir
99 # fuser will find open sockets that lsof won't, for example from gpg-agent.
100 # note: -v shows kernel processes, which then doesn't return true when we want
101 if pids
=$
(timeout
4 fuser
-m $dir 2>/dev
/null
); then
104 fuser
-$sig -mvk $dir
107 if ! $found_pids; then
115 if mountpoint
-q $dir; then
116 if m umount
-R $dir; then
119 if ! kill-dir TERM TERM TERM INT INT HUP HUP TERM TERM TERM INT INT HUP HUP
; then
120 if $force; then kill-dir KILL
; fi
123 if m umount
-R $dir; then
126 echo "$0: failed to umount $dir"
134 # duplicated in check-subvol
138 if [[ $devref == /dev
/dm-
* ]]; then
139 for mapdev
in /dev
/mapper
/*; do
140 if [[ $
(readlink
-f $mapdev) == "$devref" ]]; then
149 ##### begin command line parsing ########
151 # you can remove this if you do not have options which can have args with spaces or empty.
155 temp
=$
(getopt
-l help,force
,verbose hfv
"$@") || usage
1
159 -f|
--force) force
=true
;;
160 -v|
--verbose) verbose
=true
;;
163 *) echo "$0: unexpected args: $*" >&2 ; usage
1 ;;
168 ##### end command line parsing ########
172 ##### begin setup fstab for subvols we care about ######
174 if [[ -e /mnt
/root
/root2-crypttab
]]; then
175 tu
/etc
/crypttab
</mnt
/root
/root2-crypttab
176 while read -r mapper_dev _
; do
177 if [[ ! -e /dev
/mapper
/$mapper_dev ]]; then
178 m cryptdisks_start
$mapper_dev
180 done < <(cat /mnt
/root
/root2-crypttab
)
182 if [[ -e /mnt
/root
/root2-fstab
]]; then
183 tu
/etc
/fstab
</mnt
/root
/root2-fstab
188 root_dev
=$
(awk '$2 == "/" {print $1}' /etc
/mtab
)
191 # root2_dev=$(awk '$2 == "/mnt/root2" {print $1}' /etc/mtab)
192 # mapper-dev root2_dev
193 # # dont bother with the above for crypt2_dev
194 # crypt2_dev=$root2_dev
197 if cryptsetup status
$root_dev &>/dev
/null
; then
199 else # if we are in a recovery boot, find the next best crypt device
201 for dev
in $
(dmsetup
ls --target crypt |
awk '{print $1}'); do
203 if awk '{print $1}' /etc
/mtab |
grep -Fx $dev &>/dev
/null
; then
212 # dont tax the cpus of old laptops
213 if ((`nproc` > 2)); then
214 mopts
+=,compress=zstd
218 $crypt_dev /a btrfs noatime,subvol=a$mopts 0 0
223 # ssh and probably some other things care about parent directory
224 # ownership, and ssh doesn\'t allow any group writable parent
225 # directories, so we are forced to use a directory structure similar
226 # to home directories
227 f
=(/mnt
/root
/btrbk
/q.
*); f
=${f[0]}
230 $crypt_dev /q btrfs noatime,subvol=q,gid=1000$mopts 0 0
231 /q/p /p none bind$mopts 0 0
235 f
=(/mnt
/root
/btrbk
/o.
*); f
=${f[0]}
238 $crypt_dev /o btrfs noatime,subvol=o$mopts 0 0
239 /o/m /m none bind$mopts 0 0
243 if [[ $HOSTNAME == frodo
]]; then
245 $crypt_dev /i btrfs noatime,subvol=i$mopts 0 0
251 ##### end setup fstab for subvols we care about ######
253 ### begin get pids that this program depends on so we dont kill them
257 while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != ${my_pids[-2]} && $count -lt $loop_limit ]]; do
259 p
=$
(ps
-p ${my_pids[-1]} -o ppid
=)
260 if [[ $p == 0 ||
! $p ]]; then
265 ### end get pids that this program depends on so we dont kill them
267 for vol
in q a o i
; do
269 if ! awk '{print $2}' /etc
/fstab |
grep -xF $d &>/dev
/null
; then
274 ##### begin building up list of bind mounts ######
275 binds
=() # list of bind mounts
279 for r
in ${roots[@]}; do
280 # eg. when r=/q/p, for lines like
281 # /q/p /p none bind 0 0
283 new_roots
+=($
(sed -rn "s#^$r/\S+\s+(\S+)\s+none\s+(\S+,|)bind[[:space:],].*#\1#p" /etc
/fstab
))
285 (( ${#new_roots} )) ||
break
286 binds
+=(${new_roots[@]})
287 # roots is used to recursively find binds of binds if they exist.
288 roots
=( ${new_roots[@]} )
290 ##### end building up list of bind mounts ######
293 # if latest is already mounted, make sure binds are mounted and move on
294 m check-subvol-stale
$d
295 # populated by check-subvol-stale if stale
296 if ! fresh_snap
=$
(cat /nocow
/btrfs-stale
/$vol 2>/dev
/null
); then
299 for b
in ${binds[@]}; do
300 if mountpoint
-q $b; then
302 if [[ $did != $bid ]]; then
311 if [[ $vol == q
]]; then
312 # allow to fail, user might not be logged in
313 x sudo
-u $
(id
-nu 1000) XDG_RUNTIME_DIR
=/run
/user
/1000 systemctl
--user stop arbtt ||
:
317 for dir
in $
(echo $d ${binds[*]}\ |
tac -s\
); do
321 # if we unmounted some but not all, restore them and move on
322 if ! $umount_ret; then
323 for dir
in ${unmounted[@]}; do
329 #### begin dealing with leaf vols ####
331 ### begin getting root_dir
332 ### this is duplicated in check-subvol-stale
334 dev
=$
(sed -rn "s,^\s*([^#]\S*)\s+$d\s.*,\1,p" /etc
/fstab
/etc
/mtab|
head -n1)
336 # note, we need $dev because $d might not be mounted, and we do this loop
337 # because the device in fstab for the rootfs can be different.
338 for devx
in $
(btrfs fil show
$dev|
sed -rn 's#.*path (\S+)$#\1#p'); do
339 if [[ $devx == dm-
* ]]; then
344 root_dir
=$
(sed -rn "s,^\s*$devx\s+(\S+).*\bsubvolid=[05]\b.*,\1,p" /etc
/mtab
/etc
/fstab|
head -n1)
345 if [[ $root_dir ]]; then
350 if [[ ! $root_dir ]]; then
351 echo "$0: error could not find root subvol mount for $dev" >&2
354 ### end getting root_dir
357 if [[ -e $vol ]]; then
358 leaf
=$vol.leaf.$
(date +%Y-
%m-
%dT
%H
:%M
:%S
%z
)
360 m btrfs property
set -ts $leaf ro true
362 ### begin check if leaf is different, delete it if not ###
363 if [[ -e /a
/opt
/btrfs-snapshots-diff
/btrfs-snapshots-diff.py
]]; then
364 source /a
/bin
/distro-functions
/src
/package-manager-abstractions
365 #pi python-jmespath # dependency of btrfs-snapshots-diff
366 # todo: need python3 port of btrfs-snapshots-diff, py2 no exist on nabia
367 parentid
=$
(btrfs sub show
$leaf |
awk '$1 == "Parent" && $2 == "UUID:" {print $3}')
368 bsubs
=(/mnt
/root
/btrbk
/$vol.
*)
370 # go in reverse order as its more likely to be at the end
371 for ((i
=${#bsubs[@]}-1; i
>=0; i--
)); do
372 if [[ $parentid == $
(btrfs sub show
${bsubs[i]} |
awk '$1 == "UUID:" {print $2}') ]]; then
379 # in testing, same subvol is 136 bytes. allow some overhead. 32 happens sometimes under systemd.
381 # EPIPE 32 Broken pipe
382 btrfs send
--no-data -p $bsub $leaf |
head -c 1000 > $tmp ||
[[ $?
== 141 ||
${PIPESTATUS[0]} == 32 ]]
383 if (( $
(stat
-c%s
$tmp) < 1000)); then
384 # example output for an empty diff:
385 # Found a valid Btrfs stream header, version 1
386 # o.leaf.2019-05-15T14:00:50-0400;snapshot: uuid=ba045ea30737dd449003f1ee40ec12d0, ctrasid=109533, clone_uuid=3c7e3544e486834aa71d89e5b8f30056, clone_ctransid=109533
387 lines
=$
(/a
/opt
/btrfs-snapshots-diff
/btrfs-snapshots-diff.py
-s -f $tmp | \
388 grep -vxF "Found a valid Btrfs stream header, version 1" | \
389 grep -cv "^[^;]*;snapshot: ") ||
:
390 if [[ $lines == 0 ]]; then
391 # rotate in case we find a bug, weve got 2 old ones
392 tmpleaf
=($vol.tmpleaf2.
*)
393 if (( ${#tmpleaf[@]} )); then
394 x btrfs sub del
${tmpleaf[@]}
396 tmpleaf
=($vol.tmpleaf1.
*)
397 if (( ${#tmpleaf[@]} )); then
398 x
mv ${tmpleaf[0]} $vol.tmpleaf2.
${tmpleaf[0]#$vol.tmpleaf1.}
400 echo suspected identical
: $bsub $leaf
401 x
mv $leaf $vol.tmpleaf1.
${leaf#$vol.leaf.}
406 ### end check if leaf is different, delete it if not ###
408 ## begin expire leaf vols ##
409 leaf_vols
=($vol.leaf.
*)
410 count
=${#leaf_vols[@]}
411 leaf_limit_time
=$
(( $
(date +%s
) - 60*60*24*60 )) # 60 days
412 leaf_new_limit_time
=$
(( $
(date +%s
) - 60*60*24 )) # 1 day
413 # this goes backwards from oldest. leaf_new_limit_time is just in case
414 # the order gets screwed up or something.
415 for leaf
in ${leaf_vols[@]}; do
416 leaf_time
=$
(date -d ${leaf#$vol.leaf.} +%s
)
417 if (( leaf_limit_time
> leaf_time ||
( leaf_new_limit_time
> leaf_time
&& count
> 15 ) )); then
418 x btrfs sub del
$leaf
422 ## end expire leaf vols ##
424 #### end dealing with leaf vols ####
426 # Note, we make a few assumptions in this script, like
427 # $d was not a different subvol id than $vol, and
428 # things otherwise didn't get mounted very strangely.
429 m btrfs sub snapshot
$fresh_snap $vol
430 for dir
in $d ${binds[@]}; do
433 if [[ $vol == q
]]; then
434 # maybe this will fail if X is not running
435 x sudo
-u $
(id
-nu 1000) XDG_RUNTIME_DIR
=/run
/user
/1000 systemctl
--user start arbtt ||
:
437 stale_dir
=/nocow
/btrfs-stale
443 for dir
in /mnt
/r
7/amy
/{root
,boot
}_ubuntubionic
/mnt
/{root
2/root
,boot
2/boot
}_ubuntubionic
; do
446 if [[ ! -d $root_dir ]]; then
447 # this only exists on host kd currently
450 # if latest is already mounted, make sure binds are mounted and move on
451 m check-subvol-stale
-p $dir
452 # populated by check-subvol-stale if stale
453 if ! fresh_snap
=$
(cat /nocow
/btrfs-stale
/$vol 2>/dev
/null
); then
456 if [[ -d $dir ]]; then
457 if ! kill-dir TERM TERM TERM INT INT HUP HUP TERM TERM TERM INT INT HUP HUP
; then
458 if $force; then kill-dir KILL
; fi
462 m btrfs sub snapshot
$fresh_snap $dir
463 rm -f /nocow
/btrfs-stale
/$vol
469 # Explaining this whole thing. The host amy is used by someone else,
470 # i back it up to my extra big partition on one computer.
471 # But I also want to restore it and test out the restoration on
472 # a computer I usually use. For this, I created a separate partition
473 # that has the amy encryption password, and a separate boot so
474 # that I could encrypt my own boot partition if I want. Then,
475 # I backup from this big partition into that partition in order
476 # to boot and run it.
478 # In order to boot and run it:
480 # mount -o bind /mnt/root2/root_ubuntubionic /mnt/1
482 # /b/ds/gen-amy-fstab ubuntubionic .
483 # teeu /mnt/1/etc/default/grub <<<'GRUB_DISABLE_OS_PROBER=true'
484 # mount -o bind /mnt/boot2/boot_ubuntubionic boot
485 # mount -o bind /dev dev
486 # mount -o bind /proc proc
487 # mount -o bind /sys sys
489 # mount $(awk '$2 == "/boot/efi" {print $1}' /etc/mtab) boot/efi
491 # apt install grub-efi
493 # this is taken from partition.DEFAULT
495 # for disk in $(lsblk -do name,tran -n | awk '$2 ~ "^(sata|nvme)$" { print $1 }'); do
496 # case $(cat /sys/block/$disk/queue/rotational) in
497 # 0) ssds+=(/dev/$disk) ;;
499 # *) echo "$0: error: unknown /sys/block/$disk/queue/rotational: \
500 # $(cat $disk/queue/rotational)" ;;
504 # this is from /a/bin/fai/fai/config/scripts/GRUB_EFI/10-setup
505 # GROOT=$(grub-probe -tdrive -d ${ssds[@]})
506 # echo "GROOT=$GROOT"
507 # grub-install --no-floppy --modules=part_gpt "$GROOT"
509 # update-initramfs -u