惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
Jina AI
Jina AI
Microsoft Security Blog
Microsoft Security Blog
IT之家
IT之家
V
Visual Studio Blog
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 聂微东
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
大猫的无限游戏
大猫的无限游戏
Y
Y Combinator Blog
Stack Overflow Blog
Stack Overflow Blog
D
Docker
MyScale Blog
MyScale Blog
小众软件
小众软件
云风的 BLOG
云风的 BLOG
美团技术团队
Microsoft Azure Blog
Microsoft Azure Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 【当耐特】

OhYee 博客

小鹏辅助驾驶测评|OhYee 博客 小鹏非支持手机开启自动解锁|OhYee 博客 使用函数计算实现 301 重定向|OhYee 博客 针对 HTML 内容使用 Ant Design 图片弹框|OhYee 博客 博客进程泄露及僵尸进程解决|OhYee 博客 蓝易云服务器体验|OhYee 博客 【2022 秋招内推】阿里云后端研发工程师|OhYee 博客 使用函数计算获取 IP 地址信息|OhYee 博客 正确获取客户端 IP/HTTP Header 也可能重复|OhYee 博客 评测 Oculus Quest2 及 BigScreen|OhYee 博客 NextJS 热重载保留状态|OhYee 博客 如何优雅地贴 gist 代码|OhYee 博客 Linux 精细化文件权限|OhYee 博客 VSCode 容器开发环境|OhYee 博客 Clash 的不兼容更新排查|OhYee 博客 Zeek 导出 PCAP|OhYee 博客 记一次 ssh 配置问题|OhYee 博客 Git Commit 规范化工具|OhYee 博客 谈谈《星之卡比-探索发现》|OhYee 博客 VSCode 快捷键绑定 Shell 命令|OhYee 博客 ASN.1 语法及 X.509 证书格式解析解析|OhYee 博客 腾讯企业邮箱忽略 MX 记录发信|OhYee 博客 Chrome/Edge 标签组插件|OhYee 博客 【应届内推】阿里云后端研发工程师|OhYee 博客 损坏的 Typecho 备份处理为 JSON|OhYee 博客 VS Code VIM 插件高效使用|OhYee 博客 SSH 正反向代理|OhYee 博客 Let's Encrypt 根证书过期引发的问题|OhYee 博客 OpenWRT 忽略内核依赖|OhYee 博客 V2Ray + TLS + WS(WSS) Docker 部署|OhYee 博客
SSH 调起本地 VSCode|OhYee 博客
2022-10-23 · via OhYee 博客

SSH 调起本地 VSCode

背景

VS Code 的一大优势是其远端开发能力,remote 插件已经和本地开发完全无感

在公司开发中,已经将全部开发工作移至服务器。Go 相关开发借助 remote SSH(部分构件任务需要用到 docker,由于使用别人构建好的镜像,折腾 Docker in Docker 比较麻烦),前端、脚本 语言开发已经通过 dev container(ssh 到服务器上的 docker)开发

remote 全家桶里还有 WSL 插件,非公司的开发任务会在自己的 WSL 环境开发。

由于开发往往通常需要在多个代码中切换,而 code ~/xxx 命令只有在 vscode 的集成终端里才存在,那么如何在 ssh 窗口中实现类似的功能呢?

注: 该需求也可以借助 project manager 插件 部分解决

文件 URI

与大部分程序一样,直接给 vscode 二进制文件传入文件夹/文件路径,即可打开对应的内容。但是 remote 文件/文件夹则满足特定的格式

  • vscode-remote://ssh-remote+${SSH_SERVER}${ABS_PATH}'
  • vscode-remote://dev-container+$(printf "{\"hostPath\":\"${ABS_PATH}\",\"localDocker\":false,\"settings\":{\"host\":\"ssh://${SSH_SERVER}\"}" | od -A n -t x1 | tr -d '[\n\r ]')/workspaces/$(basename $ABS_PATH)'"

可以看出,所有需要远端服务器的文件/文件夹都需要通过特殊的 URI 来标记文件地址,这里需要给出使用的插件、SSH 地址、绝对路径地址。对于 dev container,这部分参数以 json 形式传输,并且需要进行十六进制转码

需要特别注意的是,对于 文件 和 文件夹,需要的参数不同

  • 文件 --file-uri
  • 文件夹 --folder-uri

调用 vscode

如果已经通过 vscode 连接到服务器,那么我们知道可以通过 code xxx 在新窗口打开对应的远端目录,但是如果是直接通过 SSH 则会提示没有对应的命令

简单来说,这个的实现原理是,vscode 在连接到远端后,会通过建立一个 Unix sock 连接。在 /run/user/{用户 ID} 或是 /tmp 目录下会看到大量 vscode-ipc-* 的文件,只需要对这个文件进行读写操作,即可和本地 vscode 通信(万物皆文件)

由于未知原因,sock 文件并不一定会在连接断开后删除,因此往往这里会有很多文件,需要先判断哪个是可用的。可以使用 nc 命令(netcat)判断对应的连接是否还存活,如果存活则可以通过这个连接传输要打开的文件/文件夹

找到可用的连接后,还需要按照特定的格式进行传输(和 tcp 一样,需要知道应用层的数据格式)。由于 vscode 连到服务端后,实际上服务器上也会安装 vscode 服务端二进制文件,因此可以直接调用这个二进制文件去组装格式。

通过 VSCODE_IPC_HOOK_CLI 可以告诉 vscode 应该使用哪个 Unix 连接。

至此,理论上已经可以在任意 ssh 连接里实现 vscode 集成终端里的 code 命令了

如果没有打开 vscode 怎么办

前面的操作需要已经有一个 vscode 连接,但是如果重启电脑后想直接在 ssh 连接打开文件呢?

上面的大部分内容实际上并不需要改变,唯一要解决的问题时,如何让本地执行一个指定的命令。
为了 SSH 连接的安全性,SSH 本身并不支持类似的操作,因此需要实现 SSH 反向连接 —— 从服务器 SSH 到本地机器,并执行命令

具体实现可以看下面的脚本

已知问题

服务端地址来自于 SSH 连接建立后自动写入的环境变量 SSH_CONNECTION。如果 SSH 连接中通过跳板,那么拿到的 SSH 地址是跳板机和目标服务器的连接地址,本地机器可能无法联通

如果通过反向 SSH 连回本地,需要本地开启 SSH 端口,并且 IP 可以连通(非公司网络下,这通常意味着需要公网 IP)
可以考虑 SSH 到服务器时,映射下本地 SSH 端口

最后结果

将下面的内容放到 ~/.bashrc 后重新加载即可通过 rcode 来打开文件

function clear_vscode_socks() {
    arr=()
    arr+=$(ls -t1 /run/user/$(id -u)/vscode-ipc-* 2>>/dev/null)
    arr+=$(ls -t1 /tmp/vscode-ipc-* 2>>/dev/null)
    
    for item in $arr; do 
        if [ "$(_check_unix_sock $item)" == "1" ] || [ "$1" == "-f" ]; then
            echo "delete $item"            
            rm -f $item; 
        else
            echo "keep $item"
        fi
    done
}

function _check_unix_sock() {
    nc -U -z $1 2>&1 >/dev/null;
    echo $?;
}

function rcode() { ## SSH VSCode 唤起
    # https://stackoverflow.com/questions/66581313/open-files-from-remote-ssh-host-in-vscode
    # https://github.com/microsoft/vscode-remote-release/issues/3324
    # https://github.com/microsoft/vscode-remote-release/issues/3738

    [ -z "$TMUX" ] && eval $(tmux showenv -s SSH_CONNECTION)
    SSH_CLIENT=$(cut -d ' ' -f1 <<<"$SSH_CONNECTION")
    SSH_SERVER=$(cut -d ' ' -f3 <<<"$SSH_CONNECTION")
    VSCODE_BIN=$(ls -t1 ${HOME}/.vscode-server/bin/*/bin/code | tail -n 1)

    # 获取 VSCODE 通信 sock
    if [ -n $VSCODE_IPC_HOOK_CLI ]; then
        if [ ! -f $VSCODE_IPC_HOOK_CLI ] || [ "$(_check_unix_sock $VSCODE_IPC_HOOK_CLI)" == "1" ]; then
            unset VSCODE_IPC_HOOK_CLI
        fi
    fi

    if [ -z "$VSCODE_IPC_HOOK_CLI" ]; then
        arr=()
        arr+=$(ls -t1 /run/user/$(id -u)/vscode-ipc-* 2>>/dev/null)
        arr+=$(ls -t1 /tmp/vscode-ipc-* 2>>/dev/null)
        for item in $arr; do
            if [[ $(_check_unix_sock $item) == "0" ]]; then
                VSCODE_IPC_HOOK_CLI=$item
                break
            else
                echo "rm $item"
                rm -f $item
            fi
        done
    fi

    export VSCODE_IPC_HOOK_CLI
    
    for ARG in "$@"; do
        ABS_PATH=$(realpath $ARG)
        
        CODE_ARGS=""
        if [[ -f $ABS_PATH ]]; then
            # open file
            CODE_ARGS="--file-uri 'vscode-remote://ssh-remote+${SSH_SERVER}${ABS_PATH}'";
        fi
        if [[ -d $ABS_PATH ]]; then
            if [ -e $ABS_PATH/.devcontainer ]; then
                # open container, disabled
                RAW="{\"hostPath\":\"${ABS_PATH}\",\"localDocker\":false,\"settings\":{\"host\":\"ssh://${SSH_SERVER}\"}}"
                CODE_ARGS="--folder-uri 'vscode-remote://dev-container+$(printf $RAW | od -A n -t x1 | tr -d '[\n\r ]')/workspaces/$(basename $ABS_PATH)'"
            else
                # open folder
                CODE_ARGS="--folder-uri 'vscode-remote://ssh-remote+${SSH_SERVER}${ABS_PATH}'";
            fi
        fi
       
        if [[ -z $VSCODE_IPC_HOOK_CLI ]]; then
            # no connected socks, using ssh
            echo "Using reverse ssh, input client ssh password"
            if [ -z $LOCAL_USER ]; then
                LOCAL_USER=$(whoami)
                echo "ssh client username(LOCAL_USER) not set, using $LOCAL_USER"
            fi
            CMD="ssh ${LOCAL_USER}@${SSH_CLIENT} bash -c \"code ${CODE_ARGS}\""
        else
            echo "Using sock $VSCODE_IPC_HOOK_CLI"
            CMD="${VSCODE_BIN} ${CODE_ARGS}"
        fi
        
        echo "code ${CODE_ARGS}"
        sh -c "$CMD"
            echo $CMD
    done
}