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