host info updates
[distro-setup] / mount-latest-subvol
index 93a085a8e20e764e85d43153480f1a58b924507d..433c7c2a06c4ed7c2a465476d1670ac375a3b514 100644 (file)
@@ -1,27 +1,34 @@
 #!/bin/bash
-# Copyright (C) 2016 Ian Kelling
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-script=$(readlink -f -- "$BASH_SOURCE")
+
+# On Ian's computers, mount received subvolumes after btrbk.
+# Copyright (C) 2024  Ian Kelling
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
+readonly this_file
 cd /
-[[ $EUID == 0 ]] || exec sudo -E "$script" "$@"
+[[ $EUID == 0 ]] || exec sudo -E "$this_file" "$@"
 
-source /usr/local/lib/err
+set -e; . /usr/local/lib/bash-bear; set +e
+shopt -s nullglob
 
 usage() {
   cat <<EOF
-Usage: ${0##*/} [OPTIONS]
+Usage: ${0##*/} [OPTIONS] [SUBVOLUMES]
 
 -h|--help  Print help and exit.
 -f|--force  Use kill -9 to try fixing unmount errors
@@ -37,8 +44,7 @@ EOF
   exit $1
 }
 
-all_vols=(q a o i ar qr)
-
+pre="mount-latest-subvol:${SSH_CLIENT:+ $HOSTNAME:}"
 
 tu() {
   while read -r line; do
@@ -48,17 +54,17 @@ tu() {
 }
 d() {
   if $verbose; then
-    printf "%s\n" "$*"
+    printf "$pre %s\n" "$*"
   fi
 }
 m() {
   if $verbose; then
-    printf "%s\n" "$*"
+    printf "$pre %s\n" "$*"
   fi
   "$@"
 }
 x() {
-  printf "%s\n" "$*"
+  printf "$pre %s\n" "$*"
   "$@"
 }
 
@@ -168,6 +174,16 @@ while true; do
   shift
 done
 
+if (( $# )); then
+  all_vols=( "$@" )
+else
+  all_vols=(q a o i qd qr)
+  ar_snaps=(/mnt/root/btrbk/ar.*)
+  if [[ -e /mnt/root/ar ]] || (( ${#ar_snaps[@]} > 0 )); then
+    all_vols+=(ar)
+  fi
+fi
+
 ##### end command line parsing ########
 
 ret=0
@@ -188,15 +204,11 @@ if [[ -e /mnt/root/root2-fstab ]]; then
   mnt /mnt/boot2
 fi
 
-do_o=true
 root_dev=$(awk '$2 == "/" {print $1}' /etc/mtab)
 mapper-dev root_dev
 o_dev=$(awk '$2 == "/mnt/o" {print $1}' /etc/mtab)
 mapper-dev o_dev
 
-if [[ $o_dev == "$root_dev" ]]; then
-  do_o=false
-fi
 
 # root2_dev=$(awk '$2 == "/mnt/root2" {print $1}' /etc/mtab)
 # mapper-dev root2_dev
@@ -208,7 +220,8 @@ if cryptsetup status $root_dev &>/dev/null; then
   crypt_dev=$root_dev
 else # if we are in a recovery boot, find the next best crypt device
   mopts=,noauto
-  do_o=false
+  # todo: I think I had an idea to not setup /o in this case,
+  # but never finished implementing it
   for dev in $(dmsetup ls --target crypt | awk '{print $1}'); do
     dev=/dev/mapper/$dev
     if awk '{print $1}' /etc/mtab | grep -Fx $dev &>/dev/null; then
@@ -221,7 +234,7 @@ fi
 
 
 # dont tax the cpus of old laptops
-if ((`nproc` > 2)); then
+if (( $(nproc) > 2)); then
   mopts+=,compress=zstd
 fi
 
@@ -229,60 +242,59 @@ fstab <<EOF
 $crypt_dev  /a  btrfs  noatime,subvol=a$mopts  0 0
 EOF
 
-shopt -s nullglob
 
 # ssh and probably some other things care about parent directory
 # ownership, and ssh doesn\'t allow any group writable parent
 # directories, so we are forced to use a directory structure similar
 # to home directories
-f=(/mnt/root/btrbk/q.*); f=${f[0]}
+fa=(/mnt/root/btrbk/q.*); f=${fa[0]}
 if [[ -e $f ]]; then
   fstab <<EOF
-$crypt_dev  /q  btrfs  noatime,subvol=q,gid=1000$mopts  0 0
+$crypt_dev  /q  btrfs  noatime,subvol=q$mopts  0 0
+$crypt_dev  /qd  btrfs  noatime,subvol=qd$mopts  0 0
 /q/p  /p  none  bind$mopts  0 0
 EOF
 fi
 
-f=(/mnt/root/btrbk/qr.*); f=${f[0]}
+fa=(/mnt/root/btrbk/qr.*); f=${fa[0]}
 if [[ -e $f ]]; then
   fstab <<EOF
 $crypt_dev  /qr  btrfs  noatime,subvol=qr$mopts  0 0
 EOF
 fi
 
-f=(/mnt/root/btrbk/ar.*); f=${f[0]}
-if [[ -e $f ]]; then
-  fstab <<EOF
-$crypt_dev  /ar  btrfs  noatime,subvol=ar,uid=1000,gid=1000$mopts  0 0
-EOF
-fi
+# not syncing ar at the moment
+# fa=(/mnt/root/btrbk/ar.*); f=${fa[0]}
+# if [[ -e $f ]]; then
+#   fstab <<EOF
+# $crypt_dev  /ar  btrfs  noatime,subvol=ar$mopts  0 0
+# EOF
+# fi
 
 
-f=(/mnt/o/btrbk/o.*); f=${f[0]}
+fa=(/mnt/o/btrbk/o.*); f=${fa[0]}
 if [[ -e $f ]]; then
-  fstab <<EOF
-$o_dev  /o  btrfs  noatime,subvol=o$mopts  0 0
-/o/m  /m  none  bind$mopts  0 0
+  if [[ $o_dev != "$root_dev" ]]; then
+    # ,compress=zstd regardless of mopts since these are all text files
+    # and it cuts disk use by about half.
+    fstab <<EOF
+$o_dev  /o  btrfs  noatime,subvol=o${mopts/,compress=zstd/},compress=zstd  0 0
 EOF
-else
-  do_o=false
-fi
-
-if [[ $HOSTNAME == frodo ]]; then
+  fi
   fstab <<EOF
-$crypt_dev  /i  btrfs  noatime,subvol=i$mopts  0 0
+/o/m  /m  none  bind$mopts  0 0
+/o/debbugs  /debbugs  none  bind$mopts  0 0
 EOF
 fi
 
 
-
 ##### end setup fstab for subvols we care about ######
 
 ### begin get pids that this program depends on so we dont kill them
 my_pids=($$ $PPID)
 loop_limit=30
 count=0
-while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != ${my_pids[-2]} && $count -lt $loop_limit  ]]; do
+while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != "${my_pids[-2]}" && $count -lt $loop_limit  ]]; do
   count=$((count + 1))
   p=$(ps -p ${my_pids[-1]} -o ppid=)
   if [[ $p == 0 || ! $p ]]; then
@@ -294,7 +306,7 @@ done
 
 for vol in ${all_vols[@]}; do
   d=/$vol
-  if ! awk '{print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
+  if ! awk '$3 == "btrfs" {print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
     continue
   fi
 
@@ -308,7 +320,7 @@ for vol in ${all_vols[@]}; do
       # eg. when r=/q/p, for lines like
       # /q/p  /p  none  bind  0 0
       # output /p
-      new_roots+=($(sed -rn "s#^$r/\S+\s+(\S+)\s+none\s+(\S+,|)bind[[:space:],].*#\1#p" /etc/fstab))
+      new_roots+=("$(sed -rn "s#^$r/\S+\s+(\S+)\s+none\s+(\S+,|)bind[[:space:],].*#\1#p" /etc/fstab)")
     done
     (( ${#new_roots} )) || break
     binds+=(${new_roots[@]})
@@ -318,6 +330,7 @@ for vol in ${all_vols[@]}; do
   ##### end building up list of bind mounts  ######
 
 
+
   # if latest is already mounted, make sure binds are mounted and move on
   m check-subvol-stale $d
   # populated by check-subvol-stale if stale
@@ -327,7 +340,7 @@ for vol in ${all_vols[@]}; do
     for b in ${binds[@]}; do
       if mountpoint -q $b; then
         bid=$(stat -c%d $b)
-        if [[ $did != $bid ]]; then
+        if [[ $did != "$bid" ]]; then
           umount-kill $b
         fi
       fi
@@ -336,6 +349,28 @@ for vol in ${all_vols[@]}; do
     continue
   fi
 
+
+  ##### begin checking for loopback mounts ####
+  found_loop=false
+  for l in $(losetup -ln|awk '{print $6}'); do
+    for dir in $d ${binds[@]}; do
+      if [[ $l == $dir* ]]; then
+        echo "$0: found loopback mount $l. giving up on unmounting $dir"
+        ret=1
+        found_loop=true
+        break
+      fi
+    done
+    if $found_loop; then
+      break
+    fi
+  done
+  if $found_loop; then
+    continue
+  fi
+  ##### end end checking loopback mounts ####
+
+
   ## not using arbtt at the moment
   # if [[ $vol == q ]]; then
   #   ## allow to fail, user might not be logged in
@@ -383,19 +418,18 @@ for vol in ${all_vols[@]}; do
   ### end getting root_dir
 
   cd $root_dir
-  if [[ -e $vol ]]; then
-    leaf=$vol.leaf.$(date +%Y-%m-%dT%H:%M:%S%z)
-    m mv $vol $leaf
-    m btrfs property set -ts $leaf ro true
-
-    ### begin check if leaf is different, delete it if not ###
-    if [[ -e /a/opt/btrfs-snapshots-diff/btrfs-snapshots-diff.py ]]; then
-      source /a/bin/distro-functions/src/package-manager-abstractions
-      #pi python-jmespath # dependency of btrfs-snapshots-diff
-      # todo: need python3 port of btrfs-snapshots-diff, py2 no exist on nabia
+  if [[ -e $vol  ]]; then
+    if [[ $vol == qd ]]; then
+      m btrfs sub del qd
+    else
+      leaf=$vol.leaf.$(date +%Y-%m-%dT%H:%M:%S%z)
+      m mv $vol $leaf
+      m btrfs property set -ts $leaf ro true
+
+      ### begin check if leaf is different, delete it if not ###
       parentid=$(btrfs sub show $leaf | awk '$1 == "Parent" && $2 == "UUID:" {print $3}')
       bsubs=(btrbk/$vol.*)
-      bsub=
+      bsub= # base subvolume
       # go in reverse order as its more likely to be at the end
       for ((i=${#bsubs[@]}-1; i>=0; i--)); do
         if [[ $parentid == $(btrfs sub show ${bsubs[i]} | awk '$1 == "UUID:" {print $2}') ]]; then
@@ -404,50 +438,34 @@ for vol in ${all_vols[@]}; do
         fi
       done
       if [[ $bsub ]]; then
-        tmp=$(mktemp)
         # in testing, same subvol is 136 bytes. allow some overhead. 32 happens sometimes under systemd.
         # $ errno 32
         # EPIPE 32 Broken pipe
-        btrfs send --no-data -p $bsub $leaf | head -c 1000 > $tmp || [[ $? == 141 || ${PIPESTATUS[0]} == 32 ]]
-        if (( $(stat -c%s $tmp) < 1000)); then
-          # example output for an empty diff:
-          # Found a valid Btrfs stream header, version 1
-          # o.leaf.2019-05-15T14:00:50-0400;snapshot: uuid=ba045ea30737dd449003f1ee40ec12d0, ctrasid=109533, clone_uuid=3c7e3544e486834aa71d89e5b8f30056, clone_ctransid=109533
-          lines=$(/a/opt/btrfs-snapshots-diff/btrfs-snapshots-diff.py -s -f $tmp | \
-                    grep -vxF "Found a valid Btrfs stream header, version 1" | \
-                    grep -cv "^[^;]*;snapshot: ") ||:
-          if [[ $lines == 0 ]]; then
-            # rotate in case we find a bug, weve got 2 old ones
-            tmpleaf=($vol.tmpleaf2.*)
-            if (( ${#tmpleaf[@]} )); then
-              x btrfs sub del ${tmpleaf[@]}
-            fi
-            tmpleaf=($vol.tmpleaf1.*)
-            if (( ${#tmpleaf[@]} )); then
-              x mv ${tmpleaf[0]} $vol.tmpleaf2.${tmpleaf[0]#$vol.tmpleaf1.}
-            fi
-            echo suspected identical: $bsub $leaf
-            x mv $leaf $vol.tmpleaf1.${leaf#$vol.leaf.}
-          fi
+        lines=$(btrfs send --no-data -p $bsub $leaf | btrfs receive --dump | head -n 100 | wc -l || [[ $? == 141 || ${PIPESTATUS[0]} == 32 ]])
+        if [[ $lines == 0 ]]; then
+          # example output of no differences:
+          # snapshot        ./qrtest                        uuid=c41ff6b7-0527-f34d-95ac-190eecf54ff5 transid=2239 parent_uuid=64949e1b-4a3e-3945-9a8e-cd7b7c15d7d6 parent_transid=2239
+          echo suspected identical: $bsub $leaf
+          x btrfs sub del $leaf
         fi
       fi
+      ### end check if leaf is different, delete it if not ###
+
+      ## begin expire leaf vols ##
+      leaf_vols=($vol.leaf.*)
+      count=${#leaf_vols[@]}
+      leaf_limit_time=$(( EPOCHSECONDS - 60*60*24*60 )) # 60 days
+      leaf_new_limit_time=$(( EPOCHSECONDS - 60*60*24 * 5 )) # 5 days this
+      # goes backwards from oldest. leaf_new_limit_time is a safety
+      # measure to ensure we don't delete very recent leafs.
+      for leaf in ${leaf_vols[@]}; do
+        leaf_time=$(date -d ${leaf#"$vol".leaf.} +%s)
+        if (( leaf_limit_time > leaf_time || ( leaf_new_limit_time > leaf_time && count > 30 ) )); then
+          x btrfs sub del $leaf
+        fi
+        count=$((count-1))
+      done
     fi
-    ### end check if leaf is different, delete it if not ###
-
-    ## begin expire leaf vols ##
-    leaf_vols=($vol.leaf.*)
-    count=${#leaf_vols[@]}
-    leaf_limit_time=$(( EPOCHSECONDS - 60*60*24*60 )) # 60 days
-    leaf_new_limit_time=$(( EPOCHSECONDS - 60*60*24 )) # 1 day
-    # this goes backwards from oldest. leaf_new_limit_time is just in case
-    # the order gets screwed up or something.
-    for leaf in ${leaf_vols[@]}; do
-      leaf_time=$(date -d ${leaf#$vol.leaf.} +%s)
-      if (( leaf_limit_time > leaf_time || ( leaf_new_limit_time > leaf_time && count > 15 ) )); then
-        x btrfs sub del $leaf
-      fi
-      count=$((count-1))
-    done
     ## end expire leaf vols ##
   fi
   #### end dealing with leaf vols ####
@@ -495,6 +513,9 @@ for dir in /mnt/r7/amy/{root/root,boot/boot}_ubuntubionic /mnt/{root2/root,boot2
   rm -f /nocow/btrfs-stale/$vol
 done
 
+if (( ret >= 1 )); then
+  echo "$0: exit status $ret. see error above"
+fi
 exit $ret