前置要求
本方案刚需一台开放ssh端口且可以安装各类tui agent(claude code等)的设备。
同时确认你知道用于连接的 ip地址/域名,ssh端口号(可以用于远程访问的端口号),你需要登陆的用户的用户名,该用户的密码。
组网方案可以使用tailscale等虚拟局域网方案,或是直接frp内网穿透(TCP隧道)
为什么用Termux
之前在用Termius,效果其实也不错,只是这个在我这里没法滑动屏幕滚屏
,含泪舍弃,后来发现有个叫Termux的东西,感觉很有意思,且在使用ssh时可以正常划屏,所以开始研究。没试过其他客户端,如果有其他好用的客户端推荐,欢迎回帖。
整体思路
使用软件 Termux 在移动设备用pkg包管理器安装 openssh 和 sshpass,并持久化本地ssh config,从而实现快捷远程ssh
Termux官方网址: Termux | The main termux site and help pages.
github仓库: GitHub - termux/termux-app: Termux - a terminal emulator application for Android OS extendible by variety of packages. · GitHub
F-Droid发布页: Termux | F-Droid - Free and Open Source Android App Repository
也可以直接使用谷歌版,方法通用
整体步骤
首先安装openssh和sshpass,然后在本地创建ssh目录,之后创建密钥对,用sshpass将密钥对上传到需要连接的ssh主机,期间需要提供所登录用户的密码,并定义config。之后就可以用 ssh <你的自定义名称> 登录到ssh主机。
具体流程可以参考我让ai帮我写的脚本
#!/usr/bin/env bash
set -euo pipefail
#此处内容注意修改,改为你自己的主机相关内容
HOST_ALIAS="你自己起的名称"
REMOTE_HOST="ip地址/域名,不带端口号"
REMOTE_PORT="可以远程连接到的ssh端口"
REMOTE_USER="需要登录的用户"
KEY_FILE="$HOME/.ssh/${HOST_ALIAS}_ed25519"
CONFIG_FILE="$HOME/.ssh/config"
echo "==> 检查 pkg 包管理器"
if ! command -v pkg >/dev/null 2>&1; then
echo "ERROR: 未找到 pkg 包管理器。"
echo "此脚本面向 Termux 环境,请确认你正在 Termux 中运行。"
exit 1
fi
echo "==> 安装 openssh 和 sshpass"
pkg update -y
pkg install -y openssh sshpass
echo "==> 检查 ssh / ssh-keygen / sshpass"
for cmd in ssh ssh-keygen sshpass; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "ERROR: 缺少命令:$cmd"
echo "请检查 openssh / sshpass 是否安装成功。"
exit 1
fi
done
echo "==> 准备 SSH 目录"
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
echo "==> 生成 SSH 密钥对"
if [ -f "$KEY_FILE" ]; then
echo "已存在私钥:$KEY_FILE"
echo "将复用现有密钥。"
else
ssh-keygen -t ed25519 \
-f "$KEY_FILE" \
-C "${HOST_ALIAS}-${REMOTE_USER}@${REMOTE_HOST}" \
-N ""
chmod 600 "$KEY_FILE"
chmod 644 "$KEY_FILE.pub"
echo "已生成密钥:$KEY_FILE"
fi
echo "==> 读取远程密码"
if [ -z "${SSH_PASSWORD:-}" ]; then
read -rsp "请输入 ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT} 的 SSH 密码: " SSH_PASSWORD
echo ""
fi
echo "==> 复制公钥到远程主机"
cat "$KEY_FILE.pub" | sshpass -p "$SSH_PASSWORD" ssh \
-p "$REMOTE_PORT" \
-o StrictHostKeyChecking=accept-new \
-o PreferredAuthentications=password \
-o PubkeyAuthentication=no \
"${REMOTE_USER}@${REMOTE_HOST}" \
'umask 077
mkdir -p ~/.ssh
touch ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
read -r PUBKEY
grep -qxF -- "$PUBKEY" ~/.ssh/authorized_keys || printf "%s\n" "$PUBKEY" >> ~/.ssh/authorized_keys
'
echo "==> 备份并更新本地 SSH config"
touch "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
BACKUP_FILE="${CONFIG_FILE}.bak.$(date +%Y%m%d%H%M%S)"
cp "$CONFIG_FILE" "$BACKUP_FILE"
TMP_FILE="$(mktemp)"
# 删除旧的 devssh 配置块,避免重复
awk -v host="$HOST_ALIAS" '
BEGIN { skip = 0 }
/^[[:space:]]*Host[[:space:]]+/ {
if ($2 == host) {
skip = 1
next
} else {
skip = 0
}
}
skip == 0 { print }
' "$CONFIG_FILE" > "$TMP_FILE"
cat >> "$TMP_FILE" <<EOF
Host ${HOST_ALIAS}
HostName ${REMOTE_HOST}
Port ${REMOTE_PORT}
User ${REMOTE_USER}
IdentityFile ${KEY_FILE}
IdentitiesOnly yes
ServerAliveInterval 60
ServerAliveCountMax 3
EOF
mv "$TMP_FILE" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
echo "==> 测试免密登录"
ssh -o BatchMode=yes "$HOST_ALIAS" "echo 'SSH key login success: \$(hostname)'"
echo ""
echo "完成。现在可以直接连接:"
echo ""
echo " ssh ${HOST_ALIAS}"
echo ""
echo "私钥位置:"
echo " ${KEY_FILE}"
echo ""
echo "配置文件:"
echo " ${CONFIG_FILE}"
echo ""
echo "原配置备份:"
echo " ${BACKUP_FILE}"
使用该脚本时注意修改脚本开头的 HOST_ALIAS、REMOTE_HOST、REMOTE_PORT、REMOTE_USER
实操展示
1.安装并打开Termux,长摁屏幕即可进行粘贴 ![]()

2.复制完毕之后点击回车按键,执行脚本,等出现“读取远程密码”的提示之后,输入你所登录账户的密码

3.完成之后直接尝试登录,我之前填写的名称为 devssh,那么我这里就是用 ssh devssh 这个命令进行远程ssh登录,远程登录我的ubuntu主机

4.打开你最喜欢的tui coding agent,开始愉快编码
后续优化
在之前的图片中可以看到,Termux 默认的字体样式并不适合用来进行coding,整体看上去比较杂乱,此时可以对字体进行修改。
具体参考以下脚本 ,使用的字体是 “Maple Mono NF CN”,如果你想要其他字体,可以让ai帮你改 ![]()
bash <<'EOF'
set -euo pipefail
REPO="subframe7536/maple-font"
FONT_ALIAS="Maple Mono NF CN"
ASSET_REGEX="${ASSET_REGEX:-MapleMono.*NF.*CN.*\.zip$}"
TERMUX_PREFIX="/data/data/com.termux/files/usr"
TERMUX_FONT_DIR="$HOME/.termux"
TERMUX_FONT_FILE="$TERMUX_FONT_DIR/font.ttf"
TMP_DIR=""
log() {
printf '==> %s\n' "$*" >&2
}
warn() {
printf 'WARN: %s\n' "$*" >&2
}
die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
cleanup() {
if [ -n "${TMP_DIR:-}" ] && [ -d "$TMP_DIR" ]; then
rm -rf "$TMP_DIR"
fi
}
check_termux() {
if [ ! -d "$TERMUX_PREFIX" ]; then
die "当前环境看起来不是 Termux,请在 Termux 中运行。"
fi
}
install_deps() {
log "更新软件源并安装必要依赖..."
export DEBIAN_FRONTEND=noninteractive
pkg update -y
apt-get install -y \
-o Dpkg::Options::="--force-confdef" \
-o Dpkg::Options::="--force-confold" \
wget jq unzip tar ca-certificates
}
get_latest_asset() {
local work_dir="$1"
local api_url="https://api.github.com/repos/${REPO}/releases/latest"
local json_file="$work_dir/latest.json"
log "获取 ${FONT_ALIAS} 最新版本信息..."
wget -qO "$json_file" "$api_url" \
|| die "无法访问 GitHub API:$api_url"
if ! jq -e '.assets and (.assets | type == "array")' "$json_file" >/dev/null; then
cat "$json_file" >&2 || true
die "GitHub API 返回异常,未找到 release assets。"
fi
local tag_name
tag_name="$(jq -r '.tag_name // empty' "$json_file")"
if [ -n "$tag_name" ]; then
log "最新版本:$tag_name"
fi
local asset_name
local asset_url
asset_name="$(
jq -r --arg re "$ASSET_REGEX" '
[.assets[] | select(.name | test($re; "i")) | .name][0] // empty
' "$json_file"
)"
asset_url="$(
jq -r --arg re "$ASSET_REGEX" '
[.assets[] | select(.name | test($re; "i")) | .browser_download_url][0] // empty
' "$json_file"
)"
if [ -z "$asset_url" ]; then
warn "没有找到匹配的下载文件。"
warn "当前匹配规则:$ASSET_REGEX"
warn ""
warn "可用 release assets:"
jq -r '.assets[].name' "$json_file" >&2 || true
die "未找到 ${FONT_ALIAS} 对应压缩包。"
fi
log "匹配到文件:$asset_name"
printf '%s\n' "$asset_url"
}
download_asset() {
local asset_url="$1"
local output_file="$2"
case "$asset_url" in
http://*|https://*) ;;
*) die "下载地址不是有效 URL:$asset_url" ;;
esac
log "下载字体压缩包..."
log "下载地址:$asset_url"
wget -O "$output_file" "$asset_url" \
|| die "下载失败:$asset_url"
}
extract_asset() {
local asset_file="$1"
local extract_dir="$2"
log "解压字体文件..."
mkdir -p "$extract_dir"
case "$asset_file" in
*.zip)
unzip -q "$asset_file" -d "$extract_dir"
;;
*.tar.gz|*.tgz)
tar -xzf "$asset_file" -C "$extract_dir"
;;
*.tar.xz)
tar -xJf "$asset_file" -C "$extract_dir"
;;
*.ttf|*.otf)
cp "$asset_file" "$extract_dir/"
;;
*)
die "暂不支持的文件格式:$asset_file"
;;
esac
}
find_font_file() {
local extract_dir="$1"
local font_file=""
log "查找 Maple Mono NF CN Regular 字体文件..."
font_file="$(
find "$extract_dir" -type f \( -iname "*.ttf" -o -iname "*.otf" \) \
| grep -Ei 'Maple.*Mono.*NF.*CN.*Regular.*\.ttf$' \
| grep -Eiv 'italic|bold|light|medium|semibold|extrabold|thin|black' \
| sort \
| head -n 1 || true
)"
if [ -z "$font_file" ]; then
font_file="$(
find "$extract_dir" -type f \( -iname "*.ttf" -o -iname "*.otf" \) \
| grep -Ei 'Maple.*Mono.*NF.*CN.*Regular.*\.(ttf|otf)$' \
| grep -Eiv 'italic|bold|light|medium|semibold|extrabold|thin|black' \
| sort \
| head -n 1 || true
)"
fi
if [ -z "$font_file" ]; then
font_file="$(
find "$extract_dir" -type f \( -iname "*.ttf" -o -iname "*.otf" \) \
| grep -Ei 'Regular.*\.(ttf|otf)$' \
| grep -Eiv 'italic|bold|light|medium|semibold|extrabold|thin|black' \
| sort \
| head -n 1 || true
)"
fi
if [ -z "$font_file" ]; then
font_file="$(
find "$extract_dir" -type f \( -iname "*.ttf" -o -iname "*.otf" \) \
| grep -Ei 'Maple|NF|CN' \
| grep -Eiv 'italic|bold|light|medium|semibold|extrabold|thin|black' \
| sort \
| head -n 1 || true
)"
fi
if [ -z "$font_file" ]; then
font_file="$(
find "$extract_dir" -type f \( -iname "*.ttf" -o -iname "*.otf" \) \
| sort \
| head -n 1 || true
)"
fi
if [ -z "$font_file" ]; then
die "解压后没有找到 .ttf 或 .otf 字体文件。"
fi
printf '%s\n' "$font_file"
}
install_font_to_termux() {
local font_file="$1"
log "安装字体到 Termux..."
if [ ! -f "$font_file" ]; then
die "字体文件不存在:$font_file"
fi
mkdir -p "$TERMUX_FONT_DIR"
if [ -f "$TERMUX_FONT_FILE" ]; then
local backup_file="${TERMUX_FONT_FILE}.bak.$(date +%Y%m%d%H%M%S)"
cp "$TERMUX_FONT_FILE" "$backup_file"
log "已备份原字体:$backup_file"
fi
cp "$font_file" "$TERMUX_FONT_FILE"
chmod 644 "$TERMUX_FONT_FILE"
log "已安装字体:$TERMUX_FONT_FILE"
log "字体来源:$font_file"
}
reload_termux_settings() {
log "刷新 Termux 设置..."
if command -v termux-reload-settings >/dev/null 2>&1; then
termux-reload-settings || warn "刷新失败,请手动重启 Termux。"
else
warn "未找到 termux-reload-settings,请手动重启 Termux 应用。"
fi
}
main() {
check_termux
install_deps
TMP_DIR="$(mktemp -d)"
trap cleanup EXIT
local asset_url
local asset_url_lc
local asset_file
local extract_dir
local font_file
asset_url="$(get_latest_asset "$TMP_DIR")"
asset_url_lc="$(printf '%s' "$asset_url" | tr '[:upper:]' '[:lower:]')"
log "最终下载 URL:$asset_url"
case "$asset_url_lc" in
*.zip)
asset_file="$TMP_DIR/font.zip"
;;
*.tar.gz|*.tgz)
asset_file="$TMP_DIR/font.tar.gz"
;;
*.tar.xz)
asset_file="$TMP_DIR/font.tar.xz"
;;
*.ttf)
asset_file="$TMP_DIR/font.ttf"
;;
*.otf)
asset_file="$TMP_DIR/font.otf"
;;
*)
asset_file="$TMP_DIR/font.zip"
warn "无法从 URL 判断格式,默认按 zip 处理。"
;;
esac
extract_dir="$TMP_DIR/extracted"
download_asset "$asset_url" "$asset_file"
extract_asset "$asset_file" "$extract_dir"
font_file="$(find_font_file "$extract_dir")"
log "选中的字体文件:$font_file"
install_font_to_termux "$font_file"
reload_termux_settings
log "安装完成。"
log "如果字体没有立即生效,请完全关闭并重新打开 Termux。"
}
main "$@"
EOF
更新字体之后的展示,可以看到显示效果明显比之前好。
已经测试过 F-Droid版本和google play版本,均可以正常使用
可以配合tmux进行使用,实现断开连接ai依然在跑的效果 ![]()
发现一个新的问题,在连接到物理键盘时,termux似乎无法输入中文
通过脚本 解决了该问题,谷歌版似乎无法用这个方案解决,必须用F-Droid /Github版
mkdir -p ~/.termux
echo 'enforce-char-based-input=true' >> ~/.termux/termux.properties
termux-reload-settings
第一次写这种文章,如有写得不好的地方请见谅 ![]()
1 个帖子 - 1 位参与者