跳转至

无头 Linux 服务器

当你要在没有桌面会话的 Linux 机器上运行 orca serve 时,请使用本指南,例如 Ubuntu VPS 或远程构建机。

orca serve 会启动 Orca 运行时,而不会打开桌面窗口。在 Linux 上,打包的 AppImage 仍然需要 Electron 在启动时期望的库。当前的 Orca 构建在 orca serve 未设置 DISPLAY 时会自动启动 Xvfb,但必须先安装 Xvfb。不需要单独的 D-Bus 会话。当设置了 DISPLAY 时,Orca 会使用该显示,而不是启动一个竞争的 Xvfb 进程,前提是该显示可用:其 socket 必须存在,并且如果存在 X 锁文件,它必须指向一个正在运行的进程。如果某个 DISPLAY 的锁指向一个已终止的进程,Orca 会拒绝使用它而不是替换它,并且 orca serve 会退出——请取消设置 DISPLAY,让 Orca 启动自己的 Xvfb。一个完全没有任何锁的已发布 socket(例如容器绑定挂载 /tmp/.X11-unix,或 WSLg)会被接受。

支持的部署矩阵涵盖 Ubuntu 20.04、22.04 和 24.04,以及当前 Debian stable——任何 glibc 2.31 或更新版本均可(参见 Linux glibc 兼容性)。在其他 Debian 衍生发行版中,包名可能不同。

Ubuntu 和 Debian 前置条件

安装 CLI 工具、Xvfb 以及 Electron 链接所依赖的共享库。最小化的服务器或容器镜像不包含任何 Electron 库,此时 orca serve 会在 Electron 启动前失败:

sudo apt-get update
sudo apt-get install -y \
  curl file jq xvfb zlib1g-dev ca-certificates git \
  libgtk-3-0t64 libnss3 libatk1.0-0t64 libatk-bridge2.0-0t64 libgbm1 libasound2t64 \
  libxtst6 libcups2t64 libdrm2 libxkbcommon0 libpango-1.0-0 libcairo2 libatspi2.0-0t64 \
  libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxrender1 libx11-xcb1 \
  libxcb-dri3-0 libxss1

该命令适用于 Ubuntu 24.04 及更高版本,以及 Debian 13 及更高版本。这些发行版完成了 64 位 time_t 迁移,其中六个包被重命名并带有 t64 后缀。在 Ubuntu 20.04、Ubuntu 22.04 和 Debian 12 上,请替换为不带后缀的名称:

  • libgtk-3-0t64 变为 libgtk-3-0
  • libatk1.0-0t64 变为 libatk1.0-0
  • libatk-bridge2.0-0t64 变为 libatk-bridge2.0-0
  • libasound2t64 变为 libasound2
  • libcups2t64 变为 libcups2
  • libatspi2.0-0t64 变为 libatspi2.0-0

其他名称在所有受支持的发行版中都相同。替换不是对称的,因此请使用与发行版匹配的列表。在 Ubuntu 20.04、Ubuntu 22.04 或 Debian 12 上使用 t64 名称会立即失败,并出现 E: Unable to locate package libgtk-3-0t64。反方向上,旧名称大多仍可解析,因为每个重命名的包都通过 Provides: 声明了其不带后缀的名称——但在 Ubuntu 24.04 上,universe 中的 liboss4-salsa-asound2 也声明了 libasound2 这个名称。apt 不会在两个提供者之间做出选择,并以 E: Package 'libasound2' has no installation candidate 退出,这会中止整条安装命令,导致没有任何库被安装。

在 Ubuntu 20.04 和 22.04 上,安装 libfuse2 以通过 FUSE 执行 AppImage。在 Ubuntu 24.04 和 Debian 13 上,该包为 libfuse2t64,但普通的 libfuse2 名称在那里也能解析,因为没有其他包提供它。FUSE 是可选的:如果没有它,请使用 AppImage 支持的提取路径。CLI 注册会自动执行一次此操作,因此已注册的命令不需要 FUSE。

下载并使 AppImage 可执行:

sudo mkdir -p /opt/orca
sudo curl -L https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage \
  -o /opt/orca/orca-linux.AppImage
sudo chmod +x /opt/orca/orca-linux.AppImage

要在没有 FUSE 的情况下提取它,请以 root 身份运行提取,因为安装目录由 root 拥有:

cd /opt/orca
sudo ./orca-linux.AppImage --appimage-extract
sudo chmod -R a+rX /opt/orca/squashfs-root
/opt/orca/squashfs-root/AppRun serve --port 6768

只要提取操作以与服务器不同的用户身份运行,就需要执行 chmod:--appimage-extract 会以 drwx------ 权限创建 squashfs-root,所有者为执行提取的用户,因此其他任何人——包括专用服务用户——甚至无法遍历它,运行会在 Electron 启动前失败。

Docker 通常没有 FUSE 设备。请一次性使用 --appimage-extract 或 --appimage-extract-and-run;两者都不需要特权容器。extract-and-run 包装器可能会在 Orca 启动前打印提取路径,因此要求 stdout 只包含就绪 JSON 的自动化流程应只提取一次,然后调用 squashfs-root/AppRun。

如果 Xvfb 安装在 /usr/bin 以外的位置,请确认 systemd 之后能找到它:

command -v Xvfb

在前台运行

在创建服务之前,先以前台方式运行一次:

LIBGL_ALWAYS_SOFTWARE=1 /opt/orca/orca-linux.AppImage serve --port 6768

对于远程客户端,请传递它们应使用来访问此服务器的地址。对于私有服务器,Tailscale 地址通常是最安全的选择:

LIBGL_ALWAYS_SOFTWARE=1 /opt/orca/orca-linux.AppImage serve \
  --port 6768 \
  --pairing-address 100.64.1.20

--pairing-address 只是向客户端通告的地址。它不会更改监听器的绑定地址。Orca 会绑定其 WebSocket 监听器,然后当地址省略端口时,将实际绑定的端口与通告的主机组合起来。请使用可到达的 LAN/Tailscale 主机名或 IP,或完整的反向代理 URL,例如 https://orca.example.com/runtime(http(s) 会被规范化为 ws(s))。通配符地址,例如 *、0.0.0.0 和 ::,不能被通告。

在监听器绑定和配对初始化完成后,该命令会向 stdout 写入一个就绪块:

Orca server ready
Bound endpoint: ws://0.0.0.0:6768
Advertised endpoint: ws://100.64.1.20:6768
Pairing URL: orca://pair?code=...

对于 supervisor(进程管理器),请请求带版本号的单行 JSON 契约:

/opt/orca/orca-linux.AppImage serve --port 6768 \
  --pairing-address 100.64.1.20 --json

实际输出是一行紧凑内容;此示例为可读性进行了格式化显示:

{
  "type": "orca_server_ready",
  "schemaVersion": 1,
  "runtimeId": "...",
  "endpoint": "ws://0.0.0.0:6768",
  "boundEndpoint": "ws://0.0.0.0:6768",
  "advertisedEndpoint": "ws://100.64.1.20:6768",
  "managedWslCliReconciliation": "settled",
  "pairing": {
    "available": true,
    "url": "orca://pair?code=...",
    "endpoint": "ws://100.64.1.20:6768",
    "deviceId": "...",
    "webClientUrl": "...",
    "scope": "runtime",
    "qr": null
  }
}

endpoint 是 boundEndpoint 的兼容性别名;新的自动化应使用明确的 bound 和 advertised 字段。

当服务器仍可用但无法生成配对 offer 时,pairing 仍是一个对象,包含 available:false、稳定的 reason 以及面向操作者的 guidance;它绝不会被静默省略。--recipe-json 更严格,会因该原因退出,因为其契约要求提供配对 URL。使用 Ctrl+C 停止前台服务器。稳定的原因包括 disabled_by_operator、websocket_unavailable、device_registry_unavailable、e2ee_key_unavailable 和 invalid_advertised_endpoint。

Systemd 服务

创建专用的服务用户和安装目录。以该用户而非 root 身份运行服务,以便 AppImage 可以保持 Chromium 沙箱启用。保持安装目录由 root 拥有:服务需要读取并执行 AppImage,但必须不能替换它或回滚产物。

sudo useradd --system --create-home --shell /usr/sbin/nologin orca
sudo chown root:root /opt/orca /opt/orca/orca-linux.AppImage
sudo chmod 755 /opt/orca /opt/orca/orca-linux.AppImage
# Only if you ran --appimage-extract: extraction leaves squashfs-root root-only.
sudo chmod -R a+rX /opt/orca/squashfs-root

最后一行很重要,因为如果没有它,本指南的两部分会组合得很糟糕。--appimage-extract 会将 squashfs-root 写入为 drwx------ root root,因此 orca 服务用户无法读取或遍历已提取的目录树,单元会在启动时失败。仅执行 chmod 755 /opt/orca 无法深入到其中。

对于大多数主机,一个 orca serve 服务就足够了,因为当不存在显示时,Orca 会在显示 :99 上启动 Xvfb:

# /etc/systemd/system/orca-serve.service
[Unit]
Description=Orca runtime server
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=simple
User=orca
WorkingDirectory=/home/orca
Environment=LIBGL_ALWAYS_SOFTWARE=1
ExecStart=/opt/orca/orca-linux.AppImage serve --port 6768 --pairing-address 100.64.1.20
StandardOutput=journal
StandardError=journal
KillMode=mixed
Restart=on-failure
RestartPreventExitStatus=3
RestartSec=5

[Install]
WantedBy=multi-user.target

将 100.64.1.20 替换为客户端应使用的局域网、Tailscale、隧道或公共主机名。

KillMode=mixed 仅向 Orca 主进程发送优雅停止信号,如果停止超时,则保留 systemd 针对整个 cgroup 的 SIGKILL 回退。这使 Orca 能够保持其拥有的 Xvfb 存活,直到 Electron 干净断开连接。它不会保留分离的终端守护进程:该守护进程及其 PTY 仍位于 orca-serve.service 的 cgroup 中,并会在停止完成时被终止。因此,每次 systemctl stop 或 restart 都会结束活动终端和 agent 进程,尽管其持久化布局和终端历史仍会保留。

退出状态 3 表示另一个进程已经拥有此 userData 配置文件,因此 RestartPreventExitStatus=3 会停止单元,而不是重试无法成功的启动。其他任何永久性启动故障都限制为每 5 分钟最多启动 5 次;systemd 的默认值(10 秒窗口、5 次启动)在 RestartSec=5 下永远不会触发,而这正是一次失败启动可能重启数千次的原因。启动限制也计入操作者发起的启动,因此一旦触发,systemd 会拒绝普通的 systemctl start,直到 5 分钟窗口过去。先运行 sudo systemctl reset-failed orca-serve.service 清除它——升级 和 回滚 脚本已经会这样做。在早于 230 的 systemd 上,这两个指令拼写为 StartLimitInterval=/StartLimitBurst=,并且应放在 [Service] 中;Ubuntu 20.04 是 Orca 支持的最旧基础系统,附带 systemd 245。

启用服务:

sudo systemctl daemon-reload
sudo systemctl enable --now orca-serve.service
sudo journalctl -u orca-serve.service -f

journalctl -o cat 会移除日志元数据,但仍会混合服务的 stdout 和 stderr。将每一行解析为 JSON,并在将服务视为就绪之前要求就绪类型和 schema:

sudo journalctl -u orca-serve.service -o cat \
  | jq -Rrc 'fromjson? | select(.type == "orca_server_ready" and .schemaVersion == 1)'

有界健康检查应在其启动超时内要求满足该契约;否则,检查更早的诊断信息以获取精确的配对原因、监听器错误或缺失库。

受管理的 Xvfb 服务

如果你希望在 systemd 中自行管理虚拟显示的生命周期,请将 Xvfb 作为单独的服务运行,并为 Orca 设置 DISPLAY=:99。

# /etc/systemd/system/orca-xvfb.service
[Unit]
Description=Virtual X display for Orca
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -nolisten tcp
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

如果 command -v Xvfb 返回了不同的路径,请将 ExecStart 更新为该绝对路径。

然后为 Orca 服务添加显示依赖:

# /etc/systemd/system/orca-serve.service
[Unit]
Description=Orca runtime server
After=network-online.target orca-xvfb.service
Wants=network-online.target orca-xvfb.service
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=simple
User=orca
WorkingDirectory=/home/orca
Environment=DISPLAY=:99
Environment=LIBGL_ALWAYS_SOFTWARE=1
ExecStart=/opt/orca/orca-linux.AppImage serve --port 6768 --pairing-address 100.64.1.20
Restart=on-failure
RestartPreventExitStatus=3
RestartSec=5

[Install]
WantedBy=multi-user.target

启用两个单元:

sudo systemctl daemon-reload
sudo systemctl enable --now orca-xvfb.service orca-serve.service

CLI 安装说明

已注册的 Linux CLI 命令是 orca-ide,而不是 orca,以避免遮蔽 GNOME Orca 屏幕阅读器。桌面管理的终端会获得一个终端范围的裸 orca 垫片。打包的无头 orca serve 还会尽力为服务用户自己的 shell 在 $HOME/.local/bin/orca 创建一个分发器,以便 Claude Teams 启动器可以解析其裸命令;它不会替换其他用户的 orca。在位于该服务用户受管环境之外的普通 shell 中,请将下文命令中的 orca 替换为 orca-ide。

在无头主机上,你不需要仅为了运行服务器就打开桌面 UI。直接调用 AppImage:

/opt/orca/orca-linux.AppImage serve --help

以 root 身份运行 AppImage 时,需要在命令前使用 Chromium 的 --no-sandbox 开关:

/opt/orca/orca-linux.AppImage --no-sandbox serve --port 6768

这会禁用一个安全边界。请优先使用专用的非特权服务用户,尤其是在监听器可从 localhost 之外访问时。

Linux CLI 的名称是 orca-ide,而不是 orca,因此它永远不会遮蔽位于 /usr/bin/orca 的 GNOME Orca 屏幕阅读器。.deb 和 .rpm 包在安装时会将 orca-ide 自行放入 PATH;对于 AppImage,当 CLI 注册时,它会作为 ~/.local/bin/orca-ide 出现。

打包的 orca serve 启动还会向 ~/.local/bin 写入一个裸 orca,它会 exec 同一个启动器,这就是为什么下文的 skills 命令可以输入为 orca。它在启动时写入,因此它绝不是启动服务器的命令——首次启动是 orca-ide serve,或如上所述直接调用 AppImage。该写入是尽力而为的:它以打包构建为条件,当没有可解析的捆绑启动器时跳过,并且当 Orca 不拥有的文件已经占用该名称时跳过(所有权是文件第二行上的标记)。真正运行屏幕阅读器的主机会保留其自己的 orca。

配对故障排除

  • 配对提议是一种包含设备凭据和 E2EE 材料的能力。请仅与预期客户端共享,不要将其放入代理访问日志中。
  • boundEndpoint 是进程监听的位置;advertisedEndpoint 是客户端拨号的位置。如果 DNS、防火墙、Docker 端口发布、Tailscale 策略或反向代理未将通告端点路由到绑定端口,那么即使提议看起来有效,仍然无法连接。
  • 省略的通告端口会使用实际绑定端口,包括冲突后选择的回退端口。显式代理端口会被保留。因此,端口不匹配意味着提供的外部路由不正确,而不是 Orca 更改了它。
  • 反向代理必须支持 WebSocket 升级并路由通告路径。当 TLS 在代理处终止时,请使用 wss:// 或 https://;不要通过仅限 HTTPS 的端点通告 ws://。
  • 支持主机名、IPv4、带括号的 IPv6 和原始 IPv6 字面量。IPv6 仍然需要可到达的 IPv6 监听器/网络路径。
  • 来自无头服务器不会触发到已配对手机的后台推送通知:代理完成检测在桌面渲染器中运行,而 serve 模式下不会启动它,因此即使手机成功注册,也没有任何内容到达推送网关。
  • xvfb-run 和 dbus-run-session -- xvfb-run 仍然是有效的诊断启动形式,但当安装了 Xvfb 且未配置显示时,两者都不应需要。没有 ready 块却反复出现 D-Bus 消息,表明启动未达到 serve 模式;请确认 AppImage 版本和精确的参数顺序,尤其是 --no-sandbox serve。

如果你之后从 Orca 设置中安装桌面 CLI,请使用该 CLI 执行普通 shell 工作流。在 systemd 中保留 AppImage 路径,以便服务重启不依赖于交互式 shell 配置文件。

升级

orca serve 从不自我更新。在无头模式下,Orca 根本不会配置自动更新器——内置更新器只在桌面 GUI 中运行,并且任何已配对的移动客户端或 Web 客户端都无法远程触发它。升级始终是一个有意为之的步骤:替换 AppImage 并重启服务。

两个事实使持久化状态迁移可预测:

  • 状态位于服务用户的主目录中,而不是二进制文件旁边。 持久化数据位于 /home/orca/.config/ 下(Orca 在那里同时使用 orca 和 Orca 目录),完全独立于 /opt/orca/orca-linux.AppImage。替换二进制文件绝不会触及项目、worktree 元数据、终端历史、编排状态或已配对设备密钥——因此移动客户端和 Web 客户端在升级后无需重新配对即可重新连接。
  • 新构建在加载时迁移旧状态。 Orca 将旧版 orca-data.json 状态加载到当前模式,并以当前格式写回,因此向前升级无需手动数据步骤。

这些保证不会保留活动进程。服务重启会终止其 cgroup 中的每个终端和代理;代理对话可能可以恢复,但其当前进程和任何进行中的命令都会消失。

在停止服务之前,以服务用户的 OS 账户和主目录身份获取新的清点。使用安装程序的绝对启动器路径,以便 sudo 的 secure_path 不会隐藏按用户注册的命令: sudo -Hu orca /home/orca/.local/bin/orca-ide terminal list --json。 将 orca 和 /home/orca 都替换为你的单元使用的服务账户和主目录;对于解压部署,请改用其绝对 resources/bin/orca-ide 启动器。仅当结果未被截断、具有显式 hostScope、覆盖受此服务停止影响的每个执行主机,并且未列出这些主机上的任何终端时,才继续。每个 omittedHostIds 条目都必须在此服务的执行边界之外明确说明。单独配对的运行时位于该边界之外;通过该运行时访问的本地执行和 SSH 主机则位于边界之内。受影响或未知的遗漏、缺失的作用域、失败的请求或丢失的连接均为 unverifiable,因此应推迟重启。不要在该清点与停止之间允许新工作;Orca 尚未提供原子化的清点与停止围栏。

回滚是需要谨慎处理的情况——参见 回滚。

记录您部署的版本

捆绑的 CLI 启动器通过 orca-ide --version 打印 Orca 构建。对于已解压的部署,该启动器是 squashfs-root/resources/bin/orca-ide;deb/rpm 安装和 CLI 注册会将其放入 PATH。不要使用 orca-linux.AppImage --version 进行此审计,因为 Electron 拥有直接二进制的版本标志,并可能报告其自身的运行时 版本。对于 AppImage 服务,请明确选择一个发布标签,并将其记录在 二进制文件旁边。以下步骤将该记录保存在 /opt/orca/VERSION 中。

升级步骤

切勿直接下载到 /opt/orca/orca-linux.AppImage。AppImage 是 FUSE 挂载的,因此在服务运行时就地覆盖它可能会使活动进程崩溃或 损坏——即使服务已停止,失败或不完整的下载也会覆盖可用的二进制文件。相反,请下载到同一文件系统上的临时名称, 验证它,然后使用原子重命名将其替换。

开始之前检查容量:

sudo chown root:root /opt/orca
sudo chmod 755 /opt/orca
sudo test ! -L /opt/orca/orca-linux.AppImage
sudo chown root:root /opt/orca/orca-linux.AppImage
sudo chmod 755 /opt/orca/orca-linux.AppImage
# Clear predictable staging names left by an older attempt after locking the directory
sudo rm -f /opt/orca/orca-linux.AppImage.new /opt/orca/VERSION.new \
  /opt/orca/orca-linux.AppImage.recovering /opt/orca/VERSION.recovering
sudo du -sh /home/orca/.config
df -h /opt/orca /home/orca

/opt/orca 需要为压缩的 Orca 配置文件归档、暂存的 构建和回滚二进制文件留出空间。回滚会提取旧配置文件并保留 升级后的 Orca 配置文件目录,因此 /home 需要为这两份副本留出空间。

将以下代码块作为一个 Bash 脚本运行,以便其快速失败和恢复陷阱在整个操作期间保持激活:

set -euo pipefail

# Replace this example with the release tag you intend to deploy
ORCA_VERSION=v1.4.147

# Select the release asset on the server where Orca runs
case "$(uname -m)" in
  x86_64)
    ORCA_ASSET=orca-linux.AppImage
    ORCA_FILE_MACHINE=x86-64
    ;;
  aarch64 | arm64)
    ORCA_ASSET=orca-linux-arm64.AppImage
    ORCA_FILE_MACHINE='ARM aarch64'
    ;;
  *)
    echo "Unsupported architecture: $(uname -m)" >&2
    exit 1
    ;;
esac

ORCA_ROLLBACK_NEW=
ORCA_ROLLBACK=
ORCA_SERVICE_STOPPED=0
ORCA_BINARY_PROMOTED=0
recover_failed_upgrade() {
  exit_status=$?
  trap - EXIT
  set +e
  if ((exit_status != 0)); then
    sudo rm -f /opt/orca/orca-linux.AppImage.new /opt/orca/VERSION.new \
      /opt/orca/orca-linux.AppImage.recovering /opt/orca/VERSION.recovering
  fi
  if ((exit_status != 0)) && [[ -n "$ORCA_ROLLBACK_NEW" ]] && \
    sudo test -d "$ORCA_ROLLBACK_NEW"; then
    sudo rm -rf -- "$ORCA_ROLLBACK_NEW"
  fi
  if ((exit_status != 0 && ORCA_SERVICE_STOPPED)); then
    recovery_ok=1
    if ((ORCA_BINARY_PROMOTED)); then
      if ! sudo cp -a "$ORCA_ROLLBACK/orca-linux.AppImage" \
        /opt/orca/orca-linux.AppImage.recovering || \
        ! sudo mv -f /opt/orca/orca-linux.AppImage.recovering \
          /opt/orca/orca-linux.AppImage; then
        recovery_ok=0
      fi
      if sudo test -f "$ORCA_ROLLBACK/VERSION"; then
        if ! sudo cp -a "$ORCA_ROLLBACK/VERSION" /opt/orca/VERSION.recovering || \
          ! sudo mv -f /opt/orca/VERSION.recovering /opt/orca/VERSION; then
          recovery_ok=0
        fi
      elif ! sudo rm -f /opt/orca/VERSION; then
        recovery_ok=0
      fi
    fi
    sudo rm -f /opt/orca/orca-linux.AppImage.recovering \
      /opt/orca/VERSION.recovering
    if ((recovery_ok)); then
      # A tripped StartLimitBurst refuses a plain start
      sudo systemctl reset-failed orca-serve.service || true
      sudo systemctl start orca-serve.service || true
    else
      echo 'Upgrade recovery failed; service remains stopped' >&2
    fi
  fi
  exit "$exit_status"
}
trap recover_failed_upgrade EXIT

# 1. Stage and verify the new build while the server stays online
sudo curl -fL --retry 3 "https://github.com/stablyai/orca/releases/download/${ORCA_VERSION}/${ORCA_ASSET}" \
  -o /opt/orca/orca-linux.AppImage.new
sudo chown root:root /opt/orca/orca-linux.AppImage.new
sudo chmod 755 /opt/orca/orca-linux.AppImage.new

# Both checks must match; either grep stops this fail-fast block otherwise
ORCA_FILE_INFO=$(LC_ALL=C file /opt/orca/orca-linux.AppImage.new)
grep 'ELF .* executable' <<<"$ORCA_FILE_INFO"
grep -F "$ORCA_FILE_MACHINE" <<<"$ORCA_FILE_INFO"

# 2. Assemble the prior binary and version in a root-only rollback bundle
ORCA_ROLLBACK_BASE=/opt/orca/orca-rollback-$(date +%F-%H%M%S-%N)
ORCA_ROLLBACK_NEW=${ORCA_ROLLBACK_BASE}.new
ORCA_ROLLBACK=${ORCA_ROLLBACK_BASE}.ready
sudo install -d -m 700 "$ORCA_ROLLBACK_NEW"
sudo cp -a /opt/orca/orca-linux.AppImage "$ORCA_ROLLBACK_NEW/orca-linux.AppImage"
if sudo test -f /opt/orca/VERSION; then
  sudo cp -a /opt/orca/VERSION "$ORCA_ROLLBACK_NEW/VERSION"
fi

# Stage the new version record before the stop window
printf '%s\n' "$ORCA_VERSION" | sudo tee /opt/orca/VERSION.new >/dev/null
sudo chown root:root /opt/orca/VERSION.new
sudo chmod 644 /opt/orca/VERSION.new

# 3. Stop the server so the profile backup is consistent
ORCA_SERVICE_STOPPED=1
sudo systemctl stop orca-serve.service

# Add only Orca-owned profile directories, then publish the complete bundle
ORCA_PROFILE_DIRS=()
for profile_dir in orca Orca; do
  if sudo test -L "/home/orca/.config/$profile_dir"; then
    echo "Refusing symlinked Orca profile: /home/orca/.config/$profile_dir" >&2
    exit 1
  fi
  if sudo test -d "/home/orca/.config/$profile_dir"; then
    if [[ "$profile_dir" == Orca ]] && \
      sudo test /home/orca/.config/orca -ef /home/orca/.config/Orca; then
      continue
    fi
    ORCA_PROFILE_DIRS+=("$profile_dir")
  fi
done
if ((${#ORCA_PROFILE_DIRS[@]} == 0)); then
  echo 'No Orca profile directory found under /home/orca/.config' >&2
  exit 1
fi
sudo tar czf "$ORCA_ROLLBACK_NEW/profile.tgz" \
  -C /home/orca/.config "${ORCA_PROFILE_DIRS[@]}"
sudo chmod 600 "$ORCA_ROLLBACK_NEW/profile.tgz"
sudo mv "$ORCA_ROLLBACK_NEW" "$ORCA_ROLLBACK"

# 4. Atomically replace the binary and version record, then start
ORCA_BINARY_PROMOTED=1
sudo mv -f /opt/orca/orca-linux.AppImage.new /opt/orca/orca-linux.AppImage
sudo mv -f /opt/orca/VERSION.new /opt/orca/VERSION
# Clears a start-limit hit left by the version being replaced
sudo systemctl reset-failed orca-serve.service
sudo systemctl start orca-serve.service
ORCA_SERVICE_STOPPED=0
trap - EXIT

步骤 3 创建的配置文件归档会在存在时同时捕获两个 Orca 配置文件目录名称,而不会回退 /home/orca/.config 下的无关工具。.ready 后缀只有在先前的二进制文件、版本记录和配置文件归档全部完成后才会发布。如果你运行受管理的 Xvfb 单元,则只需重启 orca-serve.service —— 保持 orca-xvfb.service 继续运行。

验证

sudo journalctl -u orca-serve.service -f

正常启动会打印一个 Orca server ready 块,其中包含实际绑定和通告的端点。请验证这些值,而不是假定已配置的端口,因为端口冲突可能会选择回退端口。 在丢弃备份之前,请确认客户端已重新连接。带时间戳的回滚包不会自动清理。在新版本满足你的保留策略后,删除前请选择并检查最新的完整回滚包:

shopt -s nullglob
ORCA_ROLLBACK_SETS=(/opt/orca/orca-rollback-*.ready)
((${#ORCA_ROLLBACK_SETS[@]} > 0))
ORCA_ROLLBACK=${ORCA_ROLLBACK_SETS[${#ORCA_ROLLBACK_SETS[@]} - 1]}
printf 'Removing rollback bundle: %s\n' "$ORCA_ROLLBACK"
sudo test -d "$ORCA_ROLLBACK"
sudo rm -rf -- "$ORCA_ROLLBACK"

每个 .ready 目录都是一个自包含的回滚代;切勿合并来自不同回滚包的文件。

回滚

回滚并非仅替换二进制文件就安全。一旦较新的构建启动,它可能会以当前模式重写 orca-data.json。如果随后较旧的构建写入该文件,它可能会丢弃其无法识别的字段。滚动生成的 orca-data.json.bak.* 文件是损坏恢复快照,而不是专用的升级前副本,正常写入可能会将它们轮转掉。若要干净地回滚,请恢复步骤 3 中的备份并且将二进制文件换回。将此代码块作为一个 Bash 脚本运行:

set -euo pipefail

# Select and validate one complete generation before taking the service offline
shopt -s nullglob
ORCA_ROLLBACK_SETS=(/opt/orca/orca-rollback-*.ready)
((${#ORCA_ROLLBACK_SETS[@]} > 0))
ORCA_ROLLBACK=${ORCA_ROLLBACK_SETS[${#ORCA_ROLLBACK_SETS[@]} - 1]}
sudo test -f "$ORCA_ROLLBACK/orca-linux.AppImage"
sudo tar tzf "$ORCA_ROLLBACK/profile.tgz" >/dev/null

# Extract and validate the old profile while the current server stays online
sudo test ! -L /home
ORCA_HOME_OWNER=$(sudo stat -c %u /home)
ORCA_HOME_MODE=$(sudo stat -c %a /home)
if [[ "$ORCA_HOME_OWNER" != 0 ]] || ((8#$ORCA_HOME_MODE & 0022)) || \
  sudo -u orca test -w /home; then
  echo 'Refusing rollback because /home is not root-controlled' >&2
  exit 1
fi
ORCA_RESTORE=$(sudo mktemp -d /home/.orca-restore.XXXXXX)
ORCA_SERVICE_STOPPED=0
ORCA_MOVED_CURRENT_DIRS=()
ORCA_INSTALLED_RESTORE_DIRS=()
ORCA_CURRENT_BINARY_MOVED=0
ORCA_CURRENT_VERSION_MOVED=0
ORCA_VERSION_REPLACEMENT_STARTED=0
ORCA_POST_UPGRADE=
ORCA_ROLLBACK_BINARY_STAGED=
ORCA_ROLLBACK_VERSION_STAGED=
ORCA_ROLLBACK_HAS_VERSION=0
restart_after_rollback_error() {
  exit_status=$?
  trap - EXIT
  set +e
  if ((exit_status != 0 && ORCA_SERVICE_STOPPED)); then
    recovery_ok=1
    if ((${#ORCA_INSTALLED_RESTORE_DIRS[@]})); then
      for profile_dir in "${ORCA_INSTALLED_RESTORE_DIRS[@]}"; do
        if sudo test -d "/home/orca/.config/$profile_dir"; then
          if ! sudo mv "/home/orca/.config/$profile_dir" \
            "$ORCA_RESTORE/$profile_dir.failed"; then
            recovery_ok=0
          fi
        fi
      done
    fi
    if ((${#ORCA_MOVED_CURRENT_DIRS[@]})); then
      for profile_dir in "${ORCA_MOVED_CURRENT_DIRS[@]}"; do
        if sudo test -d "$ORCA_POST_UPGRADE/$profile_dir"; then
          if ! sudo mv "$ORCA_POST_UPGRADE/$profile_dir" /home/orca/.config/; then
            recovery_ok=0
          fi
        elif ! sudo test -d "/home/orca/.config/$profile_dir"; then
          recovery_ok=0
        fi
      done
    fi
    if [[ -n "$ORCA_POST_UPGRADE" ]]; then
      sudo rmdir "$ORCA_POST_UPGRADE" 2>/dev/null || true
    fi
    if ((ORCA_CURRENT_BINARY_MOVED)); then
      if sudo test -f "$ORCA_CURRENT_BINARY"; then
        if ! sudo mv -f "$ORCA_CURRENT_BINARY" /opt/orca/orca-linux.AppImage; then
          recovery_ok=0
        fi
      elif ! sudo test -f /opt/orca/orca-linux.AppImage; then
        recovery_ok=0
      fi
    fi
    if ((ORCA_CURRENT_VERSION_MOVED)); then
      if sudo test -f "$ORCA_CURRENT_VERSION"; then
        if ! sudo mv -f "$ORCA_CURRENT_VERSION" /opt/orca/VERSION; then
          recovery_ok=0
        fi
      elif ! sudo test -f /opt/orca/VERSION; then
        recovery_ok=0
      fi
    elif ((ORCA_VERSION_REPLACEMENT_STARTED)); then
      if ! sudo rm -f /opt/orca/VERSION; then
        recovery_ok=0
      fi
    fi
    if ((recovery_ok)); then
      # A tripped StartLimitBurst refuses a plain start
      sudo systemctl reset-failed orca-serve.service || true
      sudo systemctl start orca-serve.service || true
    else
      echo 'Rollback recovery failed; service remains stopped' >&2
    fi
  fi
  if [[ -n "$ORCA_ROLLBACK_BINARY_STAGED" ]]; then
    sudo rm -f -- "$ORCA_ROLLBACK_BINARY_STAGED"
  fi
  if [[ -n "$ORCA_ROLLBACK_VERSION_STAGED" ]]; then
    sudo rm -f -- "$ORCA_ROLLBACK_VERSION_STAGED"
  fi
  sudo rm -rf -- "$ORCA_RESTORE"
  exit "$exit_status"
}
trap restart_after_rollback_error EXIT

if [[ "$(sudo stat -c %d "$ORCA_RESTORE")" != \
  "$(sudo stat -c %d /home/orca/.config)" ]]; then
  echo 'Refusing rollback because staging and the Orca profile are on different filesystems' >&2
  exit 1
fi
sudo tar xzf "$ORCA_ROLLBACK/profile.tgz" -C "$ORCA_RESTORE"
ORCA_RESTORE_DIRS=()
for profile_dir in orca Orca; do
  if sudo test -L "$ORCA_RESTORE/$profile_dir"; then
    echo "Rollback bundle contains a symlinked profile: $profile_dir" >&2
    exit 1
  fi
  if sudo test -d "$ORCA_RESTORE/$profile_dir"; then
    if [[ "$profile_dir" == Orca ]] && \
      sudo test "$ORCA_RESTORE/orca" -ef "$ORCA_RESTORE/Orca"; then
      continue
    fi
    ORCA_RESTORE_DIRS+=("$profile_dir")
  fi
done
if ((${#ORCA_RESTORE_DIRS[@]} == 0)); then
  echo "Rollback bundle has no Orca profile directories: $ORCA_ROLLBACK" >&2
  exit 1
fi
for profile_dir in "${ORCA_RESTORE_DIRS[@]}"; do
  sudo chown -R orca:orca "$ORCA_RESTORE/$profile_dir"
done

ORCA_ROLLBACK_STAMP=$(date +%F-%H%M%S-%N)
ORCA_ROLLBACK_BINARY_STAGED=/opt/orca/orca-linux.AppImage.rollback-staged-$ORCA_ROLLBACK_STAMP
sudo cp -a "$ORCA_ROLLBACK/orca-linux.AppImage" "$ORCA_ROLLBACK_BINARY_STAGED"
if sudo test -f "$ORCA_ROLLBACK/VERSION"; then
  ORCA_ROLLBACK_HAS_VERSION=1
  ORCA_ROLLBACK_VERSION_STAGED=/opt/orca/VERSION.rollback-staged-$ORCA_ROLLBACK_STAMP
  sudo cp -a "$ORCA_ROLLBACK/VERSION" "$ORCA_ROLLBACK_VERSION_STAGED"
fi

ORCA_SERVICE_STOPPED=1
sudo systemctl stop orca-serve.service

# Preserve and replace only Orca-owned profile directories
ORCA_CURRENT_DIRS=()
for profile_dir in orca Orca; do
  if sudo test -L "/home/orca/.config/$profile_dir"; then
    echo "Refusing symlinked Orca profile: /home/orca/.config/$profile_dir" >&2
    exit 1
  fi
  if sudo test -d "/home/orca/.config/$profile_dir"; then
    if [[ "$profile_dir" == Orca ]] && \
      sudo test /home/orca/.config/orca -ef /home/orca/.config/Orca; then
      continue
    fi
    ORCA_CURRENT_DIRS+=("$profile_dir")
  fi
done
ORCA_POST_UPGRADE=/home/orca/.config/orca-rollback-$ORCA_ROLLBACK_STAMP
sudo install -d -o orca -g orca -m 700 "$ORCA_POST_UPGRADE"
if ((${#ORCA_CURRENT_DIRS[@]})); then
  for profile_dir in "${ORCA_CURRENT_DIRS[@]}"; do
    ORCA_MOVED_CURRENT_DIRS+=("$profile_dir")
    sudo mv "/home/orca/.config/$profile_dir" "$ORCA_POST_UPGRADE/"
  done
fi
for profile_dir in "${ORCA_RESTORE_DIRS[@]}"; do
  ORCA_INSTALLED_RESTORE_DIRS+=("$profile_dir")
  sudo mv "$ORCA_RESTORE/$profile_dir" /home/orca/.config/
done

ORCA_CURRENT_BINARY=/opt/orca/orca-linux.AppImage.rollback-current-$ORCA_ROLLBACK_STAMP
ORCA_CURRENT_BINARY_MOVED=1
sudo mv /opt/orca/orca-linux.AppImage "$ORCA_CURRENT_BINARY"
sudo mv -f "$ORCA_ROLLBACK_BINARY_STAGED" /opt/orca/orca-linux.AppImage

ORCA_CURRENT_VERSION=/opt/orca/VERSION.rollback-current-$ORCA_ROLLBACK_STAMP
if sudo test -f /opt/orca/VERSION; then
  ORCA_CURRENT_VERSION_MOVED=1
  sudo mv /opt/orca/VERSION "$ORCA_CURRENT_VERSION"
fi
ORCA_VERSION_REPLACEMENT_STARTED=1
if ((ORCA_ROLLBACK_HAS_VERSION)); then
  sudo mv -f "$ORCA_ROLLBACK_VERSION_STAGED" /opt/orca/VERSION
else
  sudo rm -f /opt/orca/VERSION
fi
# The crash-looping build you are rolling back from tripped StartLimitBurst
sudo systemctl reset-failed orca-serve.service
sudo systemctl start orca-serve.service
ORCA_SERVICE_STOPPED=0
sudo rm -rf -- "$ORCA_RESTORE"
trap - EXIT

恢复备份是必需的,而不是可选的:只替换二进制文件会保留较新的 orca-data.json,而旧版本可能会丢弃它无法理解的状态。请在主机上验证新版本之前保留升级前的备份。.config 中的 orca-rollback-* 目录也是有意保留的。升级后的二进制文件和版本记录会以相同的 rollback-current-<timestamp> 后缀保留在 /opt/orca 中。回滚问题解决后,检查这些产物并根据你的保留策略删除它们。

在无桌面环境下安装 Agent Skills

Orca 的 Agent Skills(CLI 使用、编排、计算机使用等)通常从 Orca Settings 安装,它会在终端中预填一条 npx skills add ... --global 命令供你运行。无桌面主机没有 Settings 界面,因此请改用 orca skills install:

orca skills install                                      # list installable skills
orca skills install --skill orca-cli --skill orchestration # install globally (default)
orca skills install --skill orca-cli --local              # install into the current project only
orca skills install --all                                 # install every bundled skill
orca skills install --all --dry-run                       # print the npx command without running it

它会解析出 Settings 会显示给你的同一条 npx skills add <repo> --skill <name> ... 命令(除非传入 --local,否则添加 --global),然后运行该命令并转发其输出和退出码。它要求主机上存在 node/npx;不需要正在运行的 Orca runtime。

与 Settings 显示的命令不同,实际生成的命令会添加 npx --yes 和 -y。如果没有它们,skills CLI 会打开交互式 agent 选择器,并在任何已分配的 TTY 上永久阻塞——包括普通的 ssh 会话。使用 --dry-run 查看将要运行的确切命令。

Settings 有意保留该选择器,因为选择哪些 agent 获得某个 skill 是一个真实决策。无桌面运行无法回答它,因此 Orca 没有丢弃该选择,而是将其显式化:它传入一个 --agent 列表,该列表由它在主机上检测到的编码 agent 构建,并加上它自身读取的共享 .agents/skills 目录。如果未检测到任何 agent 而让 skills CLI 自行决定,它会安装到它知道的所有约 75 个 agent 中,并为每个留下一个配置目录。你可以自行覆盖目标,或仅缩小到共享目录:

orca skills install --skill orca-cli --agent claude-code,codex
orca skills install --skill orca-cli --agent universal

如果 Orca 完全未检测到任何 agent,orca skills install 会停止并要求提供 --agent,而不是猜测。

要刷新已安装的 skills,orca skills update 沿用相同的选择标志(--skill、--all、--local、--dry-run),并解析为带有匹配范围标志的 npx skills update <names...>——--global,或者当你传入 --local 时为 --project:

orca skills update --all                                  # update every bundled skill globally
orca skills update --skill orca-cli --dry-run             # print the npx command without running it

orca skills update 只刷新已经安装的 skills——对于缺失的 skill,它会以 0 退出而不做任何操作,因此请先安装它。更一般地,退出码 0 表示 skills CLI 运行未报错,而不是它写入了任何内容;请阅读其输出以确认发生了什么变化。

--json 适用于 skill 列表和 --dry-run。实际运行会流式输出 skills CLI 自身的非 JSON 输出,并拒绝 --json。

这两个命令都会安装到运行它们的机器上。在 Orca SSH workspace 或 WSL bridge 中,orca shim 会将命令转发到 Orca host,因此它们会拒绝在那里运行,并打印出你希望在其上运行的机器需要执行的命令。

故障排除

  • dlopen(): error loading libfuse.so.2:安装 libfuse2。
  • Missing X server or $DISPLAY:安装 xvfb,或启动受管理的 Xvfb 服务并设置 DISPLAY=:99。
  • [serve] Xvfb failed to start 或 [serve] Could not start Xvfb:确认 command -v Xvfb 并且它位于服务 PATH 上。
  • VPS 上的 GPU 或 DRI 警告:在服务环境中保留 LIBGL_ALWAYS_SOFTWARE=1。
  • Chromium 沙箱错误:确认服务以非 root 的 orca 用户运行,并且该用户可以读取 /opt/orca,包括如果你解压了 AppImage 时的 /opt/orca/squashfs-root。
  • 客户端无法连接:确保 --pairing-address 是客户端可达的地址,并确保防火墙允许所选的 --port。
  • 日志显示 Another Orca instance is already running for this userData profile 且单元以 3 退出:另一个进程已经拥有该 profile,因此 RestartPreventExitStatus=3 会故意让单元保持 failed。使用 systemctl status orca-serve 和 pgrep -af orca 找到所有者。停止它(或保留它并让单元保持停止),然后运行 sudo systemctl reset-failed orca-serve && sudo systemctl start orca-serve——reset-failed 会清除失败状态和任何启动限制计数器。如果不存在所有者,则锁是陈旧的(Chromium 记录了一个 pid,而该 pid 后来已被重用):从 userData 目录中删除 SingletonLock 和 SingletonSocket,然后再次启动。如果之前的崩溃循环已经泄漏了 AppImage 挂载,请使用 findmnt -rn -t fuse.orca-linux.AppImage 列出它们,并仅使用 fusermount -uz <target>(或 umount -l <target>)释放没有存活所有者的挂载,不要动运行中实例的挂载。
  • 服务在升级后立即崩溃循环:请使用 回滚 和升级前的 .ready 包。不要先重新运行升级;这样做会使崩溃版本成为下一个回滚二进制文件。该循环会触发 StartLimitBurst,因此在该脚本之外手动执行 systemctl start 之前需要先执行 sudo systemctl reset-failed orca-serve.service。
  • 诊断其他缺失库:使用 ./orca-linux.AppImage --appimage-extract 在不启动 AppImage 的情况下解压它,然后运行 ldd squashfs-root/orca-ide 列出主机缺失的任何共享库。Electron 二进制文件是 orca-ide,而不是 orca;对不存在的路径运行 ldd 不会打印任何内容并干净退出,这恰恰会在你寻找缺失库的情况下被误读为干净结果。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 orca48/orca