prologue, Makefile, local.mk: Overhaul installation.
authorMark Wooding <mdw@distorted.org.uk>
Tue, 31 May 2011 20:47:12 +0000 (21:47 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Tue, 31 May 2011 20:47:12 +0000 (21:47 +0100)
The prologue mollyguard is abstracted and generalized a bit so that we
can install stuff remotely without too much worry.  Installation is moved
into the main Makefile (with slightly spruced-up documentation), leaving
only a few very minor tweaks in the local configuration.

Makefile
dummy-payload.m4 [new file with mode: 0644]
local.mk
prologue.m4

index 242bbfd..7596df7 100644 (file)
--- a/Makefile
+++ b/Makefile
 MAIN_M4_SOURCES                 =
 HOSTS                   =
 
+## Where to install the scripts.
+FIREWALL                = /etc/init.d/firewall
+
+## How to achieve root privileges.
+ROOT                    = sudo
+
+## Throw additional scripts in here to have them installed.
 SCRIPTS                         =
+sbindir                         = /usr/local/sbin
 
+## Establish the default target early, so that targets in `local.mk' don't
+## override it.
 default: all
 .PHONY: default
 
@@ -88,6 +98,18 @@ M4_SOURCES           += $(MAIN_M4_SOURCES)
 TARGETS                         = $(addsuffix .sh,$(HOSTS))
 
 ###--------------------------------------------------------------------------
+### Prologue testing.
+
+TARGETS                        += dummy.sh
+dummy.sh: base.m4 prologue.m4 dummy-payload.m4
+       $(V_M4) $^ >$@.new && chmod +x $@.new && mv $@.new $@
+
+TARGETS                        += dummy-inst.sh
+dummy-inst.sh: dummy.sh
+       $(V_GEN)sed '/dummy_action=/s/lose/win/' $< >$@.new
+       $(V_AT)chmod +x $@.new && mv $@.new $@
+
+###--------------------------------------------------------------------------
 ### Building.
 
 all: $(TARGETS)
@@ -100,4 +122,38 @@ all: $(TARGETS)
 clean:; rm -f $(TARGETS) *.new
 .PHONY: clean
 
+###--------------------------------------------------------------------------
+### Installation.
+
+## The local machine doesn't want the complicated SSH stuff.
+THISHOST                = $(shell hostname)
+
+## Testing.
+check: $(THISHOST).sh
+       $(ROOT) ./$(THISHOST).sh test
+
+## Installation on a local host,
+install/$(THISHOST): $(THISHOST).sh
+       [ "x$(SCRIPTS)" = x ] || $(ROOT) install -m755 $(SCRIPTS) $(sbindir)
+       $(ROOT) ./$(THISHOST).sh replace
+
+## Installation on a remote host.
+install/%: %.sh
+       if [ "x$(SCRIPTS)" != x ]; then \
+         for i in $(SCRIPTS); do \
+           $(ROOT) scp $$i root@$*:$(sbindir)/$$i.new && \
+           $(ROOT) ssh root@$* \
+               'cd $(sbindir) && chmod 755 $$i.new && mv $$i.new $i' || \
+           exit 1; \
+         done; \
+       fi
+       $(ROOT) scp $*.sh root@$*:$(FIREWALL).new
+       $(ROOT) ssh root@$* $(FIREWALL) remote-prepare
+       $(ROOT) ssh root@$* $(FIREWALL) remote-commit
+       $(ROOT) ssh root@$* rm -f $(FIREWALL).new
+
+## General installation target.
+install: all install/$(THISHOST) $(addprefix install/,$(HOSTS))
+.PHONY: install install/$(THISHOST)
+
 ###----- That's all, folks --------------------------------------------------
diff --git a/dummy-payload.m4 b/dummy-payload.m4
new file mode 100644 (file)
index 0000000..117dc4c
--- /dev/null
@@ -0,0 +1,53 @@
+### -*-sh-*-
+###
+### Dummy payload for testing the prologue
+###
+### (c) 2011 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### 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 2 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, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+m4_divert(5)m4_dnl
+###--------------------------------------------------------------------------
+### Configuration.
+
+defconf(firewall_script, ./dummy-inst.sh)
+defconf(firewall_failsafe, ./dummy-failsafe.sh)
+defconf(dummy_action, lose)
+
+m4_divert(20)m4_dnl
+###--------------------------------------------------------------------------
+### Produce some output, and fail or succeed as instructed.
+
+case "$dummy_action" in
+  lose)
+    echo "dummy-payload (stdout): losing"
+    echo >&2 "dummy-payload (stderr): still losing"
+    false
+    ;;
+  win)
+    echo "dummy-payload (stdout): winning"
+    echo >&2 "dummy-payload (stderr): still winning"
+    true
+    ;;
+  *)
+    eval "$dummy_action"
+    ;;
+esac
+
+m4_divert(-1)
+###----- That's all, folks --------------------------------------------------
index df5e26d..9a5ade5 100644 (file)
--- a/local.mk
+++ b/local.mk
@@ -1,33 +1,8 @@
 ### Local configuration makefile.
 
+## Common configuration for all hosts.
 MAIN_M4_SOURCES                += local.m4
 
+## The avaiable hosts.
 HOSTS                  += metalzone
 HOSTS                  += vampire
-THISHOST                = $(shell hostname)
-
-ROOT                    = sudo
-
-## Testing.
-check: $(THISHOST).sh
-       firewall_script=./$(THISHOST).sh && \
-       firewall_failsafe=/etc/init.d/firewall && \
-       export firewall_script firewall_failsafe && \
-       [ -x $$firewall_failsafe ] && \
-       $(ROOT) ./$$firewall_script
-
-## Installation.
-install: all check
-       for i in $(HOSTS); do \
-         $(ROOT) scp $$i.sh $$i:/etc/init.d/firewall; \
-         if [ "$(SCRIPTS)" ]; then \
-           for j in $(SCRIPTS); do \
-             $(ROOT) ssh $$i <$$j " \
-                 cd /usr/local/sbin && \
-                 rm -f $$j.new && \
-                 cat >$$j.new && \
-                 chmod 755 $$j.new && \
-                 mv $$j.new $$j"; \
-           done; \
-         fi; \
-       done
index 4b14578..36f8b86 100644 (file)
@@ -26,8 +26,11 @@ m4_divert(10)m4_dnl
 ### Failsafe prologue.
 
 revert () {
-  echo "$1!  Retreating to safe version..."
-  if ! "$firewall_failsafe" revert; then
+  escape=$1 badness=$2
+  ## Report a firewall script failure and retreat to a safe place.
+
+  echo "$2!  Retreating to safe version..."
+  if ! "$1" revert; then
     echo >&2 "Safe firewall failed.  You're screwed.  Good luck."
     exit 1
   fi
@@ -40,40 +43,118 @@ finished () {
   exit 0
 }
 
+try () {
+  old=$1 new=$2
+  ## Install the NEW firewall rules.  If it fails, revert to the OLD ones.
+  ## Updating firewall rules can fail spectacularly, so be careful.  Leave a
+  ## timebomb in the form of NEW.errors: if this isn't removed in 10 seconds
+  ## after the NEW rules complete successfully, then revert.  Write errors to
+  ## NEW.errors.
+
+  ## Make sure we have an escape route.
+  if [ ! -x "$old" ]; then
+    echo >&2 "$0: no escape plan: \`$old' is missing"
+    exit 1
+  fi
+
+  ## Clear the air and make the errors file.
+  rm -f "$new.errors" "$new.timebomb" "$new.grabbed"
+  exec >"$new.errors" 2>&1
+
+  ## Now try to install the new firewall.
+  "$new" install || revert "$old" "Failed"
+
+  ## Set up the time bomb.  Leave the errors file there if we failed.
+  (sleep 10
+   if [ -f "$new.errors" ]; then
+     mv "$new.errors" "$new.timebomb"
+     revert "$old" "Time bomb"
+   fi)&
+}
+
+catch () {
+  new=$1
+  ## Report successful installation of the script.
+
+  if mv "$new.errors" "$new.grabbed" 2>/dev/null; then
+    rc=0
+    echo "Installed OK."
+  else
+    mv "$new.timebomb" "$new.grabbed"
+    echo "Timebomb went off."
+    rc=1
+  fi
+  cat "$new.grabbed" >&2
+  rm -f "$new.grabbed"
+  return $rc
+}
+
 exit_after_clearing=:
 export FWCOOKIE=magical
-case "${1-update}" in
-  start | restart | reload | force-reload)
+case "$#,${1-update}" in
+  1,start | 1,restart | 1,reload | 1,force-reload)
     echo -n "Starting up firewall... "
-    "$firewall_script" install || revert "Failed"
+    "$firewall_script" install || revert "$firewall_failsafe" "Failed"
     finished
     ;;
-  stop)
+  1,stop)
     echo -n "Shutting down firewall... "
     exit_after_clearing=finished
     ;;
-  update)
-    echo -n "Installing new firewall... "
-    "$firewall_script" install || revert "Failed"
-    echo "Done."
+  1,replace | 1,test)
+    echo -n "Running new firewall... "
+    if ! (try "$firewall_script" "$0"); then
+      echo "FAILED."
+      cat "$0.errors" >&2
+      exit
+    fi
+    echo "done."
     echo "Can you hear me?"
-    parent=$$
-    (sleep 5; kill $parent; revert "Timeout")&
-    child=$!
+    (trap 'exit 127' TERM
+     while :; do
+       if [ -f "$0.timebomb" ]; then
+        kill $$
+        echo "Timebomb went off!"
+        cat "$0.timebomb"
+        exit 1
+       fi
+       sleep 1
+     done)&
     read answer
-    kill $child
-    case "$answer" in
-      y* | Y*)
-       echo "Cool.  We're done here."
+    kill $!
+    catch "$0"
+    case "$1,$answer" in
+      replace,y* | replace,Y*)
+       install -m755 "$0" "$firewall_script"
+       echo "Cool.  Firewall script replaced."
        exit 0
        ;;
+      test,y* | test,Y*)
+       echo "Cool.  Everything seems good."
+       exit 0
+       ;;
+      *)
+       revert "$firewall_script" "Bogus"
+       ;;
     esac
-    revert "Bogus"
     ;;
-  install | revert)
+  1,remote-prepare)
+    try "$firewall_script" "$0"
+    exit 0
+    ;;
+  1,remote-commit)
+    catch "$0"
+    install -m755 "$0" "$firewall_script"
+    exit 0
+    ;;
+  1,install | 1,revert)
     ;;
   *)
-    echo >&2 "Usage: firewall start|stop|reload|restart|force-reload|update|install|revert"
+    cat >&2 <<EOF
+Usage:
+       $0 start|stop|reload|restart|force-reload
+       $0 replace|test|remote-prepare|remote-commit
+EOF
     exit 1
     ;;
 esac