Relayium

用 Relayium CLI 实现服务器到服务器直传(daemon 直连)

最近更新: 2026-09-01

当两台机器都是你自己的,并且彼此知道对方地址时,用 SSH 是白绕一道,走会合服务器更是纯粹的额外开销。daemon 直连正是为此而生:一台服务器监听,另一台通过证书固定的 TLS 1.3 连接直接推送过去。无需中继、无需 SSH、无需配对码——信任基于公钥,只需设置一次。

本指南涵盖启动监听端、向其推送、在首次联系时批准新的推送方、实现自动化,以及把监听端作为 systemd 服务运行。

开始之前

下面用到的都是 relayium CLI,所以没装的话先装上。在 macOS 或 Linux 上,一条命令就能把预编译二进制放进你的 PATH:

curl -fsSL https://relayium.com/install.sh | sh
  • 想自己挑文件,或在 Windows 上?从发布页下载二进制——relayium.com/cli 列出了所有安装方式(装了 Go 也可 go build ./cmd/relayium)。
  • relayium --version 可确认是否装好。不装这一步,下面的命令只会报 “command not found”。

启动监听端(在接收方)

你需要准备

  • 两台你自己掌控的机器,并且接收方的地址从发送方可达。主机名或裸 IP 都行。
  • 两端都装好 relayium。daemon 直连只说原生协议,所以这里没有 tar 兜底来救一台没装的机器。
  • 把监听端的端口对发送方开放——默认是 9031/TCP——主机防火墙和云安全组都要开。
  • 接收方在第一次推送时要有一个终端,好回答批准提示。没有终端的话,请改为预先授权发送方(见下文)。

在接收方服务器上,serve 监听推送并把它们写入某个目录。它默认长期运行;加上 --once 可以只接受一次传输就退出。你不需要预先共享任何东西——不用提前复制任何指纹:

  1. 建好推送要落地的目录。

    mkdir -p ~/inbox
  2. 只对发送方开放监听端的端口。把 203.0.113.7 换成发送方自己的地址——公网 IP,或者两台服务器同处一个内网时的内网 IP——并且把云安全组也收敛到同一个来源,而不是对整个互联网开放。

    sudo ufw allow from 203.0.113.7 to any port 9031 proto tcp
  3. 在终端里启动监听端,这样第一次推送时才有人回答批准提示。加 --once 只接一次传输就退出,加 --port 可以离开 9031。

    relayium serve --dir ~/inbox

监听端跑起来是什么样

serve 会写明它绑定的地址、写入的目录,以及本机自己的指纹。在还没有已批准的对端时,它还会说明每遇到一个新对端都会来问你。

relayium serve --dir ~/inbox
no authorized peers yet — you'll be asked to approve each new peer on its first push.
relayium serve: listening on [::]:9031, receiving into /home/you/inbox (fingerprint 5c1d9f04…)
  • serve 会并发处理连接,最多同时 64 个,并把文件落到 --dir 指定的目录下。
  • --dir 必须是已经存在、并且 serve 所属用户可写的目录。serve 会在绑定端口之前先检查这一点,所以路径写错会立刻失败,而不会变成一个跑起来却写不进去的监听端。
  • 默认端口是 9031;可用 --port 修改,并在防火墙上开放该端口。
  • 不加 --bind 时,serve 会监听所有网络接口(包括公网接口),安全完全依赖你的防火墙。加上 --bind 10.0.0.5(在隧道后面则用 --bind 127.0.0.1)可以直接把监听端本身限制在某个地址上。
  • 这一切和有没有登录 Relayium 账号无关。账号不会给任何人这台主机的文件访问权限:只有当推送方的指纹在本监听端自己的 authorized_fingerprints 中时,推送才会被接受。

向监听端推送(在发送方)

从发送方服务器,推送到接收方的 relayium:// 地址。第一次连接会固定接收方的指纹;此后每次连接都会校验它,指纹一旦变化,监听端就会拒绝连接,而不会默默放行——密钥掉包或中间人攻击会当场暴露,不会被当成可信。在第一次推送时,发送方会稍等片刻,等待接收方批准(下一步)。

  1. 在发送方服务器上执行推送。第一次连接时它会正好停在这里,等接收方批准。

    relayium push ./build.tar.zst relayium://receiver.example.com
  2. 在接收方回答那个提示——就是下一节的内容。之后推送会自己走完,以后的推送也不会再停在这里。

  3. 监听端不在 9031 时,在地址后面加上端口。

    relayium push ./build.tar.zst relayium://receiver.example.com:9040

推送成功是什么样

第一次接触时,发送方会学到并固定监听端的指纹,然后开始传输。接收方会记下推送方的指纹,并报告文件数和字节数。

# on the SENDER, first contact
learned receiver.example.com:9031 5c1d9f04… (added to known_hosts)
  build.tar.zst (48213004 bytes)

# on the RECEIVER
authorized 74318e3b… (added to /home/you/.config/relayium/authorized_fingerprints)
received 1 file(s), 48213004 bytes from 74318e3b…
  • 没有中继,也没有回退:如果监听端无法到达,推送就会失败——文件字节永远不会经过其他任何人转发。
  • 使用与其他模式相同的传输引擎:每个文件都做逐文件 SHA-256 校验,并先落到暂存区再安装。push 不续传——无论走这条路还是走 SSH:它会拒绝已存在的目标,所以中断之后要么补传缺失的路径,要么改用 relayium sync(它会接着传半截文件,而这个监听端会遵守,除非它是以 --no-resume 启动的)。

在首次推送时批准发送方(在接收方)

当一台新机器第一次向你的监听端推送时,serve(在终端中)会显示它的来源和指纹,并请你批准它——就像 SSH 首次连接时的提示,只不过是在接收方这一侧:

# 在接收方,当有新的发送方推送时:
Incoming push from 203.0.113.7:54021
  fingerprint: 74318e3b…
Accept and remember this peer? [y/N] y
  • 回答 y,该指纹就会记入 authorized_fingerprints;此后同一台机器的每次推送都会静默通过。
  • 指纹是一台机器的稳定身份(重启和 IP 变化都不受影响),因此批准对每个推送方来说只是一次性的步骤。
  • 推送方则在首次连接时获知监听端的密钥(首次使用即信任),并将其固定在 known_hosts 中。

实现自动化(或在没有终端的环境下运行)

由于已批准的指纹会一直保留,后续推送不再需要确认——因此 relayium push 可以直接接入部署脚本或 CI,做加密、可校验完整性的服务器到服务器投递。如果是定时往同一个目录跑的任务,请改用 relayium sync:push 会拒绝已存在的目标,所以反复往同一个固定路径 push 只有第一次会成功,之后都会被拒。当 serve 在没有终端的环境下运行(作为 systemd 服务、通过管道)时,它无法弹出提示,因此会拒绝未知的推送方;这时应改为预先授权它们。可以从推送方的 relayium id 获取指纹,或者从 serve 日志中「rejected unauthorized peer …」那一行复制它,然后:

# 在接收方:无需提示,预先授权一个发送方
relayium authorize --config-dir /etc/relayium 74318e3b...
  • 身份和信任文件存放在 ~/.config/relayium/ 中(可用 --config-dir 覆盖,例如作为服务时用 /etc/relayium)。
  • 给 authorize 传监听端运行时所用的同一个 --config-dir,否则它写进去的文件,监听端根本不会去读。以 --config-dir /etc/relayium 启动的服务,对应的是 relayium authorize --config-dir /etc/relayium <指纹>;不带参数的 authorize 写的是 ~/.config/relayium/authorized_fingerprints,推送仍然会被一直拒绝。
  • 正在运行的监听端会在下一次连接时认出新授权的指纹——不需要重启它。撤销则不对称:要收回授权,请删掉那一行并重启监听端。
  • authorize 是幂等的——对同一个指纹再次运行它不会有任何效果。
  • 如果你要的不是往里拷贝,而是镜像一个目录,请对同一个 relayium:// 目标使用 relayium sync。push 从不覆盖——接收方已存在的同名文件会被拒绝,并且拒绝信息里会指出是哪个路径;sync 则只发送有变化的部分,还可以把删除一并镜像过去。
  • sync --delete 会删除源端已经不存在的文件,但前提是监听端以 --allow-delete 启动;否则什么都不会删,并且会告知发送方。删除被限制在本次传输实际发送的那些顶层目录之内:镜像 ./site 只会动接收方 --dir 下的 site/,同级目录和无关文件永远不会被删除,空目录也只在这些根之内才会被清理,一次镜像多个源时各源之间也删不到对方。源是单个文件时作用范围就只有那一个文件,因此什么都不会删;源为空或无法解析时,两端都会直接拒绝删除,因为那等于「把所有东西都删掉」。

在 systemd 下运行监听端

要做一个常驻收件箱,把 serve 作为 systemd 服务运行。把 --config-dir 指向一个固定位置,例如 /etc/relayium,让身份在重启之间保持稳定,并交给 systemd 来保活:

# /etc/systemd/system/relayium-serve.service
[Unit]
Description=Relayium daemon-direct listener
After=network-online.target

[Service]
ExecStart=/usr/local/bin/relayium serve --dir /srv/inbox --config-dir /etc/relayium
Restart=always
User=relayium

[Install]
WantedBy=multi-user.target
  • 用 systemctl enable --now relayium-serve 启动它,并让它开机自启。
  • 先建好 /srv/inbox,并让服务用户可写。serve 会在绑定端口之前校验这个目录,所以配置有问题时单元会在启动时带着明确报错失败,而不会接下一个自己根本落不了盘的传输。
  • 授权新的推送方时要用同一个目录:relayium authorize --config-dir /etc/relayium <指纹>。不需要 systemctl restart——正在运行的监听端遇到不认识的指纹时会重新读取这个文件。
  • 让 /etc/relayium/id.key 只对服务用户可读——权限过于宽松时 relayium 会拒绝加载该密钥。
  • 如果这个单元只应该监听某一个地址,在 ExecStart 那一行加上 --bind。

推送不通的时候

先查的是可达性和信任这两件事:在发送方跑 ss -tinp,能看出监听端到底通没通;在接收方跑 relayium authorize,则是把被拒绝的发送方缺的那份信任补上。但推送出错的方式不止这两类——接收方磁盘满了、收件目录对运行它的用户不可写、某个文件传完后过不了完整性校验,这些都会自己报错——所以请先看眼前的报错,而不是假定它一定是下面四种之一。

现象、检查、修复

推送一直挂着,最后报连接错误。
# 在发送方,推送还在跑的时候——隔几秒跑两次
ss -tinp dst :9031
# ESTAB    说明够得着监听端,但不说明有没有进展
# SYN-SENT 那个端口上没人应答

SYN-SENT 意味着数据包根本没到达一个正在监听的套接字。先在接收方用 ss -tlnp | grep 9031 确认 serve 起着,再在主机防火墙和云安全组里把 9031/TCP 对发送方开放。ESTAB 只能证明够得着——一条已建立的连接照样可以闲着或者卡住——所以要区分「在传」和「卡住」,得隔几秒把这条检查跑两次,比较 -i 为该套接字打印的 bytes_acked 计数。这里没有中继通路,所以监听端不可达是硬失败,而不是变慢。

serve 日志里写着 “rejected unauthorized peer …”,推送失败。
# 在发送方
relayium id
# 74318e3b…

# 在接收方
relayium authorize --config-dir /etc/relayium 74318e3b…

serve 当时没有终端可问——比如跑在 systemd 下或接了管道——所以对未知指纹选择拒绝而不是信任。预先授权后再推一次即可:监听端每次遇到不认识的指纹时都会重新读取允许列表,所以这会在下一次连接时生效,无需重启。拒绝那行里的指纹,正是发送方 relayium id 打印出来的那个,而 authorize 是幂等的。如果还是失败,请给 authorize 也加上监听端自己的 --config-dir——把指纹写进了 serve 并不会读的另一个目录,是最常见的原因。

“fingerprint mismatch for receiver.example.com:9031”。
grep receiver.example.com ~/.config/relayium/known_hosts

监听端出示的密钥,和首次接触时固定下来的那把不一样。如果是你自己有意轮换了密钥,就删掉对应的 known_hosts 那一行再推。如果不是,先别动那一行,弄清楚密钥为什么变了,再发任何东西。

systemd 单元一启动就因为权限不安全而退出。
systemctl status relayium-serve
# secure: /etc/relayium/id.key has insecure permissions 0644; run: chmod 600 /etc/relayium/id.key
ls -l /etc/relayium/id.key

relayium 拒绝加载一把除属主之外还有人能读的私钥,这和 ssh 的规矩一样。对报错里给出的路径执行 chmod 600,确认它属于那个服务用户,然后重启单元。

常见问题

daemon 直连和通过 SSH 推送有什么不同?

通过 SSH 推送会把传输隧道进你的 SSH 连接,并且需要在远端有一个 SSH 账号。daemon 直连不需要 SSH,也不需要账号——两台服务器通过证书固定的 TLS,用证书指纹互相验证身份,当两台机器都是你自己的时候,这样更轻量。

我需要手动到处复制指纹吗?

不需要。在终端中,serve 会在每个新推送方首次推送时提示你批准它——显示其地址和指纹——并记住它,因此后续推送是静默的。只有在没有终端的非交互式场景(比如作为 systemd 服务运行,没有人来回应提示)时,你才需要用到 relayium id 或 relayium authorize。

身份和信任文件在哪里?

默认在 ~/.config/relayium/ 中(可用 --config-dir 覆盖)。id.key / id.crt 是这台主机的持久身份,known_hosts 保存着你推送过的监听端的指纹,authorized_fingerprints 则是监听端的推送方允许列表。

指纹变化了会怎样?

监听端会拒绝这次推送并发出警告。监听端的密钥在首次使用时就固定在 known_hosts 中,因此之后的变化——无论是主机重新生成了密钥,还是中间人攻击——都会遭到拒绝,而不会默默放行。只有在你确实主动轮换了密钥时,才应删除 known_hosts 中对应的那一行。

有没有中继回退?

没有。daemon 直连假定监听端地址是可达的;如果无法建立连接,就会失败。任何数据都绝不会经由 Relayium 代理——这正是这个模式的意义所在。

需要 Relayium 账号吗?登录之后别的机器就能访问我了吗?

都不需要、也不会。daemon 直连全程不会联系 Relayium,所以两端都不涉及账号。登录同样不会给任何人文件访问权限:只有当推送方的指纹在监听端自己的 authorized_fingerprints 文件里时,监听端才会接受它。账号登录和监听端授权是两件独立的事,谁都不蕴含谁。

这种场景该改用「设备收件箱」吗?

不该——CLI 上的收件箱只有接收这一侧。它接收的是你的账号发给这台机器的文件,而 CLI 并没有向收件箱发送的命令;要发给某个收件箱,请用网页版或原生 App。要在你自己掌控的两台服务器之间搬文件,serve 加 push 或 sync 才是直连的那条路,而且完全不需要账号。

推送会删掉接收方的文件吗?

普通的 push 既不会删除也不会覆盖——遇到同名冲突会拒绝,并指出是哪个路径。sync --delete 确实可以删除文件,但前提是监听端以 --allow-delete 启动,而且只在本次传输实际发送的那些顶层目录之内删除。接收方 --dir 下的其他任何东西都不在范围内;源为空或无法解析时,删除会被直接拒绝。

把你自己的两台服务器接起来做直接传输——无需中继、无需 SSH、无需配对码。

获取 CLI

继续阅读