terom@525: #!/bin/bash terom@525: # vim: set ft=sh : terom@525: terom@525: set -ue terom@525: terom@525: ROOT=$(pwd) terom@525: terom@551: # resolve $0 terom@551: self=$0 terom@551: while [ -L $self ]; do terom@554: tgt=$(readlink $self) terom@554: terom@554: if [ "${tgt:0:1}" == "/" ]; then terom@554: self=$tgt terom@554: else terom@554: self=$(dirname $self)/$tgt terom@554: fi terom@551: done terom@551: terom@551: # bin dir terom@551: BIN=$(dirname $self) terom@525: terom@551: # data files terom@544: DATA=settings terom@530: ZONES=zones terom@544: SERIALS=$DATA terom@561: REPO= terom@525: terom@551: # data args terom@525: PROCESS_ARGS='--input-charset latin-1' terom@525: terom@525: FORWARD_MX=mail terom@525: REVERSE_ZONE=194.197.235 terom@525: REVERSE_DOMAIN=paivola.fi terom@525: terom@551: # external progs terom@533: NAMED_CHECKZONE=/usr/sbin/named-checkzone terom@554: HG=/usr/bin/hg terom@550: RNDC=/usr/sbin/rndc terom@533: terom@530: ## options terom@530: IS_TTY= terom@531: terom@554: LOG_WARN=y terom@531: LOG=y terom@531: LOG_INFO= terom@531: LOG_DEBUG= terom@531: LOG_CMD= terom@531: terom@530: UPDATE_FORCE= terom@531: UPDATE_NOOP= terom@531: UPDATE_DIFF= terom@530: SERIAL_NOUPDATE= terom@544: COMMIT_SKIP= terom@544: COMMIT_FORCE= terom@545: COMMIT_MSG=' ' terom@530: terom@550: DEPLOY_SKIP= terom@550: terom@530: function help_args { terom@530: local prog=$1 terom@530: terom@530: cat <&2 terom@525: } terom@525: terom@530: function log_color { terom@530: local code=$1; shift terom@530: terom@530: if [ $IS_TTY ]; then terom@530: echo $'\e[0;'${code}'m'"$*"$'\e[00m' >&2 terom@530: else terom@530: echo "$*" >&2 terom@530: fi terom@530: } terom@530: terom@530: function log_error { terom@530: log_color 31 "$*" terom@530: } terom@530: terom@554: function log_warn { terom@554: [ $LOG_WARN ] && log_color 33 "$*" || true terom@554: } terom@554: terom@530: function log { terom@531: [ $LOG ] && log_msg "$*" || true terom@530: } terom@530: terom@530: function log_info { terom@531: [ $LOG_INFO ] && log_color 36 " $*" || true terom@530: } terom@530: terom@530: function log_debug { terom@531: [ $LOG_DEBUG ] && log_color 32 " $*" || true terom@530: } terom@530: terom@530: function log_cmd { terom@531: [ $LOG_CMD ] && log_color 35 " \$ $*" || true terom@531: } terom@531: terom@531: # XXX: broken terom@531: function log_stack { terom@531: local level=1 terom@531: terom@531: while info=$(caller $level); do terom@531: echo $info | read line sub file terom@531: terom@531: log_msg "$file:$lineno $sub()" terom@531: terom@531: level=$(($level + 1)) terom@531: done terom@531: } terom@531: terom@531: function fail { terom@531: func=$(caller 1 | cut -d ' ' -f 2) terom@531: terom@531: log_error "$func: $*" terom@531: terom@531: exit 2 terom@530: } terom@530: terom@525: function die { terom@530: log_error "$*" terom@525: exit 1 terom@525: } terom@525: terom@530: function cmd { terom@530: log_cmd "$@" terom@530: terom@530: "$@" || die "Failed" terom@530: } terom@530: terom@530: function run_cmd { terom@525: local msg=$1; shift terom@525: terom@530: log_info "$msg... " terom@525: terom@530: cmd "$@" terom@525: } terom@525: terom@530: function indent () { terom@531: local indent=$1; shift terom@531: terom@541: log_cmd "$@" terom@541: terom@549: "$@" | sed "s/^/$indent/" terom@554: terom@554: return ${PIPESTATUS[0]} terom@541: } terom@541: terom@541: function abspath () { terom@541: echo "$ROOT/$1" terom@530: } terom@530: terom@551: ## hg terom@551: function hg { terom@562: local repo=$REPO; shift terom@551: terom@554: cmd $HG -R $ROOT/$repo "$@" terom@551: } terom@551: terom@551: function hg_modified { terom@562: hg id | grep -q '+' terom@551: } terom@551: terom@551: function hg_user { terom@551: if [ ${SUDO_USER:-} ]; then terom@551: echo '-u' "$SUDO_USER" terom@551: terom@551: elif [ $HOME ] && [ -e $HOME/.hgrc ]; then terom@551: log_debug "using .hgrc user" terom@551: echo '' terom@551: terom@551: else terom@551: echo '-u' "$USER" terom@551: fi terom@551: } terom@551: terom@551: function hg_diff { terom@562: hg diff terom@551: } terom@551: terom@551: function hg_commit { terom@551: local msg=$2 terom@551: local user_opt=$(hg_user) terom@551: terom@551: log_debug "commit: $user_opt: $msg" terom@562: hg commit $user_opt -m "$msg" terom@551: } terom@551: terom@525: terom@530: ## functions terom@531: function check_update { terom@531: # target terom@531: local dst=$1; shift terom@531: terom@532: log_debug "check_update: $dst" terom@531: terom@531: # need update? terom@531: local update= terom@531: terom@531: if [ ! -e $dst ] || [ $UPDATE_FORCE ]; then terom@531: log_debug " update forced" terom@531: update=y terom@531: fi terom@531: terom@531: # check deps terom@531: for dep in "$@"; do terom@531: # don't bother checking if already figured out terom@531: [ $update ] && continue terom@531: terom@531: # check terom@531: terom@551: if [ $ROOT/$dst -ot $ROOT/$dep ]; then terom@531: log_debug " changed: $dep" terom@531: update=y terom@531: fi terom@531: done terom@531: terom@531: [ ! $update ] && log_debug " up-to-date" terom@531: terom@531: # return terom@531: [ $update ] terom@531: } terom@531: terom@531: function do_update { terom@531: local dst=$1; shift terom@530: local tmp=$dst.new terom@530: terom@532: log_debug "update: $dst" terom@551: cmd "$@" > $ROOT/$tmp terom@530: terom@531: # compare terom@551: if [ -e $ROOT/$dst ] && [ $UPDATE_DIFF ]; then terom@531: log_debug " changes:" terom@531: terom@531: # terse terom@555: indent " " diff --unified=1 $ROOT/$dst $ROOT/$tmp || true terom@531: fi terom@531: terom@531: if [ $UPDATE_NOOP ]; then terom@531: # cleanup terom@532: log_debug " no-op" terom@531: terom@551: cmd rm $ROOT/$tmp terom@531: else terom@531: # commit terom@532: log_debug " update" terom@531: terom@551: cmd mv $ROOT/$tmp $ROOT/$dst terom@531: fi terom@531: } terom@531: terom@531: function update { terom@531: local dst=$1; shift; terom@531: terom@531: local sep= terom@531: local dep=() terom@531: local cmd=() terom@531: terom@531: for arg in "$@"; do terom@531: if [ $arg == '--' ]; then terom@531: sep=y terom@530: fi terom@530: terom@531: if [ $sep ]; then terom@531: cmd=("${cmd[@]:-}" "$arg") terom@531: else terom@531: dep=("${dep[@]:-}" "$arg") terom@531: fi terom@531: done terom@531: terom@531: [ ! $sep ] && fail "Invalid args given: $@" terom@531: terom@531: check_update $dst "${dep[@]}" && do_update $dst "${cmd[@]}" || true terom@530: } terom@530: terom@561: function check_link { terom@561: local lnk=$1 terom@561: local tgt=$2 terom@537: terom@561: [ ! -e $ROOT/$lnk ] || [ $(readlink $ROOT/$lnk) != $ROOT/$tgt ] terom@561: } terom@531: terom@561: function do_link { terom@561: local lnk=$1 terom@561: local tgt=$2 terom@531: terom@561: cmd ln -sf $ROOT/$tgt $ROOT/$lnk terom@561: } terom@561: terom@561: ## hosts terom@561: # copy hosts input zone verbatim terom@561: function copy_hosts { terom@561: local zone=$1 terom@561: local base=$2 terom@561: terom@561: log_debug "base: $base" terom@561: terom@561: if check_update $zone $base; then terom@561: log_info "Copying hosts $zone <- $base..." terom@561: terom@561: do_update $zone \ terom@561: cat $ROOT/$base terom@561: else terom@561: log_info "Copying hosts $zone <- $base: not changed" terom@561: fi terom@561: } terom@561: terom@561: # generate hosts zone from input zone terom@561: function update_hosts { terom@561: local zone=$1; shift terom@561: local base=$1; shift terom@561: terom@561: if check_update $zone $base; then terom@561: log_info "Generating hosts $zone <- $base..." terom@561: terom@561: do_update $zone \ terom@563: $BIN/process-zone $PROCESS_ARGS $ROOT/$base "$@" terom@561: else terom@561: log_info "Generating hosts $zone <- $base: not changed" terom@561: fi terom@561: } terom@561: terom@561: ## actions terom@561: # serial terom@561: function update_serial { terom@561: local zone=$1; shift terom@531: terom@561: local serial=$SERIALS/$zone.serial terom@561: terom@561: local old=$(test -e $ROOT/$serial && cat $ROOT/$serial || echo '') terom@561: terom@561: log_info "Updating $serial..." terom@561: terom@563: cmd $BIN/update-serial $* $ROOT/$serial terom@561: terom@561: local new=$(cat $ROOT/$serial) terom@531: terom@531: log_debug " $old -> $new" terom@530: } terom@530: terom@561: function link_serial { terom@561: local zone=$1 terom@559: local base=$2 terom@559: terom@561: local lnk=$SERIALS/$zone.serial terom@559: local tgt=$SERIALS/$base.serial terom@559: terom@561: if check_link $lnk $tgt; then terom@561: log_info "Linking $lnk -> $tgt..." terom@561: terom@561: do_link $lnk $tgt terom@559: terom@559: else terom@561: log_info "Linking $lnk -> $tgt: not changed" terom@559: fi terom@559: } terom@559: terom@561: # zone terom@561: function copy_zone { terom@561: local view=$1 terom@561: local zone=$2 terom@561: local base=${3:-$zone} terom@525: terom@561: local out=$ZONES/$view/$zone terom@561: local src=$DATA/$base terom@530: terom@561: if check_update $out $src; then terom@561: log_info "Copying $out <- $src..." terom@531: terom@561: do_update $out cat $ROOT/$src terom@531: else terom@561: log_info "Copying $out <- $src: not changed" terom@531: fi terom@525: } terom@525: terom@525: function update_zone { terom@561: local view=$1 terom@561: local zone=$2 terom@561: local base=${3:-$zone} terom@531: terom@561: local out=$ZONES/$view/$zone terom@561: local src=$DATA/$base.zone terom@561: local lnk=$ZONES/$base terom@561: local serial=$SERIALS/$base.serial terom@530: terom@561: log_debug "$out: from src $src" terom@561: terom@561: if [ ! -e $src ]; then terom@561: fail "Missing source: $src" terom@561: terom@561: elif check_update $out $src $serial; then terom@561: log_info "Generating $out <- $src..." terom@531: terom@531: do_update $out \ terom@563: $BIN/expand-zone $ROOT/$src \ terom@561: --serial $ROOT/$serial \ terom@541: --expand zones=$(abspath $ZONES) \ terom@531: --expand view=$view terom@531: else terom@561: log_info "Generating $out <- $src: not changed" terom@531: fi terom@531: } terom@531: terom@561: function link_zone { terom@561: local view=$1 terom@561: local zone=$2 terom@561: local base=${3:-$zone} terom@559: terom@561: local out=$ZONES/$view/$zone terom@561: terom@561: for tgt in $ZONES/$view/$base $ZONES/common/$base; do terom@561: [ $tgt != $out ] && [ -e $tgt ] && break terom@559: done terom@559: terom@561: log_debug "$out: from " terom@553: terom@561: if check_link $out $tgt; then terom@561: log_info "Linking $out -> $tgt..." terom@561: terom@561: do_link $out $tgt terom@553: else terom@561: log_info "Linking $out -> $tgt: not changed" terom@553: fi terom@553: } terom@553: terom@561: ## Tests terom@536: function check_hosts { terom@536: local hosts=$1; shift 1 terom@536: terom@563: local cmd=($BIN/process-zone $PROCESS_ARGS $ROOT/$hosts --check-hosts "$@") terom@536: terom@536: if "${cmd[@]}" -q; then terom@536: log_info "Check $hosts: OK" terom@536: else terom@536: log_error " Check $hosts: Failed" terom@536: terom@536: indent " " "${cmd[@]}" terom@536: terom@536: exit 1 terom@536: fi terom@536: } terom@536: terom@533: function check_zone { terom@561: local view=$1 terom@561: local zone=$2 terom@561: local origin=$3 terom@533: terom@561: local src=$ZONES/$view/$zone terom@561: terom@561: local cmd=($NAMED_CHECKZONE $origin $ROOT/$src) terom@536: terom@533: # test terom@536: # XXX: checkzone is very specific about the order of arguments, -q must be first terom@561: if $NAMED_CHECKZONE -q $origin $ROOT/$src; then terom@561: log_info "Check $src ($origin): OK" terom@533: else terom@561: log_error " Check $src ($origin): Failed:" terom@533: terom@536: indent " " "${cmd[@]}" terom@536: terom@536: exit 1 terom@533: fi terom@533: } terom@533: terom@561: ## Deploy terom@550: # deploy new zone data to bind terom@550: function deploy_zones { terom@554: indent " rndc: " $RNDC reload terom@550: } terom@550: terom@544: # commit data changes terom@551: function commit_data { terom@562: local repo=$REPO terom@544: terom@562: if hg_modified; then terom@551: log_info "Commit changes in $repo:" terom@544: terom@562: indent " " hg_diff terom@544: terom@562: hg_commit "$COMMIT_MSG" terom@544: else terom@551: log_info "Commit changes in $repo: no changes" terom@544: fi terom@544: } terom@544: terom@554: function ensure_dir { terom@554: local dir=$1 terom@554: terom@554: if [ ! -d $ROOT/$dir ]; then terom@554: log_warn "Creating output dir: $dir" terom@554: cmd mkdir $ROOT/$dir terom@554: fi terom@554: } terom@554: terom@525: function main { terom@530: # test tty terom@530: [ -t 1 ] && IS_TTY=y terom@554: terom@530: parse_args "$@" terom@530: terom@551: ## test env terom@551: [ -d $ROOT/$DATA ] || die "Missing data: $ROOT/$DATA" terom@554: ensure_dir $ZONES terom@554: terom@561: # output dirs terom@561: local views=(internal external) terom@561: terom@561: for view in "${views[@]}" "common" "hosts" "includes"; do terom@554: ensure_dir $ZONES/$view terom@554: done terom@551: terom@550: ## hosts terom@550: # test terom@536: log "Testing hosts..." terom@544: check_hosts $DATA/paivola.txt --check-exempt ufc terom@530: terom@550: # update terom@530: log "Generating host zones..." terom@561: # zone base *args terom@561: update_hosts $ZONES/hosts/paivola:internal $DATA/paivola.txt --forward-zone --forward-txt --forward-mx $FORWARD_MX terom@561: update_hosts $ZONES/hosts/paivola:external $DATA/paivola.txt --forward-zone terom@561: update_hosts $ZONES/hosts/194.197.235 $DATA/paivola.txt --reverse-zone $REVERSE_ZONE --reverse-domain $REVERSE_DOMAIN terom@559: terom@559: terom@561: update_hosts $ZONES/hosts/10 $DATA/pvl.txt --reverse-zone 10 --reverse-domain pvl -q terom@561: update_hosts $ZONES/hosts/192.168 $DATA/pvl.txt --reverse-zone 192.168 --reverse-domain pvl -q terom@559: terom@561: # XXX: unsupported --forward-zone with pvl.txt terom@561: # update_hosts $ZONES/hosts/pvl $DATA/pvl.txt --forward-zone terom@561: copy_hosts $ZONES/hosts/pvl $DATA/pvl.txt terom@525: terom@550: ## zones terom@550: # parts terom@561: log "Copying zone includes..." terom@561: # view zone base terom@561: copy_zone includes paivola:internal paivola.zone.internal terom@561: copy_zone includes paivola:external paivola.zone.external terom@561: copy_zone includes paivola.auto paivola.zone.auto terom@561: copy_zone includes paivola.services paivola.zone.services terom@525: terom@550: # serials terom@536: if [ $SERIAL_NOUPDATE ]; then terom@541: log "Updating serials: skipped" terom@541: terom@536: else terom@541: log "Updating serials..." terom@541: terom@561: # zone base terom@561: update_serial pvl terom@561: link_serial 10 pvl terom@561: link_serial 192.168 pvl terom@559: terom@561: update_serial paivola terom@561: update_serial 194.197.235 terom@536: fi terom@536: terom@561: # zones terom@561: log "Updating zones..." terom@561: # view zone base terom@561: update_zone internal pvl terom@561: update_zone internal paivola terom@561: update_zone external paivola terom@533: terom@561: update_zone internal 10 terom@561: update_zone internal 192.168 terom@559: terom@561: update_zone common 194.197.235 terom@561: link_zone internal 194.197.235 terom@561: link_zone external 194.197.235 terom@559: terom@550: # test terom@533: log "Testing zones..." terom@561: # view zone origin terom@561: check_zone internal paivola paivola.fi terom@561: check_zone external paivola paivola.fi terom@559: terom@561: check_zone internal 10 10.in-addr.arpa terom@561: check_zone internal 192.168 192.168.in-addr.arpa terom@561: check_zone common 194.197.235 235.197.194.in-addr.arpa terom@533: terom@553: # extra zones... terom@561: local base=paivola terom@553: local link_zones=(paivola.fi paivola.net paivola.org paivola.info paivola.mobi xn--pivl-load8j.fi) terom@553: terom@561: log "Linking zones..." terom@553: for view in "${views[@]}"; do terom@553: for zone in "${link_zones[@]}"; do terom@561: link_zone $view $zone $base terom@561: check_zone $view $zone $zone terom@553: done terom@553: done terom@553: terom@550: ## deploy terom@550: if [ $DEPLOY_SKIP ]; then terom@550: log "Deploy zones: skipped" terom@544: terom@550: else terom@550: log "Deploy zones..." terom@550: terom@550: deploy_zones terom@550: fi terom@550: terom@550: ## commit terom@544: if [ $COMMIT_SKIP ] && [ ! $COMMIT_FORCE ]; then terom@544: log "Commit data: skipped" terom@544: terom@544: else terom@544: log "Commit data..." terom@562: commit_data terom@544: fi terom@525: } terom@525: terom@530: main "$@"