visual-studio-codesshiterm2vscode-remoteiterm

iTerm2: How can I trigger a local command from a remote session?


iTerm2 shell integration has some neat tricks, such as its it2copy command, which copies into the local clipboard, even if I'm logged into a remote machine via ssh.

Can it be used to run arbitrary shell commands?

For instance, while I'm logged in over ssh, I want to execute a command to open an editor on my local machine. VSCode can open a remote directory with this command:

code --remote ssh-remote+myserver /home/stuart/some-directory

I want to trigger that command locally, from an ssh session on the remote machine.


PS -- I know there's an alternative: Create a (nested) ssh connection back to my local machine to execute the commands over ssh, rather than using iTerm2's backchannel. But that has various downsides, hence this question.

I'm also aware of the PermitLocalCommand option in ~/.ssh/config, which allows me to send an escape code (~C), followed by a local command (!code --remote ...). But I'm hoping for a solution I can use in a script or bash alias.

For instance, if it2local existed, I would use it like this:

alias code_here='it2local "code --remote ssh-remote+$(uname -n) $(pwd)"'

If that is possible with ssh alone, I'd love to hear about it.


Solution

  • The proper way to do this is via iTerm2 Triggers, which can run an arbitrary command (among other options) whenever a specific pattern appears in your terminal output.

    The hypothetical it2local command I described above would just have to echo some predefined trigger pattern to your terminal, along with the command you want to execute.

    In my case, I didn't implement the generic it2local command. (Maybe I'll update this answer later.) For now, I've implemented a script that serves my specific use-case: Opening a remote file with VSCode. The code I'm using is shown below.

    #!/bin/sh
    
    #
    # This file contains the code and instructions to set up an iTerm2 "Trigger" from a
    # remote ssh session that will open up VSCode on your local machine to edit a
    # file on the remote server over ssh.
    #
    # Author: Stuart Berg
    #         https://github.com/stuarteberg
    #         bergs@janelia.hhmi.org
    #         https://stackoverflow.com/questions/61699447
    
    # SETUP OVERVIEW
    # --------------
    # - Install the VS Code Remote Development Extension Pack
    # - Ideally, setup passwordless ssh access to the remote machines you want to access
    # - Place this script somewhere on your local machine (and make sure it's executable).
    # - Copy the localcode() shell function below into your remote machine's .bashrc
    # - Define the Trigger in iTerm2 as defined below.
    #
    # Notes:
    #   Docs for iTerm2 Triggers: https://iterm2.com/documentation-triggers.html
    #   Docs for VSCode Remote Extension: https://code.visualstudio.com/docs/remote/remote-overview
    #   - CLI: https://github.com/microsoft/vscode-remote-release/issues/585#issuecomment-536580102
    
    # iTerm2 Preferences Setup
    # ------------------------
    #
    # In your iTerm2 preferences, set up a Trigger (Profiles > Advanced > Triggers > Edit)
    #
    # Regular Expression:  .*ITERM-TRIGGER-open-with-local-vscode-remote ([^ ]+) ([^ ]+) (([^ ]+ ?)+)
    #             Action:  Run Command...
    #         Parameters:  /path/to/this/script \1
    #
    # Tip: For additional feedback, try adding a duplicate entry with a "Post Notifcation" action.
    
    # HOW TO TEST
    # -----------
    #
    # NOTE: The new trigger will not be active for already-open terminal sessions.
    #       Open a new terminal after you add the trigger to your preferences.
    #
    # To test it, ssh into the remote machine, and try the 'localcode' function:
    #
    #   localcode .
    #   localcode /some/dir
    #   localcode /some/file
    #   localcode /some/file remote-machine-name
    #
    # If something is going wrong, inspect /tmp/iterm-vscode-trigger.log
    
    #
    # Put this in your remote ~/.bashrc
    #
    
    # Set this to the name of your remote machine,
    # which will be serving the files you want to edit.
    # (In my case, the machine is named 'submit'.)
    DEFAULT_REMOTE_MACHINE_FOR_VSCODE=submit
    
    localcode() (
        # Tell zsh to use bash-style arrays
        setopt ksh_arrays 2> /dev/null || true
    
        CMD=ITERM-TRIGGER-open-with-local-vscode-remote
        MACHINE=${LOCALCODE_MACHINE-${DEFAULT_REMOTE_MACHINE_FOR_VSCODE}}
        FILENAMES=( "$@" )
    
        if [[ ${#FILENAMES[@]} == 0 ]]; then
            FILENAMES=.
        fi
    
        if [[ ${#FILENAMES[@]} == 1 && -d ${FILENAMES[0]} ]]; then
                FILENAMES[0]=$(cd ${FILENAMES[0]}; pwd)
                FTYPE=directory
        else
            # Convert filenames to abspaths
            for (( i=0; i < ${#FILENAMES[@]}; i++ )); do
                FN=${FILENAMES[i]}
                if [[ -f ${FN} ]]; then
                    DIRNAME=$(cd $(dirname ${FN}); pwd)
                    FILENAMES[i]=${DIRNAME}/$(basename ${FN})
                    FTYPE=file
                else
                    1>&2 echo "Not a valid file: ${FN}"
                    exit 1
                fi
            done
        fi
    
        echo ${CMD} ${FTYPE} ${MACHINE} ${FILENAMES[@]}
    )
    export -f localcode
    
    #
    # Copy this whole file onto your local machine, or at least the following lines.
    # Make sure it is executable (chmod +x /path/to/this/script)
    #
    trigger_vscode_remote_editing() (
        # Tell zsh to use bash-style arrays
        setopt ksh_arrays 2> /dev/null || true
    
        # The git extension runs 'git status -z -u' on the remote machine,
        # which takes a very long time if the remote directory is a git repo
        # with a lot of untracked files.
        # That can be fixed if you configure .gitignore appropriately,
        # but for my purposes it's easier to just disable git support when editing remote files.
        # If you want git support when using remote SSH, then comment out this line.
        # See: https://github.com/microsoft/vscode-remote-release/issues/4073
        VSCODE='/usr/local/bin/code'
        VSCODE="${VSCODE} --disable-extension vscode.git --disable-extension vscode.github --disable-extension waderyan.gitblame"
        LOGFILE=/tmp/iterm-vscode-trigger.log
        FTYPE=$1
        MACHINE=$2
        FILEPATHS=( "$@" )
        FILEPATHS=( "${FILEPATHS[@]:2}" )
    
        TS="["$(date "+%Y-%m-%d %H:%M:%S")"]"
        echo "${TS} Triggered: ""$@" >> ${LOGFILE}
    
        # https://github.com/microsoft/vscode-remote-release/issues/585#issuecomment-536580102
        if [[ "${FTYPE}" == "directory" ]]; then
            CMD="${VSCODE} --remote ssh-remote+${MACHINE} ${FILEPATHS[@]}"
            echo "${TS} ${CMD}" >> ${LOGFILE}
            ${CMD}
        elif [[ "${FTYPE}" == "file" ]]; then
            for FN in ${FILEPATHS[@]}; do
                CMD="${VSCODE} --file-uri vscode-remote://ssh-remote+${MACHINE}${FN}"
                echo "${TS} ${CMD}" >> ${LOGFILE}
                ${CMD}
            done
        else
            echo "${TS} Error: Bad arguments." >> ${LOGFILE}
            exit 1
        fi
    )
    
    trigger_vscode_remote_editing "$@"