PsiTransfer 2.4.5 常用 CLI 命令与工具详解

PsiTransfer 2.4.5 日常操作命令全集:Compose 服务管理、curl tus 分块上传/断点续传/打包下载全链路实测回显、bucket 清单与 admin 接口、数据目录巡检与孤儿清理。

PsiTransferNodejs文件传输文件分享自托管2026/11/01

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 APIcurl上传(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 档文件下载即焚,备份只能保住尚存文件。)

六、注意事项

  1. Upload-Metadata 值必须 base64:含中文的文件名直接传会报 tus meta prop missing,务必 echo -n <值> | base64;tusMeta.encode 只支持字符串类型;
  2. retention 是白名单校验:不在服务端 retentions 集合内的取值一律 400(集合见 /config.json 的 retentions 字段);
  3. one-time 是下载即焚:curl 调试时的一次 GET 就会消耗掉文件,验证阶段建议先用时长档;
  4. 上传 ID 是长随机串:<sid>++<uuid> 中的 ++ 是固定分隔符,脚本中注意转义与截取;
  5. x-passwd 与 bucket 密码是两回事:前者是站点级口令(服务端配置 uploadPass),后者是上传者自设、加密清单用的密码(password 元数据),互不影响;
  6. 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 标记(防管理页泄露密码文件直链)。需要核对内容时到对应下载页用口令访问。

八、参考链接