update: refactor into modularized lib/pvl/**.sh layout
authorTero Marttila <tero.marttila@aalto.fi>
Thu, 26 Feb 2015 22:36:05 +0200
changeset 628 b10ad946d01d
parent 627 a81206440be2
child 629 7214fe5c6fac
update: refactor into modularized lib/pvl/**.sh layout
bin/update
lib/pvl/apply.sh
lib/pvl/apply/cmd.sh
lib/pvl/commit.sh
lib/pvl/commit/git.sh
lib/pvl/file.sh
lib/pvl/hosts/dhcp.sh
lib/pvl/hosts/hosts.sh
lib/pvl/hosts/update.sh
lib/pvl/hosts/zone.sh
lib/pvl/list.sh
lib/pvl/log.sh
lib/pvl/main.sh
lib/update.config
lib/update.operations
--- a/bin/update	Thu Feb 26 21:38:09 2015 +0200
+++ b/bin/update	Thu Feb 26 22:36:05 2015 +0200
@@ -7,7 +7,6 @@
 LIB=${LIB:-$SRC/lib}
 VAR=${VAR:-$SRV/var}
 
-
 . $LIB/pvl/main.sh
 
 MODULES=(log commit apply update)
--- a/lib/pvl/apply.sh	Thu Feb 26 21:38:09 2015 +0200
+++ b/lib/pvl/apply.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -48,11 +48,11 @@
 
     if [ ${#@} -eq 1 ]; then
         debug "  update: unknown deps"
-        return 0
+        return 2
 
     elif [ ! -e "$out" ]; then
         debug "  update: dest missing"
-        return 0
+        return 2
         
     elif [ "$APPLY" = 1]; then
         debug "  update: forced"
--- a/lib/pvl/apply/cmd.sh	Thu Feb 26 21:38:09 2015 +0200
+++ b/lib/pvl/apply/cmd.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -4,12 +4,12 @@
 #
 # Writes output to a temporary .new file, optionally shows a diff of changes, and commits
 # the new version to $out (unless noop'd).
-function cmd_apply {
+function apply_cmd {
     local out="$1"
     local tmp="$out.new"
 
     debug "$out"
-    cmd "${@:1}" > "$tmp"
+    cmd "${@:2}" > "$tmp"
 
     # compare
     if [ -e "$out" -a -z "$APPLY_DIFF" ]; then
--- a/lib/pvl/commit.sh	Thu Feb 26 21:38:09 2015 +0200
+++ b/lib/pvl/commit.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -28,7 +28,7 @@
 
         m)  COMMIT_MSG="$optarg" ;;
         
-        n)  COMMIT= ;;
+        n)  COMMIT=0 ;;
         p)  COMMIT_DIFF=1 ;;
         *)  return 1
     esac
@@ -68,7 +68,7 @@
     if [ "$COMMIT" = 1 ]; then
         log_force   "$repo: force commit"
 
-        [ $COMMIT_DIFF ] && indent "    " ${commit}_diff "$repo"
+        [ "$COMMIT_DIFF" ] && indent "    " ${commit}_diff "$repo" || true
 
         ${commit}_commit "$repo" "$commit_msg"
 
@@ -79,12 +79,39 @@
         log_noop    "$repo: skip commit"
         
         # still show diff, though
-        [ $COMMIT_DIFF ] && indent "    " ${commit}_diff "$repo"
+        [ "$COMMIT_DIFF" ] && indent "    " ${commit}_diff "$repo" || true
     else
         log_apply   "$repo: commit: $commit_msg"
 
-        [ $COMMIT_DIFF ] && indent "    " ${commit}_diff "$repo"
+        [ "$COMMIT_DIFF" ] && indent "    " ${commit}_diff "$repo" || true
 
         ${commit}_commit "$repo" "$commit_msg"
     fi
 }
+
+function _commit {
+    local repo="$1"
+    local cmd="$2"
+
+    # detect
+    local commit="$(commit_probe "$repo")"
+
+    if [ -z "$commit" ]; then
+        log_warn "$repo: Unable to detect VCS repo"
+        return 1
+    fi
+    
+    ${commit}_${cmd} $repo "${@:3}"
+}
+
+function commit_modified {
+    local repo="$1"
+
+    _commit $repo modified
+}
+
+function commit_time {
+    local repo="$1"
+
+    _commit $repo time
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/commit/git.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -0,0 +1,60 @@
+# Git wrappers
+
+GIT=/usr/bin/git
+GIT_ARGS=()
+
+
+function git_probe {
+    local repo=$1
+
+    [ -d "$repo/.git" ]
+}
+
+## Run `git ...` within $REPO.
+function git {
+    local repo=$1
+    cmd $GIT -C "$repo" "${GIT_ARGS[@]:-}" "${@:2}"
+}
+
+## Does the repo have local modifications?
+function git_modified {
+    local repo=$1
+
+    git $repo diff --quiet
+}
+
+## Get the date for the current commit as an unix timestamp
+function hg_time {
+    local repo=$1
+
+    git $repo log -1 --format=format:%ct
+}
+
+## Show changes in repo
+#   git_diff     [path ...]
+function git_diff {
+    local repo=$1
+    git $repo diff --cached "$@"
+    git $repo diff "$@"
+}
+
+## Commit changes in repo, with given message:
+#
+#   git_commit   $repo $msg
+#
+# Automatically determines possible -u to use when running with sudo.
+function git_commit {
+    local repo="$1"
+    local msg="$2"
+    local opts=()
+
+    if [ ${SUDO_USER:-} ]; then
+        opts+=("--author=$SUDO_USER")
+    fi
+    
+    if [ "$msg" ]; then
+        opts+=('-m' "$msg")
+    fi
+   
+    git $repo commit -a "${opts[@]:-}"
+}
--- a/lib/pvl/file.sh	Thu Feb 26 21:38:09 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-### FS utils
-
-## Output absolute path
-#
-#   abspath $path
-#
-# XXX: improve...?
-function abspath () {
-    local path="$1"
-
-    echo "$SRV/$path"
-}
-
-function _list {
-    local glob="$1"
-    local test="$2"
-    local prefix="$3"
-
-    for file in $glob; do
-        [ $test "$file" ] || continue
-        [ -n "$prefix" ] && file="${file#$prefix}"
-
-        echo -n "$file "
-    done
-}
-
-## List names of all files in dir
-function list {
-    _list "$1/*" '-e' ${2:-$1/}
-}
-
-## List names of files in dir:
-#
-#   list_files $dir
-#
-function list_files {
-    _list "$1/*" '-f' ${2:-$1/}
-}
-
-## List names of dirs in dir:
-function list_dirs {
-    _list "$1/*" '-d' ${2:-$1/}
-}
-
-## List names of any files underneath dir or file:
-function expand_files {
-    _list "$1 $1/**" '-f' ''
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/hosts/dhcp.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -0,0 +1,94 @@
+DHCPD=/usr/sbin/dhcpd
+DHCPD_CONF=/etc/dhcp/dhcpd.conf
+DHCP_SERVER=isc-dhcp-server
+
+## Generate DHCP hosts config from hosts hosts using pvl.hosts-dhcp
+#
+#   update_hosts_dhcpe $out $src
+function update_hosts_dhcp {
+    local out="$1"
+    local src="$2"
+    local srcs=($src/*)
+    local msg="$out: Generating DHCP hosts: $src"
+
+    if apply_check "$out" "${srcs[@]}"; then
+        log_skip "$msg"
+    else
+        log_apply "$msg"
+    
+        apply_cmd "$out" $OPT/bin/pvl.hosts-dhcp \
+            --hosts-include="$HOSTS_INCLUDE" \
+             "$src"
+    fi
+}
+
+## Generate dhcp confs from source using pvl.dhcp-conf:
+function update_dhcp_conf {
+    local out="$1"
+    local src="$2"
+    local msg="$out: Generating DHCP conf: $src"
+
+    if apply_check "$out" "$src"; then
+        log_skip "$msg"
+    else
+        log_apply "$msg"
+            
+        apply_cmd "$out" $OPT/bin/pvl.dhcp-conf \
+            --include-path=$VAR/dhcp \
+            "$src"
+    fi
+}
+
+## Test DHCP configuration for validity using dhcpd -t:
+#
+#   check_dhcp      [$conf]
+#
+# Defaults to the global $DHCPD_CONF.
+# Fails if the check fails.
+function test_dhcp {
+    local conf="$1"
+
+    if [ ! -e "$DHCPD" ]; then
+        log_warn "check_dhcp: dhcpd not installed, skipping: $conf"
+        return 0
+    fi
+   
+    log_check "Checking DHCP: $conf" 
+
+    test_cmd "$conf" \
+        "$DHCPD" -cf "$conf" -t
+}
+
+## Reload dhcp hosts
+#
+#   reload_dhcp
+#
+# noop's if we haven't reloaded zones
+function reload_dhcp {
+    if [ "$RELOAD" = 1 ]; then
+        log_force "Reload DHCP"
+        
+    elif [ "$RELOAD" = 0 ]; then
+        log_noop "Skip reload DHCP"
+
+        return
+ 
+    elif [ ! -e "$DHCPD" ]; then
+        warn "Skip missing DHCP"
+
+        return 
+   
+    elif test_dhcp "$DHCP_CONF"; then
+        log_error "Skip config error DHCP"
+
+    elif ! cmd_test service $DHCP_SERVICE status >/dev/null; then
+        log_warn "dhcpd not running; did not restart"
+
+        return
+
+    else
+        log_apply "Reload DHCP"
+    fi
+
+    cmd service $DHCP_SERVICE restart
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/hosts/hosts.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -0,0 +1,11 @@
+# TODO
+## Test hosts zone for validity using pvl.hosts-check:
+#
+#   check_hosts     .../hosts
+function check_hosts {
+    local hosts=$1; shift 1
+    
+    check $hosts \
+        $OPT/bin/pvl.hosts-check $hosts
+}
+
--- a/lib/pvl/hosts/update.sh	Thu Feb 26 21:38:09 2015 +0200
+++ b/lib/pvl/hosts/update.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -3,6 +3,9 @@
 UPDATE_SERIAL=
 UPDATE_RELOAD=
 
+. $LIB/pvl/hosts/dhcp.sh
+. $LIB/pvl/hosts/zone.sh
+
 function update_help {
     cat <<END
 Update:
@@ -34,7 +37,7 @@
 }
 
 function update_setup {
-    for dir in etc etc/zones etc/hosts; do
+    for dir in $ETC $ETC/zones $ETC/hosts; do
         [ -d $dir ] || die "$dir: missing source directory"
     done
     
@@ -52,67 +55,57 @@
 }
 
 function update_commit {
-    ## Commit
-    # pre-commit check
-    log "Testing hosts..."
-    for hosts in $(list_files etc/hosts); do
-        log_warn "TODO: check_hosts $hosts"
-    done
+    # TODO: pre-commit test host files
 
-    # commit, unless noop'd
     log "Commit..."
         commit  $SRV
 }
 
 function update_update {
-    if hg_modified etc; then
+    if commit_modified $SRV; then
         serial=$(unix_time)
         log_warn "Using local unix time for uncommited changes: $serial"
     else
-        serial=$(hg_time etc)
+        serial=$(commit_time $SRV)
         log_update "Using HG commit timestamp: $serial"
     fi
 
     ## Hosts
     log "Updating forward host zones..."
-    for zone in $(list_dirs etc/hosts/forward); do
-        update_hosts_forward    "var/zones/hosts/forward/$zone"     "$zone" \
-            etc/hosts/forward/$zone/*
+    for zone in $(list_dirs etc/zones/forward); do
+        update_hosts_forward "var/zones/forward/$zone" "etc/zones/forward/$zone/"
+    done
+
+    log "Updating reverse host zones..."
+    for zone in $(list_dirs etc/zones/reverse); do
+        update_hosts_reverse "var/zones/reverse/$zone" "etc/zones/reverse/$zone/"
     done
 
     log "Updating DHCP hosts..."
-    for hosts in $(list etc/hosts/dhcp); do
-        update_hosts_dhcp       "var/dhcp/hosts/$hosts.conf"        $hosts  \
-            $(expand_files "etc/hosts/dhcp/$hosts")
-    done
-
-    log "Updating reverse host zones..."
-    for zone in $(list_dirs etc/hosts/reverse); do
-        update_hosts_reverse    "var/zones/hosts/reverse/$zone"     "$zone" \
-            etc/hosts/reverse/$zone/*
+    for hosts in $(list etc/dhcp/hosts); do
+        update_hosts_dhcp "var/dhcp/hosts/$hosts.conf" "etc/dhcp/hosts/$hosts"
     done
 
     ## Zones
     log "Copying zone includes..."
     for zone in $(list_files etc/zones/includes); do
-        copy                "var/zones/includes/$zone"      "etc/zones/includes/$zone"
-    done
-
-    log "Updating zone serials..."
-    for zone in $(list_files etc/zones); do
-        update_serial       "var/serials/$zone"             $serial \
-            "etc/zones/$zone" $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
+        update_zone_include "var/zones/includes/$zone" "etc/zones/includes/$zone"
     done
 
     log "Updating zones..."
     for zone in $(list_files etc/zones); do
-        update_zone         "var/zones/$zone"               "etc/zones/$zone"       "var/serials/$zone" \
-            $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
+        zone_includes=$(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
+
+        update_zone_serial "var/serials/$zone" $serial \
+            "etc/zones/$zone" $zone_includes
+
+        update_zone "var/zones/$zone" "etc/zones/$zone" "var/serials/$zone" \
+            $zone_includes
     done
 
     log "Updating DHCP confs..."
     for conf in $(list_files etc/dhcp); do
-        update_dhcp_conf    "var/dhcp/$conf"                "etc/dhcp/$conf"
+        update_dhcp_conf "var/dhcp/$conf" "etc/dhcp/$conf"
     done
 }
 
@@ -120,12 +113,12 @@
     ## Check
     log "Testing zones..."
     for zone in $(list_files etc/zones); do
-        check_zone          "var/zones/$zone"       $zone
+        test_zone "var/zones/$zone" $zone
     done
 
     log "Testing DHCP confs..."
-    for conf in var/dhcp/*.conf; do
-        check_dhcp          $conf
+    for conf in $(list_files var/dhcp); do
+        test_dhcp "var/dhcp/$conf"
     done
 
     log "Reload zones..."
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/hosts/zone.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -0,0 +1,216 @@
+#HOSTS_CHARSET='utf-8'
+HOSTS_INCLUDE="$ETC/hosts"
+
+NAMED_CHECKZONE=/usr/sbin/named-checkzone
+
+RNDC=/usr/sbin/rndc
+RNDC_KEY=/etc/bind/rndc.key
+
+
+## Generate forward zone from hosts hosts using pvl.hosts-forward
+#
+#   update_hosts_forward $out $src
+function update_hosts_forward {
+    local out="$1"
+    local src="$2"
+    local srcs=($src/*)
+    local msg="$out: Generating forward hosts zone: $src"
+
+    if apply_check "$out" "${srcs[@]}"; then
+        log_skip "$msg"
+    else
+        log_apply "$msg"
+    
+        apply_cmd "$out" $OPT/bin/pvl.hosts-forward \
+            --hosts-include="$HOSTS_INCLUDE" \
+             "$src"
+    fi
+}
+
+## Generate reverse zone from hosts hosts using pvl.hosts-reverse
+#
+#   update_hosts_reverse $out $src
+function update_hosts_reverse {
+    local out="$1"
+    local src="$2"
+    local srcs=($src/*)
+    local msg="$out: Generating reverse hosts zone: $src"
+
+    if apply_check "$out" "${srcs[@]}"; then
+        log_skip "$msg"
+    else
+        log_apply "$msg"
+    
+        apply_cmd "$out" $OPT/bin/pvl.hosts-reverse \
+            --hosts-include="$HOSTS_INCLUDE" \
+             "$src"
+    fi
+}
+
+function update_zone_include {
+    local out="$1"
+    local src="$2"
+    local msg="$out: Copy zone include: $src"
+
+    if apply_check "$out" "${srcs[@]}"; then
+        log_skip "$msg"
+    else
+        log_apply "$msg"
+
+        apply_cmd "$out" cat \
+            "$src"
+    fi
+}
+
+## Read include paths from file
+function read_zone_includes {
+    cmd sed -n -E 's/^\$INCLUDE\s+"(.+)"/\1/p' "$@"
+}
+
+## (cached) include paths for zone file
+function zone_includes {
+    local cache="$1"
+    local src="$2"
+    local prefix="${3:-}"
+
+    if [ ! -e "$cache" -o "$cache" -ot "$src" ]; then
+        read_zone_includes "$src" > "$cache"
+    fi
+
+    while read include; do
+        echo -n "$prefix$include "
+    done < "$cache"
+}
+
+## Search for prefix-matching includes in zone file
+function zone_includes_grep {
+    local cache="$1"
+    local src="$2"
+    local prefix="$3"
+    
+    for include in $(zone_includes $cache $src); do
+        if [ "${include#$prefix}" != "$include" ]; then
+            echo -n " ${include#$prefix}"
+        fi
+    done
+}
+
+## Generate new serial for zone using pvl.dns-serial, if the zone data has changed:
+#
+#   update_serial   .../serials/$zone   $serial     $deps...
+#
+# Supports SERIAL_FORCE/NOOP.
+# Updates $SERIALS/$zone.serial.
+function update_zone_serial {
+    local out="$1"
+    local serial="$2"
+
+    local old=$(test -e "$out" && cat "$out" || echo '')
+    
+    # test
+    if [ "$SERIAL" = 1 ]; then
+        log_force "$out: Force serial $old <- $serial"
+
+    elif apply_check "$out" "${@:3}"; then
+        log_skip "$out: Skip serial: $old <- $serial"
+        
+        return
+
+    elif [ "$SERIAL" = 0 ]; then
+        log_noop "$out: Noop serial: $old <- $serial"
+        
+        return
+
+    else
+        log_apply "$out: Update serial: $old <- $serial"
+    fi
+
+    echo "$serial" > $out
+}
+
+## Generate zone file from source using pvl.dns-zone:
+#
+#   update_zone out/zones/$zone in/zones/$zone var/serials/$zone
+function update_zone {
+    local out="$1"
+    local src="$2"
+    local serial="$3"
+    local serial_opt=
+    local msg="$out: Generate zone: $src"
+
+    if [ -n "$serial" -a -f "$serial" ]; then
+        serial_opt="--serial=$(cat "$serial")"
+    elif [ "$SERIAL" = 0 ]; then
+        warn "$out: noop'd serial, omitting"
+    else
+        fail "$out: missing serial: $serial"
+    fi
+
+    if apply_check "$@"; then
+        log_skip "$msg"
+    else
+        log_apply "$msg"
+
+        apply_cmd "$out" $OPT/bin/pvl.dns-zone \
+                --include-path=$VAR/zones  \
+                $serial_opt \
+                "$src"
+    fi
+}
+
+## Test zone file for validity using named-checkzone:
+#
+#   check_zone      ..../$zone $origin
+function test_zone {
+    local zone=$1
+    local origin=$2
+
+    log_check "$zone: Checking zone @$origin..." 
+
+    # checkzone is very specific about the order of arguments, -q must be first
+    test_cmd $zone \
+        $NAMED_CHECKZONE $origin $zone
+}
+
+# set by do_reload_zone if zone data has actually been reloaded
+RELOAD_ZONES=
+
+## Load update zonefiles into bind:
+#
+#   reload_zones    
+#
+# Invokes `rndc reload`, showing its output.
+function reload_zones {
+    if [ "$RELOAD" = 1 ]; then
+        log_force "Reload zones"
+        
+    elif [ "$RELOAD" = 0 ]; then
+        log_noop "Skip reload zones"
+        
+        return
+    
+    elif [ ! -e "$RNDC" ]; then
+        warn "Skip with missing RNDC: $RNDC"
+        
+        return
+
+    elif [ ! -e "$RNDC_KEY" ]; then
+        warn "Skip with missing RNDC_KEY: $RNDC_KEY"
+
+        return
+
+    elif [ ! -r $RNDC_KEY ]; then
+        error "Permission denied for RNDC_KEY: $RNDC_KEY"
+
+        return 1
+
+    else
+        log_apply "Reload zones"
+    fi
+
+    indent "        rndc: " \
+        $RNDC reload
+
+    # set flag for dhcp
+    RELOAD_ZONES=1
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/list.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -0,0 +1,38 @@
+### FS utils
+
+function _list {
+    local glob="$1"
+    local test="$2"
+    local prefix="$3"
+
+    for file in $glob; do
+        [ $test "$file" ] || continue
+        [ -n "$prefix" ] && file="${file#$prefix}"
+
+        echo -n "$file "
+    done
+}
+
+## List names of all things in dir
+function list {
+    _list "$1/*" '-e' ${2:-$1/}
+}
+
+## List names of files in dir:
+#
+#   list_files $dir
+#
+function list_files {
+    _list "$1/*" '-f' ${2:-$1/}
+}
+
+## List names of dirs in dir:
+function list_dirs {
+    _list "$1/*" '-d' ${2:-$1/}
+}
+
+## List names of any files underneath dir or file:
+function list_tree {
+    _list "$1 $1/**" '-f' ''
+}
+
--- a/lib/pvl/log.sh	Thu Feb 26 21:38:09 2015 +0200
+++ b/lib/pvl/log.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -47,6 +47,7 @@
             ;;
         V)  LOG_CMD=y ;;
 
+        n)  LOG_NOOP=y ;;
         *)  return 1
     esac
 }
--- a/lib/pvl/main.sh	Thu Feb 26 21:38:09 2015 +0200
+++ b/lib/pvl/main.sh	Thu Feb 26 22:36:05 2015 +0200
@@ -3,8 +3,9 @@
 shopt -s globstar nullglob
 
 . $LIB/pvl/util.sh
+. $LIB/pvl/list.sh
+. $LIB/pvl/log.sh
 . $LIB/pvl/cmd.sh
-. $LIB/pvl/log.sh
 
 function main_help {
     cat <<END
--- a/lib/update.config	Thu Feb 26 21:38:09 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-# charset for files under etc/
-CHARSET='utf-8'
-
-# External bins
-NAMED_CHECKZONE=/usr/sbin/named-checkzone
-
-DHCPD=/usr/sbin/dhcpd
-DHCPD_CONF=/etc/dhcp/dhcpd.conf
-DHCPD_INIT=/etc/init.d/isc-dhcp-server
-
-RNDC=/usr/sbin/rndc
-RNDC_KEY=/etc/bind/rndc.key
-
--- a/lib/update.operations	Thu Feb 26 21:38:09 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,324 +0,0 @@
-#!/bin/bash
-## vim: set ft=sh :
-#
-# Operations on zonefiles/hosts/whatever
-
-## Read include paths from file
-function read_zone_includes {
-    cmd sed -n -E 's/^\$INCLUDE\s+"(.+)"/\1/p' "$@"
-}
-
-## (cached) include paths for zone file
-function zone_includes {
-    local cache="$1"
-    local src="$2"
-    local prefix="${3:-}"
-
-    if [ ! -e "$cache" -o "$cache" -ot "$src" ]; then
-        read_zone_includes "$src" > "$cache"
-    fi
-
-    while read include; do
-        echo -n "$prefix$include "
-    done < "$cache"
-}
-
-## Search for prefix-matching includes in zone file
-function zone_includes_grep {
-    local cache="$1"
-    local src="$2"
-    local prefix="$3"
-    
-    for include in $(zone_includes $cache $src); do
-        if [ "${include#$prefix}" != "$include" ]; then
-            echo -n " ${include#$prefix}"
-        fi
-    done
-}
-
-## Generate forward zone from hosts hosts using pvl.hosts-dns:
-#
-#   update_hosts_forward out/hosts/$hosts $hosts in/hosts/$hosts
-function update_hosts_forward {
-    local out="$1"; shift
-    local domain="$1"; shift
-
-    if check_update "$out" "$@"; then
-        log_update "Generating forward hosts zone $out @ $domain <- $@..."
-    
-        do_update "$out" $OPT/bin/pvl.hosts-dns \
-            --hosts-charset=$CHARSET \
-            --hosts-include=etc/hosts \
-            --forward-zone="$domain" \
-             "$@"
-    else
-        log_skip "Generating forward hosts $out <- $@: not changed"
-    fi
-}
-
-function update_hosts_dhcp {
-    local out=$1; shift
-    local domain="$1"; shift
-
-    if check_update $out "$@"; then
-        log_update "Generating DHCP hosts $out @ $domain <- $@..."
-
-        do_update $out $OPT/bin/pvl.hosts-dhcp \
-            --hosts-charset=$CHARSET \
-            --hosts-include=etc/hosts \
-            "$@"
-    else
-        log_skip "Generating DHCP hosts $out <- $@: not changed"
-    fi
-}
-
-## Generate reverse zone from hosts hosts using pvl.hosts-dns:
-#
-#   update_hosts_reverse out/hosts/$reverse $reverse in/hosts/$hosts
-function update_hosts_reverse {
-    local out="$1"; shift
-    local reverse="$1"; shift
-
-    if check_update "$out" "$@"; then
-        log_update "Generating reverse hosts zone $out <- $@..."
-    
-        do_update "$out" $OPT/bin/pvl.hosts-dns \
-            --hosts-charset=$CHARSET \
-            --hosts-include=etc/hosts \
-            --reverse-zone="$reverse" \
-            "$@"
-    else
-        log_skip "Generating reverse hosts $out <- $@: not changed"
-    fi
-}
-
-## Update .serial number:
-#
-#   do_update_serial .../serials/$zone  $serial
-#
-function do_update_serial {
-    local dst="$1"
-    local serial="$2"
-
-    echo $serial > $dst
-}
-
-
-## Generate new serial for zone using pvl.dns-serial, if the zone data has changed:
-#
-#   update_serial   .../serials/$zone   $serial     $deps...
-#
-# Supports SERIAL_FORCE/NOOP.
-# Updates $SERIALS/$zone.serial.
-function update_serial {
-    local dst="$1"; shift
-    local serial="$1"; shift
-
-    local old=$(test -e "$dst" && cat "$dst" || echo '')
-    
-    # test
-    if [ $SERIAL_FORCE ]; then
-        log_force "Updating $dst: $old <- $serial: forced"
-
-        do_update_serial "$dst" "$serial"
-
-    elif ! check_update "$dst" "$@"; then
-        log_skip "Updating $dst: $old <- $serial: not changed"
-
-    elif [ $SERIAL_NOOP ]; then
-        log_noop "Updating $dst: $old <- $serial: skipped"
-
-    else
-        log_update "Updating $dst: $old <- $serial"
-
-        do_update_serial "$dst" "$serial"
-    fi
-}
-
-## Generate zone file from source using pvl.dns-zone:
-#
-#   update_zone out/zones/$zone in/zones/$zone var/serials/$zone
-function update_zone {
-    local out="$1"; shift
-    local src="$1"; shift
-    local serial="$1"; shift
-    local serial_opt=
-
-    if [ -n "$serial" -a -f "$serial" ]; then
-        serial_opt="--serial=$(cat "$serial")"
-    elif [ $SERIAL_NOOP ]; then
-        warn "$out: noop'd serial, omitting"
-    else
-        fail "$out: missing serial: $serial"
-    fi
-
-    if check_update "$out" "$src" "$serial" "$@"; then
-        log_update "Generating $out <- $src..." 
-
-        do_update "$out" $OPT/bin/pvl.dns-zone "$src" \
-                --include-path=$SRV/var/zones   \
-                $serial_opt
-    else
-        log_skip "Generating $out <- $src: not changed" 
-    fi
-}
-
-## Generate dhcp confs from source using pvl.dhcp-conf:
-function update_dhcp_conf {
-    local out="$1"
-    local src="$2"
-
-    if check_update "$out" "$src"; then
-        log_update "Generating $out <- $src..."
-            
-        do_update "$out" $OPT/bin/pvl.dhcp-conf "$src" \
-            --include-path=$SRV/var/dhcp
-    else
-        log_skip "Generating $out <- $src: not changed"
-    fi
-}
-
-## Test hosts zone for validity using pvl.hosts-check:
-#
-#   check_hosts     .../hosts
-function check_hosts {
-    local hosts=$1; shift 1
-    
-    # TODO
-    check $hosts \
-        $OPT/bin/pvl.hosts-check $hosts
-}
-
-## Test zone file for validity using named-checkzone:
-#
-#   check_zone      ..../$zone $origin
-function check_zone {
-    local zone=$1
-    local origin=$2
-
-    log_check "Checking $zone @ $origin..." 
-
-    # checkzone is very specific about the order of arguments, -q must be first
-    check $zone $NAMED_CHECKZONE $origin $zone
-}
-
-## Test DHCP configuration for validity using dhcpd -t:
-#
-#   check_dhcp      [$conf]
-#
-# Defaults to the global $DHCPD_CONF.
-# Fails if the check fails.
-function check_dhcp {
-    local conf=${1:-$DHCPD_CONF}
-    
-    log_check "Checking DHCP $conf..." 
-
-    if [ ! -e $DHCPD ]; then
-        log_warn "check_dhcp: dhcpd not installed, skipping: $conf"
-        return 0
-    fi
-
-    check $conf \
-        $DHCPD -cf $conf -t
-}
-
-## Test DHCP configuration of given settings/dhcp using check_dhcp $DHCP_DATA/$host.conf:
-#
-#   check_dhcp_conf     $conf
-#
-function check_dhcp_conf {
-    local conf=$1;
-
-    check_dhcp $DHCP_DATA/$conf.conf
-}
-
-### Deploy
-# set by do_reload_zone if zone data has actually been reloaded
-RELOAD_ZONES=
-
-## Run rndc reload
-function do_reload_zones {
-    # run
-    indent "        rndc: " \
-        $RNDC reload
-
-    # set flag
-    RELOAD_ZONES=y
-}
-
-## Load update zonefiles into bind:
-#
-#   reload_zones    
-#
-# Invokes `rndc reload`, showing its output.
-function reload_zones {
-    local msg="Reload zones"
-
-    if [ $RELOAD_FORCE ]; then
-        log_force  "$msg..."
-        
-        do_reload_zones
-
-    elif [ $RELOAD_NOOP ]; then
-        log_noop    "$msg: skipped"
-    
-    elif [ ! -e $RNDC ]; then
-        log_warn "reload_zones: rndc not installed, skipping"
-
-    elif [ ! -e $RNDC_KEY ]; then
-        log_warn   "  $msg: rndc: key not found: $RNDC_KEY"
-
-    elif [ ! -r $RNDC_KEY ]; then
-        log_error   "  $msg: rndc: permission denied: $RNDC_KEY"
-
-        return 1
-
-    else
-        log_update  "$msg..."
-
-        # run
-        do_reload_zones
-    fi
-}
-
-## Reload DHCP by restarting it, if running:
-#
-#   do_reload_dhcp
-#
-# Does NOT restart dhcp if it is not running (status).
-function do_reload_dhcp {
-    if cmd_test $DHCPD_INIT status >/dev/null; then
-        cmd $DHCPD_INIT restart
-    else
-        log_warn "dhcpd not running; did not restart"
-    fi
-}
-
-## Reload dhcp hosts
-#
-#   reload_dhcp
-#
-# noop's if we haven't reloaded zones
-function reload_dhcp {
-    local msg="Reload DHCP hosts"
-
-    if [ $RELOAD_FORCE ]; then
-        log_force  "$msg..."
-        
-        do_reload_dhcp
-
-    elif [ $RELOAD_NOOP ]; then
-        log_noop    "$msg: skipped"
- 
-    elif [ ! -e $DHCPD ]; then
-        log_warn "reload_dhcp: dhcpd not installed, skipping: $conf"
-   
-    else
-        log_update  "$msg..."
-
-        # run
-        do_reload_dhcp
-    fi
-}
-
-