MSYS2 共用 Windows OpenSSH 的 SSH Agent

在有了 同 WSL 分享 SSH Agent 之后 Windows; WSL2; PuTTY / WinSCP 就都能用同一份 Windows 系统服务管理的 SSH Agent 了。添加一次私钥后再也不同重新输入密码。Git for Windows 也因为 PATH 中选用 /c/Program Files/OpenSSH/ssh-add 吃到了便利。

而 MSYS2 默认不添加来自 Windows 的 PATH,也不自带 OpenSSH。如果用 pacboy -S openssh 安装一份,它的 ssh-add 读取不到保存在 Windows Registry 的私钥。

好在 MSYS2 环境执行 .exe 文件还是很兼容的,ssh-agent 进程间访问通过标准输入输入通讯,直接删除 /usr/bin/ssh-add 让 PATH 自然会退到来自 Program Files 的即可。
也能用 MSYS2 的 ssh-add,见 想要实现 Windows to MSYS2 也是可以的

Windows OpenSSH 错误信息展示为八进制数字

在 Windows 上使用 SSH 一旦遇到 DNS 错误或者输入错误地址,按 SSH 连接失败的乱码究竟是什么? - 枫茶舍 解码,就会得到 不知道这样的主机。 错误提示,可它却以八进制数字的形式展示,难以接受。

ssh: Could not resolve hostname badname: \262\273\326\252\265\300\325\342\321\371\265\304\326\367\273\372\241\243

好在有高人指点 Localized socket error messages are shown as octal numbers · 议题 #1654 · PowerShell/Win32-OpenSSH,本地化不行就切换会英语,总比乱码强。加一行自己编译

diff --git a/contrib/win32/win32compat/win32-utf8.c b/contrib/win32/win32compat/win32-utf8.c
index 30d9bc5d1..7105bf0d0 100644
--- a/contrib/win32/win32compat/win32-utf8.c
+++ b/contrib/win32/win32compat/win32-utf8.c
@@ -100,6 +100,8 @@ asmprintf(char **outp, size_t sz, int *written, const char *fmt, ...)
 void
 msetlocale(void)
 {
+	SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
+
 	// save previous codepage
 	g_previous_codepage = GetConsoleOutputCP();
 
 
 

加一行自己按 Building OpenSSH for Windows (using LibreSSL crypto) · PowerShell/Win32-OpenSSH Wiki 的说明编译。注意不要贪速度用 shallow clone,编译过程会读取 Git 历史。

git clone https://github.com/PowerShell/openssh-portable
git clone https://github.com/Microsoft/Vcpkg
 
cd Vcpkg
./bootstrap-vcpkg.bat
./vcpkg.exe integrate install
 
cd openssh-portable
ipmo ./contrib/win32/openssh/OpenSSHBuildHelper.psm1 -Force
Start-OpenSSHBuild -Configuration Release -NativeHostArch x64
 
./bin/x64/Release/ssh -V

在手动把二进制复制到安装位置,比如我是 C:/Program Files/OpenSSH,再测试就 OK 啦

ssh: Could not resolve hostname a.b.c.d.: No such host is known.

Bash 的自动补全和语法提示

从 ble.sh 看到了 oh-my-bash,最后选择了 bash-it

同 WSL 分享 Git-Bash 的 GPG Agent

同 WSL 分享 SSH Agent 的后继

网络上有许多能够实现将来自 Gpg4win 的 ~/AppData/Local/gnupg/S.gpg-agent Assuan Socket 转发到 WSL2 的方法,比如

有很多介绍文章,但它们用的都是 Gpg4win,也都建议关闭 Windows 自带的 Win32-OpenSSH Agent,和我的使用场景不符。

因为习惯在 Windows 上编程,由 Git for Windows 提供 Git & GPG,时不时还在 WSL2 做 Linux 开发。我是希望将来自 Git for Windows (MSYS2)的 GPG 创建的 ~/.gnupg/S.gpg-agent 伪 Unix Domain Socket 分享给 WSL2。整来整去

Easy workaround

[gpg]
	program = gpg.exe

Profile Script

#!/usr/bin/env sh
# Expose Git for Windows (Cygwin) GPG Agent to WSL environments
#
# This script targets the cygwin/msys2 GPG agent implementation,
# which use a cygwin emulated UNIX domain socket.
# NOT the Agent included with Gpg4win, or WSL2.
# Ensure the correct implementation is matched.
# - gpg-agent.exe C:\Program Files\Git\usr\bin\gpg-agent.exe typlically
# - nieperelay    https://github.com/albertony/npiperelay the forked version
#
# Note that use of systemd unit files is recommended in WSL2 environments.
 
socat_gpg_agent_socket() {
  local socket_path="$1"
  local socket_name="${socket_path##*/}"
  # Exit if socket already exists and is a valid socket
  if ss -lxn | grep -q "$socket_path"; then
    return
  fi
 
  # Remove the stale socket, if it exists
  rm -f "$socket_path"
 
  # Launch socat/npiperelay as a background process
  (setsid socat \
    UNIX-LISTEN:"$socket_path",umask=007,fork \
    EXEC:"npiperelay.exe -ei -s -c C\:/Users/$USER/.gnupg/$socket_name",nofork \
    &) >/dev/null 2>&1
}
 
# e.g. /run/user/1000/gnupg/S.gpg-agent
socat_gpg_agent_socket "$(gpgconf --list-dirs agent-socket)"
# e.g. /run/user/1000/gnupg/S.gpg-agent.ssh
socat_gpg_agent_socket "$(gpgconf --list-dirs agent-ssh-socket)"

SystemD Socket

Sharing the ssh agent between Windows and WSL | sxda.io 的启发,感觉用 systemd 提供的 socket 触发启动进程的模式更加合理。也不用安装 socat 了,也不会有后台进程持续运行了。

我的版本相比链接中的,有一些调整

~/.config/systemd/user/named-pipe-gpg-agent.socket
# systemd socket unit for named-pipe-gpg-agent
# which accept connections on a Unix socket
# and forward them to the Cygwin GPG agent.
#
# enable with:
#   systemctl --user daemon-reload
#   systemctl --user enable named-pipe-gpg-agent.socket --now
 
[Unit]
Description=GPG Agent provided by Cygwin emulated unix domain socket
Conflicts=gpg-agent.socket
 
[Socket]
ListenStream=%t/gnupg/S.gpg-agent
FileDescriptorName=std
SocketMode=0600
DirectoryMode=0700
Accept=yes
 
[Install]
WantedBy=sockets.target
  • Conflicts=gpg-agent.socket 因为 gpg-agent 一个进程就能创建 S.gpg-agent, S.gpg-agent.ssh 等多个 socket,用 apt 安装的 gpg 的 systemd 配置文件,自然是多个 socket 对应同一个 service 文件。这里 npiperelay 只能做到一个进程一个 socket,不得不拆分为多个 socket 和 service 来定义。所以和系统自带的 gpg 冲突,别一起用。
  • Accept=yes 很有必要,并且 service 文件名也带有 @,否则会无限卡住。让每次请求启动新的进程,npiperelay 处理完一次请求就自动退出
~/.config/systemd/user/named-pipe-gpg-agent@.service
# systemd service unit for npiperelay-gpg-agent.socket
# StandardInput can only handle one socket per service, thus multiple units.
# each service spawn its own npiperelay instance to serve one socket.
 
[Unit]
Description=Proxy to Windows GPG Agent, which provides the standard named pipe (Cygwin/MSYS2, etc.)
 
[Service]
Type=simple
ExecStart=/mnt/c/Users/%u/AppData/Local/Microsoft/WinGet/Links/npiperelay.exe -ei -s -c "C:/Users/%u/.gnupg/S.gpg-agent"
StandardInput=socket
  • ExecStart 可执行文件路径要写绝对路径,放在哪里无所谓
  • -c 的参数在 shell 中要转义,但在这里放在引号内就不用

使用前需要先禁用系统自带的

systemctl --user daemon-reload
systemctl --user disable --now gpg-agent.socket

在 WSL2 中可以这样测试

systemctl --user daemon-reload
systemctl --user disable --now named-pipe-gpg-agent.socket
systemctl --user enable  --now named-pipe-gpg-agent.socket
ll /run/user/1000/gnupg/S.gpg-agent
gpg-connect-agent --no-autostart 'keyinfo --list' /bye

如果正常输出类似你的 gpg -k 列表就说明成功了。

想要实现 Windows to MSYS2 也是可以的,Profile Script 通过在 MSYS2 的 /etc/profile 中运行 socat 后台的方式,不过需要用 MSYS2 中的 go 编译 npiperelay.exe 才行,否则会遇到 copy from stdin to pipe failed: read /dev/stdin: The parameter is incorrect. 的错误。

不过在 MSYS2 中跑后台进程感觉不是很合适,还是直接调用 ssh-add.exe 方便又直接,还免得设置 SSH_AUTH_SOCK 环境变量。
MSYS2 的 ssh.exe 会访问 SSH_AUTH_SOCK 环境变量,否则就尝试加载 ~/.ssh/id_ed25519 这种文件。
而且问题是 MSYS2 编译的 npiperelay 只能给 MSYS2 的 ssh 使用,Windows 环境编译的 npiperelay 又不能给 MSYS2 的 ssh 使用,因为它们的 stdio 不一样。只能在 socat 脚本里分开两个目标了。

所以需要为 MSYS2 编译一套 npiperelay,然后用 Profile 的方式启动。或者临时地,在两个窗口里

socat UNIX-LISTEN:/tmp/run/197608/ssh/ssh-agent.sock,umask=007,fork EXEC:"npiperelay-msys2.exe -v -ei -s //./pipe/openssh-ssh-agent",nofork
export SSH_AUTH_SOCK=/tmp/run/197608/ssh/ssh-agent.sock