
前阵子我在地铁上改了半天的代码回到家发现笔记本上还没有最新版本。当时就琢磨能不能让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服务器行为一致”的仓库环境。当然也要承认它的风险既然是模拟整套系统性能肯定不如原生AppCPU密集型的操作比如大仓库的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仓库的账户我习惯叫gitadduser -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 gitiPhone的IP:/home/git/repos/app.git第一次连接会提示确认Host fingerprint输入yes回车然后输入git用户的密码。等clone成功手机上这个仓库就算正式跑起来了。3.3 从同一局域网内的电脑验证服务这里要明确一个网络前提手机和电脑需要在同一个Wi-Fi下。iPhone的IP在“设置→无线局域网→点当前连接的Wi-Fi→查看IP地址”里可以看到。然后电脑终端里执行ssh git192.168.x.x如果看到git用户的shell说明SSH已经通。接下来就能做完整流程了cd /tmp git clone git192.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 git192.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 git192.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必须是700authorized_keys必须是600否则ssh会认为文件不可信直接拒绝使用公钥。还有一个容易忽略的点如果你是通过su切换到的git用户ssh目录的属主和属组必须是git。我之前用root给git用户创建了authorized_keys结果是root组的导致公钥一直不生效折腾了十分钟才发现是chown没做彻底。5.4 iSH进程被杀或重启后服务丢失每当你重新打开iSHsshd都不会自动启动。为了少敲命令可以把启动逻辑放到profile里。我自己的做法是在/etc/profile.d/ssh-server.sh里加一段if ! pgrep -x sshd /dev/null 21; 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和后台存活这三个地方。