Shipback:专为 AI Agent 打造、支持一键撤销回滚的 SFTP 部署工具
英文标题:🔶 Show HN: Shipback – SFTP deploys your AI agent can undo
开发者 aldoh 在 Hacker News 上展示了专为 AI Agent(如 Claude Code、Cursor)设计的 SFTP 自动化部署工具 Shipback。该工具在每次上传覆盖前自动对远程文件生成带 SHA-256 校验的备份,不仅支持单命令一键安全撤销,还能智能识别线上手动修改所导致的文件漂移与冲突。Shipback 提供安全的 MCP 服务,严格杜绝危险删除操作并将密码妥善保存在系统钥匙串中。这为在缺乏 Git 版本控制的传统 VPS 或虚拟主机环境下进行自动化 Agent 部署提供了坚实的安全防护屏障。
7.5 KB
122.3 KB
中文
让 Agent 放心部署,随时一键撤销。
深夜时分,Agent 提示“部署完成 ✓”。紧接着,客户网站的联系表单却不见了。原来是 Agent 覆盖了上周有人在 cPanel 中手动修复的文件,而共享主机(Shared Hosting)并没有 git revert 可用。
Shipback 可以将本地文件夹部署至任意 SFTP 主机:包括 cPanel、Plesk、DirectAdmin 或普通的 VPS。每次部署都可以通过单条命令直接撤销。你可以手动运行、编写脚本,或者交由 Claude Code、Cursor 及任意 MCP 客户端调度。
- 每次覆盖前自动备份:在上传开始前,部署所替换的每个服务器端文件副本都会保存到服务器上的
_backup-<时间>/目录中。 - 一键撤销:
--restore可放回这些文件,并清理该次部署新增的文件。 - 不会覆盖线上实时修改:如果部署后有人在服务器上修改了文件,恢复操作将停止并指出该文件。除非传入
--force,否则不会改动任何内容;即便强制恢复,修改后的副本也会先被备份。 - 恢复操作本身也可撤销:恢复过程会像部署一样,自动备份被其替换的内容。
- 面向 Agent 具备安全性:MCP 服务器不提供删除服务器文件或跳过备份的开关。部署只会执行 Agent 预览过的确切计划;若期间文件发生变动,将直接拒绝执行。
- 洞察文件漂移:
--pull --dry-run会显示自上次部署以来服务器上发生的所有变动,避免误覆盖在 cPanel 中实时进行的修改。
服务器端无需安装 git 或 SSH shell,仅需 SFTP 支持。运行环境需要 Node 18+。测试套件在每次代码推送时,均在 Windows、macOS 和 Linux 环境下的 Node 18、20 和 22 版本中运行。密码保存在操作系统钥匙串(Keychain)中,绝不传入模型。
已在生产环境中验证运行。 底层的部署引擎自 2026 年 9 月以来,已为 aldo.today 成功部署 235 次(向一台 cPanel 主机累计上传 2,907 个文件)。恢复功能和 MCP 服务器为 0.1 版本的新特性。
bash npm install -g shipback-sftp # 安装 shipback 和 shipback-mcp 命令 cd ~/sites # 进入包含 sites.json 的任意目录(格式见下文) shipback mysite --dry-run # 查看部署计划;仅首次询问密码并存储 shipback mysite # 部署 shipback mysite --restore # 想要反悔?一键撤销
不想全局安装?使用 npx -p shipback-sftp shipback mysite --dry-run 效果完全相同。--sites <file> 可以指向存放在其他目录下的 sites.json。
通过源码运行:git clone https://github.com/aldohushi1-stack/shipback-sftp.git,执行 npm install,随后在文档中提及 shipback 的地方改用 node bin/cli.js 即可。
sites.json 配置示例,每个站点一条记录:
{ "sites": [ { "name": "mysite", "host": "server123.example-host.com", "port": 22, "username": "cpaneluser", "remoteRoot": "/home/cpaneluser/public_html", "localDir": "/Users/you/sites/mysite", "ignore": ["drafts/", "*.psd"] } ] }
许多共享虚拟主机使用独立端口而非 22 端口运行 SFTP(请在控制面板的 SSH Access 中确认),部分主机仅接受来自白名单 IP 的连接。如果连接超时,请优先排查这两项。若主机配置了你的密钥,可使用 "privateKeyPath" 替代密码。
bash shipback --dry-run # 查看部署将上传哪些内容 shipback # 部署:备份、上传并记录 shipback --local # 部署与 localDir 不同的自定义文件夹 shipback --pull --dry-run # 自上次部署后服务器上有何变化? shipback --pull # 同步拉取这些变更(本地副本先备份) shipback --backups # 列出备份:每次运行覆盖、新增和删除了什么 shipback --restore --dry-run # 查看撤销最新一次部署将产生何种操作 shipback --restore [] # 撤销部署(或指定更早的 _backup-… 目录) shipback --prune --keep 10 # 清理旧备份;拒绝清理包含某文件唯一副本的备份 shipback --history [site] # 查看上传记录:时间、源路径及内容
在任意命令后附加 --json 可获得机器可读的输出。被拒绝的恢复操作将以退出码 4 退出。
--mirror(删除本地文件夹中不存在的服务器端文件)和 --no-backup 选项仅为明确了解其风险的人类用户提供。Agent 工具不会提供这些选项,防护钩子(Guard Hook)也会阻止 Claude 键入它们。
每次部署都会在远端根目录下生成 _backup-YYYY-MM-DD-HHMM/。其中包含:
- 部署所覆盖或删除的每个文件的服务器端副本;
.backup.json:记录部署新增了什么,以及部署后留在服务器上的每个文件的 SHA-256 散列值。
--restore 会读取该记录,并预先校验线上当前的每个文件:
| 线上当前文件状态 | 恢复操作的处理方式 |
|---|---|
| 与部署上传的内容完全一致 | 放回旧副本(如果该文件为该次部署新增,则予以删除) |
| 已经是旧副本 | 不执行任何操作;视为已经是正确状态 |
| 与上述两者均不相同 | 冲突:停止执行,指出文件名,不做改动(可用 --force 覆盖) |
在写入任何内容之前,恢复操作会先将当前线上文件保存到一个新的 _backup-… 文件夹中并配有自身的记录,因此在该文件夹上执行 --restore 可以带你再次“重做(前进)”。它还会从 .deploy-manifest.json 中清除已恢复的路径,以便下一次部署根据内容进行比较,而不是盲目信任陈旧的哈希值。
备份文件夹在部署和拉取过程中均会被跳过,且除了 --prune 之外绝不会被删除。
通过 AI Agent 调用(MCP)
shipback-mcp (bin/mcp.js) 是一个基于 stdio 的 MCP 服务器。
Claude Code
bash claude mcp add shipback-sftp -- npx -y -p shipback-sftp shipback-mcp --sites /path/to/sites.json
Cursor、Claude Desktop 等客户端 (mcp.json / claude_desktop_config.json):
{ "mcpServers": { "shipback-sftp": { "command": "npx", "args": ["-y", "-p", "shipback-sftp", "shipback-mcp", "--sites", "/path/to/sites.json"] } } }
在 Windows 环境下,若客户端无法启动 npx,请使用 "command": "cmd" 并在 args 最前面添加 "/c", "npx"。全局安装后,使用 "command": "shipback-mcp" 也可生效;如果基于仓库克隆运行,则为 node 并以 bin/mcp.js 作为首个参数。
请先针对站点运行一次任意 CLI 命令(例如 shipback mysite --backups)。这会将密码存入操作系统钥匙串,供 MCP 服务器读取,模型自身永远无法接触到密码。在 CI 环境中,你也可以在服务器环境中设置 SFTP_PASSWORD。
| 工具名称 | 功能描述 |
|---|---|
list_sites |
列出已配置的站点(不含密码) |
preview_deploy |
预览将要上传的内容;返回一个 planId |
deploy |
上传该计划;若自预览后发生过任何更改则拒绝执行 |
check_server_changes |
检查自上次部署以来线上服务器修改或新增的文件 |
pull |
将这些变更同步至本地文件夹(本地副本预先备份) |
list_backups |
按从新到旧列出备份,附带每次运行变更的内容 |
preview_restore |
预览撤销操作将还原和删除的内容,以及潜在冲突;返回一个 planId |
restore |
执行该撤销操作;除非指定 force,否则在出现冲突时拒绝执行 |
history |
查询近期的部署、拉取和恢复历史 |
工具层面对部分功能的剔除是刻意为之的:不支持镜像/删除模式,无法跳过备份,也不支持修剪删除备份。
无 Shell 权限的 Agent:Drop-box 队列机制
部分 Agent 环境具备文件写入能力但无法执行系统命令。在本地应用程序运行(npm start,或 Windows 上的 start.bat)期间,任何能够写文件的工具都可以将任务推入队列:
queue/.request.json { "action": "deploy", "site": "mysite", "dryRun": true, "note": "new homepage" } queue/.result.json ← 完成时写入,并在旁边附带 .log.txt
支持的操作:deploy、preview、pull、restore、backups、prune。
运行 npm start 会在 http://127.0.0.1:4646 启动一个轻量 Web 界面:支持添加站点、预览、部署、拉取、拖拽单次上传、浏览服务器文件以及查看历史。它仅响应来自本机自身的请求。
如果 Claude 拥有直接运行 CLI 的权限,可将 bin/claude-guard.js 添加为 PreToolUse 钩子。它能拦截 Shell 命令、队列文件及 API 调用中的 --mirror 与 --no-backup,并告知 Claude 被拦截的原因。人类用户仍可在终端中手动执行它们。在执行 npm install -g shipback-sftp 后,该文件位于 npm root -g 输出路径下的 shipback-sftp/bin/ 目录中。
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|PowerShell|Write|Edit|MultiEdit", "hooks": [{ "type": "command", "command": "node /path/to/shipback-sftp/bin/claude-guard.js" }] } ] } }
测试套件基于 node:test 构建,无需网络连接。部署、恢复、CLI 以及 MCP 服务器在测试运行时,都会针对测试内部启动的真实 SFTP 服务器完成端到端验证。
- 比较机制基于文件大小和 SHA-256。初次部署到已有文件的站点时,会读取服务器端相同大小文件的内容来进行比对。
- 备份与站点存放在同一台服务器上。它们用于防护误操作导致的损坏部署,无法防范虚拟主机账户本身的丢失。请同时保留主机商自带的备份。
- 当没有清册(Manifest)哈希值可比对时,对于两侧大小相同且大于 8 MB 的文件,会默认假定其未发生更改。
MIT © 2026 Aldo Hushi
支撑 aldo.today 稳定运行。出自 BlueprintAU。
English / 原文
Let the agent deploy. Keep the undo.
It's late, and the agent says "Deployed ✓". Then the client's contact form is gone. The agent overwrote a file someone had fixed by hand in cPanel last week, and shared hosting has no git revert.
Shipback deploys a folder to any SFTP host: cPanel, Plesk, DirectAdmin, a plain VPS. Every deploy can be undone with one command. You can run it yourself, script it, or hand it to Claude Code, Cursor or any MCP client.
- Backup before every overwrite. The server's copy of each file a deploy replaces is saved into
_backup-<time>/on the server, before the upload starts. - One-command undo.
--restoreputs those files back and removes the files that deploy added. - It won't undo over a live edit. If someone changed a file on the server after the deploy, restore stops and names the file. Nothing is touched unless you pass
--force, and even then the edited copy is backed up first. - Restores can be undone too. A restore backs up what it replaces, the same way a deploy does.
- Safe for agents. The MCP server has no switch to delete server files or skip the backup. A deploy only runs the exact plan the agent previewed: if a file changed in between, it refuses.
- Spots the drift.
--pull --dry-runshows what changed on the server since your last deploy, so edits made live in cPanel don't get overwritten by accident.
No git or SSH shell is needed on the server, just SFTP. It needs Node 18+. The tests run on Windows, macOS and Linux with Node 18, 20 and 22 on every push. Passwords are kept in the OS keychain and never pass through the model.
It has run in production. The deploy engine underneath has shipped aldo.today 235 times since September 2026 (2,907 files to a cPanel host). Restore and the MCP server are new in 0.1.
npm install -g shipback-sftp # installs the shipback and shipback-mcp commands
cd ~/sites # any folder with a sites.json in it (format below)
shipback mysite --dry-run # see the plan; asks for the password once and stores it
shipback mysite # deploy
shipback mysite --restore # changed your mind? undo it
No global install? npx -p shipback-sftp shipback mysite --dry-run works the same way. --sites <file> points at a sites.json in another folder.
From source: git clone https://github.com/aldohushi1-stack/shipback-sftp.git, npm install, then node bin/cli.js wherever these docs say shipback.
sites.json, one entry per site:
{
"sites": [
{
"name": "mysite",
"host": "server123.example-host.com",
"port": 22,
"username": "cpaneluser",
"remoteRoot": "/home/cpaneluser/public_html",
"localDir": "/Users/you/sites/mysite",
"ignore": ["drafts/", "*.psd"]
}
]
}
Many shared hosts run SFTP on their own port rather than 22 (check SSH Access in your control panel), and some only accept connections from allow-listed IPs. If a connection times out, check those two first. Use "privateKeyPath" instead of a password if your host has your key.
shipback <site> --dry-run # what a deploy would upload
shipback <site> # deploy: backup, upload, record
shipback <site> --local <folder> # deploy a different folder than localDir
shipback <site> --pull --dry-run # what changed on the server since the last deploy?
shipback <site> --pull # bring those changes down (local copies backed up first)
shipback <site> --backups # list backups: what each run overwrote, added, deleted
shipback <site> --restore --dry-run # what an undo of the latest deploy would do
shipback <site> --restore [<backup>] # undo it (or name an older _backup-… folder)
shipback <site> --prune --keep 10 # tidy old backups; refuses any holding the only copy of a file
shipback --history [site] # what went up, when, from where
Add --json to any command for machine-readable output. A refused restore exits with code 4.
--mirror (delete server files that aren't in the local folder) and --no-backup exist for humans who know they want them. The agent tools don't offer them, and the guard hook blocks Claude from typing them.
Each deploy writes _backup-YYYY-MM-DD-HHMM/ in the remote root. It holds:
- the server's copy of every file the deploy overwrote or deleted
.backup.json, a record of what the deploy added , and a SHA-256 of every file it left on the server
--restore reads that record and checks each live file first:
| Live file now | Restore does |
|---|---|
| exactly what the deploy uploaded | puts the old copy back (or removes it, if the deploy added it) |
| already the old copy | nothing; it counts as already right |
| different from both | conflict : stops, names the file, changes nothing (--force overrides) |
Before writing anything, restore saves the current live files into a new _backup-… folder with its own record, so --restore on that folder takes you forward again. It also clears the restored paths from .deploy-manifest.json, so the next deploy compares them by content instead of trusting stale hashes.
Backup folders are skipped by deploys and pulls, and are never deleted except by --prune.
Use it from an AI agent (MCP)
shipback-mcp (bin/mcp.js) is an MCP server over stdio.
Claude Code
claude mcp add shipback-sftp -- npx -y -p shipback-sftp shipback-mcp --sites /path/to/sites.json
Cursor, Claude Desktop, and others (mcp.json / claude_desktop_config.json):
{
"mcpServers": {
"shipback-sftp": {
"command": "npx",
"args": ["-y", "-p", "shipback-sftp", "shipback-mcp", "--sites", "/path/to/sites.json"]
}
}
}
On Windows, if the client can't start npx, use "command": "cmd" with "/c", "npx" at the front of args. After a global install, "command": "shipback-mcp" works too, and from a clone it's node with bin/mcp.js as the first arg.
Run any CLI command for a site once first (for example shipback mysite --backups). That stores the password in the OS keychain, where the MCP server reads it. The model never sees it. For CI you can set SFTP_PASSWORD in the server's environment instead.
| Tool | Does |
|---|---|
list_sites |
the configured sites (no passwords) |
preview_deploy |
what would upload; returns a planId |
deploy |
uploads that plan; refuses if anything changed since the preview |
check_server_changes |
files edited or added on the live server since the last deploy |
pull |
brings those down into the local folder (local copies backed up) |
list_backups |
backups, newest first, with what each run changed |
preview_restore |
what an undo would put back and remove, plus any conflicts; returns a planId |
restore |
runs that undo; refuses on conflicts unless force |
history |
recent deploys, pulls and restores |
What the tools leave out is deliberate: there is no mirror/delete, no way to skip the backup, and no pruning of backups.
Agents without a shell: the drop-box queue
Some agent setups can write files but can't run commands. While the local app is running (npm start, or start.bat on Windows), anything that can write a file can queue a job:
queue/<name>.request.json { "action": "deploy", "site": "mysite", "dryRun": true, "note": "new homepage" }
queue/<name>.result.json ← written when it finishes, with a .log.txt beside it
Actions: deploy, preview, pull, restore, backups, prune.
npm start opens a small UI on http://127.0.0.1:4646: add sites, preview, deploy, pull, drag in one-off uploads, browse the server, and see history. It only answers requests from your own machine.
If Claude also runs the CLI directly, add bin/claude-guard.js as a PreToolUse hook. It blocks --mirror and --no-backup in shell commands, queue files and API calls, and tells Claude why. You can still run them yourself in a terminal. After npm install -g shipback-sftp, the file is in the folder npm root -g prints, under shipback-sftp/bin/.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell|Write|Edit|MultiEdit",
"hooks": [{ "type": "command", "command": "node /path/to/shipback-sftp/bin/claude-guard.js" }]
}
]
}
}
The suite uses node:test and needs no network. Deploy, restore, the CLI and the MCP server are each also exercised end to end against a real SFTP server started inside the test run.
- It compares by size and SHA-256. The first deploy to a site that already has files reads the server copies of any same-size files to compare them.
- Backups live on the same server as the site. They protect against bad deploys, not against losing the hosting account. Keep your host's own backups too.
- Files over 8 MB with the same size on both sides are assumed unchanged when there is no manifest hash to compare.
MIT © 2026 Aldo Hushi
Runs aldo.today. Made at BlueprintAU.