improvements
[distro-setup] / switch-mail-host
1 #!/bin/bash
2
3 source /usr/local/lib/err
4
5 usage() {
6 cat <<EOF
7 Usage: ${0##*/} [OPTIONS] push|pull HOST
8
9 Turn off mail receiving on OLD_HOST, run btrbk to move mail to NEW_HOST,
10 turn on mail receiving on NEW_HOST. Assumes we want to move all
11 filesystems unless passing -o.
12
13 -i Disallow incremental backup.
14 -o Only btrbk /o, instead of all filesystems.
15 -h|--help Print help and exit.
16
17 I used to adjust home network dns so NEW_HOST resolves locally if it is
18 on the local network, but its simpler just not to and just rely
19 on the internet. Email can wait.
20
21 Note: Uses GNU getopt options parsing style
22 EOF
23 exit $1
24 }
25
26
27 restore_new_btrbk=false
28 restore_old_btrbk=false
29 err-cleanup() {
30 if $restore_new_btrbk; then
31 e WARNING: due to failure, btrbk.timer may need manual restoration:
32 e $new_shell systemctl start btrbk.timer
33 fi
34 if $restore_old_btrbk; then
35 e WARNING: due to failure, btrbk.timer may need manual restoration:
36 e $old_shell systemctl start btrbk.timer
37 fi
38 }
39
40 pre="${0##*/}:"
41 m() { printf "$pre %s\n" "$*"; "$@"; }
42 e() { printf "$pre %s\n" "$*"; }
43 err() { echo "$pre ERROR: $*" >&2; }
44
45 if [[ $EUID != 0 ]]; then
46 err "requires running as root"
47 exit 1
48 fi
49
50
51 ##### begin command line parsing ########
52
53 force=false
54 mp_args="-m /o,/q,/a"
55 temp=$(getopt -l force,help ioh "$@") || usage 1
56 eval set -- "$temp"
57 while true; do
58 case $1 in
59 --force) force=true ;;
60 -i) incremental_arg="-i" ;;
61 -o) mp_args="-m /o" ;;
62 -h|--help) usage ;;
63 --) shift; break ;;
64 *) echo "$0: Internal error! unexpected args: $*" ; exit 1 ;;
65 esac
66 shift
67 done
68
69
70 (( $# == 2 )) || usage 1
71
72 if [[ ! $HOSTNAME ]]; then
73 err "\$HOSTNAME is unset"
74 exit 1
75 fi
76
77 case $1 in
78 push)
79 old_host=$HOSTNAME
80 old_hostname=$HOSTNAME
81 new_host=$2
82 bbk_args="-t $new_host"
83 new_shell="ssh -F $HOME/.ssh/confighome root@$new_host"
84 $new_shell -v hostname
85 new_hostname=$($new_shell hostname)
86 ;;
87 pull)
88 old_host=$2
89 new_host=$HOSTNAME
90 new_hostname=$HOSTNAME
91 bbk_args="-s $old_host"
92 old_shell="ssh -F $HOME/.ssh/confighome root@$old_host"
93 # tests ssh connection
94 if ! old_hostname=$($old_shell hostname); then
95 echo "retrying failed $old_shell with -v"
96 $old_shell -v hostname
97 exit 1
98 fi
99 ;;
100 *)
101 err invalid first argument
102 exit 1
103 ;;
104 esac
105
106 source /a/bin/bash_unpublished/source-state
107
108 if [[ $old_hostname != "$MAIL_HOST" ]] && ! $force; then
109 err "\$old_hostname($old_hostname) != \$MAIL_HOST($MAIL_HOST). Rerun with --force if you really want this."
110 exit 1
111 fi
112
113 if [[ ! $new_host || ! $old_host ]]; then
114 echo "$0: bad args. see script"
115 exit 1
116 fi
117
118
119 ########### end initial processing, begin actually modifying things ##########
120
121 if $new_shell systemctl is-active btrbk.timer; then
122 m $new_shell systemctl stop btrbk.timer
123 restore_new_btrbk=true
124 fi
125 if $old_shell systemctl is-active btrbk.timer; then
126 m $old_shell systemctl stop btrbk.timer
127 restore_old_btrbk=true
128 fi
129
130 btrbk_test="systemctl is-active btrbk.service"
131 active=true
132 while $active; do
133 active=false
134 for shell in "$new_shell" "$old_shell"; do
135 e $shell $btrbk_test
136 status=$($shell $btrbk_test) ||:
137 case $status in
138 inactive|failed) : ;;
139 *)
140 # This covers conditions like "activating", which still return 3 from
141 # systemctl is-active.
142 active=true
143 e "btrbk active on shell:$shell, status:$status, sleeping 8 seconds"
144 sleep 8
145 break
146 ;;
147 esac
148 done
149 done
150
151 # ensure these are unused before doing anything
152
153 e "On $new_host: umounting /m and /o, checking emacs"
154 $new_shell bash -s <<'EOF'
155 set -eE
156 if pgrep -f 'emacs --daemon' &>/dev/null; then
157 bufs="$(emacsclient --eval "$(cat /a/bin/ds/unsaved-buffers.el)"| sed '/^"nil"$/d;s/^"(/E: /;s/)"$//')"
158 if [[ $bufs ]]; then
159 echo "error: on $HOSTNAME, unsaved emacs files: $bufs" >&2
160 exit 1
161 fi
162 fi
163 for dir in m o; do
164 if mountpoint -q /$dir; then
165 echo On $new_host: umount /$dir
166 umount /$dir
167 fi
168 done
169 EOF
170
171 $old_shell bash -s <<'EOF'
172 if pgrep -f 'emacs --daemon' &>/dev/null; then
173 bufs="$(emacsclient --eval "$(cat /a/bin/ds/unsaved-buffers.el)"| sed '/^"nil"$/d;s/^"(/E: /;s/)"$//')"
174 if [[ $bufs ]]; then
175 echo "error: on $HOSTNAME, unsaved emacs files: $bufs" >&2
176 exit 1
177 fi
178 fi
179 EOF
180
181 # previously, I was checking to see if the new mail host
182 # is on my home network, then changing my home dns
183 # to resolve on the local network, so that I didnt
184 # have to send traffic out to the internet or rely
185 # on that. However, that breaks for a laptop that roams.
186 # So, we could have a cronjob that updates that dns,
187 # however, another solution is to just use ipv6,
188 # and I prefer that.
189 #
190 # TODO: enable ipv6 for email. exim config setting disables it.
191 # need to add vpn support. need to add firewall / routing.
192 # I think exim will try ipv6 first, so no need to disable
193 # ipv6 i think.
194
195
196 e Running initial btrbk
197 if ! m btrbk-run -v $bbk_args $incremental_arg $mp_args; then
198 ret=$?
199 err "failed initial btrbk"
200 exit $ret
201 fi
202
203 if ! m $old_shell /a/exe/primary-setup $new_hostname; then
204 ret=$?
205 err "failed \$old_shell primary-setup \$new_hostname. fix and rerun switch-mail-host"
206 exit $ret
207 fi
208
209 # Try to prevent emacs from saving stale data it has in memory to disk. eg: files, recentf list, etc.
210 # But if emacs ignores the signal, let it live.
211 m $new_shell killall -q emacs ||:
212
213 e Running main btrbk
214 m btrbk-run -v $bbk_args $incremental_arg -m /o || ret=$?
215 if (( ret )); then
216 bang="$(printf "$(tput setaf 5)█$(tput sgr0)%.0s" 1 2 3 4 5 6 7)"
217 e $bang failed btrbk of /o. restoring old host as primary
218 m $old_shell /a/exe/primary-setup localhost
219 exit $ret
220 fi
221
222 # once I accidentally accepted incoming mail on old host. I used this script to copy over that mail:
223 #
224 # die=false; for d in o.leaf.2021-05-29T10:02:08-0400/m/{4e,md,4e2}/{,l/}!(*myarchive)/new; do if $die; then break; fi; find $d -type f -mtime -5 | while read -r f; do dir="${f%new/*}"; dir="btrbk/o.20210530T000011-0400/${dir#*/}"; fname="${f##*/}"; [[ -e $dir/new/$fname || -e $dir/cur/$fname ]] && continue; if ! e cp -a $f /${dir#*/*/}new; then echo failed cp; die=true; break; fi ; done; done
225
226 # once I accidentally sent mail from non-main mail host. to copy into the main mail host's sent dir, cd into dir of non-mail mail host Sent/cur, then
227 #
228 # shopt -s nullglob; find . -type f -mtime -2 | while read -r f; do a=( /m/4e/Sent/cur/${f%,*}* ); if (( ${#a[@]} )); then e exists $a; else m cp -a $f /m/4e/Sent/cur; fi; done
229
230 if ! m $new_shell /a/exe/primary-setup localhost; then
231 ret=$?
232 err "failed final primary-setup, just fix and rerun: $new_shell /a/exe/primary-setup localhost"
233 exit $ret
234 fi
235
236 m exit 0