#!/bin/bash # cluster-gateway.sh — M3U96a comme porte d'entrée (staging) du cluster MacLustr. # # Principe : on pousse un projet UNE fois laptop -> M3U96a (~/cluster-projects//), # puis M3U96a le diffuse aux autres noeuds en parallele via le reseau local (Tailscale/LAN). # Le meme chemin ~/cluster-projects// existe ainsi sur tous les noeuds cibles. # # Usage : # cluster-gateway.sh stage [nom] # laptop -> M3U96a # cluster-gateway.sh fanout [noeud ...] # M3U96a -> noeuds (defaut: tous sauf M3U96a & M2U64) # cluster-gateway.sh push [nom] # stage + fanout (defaut) # cluster-gateway.sh list # projets stages sur M3U96a # cluster-gateway.sh where # sur quels noeuds le projet est present # cluster-gateway.sh clean [--everywhere] # efface de M3U96a (et de tous les noeuds) # # Options : --include-m2u64 (fanout/push : inclure le noeud prod M2U64) # Doc complete : ~/Desktop/cluster-skill/GATEWAY.md set -euo pipefail GATEWAY="M3U96a" DOMAIN="maclustr.io" STAGE_DIR="cluster-projects" LAN_PREFIX_RE='192\.168\.2\.' # sous-reseau LAN du cluster ALL_NODES="M3U96b M2U64 M4M64a M4M64b M4BP48 M4BP36 M4M36 M2M32 M2M32b M2M32c m4mc M1M32 m4ma m4mb m2m16 M3BA24 M3BA16 m2m8a m2m8b" usage() { sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 1; } CMD="${1:-}"; shift || true INCLUDE_M2U64=0 ARGS=() for a in "$@"; do case "$a" in --include-m2u64) INCLUDE_M2U64=1 ;; *) ARGS+=("$a") ;; esac done set -- "${ARGS[@]:-}" default_targets() { local out="" for n in $ALL_NODES; do [ "$n" = "M2U64" ] && [ "$INCLUDE_M2U64" -eq 0 ] && continue out="$out $n" done echo "$out" } do_stage() { local src="${1:?usage: stage [nom]}" src="${src%/}" [ -d "$src" ] || { echo "ERREUR: $src n'est pas un dossier" >&2; exit 2; } local name="${2:-$(basename "$src")}" echo ">> stage: $src -> $GATEWAY:~/$STAGE_DIR/$name/" rsync -az --delete --rsync-path="mkdir -p $STAGE_DIR && rsync" \ "$src/" "$GATEWAY:$STAGE_DIR/$name/" echo "OK stage $name ($(du -sh "$src" | cut -f1))" } do_fanout() { local name="${1:?usage: fanout [noeud ...]}"; shift || true local nodes="${*:-$(default_targets)}" # Le SSH inter-noeuds via Tailscale est filtre (ACL) : le fanout passe par le LAN # 192.168.2.x. Les IP LAN sont en DHCP -> on les resout en live depuis le laptop. echo ">> resolution des IP LAN des cibles ($(echo $nodes | wc -w | tr -d ' ') noeuds)..." local tmp; tmp=$(mktemp -d) local n for n in $nodes; do ( ip=$(ssh -n -o ConnectTimeout=8 "$n" \ "ifconfig | awk '/inet $LAN_PREFIX_RE/{print \$2; exit}'" 2>/dev/null || true) echo "${ip:-NONE}" >"$tmp/$n" ) & done wait local pairs="" for n in $nodes; do pairs="$pairs $n=$(cat "$tmp/$n" 2>/dev/null || echo NONE)"; done rm -rf "$tmp" echo ">> fanout depuis $GATEWAY via LAN: $name ->$(echo "$pairs" | tr ' ' '\n' | grep -c '=' | xargs echo) noeuds" # shellcheck disable=SC2087 ssh -A "$GATEWAY" "NAME='$name' PAIRS='$pairs' bash -s" <<'REMOTE' set -u SRC="$HOME/cluster-projects/$NAME/" [ -d "$SRC" ] || { echo "ERREUR: $SRC absent sur la passerelle — faire stage d abord" >&2; exit 2; } TMP=$(mktemp -d) for pair in $PAIRS; do n="${pair%%=*}"; ip="${pair##*=}" if [ "$ip" = "NONE" ]; then echo "FAIL (hors LAN cluster ou injoignable)" >"$TMP/$n"; continue; fi ( rsync -az --delete \ -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8" \ --rsync-path="mkdir -p cluster-projects && rsync" \ "$SRC" "$ip:cluster-projects/$NAME/" \ >/dev/null 2>"$TMP/$n.err" && echo "OK ($ip)" >"$TMP/$n" || echo "FAIL ($ip)" >"$TMP/$n" ) & done wait FAILED=0 for pair in $PAIRS; do n="${pair%%=*}" st=$(cat "$TMP/$n" 2>/dev/null || echo FAIL) printf " %-8s %s\n" "$n" "$st" case "$st" in FAIL*) FAILED=1; [ -f "$TMP/$n.err" ] && sed 's/^/ /' "$TMP/$n.err" | grep -v "known hosts" | head -2 ;; esac done rm -rf "$TMP" exit $FAILED REMOTE echo "OK fanout $name termine" } do_list() { echo ">> projets stages sur $GATEWAY:~/$STAGE_DIR/" ssh "$GATEWAY" "du -sh $STAGE_DIR/*/ 2>/dev/null || echo ' (aucun projet stage)'" } do_where() { local name="${1:?usage: where }" echo ">> presence de '$name' sur le cluster :" for n in $GATEWAY $ALL_NODES; do ( sz=$(ssh -o ConnectTimeout=6 "$n.$DOMAIN" "du -sh $STAGE_DIR/$name 2>/dev/null | cut -f1" 2>/dev/null) printf " %-8s %s\n" "$n" "${sz:-—}" ) & done wait } do_clean() { local name="${1:?usage: clean [--everywhere]}" local everywhere=0 [ "${2:-}" = "--everywhere" ] && everywhere=1 if [ "$everywhere" -eq 1 ]; then echo ">> clean '$name' sur TOUS les noeuds" for n in $GATEWAY $ALL_NODES; do ( ssh -o ConnectTimeout=6 "$n.$DOMAIN" "rm -rf $STAGE_DIR/$name" 2>/dev/null \ && printf " %-8s OK\n" "$n" || printf " %-8s (injoignable)\n" "$n" ) & done wait else echo ">> clean '$name' sur $GATEWAY uniquement" ssh "$GATEWAY" "rm -rf $STAGE_DIR/$name" && echo " $GATEWAY OK" fi } case "$CMD" in stage) do_stage "$@" ;; fanout) do_fanout "$@" ;; push) do_stage "$1" "${2:-}"; do_fanout "${2:-$(basename "${1%/}")}" ;; list) do_list ;; where) do_where "$@" ;; clean) do_clean "$@" ;; *) usage ;; esac