fix vpn host naming
[distro-setup] / mount-latest-subvol
1 #!/bin/bash
2 # Copyright (C) 2016 Ian Kelling
3 #
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
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 script=$(readlink -f -- "$BASH_SOURCE")
17 cd /
18 [[ $EUID == 0 ]] || exec sudo -E "$script" "$@"
19
20 source /usr/local/lib/err
21
22 usage() {
23 cat <<EOF
24 Usage: ${0##*/} [OPTIONS]
25
26 -h|--help Print help and exit.
27
28
29 Note, at source location, intentionally not executable, run and read
30 install-my-scripts.
31
32 Note: Uses util-linux getopt option parsing: spaces between args and
33 options, short options can be combined, options before args.
34 EOF
35 exit $1
36 }
37
38
39 tu() {
40 while read -r line; do
41 file="$1"
42 grep -xFq "$line" "$file" || tee -a "$file"<<<"$line"
43 done
44 }
45 m() {
46 if $verbose; then
47 printf "%s\n" "$*"
48 fi
49 "$@"
50 }
51 x() {
52 printf "%s\n" "$*"
53 "$@"
54 }
55
56 mnt() {
57 dir=$1
58 if ! mountpoint -q $dir; then
59 mkdir -p $dir
60 m mount $dir
61 fi
62 }
63 fstab() {
64 while read -r start mpoint end; do
65 l="$start $mpoint $end"
66 # kill off any lines that duplicate the mount point.
67 sed --follow-symlinks -ri "\%$l%b;\%^\s*\S+\s+$mpoint\s%d" /etc/fstab
68 tu /etc/fstab <<<"$l"
69 done
70 }
71 pid-check() {
72 for p in ${pids}; do
73 for m in ${my_pids[@]}; do
74 if (( p == m )); then
75 echo "$0: error: pids to kill includes our pid or a parent. ps output:" >&2
76 ps -f -p $p
77 exit 1
78 fi
79 done
80 done
81 }
82 kill-dir() {
83 for sig; do
84 echo kill-dir $sig
85 found_pids=false
86 if pids=$(timeout 4 lsof -t $dir); then
87 found_pids=true
88 timeout 4 lsof -w $dir
89 pid-check
90 kill -$sig $pids
91 fi
92 # fuser will find open sockets that lsof won't, for example from gpg-agent.
93 # note: -v shows kernel processes, which then doesn't return true when we want
94 if pids=$(timeout 4 fuser -m $dir 2>/dev/null); then
95 pid-check
96 found_pids=true
97 fuser -$sig -mvk $dir
98 fi
99 sleep .5
100 if ! $found_pids; then
101 return 0
102 fi
103 done
104 return 1
105 }
106
107 ##### begin command line parsing ########
108
109 # you can remove this if you do not have options which can have args with spaces or empty.
110
111 verbose=false
112 force=false
113 temp=$(getopt -l help,force,verbose hfv "$@") || usage 1
114 eval set -- "$temp"
115 while true; do
116 case $1 in
117 -f|--force) force=true ;;
118 -v|--verbose) verbose=true ;;
119 -h|--help) usage ;;
120 --) shift; break ;;
121 *) echo "$0: unexpected args: $*" >&2 ; usage 1 ;;
122 esac
123 shift
124 done
125
126 ##### end command line parsing ########
127
128 ret=0
129
130 ##### begin setup fstab for subvols we care about ######
131 root_dev=$(awk '$2 == "/" {print $1}' /etc/mtab)
132 if [[ $root_dev == /dev/dm-* ]]; then
133 for d in /dev/mapper/*; do
134 if [[ $(readlink -f $d) == "$root_dev" ]]; then
135 root_dev=$d
136 break
137 fi
138 done
139 fi
140
141 if cryptsetup status $root_dev &>/dev/null; then
142 crypt_dev=$root_dev
143 else # if we are in a recovery boot, find the next best crypt device
144 noauto=,noauto
145 for dev in $(dmsetup ls --target crypt | awk '{print $1}'); do
146 dev=/dev/mapper/$dev
147 if awk '{print $1}' /etc/mtab | grep -Fx $dev &>/dev/null; then
148 crypt_dev=$dev
149 break
150 fi
151 done
152 fi
153
154
155 fstab <<EOF
156 $crypt_dev /a btrfs noatime,subvol=a$noauto 0 0
157 EOF
158
159 shopt -s nullglob
160
161 # ssh and probably some other things care about parent directory
162 # ownership, and ssh doesn\'t allow any group writable parent
163 # directories, so we are forced to use a directory structure similar
164 # to home directories
165 f=(/mnt/root/btrbk/q.*); f=${f[0]}
166 if [[ -e $f ]]; then
167 fstab <<EOF
168 $crypt_dev /q btrfs noatime,subvol=q,gid=1000$noauto 0 0
169 /q/p /p none bind$noauto 0 0
170 EOF
171 fi
172
173 f=(/mnt/root/btrbk/o.*); f=${f[0]}
174 if [[ -e $f ]]; then
175 fstab <<EOF
176 $crypt_dev /o btrfs noatime,subvol=o$noauto 0 0
177 /o/m /m none bind$noauto 0 0
178 EOF
179 fi
180
181 if [[ $HOSTNAME == frodo ]]; then
182 fstab <<EOF
183 $crypt_dev /i btrfs noatime,subvol=i$noauto 0 0
184 EOF
185 fi
186 ##### end setup fstab for subvols we care about ######
187
188 # get pids that this program depends on so we dont kill them
189 my_pids=($$ $PPID)
190 loop_limit=30
191 count=0
192 while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != ${my_pids[-2]} && $count -lt $loop_limit ]]; do
193 count=$((count + 1))
194 p=$(ps -p ${my_pids[-1]} -o ppid=)
195 if [[ $p == 0 || ! $p ]]; then
196 break
197 fi
198 my_pids+=($p)
199 done
200
201
202 for vol in q a o i; do
203 d=/$vol
204 if ! awk '{print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
205 continue
206 fi
207
208
209 ##### begin building up list of bind mounts ######
210 binds=() # list of bind mounts
211 roots=($d) # list of bind mounts, plus the original mount
212 while true; do
213 new_roots=()
214 for r in ${roots[@]}; do
215 # eg. when r=/q/p, for lines like
216 # /q/p /p none bind 0 0
217 # output /p
218 new_roots+=($(sed -rn "s#^$r/\S+\s+(\S+)\s+none\s+bind\s.*#\1#p" /etc/fstab))
219 done
220 (( ${#new_roots} )) || break
221 binds+=(${new_roots[@]})
222 roots=( ${new_roots[@]} )
223 done
224 ##### end building up list of bind mounts ######
225
226
227 # if latest is already mounted, make sure binds are mounted and move on
228 m check-subvol-stale $d
229 # populated by check-subvol-stale if stale
230 if ! fresh_snap=$(cat /nocow/btrfs-stale/$vol 2>/dev/null); then
231 mnt $d
232 for b in ${binds[@]}; do
233 mnt $b
234 done
235 continue
236 fi
237
238 umount_ret=true
239 unmounted=()
240 for dir in $(echo $d ${binds[*]}\ |tac -s\ ); do
241 if mountpoint -q $dir; then
242 if m umount -R $dir; then
243 unmounted+=($dir)
244 else
245 if ! kill-dir TERM TERM TERM INT INT HUP HUP; then
246 if $force; then kill-dir KILL; fi
247 fi
248
249 if m umount -R $dir; then
250 unmounted+=($dir)
251 else
252 echo "$0: failed to umount $dir"
253 umount_ret=false
254 ret=1
255 continue
256 fi
257 fi
258 fi
259 done
260
261 # if we unmounted some but not all, restore them and move on
262 if ! $umount_ret; then
263 for dir in ${unmounted[@]}; do
264 mnt $dir
265 done
266 continue
267 fi
268
269 #### begin dealing with leaf vols ####
270 # todo: decipher /mnt/root, like we do in check-subvol-stale
271 cd /mnt/root
272 if [[ -e $vol ]]; then
273 leaf=$vol.leaf.$(date +%Y-%m-%dT%H:%M:%S%z)
274 m mv $vol $leaf
275 m btrfs property set -ts $leaf ro true
276
277 ### begin check if leaf is different, delete it if not ###
278 if [[ -e /a/opt/btrfs-snapshots-diff/btrfs-snapshots-diff.py ]]; then
279 source /a/bin/distro-functions/src/package-manager-abstractions
280 pi python-jmespath # dependency
281 parentid=$(btrfs sub show $leaf | awk '$1 == "Parent" && $2 == "UUID:" {print $3}')
282 bsubs=(/mnt/root/btrbk/$vol.*)
283 bsub=
284 # go in reverse order as its more likely to be at the end
285 for ((i=${#bsubs[@]}-1; i>=0; i--)); do
286 if [[ $parentid == $(btrfs sub show ${bsubs[i]} | awk '$1 == "UUID:" {print $2}') ]]; then
287 bsub=${bsubs[i]}
288 break
289 fi
290 done
291 if [[ $bsub ]]; then
292 tmp=$(mktemp)
293 # in testing, same subvol is 136 bytes. allow some overhead
294 btrfs send --no-data -p $bsub $leaf | head -c 1000 > $tmp || [[ $? == 141 ]]
295 if (( $(stat -c%s $tmp) < 1000)); then
296 # example output for an empty diff:
297 # Found a valid Btrfs stream header, version 1
298 # o.leaf.2019-05-15T14:00:50-0400;snapshot: uuid=ba045ea30737dd449003f1ee40ec12d0, ctrasid=109533, clone_uuid=3c7e3544e486834aa71d89e5b8f30056, clone_ctransid=109533
299 lines=$(/a/opt/btrfs-snapshots-diff/btrfs-snapshots-diff.py -s -f $tmp | \
300 grep -vxF "Found a valid Btrfs stream header, version 1" | \
301 grep -cv "^[^;]*;snapshot: ") ||:
302 if [[ $lines == 0 ]]; then
303 x btrfs sub del $leaf
304 fi
305 fi
306 fi
307 fi
308 ### end check if leaf is different, delete it if not ###
309
310 ## begin expire leaf vols ##
311 leaf_vols=($vol.leaf.*)
312 count=1
313 for leaf in ${leaf_vols[@]}; do
314 leaf_secs=$(date -d ${leaf#$vol.leaf.} +%s)
315 if (( $(date +%s) - 60*60*24*60 > leaf_secs || count > 200 )); then # 60 days
316 x btrfs sub del $leaf
317 fi
318 count=$((count+1))
319 done
320 ## end expire leaf vols ##
321 fi
322 #### end dealing with leaf vols ####
323
324 # Note, we make a few assumptions in this script, like
325 # $d was not a different subvol id than $vol, and
326 # things otherwise didn't get mounted very strangely.
327 m btrfs sub snapshot $fresh_snap $vol
328 for dir in $d ${binds[@]}; do
329 m mnt $dir
330 done
331 stale_dir=/nocow/btrfs-stale
332 rm -f $stale_dir/$d
333
334 done
335
336 ### disabled
337 if [[ $HOSTNAME == kdxxxxxxxxx ]]; then
338 # partitioned it with fai partitioner outside of fai,
339 # because it\'s worth it to have 1% space reserved for boot and
340 # swap partitions in case I ever want to boot off those drives.
341 # as root:
342 # . /a/bin/fai/fai-wrapper
343 # eval-fai-classfile /a/bin/fai/fai/config/class/51-multi-boot
344 # fai-setclass ROTATIONAL
345 # export LUKS_DIR=/q/root/luks/
346 # # because the partition nums existed already
347 # fai-setclass REPARTITION
348 # /a/bin/fai/fai/config/hooks/partition.DEFAULT
349
350 devs=(
351 ata-TOSHIBA_MD04ACA500_84REK6NTFS9A-part1
352 ata-TOSHIBA_MD04ACA500_84R2K773FS9A-part1
353 ata-TOSHIBA_MD04ACA500_8471K430FS9A-part1
354 ata-TOSHIBA_MD04ACA500_8481K493FS9A-part1
355 )
356 first=true
357 for dev in ${devs[@]}; do
358 if $first; then
359 first=false
360 tu /etc/fstab <<EOF
361 /dev/mapper/crypt_dev_$dev /i btrfs noatime,subvol=i,noauto 0 0
362 /dev/mapper/crypt_dev_$dev /mnt/iroot btrfs noatime,subvolid=0,noauto 0 0
363 EOF
364 fi
365 tu /etc/crypttab <<EOF
366 crypt_dev_$dev /dev/disk/by-id/$dev /q/root/luks/host-kd discard,luks
367 EOF
368 if [[ ! -e /dev/mapper/crypt_dev_$dev ]]; then
369 cryptdisks_start crypt_dev_$dev
370 fi
371 done
372 # note, could do an else here and have some kind of mount for /i
373 # on other hosts.
374 fi
375
376 exit $ret