--- a/bin/update Tue Mar 20 12:55:31 2012 +0200
+++ b/bin/update Tue Mar 20 13:38:12 2012 +0200
@@ -3,6 +3,8 @@
set -ue
+
+### Paths
ROOT=$(pwd)
# resolve $0
@@ -17,30 +19,29 @@
fi
done
-# bin dir
+# Our bin dir, with scripts
BIN=$(dirname $self)
-# data files
+# Data files
DATA=settings
ZONES=zones
SERIALS=$DATA
REPO=
-# data args
+# Script/data args
PROCESS_ARGS='--input-charset latin-1'
-FORWARD_MX=mail
-REVERSE_ZONE=194.197.235
-REVERSE_DOMAIN=paivola.fi
-
# external progs
NAMED_CHECKZONE=/usr/sbin/named-checkzone
HG=/usr/bin/hg
RNDC=/usr/sbin/rndc
-## options
+### Command-line argument handling
+
IS_TTY=
+## Options
+LOG_ERROR=y
LOG_WARN=y
LOG=y
LOG_FORCE=y
@@ -63,6 +64,7 @@
DEPLOY_SKIP=
+## Output command-line argument help.
function help_args {
local prog=$1
@@ -93,6 +95,7 @@
END
}
+## Parse any command-line arguments, setting the global options vars.
function parse_args {
OPTIND=1
@@ -146,11 +149,13 @@
done
}
-## lib
+### Logging
+# Output message to stderr.
function log_msg {
echo "$*" >&2
}
+# Output message to stderr, optionally with given color, if TTY.
function log_color {
local code=$1; shift
@@ -161,14 +166,17 @@
fi
}
+## Log at various log-levels
+
function log_error {
- log_color '31' "$*"
+ [ $LOG_ERROR ] && log_color '31' "$*"
}
function log_warn {
[ $LOG_WARN ] && log_color '33' "$*" || true
}
+# plain
function log {
[ $LOG ] && log_msg "$*" || true
}
@@ -197,7 +205,7 @@
[ $LOG_CMD ] && log_color 35 " \$ $*" || true
}
-# XXX: broken
+# Output stacktrace, broken.
function log_stack {
local level=1
@@ -210,27 +218,38 @@
done
}
+# Output calling function's name.
function func_caller {
caller 1 | cut -d ' ' -f 2
}
+### High-level logging output
+# Log with func_caller at log_debug
function debug {
printf -v prefix "%s" $(func_caller)
log_debug "$prefix: $*"
}
+# Log with func_caller at log_error and exit, intended for internal errors...
function fail {
log_error "$(func_caller): $*"
exit 2
}
+# Log at log_error and exit
function die {
log_error "$*"
exit 1
}
+### Command execution
+## Execute command, possibly logging its execution.
+#
+# cmd $cmd...
+#
+# Fails if the command returns an error exit code.
function cmd {
log_cmd "$@"
@@ -247,6 +266,9 @@
return ${PIPESTATUS[0]}
}
+
+### FS utils
+# Create dir in $ROOT if not exists.
function ensure_dir {
local dir=$1
@@ -256,21 +278,30 @@
fi
}
+## Output absolute path from $ROOT:
+#
+# abspath $path
+#
function abspath () {
- echo "$ROOT/$1"
+ local path=$1
+
+ echo "$ROOT/$path"
}
-## hg
+### HG wrappers
+# Run `hg ...` within $REPO.
function hg {
local repo=$REPO; shift
cmd $HG -R $ROOT/$repo "$@"
}
+# Does the repo have local modifications?
function hg_modified {
hg id | grep -q '+'
}
+# Output possible -u flag for commit.
function hg_user {
if [ ${SUDO_USER:-} ]; then
echo '-u' "$SUDO_USER"
@@ -284,12 +315,17 @@
fi
}
+# Show changes in repo
function hg_diff {
hg diff
}
+## Commit changes in repo, with given message:
+#
+# hg_commit $msg
+#
function hg_commit {
- local msg=$2
+ local msg=$1
local user_opt=$(hg_user)
debug "$user_opt: $msg"
@@ -297,17 +333,18 @@
}
-## functions
+### Dependency-based updates
-## Dependency-based updates
-# Compare the given output file with all given source files.
+## Compare the given output file with all given source files:
+#
+# check_update $out ${deps[@]} && do_update $out ... || ...
#
# Returns true if the output file needs to be updated.
function check_update {
# target
- local dst=$1; shift
+ local out=$1; shift
- debug "$dst"
+ debug "$out"
# need update?
local update=
@@ -316,7 +353,7 @@
debug " update: unknown deps"
update=y
- elif [ ! -e $dst ]; then
+ elif [ ! -e $out ]; then
debug " update: dest missing"
update=y
@@ -334,7 +371,7 @@
if [ ! -e $ROOT/$dep ]; then
fail "$dst: Missing source: $dep"
- elif [ $ROOT/$dst -ot $ROOT/$dep ]; then
+ elif [ $ROOT/$out -ot $ROOT/$dep ]; then
debug " update: $dep"
update=y
else
@@ -348,21 +385,28 @@
[ $update ]
}
+## Generate updated output file from given command's stdout:
+#
+# do_update $out $BIN/cmd --args
+#
+# Writes output to a temporary .new file, optionally shows a diff of changes, and commits
+# the new version to $out (unless noop'd).
function do_update {
- local dst=$1; shift
- local tmp=$dst.new
+ local out=$1; shift
+ local tmp=$out.new
- debug "$dst"
+ debug "$out"
cmd "$@" > $ROOT/$tmp
# compare
- if [ -e $ROOT/$dst ] && [ $UPDATE_DIFF ]; then
+ if [ -e $ROOT/$out ] && [ $UPDATE_DIFF ]; then
debug " changes:"
# terse
- indent " " diff --unified=1 $ROOT/$dst $ROOT/$tmp || true
+ indent " " diff --unified=1 $ROOT/$out $ROOT/$tmp || true
fi
+ # deploy
if [ $UPDATE_NOOP ]; then
# cleanup
debug " no-op"
@@ -370,20 +414,49 @@
cmd rm $ROOT/$tmp
else
# commit
- debug " commit"
+ debug " deploy"
- cmd mv $ROOT/$tmp $ROOT/$dst
+ cmd mv $ROOT/$tmp $ROOT/$out
fi
}
-# links
+## Look for a link target:
+#
+# find_link $lnk $tgt...
+#
+# Outputs the first given target to exist, skipping any that are the same as the given $lnk.
+# If no $tgt matches, outputs the last one, or '-'.
+function choose_link {
+ local lnk=$1; shift
+ local tgt=-
+
+ for tgt in "$@"; do
+ [ $tgt != $out ] && [ -e $ROOT/$tgt ] && break
+ done
+
+ echo $tgt
+}
+
+
+## Compare symlink to target:
+#
+# check_link $lnk $tgt && do_link $lnk $tgt || ...
+#
+# Tests if the symlink exists, and the target matches.
+# Fails if the target does not exist.
function check_link {
local lnk=$1
local tgt=$2
+
+ [ ! -e $ROOT/$tgt ] && fail "$tgt: target does not exist"
[ ! -e $ROOT/$lnk ] || [ $(readlink $ROOT/$lnk) != $ROOT/$tgt ]
}
+## Update symlink to point to target:
+#
+# do_link $lnk $tgt
+#
function do_link {
local lnk=$1
local tgt=$2
@@ -391,39 +464,11 @@
cmd ln -sf $ROOT/$tgt $ROOT/$lnk
}
-## hosts
-# copy hosts input zone verbatim
-function copy_hosts {
- local zone=$1
- local base=$2
-
- if check_update $zone $base; then
- log_update "Copying hosts $zone <- $base..."
-
- do_update $zone \
- cat $ROOT/$base
- else
- log_skip "Copying hosts $zone <- $base: not changed"
- fi
-}
-
-# generate hosts zone from input zone
-function update_hosts {
- local zone=$1; shift
- local base=$1; shift
-
- if check_update $zone $base; then
- log_update "Generating hosts $zone <- $base..."
-
- do_update $zone \
- $BIN/process-zone $PROCESS_ARGS $ROOT/$base "$@"
- else
- log_skip "Generating hosts $zone <- $base: not changed"
- fi
-}
-
-## actions
-# serial
+## Update .serial number:
+#
+# do_update_serial $serial
+#
+# Shows old/new serial on debug.
function do_update_serial {
local serial=$1
@@ -439,6 +484,60 @@
debug " $old -> $new"
}
+## Perform `hg commit` for $DATA
+function do_commit {
+ local msg=$1
+
+ indent " " hg_diff
+
+ hg_commit "$msg"
+}
+
+### Hosts
+## Update hosts from verbatim from input zone data:
+#
+# copy_hosts $ZONES/$zone $DATA/$base
+#
+# Writes updated zone to $zone, deps on $base.
+function copy_hosts {
+ local zone=$1
+ local base=$2
+
+ if check_update $zone $base; then
+ log_update "Copying hosts $zone <- $base..."
+
+ do_update $zone \
+ cat $ROOT/$base
+ else
+ log_skip "Copying hosts $zone <- $base: not changed"
+ fi
+}
+
+## Generate hosts from input zone data using $BIN/process-zone:
+#
+# update_hosts $ZONES/$zone $DATA/$base
+#
+# Writes process-zone'd data to $zone, deps on $base.
+function update_hosts {
+ local zone=$1; shift
+ local base=$1; shift
+
+ if check_update $zone $base; then
+ log_update "Generating hosts $zone <- $base..."
+
+ do_update $zone \
+ $BIN/process-zone $PROCESS_ARGS $ROOT/$base "$@"
+ else
+ log_skip "Generating hosts $zone <- $base: not changed"
+ fi
+}
+
+## Generate new serial for zone using $BIN/update-serial, if the zone data has changed:
+#
+# update_serial $zone $deps...
+#
+# Supports SERIAL_FORCE/NOOP.
+# Updates $SERIALS/$zone.serial.
function update_serial {
local zone=$1; shift
@@ -463,6 +562,9 @@
fi
}
+## Link serial for zone from given base-zone:
+#
+# link_serial $zone $base
function link_serial {
local zone=$1
local base=$2
@@ -480,7 +582,11 @@
fi
}
-# zone
+## Update zone file verbatim from source:
+#
+# copy_zone $view $zone [$base]
+#
+# Copies changed $DATA/$base zone data to $ZONES/$view/$zone.
function copy_zone {
local view=$1
local zone=$2
@@ -499,6 +605,11 @@
fi
}
+## Expand zone file from source using $BIN/expand-zone:
+#
+# update_zone $view $zone [$base]
+#
+# Processed $DATA/$base zone data through $BIN/expand-zone, writing output to $ZONES/$view/$zone.
function update_zone {
local view=$1
local zone=$2
@@ -523,28 +634,36 @@
fi
}
+## Link zone file to ues given shared zone.
+#
+# link_zone $view $zone [$base]
+#
+# Looks for shared zone at:
+# $ZONES/$view/$base
+# $ZONES/common/$base
function link_zone {
local view=$1
local zone=$2
local base=${3:-$zone}
local out=$ZONES/$view/$zone
-
- # find tgt
- for tgt in $ZONES/$view/$base $ZONES/common/$base; do
- [ $tgt != $out ] && [ -e $tgt ] && break
- done
+ local tgt=$(choose_link $out $ZONES/$view/$base $ZONES/common/$base)
if check_link $out $tgt; then
log_update "Linking $out -> $tgt..."
do_link $out $tgt
+
else
log_skip "Linking $out -> $tgt: not changed"
fi
}
-## Tests
+## Test hosts zone for validity:
+#
+# check_hosts $DATA/$hosts --check-exempt ...
+#
+# Fails if the check fails.
function check_hosts {
local hosts=$1; shift 1
@@ -561,6 +680,12 @@
fi
}
+## Test zone file for validity using named-checkzone:
+#
+# check_zone $view $zone $origin
+#
+# Uses the zonefile at $ZONES/$view/$zone, loading it with given initial $ORIGIN.
+# Fails if the check fails.
function check_zone {
local view=$1
local zone=$2
@@ -583,52 +708,83 @@
fi
}
-## Deploy
-# deploy new zone data to bind
+## Load update zonefiles into bind:
+#
+# deploy_zones
+#
+# Invokes `rndc reload`, showing its output.
function deploy_zones {
- indent " rndc: " $RNDC reload
+ if [ $DEPLOY_SKIP ]; then
+ log_skip "Reload zones: skipped"
+
+ else
+ log_update "Reload zones..."
+
+ # run
+ indent " rndc: " \
+ $RNDC reload
+ fi
}
-
-# commit data changes
+## Commit changes in $DATA to version control:
+#
+# commit_data
+#
+# Invokes `hg commit` in the $REPO, first showing the diff.
function commit_data {
local repo=$REPO
-
- if hg_modified; then
- log_update "Commit changes in $repo:"
+ local commit_msg="$COMMIT_MSG"
- indent " " hg_diff
+ local msg="Commit changes in $repo..."
- hg_commit "$COMMIT_MSG"
+ # operate?
+ if [ $COMMIT_FORCE ]; then
+ log_force "$msg..."
+
+ do_commit "$commit_msg"
+
+ elif ! hg_modified; then
+ log_skip "$msg: no changes"
+
+ elif [ $COMMIT_SKIP ]; then
+ log_noop "$msg: skipped"
+
else
- log_skip "Commit changes in $repo: no changes"
+ log_update "msg..."
+
+ do_commit "$commit_msg"
fi
}
-function main {
- # test tty
- [ -t 1 ] && IS_TTY=y
-
- parse_args "$@"
+## Site settings, used as arguments to scripts
+# MX record to generate in hosts --forward-zone
+FORWARD_MX=mail
- ## test env
- [ -d $ROOT/$DATA ] || die "Missing data: $ROOT/$DATA"
- ensure_dir $ZONES
-
- # output dirs
- local views=(internal external)
+# IP network to generate reverse records for in --reverse-zone
+REVERSE_ZONE=194.197.235
- for view in "${views[@]}" "common" "hosts" "includes"; do
- ensure_dir $ZONES/$view
- done
+# Origin domain to generate reverse records for in --reverse-zone
+REVERSE_DOMAIN=paivola.fi
- ## hosts
+# Views used
+VIEWS=(internal external)
+
+# Base domain zone for domains
+DOMAIN_BASE=paivola
+
+# List of actual domains used; will be linked to $DOMAIN_BASE
+DOMAINS=(paivola.fi paivola.net paivola.org paivola.info paivola.mobi xn--pivl-load8j.fi)
+
+## Operate!
+function run {
+ ## Hosts
# test
log "Testing hosts..."
- check_hosts $DATA/paivola.txt --check-exempt ufc
+ # data args...
+ check_hosts $DATA/paivola.txt --check-exempt ufc
# update
log "Generating host zones..."
- # zone base *args
+ # hosts data args...
update_hosts $ZONES/hosts/paivola:internal $DATA/paivola.txt --forward-zone --forward-txt --forward-mx $FORWARD_MX
update_hosts $ZONES/hosts/paivola:external $DATA/paivola.txt --forward-zone
update_hosts $ZONES/hosts/194.197.235 $DATA/paivola.txt --reverse-zone $REVERSE_ZONE --reverse-domain $REVERSE_DOMAIN
@@ -641,8 +797,7 @@
# update_hosts $ZONES/hosts/pvl $DATA/pvl.txt --forward-zone
copy_hosts $ZONES/hosts/pvl $DATA/pvl.txt
- ## zones
- # parts
+ ## Includes
log "Copying zone includes..."
# view zone base
copy_zone includes paivola:internal paivola.zone.internal
@@ -650,25 +805,25 @@
copy_zone includes paivola.auto paivola.zone.auto
copy_zone includes paivola.services paivola.zone.services
- # serials
+ ## Serials
log "Updating serials..."
- # zone *deps
- update_serial pvl $ZONES/hosts/pvl $DATA/pvl.zone
- update_serial 10 $ZONES/hosts/10 $DATA/10.zone
- update_serial 192.168 $ZONES/hosts/192.168 $DATA/192.168.zone
+ # zone deps...
+ update_serial pvl $ZONES/hosts/pvl $DATA/pvl.zone
+ update_serial 10 $ZONES/hosts/10 $DATA/10.zone
+ update_serial 192.168 $ZONES/hosts/192.168 $DATA/192.168.zone
- update_serial paivola \
- $ZONES/hosts/paivola:* \
- $DATA/paivola.zone \
- $ZONES/includes/paivola:* \
- $ZONES/includes/paivola.*
+ update_serial paivola \
+ $ZONES/hosts/paivola:* \
+ $DATA/paivola.zone \
+ $ZONES/includes/paivola:* \
+ $ZONES/includes/paivola.*
- update_serial 194.197.235 \
- $ZONES/hosts/194.197.235 \
- $DATA/194.197.235.zone
+ update_serial 194.197.235 \
+ $ZONES/hosts/194.197.235 \
+ $DATA/194.197.235.zone
- # zones
+ ## Zones
log "Updating zones..."
# view zone base
update_zone internal pvl
@@ -682,7 +837,7 @@
link_zone internal 194.197.235
link_zone external 194.197.235
- # test
+ ## Test
log "Testing zones..."
# view zone origin
check_zone internal paivola paivola.fi
@@ -692,36 +847,45 @@
check_zone internal 192.168 192.168.in-addr.arpa
check_zone common 194.197.235 235.197.194.in-addr.arpa
- # extra zones...
- local base=paivola
- local link_zones=(paivola.fi paivola.net paivola.org paivola.info paivola.mobi xn--pivl-load8j.fi)
+ ## Domains...
+ log "Linking domains..."
+ for view in "${VIEWS[@]}"; do
+ for zone in "${DOMAINS[@]}"; do
+ # link
+ link_zone $view $zone $DOMAIN_BASE
- log "Linking zones..."
- for view in "${views[@]}"; do
- for zone in "${link_zones[@]}"; do
- link_zone $view $zone $base
- check_zone $view $zone $zone
+ # test
+ check_zone $view $zone $zone
+ done
done
+
+ ## Deploy
+ log "Deploy zones..."
+ deploy_zones
+
+ ## Commit
+ log "Commit data..."
+ commit_data
+}
+
+## Main entry point
+function main {
+ # test tty
+ [ -t 1 ] && IS_TTY=y
+
+ parse_args "$@"
+
+ ## test env
+ [ -d $ROOT/$DATA ] || die "Missing data: $ROOT/$DATA"
+ ensure_dir $ZONES
+
+ ## Output dirs
+ for dir in "common" "hosts" "includes" "${VIEWS[@]}"; do
+ ensure_dir $ZONES/$dir
done
- ## deploy
- if [ $DEPLOY_SKIP ]; then
- log "Deploy zones: skipped"
-
- else
- log "Deploy zones..."
-
- deploy_zones
- fi
-
- ## commit
- if [ $COMMIT_SKIP ] && [ ! $COMMIT_FORCE ]; then
- log "Commit data: skipped"
-
- else
- log "Commit data..."
- commit_data
- fi
+ ## Go
+ run
}
main "$@"