PsiTransfer 2.4.5 常用 CLI 命令与工具详解
PsiTransfer 2.4.5 日常操作命令全集:Compose 服务管理、curl tus 分块上传/断点续传/打包下载全链路实测回显、bucket 清单与 admin 接口、数据目录巡检与孤儿清理。
PsiTransfer 2.4.5 常用 CLI 命令与工具详解
目标:整理 PsiTransfer 2.4.5 的日常操作命令全集——服务进程管理(Docker Compose)、基于 curl 的上传/下载/管理 API 全链路(tus 分块协议)、数据目录维护命令,全部命令逐条实测并附真实回显。已在 Ubuntu 24.04 LTS(x86_64)+ PsiTransfer 2.4.5(官方镜像
psitrax/psitransfer:2)环境实测通过(2026-11-01)。
一、环境信息
- 操作系统:Ubuntu 24.04.5 LTS(x86_64)
- 软件版本:PsiTransfer 2.4.5(镜像标签
psitrax/psitransfer:2,2026-11-01 实查为最新稳定版) - Docker:29.9.2 / Docker Compose:v5.6.1
- 服务地址:http://192.168.1.172:8130/(宿主 8130 → 服务 3000)
- 数据目录:
/opt/psitransfer/data/ - 演示口令:管理页
q1w2e3r4(PSITRANSFER_ADMIN_PASS);本文默认未启用上传口令(uploadPass=false),启用后的差异在 3.5 节说明
二、工具概览
PsiTransfer 没有独立的 CLI 二进制工具(仓库内 cli.js 仅是 require('./app') 的启动入口),日常运维由三类命令组合完成:
| 工具类别 | 命令载体 | 用途 |
|---|---|---|
| 服务进程管理 | docker compose 子命令 | 启停、日志、状态、升级 |
| HTTP API | curl | 上传(tus 协议)、下载、清单、管理数据 |
| 数据维护 | ls / du / tar 等核心命令 | 数据目录巡检、备份、孤儿清理 |
上传链路基于 tus.io 分块续传协议(官方 README 声明),服务端在 /files 路径实现了 create 扩展:POST 创建上传会话 → PATCH 追加分块 → HEAD 查询进度,全程可断点续传。
三、curl 上传下载全链路
3.1 探测服务 tus 能力
操作任何上传前,先探测 /files 端点支持的协议能力:
curl -s -X OPTIONS http://localhost:8130/files -i
实测回显:
HTTP/1.1 204 No Content
Tus-Extension: create
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST,HEAD,PATCH,OPTIONS
Access-Control-Allow-Headers: Content-Type,Location,Tus-Extension,Tus-Max-Size,Tus-Resumable,Tus-Version,Upload-Defer-Length,Upload-Length,Upload-Metadata,Upload-Offset,X-HTTP-Method-Override,X-Requested-With
Access-Control-Max-Age: 86400
Content-Length: 0
Date: Sun, 01 Nov 2026 02:46:32 GMT
Connection: keep-alive
Keep-Alive: timeout=5
关键字段:Tus-Extension: create 表示支持「创建上传会话」扩展;POST,HEAD,PATCH 为核心方法集。
准备测试文件:
echo 'Hello PsiTransfer 2.4.5 test file line1' > hello.txt
dd if=/dev/urandom of=random.bin bs=1k count=64
ls -l hello.txt random.bin
实测回显:
65536 bytes (66 kB, 64 KiB) copied, 0.000712557 s, 92.0 MB/s
-rw-r--r-- 1 root root 40 Nov 1 10:46 hello.txt
-rw-r--r-- 1 root root 65536 Nov 1 10:46 random.bin
3.2 创建上传会话(POST /files)
Upload-Metadata 头携带业务元数据,格式为 键 base64值 逗号分隔;name(文件名)、sid(bucket ID)、retention(保留档)三项必填:
curl -s -X POST http://localhost:8130/files -D - -o /dev/null \
-H 'Tus-Resumable: 1.0.0' \
-H 'Upload-Length: 40' \
-H "Upload-Metadata: name $(echo -n hello.txt | base64),sid $(echo -n demo1234 | base64),retention $(echo -n one-time | base64)"
实测回显:
HTTP/1.1 201 Created
Vary: X-HTTP-Method-Override, Origin
Access-Control-Expose-Headers: Content-Type,Location,Tus-Extension,Tus-Max-Size,Tus-Resumable,Tus-Version,Upload-Defer-Length,Upload-Length,Upload-Metadata,Upload-Offset,X-HTTP-Method-Override,X-Requested-With
Tus-Resumable: 1.0.0
Location: /files/demo1234++ddeaa026-57d8-4d6a-9c6c-3ff35f9dbd5c
Date: Sun, 01 Nov 2026 02:46:42 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
要点:
Location响应头即上传 ID,格式<sid>++<uuid>,后续 PATCH/HEAD/下载都靠它;retention必须是服务端保留档集合的成员(one-time或秒数),非法值返回400:
curl -s -X POST http://localhost:8130/files -o - -H 'Tus-Resumable: 1.0.0' -H 'Upload-Length: 40' -H "Upload-Metadata: name $(echo -n x.txt | base64),sid $(echo -n demoerr | base64),retention $(echo -n 999 | base64)"
实测回显:
HTTP/1.1 400 Bad Request
invalid tus meta prop retention. Value 999 not in [3600,21600,86400,259200,604800,1209600,2419200,4838400,one-time]
- 可选元数据:
password <base64>为 bucket 设密码(见 3.6 节效果)。
3.3 分块上传(PATCH)与断点续传
把 64KB 的 random.bin 分两块(各 32KB)上传到 demo1234 bucket(先按 3.2 创建 retention=604800 的会话,得到上传 ID demo1234++a20359ce-04a8-4239-bc16-f372590a0d53):
head -c 32768 random.bin > part1.bin
tail -c +32769 random.bin > part2.bin
# 第 1 块:offset 从 0 开始
curl -s -X PATCH http://localhost:8130/files/demo1234++a20359ce-04a8-4239-bc16-f372590a0d53 \
-D - -o /dev/null \
-H 'Tus-Resumable: 1.0.0' \
-H 'Upload-Offset: 0' \
-H 'Content-Type: application/offset+octet-stream' \
--data-binary @part1.bin
实测回显:
HTTP/1.1 204 No Content
Upload-Offset: 32768
(完整响应含 Tus 头组,节选关键字段,下同)
# 第 2 块:offset 从服务端确认值继续
curl -s -X PATCH http://localhost:8130/files/demo1234++a20359ce-04a8-4239-bc16-f372590a0d53 \
-D - -o /dev/null \
-H 'Tus-Resumable: 1.0.0' \
-H 'Upload-Offset: 32768' \
-H 'Content-Type: application/offset+octet-stream' \
--data-binary @part2.bin
实测回显:
HTTP/1.1 204 No Content
Upload-Offset: 65536
Upload-Offset 达到 Upload-Length(65536)即上传完成。断点续传正是这个机制:传输中断后客户端先 HEAD 拿服务端已收到的 offset,再从该 offset 继续 PATCH——Upload-Offset 不匹配服务端进度时请求会被拒绝,保证块不重不漏。
3.4 查询上传进度(HEAD)
curl -s -I http://localhost:8130/files/demo1234++a20359ce-04a8-4239-bc16-f372590a0d53 -H 'Tus-Resumable: 1.0.0'
实测回显(节选):
HTTP/1.1 200 OK
Upload-Offset: 65536
Upload-Length: 65536
3.5 下载文件(GET /files/:fid)与 one-time 语义
下载 3.2 创建的 one-time 文件(上传 ID demo1234++ddeaa026-57d8-4d6a-9c6c-3ff35f9dbd5c):
curl -s http://localhost:8130/files/demo1234++ddeaa026-57d8-4d6a-9c6c-3ff35f9dbd5c -D - -o hello.down.txt
head -1 hello.down.txt
实测回显:
HTTP/1.1 200 OK
Content-Disposition: attachment; filename="hello.txt"
Content-Length: 40
(响应头节选;Content-Disposition 带回原始文件名)
one-time 档在首次下载完成后立即删除文件,再访问同一 URL:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:8130/files/demo1234++ddeaa026-57d8-4d6a-9c6c-3ff35f9dbd5c
实测回显:
HTTP 404
启用上传口令后(PSITRANSFER_UPLOAD_PASS=<口令>),上传与 /config.json 都要带 x-passwd 头,实测三态:
curl -s -o /dev/null -w 'no x-passwd: HTTP %{http_code}\n' -X POST http://localhost:8130/files -H 'Tus-Resumable: 1.0.0' -H 'Upload-Length: 40'
curl -s -o /dev/null -w 'wrong passwd: HTTP %{http_code}\n' -X POST http://localhost:8130/files -H 'x-passwd: bad' -H 'Tus-Resumable: 1.0.0' -H 'Upload-Length: 40'
curl -s -o /dev/null -w 'right passwd: HTTP %{http_code}\n' -H 'x-passwd: upload123' http://localhost:8130/config.json
实测回显:
no x-passwd: HTTP 401
wrong passwd: HTTP 403
right passwd: HTTP 200
3.6 查看 bucket 清单(GET /:sid.json)
curl -s http://localhost:8130/demo1234.json | python3 -m json.tool
实测回显:
{
"items": [
{
"uploadLength": 65536,
"metadata": {
"name": "random.bin",
"sid": "demo1234",
"retention": "604800",
"uploadLength": "65536",
"key": "a20359ce-04a8-4239-bc16-f372590a0d53",
"createdAt": "1793501240755"
},
"size": 65536,
"offset": 65536,
"key": "a20359ce-04a8-4239-bc16-f372590a0d53",
"url": "/files/demo1234++a20359ce-04a8-4239-bc16-f372590a0d53"
}
],
"config": {
"maxPreviewSize": 2097152
}
}
items[].url 就是单文件下载地址;offset < size 表示仍在上传中。设置了 bucket 密码(上传时带 password 元数据)的文件在此接口返回 AES 密文串而非明文对象,实测形态:
U2FsdGVkX18vgAHB4hpB+PDnD1mmMs4im58GChw7pTINcqtergpsRAAJxpo796Igdv...
(输出节选;接收端用上传者告知的口令解密后得到文件清单)
3.7 整包下载(zip/tar.gz)
打包链接的文件名是 MD5(所有文件 key 按清单顺序逗号拼接),先算再下:
KEYS=$(curl -s http://localhost:8130/demo1234.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(','.join(i['key'] for i in d['items']))")
MD5=$(python3 -c "import hashlib;print(hashlib.md5('$KEYS'.encode()).hexdigest())")
echo "md5=$MD5"
curl -s "http://localhost:8130/files/demo1234++${MD5}.zip" -o bucket.zip -D - | grep -iE 'HTTP|content-disposition'
实测回显:
md5=78964ebf79d4f4922595f6ed44198dc1
HTTP/1.1 200 OK
Content-Disposition: attachment; filename="demo1234.zip"
完整性校验(解包比对 MD5):
python3 -c "import zipfile,hashlib; z=zipfile.ZipFile('bucket.zip'); print(z.namelist()); print(hashlib.md5(z.read('random.bin')).hexdigest())"
md5sum random.bin
实测回显:
['random.bin']
51a4581e751409948d0770bc3043c9e3
51a4581e751409948d0770bc3043c9e3
两端 MD5 一致。打包格式改用 .tar.gz 后缀即得 gzip 压缩包;官方声明 zip/tar.gz 整包下载不支持断点续传,超大集合建议分批单文件下载。
3.8 管理数据(GET /admin/data.json)
curl -s -H 'x-passwd: q1w2e3r4' http://localhost:8130/admin/data.json | python3 -m json.tool
实测回显:
{
"demo1234": [
{
"uploadLength": 65536,
"metadata": {
"name": "random.bin",
"sid": "demo1234",
"retention": "604800",
"uploadLength": "65536",
"key": "a20359ce-04a8-4239-bc16-f372590a0d53",
"createdAt": "1793501240755",
"lastDownload": 1793501379801
},
"size": 65536,
"offset": 65536,
"key": "a20359ce-04a8-4239-bc16-f372590a0d53",
"url": "/files/demo1234++a20359ce-04a8-4239-bc16-f372590a0d53"
}
]
}
全站所有 bucket 一览,lastDownload 时间戳可用于巡检「从未被下载」的滞留文件。认证三态(无头 401 / 错口令 403 / 正确 200)实测回显:
HTTP 401
HTTP 403
HTTP 200
四、服务进程管理命令
均在部署目录 /opt/psitransfer/ 内执行:
# 状态一览
docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
psitransfer-2.4.5-app psitrax/psitransfer:2 "docker-entrypoint.s…" app 5 minutes ago Up 5 minutes 0.0.0.0:8130->3000/tcp, [::]:8130->3000/tcp
# 启动日志(确认监听)
docker logs psitransfer-2.4.5-app
实测回显:
PsiTransfer listening on http://0.0.0.0:3000
# 重启(改配置文件后)
docker compose restart
# 停止 / 启动
docker compose down
docker compose up -d
# 升级镜像并重建
docker compose pull && docker compose up -d
五、数据目录维护命令
5.1 结构巡检
ls /opt/psitransfer/data/
du -sh /opt/psitransfer/data/
ls -la /opt/psitransfer/data/demo1234/
实测回显:
demo1234
demosec
124K /opt/psitransfer/data
total 108
drwxr-xr-x 4 1000 1000 4096 Nov 1 10:49 .
-rw-r--r-- 1 1000 1000 65536 Nov 1 10:49 a20359ce-04a8-4239-bc16-f372590a0d53
-rw-r--r-- 1 1000 1000 424 Nov 1 10:49 a20359ce-04a8-4239-bc16-f372590a0d53.json
(节选)每个 bucket 一个子目录;文件本体以 UUID 命名,旁挂同名 .json 元数据文件。
5.2 孤儿文件清理
「有本体无 .json」或「从未传完的 partial」会占磁盘但不出现在任何下载页,找出并人工确认后删除:
cd /opt/psitransfer/data
for f in */*; do
case "$f" in
*.json) ;;
*) [ -f "$f.json" ] || echo "orphan: $f" ;;
esac
done
实测回显(示例环境存在一个下载后遗留的本体文件):
orphan: demo1234/ddeaa026-57d8-4d6a-9c6c-3ff35f9dbd5c
确认后 rm /opt/psitransfer/data/demo1234/ddeaa026-57d8-4d6a-9c6c-3ff35f9dbd5c 移除。该循环是一次性巡检脚本(属运维排障逻辑而非部署命令),逐条输出结果人工复核,不做静默批量删除。
5.3 备份
tar czf /opt/psitransfer/psitransfer-data-$(date +%F).tar.gz -C /opt/psitransfer data
ls -lh /opt/psitransfer/*.tar.gz
实测回显:
-rw-r--r-- 1 root root 1.2K Nov 1 11:05 /opt/psitransfer/psitransfer-data-2026-11-01.tar.gz
(one-time 档文件下载即焚,备份只能保住尚存文件。)
六、注意事项
Upload-Metadata值必须 base64:含中文的文件名直接传会报tus meta prop missing,务必echo -n <值> | base64;tusMeta.encode只支持字符串类型;retention是白名单校验:不在服务端retentions集合内的取值一律 400(集合见/config.json的retentions字段);- one-time 是下载即焚:curl 调试时的一次 GET 就会消耗掉文件,验证阶段建议先用时长档;
- 上传 ID 是长随机串:
<sid>++<uuid>中的++是固定分隔符,脚本中注意转义与截取; x-passwd与 bucket 密码是两回事:前者是站点级口令(服务端配置uploadPass),后者是上传者自设、加密清单用的密码(password元数据),互不影响;sid即 URL 路径:bucket ID 会成为下载页 URL 的一段(/<sid>),用语义化 ID(如proj-report-2026)便于口口相传,也便于服务端按目录巡检。
七、常见问题
Q1:PATCH 返回 460/409 之类错误(offset 不匹配)?
tus 协议要求 Upload-Offset 精确等于服务端已收字节数。中断恢复时先 HEAD(带 Tus-Resumable: 1.0.0 头)取回 Upload-Offset 再续传,不要凭本地记忆猜 offset。
Q2:下载返回 404 但文件明明上传过?
三个最常见原因按序排查:其一,one-time 档已被首次下载消耗(3.5 节实测);其二,maxAge(默认 75 天)兜底清理已回收该文件;其三,下载 URL 拼错——格式固定为 /files/<sid>++<key>,key 取清单接口的 items[].key 而非文件名。
Q3:整包下载的 MD5 文件名怎么拼?
MD5 的输入是「清单接口 items[] 中各文件 key 按返回顺序以英文逗号拼接」的字符串(3.7 节实测 a20359ce-… 单文件即其自身 key 的 MD5 78964ebf…)。顺序以接口返回为准,不要按文件名重排。
Q4:日志周期性出现 NotFoundError at Store.info (/app/lib/store.js:65)?
清理扫描对「元数据在、本体不在」的记录(partial、one-time 竞态窗口)抛出的噪音报错,主流程不受影响。按 5.2 清理孤儿文件并缩短 maxAge 可逐步减少出现频率。
Q5:/admin/data.json 返回的条目里没有 url 字段?
该 bucket 的文件设置了密码:管理接口会抹掉 url/key/密码字段只留 _password: true 标记(防管理页泄露密码文件直链)。需要核对内容时到对应下载页用口令访问。
八、参考链接
- 官方仓库:https://github.com/psi-4ward/psitransfer (README 上传协议与特性声明,2026-11-01 查询)
- tus 协议:https://tus.io/
- 服务端路由源码:
/app/lib/endpoints.js(2.4.5 版实测读取,本文全部接口行为与其一致) - 本专栏配套:《PsiTransfer 2.4.5 介绍与选型指南》、《PsiTransfer 2.4.5 Docker Compose 部署指南》、《PsiTransfer 2.4.5 配置参数详解》