|
|
|
@@ -0,0 +1,551 @@
|
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
#
|
|
|
|
|
# deploy.sh -- deploy an authoritative Knot DNS node on Alpine, native (no
|
|
|
|
|
# container). Single-purpose box, runs as root.
|
|
|
|
|
#
|
|
|
|
|
# What this does:
|
|
|
|
|
# 1. Installs knot + knot-utils and creates the knot user/directories.
|
|
|
|
|
# 2. Seeds /etc/knot/.env on first run; on a re-run updates only the values
|
|
|
|
|
# passed that run, and re-renders knot.conf from it either way.
|
|
|
|
|
# 3. Renders /etc/knot/knot.conf from the skeleton (@NODE_ID@, @LISTEN@).
|
|
|
|
|
# 4. TSIG: ROLE=primary generates secrets.conf and prints the keys once;
|
|
|
|
|
# ROLE=secondary REQUIRES them to be supplied, because they must byte-match
|
|
|
|
|
# the primary's or no transfer will ever authenticate.
|
|
|
|
|
# 5. Stubs the seven policy files knot.conf includes, so conf-check passes and
|
|
|
|
|
# knotd starts clean on a box the `dns` repo has never delivered to.
|
|
|
|
|
# 6. Installs knsctl, zone.tmpl, the login aliases and the MOTD.
|
|
|
|
|
# 7. Opens 53/tcp + 53/udp on the host firewall.
|
|
|
|
|
# 8. conf-check, then enables and starts knot.
|
|
|
|
|
#
|
|
|
|
|
# What this does NOT do: manage zones or DNS policy. Those come from the `dns`
|
|
|
|
|
# repo's pipeline -- see the README's split table. This script never overwrites
|
|
|
|
|
# a policy file the pipeline owns, nor an existing secrets.conf.
|
|
|
|
|
#
|
|
|
|
|
# Idempotent: re-run to change identity/listen/peers or pull a newer knot.
|
|
|
|
|
#
|
|
|
|
|
# Self-contained: knot.conf, secrets.conf.example, knsctl, zone.tmpl,
|
|
|
|
|
# knotdns-aliases.sh, knot-dns.motd and .env.example are embedded as a base64
|
|
|
|
|
# tar.gz at the bottom. Rebuild with build.sh after editing the loose files.
|
|
|
|
|
#
|
|
|
|
|
# Usage:
|
|
|
|
|
# ROLE=primary NODE_ID=ANYCAST-DNS-1 bash deploy.sh
|
|
|
|
|
# ROLE=secondary NODE_ID=ANYCAST-DNS-3 PRIMARY_ADDR=10.1.24.64 \
|
|
|
|
|
# TSIG_AUTHORITIVE=... TSIG_ADMIN=... SKIP_PROMPTS=1 bash deploy.sh
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
: "${CONF_DIR:=/etc/knot}"
|
|
|
|
|
: "${DATA_DIR:=/var/lib/knot}"
|
|
|
|
|
: "${SKIP_PROMPTS:=0}" # non-interactive: require values via env, no prompts
|
|
|
|
|
: "${FORCE:=0}"
|
|
|
|
|
[[ "$SKIP_PROMPTS" == "1" ]] && FORCE=1
|
|
|
|
|
|
|
|
|
|
ENV_FILE="$CONF_DIR/.env"
|
|
|
|
|
|
|
|
|
|
# Which keys arrived in this script's ENVIRONMENT has to be recorded BEFORE the
|
|
|
|
|
# ":=" defaults below, which make an unset variable look like an empty one --
|
|
|
|
|
# and a blank written over a live TSIG secret would break every transfer.
|
|
|
|
|
RUNTIME_KEYS=(NODE_ID ROLE LISTEN PRIMARY_ADDR DATACENTER PEERS)
|
|
|
|
|
KEYS_FROM_ENV=()
|
|
|
|
|
for _k in "${RUNTIME_KEYS[@]}"; do
|
|
|
|
|
if [[ -n "${!_k+x}" ]]; then KEYS_FROM_ENV+=("$_k"); fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
: "${NODE_ID:=}"
|
|
|
|
|
: "${ROLE:=}"
|
|
|
|
|
: "${LISTEN:=}"
|
|
|
|
|
: "${PRIMARY_ADDR:=}"
|
|
|
|
|
: "${DATACENTER:=}"
|
|
|
|
|
: "${PEERS:=}"
|
|
|
|
|
: "${TSIG_AUTHORITIVE:=}" # secondary: must match the primary's
|
|
|
|
|
: "${TSIG_ADMIN:=}"
|
|
|
|
|
|
|
|
|
|
LISTEN_DEFAULT='0.0.0.0@53, ::@53'
|
|
|
|
|
|
|
|
|
|
log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; }
|
|
|
|
|
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; }
|
|
|
|
|
die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
[[ $EUID -eq 0 ]] || die "Run as root."
|
|
|
|
|
|
|
|
|
|
# Alpine only, deliberately: Knot binds :53 directly and keeps its DNSSEC key
|
|
|
|
|
# store on the host filesystem, and this estate's nodes are Alpine. The RHEL
|
|
|
|
|
# packaging needs EPEL, which nothing in this repo sets up, so fail loudly
|
|
|
|
|
# rather than half-install somewhere untested.
|
|
|
|
|
_osid="$( [[ -r /etc/os-release ]] && . /etc/os-release 2>/dev/null && echo "${ID:-}" )"
|
|
|
|
|
[[ "$_osid" == alpine ]] || \
|
|
|
|
|
die "This deployment is Alpine-only (detected: ${_osid:-unknown}). Knot on other distros is not packaged or tested here."
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Extract embedded archive (falls back to the source dir before build.sh runs)
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
SCRIPT_DIR=$(mktemp -d -t knot-deploy.XXXXXX)
|
|
|
|
|
trap 'rm -rf "$SCRIPT_DIR"' EXIT
|
|
|
|
|
|
|
|
|
|
grep -a -A 9999999 '^__ARCHIVE_BELOW__$' "$0" \
|
|
|
|
|
| tail -n +2 \
|
|
|
|
|
| base64 -d \
|
|
|
|
|
| tar -xz -C "$SCRIPT_DIR" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
if [[ ! -f "$SCRIPT_DIR/knot.conf" ]]; then
|
|
|
|
|
SRC=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
|
|
|
for f in knot.conf secrets.conf.example knsctl zone.tmpl knotdns-aliases.sh knot-dns.motd .env.example; do
|
|
|
|
|
[[ -f "$SRC/$f" ]] || die "Missing $f (no embedded payload and not in $SRC -- run build.sh)."
|
|
|
|
|
cp "$SRC/$f" "$SCRIPT_DIR/$f"
|
|
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# .env: seed once, then update only what this run passed
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
set_env() { # <KEY> <value>: update KEY in .env, or append if absent
|
|
|
|
|
# The value goes through the ENVIRONMENT, never interpolated into a sed
|
|
|
|
|
# script: a TSIG secret or a listen list containing & or the s||| delimiter
|
|
|
|
|
# would otherwise be silently corrupted or abort the run.
|
|
|
|
|
local key="$1" val="$2" tmp
|
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
|
|
|
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
tmp="$(mktemp)"
|
|
|
|
|
_SE_KEY="$key" _SE_VAL="$val" awk '
|
|
|
|
|
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
|
|
|
|
|
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
|
|
|
|
|
{ print }
|
|
|
|
|
END { if (!seen) print k "=" v }
|
|
|
|
|
' "$ENV_FILE" > "$tmp"
|
|
|
|
|
cat "$tmp" > "$ENV_FILE" # rewrite in place: keeps the original mode/owner
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
install -d -m 0755 "$CONF_DIR"
|
|
|
|
|
|
|
|
|
|
# On a re-run the deployed values live in .env. Load them into any key NOT
|
|
|
|
|
# passed this run, so the prompts below do not demand a full retype and
|
|
|
|
|
# SKIP_PROMPTS=1 does not die over values the node already has.
|
|
|
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
|
|
|
_preloaded=()
|
|
|
|
|
for _k in "${RUNTIME_KEYS[@]}"; do
|
|
|
|
|
if [[ " ${KEYS_FROM_ENV[*]-} " == *" ${_k} "* ]]; then continue; fi
|
|
|
|
|
_v=$(sed -n "s/^${_k}=//p" "$ENV_FILE" | tail -n1)
|
|
|
|
|
if [[ -z "$_v" ]]; then continue; fi
|
|
|
|
|
printf -v "$_k" '%s' "$_v"
|
|
|
|
|
_preloaded+=("$_k")
|
|
|
|
|
done
|
|
|
|
|
if (( ${#_preloaded[@]} > 0 )); then
|
|
|
|
|
log "Reusing ${ENV_FILE} values for: ${_preloaded[*]}"
|
|
|
|
|
log " (pass VAR=... to change one; it is then written back to .env)"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
prompt() {
|
|
|
|
|
local varname="$1" message="$2"
|
|
|
|
|
local -n ref="$varname"
|
|
|
|
|
if [[ -z "${ref:-}" ]]; then
|
|
|
|
|
[[ "$SKIP_PROMPTS" == "1" ]] && die "$varname required (set it in the environment; running with SKIP_PROMPTS=1)."
|
|
|
|
|
read -r -p "$message: " ref
|
|
|
|
|
[[ -n "$ref" ]] || die "$varname required."
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prompt NODE_ID "Node ID for server.identity / NSID (e.g. ANYCAST-DNS-3)"
|
|
|
|
|
prompt ROLE "Role: primary or secondary"
|
|
|
|
|
case "$ROLE" in
|
|
|
|
|
primary|secondary) ;;
|
|
|
|
|
*) die "ROLE must be 'primary' or 'secondary' (got '$ROLE')." ;;
|
|
|
|
|
esac
|
|
|
|
|
[[ -n "$LISTEN" ]] || LISTEN="$LISTEN_DEFAULT"
|
|
|
|
|
|
|
|
|
|
if [[ "$ROLE" == secondary ]]; then
|
|
|
|
|
prompt PRIMARY_ADDR "Address of the primary this node transfers from"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Packages, user, directories
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
log "Installing Knot DNS..."
|
|
|
|
|
apk add -q knot knot-utils
|
|
|
|
|
|
|
|
|
|
# The package normally creates knot:knot; make that certain before any chown.
|
|
|
|
|
getent group knot >/dev/null 2>&1 || addgroup -S knot
|
|
|
|
|
getent passwd knot >/dev/null 2>&1 || adduser -S -D -H -G knot -s /sbin/nologin knot
|
|
|
|
|
|
|
|
|
|
# storage/journal/kasp/timer/catalog as declared in knot.conf's database:
|
|
|
|
|
# section, plus the zone root knsctl writes into.
|
|
|
|
|
for d in database journal keys timer catalog zones; do
|
|
|
|
|
install -d -m 0750 -o knot -g knot "$DATA_DIR/$d"
|
|
|
|
|
done
|
|
|
|
|
install -d -m 0750 -o knot -g knot /run/knot
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# knot.conf -- re-rendered every run, since it is derived purely from .env
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
log "Rendering $CONF_DIR/knot.conf (NODE_ID=$NODE_ID, ROLE=$ROLE)..."
|
|
|
|
|
_KC_NODE_ID="$NODE_ID" _KC_LISTEN="$LISTEN" awk '
|
|
|
|
|
{ gsub(/@NODE_ID@/, ENVIRON["_KC_NODE_ID"]); gsub(/@LISTEN@/, ENVIRON["_KC_LISTEN"]); print }
|
|
|
|
|
' "$SCRIPT_DIR/knot.conf" > "$CONF_DIR/knot.conf.new"
|
|
|
|
|
install -m 0640 -o root -g knot "$CONF_DIR/knot.conf.new" "$CONF_DIR/knot.conf"
|
|
|
|
|
rm -f "$CONF_DIR/knot.conf.new"
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# TSIG. A secondary's keys must byte-match the primary's, so they can never be
|
|
|
|
|
# generated here -- a generated key would authenticate nothing.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
gen_tsig() { # gen_tsig <key-id> -> base64 secret on stdout
|
|
|
|
|
local id="$1" out=""
|
|
|
|
|
if command -v keymgr >/dev/null 2>&1; then
|
|
|
|
|
# keymgr prints a whole key: block; take just the secret.
|
|
|
|
|
out="$(keymgr -t "$id" hmac-sha256 2>/dev/null \
|
|
|
|
|
| awk '/secret:/ { sub(/^[[:space:]]*secret:[[:space:]]*/, ""); print; exit }' || true)"
|
|
|
|
|
fi
|
|
|
|
|
[[ -n "$out" ]] || out="$(openssl rand -base64 32)"
|
|
|
|
|
printf '%s' "$out"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECRETS="$CONF_DIR/secrets.conf"
|
|
|
|
|
TSIG_PRINTED=0
|
|
|
|
|
if [[ -f "$SECRETS" ]]; then
|
|
|
|
|
log "$SECRETS exists; leaving it alone (TSIG keys are never rotated by a re-run)."
|
|
|
|
|
elif [[ "$ROLE" == primary ]]; then
|
|
|
|
|
log "Generating TSIG keys for $SECRETS..."
|
|
|
|
|
[[ -n "$TSIG_AUTHORITIVE" ]] || TSIG_AUTHORITIVE="$(gen_tsig authortive-tsig)"
|
|
|
|
|
[[ -n "$TSIG_ADMIN" ]] || TSIG_ADMIN="$(gen_tsig admin-tsig)"
|
|
|
|
|
TSIG_PRINTED=1
|
|
|
|
|
else
|
|
|
|
|
[[ -n "$TSIG_AUTHORITIVE" && -n "$TSIG_ADMIN" ]] || die \
|
|
|
|
|
"A secondary's TSIG keys must match the primary's byte for byte, so they cannot be generated here.
|
|
|
|
|
Copy them off the primary ($CONF_DIR/secrets.conf) and re-run with:
|
|
|
|
|
TSIG_AUTHORITIVE=<secret> TSIG_ADMIN=<secret> bash deploy.sh"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ ! -f "$SECRETS" ]]; then
|
|
|
|
|
# Increment only on a MATCHING line: as a gsub argument, `++n` would be
|
|
|
|
|
# evaluated on every line of the file, so both keys would get the second
|
|
|
|
|
# secret -- making the read-only admin key identical to the replication key.
|
|
|
|
|
_TS_AUTH="$TSIG_AUTHORITIVE" _TS_ADMIN="$TSIG_ADMIN" awk '
|
|
|
|
|
/REPLACE_ME/ { n++; sub(/REPLACE_ME/, n == 1 ? ENVIRON["_TS_AUTH"] : ENVIRON["_TS_ADMIN"]) }
|
|
|
|
|
{ print }
|
|
|
|
|
' "$SCRIPT_DIR/secrets.conf.example" > "$SECRETS.new"
|
|
|
|
|
install -m 0640 -o root -g knot "$SECRETS.new" "$SECRETS"
|
|
|
|
|
rm -f "$SECRETS.new"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# The seven policy files knot.conf includes. The `dns` repo owns them; a fresh
|
|
|
|
|
# node has none, and Knot treats a missing include as a config error -- so
|
|
|
|
|
# conf-check would fail and knotd would never start. Stub only what is absent;
|
|
|
|
|
# never touch a file the pipeline has already delivered.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
_stubbed=()
|
|
|
|
|
for f in remotes policy modules templates arpa dnssec public; do
|
|
|
|
|
[[ -f "$CONF_DIR/$f.conf" ]] && continue
|
|
|
|
|
cat > "$CONF_DIR/$f.conf" <<EOF
|
|
|
|
|
# $f.conf -- PLACEHOLDER written by deploy.sh.
|
|
|
|
|
#
|
|
|
|
|
# This file is owned by the \`dns\` repo and is overwritten by its pipeline.
|
|
|
|
|
# It exists only so knot.conf's include: chain resolves and knotd can start on
|
|
|
|
|
# a node the pipeline has not delivered to yet. Do not edit it here.
|
|
|
|
|
EOF
|
|
|
|
|
chown root:knot "$CONF_DIR/$f.conf"; chmod 0640 "$CONF_DIR/$f.conf"
|
|
|
|
|
_stubbed+=("$f.conf")
|
|
|
|
|
done
|
|
|
|
|
(( ${#_stubbed[@]} == 0 )) || log "Stubbed policy files (the dns pipeline overwrites these): ${_stubbed[*]}"
|
|
|
|
|
|
|
|
|
|
# A secondary otherwise cannot transfer anything until the pipeline first runs.
|
|
|
|
|
# Seed remotes.conf from PRIMARY_ADDR so the node can bootstrap by AXFR -- but
|
|
|
|
|
# only when we just stubbed it, so a delivered remotes.conf is never clobbered.
|
|
|
|
|
if [[ "$ROLE" == secondary && -n "$PRIMARY_ADDR" ]] \
|
|
|
|
|
&& printf '%s\n' "${_stubbed[@]-}" | grep -qx 'remotes.conf'; then
|
|
|
|
|
log "Seeding $CONF_DIR/remotes.conf with the primary ($PRIMARY_ADDR)..."
|
|
|
|
|
cat > "$CONF_DIR/remotes.conf" <<EOF
|
|
|
|
|
# remotes.conf -- BOOTSTRAP seed written by deploy.sh from PRIMARY_ADDR.
|
|
|
|
|
#
|
|
|
|
|
# Enough for this node to AXFR from the primary before the \`dns\` repo's
|
|
|
|
|
# pipeline has ever delivered here. The pipeline owns this file and will
|
|
|
|
|
# overwrite it; do not add remotes by hand.
|
|
|
|
|
|
|
|
|
|
remote:
|
|
|
|
|
- id: primary
|
|
|
|
|
address: ${PRIMARY_ADDR}
|
|
|
|
|
key: authortive-tsig
|
|
|
|
|
|
|
|
|
|
acl:
|
|
|
|
|
- id: acl_primary
|
|
|
|
|
address: ${PRIMARY_ADDR}
|
|
|
|
|
key: authortive-tsig
|
|
|
|
|
action: [ transfer, notify ]
|
|
|
|
|
EOF
|
|
|
|
|
chown root:knot "$CONF_DIR/remotes.conf"; chmod 0640 "$CONF_DIR/remotes.conf"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Operator tooling
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
install -m 0755 "$SCRIPT_DIR/knsctl" /usr/local/sbin/knsctl
|
|
|
|
|
install -m 0644 -o root -g knot "$SCRIPT_DIR/zone.tmpl" "$CONF_DIR/zone.tmpl"
|
|
|
|
|
install -m 0644 "$SCRIPT_DIR/knotdns-aliases.sh" /etc/profile.d/knotdns-aliases.sh
|
|
|
|
|
|
|
|
|
|
# PEERS drives `knsctl serials` / the knsers alias.
|
|
|
|
|
if [[ -n "$PEERS" ]]; then
|
|
|
|
|
printf 'export PEERS="%s"\n' "$PEERS" > /etc/profile.d/knotdns-peers.sh
|
|
|
|
|
chmod 0644 /etc/profile.d/knotdns-peers.sh
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
_FQDN="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "$NODE_ID")"
|
|
|
|
|
# The MOTD is a pre-drawn box, so a value wider or narrower than its @TOKEN@
|
|
|
|
|
# would shift the right-hand border on every login. Replace "@TOKEN@ + the run
|
|
|
|
|
# of spaces after it" with the value padded to that same span, which keeps the
|
|
|
|
|
# border put. That span is pure ASCII, so byte length == display width and this
|
|
|
|
|
# stays correct under busybox awk in the C locale (the box-drawing characters
|
|
|
|
|
# themselves are never measured). A value longer than its span is left to
|
|
|
|
|
# overflow rather than truncated: a clipped hostname in an ops banner is worse
|
|
|
|
|
# than a ragged edge.
|
|
|
|
|
_MD_NODE_ID="$NODE_ID" _MD_ROLE="$ROLE" _MD_DC="${DATACENTER:-unset}" _MD_FQDN="$_FQDN" awk '
|
|
|
|
|
function fit(line, tok, val, n, span, pad) {
|
|
|
|
|
while (match(line, tok " *")) {
|
|
|
|
|
span = RLENGTH
|
|
|
|
|
pad = val
|
|
|
|
|
while (length(pad) < span) pad = pad " "
|
|
|
|
|
line = substr(line, 1, RSTART - 1) pad substr(line, RSTART + RLENGTH)
|
|
|
|
|
}
|
|
|
|
|
return line
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
$0 = fit($0, "@NODE_ID@", ENVIRON["_MD_NODE_ID"])
|
|
|
|
|
$0 = fit($0, "@ROLE@", ENVIRON["_MD_ROLE"])
|
|
|
|
|
$0 = fit($0, "@DATACENTER@", ENVIRON["_MD_DC"])
|
|
|
|
|
$0 = fit($0, "@FQDN@", ENVIRON["_MD_FQDN"])
|
|
|
|
|
print
|
|
|
|
|
}
|
|
|
|
|
' "$SCRIPT_DIR/knot-dns.motd" > /etc/motd
|
|
|
|
|
chmod 0644 /etc/motd
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# .env: seed on first run, else update only what was passed this run
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
|
|
|
log "Seeding $ENV_FILE..."
|
|
|
|
|
install -m 0640 -o root -g knot "$SCRIPT_DIR/.env.example" "$ENV_FILE"
|
|
|
|
|
for _k in "${RUNTIME_KEYS[@]}"; do set_env "$_k" "${!_k}"; done
|
|
|
|
|
else
|
|
|
|
|
# knot.conf is re-rendered from .env above, so letting .env drift from what
|
|
|
|
|
# was passed would silently redeploy the old identity on the next run.
|
|
|
|
|
log ".env exists; keeping it (only values passed this run are updated)."
|
|
|
|
|
for _k in ${KEYS_FROM_ENV[@]+"${KEYS_FROM_ENV[@]}"}; do
|
|
|
|
|
_cur=$(sed -n "s/^${_k}=//p" "$ENV_FILE" | tail -n1)
|
|
|
|
|
if [[ "$_cur" == "${!_k}" ]]; then continue; fi
|
|
|
|
|
set_env "$_k" "${!_k}"
|
|
|
|
|
log " ${_k}: ${_cur:-<unset>} -> ${!_k:-<empty>}"
|
|
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Firewall: authoritative DNS answers on 53, TCP and UDP both.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
if [[ -d /etc/firewall/ports.d && -x /usr/local/sbin/firewall-apply ]]; then
|
|
|
|
|
log "Registering 53/tcp + 53/udp with the host firewall..."
|
|
|
|
|
printf '53/tcp\n53/udp\n' > /etc/firewall/ports.d/knot-dns.rule
|
|
|
|
|
/usr/local/sbin/firewall-apply
|
|
|
|
|
else
|
|
|
|
|
warn "No host firewall found (scripts/harden-firewall.sh not installed); 53/tcp+udp not registered."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Validate, then start. -b so a rejected config fails here, not silently.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
log "Validating configuration..."
|
|
|
|
|
knotc -b conf-check || die "knotc conf-check failed -- not starting. Fix $CONF_DIR/knot.conf and re-run."
|
|
|
|
|
|
|
|
|
|
rc-update add knot default >/dev/null 2>&1 || true
|
|
|
|
|
if rc-service knot status >/dev/null 2>&1; then
|
|
|
|
|
log "Reloading knot..."
|
|
|
|
|
knotc -b reload || warn "Reload failed; check 'rc-service knot status' and the syslog."
|
|
|
|
|
else
|
|
|
|
|
log "Starting knot..."
|
|
|
|
|
rc-service knot start || die "knot failed to start -- check the syslog (log target is syslog, zone at info)."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
cat <<EOF
|
|
|
|
|
|
|
|
|
|
================================================================
|
|
|
|
|
DEPLOYED ${NODE_ID} (${ROLE})
|
|
|
|
|
|
|
|
|
|
Config: ${CONF_DIR}/knot.conf (re-rendered from ${ENV_FILE} every run)
|
|
|
|
|
Secrets: ${SECRETS} (0640 root:knot, never in git)
|
|
|
|
|
Data: ${DATA_DIR}
|
|
|
|
|
Admin: knsctl -- 'knsctl list', 'knsctl status'; aliases in a login shell
|
|
|
|
|
|
|
|
|
|
This node serves NO ZONES yet. Zone data and DNS policy come from the \`dns\`
|
|
|
|
|
repo's pipeline; the policy files here are placeholders until it delivers.
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
if [[ "$ROLE" == secondary ]]; then
|
|
|
|
|
cat <<EOF
|
|
|
|
|
|
|
|
|
|
Secondary bootstrap: remotes.conf points at ${PRIMARY_ADDR:-<unset>} with
|
|
|
|
|
authortive-tsig. Add this node's address to the PRIMARY's remotes.conf (or let
|
|
|
|
|
the catalog zone do it) before it can transfer.
|
|
|
|
|
EOF
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ "$TSIG_PRINTED" == "1" ]]; then
|
|
|
|
|
cat <<EOF
|
|
|
|
|
|
|
|
|
|
>> TSIG KEYS -- SHOWN ONCE. Every secondary needs these byte for byte:
|
|
|
|
|
TSIG_AUTHORITIVE=${TSIG_AUTHORITIVE}
|
|
|
|
|
TSIG_ADMIN=${TSIG_ADMIN}
|
|
|
|
|
Store them out of band. They are in ${SECRETS} on this node.
|
|
|
|
|
EOF
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
cat <<EOF
|
|
|
|
|
|
|
|
|
|
>> BACK UP THE KASP DATABASE (${DATA_DIR}/keys). It replicates nowhere, and
|
|
|
|
|
losing it makes every signed domain go bogus until each registrar is updated
|
|
|
|
|
by hand:
|
|
|
|
|
knotc -b zone-backup +backupdir /var/backups/knot/\$(date -u +%FT%H) +journal
|
|
|
|
|
EOF
|
|
|
|
|
__ARCHIVE_BELOW__
|
|
|
|
|
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
|
|
|
|
|
ohWtaP8r7Xext/XSAHgAAA==
|