Created
March 2, 2026 16:31
-
-
Save megamen32/5bce58d71973d70b64e8f220053368c0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # --- all: run command or script on local + many hosts (bash-only) ------------ | |
| all() { | |
| # ---- defaults / env ---- | |
| local parallel=${ALL_PARALLEL:-1} # 1=parallel remotes | |
| local run_local=${ALL_LOCAL:-1} # 1=run local too | |
| local local_parallel=${ALL_LOCAL_PARALLEL:-1} # 1=local concurrent with remotes | |
| local timeout=${ALL_TIMEOUT:-8} | |
| local pty=${ALL_PTY:-0} | |
| local sudo_n=${ALL_SUDO_N:-1} | |
| local prefix=${ALL_PREFIX:-1} # kept for backward compatibility | |
| local show_ok=${ALL_SHOW_OK:-0} # 1=print OK lines in summary | |
| # suppress bash job-control noise | |
| set +m 2>/dev/null || true | |
| set +o notify 2>/dev/null || true | |
| _hdr() { printf '===== %s =====\n' "$1"; } | |
| # expand "~" at beginning (supports ~ and ~/ ; ~user/ optional) | |
| _expand_path() { | |
| local p="$1" | |
| case "$p" in | |
| "~") printf '%s\n' "$HOME" ;; | |
| "~/"*) printf '%s\n' "$HOME/${p:2}" ;; | |
| "~"[^/]*/*) | |
| local u="${p%%/*}"; u="${u:1}" | |
| local rest="${p#*/}" | |
| local uh | |
| uh="$(getent passwd "$u" 2>/dev/null | awk -F: '{print $6}' | head -n1)" | |
| [[ -n "$uh" ]] && printf '%s\n' "$uh/$rest" || printf '%s\n' "$p" | |
| ;; | |
| *) printf '%s\n' "$p" ;; | |
| esac | |
| } | |
| # ---- hosts ---- | |
| local -a hosts | |
| if [[ -n "${ALL_HOSTS-}" ]]; then | |
| read -r -a hosts <<<"$ALL_HOSTS" | |
| else | |
| hosts=(192.168.2.5 192.168.2.75) | |
| fi | |
| # ---- ssh opts ---- | |
| local -a ssh_opts=() | |
| if [[ -n "${ALL_SSH_OPTS-}" ]]; then | |
| read -r -a ssh_opts <<<"$ALL_SSH_OPTS" | |
| fi | |
| local -a base_ssh=(ssh -o BatchMode=yes -o ConnectTimeout="$timeout") | |
| (( pty )) && base_ssh+=( -tt ) | |
| base_ssh+=( "${ssh_opts[@]}" ) | |
| # ---- mode parse ---- | |
| local mode="argv" # argv|file|stdin | |
| local script_path="" script_blob="" | |
| local -a argv=() | |
| if (( $# == 0 )); then | |
| if ! -t 0; then | |
| mode="stdin" | |
| script_blob="$(cat)" | |
| else | |
| echo "Usage:" >&2 | |
| echo " all <command...>" >&2 | |
| echo " all @/path/to/script.sh" >&2 | |
| echo " all <<'SH' ... SH" >&2 | |
| echo " cat script.sh | all" >&2 | |
| echo "Env: ALL_HOSTS=\"h1 h2\" ALL_PARALLEL=1 ALL_TIMEOUT=8 ALL_SSH_OPTS=\"...\" ALL_PTY=1 ALL_SUDO_N=1 ALL_LOCAL=1" >&2 | |
| return 1 | |
| fi | |
| else | |
| if [[ "$1" == @* ]]; then | |
| mode="file" | |
| script_path="$(_expand_path "${1#@}")" | |
| [[ -f "$script_path" ]] || { echo "ERROR: script file not found: $script_path" >&2; return 2; } | |
| script_blob="$(cat "$script_path")" | |
| shift || true | |
| (( $# == 0 )) || { echo "ERROR: extra args after @file are not supported" >&2; return 2; } | |
| else | |
| mode="argv" | |
| argv=( "$@" ) | |
| if (( sudo_n )) && [[ "${argv[0]}" == "sudo" ]]; then | |
| case " ${argv[*]} " in | |
| *" sudo -n "*|*" sudo -S "*|*" sudo -A "* ) : ;; | |
| * ) argv=( sudo -n "${argv[@]:1}" ) ;; | |
| esac | |
| fi | |
| fi | |
| fi | |
| # ---- build remote cmd (argv mode) ---- | |
| local remote_cmd="" bash_lc="" | |
| if [[ "$mode" == "argv" ]]; then | |
| remote_cmd="$(printf '%q ' "${argv[@]}")" | |
| bash_lc="$(printf '%q' "$remote_cmd")" | |
| fi | |
| # ---- runners ---- | |
| _run_local() { | |
| if [[ "$mode" == "argv" ]]; then | |
| "${argv[@]}" | |
| else | |
| bash -s <<<"$script_blob" | |
| fi | |
| } | |
| _run_remote() { | |
| local host="$1" | |
| if [[ "$mode" == "argv" ]]; then | |
| "${base_ssh[@]}" "$host" "bash -lc $bash_lc" | |
| else | |
| printf '%s' "$script_blob" | "${base_ssh[@]}" "$host" "bash -s" | |
| fi | |
| } | |
| _print_block() { | |
| local title="$1" rc="$2" file="$3" status | |
| if (( rc == 0 )); then | |
| status="OK" | |
| else | |
| status="FAIL:$rc" | |
| fi | |
| printf '\n===== %s [%s] =====\n' "$title" "$status" | |
| if [[ -s "$file" ]]; then | |
| cat "$file" | |
| else | |
| printf '(no output)\n' | |
| fi | |
| } | |
| # ---- exec ---- | |
| local tmpdir | |
| tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/all.XXXXXX")" || return 1 | |
| trap 'rm -rf "$tmpdir"' RETURN | |
| local -a pids=() names=() titles=() files=() rcs=() | |
| local rc any_fail=0 | |
| _spawn() { | |
| local name="$1" title="$2" out="$3"; shift 3 | |
| local pid | |
| { ( "$@" >"$out" 2>&1 ) & pid=$!; } 2>/dev/null | |
| pids+=( "$pid" ) | |
| names+=( "$name" ) | |
| titles+=( "$title" ) | |
| files+=( "$out" ) | |
| } | |
| if (( parallel )); then | |
| if (( run_local && local_parallel )); then | |
| _spawn "LOCAL" "LOCAL ($(hostname))" "$tmpdir/LOCAL.log" _run_local | |
| fi | |
| local host | |
| for host in "${hosts[@]}"; do | |
| _spawn "$host" "$host" "$tmpdir/$host.log" _run_remote "$host" | |
| done | |
| local i | |
| for i in "${!pids[@]}"; do | |
| wait "${pids[$i]}"; rc=$? | |
| rcs+=( "$rc" ) | |
| (( rc != 0 )) && any_fail=1 | |
| done | |
| if (( run_local && ! local_parallel )); then | |
| names+=( "LOCAL" ) | |
| titles+=( "LOCAL ($(hostname))" ) | |
| files+=( "$tmpdir/LOCAL.log" ) | |
| _run_local >"${files[-1]}" 2>&1; rc=$? | |
| rcs+=( "$rc" ) | |
| (( rc != 0 )) && any_fail=1 | |
| fi | |
| else | |
| if (( run_local )); then | |
| names+=( "LOCAL" ) | |
| titles+=( "LOCAL ($(hostname))" ) | |
| files+=( "$tmpdir/LOCAL.log" ) | |
| _run_local >"${files[-1]}" 2>&1; rc=$? | |
| rcs+=( "$rc" ) | |
| (( rc != 0 )) && any_fail=1 | |
| fi | |
| local host | |
| for host in "${hosts[@]}"; do | |
| names+=( "$host" ) | |
| titles+=( "$host" ) | |
| files+=( "$tmpdir/$host.log" ) | |
| _run_remote "$host" >"${files[-1]}" 2>&1; rc=$? | |
| rcs+=( "$rc" ) | |
| (( rc != 0 )) && any_fail=1 | |
| done | |
| fi | |
| # ---- output blocks ---- | |
| local i | |
| for i in "${!names[@]}"; do | |
| _print_block "${titles[$i]}" "${rcs[$i]}" "${files[$i]}" | |
| done | |
| # ---- summary ---- | |
| local name | |
| for i in "${!names[@]}"; do | |
| rc="${rcs[$i]}" | |
| name="${names[$i]}" | |
| if (( rc != 0 )); then | |
| printf 'FAIL (%s): exit=%s\n' "$name" "$rc" >&2 | |
| elif (( show_ok )); then | |
| printf 'OK (%s)\n' "$name" | |
| fi | |
| done | |
| (( any_fail )) && return 1 | |
| return 0 | |
| } | |
| # --- helpers for your ansible-sync project ----------------------------------- | |
| all_add() { | |
| local path="$1" | |
| [[ -n "$path" ]] || { echo "Usage: all_add /absolute/path"; return 2; } | |
| [[ "$path" == /* ]] || { echo "ERROR: use absolute path"; return 2; } | |
| [[ -e "$path" ]] || { echo "ERROR: not found: $path"; return 2; } | |
| local proj="$HOME/ansible-sync" | |
| [[ -d "$proj" ]] || { echo "ERROR: $proj not found"; return 2; } | |
| echo "$path" >> "$proj/sync_paths.txt" | |
| echo "Registered: $path" | |
| ( cd "$proj" && ./bin_sync.sh ) | |
| } | |
| all_deploy() { | |
| local proj="$HOME/ansible-sync" | |
| [[ -d "$proj" ]] || { echo "ERROR: $proj not found"; return 2; } | |
| ( cd "$proj" && ./bin_sync.sh ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment