前阵子我在地铁上改了半天的代码,回到家发现笔记本上还没有最新版本。当时就琢磨,能不能让iPhone直接提供一个Git仓库,电脑那边一push,手机这边就收下,回头到任何一台机器上都能接着拉。后来试了一圈,发现iSH这套免越狱方案最省事:不需要折腾签名、不需要越狱、不用额外硬件,通过TestFlight或App Store装上之后,在模拟出的Alpine Linux环境里跑起Git和OpenSSH,同一局域网内的电脑就能直接通过SSH推拉代码。这篇就把完整的搭建过程、认证配置、日常用法和我实际踩过的坑一起整理出来。
1. 方案选型:为什么要在iPhone上搭Git服务器
1.1 免越狱环境下有哪些能跑的Git方案
先说结论:想在iPhone上跑一个“能接受外部推送”的Git服务器,可选方案并不算多。
第一个思路是用那种全功能Git客户端App,比如Working Copy、aShell之类的。Working Copy本身是很优秀的Git客户端,支持在手机上本地创建仓库、提交、推送,也能通过内置的Git Server功能对外提供访问。适合个人使用,但高级的服务器托管能力、用户管理、钩子脚本这些还是相对封闭,而且不少核心功能走订阅制。
第二个思路是纯软层方案:自己用Node.js或Python写一个轻量HTTP钩子服务,接收git push后落盘。这个路子灵活度最高,但意味着要自己处理认证、权限、仓库路径映射这些脏活,在iPhone这种移动设备上维护起来并不轻松。
第三个思路就是本文要讲的iSH方案。iSH本质是一个用户态x86模拟器,它在iOS的应用沙盒里模拟出一个完整的Alpine Linux环境,里面可以用apk安装git、openssh-server、rsync这些常规服务端组件。这样做的最大好处是:你不需要换思路,所有Linux服务器上熟悉的那套Git托管流程,在iPhone上可以原封不动地照搬。我这段时间实测下来,只要能接受“手机亮屏时服务可用”这个限制,稳定性完全够个人开发场景用。
1.2 为什么最终选了iSH而不是别的方案
iSH的优势在于“通用性”和“可控性”。因为它是一个完整的Linux用户态,git push到手机,本质上是push到了一个标准的POSIX环境,钩子脚本、裸仓库、SSH key、.git/config这些概念都跟正式服务器完全一致。对我来说很关键的一点是:我在公司服务器上写的部署钩子,拿过来稍作调整就能在手机上跑,几乎不用学新东西。
另外,iSH是开源的,长期维护稳定,不需要额外付费。比起那些只能在本App内部使用的Git工具,它能让我获得一个真正意义上“跟Linux服务器行为一致”的仓库环境。
当然也要承认它的风险:既然是模拟整套系统,性能肯定不如原生App,CPU密集型的操作比如大仓库的gc会明显变慢。而且iOS对后台任务有严格限制,一旦iPhone锁屏,iSH进后台后服务会被暂停,这是整个方案最大的短板。所以我个人建议把这套Git服务器定位成外出时的随身临时仓库、两台设备之间的代码中转站,而不是替代云端的正式托管服务。
对比一下常见方案:
| 方案 | 是否需要越狱 | 能否对外提供Git服务 | 学习成本 | 主要限制 |
|---|---|---|---|---|
| Working Copy等Git客户端 | 否 | 可以,但功能封闭 | 低 | 高级能力订阅,脚本能力弱 |
| 自写HTTP钩子 | 否 | 可以 | 高 | 认证、权限都要自己实现 |
| iSH + Git + OpenSSH | 否 | 可以,完整SSH | 中 | 后台受限,性能一般 |
1.3 这套Git服务器的边界与适用人群
把这个方案的边界说清楚,免得大家抱着过高的期待去折腾。iSH跑Git服务器适合这些场景:个人或小团队在同一WiFi下临时交换代码;出差时用手机开一个仓库给另一台设备拉取最新代码;或者在手机本地做流水线演示和钩子脚本实验。
不适合的场景:多人持续访问的高可用服务、需要7×24小时在线的生产仓库,以及超大二进制文件的托管。
如果你只是想解决“外出时电脑上没代码”的痛点,建议把它当作“能跑起来的移动Git中转站”来用,而不是严肃的代码中心。心态调整好之后,这套方案会变得非常顺手。
2. 环境准备:安装iSH与基础依赖
2.1 安装iSH并确认基础环境
安装很简单,App Store或TestFlight装iSH都行。装上之后打开,会默认进入一个Alpine Linux 3.x的shell,默认用户是root,没有密码。
这里提前提醒:iSH的键盘输入偶尔会跟iOS自带输入法打架。建议在“设置→通用→键盘”里把“自动更正”“智能标点”都关掉,否则敲命令时出现自动补全的引号会特别影响心情。我就因为智能标点把单引号自动转成弯引号,导致某条命令怎么都对不上,排查了半天。
打开终端后第一件事是看看系统版本:
cat /etc/alpine-release uname -miSH模拟的是x86架构,所以在arm版的iPhone上跑x86二进制会有转译开销,但这个开销对Git这种IO密集但计算量可控的场景影响不大。
2.2 配置软件源并安装基础组件
Alpine默认的软件源有时候会很慢,可以先换成国内镜像。这一步不是必须的,但强烈建议,因为apk要拉不少包:
sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories apk update然后安装核心组件:
apk add git openssh-server openssh-client rsyncgit不用多说,openssh-server是负责对外提供SSH连接的核心,openssh-client是为了在iSH里测试连接。rsync严格来说不是必须的,但我后面会用它同步仓库目录,装上不亏。
装完之后顺手验证一下版本:
git --version ssh -V这里解释一下为什么用Alpine的apk而不是自己去编译:iSH模拟的是x86环境,虽然也能编译,但编译OpenSSH和Git耗时很长,而且依赖库要一个个手搓,完全不划算。Alpine的软件源已经提供了编译好的二进制包,直接拉下来用是最省时间的。
2.3 用户与目录规划
提前规划好用户,会省掉后面很多权限上的麻烦。Alpine里可以直接用adduser创建一个专门跑Git仓库的账户,我习惯叫git:
adduser -D -s /bin/sh git passwd git-D参数表示不创建家目录对应的初始配置文件,-s指定默认shell为/bin/sh。赋一个你记得住的密码,这个密码只在第一次公钥还没配置时用来登录,之后强烈建议换公钥登录。
仓库目录我习惯放在/home/git/repos下,方便后面单独给git用户授权:
mkdir -p /home/git/repos chown -R git:git /home/git/repos这样后面所有仓库都统一丢在这个目录下,权限清晰,备份也方便。
3. 核心配置:SSH服务与Git仓库
3.1 生成Host Key并修改SSH配置
OpenSSH在启动服务之前,必须先有Host Key,否则sshd会直接拒绝启动。第一次配置时运行:
ssh-keygen -A这样就一次性生成了rsa、ecdsa、ed25519三类主机密钥。之后编辑/etc/ssh/sshd_config,按下面的建议调整:
PermitRootLogin no PasswordAuthentication yes PubkeyAuthentication yes AllowUsers git ListenAddress 0.0.0.0这里有一个取舍值得说明:我平时在手机这套环境里会把PasswordAuthentication临时开着,方便第一次从电脑上通过密码登录。等公钥配好了,再把它改成no,防止其他人扫到22端口后天天试探密码。AllowUsers git则是把SSH登录入口收窄到只有git用户,root即使开了密码也不能直接进来,这是iOS设备上减少暴露面的一个简单有效的做法。
修改完配置后,先把配置语法检查一遍:
sshd -t如果没有输出,再启动sshd:
/usr/sbin/sshd注意,iSH里面没有systemd,所以没有service ssh start这类命令,直接手动起sshd进程即可。每次重启iSH之后都要手动再执行一次,或者后面用一个小技巧自动执行。
3.2 初始化第一个裸仓库
现在可以在git用户下建仓库了。我建议用裸仓库,也就是不带工作树的仓库,这样只存放提交历史,比较干净:
su git -s /bin/sh -c "git init --bare /home/git/repos/app.git"裸仓库直白点理解就是没有工作区的仓库,只存.git目录里的历史对象。之所以用裸仓库而不是普通仓库,是因为远程服务器端只需要接收push、保存历史即可,不需要对外暴露那个会被刷新的工作区。用普通仓库做远程push还会触发另一个坑:远端有checkout时,git会默认拒绝push,因为正在检出的分支不允许被覆盖。用裸仓库则完全没有这个问题。
裸仓库初始化好之后,你可以在电脑上直接测试:
git clone git@<iPhone的IP>:/home/git/repos/app.git第一次连接会提示确认Host fingerprint,输入yes回车,然后输入git用户的密码。等clone成功,手机上这个仓库就算正式跑起来了。
3.3 从同一局域网内的电脑验证服务
这里要明确一个网络前提:手机和电脑需要在同一个Wi-Fi下。iPhone的IP在“设置→无线局域网→点当前连接的Wi-Fi→查看IP地址”里可以看到。
然后电脑终端里执行:
ssh git@192.168.x.x如果看到git用户的shell,说明SSH已经通。接下来就能做完整流程了:
cd /tmp git clone git@192.168.x.x:/home/git/repos/app.git cd app echo "# test" > README.md git add . git commit -m "first commit" git push origin masterpush完成之后,在iSH终端里能看到refs更新。这种“代码在电脑上写完、一推就进手机”的体验,和云端仓库在行为上基本一致。
另外提醒一下,iSH需要拿到iOS的本地网络权限,否则其他设备根本扫不到它监听的端口。iPhone首次安装iSH并尝试网络连接时,系统会弹出是否允许本地网络的对话框,记得选允许。如果之前误点了不允许,可以到“设置→隐私与安全性→本地网络”里手动打开。
3.4 配置公钥登录,关闭密码认证
一直用密码登录虽然能用,但每次都要输入,而且密码认证总是比公钥容易受到暴力尝试。我建议把电脑的SSH公钥放到手机上。
电脑上执行:
ssh-keygen -t ed25519 -C "yourhost" ssh-copy-id git@192.168.x.x如果没有ssh-copy-id,就手动把公钥内容追加到git用户家目录下的~/.ssh/authorized_keys文件里:
su git -s /bin/sh -c "mkdir -p /home/git/.ssh && chmod 700 /home/git/.ssh && touch /home/git/.ssh/authorized_keys && chmod 600 /home/git/.ssh/authorized_keys"然后把电脑上~/.ssh/id_ed25519.pub的内容粘贴到authorized_keys中,保存。再回到电脑上重新ssh,应该就不需要密码了。确认没问题后,把sshd_config里的PasswordAuthentication改成no,重启sshd。
4. 日常使用与进阶玩法
4.1 用仓库做两台设备之间的代码中转
最常用的场景就是:电脑A写好代码推到iPhone,过一会儿在公司电脑B上再从iPhone拉下来。这个场景对仓库服务的要求很单一,只要SSH可用、仓库路径固定,就能像普通Git远程仓库一样使用。
我在实际操作中会专门在iPhone上建一个叫mobile-staging.git的裸仓库,用来临时接一些出差路上的改动。手机上只有git一个账户,所以电脑A和电脑B都用同一把公钥认证。如果你希望多台电脑都能访问,就把它们的公钥都追加到authorized_keys里,每行一个。
这里有个小细节:裸仓库默认不显示当前分支,你clone下来的主分支是master还是main,取决于git init时默认分支设置。建议在手机上执行:
git config --global init.defaultBranch main这样新建的裸仓库默认分支就是main,跟现在主流平台的习惯保持一致。
4.2 用钩子脚本实现push后自动部署
如果你外出时需要在手机上直接预览某个web项目的效果,可以在裸仓库的hooks目录里放一个post-receive脚本。这样一旦电脑push完成,脚本会把最新代码同步到手机上的一个固定目录,方便你接着用手机上的静态服务器验证。
post-receive钩子的原理很简单:git在每次push完成并更新引用后会执行这个脚本,脚本通过读取stdin拿到refname、oldrev、newrev三个值。一个最简单的部署脚本可以这么写:
#!/bin/sh DEPLOY_DIR=/home/git/www/app if [ ! -d "$DEPLOY_DIR" ]; then mkdir -p "$DEPLOY_DIR" fi git --work-tree="$DEPLOY_DIR" --git-dir=/home/git/repos/app.git checkout -f给脚本加执行权限后,后续每次push到监控分支,手机上对应的代码目录就会被更新。这个小技巧在我做手机端页面现场演示时非常管用。
4.3 后台运行与锁屏的取舍
这是iSH方案绕不开的痛点:iOS对后台进程的管控非常严格。iPhone锁屏之后,iSH进程会进入暂停状态,sshd也就无法继续监听端口。所以如果你想保持仓库可访问,至少要让App保持在前台,或者使用iOS的“引导式访问”锁住当前任务,降低误操作切走的概率。
此外,iSH进程本身也有被系统回收的可能,尤其是长时间不操作之后。目前没有特别完美的办法完全规避,一个比较折中的做法是把iSH作为常驻前台App,配合iPhone的自动锁屏时间调到“永不”,在需要对外提供仓库的时段保持屏幕点亮。
4.4 安全性提醒
虽然这套方案只是个人仓库,但只要sshd监听在0.0.0.0:22,就意味着同一网段的任何设备都可以尝试连接。建议做三件事:一是给git用户设置足够复杂的密码;二是尽快切换为公钥认证并关闭密码登录;三是不要把仓库目录放到默认的无约束位置,最好单独给git用户分配目录,权限收紧到最小。
如果路由器支持,也可以只允许已知设备的MAC地址访问手机IP,或者把sshd的监听端口改成其他高位端口,比如监听2222端口。虽然这不能彻底防御扫描,但能明显减少噪音。改端口的方法是在sshd_config里把Port 22改成Port 2222,然后电脑端连接时用ssh -p 2222 git@192.168.x.x。
5. 常见问题与排查实录
5.1 ssh连接超时或拒绝连接
如果电脑上连不上,先确认三件事:一是iSH里的sshd进程确实还在,执行ps aux | grep sshd看看;二是手机和电脑在同一WiFi下,并且手机IP没变;三是检查iOS设置里是否允许iSH访问本地网络,这个权限在“设置→隐私与安全性→本地网络”里,iSH需要在开启状态下才能被局域网设备访问。
我遇到最多的其实是IP地址变化。iPhone的WiFi地址如果是自动获取的,重新连网后很可能换掉,建议在路由器里给iPhone绑定一个固定IP,或者做DHCP静态绑定,这样电脑端的remote地址就不用反复改。
5.2 Host key verification failed
电脑第一次连的时候如果提示:
Host key verification failed.多半是之前连接过同一台IP但Host key变了。因为iSH每次重新生成Host key后,电脑上的~/.ssh/known_hosts里还留着旧值。最简单的处理办法是在电脑上执行:
ssh-keygen -R 192.168.x.x然后再重新连接。为了避免每次都遇到这个问题,可以把iSH系统镜像备份好,保证/etc/ssh/ssh_host_*这些文件不丢。我自己会把iSH的应用数据定期做一份备份,这样即使重装iSH,也能恢复原来的Host key和仓库目录。
5.3 Permission denied (publickey,password)
这类报错一般排查两个方向:一是密码认证是否真的打开了,我见过有人改了sshd_config后忘记重启sshd,配置一直没生效;二是git用户家目录和.ssh目录权限是否正确。在Alpine环境下,~/.ssh必须是700,authorized_keys必须是600,否则ssh会认为文件不可信,直接拒绝使用公钥。
还有一个容易忽略的点:如果你是通过su切换到的git用户,ssh目录的属主和属组必须是git。我之前用root给git用户创建了authorized_keys,结果是root组的,导致公钥一直不生效,折腾了十分钟才发现是chown没做彻底。
5.4 iSH进程被杀或重启后服务丢失
每当你重新打开iSH,sshd都不会自动启动。为了少敲命令,可以把启动逻辑放到profile里。我自己的做法是在/etc/profile.d/ssh-server.sh里加一段:
if ! pgrep -x sshd > /dev/null 2>&1; then /usr/sbin/sshd fi每次打开iSH进入shell时,就会自动拉起sshd。这个方法不算严格意义上的开机自启,但对于iSH这种手动打开App的场景已经够用了。
另外建议养成一个习惯:在iSH里定期备份git用户目录下的repos和.ssh,打包后导出到iOS的“文件”App,防止哪天iSH数据被系统清理。备份命令可以这样写:
tar -czf /home/git/git-backup.tar.gz /home/git/repos /home/git/.ssh然后把生成的tar包拷贝到iSH能访问的共享目录里,用iOS“文件”App保存一份。
最后说一点个人体会。iSH这套方案并不是要替代正规的Git托管平台,它更像是一个“塞进口袋里的临时仓库”。对我来说,最大的价值在于,只要手机亮着,我就可以在任意一台电脑上把最新的代码推过来或者拉走,整个体验跟操作一台真正的Linux服务器没有区别。外出给客户做演示、临时让同事拉分支、在手机本地跑一遍部署钩子,这些都是它的高光场景。如果你也想在iPhone上搭一套免越狱的随身Git服务器,按上面的步骤走一遍就能跑通,踩坑点基本也就集中在网络权限、Host key和后台存活这三个地方。