# netssh multi with real tmux and zellij: selectors against NetBox, panes, # broadcast typing reaching every host, netssh sync, zellij layouts and tabs. config <<'EOF' resolver: strategies: [management_subnet, primary_ip] management_subnets: [10.231.1.0/24] ssh: default_user: ops rules: - match: {kind: vm, instance: fsn} proxy_jump: ops@10.231.0.30 groups: pve: ["cluster:pve-fsn1"] mixed: ["@pve", "lab/lab-db1", "tag:web"] EOF check "refresh" netssh cache refresh nodes="fsn/fsn1-pve1.hoster.test fsn/fsn1-pve2.hoster.test fsn/fsn1-pve3.hoster.test" expect_out "cluster: selects the devices" "# 3 hosts: $nodes" netssh multi --dry-run --tmux cluster:pve-fsn1 expect_out "--kind vm selects the VMs" "# 2 hosts: fsn/web1 fsn/web2" netssh multi --dry-run --tmux --kind vm cluster:pve-fsn1 expect_out "groups, tags, and hosts combined" "# 7 hosts: $nodes lab/lab-db1 fsn/web1 fsn/web2 lab/web1" netssh multi --dry-run --tmux @mixed expect_fail "unknown cluster" "matches no hosts" netssh multi --dry-run --tmux cluster:nope expect_out "completion offers clusters" "cluster:pve-fsn1" netssh __complete multi cluster: expect_out "completion offers groups" "@pve" netssh __complete multi @ export TMUX_TMPDIR="$HOME/tmux" mkdir -p "$TMUX_TMPDIR" "$HOME/.config/zellij" # No first-start tips or release notes on top of the panes. printf 'show_startup_tips false\nshow_release_notes false\n' >"$HOME/.config/zellij/config.kdl" envs=(-e HOME="$HOME" -e XDG_CONFIG_HOME="$XDG_CONFIG_HOME" -e XDG_CACHE_HOME="$XDG_CACHE_HOME") hosts_of() { tmux list-panes -t "$1" -F '#{@netssh_host}' 2>/dev/null | sort | tr '\n' ' '; } # wait_for retries a command, so checks must re-evaluate on every call. panes_in() { [ "$(tmux list-panes -t "$1" 2>/dev/null | wc -l)" = "$2" ]; } sync_is() { [ "$(tmux show-window-options -t "$1" -v synchronize-panes 2>/dev/null)" = "$2" ]; } prompts() { [ "$(tmux capture-pane -p -t z | grep -o 'fsn1-pve[123]:~\$' | sort -u | wc -l)" = 3 ]; } remote_file() { for n in 1 2 3; do netssh "fsn/fsn1-pve$n.hoster.test" test -f "$1" || return 1; done; } # Inside tmux: a new window in the current session, broadcast from the start. tmux new-session -d -s outer -x 200 -y 60 "${envs[@]}" bash tmux send-keys -t outer "netssh multi --sync @pve" Enter if wait_for 20 panes_in outer:pve 3; then ok "tmux: three panes in a new window"; else bad "tmux: three panes in a new window" "$(tmux list-windows -a)"; fi expect_eq "tmux: panes are labelled" "$nodes " hosts_of outer:pve expect_eq "tmux: broadcast is on" "on" tmux show-window-options -t outer:pve -v synchronize-panes sleep 2 marker="/tmp/netssh-multi-$$" tmux send-keys -t outer:pve "touch $marker" Enter if wait_for 15 remote_file "$marker"; then ok "tmux: one command reached all three hosts"; else bad "tmux: one command reached all three hosts" "$(tmux capture-pane -p -t outer:pve)"; fi tmux new-window -t outer -n ctl "${envs[@]}" bash tmux send-keys -t outer:ctl "netssh sync" Enter if wait_for 10 sync_is outer:ctl on; then ok "netssh sync toggles broadcast"; else bad "netssh sync toggles broadcast" "$(tmux capture-pane -p -t outer:ctl)"; fi # Outside tmux: a new session, attached. tmux send-keys -t outer:ctl "netssh sync; env -u TMUX netssh multi --kind vm cluster:pve-fsn1" Enter if wait_for 20 panes_in netssh-pve-fsn1 2; then ok "tmux: new session outside tmux"; else bad "tmux: new session outside tmux" "$(tmux list-sessions)"; fi expect_eq "tmux: VMs through the bastion" "fsn/web1 fsn/web2 " hosts_of netssh-pve-fsn1 tmux kill-server # zellij outside a session: netssh starts zellij with a grid layout. tmux new-session -d -s z -x 200 -y 60 "${envs[@]}" bash tmux send-keys -t z "netssh multi --zellij @pve" Enter zscreen() { tmux capture-pane -p -t z; } if wait_for 30 prompts; then ok "zellij: three panes with remote shells"; else bad "zellij: three panes with remote shells" "$(zscreen)"; fi expect_out "zellij: pane titles" "fsn/fsn1-pve2.hoster.test" zscreen zsession() { zellij list-sessions --short --no-formatting 2>/dev/null | head -1 | grep .; } if wait_for 10 zsession; then ok "zellij: session is running"; else bad "zellij: session is running" "$(zellij list-sessions 2>&1)"; fi session=$(zsession) # zellij inside that session: netssh multi adds a tab. ZELLIJ_SESSION_NAME=$session ZELLIJ=0 netssh multi --kind vm cluster:pve-fsn1 >/dev/null 2>&1 expect_out "zellij: new tab inside a session" "pve-fsn1" zellij --session "$session" action query-tab-names expect_out "zellij: tab layout runs netssh for the VMs" "fsn/web2" zellij --session "$session" action dump-layout zellij kill-all-sessions --yes >/dev/null 2>&1 tmux kill-server 2>/dev/null true