4 138e225e 2025-06-04 xs # Copyright 2025 xs <xs@inda.re>
6 138e225e 2025-06-04 xs # Permission to use, copy, modify, and/or distribute this software for any
7 138e225e 2025-06-04 xs # purpose with or without fee is hereby granted, provided that the above
8 138e225e 2025-06-04 xs # copyright notice and this permission notice appear in all copies.
10 138e225e 2025-06-04 xs # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11 138e225e 2025-06-04 xs # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12 138e225e 2025-06-04 xs # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13 138e225e 2025-06-04 xs # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14 138e225e 2025-06-04 xs # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15 138e225e 2025-06-04 xs # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 138e225e 2025-06-04 xs # PERFORMANCE OF THIS SOFTWARE.
18 46924c01 2025-05-28 xs # shellcheck disable=SC2046,SC2031
20 03d8f521 2025-05-27 xs # Global configuration
21 03d8f521 2025-05-27 xs # ====================
24 03d8f521 2025-05-27 xs : "${HOSTNAME:=$(uname -n)}"
26 46924c01 2025-05-28 xs # shellcheck disable=SC2031
28 46924c01 2025-05-28 xs _debug _INIT_ENV
30 46924c01 2025-05-28 xs # Here is the default configuration
31 46924c01 2025-05-28 xs : "${R7_DEBUG:=no}"
32 46924c01 2025-05-28 xs : "${R7_DRYRUN:=no}"
33 31e019a2 2025-05-31 xs : "${R7_GROUP_ALLPREFIX:=no}"
34 31e019a2 2025-05-31 xs : "${R7_GROUP_SUMMARY:=no}"
35 730e151b 2025-06-02 xs : "${R7_GROUP_EXITONERROR:=no}"
36 46924c01 2025-05-28 xs : "${R7_PARALLEL:=no}"
37 46924c01 2025-05-28 xs : "${R7_PREFIX:=install state doc}"
38 31e019a2 2025-05-31 xs : "${R7_SHOWOUTPUT:=no}"
39 31e019a2 2025-05-31 xs : "${R7_WORKDIR:=$PWD}"
40 46924c01 2025-05-28 xs : "${R7_SITE_CONFIG:=$R7_WORKDIR/site-config}"
41 31e019a2 2025-05-31 xs : "${R7_OUTPUTDIR:=$R7_WORKDIR/_output}"
42 31e019a2 2025-05-31 xs : "${SSH_CONFIG_DIR:=$R7_WORKDIR/.ssh}"
43 46924c01 2025-05-28 xs : "${SSH_CONFIG_FILE:=$R7_WORKDIR/.ssh/config}"
44 31e019a2 2025-05-31 xs : "${SSH_CONTROL_DIR:=$SSH_CONFIG_DIR/control}"
45 46924c01 2025-05-28 xs : "${SSH_IDENTITY_FILE:=$R7_WORKDIR/.ssh/id_ed25519_r7}"
46 f83445b1 2025-05-30 xs : "${SSH_KNOWN_HOSTS:=$R7_WORKDIR/.ssh/known_hosts}"
47 31e019a2 2025-05-31 xs : "${SSH_CONNECT_TIMEOUT:=3}"
49 46924c01 2025-05-28 xs _debug ENVIRONMENT
51 46924c01 2025-05-28 xs # Validate environment
52 46924c01 2025-05-28 xs if test "$R7_DEBUG" = yes; then
53 46924c01 2025-05-28 xs set | grep -e '^R7_' -e '^SSH' 1>&2
56 46924c01 2025-05-28 xs for param in "$R7_DEBUG" "$R7_DRYRUN" "$R7_PARALLEL"; do
57 46924c01 2025-05-28 xs case $param in
58 46924c01 2025-05-28 xs yes | no) : ;;
59 46924c01 2025-05-28 xs *) _fail "Invalid parameter value: '$param'" ;;
63 46924c01 2025-05-28 xs R7_WORKDIR_tmp=$(_resolv_dir "$R7_WORKDIR")
64 730e151b 2025-06-02 xs test -d "$R7_WORKDIR_tmp" || _error "Working directory not found: '$R7_WORKDIR'"
65 46924c01 2025-05-28 xs R7_WORKDIR=$R7_WORKDIR_tmp
66 46924c01 2025-05-28 xs R7_RUNDIR=$R7_WORKDIR/_run
67 46924c01 2025-05-28 xs R7_OUTPUTDIR=$R7_WORKDIR/_output
69 46924c01 2025-05-28 xs R7_SITE_CONFIG_tmp=$(_resolv_file "$R7_SITE_CONFIG")
70 730e151b 2025-06-02 xs test -f "$R7_SITE_CONFIG_tmp" || _error "Site configuration not found: '$R7_SITE_CONFIG'"
71 46924c01 2025-05-28 xs R7_SITE_CONFIG=$R7_SITE_CONFIG_tmp
73 46924c01 2025-05-28 xs SSH_CONFIG_FILE_tmp=$(_resolv_file "$SSH_CONFIG_FILE")
74 730e151b 2025-06-02 xs test -f "$SSH_CONFIG_FILE_tmp" || _error "Unable to read SSH configuration: '$SSH_CONFIG_FILE'"
75 46924c01 2025-05-28 xs SSH_CONFIG_FILE=$SSH_CONFIG_FILE_tmp
77 46924c01 2025-05-28 xs SSH_IDENTITY_FILE_tmp=$(_resolv_file "$SSH_IDENTITY_FILE")
78 730e151b 2025-06-02 xs test -f "$SSH_IDENTITY_FILE_tmp" || _error "Unable to read SSH identity file: '$SSH_IDENTITY_FILE'"
79 46924c01 2025-05-28 xs SSH_IDENTITY_FILE=$SSH_IDENTITY_FILE_tmp
81 0253bef1 2025-05-29 xs R7_ENV_READY=yes
82 46924c01 2025-05-28 xs unset R7_WORKDIR_tmp R7_SITE_CONFIG_tmp SSH_CONFIG_FILE_tmp SSH_IDENTITY_FILE_tmp
89 730e151b 2025-06-02 xs printf "FAILURE: %s\n" "$*" >&2
94 730e151b 2025-06-02 xs printf "ERROR: %s\n" "$*" >&2
98 f83445b1 2025-05-30 xs test "$R7_DEBUG" = yes && printf %s\\n "${*:-$(cat -)}" >&2
102 0253bef1 2025-05-29 xs if test "$R7_ENV_READY" = yes; then
103 0253bef1 2025-05-29 xs cd "$R7_WORKDIR" || return 2
104 f6bdfddc 2025-06-03 xs _trace_id_last=$(trace_id_last)
105 f6bdfddc 2025-06-03 xs if test -n "$_trace_id_last"; then
106 f6bdfddc 2025-06-03 xs printf -- "- (%s) - in %s the %s\n" \
107 f6bdfddc 2025-06-03 xs "$_trace_id_last" "$(basename "$R7_WORKDIR")" "$(_epoch "$_trace_id_last")"
108 f6bdfddc 2025-06-03 xs nodes_active | sed 's/^/ - (active) /'
117 46924c01 2025-05-28 xs alias help=usage
120 2f0c97a2 2025-06-04 xs version: $VERSION
121 730e151b 2025-06-02 xs usage: $0 [options] [command [arguments...]]
122 03d8f521 2025-05-27 xs usage options:
123 03d8f521 2025-05-27 xs -h Show usage and exit
125 03d8f521 2025-05-27 xs -i SSH identity file
126 03d8f521 2025-05-27 xs -F SSH client configuration file
127 03d8f521 2025-05-27 xs -w Work directory where to find ./nodes and ./groups
128 03d8f521 2025-05-27 xs -r Run directory, default to ./_run
129 03d8f521 2025-05-27 xs -o Output directory, default to ./_output
130 03d8f521 2025-05-27 xs -c Site configuration file, default to ./site-config
131 03d8f521 2025-05-27 xs -n Dry run, prevent any host modification
132 730e151b 2025-06-02 xs -e Exit the group execution on the error function
133 03d8f521 2025-05-27 xs -p Run each host in parallel per group call
134 0253bef1 2025-05-29 xs -O Show output interactively
136 0253bef1 2025-05-29 xs -a Select all available prefixes for a group command
137 0253bef1 2025-05-29 xs -s Output-dump and summary are applied after a group command
140 03d8f521 2025-05-27 xs run-config Run the site configuration
141 03d8f521 2025-05-27 xs group Deploy specified group to hosts
142 03d8f521 2025-05-27 xs output-dump Dump the run directory to the output directory
143 03d8f521 2025-05-27 xs output Consult the output
144 0253bef1 2025-05-29 xs summary Summary of errors and changes
145 03d8f521 2025-05-27 xs digraph-host Produce digraph host
146 730e151b 2025-06-02 xs digraph-ssh Produce SSH IDs digraph
147 730e151b 2025-06-02 xs import Import TARGET host in current nodes directory
148 87bce7c5 2025-05-30 xs info Default command showing active nodes and summary
149 31e019a2 2025-05-31 xs init Initialize a new work directory
151 03d8f521 2025-05-27 xs site-config format:
152 03d8f521 2025-05-27 xs prefix [prefix...] # Define the prefix to be executed in order
153 03d8f521 2025-05-27 xs group groupname ['.*'|hostname...] # Deploy specified group to hosts
158 03d8f521 2025-05-27 xs # shellcheck disable=SC2086
160 730e151b 2025-06-02 xs test -n "$ZSH_VERSION" && {
161 730e151b 2025-06-02 xs echo >&2 "ZSH is unsupported. Exiting."
164 730e151b 2025-06-02 xs while getopts 'P:F:r:o:i:w:c:npdseaOh' arg; do
172 46924c01 2025-05-28 xs _debug "R7 DEBUG $VERSION"
174 03d8f521 2025-05-27 xs i) SSH_IDENTITY_FILE=$OPTARG ;;
175 03d8f521 2025-05-27 xs F) SSH_CONFIG_FILE=$OPTARG ;;
176 46924c01 2025-05-28 xs w) R7_WORKDIR=$OPTARG ;;
177 46924c01 2025-05-28 xs r) R7_RUNDIR=$OPTARG ;;
178 46924c01 2025-05-28 xs o) R7_OUTPUTDIR=$OPTARG ;;
179 0253bef1 2025-05-29 xs s) R7_GROUP_SUMMARY=yes ;;
180 0253bef1 2025-05-29 xs a) R7_GROUP_ALLPREFIX=yes ;;
181 730e151b 2025-06-02 xs e) R7_GROUP_EXITONERROR=yes ;;
182 46924c01 2025-05-28 xs c) R7_SITE_CONFIG=$OPTARG ;;
184 46924c01 2025-05-28 xs R7_DRYRUN=yes
185 46924c01 2025-05-28 xs _debug "MODE DRYRUN"
187 46924c01 2025-05-28 xs p) R7_PARALLEL=yes ;;
189 46924c01 2025-05-28 xs R7_PREFIX=$OPTARG
190 46924c01 2025-05-28 xs prefix $R7_PREFIX
192 46924c01 2025-05-28 xs O) R7_SHOWOUTPUT=yes ;;
195 0253bef1 2025-05-29 xs echo 1>&2 "Unknown option '$arg'"
200 03d8f521 2025-05-27 xs shift $((OPTIND - 1))
203 46924c01 2025-05-28 xs init | workdir-init)
205 730e151b 2025-06-02 xs _workdir_init "$@"
213 730e151b 2025-06-02 xs rc | run-config) run_config ;;
216 46924c01 2025-05-28 xs trace_id=$(_trace_id_new)
217 0253bef1 2025-05-29 xs if test "$R7_GROUP_ALLPREFIX" = yes; then
218 0253bef1 2025-05-29 xs prefix $(group_prefixes "$1")
220 87bce7c5 2025-05-30 xs prefix $R7_PREFIX
223 0253bef1 2025-05-29 xs if test "$R7_GROUP_SUMMARY" = yes; then
228 87bce7c5 2025-05-30 xs info) info ;;
233 87bce7c5 2025-05-30 xs out | output)
237 87bce7c5 2025-05-30 xs outdump | output-dump) output_dump ;;
238 0253bef1 2025-05-29 xs summary) summary ;;
239 03d8f521 2025-05-27 xs digraph-host)
240 46924c01 2025-05-28 xs prefix $R7_PREFIX
244 730e151b 2025-06-02 xs digraph_ssh_id_hosts
246 0551e9c2 2025-06-04 xs html) html "$@" ;;
250 730e151b 2025-06-02 xs -) exit 33 ;;
252 46924c01 2025-05-28 xs echo 1>&2 "Unknown command '$1'"
256 730e151b 2025-06-02 xs *) prefix $R7_PREFIX ;;
262 46924c01 2025-05-28 xs _resolv_dir() {
263 46924c01 2025-05-28 xs if test -d "$1"; then
264 46924c01 2025-05-28 xs cd "$1" || return 2
265 46924c01 2025-05-28 xs printf %s "$PWD"
269 46924c01 2025-05-28 xs _resolv_file() {
270 46924c01 2025-05-28 xs if test -f "$1"; then
271 46924c01 2025-05-28 xs cd "${1%/*}" || return 2
272 46924c01 2025-05-28 xs printf %s "$PWD/${1##*/}"
276 46924c01 2025-05-28 xs # shellcheck disable=SC2030
277 46924c01 2025-05-28 xs _workdir_init() (
278 730e151b 2025-06-02 xs _debug _WORKDIR_INIT "$PWD" "$R7_WORKDIR"
279 730e151b 2025-06-02 xs if test -n "$R7_WORKDIR"; then
280 730e151b 2025-06-02 xs mkdir -p "$R7_WORKDIR" || _fail "Unable to create directory: '$R7_WORKDIR'"
281 730e151b 2025-06-02 xs cd "$R7_WORKDIR" || _fail "Unable to cd to directory: '$R7_WORKDIR'"
282 730e151b 2025-06-02 xs R7_WORKDIR=$PWD
284 730e151b 2025-06-02 xs R7_WORKDIR=$PWD
285 46924c01 2025-05-28 xs mkdir -p groups
286 46924c01 2025-05-28 xs mkdir -p nodes
287 730e151b 2025-06-02 xs mkdir -p .ssh/control
288 730e151b 2025-06-02 xs : "${SSH_CONFIG_FILE:=$R7_WORKDIR/.ssh/config}"
289 730e151b 2025-06-02 xs : "${SSH_IDENTITY_FILE:=$R7_WORKDIR/.ssh/id_ed25519_r7}"
290 46924c01 2025-05-28 xs R7_SITE_CONFIG=./site-config
291 46924c01 2025-05-28 xs if ! test -f "$R7_SITE_CONFIG"; then
292 46924c01 2025-05-28 xs cat >"$R7_SITE_CONFIG" <<-eof
293 46924c01 2025-05-28 xs # Example r7 site configuration
294 730e151b 2025-06-02 xs group common '.*'
297 46924c01 2025-05-28 xs if ! test -f "$SSH_CONFIG_FILE"; then
298 46924c01 2025-05-28 xs cat >"$SSH_CONFIG_FILE" <<eof
299 46924c01 2025-05-28 xs # Example SSH client configuration
301 46924c01 2025-05-28 xs AddKeysToAgent yes
302 46924c01 2025-05-28 xs BatchMode yes
303 46924c01 2025-05-28 xs ConnectTimeout 3
304 46924c01 2025-05-28 xs ConnectionAttempts 1
305 46924c01 2025-05-28 xs ControlMaster auto
306 730e151b 2025-06-02 xs #ControlPath $R7_WORKDIR/.ssh/control/%C
307 730e151b 2025-06-02 xs ControlPath $R7_WORKDIR/.ssh/control/%r@%h:%p
308 46924c01 2025-05-28 xs ControlPersist 2m
309 46924c01 2025-05-28 xs IdentityFile $SSH_IDENTITY_FILE
310 46924c01 2025-05-28 xs StrictHostKeyChecking yes
312 46924c01 2025-05-28 xs UserKnownHostsFile $R7_WORKDIR/.ssh/known_hosts
315 46924c01 2025-05-28 xs Hostname 10.10.0.1
318 46924c01 2025-05-28 xs Hostname 10.10.0.2
321 46924c01 2025-05-28 xs Hostname 10.10.0.3
324 31e019a2 2025-05-31 xs touch "${SSH_IDENTITY_FILE}" "${SSH_IDENTITY_FILE}.pub"
325 46924c01 2025-05-28 xs touch .ssh/known_hosts
326 46924c01 2025-05-28 xs echo Created:
327 46924c01 2025-05-28 xs find "$PWD" -exec printf -- "\t- %s\n" {} +
330 46924c01 2025-05-28 xs # SSH functions
331 03d8f521 2025-05-27 xs # =============
334 46924c01 2025-05-28 xs _debug "SSH $*"
335 03d8f521 2025-05-27 xs /usr/bin/ssh "-oConnectTimeout=$SSH_CONNECT_TIMEOUT" -F "$SSH_CONFIG_FILE" "$@"
338 f83445b1 2025-05-30 xs alias ssh-debug=ssh_debug
339 f83445b1 2025-05-30 xs ssh_debug() {
340 f83445b1 2025-05-30 xs _debug "SSH $*"
341 f83445b1 2025-05-30 xs ssh -vvv "$@" :
344 f83445b1 2025-05-30 xs alias ssh-known-hosts=ssh_known_hosts
345 f83445b1 2025-05-30 xs ssh_known_hosts() {
346 f83445b1 2025-05-30 xs /usr/bin/ssh-keygen -lv -f "${SSH_KNOWN_HOSTS:-$R7_WORKDIR/.ssh/known_hosts}"
349 f83445b1 2025-05-30 xs alias ssh-authorized-keys-reset=ssh_authorized_keys_reset
350 f83445b1 2025-05-30 xs alias ssh-authreset=ssh_authorized_keys_reset
351 f83445b1 2025-05-30 xs ssh_authorized_keys_reset() {
352 f83445b1 2025-05-30 xs ssh -o BatchMode=no "$@" "
353 f83445b1 2025-05-30 xs mkdir -p ~/.ssh
354 f83445b1 2025-05-30 xs chmod 700 ~/.ssh
355 f83445b1 2025-05-30 xs touch ~/.ssh/authorized_keys
356 f83445b1 2025-05-30 xs chmod 600 ~/.ssh/authorized_keys
357 f83445b1 2025-05-30 xs echo '$(cat "$SSH_IDENTITY_FILE.pub")' >~/.ssh/authorized_keys
361 f83445b1 2025-05-30 xs alias ssh-accept-new=ssh_accept_new
362 f83445b1 2025-05-30 xs ssh_accept_new() {
363 f83445b1 2025-05-30 xs ssh -o StrictHostKeyChecking=accept-new "$@" :
366 f83445b1 2025-05-30 xs alias ssh-control-master-clean=ssh_control_master_clean
367 f83445b1 2025-05-30 xs ssh_control_master_clean() {
368 31e019a2 2025-05-31 xs find "$SSH_CONTROL_DIR" -type f -exec rm {} \;
371 f83445b1 2025-05-30 xs alias ssh-users=ssh_users
372 f83445b1 2025-05-30 xs ssh_users() {
373 730e151b 2025-06-02 xs cd "$R7_WORKDIR/nodes" || _fail "Unable to cd to '$R7_WORKDIR/nodes'."
374 730e151b 2025-06-02 xs find ./* -type f -name "authorized_keys_*" |
375 3aa5672d 2025-06-04 xs sed 's;^./;;' |
376 f83445b1 2025-05-30 xs sort | while read -r keyfile; do
377 f83445b1 2025-05-30 xs echo "- $keyfile:"
378 f83445b1 2025-05-30 xs grep -v "^#" "$R7_WORKDIR/nodes/$keyfile"
382 f83445b1 2025-05-30 xs alias ssh-ids=ssh_ids
384 f83445b1 2025-05-30 xs ssh_users | grep -v ^- | sort -u
387 f83445b1 2025-05-30 xs alias ssh-authorized-keys=ssh_authorized_keys
388 f83445b1 2025-05-30 xs alias ssh-auth=ssh_authorized_keys
389 f83445b1 2025-05-30 xs ssh_authorized_keys() {
390 f83445b1 2025-05-30 xs ssh_users | grep -e ^- | sed -e 's/^- /nodes\//' -e 's/:$//'
393 f83445b1 2025-05-30 xs alias ssh-ids-hosts=ssh_ids_hosts
394 f83445b1 2025-05-30 xs # shellcheck disable=SC2046
395 f83445b1 2025-05-30 xs ssh_ids_hosts() {
396 f83445b1 2025-05-30 xs ssh_ids | awk '{ print $3 }' |
397 f83445b1 2025-05-30 xs while read -r id; do
398 f83445b1 2025-05-30 xs grep -e "$id" $(ssh_authorized_keys)
399 f83445b1 2025-05-30 xs done | while IFS="$IFS/:" read -r _ hostname _ keyfile _ _ comment; do
400 f83445b1 2025-05-30 xs printf '"%s" -> "%s" -> "%s"\n' "${comment#* }" "${keyfile#authorized_keys_}" "$hostname"
405 03d8f521 2025-05-27 xs pkill ssh-agent
406 03d8f521 2025-05-27 xs eval "$(ssh-agent)"
407 03d8f521 2025-05-27 xs ssh-add "$SSH_IDENTITY_FILE"
411 87bce7c5 2025-05-30 xs test "$R7_DRYRUN" = yes && return 0
412 46924c01 2025-05-28 xs _debug "COPY $1 $2 BEGIN"
413 03d8f521 2025-05-27 xs node=${2%%:*}
415 03d8f521 2025-05-27 xs if r=$(ssh "$node" command -v openrsync); then
416 03d8f521 2025-05-27 xs if openrsync -V 2>/dev/null; then
417 46924c01 2025-05-28 xs _debug "COPY $1 $2 USE OPENRSYNC"
418 03d8f521 2025-05-27 xs openrsync -e "/usr/bin/ssh -oConnectTimeout=$SSH_CONNECT_TIMEOUT -F $SSH_CONFIG_FILE" \
419 03d8f521 2025-05-27 xs --rsync-path="$r" \
421 03d8f521 2025-05-27 xs -a "$1" "$2" && return 0
424 03d8f521 2025-05-27 xs if r=$(ssh "$node" command -v rsync); then
425 03d8f521 2025-05-27 xs if rsync -V 1>/dev/null; then
426 46924c01 2025-05-28 xs _debug "COPY $1 $2 USE RSYNC"
427 03d8f521 2025-05-27 xs rsync -e "/usr/bin/ssh -oConnectTimeout=$SSH_CONNECT_TIMEOUT -F $SSH_CONFIG_FILE" \
428 03d8f521 2025-05-27 xs --rsync-path="$r" \
430 03d8f521 2025-05-27 xs -aq "$1" "$2" && return 0
433 46924c01 2025-05-28 xs _debug "COPY $1 $2 USE SCP"
434 03d8f521 2025-05-27 xs dir=${dir:-/tmp/r7}
435 03d8f521 2025-05-27 xs ssh "$node" rm -fr "$dir/$1" 2>/dev/null
436 87bce7c5 2025-05-30 xs /usr/bin/scp "-oConnectTimeout=$SSH_CONNECT_TIMEOUT" -qF "$SSH_CONFIG_FILE" -r "$1" "$2"
439 03d8f521 2025-05-27 xs # Deployment functions
440 03d8f521 2025-05-27 xs # ====================
442 46924c01 2025-05-28 xs _trace_id_new() { date +%s; }
445 46924c01 2025-05-28 xs cd "$R7_WORKDIR" || return 2
446 0253bef1 2025-05-29 xs test -d ./nodes || return 0
447 03d8f521 2025-05-27 xs find nodes -type d -maxdepth 1 | tail -n +2 | awk -F/ '{ print $2 }'
450 46924c01 2025-05-28 xs alias nodes-active=nodes_active
451 46924c01 2025-05-28 xs alias nodesact=nodes_active
452 03d8f521 2025-05-27 xs nodes_active() (
453 f83445b1 2025-05-30 xs test "$R7_DRYRUN" = yes && {
457 46924c01 2025-05-28 xs if test -s "$R7_WORKDIR/.active_nodes"; then
458 46924c01 2025-05-28 xs cat "$R7_WORKDIR/.active_nodes"
460 46924c01 2025-05-28 xs touch "$R7_WORKDIR/.active_nodes"
461 03d8f521 2025-05-27 xs for node in $(nodes); do
462 46924c01 2025-05-28 xs _debug "ACTIVE $node"
463 03d8f521 2025-05-27 xs if ssh "$node" :; then
464 46924c01 2025-05-28 xs printf "%s\\n" "$node" >>"$R7_WORKDIR/.active_nodes"
467 46924c01 2025-05-28 xs cat "$R7_WORKDIR/.active_nodes"
471 46924c01 2025-05-28 xs alias run-config=run_config
472 46924c01 2025-05-28 xs alias rc=run_config
473 46924c01 2025-05-28 xs # shellcheck disable=SC1090,SC2031
474 03d8f521 2025-05-27 xs run_config() {
475 46924c01 2025-05-28 xs trace_id=$(_trace_id_new)
476 03d8f521 2025-05-27 xs # shellcheck disable=SC1091
477 46924c01 2025-05-28 xs _debug LOADING "$R7_SITE_CONFIG"
478 f6bdfddc 2025-06-03 xs sh -n "$R7_SITE_CONFIG" && . "$R7_SITE_CONFIG"
481 03d8f521 2025-05-27 xs # Inherits $target from group()
482 03d8f521 2025-05-27 xs # shellcheck disable=SC2031
484 46924c01 2025-05-28 xs _debug "DEPLOY $new_name $target"
485 46924c01 2025-05-28 xs cd "$R7_WORKDIR" || exit 2
486 03d8f521 2025-05-27 xs stem="groups/$new_name"
487 03d8f521 2025-05-27 xs nodedir="nodes/$target"
488 0253bef1 2025-05-29 xs test "$R7_DRYRUN" = yes || {
489 0253bef1 2025-05-29 xs if test -d "$stem.dir"; then
490 03d8f521 2025-05-27 xs ssh "$target" 'test -d /tmp/r7 || { umask 027; mkdir -p /tmp/r7; }'
491 03d8f521 2025-05-27 xs copy "$stem.dir" "$target:/tmp/r7" || return 3
493 0253bef1 2025-05-29 xs if test -d "$nodedir"; then
494 03d8f521 2025-05-27 xs test -f /tmp/r7/"$trace_id.$target" || {
495 03d8f521 2025-05-27 xs ssh "$target" 'test -d /tmp/r7 || { umask 027; mkdir -p /tmp/r7; }'
496 03d8f521 2025-05-27 xs copy "$nodedir" "$target:/tmp/r7" || return 3
500 03d8f521 2025-05-27 xs if test -f "$stem"; then
501 0253bef1 2025-05-29 xs if _payload "$stem" | sh -n; then
502 0253bef1 2025-05-29 xs _debug SYNTAX OK "$stem"
503 0253bef1 2025-05-29 xs test "$R7_DRYRUN" = yes || {
504 0253bef1 2025-05-29 xs _payload "$stem" | ssh 2>&1 "$target" /bin/sh - |
505 0253bef1 2025-05-29 xs _collect "$R7_RUNDIR/$trace_id.$target.$new_name"
508 0253bef1 2025-05-29 xs _debug SYNTAX KO "$stem"
509 0253bef1 2025-05-29 xs echo 1>&2 "syntax error in '$stem', skipping..."
516 03d8f521 2025-05-27 xs set -f -- $(printf -- "-e %s " "$@")
517 03d8f521 2025-05-27 xs nodes_active | grep "$@"
520 0253bef1 2025-05-29 xs # shellcheck disable=SC2030
522 46924c01 2025-05-28 xs test $# -eq 0 && return 0
523 46924c01 2025-05-28 xs cd "$R7_WORKDIR" || return 2
525 0253bef1 2025-05-29 xs test "$R7_DRYRUN" = yes || {
526 0253bef1 2025-05-29 xs mkdir -p "$R7_RUNDIR" || return 2
527 0253bef1 2025-05-29 xs cd "$R7_RUNDIR" || return 2
529 46924c01 2025-05-28 xs _debug "CREATED $R7_RUNDIR"
531 46924c01 2025-05-28 xs _debug "PROCESSING GROUP $new_name"
533 03d8f521 2025-05-27 xs # shellcheck disable=SC2046
535 03d8f521 2025-05-27 xs for target in $(members "$@"); do
536 46924c01 2025-05-28 xs case $R7_PARALLEL in
548 0253bef1 2025-05-29 xs group_prefixes() (
549 0253bef1 2025-05-29 xs grep 2>/dev/null -e "^.*()" "$R7_WORKDIR/groups/$1" | cut -d\( -f1 | cut -d_ -f1 | uniq
552 46924c01 2025-05-28 xs alias group-functions=group_functions
553 46924c01 2025-05-28 xs alias groupfuncs=group_functions
554 46924c01 2025-05-28 xs group_functions() (
555 0253bef1 2025-05-29 xs if test "$R7_GROUP_ALLPREFIX" = yes; then
556 0253bef1 2025-05-29 xs grep 2>/dev/null -e "^.*()" "$R7_WORKDIR/groups/$1" | cut -d\( -f1
559 03d8f521 2025-05-27 xs for pre in $prefix; do
560 46924c01 2025-05-28 xs grep 2>/dev/null -e "^${pre}_.*()" "$R7_WORKDIR/groups/$1" | cut -d\( -f1
566 730e151b 2025-06-02 xs R7_PREFIX=$prefix
567 730e151b 2025-06-02 xs export prefix R7_PREFIX
571 46924c01 2025-05-28 xs cd "$R7_WORKDIR" || return 2
572 03d8f521 2025-05-27 xs grep -e "^$2_.*()" "$1" |
573 03d8f521 2025-05-27 xs cut -d\( -f1 | while read -r function; do
574 46924c01 2025-05-28 xs _debug "CALL $1 $function"
575 03d8f521 2025-05-27 xs echo trace "${1#*/}" "$function" START
576 03d8f521 2025-05-27 xs echo "_ret=; $function || { _ret=\$?; : ;}"
577 03d8f521 2025-05-27 xs echo trace "${1#*/}" "$function" END "\${_ret:-0}"
578 f83445b1 2025-05-30 xs test "$R7_DRYRUN" = yes && {
579 f83445b1 2025-05-30 xs echo 1>&2 "- (dryrun) $target:$new_name::$function"
585 03d8f521 2025-05-27 xs echo "trace_id=$trace_id"
586 0253bef1 2025-05-29 xs echo "groupname=$new_name"
587 730e151b 2025-06-02 xs echo "exitonerror=$R7_GROUP_EXITONERROR"
590 03d8f521 2025-05-27 xs echo ":: $trace_id $(nodename) $*" 1>&2
594 46924c01 2025-05-28 xs trace ERROR "$*" 1>&2
595 730e151b 2025-06-02 xs test "$exitonerror" = yes && {
596 730e151b 2025-06-02 xs case $_ret in (0) _ret=1 ;; esac
597 730e151b 2025-06-02 xs trace EXIT $_ret
602 46924c01 2025-05-28 xs hostname=${hostname:-$(uname -n)}
603 03d8f521 2025-05-27 xs echo "${hostname%%.*}"
606 03d8f521 2025-05-27 xs atom=/tmp/r7/$(nodename)/$1
607 b7a0ae1e 2025-05-27 xs test -f "$atom" && return 0
608 b7a0ae1e 2025-05-27 xs test -d "$atom" && return 0
612 03d8f521 2025-05-27 xs echo "/tmp/r7/$(nodename)"
615 0253bef1 2025-05-29 xs echo "/tmp/r7/$groupname.dir"
617 730e151b 2025-06-02 xs _presource() {
618 730e151b 2025-06-02 xs test $# -gt 0 || {
619 730e151b 2025-06-02 xs error missing source argument
622 730e151b 2025-06-02 xs source "$(test -f "$1" && printf %s "$PWD/${1##"$PWD"}")"
624 730e151b 2025-06-02 xs nodesource() {
625 730e151b 2025-06-02 xs node "$1" && cd "$(nodedir)" && _presource "$1"
627 730e151b 2025-06-02 xs groupsource() {
628 730e151b 2025-06-02 xs cd "$(groupdir)" && _presource "$1"
631 730e151b 2025-06-02 xs if sh -n "$1"; then
632 730e151b 2025-06-02 xs trace SOURCE "$1"
635 730e151b 2025-06-02 xs trace SOURCE FAILED "$1"
639 87bce7c5 2025-05-30 xs install() ( cd /tmp/r7 && r7_install "$@" ; )
640 87bce7c5 2025-05-30 xs groupinstall() ( cd "$(groupdir)" && r7_install "$@" ; )
641 87bce7c5 2025-05-30 xs nodeinstall() ( cd "$(nodedir)" && r7_install "$@" ; )
643 730e151b 2025-06-02 xs test -f "$1" && {
644 730e151b 2025-06-02 xs ls -l "$1" | grep -v total | awk '{ print $1 " " $3 " " $4 }'
648 730e151b 2025-06-02 xs before=$(_getperm "$1")
649 730e151b 2025-06-02 xs if test -n "$owner"; then
650 730e151b 2025-06-02 xs chown "$owner" "$1" || {
651 730e151b 2025-06-02 xs error chown "$1"
655 730e151b 2025-06-02 xs if test -n "$mode"; then
656 730e151b 2025-06-02 xs chmod "$mode" "$1" || {
657 730e151b 2025-06-02 xs error chmod "$1"
661 730e151b 2025-06-02 xs after=$(_getperm "$1")
662 730e151b 2025-06-02 xs if test "$before" != "$after"; then
663 730e151b 2025-06-02 xs trace PERMISSIONS CHANGE "$before" TO "$after" ON "$1"
667 730e151b 2025-06-02 xs test -e "$2" && {
669 730e151b 2025-06-02 xs diff -U2 "$2" "$1"
671 730e151b 2025-06-02 xs cp "$1" "$2" ||
672 730e151b 2025-06-02 xs { error copying "$source" to "$2"; return 2 ; }
673 730e151b 2025-06-02 xs _setperm "$2"
676 0253bef1 2025-05-29 xs r7_install() (
677 03d8f521 2025-05-27 xs while getopts "o:m:" arg; do
679 730e151b 2025-06-02 xs o) owner=$OPTARG ;;
680 730e151b 2025-06-02 xs m) mode=$OPTARG ;;
684 46924c01 2025-05-28 xs shift $((OPTIND - 1))
687 b7a0ae1e 2025-05-27 xs source_dirname=${1%/*}
688 730e151b 2025-06-02 xs if ! test -e "$source"; then
689 46924c01 2025-05-28 xs error "$source" not found
692 730e151b 2025-06-02 xs case $target in
693 730e151b 2025-06-02 xs */) test -d "$target" || {
694 730e151b 2025-06-02 xs error "$target" directory not found
698 730e151b 2025-06-02 xs if test -e "$target"; then
699 730e151b 2025-06-02 xs cmp -s "$source" "$target"
701 730e151b 2025-06-02 xs destination_is=file
703 46924c01 2025-05-28 xs if test -d "$target"; then
704 46924c01 2025-05-28 xs new_filename="$target/${source##$source_dirname/}"
705 46924c01 2025-05-28 xs cmp -s "$source" "$new_filename"
707 730e151b 2025-06-02 xs destination_is=directory
709 730e151b 2025-06-02 xs if test -e "$source"; then
710 730e151b 2025-06-02 xs if test -d "$target"; then
711 730e151b 2025-06-02 xs destination_is=directory
713 730e151b 2025-06-02 xs destination_is=file
716 46924c01 2025-05-28 xs case "$ret" in
717 46924c01 2025-05-28 xs 0) return 30 ;;
719 730e151b 2025-06-02 xs case $destination_is in
721 730e151b 2025-06-02 xs new_filename="$target/${source##$source_dirname/}"
722 730e151b 2025-06-02 xs _install "$source" "$new_filename"
725 730e151b 2025-06-02 xs _install "$source" "$target"
731 b6cef3b8 2025-06-04 xs installdir() { r7_installdir "$@" ; }
732 f6bdfddc 2025-06-03 xs groupinstalldir() ( cd "$(groupdir)" && r7_installdir "$@" ; )
733 f6bdfddc 2025-06-03 xs nodeinstalldir() ( cd "$(nodedir)" && r7_installdir "$@" ; )
734 730e151b 2025-06-02 xs r7_installdir() (
735 f6bdfddc 2025-06-03 xs while getopts "o:m:O:M:" arg; do
737 730e151b 2025-06-02 xs o) fileowner=$OPTARG ;;
738 730e151b 2025-06-02 xs m) filenode=$OPTARG ;;
739 730e151b 2025-06-02 xs O) dirowner=$OPTARG ;;
740 730e151b 2025-06-02 xs M) dirmode=$OPTARG ;;
744 f6bdfddc 2025-06-03 xs shift $((OPTIND - 1))
747 f6bdfddc 2025-06-03 xs source_basename=${source##*/}
748 f6bdfddc 2025-06-03 xs source_dirname=$PWD/${source%%/*}
749 f6bdfddc 2025-06-03 xs target_basename=${target##*/}
750 f6bdfddc 2025-06-03 xs test -d "$source" || {
751 f6bdfddc 2025-06-03 xs error "$source" directory not found
754 f6bdfddc 2025-06-03 xs for dir in $(find "$source" -type d); do
755 f6bdfddc 2025-06-03 xs newdir=${dir#./}
757 f6bdfddc 2025-06-03 xs cd "$target" || return 2
758 f6bdfddc 2025-06-03 xs mkdir -p "$newdir" || { error unable to create "$newdir"; return 2 ; }
759 f6bdfddc 2025-06-03 xs owner=$dirowner mode=$dirmode _setperm "$1"
762 f6bdfddc 2025-06-03 xs for file in $(find "$source" -type f); do
763 f6bdfddc 2025-06-03 xs newfile=${file#./}
764 f6bdfddc 2025-06-03 xs r7_install -o "$fileowner" -m "$filemode" "$file" "$target/$newfile"
768 730e151b 2025-06-02 xs cd /tmp/r7 && umask 027
770 730e151b 2025-06-02 xs nodename=$(nodename)
771 730e151b 2025-06-02 xs nodedir=$(nodedir)
772 730e151b 2025-06-02 xs groupdir=$(groupdir)
773 03d8f521 2025-05-27 xs trace CONNECTED $(date +%s)
776 730e151b 2025-06-02 xs _debug "PREFIX $R7_PREFIX"
777 730e151b 2025-06-02 xs for run in $R7_PREFIX; do
778 46924c01 2025-05-28 xs _call "$1" "$run"
781 03d8f521 2025-05-27 xs trace DISCONNECT $(date +%s)
785 03d8f521 2025-05-27 xs # Interactive output function
786 03d8f521 2025-05-27 xs # ===========================
788 46924c01 2025-05-28 xs # shellcheck disable=SC2086,SC2030
790 03d8f521 2025-05-27 xs tee -a "$1" | while read -r line; do
792 03d8f521 2025-05-27 xs case $line in
794 46924c01 2025-05-28 xs # Traces from payload
801 688c9bfd 2025-06-03 xs # Traces from _call
802 03d8f521 2025-05-27 xs case $status in
804 46924c01 2025-05-28 xs save_group=$group save_function=$function
805 03d8f521 2025-05-27 xs in_function=1
806 46924c01 2025-05-28 xs if test "$R7_SHOWOUTPUT" = yes; then
807 03d8f521 2025-05-27 xs echo "- (output) $host:$group::$function"
812 03d8f521 2025-05-27 xs in_function=0
813 03d8f521 2025-05-27 xs case $endcode in
814 730e151b 2025-06-02 xs 0 | 30 | 31) echo "- (ok) $host:$group::$function" ;;
815 03d8f521 2025-05-27 xs *) echo "! (error $endcode) $host:$group::$function" ;;
819 46924c01 2025-05-28 xs # Traces from functions
823 730e151b 2025-06-02 xs SOURCE) echo "+ (source) $host:$save_group::$save_function" ;;
824 46924c01 2025-05-28 xs DIFF) echo "+ (diff) $host:$save_group::$save_function" ;;
825 46924c01 2025-05-28 xs PERMISSIONS) echo "+ (permissions) $host:$save_group::$save_function" ;;
826 46924c01 2025-05-28 xs ERROR) echo "! (error) $host:$save_group::$save_function" ;;
827 730e151b 2025-06-02 xs EXIT) echo "! (exit) $host:$save_group::$save_function" ;;
834 46924c01 2025-05-28 xs if test "$R7_SHOWOUTPUT" = yes; then
835 03d8f521 2025-05-27 xs test "$in_function" -eq 1 &&
836 03d8f521 2025-05-27 xs printf "%s\\n" "$line"
841 03d8f521 2025-05-27 xs # Text output functions
842 03d8f521 2025-05-27 xs # =====================
844 46924c01 2025-05-28 xs alias output-dump=output_dump
845 46924c01 2025-05-28 xs alias outdump=output_dump
846 03d8f521 2025-05-27 xs output_dump() (
847 03d8f521 2025-05-27 xs set -e -- $(output_latest)
848 46924c01 2025-05-28 xs test $# -eq 0 && return 0
849 0253bef1 2025-05-29 xs cd "$R7_WORKDIR" || _fail "Unable to cd to workdir: $R7_WORKDIR"
850 0253bef1 2025-05-29 xs test "$R7_DRYRUN" = yes || {
851 0253bef1 2025-05-29 xs mkdir -p "$R7_OUTPUTDIR"
852 0253bef1 2025-05-29 xs awk -voutput_dir="$R7_OUTPUTDIR" '
858 03d8f521 2025-05-27 xs in_function_block=0
860 03d8f521 2025-05-27 xs /^:: [0-9].*/ {
861 03d8f521 2025-05-27 xs if ($4 == "CONNECTED") {
864 03d8f521 2025-05-27 xs timestamp_connect=$5
865 03d8f521 2025-05-27 xs system("mkdir -p " output_dir "/" hostname)
868 03d8f521 2025-05-27 xs $2 == trace_id && $6 == "START" {
871 03d8f521 2025-05-27 xs in_function_block=1
872 03d8f521 2025-05-27 xs output_node = output_dir "/" hostname "/" groupname "/" funcname
873 03d8f521 2025-05-27 xs output_status = output_node "/status"
874 03d8f521 2025-05-27 xs output_output = output_node "/output"
875 03d8f521 2025-05-27 xs system("rm -fr " output_node)
876 03d8f521 2025-05-27 xs system("mkdir -p " output_node)
879 730e151b 2025-06-02 xs $2 == trace_id && $4 == "EXIT" {
880 730e151b 2025-06-02 xs in_function_block=0
881 730e151b 2025-06-02 xs system("echo " $5 " >" output_status)
883 03d8f521 2025-05-27 xs $2 == trace_id && $6 == "END" {
884 03d8f521 2025-05-27 xs in_function_block=0
885 03d8f521 2025-05-27 xs system("echo " $7 " >" output_status)
887 03d8f521 2025-05-27 xs in_function_block == 1 {
888 03d8f521 2025-05-27 xs print > output_output
890 03d8f521 2025-05-27 xs $2 == trace_id && $4 == "DISCONNECT" {
891 03d8f521 2025-05-27 xs timestamp_disconnect=$5
892 03d8f521 2025-05-27 xs difference=timestamp_disconnect - timestamp_connect
893 03d8f521 2025-05-27 xs output_duration = output_dir "/" hostname "/" groupname "/duration"
894 03d8f521 2025-05-27 xs system("echo " difference " >" output_duration)
901 f6bdfddc 2025-06-03 xs case $(uname) in
902 f6bdfddc 2025-06-03 xs OpenBSD | FreeBSD | NetBSD | Darwin)
906 f6bdfddc 2025-06-03 xs date -d "@$1"
909 f6bdfddc 2025-06-03 xs perl -e "print scalar localtime($1)"
915 46924c01 2025-05-28 xs alias output-latest=output_latest
916 46924c01 2025-05-28 xs alias outlatest=output_latest
917 46924c01 2025-05-28 xs output_latest() (
920 03d8f521 2025-05-27 xs # shellcheck disable=SC2012
921 31e019a2 2025-05-31 xs ls 2>/dev/null -lt1 "$R7_RUNDIR/" | while read -r file; do
922 03d8f521 2025-05-27 xs echo "${file%%.*}"
926 46924c01 2025-05-28 xs test -z "$latest" && return 0
927 f6bdfddc 2025-06-03 xs latest_date=$(_epoch "$latest")
928 03d8f521 2025-05-27 xs if test -n "$latest_date"; then
929 0253bef1 2025-05-29 xs find "$R7_RUNDIR" -type f -name "$latest.*"
933 46924c01 2025-05-28 xs _output_files() (
934 46924c01 2025-05-28 xs cd "$R7_OUTPUTDIR" 2>/dev/null || return 2
935 03d8f521 2025-05-27 xs find ./* -type f -name 'output'
938 46924c01 2025-05-28 xs _status_files() (
939 46924c01 2025-05-28 xs cd "$R7_OUTPUTDIR" 2>/dev/null || return 2
940 03d8f521 2025-05-27 xs find ./* -type f -name 'status'
943 46924c01 2025-05-28 xs _duration_files() (
944 46924c01 2025-05-28 xs cd "$R7_OUTPUTDIR" 2>/dev/null || return 2
945 03d8f521 2025-05-27 xs find ./* -type f -name 'duration'
949 f6bdfddc 2025-06-03 xs column -t 2>/dev/null ||
953 46924c01 2025-05-28 xs alias output-status=output_status
954 46924c01 2025-05-28 xs alias outstat=output_status
955 03d8f521 2025-05-27 xs output_status() (
956 46924c01 2025-05-28 xs cd "$R7_OUTPUTDIR" || return 2
957 f6bdfddc 2025-06-03 xs echo HOST GROUP FUNCTION STATUS
958 46924c01 2025-05-28 xs _status_files | {
959 03d8f521 2025-05-27 xs while read -r status; do
960 03d8f521 2025-05-27 xs echo "$status" | while IFS=/ read -r _ host groupname funcname _; do
961 03d8f521 2025-05-27 xs echo "$host" "$groupname" "$funcname" "$(cat "$status")"
967 46924c01 2025-05-28 xs alias output-duration=output_duration
968 46924c01 2025-05-28 xs alias outdur=output_duration
969 03d8f521 2025-05-27 xs output_duration() (
970 730e151b 2025-06-02 xs cd "$R7_OUTPUTDIR" || return 2
971 f6bdfddc 2025-06-03 xs echo HOST GROUP DURATION
972 46924c01 2025-05-28 xs _duration_files | {
973 03d8f521 2025-05-27 xs while read -r duration; do
974 03d8f521 2025-05-27 xs echo "$duration" | while IFS=/ read -r _ host groupname _; do
975 03d8f521 2025-05-27 xs echo "$host" "$groupname" "$(cat "$duration")"
981 46924c01 2025-05-28 xs alias out=output
982 87bce7c5 2025-05-30 xs # shellcheck disable=SC2254
984 730e151b 2025-06-02 xs cd "$R7_OUTPUTDIR" || return 2
985 46924c01 2025-05-28 xs _output_files | while read -r output; do
986 03d8f521 2025-05-27 xs echo "$output" | while IFS=/ read -r _ host groupname funcname _; do
987 87bce7c5 2025-05-30 xs case $host in ${1:-*})
988 87bce7c5 2025-05-30 xs case $groupname in ${2:-*})
989 87bce7c5 2025-05-30 xs case $funcname in ${3:-*})
990 87bce7c5 2025-05-30 xs echo "- $host" "$groupname" "$funcname"
992 87bce7c5 2025-05-30 xs sed 's/^/ /' "$R7_OUTPUTDIR/$output"
1004 46924c01 2025-05-28 xs alias trace-id-last=trace_id_last
1005 46924c01 2025-05-28 xs alias lastid=trace_id_last
1006 46924c01 2025-05-28 xs trace_id_last() {
1007 46924c01 2025-05-28 xs output_latest | while read -r output; do
1008 46924c01 2025-05-28 xs output=${output##*/}
1009 46924c01 2025-05-28 xs output=${output%%.*}
1010 46924c01 2025-05-28 xs echo "$output"
1015 46924c01 2025-05-28 xs # _output/tools0/r7test/r7test_diff/output::: 1748408055 tools0 DIFF /tmp/difftest r7test.dir/difftest
1016 46924c01 2025-05-28 xs alias output-diffs=output_diffs
1017 46924c01 2025-05-28 xs alias outdiffs=output_diffs
1018 46924c01 2025-05-28 xs # shellcheck disable=SC2048,SC2086
1019 46924c01 2025-05-28 xs output_diffs() (
1020 46924c01 2025-05-28 xs trace_id=$(trace_id_last)
1021 0253bef1 2025-05-29 xs cd "$R7_OUTPUTDIR" || return 0
1022 730e151b 2025-06-02 xs grep -r "^:: ${trace_id:-} .* DIFF" | uniq | while read -r line; do
1023 46924c01 2025-05-28 xs set -- $line
1024 46924c01 2025-05-28 xs output_file=${1%%:::}
1027 46924c01 2025-05-28 xs set -- $output_file
1028 0253bef1 2025-05-29 xs host=$1 groupname=$2 funcname=$3
1030 f83445b1 2025-05-30 xs echo "- (diff) -" "in" "$host" "$groupname" "$funcname" "($trace_id)"
1032 46924c01 2025-05-28 xs sed 's/^/ /' "$R7_OUTPUTDIR/$host/$groupname/$funcname/output" | grep -v "^ :: $trace_id $host"
1034 730e151b 2025-06-02 xs done || return 0
1038 46924c01 2025-05-28 xs # :: 1748415578 tools0 PERMISSIONS CHANGE -rw-r-xr-x root wheel -> -rw-r--r-- root wheel on /tmp/difftest
1039 46924c01 2025-05-28 xs alias output-permissions=output_permissions
1040 46924c01 2025-05-28 xs alias outperm=output_permissions
1041 46924c01 2025-05-28 xs # shellcheck disable=SC2048,SC2086
1042 46924c01 2025-05-28 xs output_permissions() (
1043 46924c01 2025-05-28 xs trace_id=$(trace_id_last)
1044 730e151b 2025-06-02 xs cd "$R7_OUTPUTDIR" || return 0
1045 730e151b 2025-06-02 xs grep -r "^:: ${trace_id:-} .* PERMISSIONS" | while read -r line; do
1046 46924c01 2025-05-28 xs set -- $line
1047 46924c01 2025-05-28 xs output_file=${1%%:::}
1050 46924c01 2025-05-28 xs set -- $output_file
1051 730e151b 2025-06-02 xs host=$1 groupname=$2 funcname=$3
1053 f83445b1 2025-05-30 xs echo "- (permissions) -" "in" "$host" "$groupname" "$funcname" "($trace_id)"
1055 730e151b 2025-06-02 xs grep -e "^:: ${trace_id:-} .* PERMISSIONS CHANGE" "$R7_OUTPUTDIR/$host/$groupname/$funcname/output" |
1056 730e151b 2025-06-02 xs while read -r _ _ _ _ _ mode owner group _ mode2 owner2 group2 _ filename; do
1057 730e151b 2025-06-02 xs echo " - $filename from $mode $owner:$group to $mode2 $owner2:$group2"
1064 46924c01 2025-05-28 xs alias output-errors=output_errors
1065 46924c01 2025-05-28 xs alias outerr=output_errors
1066 46924c01 2025-05-28 xs output_errors() (
1067 f83445b1 2025-05-30 xs trace_id=$(trace_id_last)
1068 f6bdfddc 2025-06-03 xs output_status | while read -r host groupname funcname status; do
1069 b7a0ae1e 2025-05-27 xs case $status in
1070 f6bdfddc 2025-06-03 xs STATUS | 0 | 30 | 31) continue ;;
1072 f83445b1 2025-05-30 xs echo "- (error $status) -" "in" "$host" "$groupname" "$funcname" "($trace_id)"
1073 46924c01 2025-05-28 xs if test -f "$R7_OUTPUTDIR/$host/$groupname/$funcname/output"; then
1075 46924c01 2025-05-28 xs sed 's/^/ /' "$R7_OUTPUTDIR/$host/$groupname/$funcname/output"
1083 0253bef1 2025-05-29 xs test -d "$R7_OUTPUTDIR" || return 0
1084 0253bef1 2025-05-29 xs summary_out=$(
1085 0253bef1 2025-05-29 xs output_errors
1086 0253bef1 2025-05-29 xs output_diffs
1087 0253bef1 2025-05-29 xs output_permissions
1089 0253bef1 2025-05-29 xs test -n "$summary_out" &&
1090 0253bef1 2025-05-29 xs printf "%s\n" "$summary_out"
1094 03d8f521 2025-05-27 xs # Digraph functions
1095 03d8f521 2025-05-27 xs # =================
1097 03d8f521 2025-05-27 xs # Used in the digraph generation because inside a heredoc
1098 03d8f521 2025-05-27 xs # context, memebers don't work as expected (OKSH).
1099 46924c01 2025-05-28 xs # shellcheck disable=SC2048,SC2086
1100 46924c01 2025-05-28 xs _members2() (
1103 03d8f521 2025-05-27 xs nodes | grep -e '.*'
1107 03d8f521 2025-05-27 xs set -f -- $(printf -- "-e %s " "$@")
1108 46924c01 2025-05-28 xs nodes | grep $*
1111 46924c01 2025-05-28 xs alias digraph-host=digraph_host
1112 46924c01 2025-05-28 xs # shellcheck disable=SC2086
1113 03d8f521 2025-05-27 xs digraph_host() (
1114 46924c01 2025-05-28 xs _debug "DIGRAPH HOST"
1116 03d8f521 2025-05-27 xs digraph host {
1117 03d8f521 2025-05-27 xs layout="dot"
1118 03d8f521 2025-05-27 xs splines="true"
1119 03d8f521 2025-05-27 xs graph [overlap="false", rankdir=LR, mode=KK]
1122 03d8f521 2025-05-27 xs for node in $(nodes); do echo "\"$node\" [peripheries=2 color=blue]"; done
1123 46924c01 2025-05-28 xs grep 2>/dev/null "^group" "$R7_SITE_CONFIG" | while read -r _ groupname nodes; do
1124 03d8f521 2025-05-27 xs echo "\"$groupname\" [color=orange]"
1125 46924c01 2025-05-28 xs for node in $(_members2 $nodes); do
1126 03d8f521 2025-05-27 xs echo "\"$node\" -> \"$groupname\""
1128 46924c01 2025-05-28 xs for funct in $(group_functions "$groupname"); do
1129 730e151b 2025-06-02 xs echo "\"$groupname::$funct\" [color=red]"
1130 730e151b 2025-06-02 xs echo "\"$groupname\" -> \"$groupname::$funct\""
1138 f83445b1 2025-05-30 xs alias digraph-ssh-id-hosts=digraph_ssh_id_hosts
1139 f83445b1 2025-05-30 xs digraph_ssh_id_hosts() {
1141 730e151b 2025-06-02 xs digraph ssh_id_hosts {
1142 730e151b 2025-06-02 xs layout="dot"
1143 730e151b 2025-06-02 xs splines="true"
1144 730e151b 2025-06-02 xs graph [overlap="false"]
1145 730e151b 2025-06-02 xs $(ssh_ids_hosts)
1155 730e151b 2025-06-02 xs pandoc -t html -f markdown+smart "$@"
1159 0551e9c2 2025-06-04 xs summary | while read -r line; do
1160 0551e9c2 2025-06-04 xs set -- $line
1163 0551e9c2 2025-06-04 xs printf %s\\n "$line"
1165 0551e9c2 2025-06-04 xs *) continue ;;
1169 0551e9c2 2025-06-04 xs cat "$R7_OUTPUTDIR/$6/$7/$8/output"
1177 688c9bfd 2025-06-03 xs <!DOCTYPE html>
1178 688c9bfd 2025-06-03 xs <html lang="en">
1180 688c9bfd 2025-06-03 xs <meta charset="UTF-8">
1181 688c9bfd 2025-06-03 xs <meta name="viewport" content="width=device-width, initial-scale=1.0">
1182 688c9bfd 2025-06-03 xs <title>inda.re report</title>
1183 688c9bfd 2025-06-03 xs <style type="text/css" media="screen">
1184 688c9bfd 2025-06-03 xs html { margin: 4%; }
1186 688c9bfd 2025-06-03 xs margin: 0 auto;
1187 688c9bfd 2025-06-03 xs max-width: 40em;
1188 688c9bfd 2025-06-03 xs padding-left: 50px;
1189 688c9bfd 2025-06-03 xs padding-right: 50px;
1190 688c9bfd 2025-06-03 xs padding-top: 50px;
1191 688c9bfd 2025-06-03 xs padding-bottom: 50px;
1192 688c9bfd 2025-06-03 xs hyphens: auto;
1193 688c9bfd 2025-06-03 xs overflow-wrap: break-word;
1194 688c9bfd 2025-06-03 xs text-rendering: optimizeLegibility;
1195 688c9bfd 2025-06-03 xs font-kerning: normal;
1198 688c9bfd 2025-06-03 xs text-align: justify;
1199 688c9bfd 2025-06-03 xs text-justify: inter-word;
1201 688c9bfd 2025-06-03 xs @media screen and (min-width: 1000px) {
1203 688c9bfd 2025-06-03 xs max-width:1300px;
1204 688c9bfd 2025-06-03 xs margin: auto;
1207 688c9bfd 2025-06-03 xs @media (prefers-color-scheme: dark) {
1209 688c9bfd 2025-06-03 xs --red: #E05434;
1210 688c9bfd 2025-06-03 xs --green: #20B700;
1211 688c9bfd 2025-06-03 xs --blue: #A7C1E5;
1212 688c9bfd 2025-06-03 xs --buildhead: #474749;
1215 688c9bfd 2025-06-03 xs color: #BAD7FF;
1218 688c9bfd 2025-06-03 xs color: #F6BAFF;
1221 688c9bfd 2025-06-03 xs background-color: #1E1F21;
1222 688c9bfd 2025-06-03 xs color: #EEEFF1;
1228 3aa5672d 2025-06-04 xs <h1>${PWD##*/}</h1>
1231 688c9bfd 2025-06-03 xs <li><a href="#Documentation">Documentation</a></li>
1232 0551e9c2 2025-06-04 xs <li><a href="#Summary">Summary</a></li>
1236 688c9bfd 2025-06-03 xs <section id="Documentation">
1237 688c9bfd 2025-06-03 xs $(md README.md)
1239 0551e9c2 2025-06-04 xs <section id="Summary">
1257 d8747c12 2025-06-03 xs cd "$R7_WORKDIR" || _fail "Unable to cd to '$R7_WORKDIR'"
1259 730e151b 2025-06-02 xs etc_files_to_import='
1260 730e151b 2025-06-02 xs acme-client.conf
1277 f976c0b3 2025-06-05 xs gotwebd.conf
1279 519b84c4 2025-06-05 xs etc_mail_files_to_import='
1280 519b84c4 2025-06-05 xs mail/aliases
1281 519b84c4 2025-06-05 xs mail/smtpd.conf
1282 519b84c4 2025-06-05 xs mail/spamd.conf
1284 730e151b 2025-06-02 xs etc_ssh_files_to_import='
1285 730e151b 2025-06-02 xs ssh/sshd_config
1286 730e151b 2025-06-02 xs ssh/ssh_config
1288 730e151b 2025-06-02 xs var_unbound_files='
1289 730e151b 2025-06-02 xs db/named.root
1290 730e151b 2025-06-02 xs etc/unbound.conf
1292 730e151b 2025-06-02 xs var_nsd_files='
1293 730e151b 2025-06-02 xs etc/nsd.conf
1295 730e151b 2025-06-02 xs var_postgresql_files='
1296 730e151b 2025-06-02 xs data/pg_hba.conf
1297 730e151b 2025-06-02 xs data/postgresql.conf
1299 730e151b 2025-06-02 xs if test -n "$target"; then
1300 730e151b 2025-06-02 xs echo >&2 "-> Importing target host: $target"
1301 730e151b 2025-06-02 xs case $(ssh "$target" uname) in OpenBSD) : ;; *)
1302 8a9cbf90 2025-06-04 xs _error "Platform unsupported or host unavailable."
1306 d8747c12 2025-06-03 xs _OpenBSD_import_ssh_authorized_keys
1307 d8747c12 2025-06-03 xs _OpenBSD_import_etc_if_exists
1308 d8747c12 2025-06-03 xs _OpenBSD_import_etc_ssh_if_exists
1309 519b84c4 2025-06-05 xs _OpenBSD_import_etc_mail_if_exists
1310 d8747c12 2025-06-03 xs _OpenBSD_import_unbound_if_enabled
1311 d8747c12 2025-06-03 xs _OpenBSD_import_nsd_if_enabled
1312 d8747c12 2025-06-03 xs _OpenBSD_import_postgresql_if_installed
1313 730e151b 2025-06-02 xs echo >&2 "-> done: $target"
1315 730e151b 2025-06-02 xs echo >&2 "usage: $0 target-host"
1319 d8747c12 2025-06-03 xs _OpenBSD_import_ssh_authorized_keys() {
1320 730e151b 2025-06-02 xs other_users=$(
1321 730e151b 2025-06-02 xs ssh "$target" getent passwd | awk -F: '$3 >= 1000 { print $1 }' |
1322 730e151b 2025-06-02 xs grep -v nobody
1325 730e151b 2025-06-02 xs for user in root $other_users; do
1326 730e151b 2025-06-02 xs # shellcheck disable=SC2029
1327 730e151b 2025-06-02 xs home=$(ssh "$target" getent passwd "$user" | awk -F: '{ print $6 }')
1328 730e151b 2025-06-02 xs authorized_keys="$home/.ssh/authorized_keys"
1329 730e151b 2025-06-02 xs ssh "$target" test -f "$authorized_keys" || continue
1330 730e151b 2025-06-02 xs mkdir -p "nodes/$target/ssh/"
1331 730e151b 2025-06-02 xs echo >&2 "--> Importing $authorized_keys"
1332 f976c0b3 2025-06-05 xs #_import_copy "root@$target:$authorized_keys" "nodes/$target/ssh/authorized_keys_$user"
1333 f976c0b3 2025-06-05 xs scp "root@$target:$authorized_keys" "nodes/$target/ssh/authorized_keys_$user"
1337 d8747c12 2025-06-03 xs _OpenBSD_import_etc_if_exists() {
1338 730e151b 2025-06-02 xs mkdir -p nodes/"$target"
1339 730e151b 2025-06-02 xs interfaces=$(ssh "$target" 'cd /etc && ls -1 hostname.*')
1340 730e151b 2025-06-02 xs echo >&2 "--> Importing /etc"
1341 730e151b 2025-06-02 xs for file in $etc_files_to_import $interfaces; do
1342 730e151b 2025-06-02 xs ssh "$target" test -f "'/etc/$file'" &&
1343 d8747c12 2025-06-03 xs _import_copy "$target:/etc/$file" "nodes/$target/"
1347 d8747c12 2025-06-03 xs _OpenBSD_import_etc_ssh_if_exists() {
1348 730e151b 2025-06-02 xs mkdir -p nodes/"$target"
1349 730e151b 2025-06-02 xs echo >&2 "--> Importing /etc/ssh"
1350 730e151b 2025-06-02 xs for file in $etc_ssh_files_to_import; do
1351 730e151b 2025-06-02 xs ssh "$target" test -f "'/etc/$file'" &&
1352 d8747c12 2025-06-03 xs _import_copy "$target:/etc/$file" "nodes/$target/ssh"
1356 519b84c4 2025-06-05 xs _OpenBSD_import_etc_mail_if_exists() {
1357 519b84c4 2025-06-05 xs mkdir -p nodes/"$target"
1358 519b84c4 2025-06-05 xs echo >&2 "--> Importing /etc/ssh"
1359 519b84c4 2025-06-05 xs for file in $etc_mail_files_to_import; do
1360 519b84c4 2025-06-05 xs ssh "$target" test -f "'/etc/$file'" &&
1361 519b84c4 2025-06-05 xs _import_copy "$target:/etc/$file" "nodes/$target/mail"
1365 d8747c12 2025-06-03 xs _OpenBSD_import_unbound_if_enabled() {
1366 730e151b 2025-06-02 xs ssh "$target" rcctl check unbound >/dev/null && {
1367 730e151b 2025-06-02 xs echo >&2 "--> Importing /var/unbound"
1368 730e151b 2025-06-02 xs for file in $var_unbound_files; do
1369 730e151b 2025-06-02 xs prefix=${file%%/*}
1370 730e151b 2025-06-02 xs mkdir -p "nodes/$target/unbound/$prefix"
1371 d8747c12 2025-06-03 xs _import_copy "$target:/var/unbound/$file" "nodes/$target/unbound/$prefix/"
1377 d8747c12 2025-06-03 xs _OpenBSD_import_nsd_if_enabled() {
1378 730e151b 2025-06-02 xs ssh "$target" rcctl check nsd >/dev/null && {
1379 730e151b 2025-06-02 xs echo >&2 "--> Import /var/nsd"
1380 730e151b 2025-06-02 xs for file in $var_nsd_files; do
1381 730e151b 2025-06-02 xs prefix=${file%%/*}
1382 730e151b 2025-06-02 xs mkdir -p "nodes/$target/nsd/$prefix"
1383 d8747c12 2025-06-03 xs _import_copy "$target:/var/nsd/$file" "nodes/$target/nsd/$prefix/"
1389 d8747c12 2025-06-03 xs _OpenBSD_import_postgresql_if_installed() {
1390 730e151b 2025-06-02 xs ssh "$target" postgres -V 2>/dev/null | grep -q PostgreSQL && {
1391 730e151b 2025-06-02 xs echo >&2 "--> Importing /var/postgresql"
1392 730e151b 2025-06-02 xs for file in $var_postgresql_files; do
1393 730e151b 2025-06-02 xs prefix=${file%%/*}
1394 730e151b 2025-06-02 xs mkdir -p "nodes/$target/postgresql/$prefix"
1395 d8747c12 2025-06-03 xs _import_copy "$target:/var/postgresql/$file" "nodes/$target/postgresql/$prefix/"
1397 730e151b 2025-06-02 xs echo >&2 "-- Importing /etc/login.conf.d/postgresql"
1398 730e151b 2025-06-02 xs mkdir -p "nodes/$target/login.conf.d"
1399 d8747c12 2025-06-03 xs _import_copy "$target:/etc/login.conf.d/postgresql" "nodes/$target/login.conf.d/"
1404 d8747c12 2025-06-03 xs _import_copy() {
1405 730e151b 2025-06-02 xs _debug "IMPORT $1 $2 USE OPENRSYNC"
1406 730e151b 2025-06-02 xs openrsync -e "/usr/bin/ssh -oConnectTimeout=$SSH_CONNECT_TIMEOUT -F $SSH_CONFIG_FILE" \
1407 730e151b 2025-06-02 xs --rsync-path="/usr/bin/openrsync" \
1408 730e151b 2025-06-02 xs -a "$1" "$2" && return 0
1414 46924c01 2025-05-28 xs _main "${@:-}"