diff --git a/dot_config/private_fish/functions/__zmx_choose_session.fish b/dot_config/private_fish/functions/__zmx_choose_session.fish new file mode 100644 index 0000000000000000000000000000000000000000..d053c1ae941ecc1887cbfa1820cefeb530196700 --- /dev/null +++ b/dot_config/private_fish/functions/__zmx_choose_session.fish @@ -0,0 +1,49 @@ +function __zmx_choose_session --description "Choose or create a zmx session name with fzf" + argparse 'p/preview=' -- $argv + or return 1 + + if not type -q fzf + echo "__zmx_choose_session: fzf is not installed or not on PATH" >&2 + return 127 + end + + set -l preview_command 'zmx history {1}' + if set -q _flag_preview + set preview_command $_flag_preview + end + + set -l display + read -lz display + + set -l output_file (mktemp) + begin + if test -n "$display" + printf "%s" $display + end + end | fzf \ + --print-query \ + --expect=ctrl-n \ + --height=80% \ + --reverse \ + --prompt="zmx> " \ + --header="Enter: select | Ctrl-N: create new" \ + --preview=$preview_command \ + --preview-window=right:60%:follow \ + >$output_file + + set -l fzf_status $status + set -l query (sed -n '1p' $output_file) + set -l key (sed -n '2p' $output_file) + set -l selected (sed -n '3p' $output_file) + rm -f $output_file + + if test "$key" = ctrl-n; and test -n "$query" + echo $query + else if test $fzf_status -eq 0; and test -n "$selected" + string split -f1 ' ' -- $selected + else if test -n "$query" + echo $query + else + return 130 + end +end diff --git a/dot_config/private_fish/functions/__zmx_format_sessions.fish b/dot_config/private_fish/functions/__zmx_format_sessions.fish new file mode 100644 index 0000000000000000000000000000000000000000..4d2ad341769944d536eaf791a75a4e68f0824f45 --- /dev/null +++ b/dot_config/private_fish/functions/__zmx_format_sessions.fish @@ -0,0 +1,16 @@ +function __zmx_format_sessions --description "Format zmx list output for fzf" + while read -l line + set -l fields (string split \t -- $line) + + if test (count $fields) -lt 5 + continue + end + + set -l name (string replace -r '^session_name=' '' -- $fields[1]) + set -l pid (string replace -r '^pid=' '' -- $fields[2]) + set -l clients (string replace -r '^clients=' '' -- $fields[3]) + set -l dir (string replace -r '^started_in=' '' -- $fields[5]) + + printf "%-20s pid:%-8s clients:%-2s %s\n" $name $pid $clients $dir + end +end diff --git a/dot_config/private_fish/functions/zmx-remote-install.fish b/dot_config/private_fish/functions/zmx-remote-install.fish new file mode 100644 index 0000000000000000000000000000000000000000..e3bf72a3865b52873260182557b91d9abc2ca54a --- /dev/null +++ b/dot_config/private_fish/functions/zmx-remote-install.fish @@ -0,0 +1,159 @@ +function zmx-remote-install --description "Install zmx on a remote host by streaming a locally downloaded release tarball" + argparse 'h/help' 'upgrade-only' 'v/version=' -- $argv + or return 1 + + if set -q _flag_help; or test (count $argv) -ne 1 + echo "Usage: zmx-remote-install [--upgrade-only] [--version VERSION] [user@]host" + echo "Example: zmx-remote-install hel1" + echo "By default, installs the active local zmx version." + echo "With --upgrade-only, only upgrades an existing remote zmx that is older than local." + return 0 + end + + set -l destination $argv[1] + set -l desired_version + + if set -q _flag_version + set desired_version $_flag_version + else if type -q zmx + set desired_version (zmx version | awk 'NR == 1 {print $2}') + else if type -q mise + set desired_version (mise exec -- zmx version | awk 'NR == 1 {print $2}') + end + + if test -z "$desired_version" + echo "zmx-remote-install: could not determine local zmx version from zmx or mise" >&2 + return 1 + end + + mkdir -p "$HOME/.ssh/controlmasters" + + set -l remote_version (command ssh \ + -o ControlMaster=auto \ + -o ControlPersist=10m \ + -o ControlPath="$HOME/.ssh/controlmasters/%C" \ + $destination \ + "sh -c 'PATH=\"\$HOME/.local/bin:\$PATH\"; if command -v zmx >/dev/null 2>&1; then zmx version | awk \"NR == 1 {print \\\$2}\"; fi'") + or return $status + + if test -z "$remote_version" + if set -q _flag_upgrade_only + return 0 + end + + echo "Remote zmx is not installed; installing local zmx $desired_version..." >&2 + else if test "$remote_version" = "$desired_version" + return 0 + else + if not type -q semver + echo "zmx-remote-install: semver is required to compare local zmx $desired_version with remote zmx $remote_version" >&2 + return 1 + end + + if not semver "$desired_version" >/dev/null 2>&1; or not semver "$remote_version" >/dev/null 2>&1 + echo "zmx-remote-install: cannot compare local zmx $desired_version with remote zmx $remote_version; refusing to sidegrade" >&2 + return 1 + end + + if not semver -r ">$remote_version" "$desired_version" >/dev/null 2>&1 + return 0 + end + + echo "Remote zmx is $remote_version; upgrading to local zmx $desired_version..." >&2 + end + + set -l remote_platform (command ssh \ + -o ControlMaster=auto \ + -o ControlPersist=10m \ + -o ControlPath="$HOME/.ssh/controlmasters/%C" \ + $destination \ + "sh -c 'printf \"%s %s\\n\" \"\$(uname -s)\" \"\$(uname -m)\"'") + or return $status + + set -l remote_os (string split ' ' -- $remote_platform)[1] + set -l remote_arch (string split ' ' -- $remote_platform)[2] + set -l zmx_os + set -l zmx_arch + + switch $remote_os + case Linux + set zmx_os linux + case Darwin + set zmx_os macos + case '*' + echo "zmx-remote-install: unsupported remote OS '$remote_os'" >&2 + return 2 + end + + switch $remote_arch + case x86_64 amd64 + set zmx_arch x86_64 + case aarch64 arm64 + set zmx_arch aarch64 + case '*' + echo "zmx-remote-install: unsupported remote architecture '$remote_arch'" >&2 + return 2 + end + + set -l archive "zmx-$desired_version-$zmx_os-$zmx_arch.tar.gz" + set -l base_url "https://zmx.sh/a" + set -l temp_dir (mktemp -d) + set -l archive_path "$temp_dir/$archive" + set -l checksum_path "$archive_path.sha256" + + if test -z "$temp_dir" + echo "zmx-remote-install: failed to create a temporary directory" >&2 + return 1 + end + + echo "Downloading $archive..." >&2 + if not curl -fsSL "$base_url/$archive" -o "$archive_path" + rm -rf $temp_dir + return 1 + end + + if curl -fsSL "$base_url/$archive.sha256" -o "$checksum_path" + set -l expected (awk '{print $1}' "$checksum_path") + set -l actual + + if type -q sha256sum + set actual (sha256sum "$archive_path" | awk '{print $1}') + else if type -q shasum + set actual (shasum -a 256 "$archive_path" | awk '{print $1}') + else + echo "zmx-remote-install: no local sha256sum or shasum found; refusing to install unverified archive" >&2 + rm -rf $temp_dir + return 1 + end + + if test "$expected" != "$actual" + echo "zmx-remote-install: checksum verification failed for $archive" >&2 + rm -rf $temp_dir + return 1 + end + else + echo "zmx-remote-install: could not download checksum for $archive; refusing to install unverified archive" >&2 + rm -rf $temp_dir + return 1 + end + + echo "Installing zmx $desired_version on $destination..." >&2 + if not command ssh \ + -o ControlMaster=auto \ + -o ControlPersist=10m \ + -o ControlPath="$HOME/.ssh/controlmasters/%C" \ + $destination \ + "sh -c 'mkdir -p \"\$HOME/.local/bin\" && tmp=\$(mktemp \"\$HOME/.local/bin/.zmx.XXXXXX\") && tar -xOzf - zmx > \"\$tmp\" && chmod 755 \"\$tmp\" && mv \"\$tmp\" \"\$HOME/.local/bin/zmx\"'" <"$archive_path" + rm -rf $temp_dir + return 1 + end + + rm -rf $temp_dir + + command ssh \ + -o ControlMaster=auto \ + -o ControlPersist=10m \ + -o ControlPath="$HOME/.ssh/controlmasters/%C" \ + $destination \ + "sh -c 'PATH=\"\$HOME/.local/bin:\$PATH\"; zmx version'" +end diff --git a/dot_config/private_fish/functions/zmx-select.fish b/dot_config/private_fish/functions/zmx-select.fish new file mode 100644 index 0000000000000000000000000000000000000000..e58447c763c7aa6a0f75755e86baab5d8d77d768 --- /dev/null +++ b/dot_config/private_fish/functions/zmx-select.fish @@ -0,0 +1,11 @@ +function zmx-select --description "Fuzzy-find or create a zmx session" + if not type -q zmx + echo "zmx-select: zmx is not installed or not on PATH" >&2 + return 127 + end + + set -l session_name (zmx list 2>/dev/null | __zmx_format_sessions | __zmx_choose_session --preview 'zmx history {1}') + or return $status + + zmx attach $session_name +end diff --git a/dot_config/private_fish/functions/zssh-select.fish b/dot_config/private_fish/functions/zssh-select.fish new file mode 100644 index 0000000000000000000000000000000000000000..080212efb2b6e11cc74da0d865fba9d1b21091f9 --- /dev/null +++ b/dot_config/private_fish/functions/zssh-select.fish @@ -0,0 +1,41 @@ +function zssh-select --description "Fuzzy-find or create a zmx session on a remote host" + argparse 'h/help' 'install' -- $argv + or return 1 + + if set -q _flag_help; or test (count $argv) -ne 1 + echo "Usage: zssh-select [--install] [user@]host" + echo "Example: zssh-select hel1" + return 0 + end + + set -l destination $argv[1] + + if not string match -qr '^[A-Za-z0-9._@:%+-]+$' -- $destination + echo "zssh-select: destination contains characters that cannot be safely used in the preview command" >&2 + return 2 + end + + mkdir -p "$HOME/.ssh/controlmasters" + + if set -q _flag_install + zmx-remote-install $destination + or return $status + else + zmx-remote-install --upgrade-only $destination + or return $status + end + + set -l remote_list (command ssh \ + -o ControlMaster=auto \ + -o ControlPersist=10m \ + -o ControlPath="$HOME/.ssh/controlmasters/%C" \ + $destination \ + "sh -c 'PATH=\"\$HOME/.local/bin:\$PATH\"; command -v zmx >/dev/null 2>&1 || { echo \"zssh-select: zmx is not installed on the remote host or is not on PATH; retry with zssh-select --install\" >&2; exit 127; }; zmx list 2>/dev/null'") + or return $status + + set -l preview_command "ssh -o ControlMaster=auto -o ControlPersist=10m -o ControlPath=$HOME/.ssh/controlmasters/%C $destination 'sh -c '\''PATH=\"\$HOME/.local/bin:\$PATH\"; zmx history {1}'\'' '" + set -l session_name (printf '%s\n' $remote_list | __zmx_format_sessions | __zmx_choose_session --preview $preview_command) + or return $status + + zssh $destination $session_name +end diff --git a/dot_config/private_fish/functions/zssh.fish b/dot_config/private_fish/functions/zssh.fish new file mode 100644 index 0000000000000000000000000000000000000000..407f732cbebcec4e7ce10698d98f6d3a9ab5232e --- /dev/null +++ b/dot_config/private_fish/functions/zssh.fish @@ -0,0 +1,42 @@ +function zssh --description "SSH to a host and attach to a remote zmx session" + argparse 'h/help' 'install' -- $argv + or return 1 + + if set -q _flag_help; or test (count $argv) -lt 1 + echo "Usage: zssh [--install] [user@]host [session]" + echo "Example: zssh devbox term" + return 0 + end + + set -l destination $argv[1] + set -l session main + + if test (count $argv) -ge 2 + set session $argv[2] + end + + if not string match -qr '^[A-Za-z0-9._:-]+$' -- $session + echo "zssh: session names may only contain letters, numbers, dots, underscores, colons, and hyphens" >&2 + return 2 + end + + mkdir -p "$HOME/.ssh/controlmasters" + + if set -q _flag_install + zmx-remote-install $destination + or return $status + else + zmx-remote-install --upgrade-only $destination + or return $status + end + + set -l remote_command "sh -c 'PATH=\"\$HOME/.local/bin:\$PATH\"; command -v zmx >/dev/null 2>&1 || { echo \"zssh: zmx is not installed on the remote host or is not on PATH; retry with zssh --install\" >&2; exit 127; }; exec zmx attach \"\$1\"' sh '$session'" + + command ssh \ + -o ControlMaster=auto \ + -o ControlPersist=10m \ + -o ControlPath="$HOME/.ssh/controlmasters/%C" \ + -o RequestTTY=yes \ + -o RemoteCommand="$remote_command" \ + $destination +end