7faa9098de
Restructure around a single entry point (automations.sh) with a Gum wizard and a self-extracting bundle for repo-less installs. Add scripts/oslib.sh so the provisioning scripts (setup-host, harden-ssh, harden-jumphost, sshuser) run on Alpine/Debian/Alma; seed root keys from globals/. - ntfy SSH-login alerts (user, source IP, key, region, jump target) via pam_exec - daily auto-updates with AUTO_REBOOT=idle (reboots only when no SSH active) and opt-in Alpine stable-branch upgrades - generic + per-deployment cloud-init; Gitea release workflow on tag - README/LICENSE/.gitignore/.gitattributes (force LF); repo URLs -> Gitea
167 lines
6.3 KiB
Bash
167 lines
6.3 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# setup-host.sh -- set a host's name (per the Network Domain Name Schema) and
|
|
# install the shared SSH MOTD banner. Works on Alpine, Debian, and Alma via
|
|
# oslib.sh.
|
|
#
|
|
# Hostnames follow globals/Network Domain Name Schema.md. Our VMs skip the
|
|
# region code and use srvno.de as the base, so the form is:
|
|
# <service>-<n>.srvno.de e.g. sto-1.srvno.de
|
|
# from which we derive:
|
|
# Node ID = <SERVICE>-<N> e.g. STO-1 (short name, uppercased)
|
|
#
|
|
# The MOTD is rendered from globals/motd.txt. You edit the *content* there;
|
|
# this script draws the borders and computes every bit of padding, so the box
|
|
# stays aligned regardless of value length (the spacing problem, solved).
|
|
#
|
|
# Usage:
|
|
# bash setup-host.sh sto-1 # -> sto-1.srvno.de, Node ID STO-1
|
|
# HOST=dns-1 bash setup-host.sh
|
|
# HOST=web-2.srvno.de bash setup-host.sh # full FQDN accepted too
|
|
# BASE_DOMAIN=example.net HOST=app-1 bash setup-host.sh
|
|
# DATACENTER="Stockholm SE" HOST=sto-1 bash setup-host.sh
|
|
#
|
|
# Env:
|
|
# HOST required short name (sto-1) or FQDN (sto-1.srvno.de)
|
|
# BASE_DOMAIN srvno.de appended when HOST has no dot
|
|
# DATACENTER "Globally Everywhere" shown in the MOTD
|
|
# MOTD_TEMPLATE <repo>/globals/motd.txt
|
|
# MOTD_WIDTH 60 inner width of the MOTD box
|
|
# SET_HOSTNAME 1 set to 0 to render MOTD only (skip hostname)
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
# shellcheck source=scripts/oslib.sh
|
|
. "$REPO_ROOT/scripts/oslib.sh"
|
|
|
|
: "${HOST:=${1:-}}"
|
|
: "${BASE_DOMAIN:=srvno.de}"
|
|
: "${DATACENTER:=Globally Everywhere}"
|
|
: "${MOTD_TEMPLATE:=$REPO_ROOT/globals/motd.txt}"
|
|
: "${MOTD_WIDTH:=60}"
|
|
: "${MOTD_OUT:=/etc/motd}"
|
|
: "${SET_HOSTNAME:=1}"
|
|
|
|
[[ $EUID -eq 0 ]] || _die "Run as root."
|
|
[[ -n "$HOST" ]] || _die "Set HOST=<service>-<n> (e.g. sto-1) or a full FQDN."
|
|
os_detect
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Derive FQDN / short name / Node ID from HOST
|
|
# ----------------------------------------------------------------------------
|
|
if [[ "$HOST" == *.* ]]; then
|
|
FQDN="$HOST"; SHORT="${HOST%%.*}"
|
|
else
|
|
SHORT="$HOST"; FQDN="$HOST.$BASE_DOMAIN"
|
|
fi
|
|
NODE_ID="$(printf '%s' "$SHORT" | tr '[:lower:]' '[:upper:]')"
|
|
|
|
# Light sanity check against the schema (svc code + instance number). Warn
|
|
# only -- we don't want to block an intentional exception.
|
|
[[ "$SHORT" =~ ^[a-z]{2,4}-[0-9]+$ ]] \
|
|
|| _warn "Short name '$SHORT' doesn't match <svc>-<n> (see globals/Network Domain Name Schema.md)."
|
|
|
|
_log "Host: $FQDN"
|
|
_log "Node ID: $NODE_ID"
|
|
_log "Data Cntr: $DATACENTER"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Set hostname (OS-gated inside oslib)
|
|
# ----------------------------------------------------------------------------
|
|
if [[ "$SET_HOSTNAME" == "1" ]]; then
|
|
_log "Setting hostname to $FQDN ..."
|
|
set_hostname "$FQDN"
|
|
fi
|
|
|
|
# ============================================================================
|
|
# MOTD renderer
|
|
# ============================================================================
|
|
# All padding is computed in ASCII (spaces, labels, values are ASCII so
|
|
# ${#str} is the column count). Box-drawing glyphs are only ever emitted by
|
|
# repetition, never measured -- so this is correct under any shell/locale.
|
|
|
|
W="$MOTD_WIDTH"
|
|
|
|
_spaces() { printf '%*s' "$1" ''; } # N ASCII spaces
|
|
_repeat() { local i; for ((i=0;i<$2;i++)); do printf '%s' "$1"; done; } # glyph xN
|
|
|
|
# Bordered content line: ┃ + <inner W cols> + ┃
|
|
_line() { printf '┃%s┃\n' "$1"; }
|
|
|
|
# Center an ASCII string within W.
|
|
_center() {
|
|
local s="$1" len=${#1} pad left
|
|
(( len > W )) && { s="${s:0:W}"; len=$W; }
|
|
pad=$(( W - len )); left=$(( pad / 2 ))
|
|
_line "$(_spaces "$left")$s$(_spaces $(( pad - left )))"
|
|
}
|
|
|
|
# Center a pre-built string of KNOWN visible width vis (may contain glyphs).
|
|
_center_known() {
|
|
local s="$1" vis="$2" pad left
|
|
pad=$(( W - vis )); left=$(( pad / 2 ))
|
|
_line "$(_spaces "$left")$s$(_spaces $(( pad - left )))"
|
|
}
|
|
|
|
# Field line: left margin, right-aligned label, ": ", value, pad to W.
|
|
LM=8 # left margin before the label column
|
|
_field() {
|
|
local label="$1" value="$2" content
|
|
content="$(printf '%*s%*s: %s' "$LM" '' "$LABELW" "$label" "$value")"
|
|
local len=${#content}
|
|
(( len > W )) && { content="${content:0:W}"; len=$W; }
|
|
_line "$content$(_spaces $(( W - len )))"
|
|
}
|
|
|
|
# Mini double-line box around centered ASCII text, itself centered in W.
|
|
_inner_box() {
|
|
local text="$1" tw=$(( ${#1} + 2 )) vis
|
|
vis=$(( tw + 2 )) # ║ + text(+2 spaces) + ║
|
|
_center_known "╔$(_repeat '═' "$tw")╗" "$vis"
|
|
_center_known "║ $text ║" "$vis"
|
|
_center_known "╚$(_repeat '═' "$tw")╝" "$vis"
|
|
}
|
|
|
|
render_motd() {
|
|
# First pass: compute LABELW (max @F@ label width) so colons align.
|
|
LABELW=0
|
|
local line tok rest label
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
[[ "$line" == "@F@ "* ]] || continue
|
|
rest="${line#@F@ }"; label="${rest%%|*}"
|
|
(( ${#label} > LABELW )) && LABELW=${#label}
|
|
done < "$MOTD_TEMPLATE"
|
|
|
|
# Second pass: emit. Borders come from the @TOP@/@HR@/@BOT@ tokens.
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
[[ -z "$line" ]] && continue
|
|
tok="${line%% *}"; rest="${line#"$tok"}"; rest="${rest# }"
|
|
# substitute placeholders
|
|
rest="${rest//\{\{HOSTNAME\}\}/$FQDN}"
|
|
rest="${rest//\{\{NODE_ID\}\}/$NODE_ID}"
|
|
rest="${rest//\{\{DATACENTER\}\}/$DATACENTER}"
|
|
case "$tok" in
|
|
@TOP@) printf '┏%s┓\n' "$(_repeat '━' "$W")" ;;
|
|
@BOT@) printf '┗%s┛\n' "$(_repeat '━' "$W")" ;;
|
|
@HR@) printf '┣%s┫\n' "$(_repeat '━' "$W")" ;;
|
|
@BLANK@) _line "$(_spaces "$W")" ;;
|
|
@C@) _center "$rest" ;;
|
|
@BOX@) _inner_box "$rest" ;;
|
|
@F@) _field "${rest%%|*}" "${rest#*|}" ;;
|
|
*) _warn "Unknown MOTD token: $tok (skipped)" ;;
|
|
esac
|
|
done < "$MOTD_TEMPLATE"
|
|
}
|
|
|
|
[[ -f "$MOTD_TEMPLATE" ]] || _die "MOTD template not found: $MOTD_TEMPLATE"
|
|
_log "Rendering MOTD -> $MOTD_OUT"
|
|
render_motd > "$MOTD_OUT"
|
|
chmod 644 "$MOTD_OUT" 2>/dev/null || true
|
|
|
|
_log "Done."
|
|
echo
|
|
cat "$MOTD_OUT"
|