d36685f3b41e6bf717b5a31ba8f152cfe1d79bde
[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 # usage: mount-latest-subvol
17
18 cd /
19 [[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
20
21 errcatch() {
22 set -E; shopt -s extdebug
23 _err-trap() {
24 err=$?
25 exec >&2
26 set +x
27 echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}:in \`$BASH_COMMAND' returned $err"
28 bash-trace 2
29 echo "$0: exiting with code $err"
30 exit $err
31 }
32 trap _err-trap ERR
33 set -o pipefail
34 }
35 bash-trace() {
36 local -i argc_index=0 frame i start=${1:-1} max_indent=8 indent
37 local source
38 local extdebug=false
39 if [[ $(shopt -p extdebug) == *-s* ]]; then
40 extdebug=true
41 fi
42
43 for ((frame=0; frame < ${#FUNCNAME[@]}-1; frame++)); do
44 argc=${BASH_ARGC[frame]}
45 argc_index+=$argc
46 ((frame < start)) && continue
47 if (( ${#BASH_SOURCE[@]} > 1 )); then
48 source="${BASH_SOURCE[frame+1]}:${BASH_LINENO[frame]}:"
49 fi
50 indent=$((frame-start+1))
51 indent=$((indent < max_indent ? indent : max_indent))
52 printf "%${indent}s↳%sin \`%s" '' "$source" "${FUNCNAME[frame]}"
53 if $extdebug; then
54 for ((i=argc_index-1; i >= argc_index-argc; i--)); do
55 printf " %s" "${BASH_ARGV[i]}"
56 done
57 fi
58 echo \'
59 done
60 }
61 errcatch
62
63 tu() {
64 while read -r line; do
65 file="$1"
66 grep -xFq "$line" "$file" || tee -a "$file"<<<"$line"
67 done
68 }
69 e() { printf "%s\n" "$*"; "$@"; }
70 mnt() {
71 dir=$1
72 if ! mountpoint $dir &>/dev/null; then
73 mkdir -p $dir
74 e mount $dir
75 fi
76 }
77 fstab() {
78 while read -r start mpoint end; do
79 l="$start $mpoint $end"
80 # kill off any lines that duplicate the mount point.
81 sed --follow-symlinks -ri "\%$l%b;\%^\s*\S+\s+$mpoint\s%d" /etc/fstab
82 tu /etc/fstab <<<"$l"
83 done
84 }
85 pid-check() {
86 for p in ${pids}; do
87 for m in ${my_pids[@]}; do
88 if (( p == m )); then
89 echo "$0: error: pids to kill includes our pid or a parent" >&2
90 ps -f -p $p
91 exit 1
92 fi
93 done
94 done
95 }
96 kill-dir() {
97 for sig; do
98 echo kill-dir $sig
99 found_pids=false
100 if pids=$(timeout 4 lsof -t $dir); then
101 found_pids=true
102 timeout 4 lsof -w $dir
103 pid-check
104 kill -$sig $pids
105 fi
106 # fuser will find open sockets that lsof won't, for example from gpg-agent.
107 # note: -v shows kernel processes, which then doesn't return true when we want
108 if pids=$(timeout 4 fuser -m $dir 2>/dev/null); then
109 pid-check
110 found_pids=true
111 fuser -$sig -mvk $dir
112 fi
113 sleep .5
114 if ! $found_pids; then
115 return 0
116 fi
117 done
118 return 1
119 }
120
121 force=false
122 if [[ $1 == -f ]]; then
123 force=true
124 fi
125
126 ret=0
127
128 ##### begin setup fstab for subvols we care about ######
129 root_dev=$(awk '$2 == "/" {print $1}' /etc/mtab)
130
131
132 if cryptsetup status $root_dev &>/dev/null; then
133 crypt_dev=$root_dev
134 else # if we are in a recovery boot, find the next best crypt device
135 noauto=,noauto
136 for dev in $(dmsetup ls --target crypt | awk '{print $1}'); do
137 dev=/dev/mapper/$dev
138 if awk '{print $1}' /etc/mtab | grep -Fx $dev &>/dev/null; then
139 crypt_dev=$dev
140 break
141 fi
142 done
143 fi
144
145
146 fstab <<EOF
147 $crypt_dev /a btrfs noatime,subvol=a$noauto 0 0
148 EOF
149
150 shopt -s nullglob
151
152 # ssh and probably some other things care about parent directory
153 # ownership, and ssh doesn\'t allow any group writable parent
154 # directories, so we are forced to use a directory structure similar
155 # to home directories
156 f=(/mnt/root/btrbk/q.*)
157 if [[ -e $f ]]; then
158 fstab <<EOF
159 $crypt_dev /q btrfs noatime,subvol=q,gid=1000$noauto 0 0
160 /q/p /p none bind$noauto 0 0
161 EOF
162 fi
163
164 f=(/mnt/root/btrbk/o.*)
165 if [[ -e $f ]]; then
166 fstab <<EOF
167 $crypt_dev /o btrfs noatime,subvol=o$noauto 0 0
168 /o/m /m none bind$noauto 0 0
169 EOF
170 fi
171
172 if [[ $HOSTNAME == frodo ]]; then
173 fstab <<EOF
174 $crypt_dev /i btrfs noatime,subvol=i$noauto 0 0
175 EOF
176 fi
177 ##### end setup fstab for subvols we care about ######
178
179 # get pids that this program depends on so we dont kill them
180 my_pids=($$ $PPID)
181 loop_limit=30
182 count=0
183 while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != ${my_pids[-2]} && $count -lt $loop_limit ]]; do
184 count=$((count + 1))
185 p=$(ps -p ${my_pids[-1]} -o ppid=)
186 if [[ $p == 0 || ! $p ]]; then
187 break
188 fi
189 my_pids+=($p)
190 done
191
192
193 for vol in q a o i; do
194 d=/$vol
195 if ! awk '{print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
196 continue
197 fi
198
199
200 ##### begin building up list of bind mounts ######
201 binds=() # list of bind mounts
202 roots=($d) # list of bind mounts, plus the original mount
203 while true; do
204 new_roots=()
205 for r in ${roots[@]}; do
206 # eg. when r=/q/p, for lines like
207 # /q/p /p none bind 0 0
208 # output /p
209 new_roots+=($(sed -rn "s#^$r/\S+\s+(\S+)\s+none\s+bind\s.*#\1#p" /etc/fstab))
210 done
211 (( ${#new_roots} )) || break
212 binds+=(${new_roots[@]})
213 roots=( ${new_roots[@]} )
214 done
215 ##### end building up list of bind mounts ######
216
217
218 # if latest is already mounted, make sure binds are mounted and move on
219 if e check-subvol-stale $d; then
220 mnt $d
221 for b in ${binds[@]}; do
222 mnt $b
223 done
224 continue
225 fi
226
227 # populated by check-subvol-stale
228 fresh_snap=$(</nocow/btrfs-stale/$vol)
229 if [[ ! $fresh_snap ]]; then
230 echo "$0: error. empty fresh_snap var"
231 ret=1
232 continue
233 fi
234
235 umount_ret=true
236 unmounted=()
237 for dir in $(echo $d ${binds[*]}\ |tac -s\ ); do
238 if mountpoint $dir; then
239 if e umount -R $dir; then
240 unmounted+=($dir)
241 else
242 if ! kill-dir TERM TERM TERM INT INT HUP HUP; then
243 if $force; then kill-dir KILL; fi
244 fi
245
246 if e umount -R $dir; then
247 unmounted+=($dir)
248 else
249 echo "$0: failed to umount $dir"
250 umount_ret=false
251 ret=1
252 continue
253 fi
254 fi
255 fi
256 done
257
258 if ! $umount_ret; then
259 for dir in ${unmounted[@]}; do
260 mnt $dir
261 done
262 continue
263 fi
264
265 # todo: decipher /mnt/root, like we do in check-subvol-stale
266 cd /mnt/root
267 if [[ -e $vol ]]; then
268 e mv $vol $vol.leaf.$(date +%Y-%m-%dT%H:%M:%S%z)
269 leaf_vols=($vol.leaf.*)
270 for leaf in ${leaf_vols[@]}; do
271 leaf_secs=$(date -d ${leaf#$vol.leaf.} +%s)
272 if (( $(date +%s) - 60*60*24*60 > leaf_secs )); then # 60 days
273 e btrfs sub del $leaf
274 fi
275 done
276 fi
277 # Note, we make a few assumptions in this script, like
278 # $d was not a different subvol id than $vol, and
279 # things otherwise didn't get mounted very strangely.
280 e btrfs sub snapshot $fresh_snap $vol
281 for dir in $d ${binds[@]}; do
282 e mnt $dir
283 done
284 stale_dir=/nocow/btrfs-stale
285 rm -f $stale_dir/$d
286 done
287
288 ### disabled
289 if [[ $HOSTNAME == kdxxxxxxxxx ]]; then
290 # partitioned it with fai partitioner outside of fai,
291 # because it\'s worth it to have 1% space reserved for boot and
292 # swap partitions in case I ever want to boot off those drives.
293 # as root:
294 # . /a/bin/fai/fai-wrapper
295 # eval-fai-classfile /a/bin/fai/fai/config/class/51-multi-boot
296 # fai-setclass ROTATIONAL
297 # export LUKS_DIR=/q/root/luks/
298 # # because the partition nums existed already
299 # fai-setclass REPARTITION
300 # /a/bin/fai/fai/config/hooks/partition.DEFAULT
301
302 devs=(
303 ata-TOSHIBA_MD04ACA500_84REK6NTFS9A-part1
304 ata-TOSHIBA_MD04ACA500_84R2K773FS9A-part1
305 ata-TOSHIBA_MD04ACA500_8471K430FS9A-part1
306 ata-TOSHIBA_MD04ACA500_8481K493FS9A-part1
307 )
308 first=true
309 for dev in ${devs[@]}; do
310 if $first; then
311 first=false
312 tu /etc/fstab <<EOF
313 /dev/mapper/crypt_dev_$dev /i btrfs noatime,subvol=i,noauto 0 0
314 /dev/mapper/crypt_dev_$dev /mnt/iroot btrfs noatime,subvolid=0,noauto 0 0
315 EOF
316 fi
317 tu /etc/crypttab <<EOF
318 crypt_dev_$dev /dev/disk/by-id/$dev /q/root/luks/host-kd discard,luks
319 EOF
320 if [[ ! -e /dev/mapper/crypt_dev_$dev ]]; then
321 cryptdisks_start crypt_dev_$dev
322 fi
323 done
324 # note, could do an else here and have some kind of mount for /i
325 # on other hosts.
326 fi
327
328 exit $ret