fix(chv-muse-r11fix): browser.sock 移进 chv-browser 自有目录(缺陷 4)+ vsock 自动启动

缺陷 4:/run/chv 是 0755 root root,chv-browser 在 bind 时
EACCES 而无限 crash loop(restart 21)。browser.sock 从
/run/chv/browser.sock 移到 /run/chv-browser/browser.sock
(broker 与 browser 两端常量同改);build.sh 建目录 + tmpfiles
登记(0755 chv-browser 所有)。不用组可写 /run/chv——那会给
chv-browser 对 broker.sock/cell.pid 的 unlink 能力。
vsock 自动启动:此前 build.sh 只拷 vsock_notify.py、不建服务,
vsock 通道从未自动启动。按 vsock_notify.py 的 docstring 补
vsock-notify.service(ExecStart=/usr/local/bin/vsock-notify %H,
Restart=no,After/WantedBy=multi-user.target)并 enable;
printf 格式串里 %H 写 %%H。测试:干跑针 /run/chv-browser;
vsock 在场时干跑含服务与 enable 语句。
This commit is contained in:
host committed 2026-11-07 04:01:24 +00:00
1 parent f093558c0d
commit 4a7ca25334
4 files changed
+54 -12

No files matched your search

+9 -5
View File
@@ -316,10 +316,10 @@ VC_ARGS=(-a "$OUT/disk.raw"
--run-command 'printf "ssh_deletekeys: false\nbootcmd:\n - \"[ -f /etc/ssh/ssh_host_ed25519_key ] || ssh-keygen -A\"\n" > /etc/cloud/cloud.cfg.d/99-chv-hostkey.cfg; systemctl enable ssh || true' --run-command 'printf "ssh_deletekeys: false\nbootcmd:\n - \"[ -f /etc/ssh/ssh_host_ed25519_key ] || ssh-keygen -A\"\n" > /etc/cloud/cloud.cfg.d/99-chv-hostkey.cfg; systemctl enable ssh || true'
--run-command 'printf "PasswordAuthentication no\nPermitRootLogin prohibit-password\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nPubkeyAuthentication yes\n" > /etc/ssh/sshd_config.d/99-chv.conf && mkdir -p /run/sshd && ssh-keygen -A && sshd -t && grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config.d/99-chv.conf && grep -q "^PermitRootLogin prohibit-password" /etc/ssh/sshd_config.d/99-chv.conf && rm -f /etc/ssh/ssh_host_* && test -z "$(ls /etc/ssh/ssh_host_* 2>/dev/null)"' --run-command 'printf "PasswordAuthentication no\nPermitRootLogin prohibit-password\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nPubkeyAuthentication yes\n" > /etc/ssh/sshd_config.d/99-chv.conf && mkdir -p /run/sshd && ssh-keygen -A && sshd -t && grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config.d/99-chv.conf && grep -q "^PermitRootLogin prohibit-password" /etc/ssh/sshd_config.d/99-chv.conf && rm -f /etc/ssh/ssh_host_* && test -z "$(ls /etc/ssh/ssh_host_* 2>/dev/null)"'
--run-command 'dpkg-query -W -f "\${Package} \${Version}\n" > /app/guest-versions-dpkg.txt; pip3 freeze > /app/guest-versions-pip.txt || true' --run-command 'dpkg-query -W -f "\${Package} \${Version}\n" > /app/guest-versions-dpkg.txt; pip3 freeze > /app/guest-versions-pip.txt || true'
--run-command 'useradd --system --home-dir /var/lib/chv-browser --shell /usr/sbin/nologin chv-browser && mkdir -p /var/lib/chv-browser /run/chv/shots && chown chv-browser:chv-browser /var/lib/chv-browser /run/chv/shots && chmod 0755 /run/chv /run/chv/shots' --run-command 'useradd --system --home-dir /var/lib/chv-browser --shell /usr/sbin/nologin chv-browser && mkdir -p /var/lib/chv-browser /run/chv/shots /run/chv-browser && chown chv-browser:chv-browser /var/lib/chv-browser /run/chv/shots /run/chv-browser && chmod 0755 /run/chv /run/chv/shots /run/chv-browser'
--run-command 'printf "[Unit]\nDescription=CHV cell broker (Muse P2)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/python3 /usr/local/bin/chv-broker\nRestart=on-failure\nRestartSec=2\n\n[Install]\nWantedBy=multi-user.target\n" > /etc/systemd/system/chv-broker.service' --run-command 'printf "[Unit]\nDescription=CHV cell broker (Muse P2)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/python3 /usr/local/bin/chv-broker\nRestart=on-failure\nRestartSec=2\n\n[Install]\nWantedBy=multi-user.target\n" > /etc/systemd/system/chv-broker.service'
--run-command 'printf "[Unit]\nDescription=CHV browser sub-agent (Muse P2)\nAfter=network.target\n\n[Service]\nType=simple\nUser=chv-browser\nGroup=chv-browser\nExecStart=/usr/bin/python3 /usr/local/bin/chv-browser\nRestart=on-failure\nRestartSec=2\n\n[Install]\nWantedBy=multi-user.target\n" > /etc/systemd/system/chv-browser.service' --run-command 'printf "[Unit]\nDescription=CHV browser sub-agent (Muse P2)\nAfter=network.target\n\n[Service]\nType=simple\nUser=chv-browser\nGroup=chv-browser\nExecStart=/usr/bin/python3 /usr/local/bin/chv-browser\nRestart=on-failure\nRestartSec=2\n\n[Install]\nWantedBy=multi-user.target\n" > /etc/systemd/system/chv-browser.service'
--run-command 'printf "d /run/chv 0755 root root -\nd /run/chv/shots 0755 chv-browser chv-browser -\n" > /etc/tmpfiles.d/chv.conf && systemctl enable chv-broker chv-browser || true') --run-command 'printf "d /run/chv 0755 root root -\nd /run/chv/shots 0755 chv-browser chv-browser -\nd /run/chv-browser 0755 chv-browser chv-browser -\n" > /etc/tmpfiles.d/chv.conf && systemctl enable chv-broker chv-browser || true')
# P3 交接(Muse P3 的 authd/sentinel 叶子模块):文件在仓库里存在时 # P3 交接(Muse P3 的 authd/sentinel 叶子模块):文件在仓库里存在时
# 拷入镜像(本分支已有,合入后自动生效);不存在时只警告、不失败—— # 拷入镜像(本分支已有,合入后自动生效);不存在时只警告、不失败——
# M4 既定 build.sh 不硬要求 cloud/,见上。缺叶子模块时 chv-authd # M4 既定 build.sh 不硬要求 cloud/,见上。缺叶子模块时 chv-authd
@@ -338,12 +338,16 @@ if [ "$P3_LIBS_PRESENT" = "1" ]; then
--run-command 'mv /usr/local/lib/chv-authd/cloud/chv/authd_init_stub.py /usr/local/lib/chv-authd/cloud/chv/__init__.py') --run-command 'mv /usr/local/lib/chv-authd/cloud/chv/authd_init_stub.py /usr/local/lib/chv-authd/cloud/chv/__init__.py')
fi fi
# P1 交接(Muse P1 的 vsock_notify.py):文件在仓库里存在时拷入 # P1 交接(Muse P1 的 vsock_notify.py):文件在仓库里存在时拷入
# 镜像(P1 分支才有,本分支尚无);不存在时只警告、不失败—— # 镜像并建 vsock-notify.service(自动启动,round-11 真机修复:
# 合并后自动生效,P2 不改该脚本内容。 # 此前只拷文件、不建服务,vsock 通道从未自动启动;服务形状按
# vsock_notify.py 的 docstring:ExecStart=/usr/local/bin/
# vsock-notify %H,Restart=no,After/WantedBy=multi-user.target;
# printf 格式串里 %H 须写 %%H);不存在时只警告、不失败。
VSOCK_NOTIFY_PRESENT=0 VSOCK_NOTIFY_PRESENT=0
if [ -f "$REPO_DIR/cloud/chv/guest/vsock_notify.py" ]; then if [ -f "$REPO_DIR/cloud/chv/guest/vsock_notify.py" ]; then
VC_ARGS+=(--copy-in "$REPO_DIR/cloud/chv/guest/vsock_notify.py:/usr/local/bin/" VC_ARGS+=(--copy-in "$REPO_DIR/cloud/chv/guest/vsock_notify.py:/usr/local/bin/"
--run-command 'mv /usr/local/bin/vsock_notify.py /usr/local/bin/vsock-notify && chmod +x /usr/local/bin/vsock-notify') --run-command 'mv /usr/local/bin/vsock_notify.py /usr/local/bin/vsock-notify && chmod +x /usr/local/bin/vsock-notify'
--run-command 'printf "[Unit]\nDescription=CHV vsock notify (Muse P1)\nAfter=multi-user.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/vsock-notify %%H\nRestart=no\n\n[Install]\nWantedBy=multi-user.target\n" > /etc/systemd/system/vsock-notify.service && systemctl enable vsock-notify || true')
VSOCK_NOTIFY_PRESENT=1 VSOCK_NOTIFY_PRESENT=1
else else
echo "警告:cloud/chv/guest/vsock_notify.py 不在仓库里(P1 未合并),镜像暂不含 vsock notify" >&2 echo "警告:cloud/chv/guest/vsock_notify.py 不在仓库里(P1 未合并),镜像暂不含 vsock notify" >&2
+6 -1
View File
@@ -31,7 +31,12 @@ import subprocess
import sys import sys
BROKER_SOCK = "/run/chv/broker.sock" BROKER_SOCK = "/run/chv/broker.sock"
BROWSER_SOCK = "/run/chv/browser.sock" # round-11 真机修复(缺陷 4):browser.sock 从 /run/chv 移到
# chv-browser 自有目录——/run/chv 是 0755 root root,
# chv-browser 在 bind 时 EACCES 而无限 crash loop(restart 21);
# 放进组可写的 /run/chv 会给 chv-browser 对 broker.sock/cell.pid
# 的 unlink 能力,所以用独立目录(0755 chv-browser 所有)。
BROWSER_SOCK = "/run/chv-browser/browser.sock"
CELL_PID_FILE = "/run/chv/cell.pid" CELL_PID_FILE = "/run/chv/cell.pid"
UID_BASE = 1000 UID_BASE = 1000
UID_COUNT = 65536 UID_COUNT = 65536
+4 -1
View File
@@ -31,7 +31,10 @@ import socket
import sys import sys
import uuid import uuid
BROWSER_SOCK = "/run/chv/browser.sock" # round-11 真机修复(缺陷 4):自有目录 /run/chv-browser,
# 0755 chv-browser 所有——/run/chv 是 0755 root root,
# 在此 bind 会 EACCES 而无限 crash loop(见 chv-broker.py)。
BROWSER_SOCK = "/run/chv-browser/browser.sock"
SHOTS_DIR = "/run/chv/shots" SHOTS_DIR = "/run/chv/shots"
PLAYWRIGHT_BROWSERS_PATH = "/usr/local/share/ms-playwright" PLAYWRIGHT_BROWSERS_PATH = "/usr/local/share/ms-playwright"
MAX_REQUEST_BYTES = 65536 MAX_REQUEST_BYTES = 65536
+35 -5
View File
@@ -562,10 +562,10 @@ class BuildInfoTest(unittest.TestCase):
class BuildDryRunTest(unittest.TestCase): class BuildDryRunTest(unittest.TestCase):
def test_dry_run_contains_p2_steps(self): def _fixture(self, tmp, with_vsock=False):
# 在隔离的临时 git 仓里跑 build.sh --dry-run:工作树干净、 # 隔离的临时 git 仓:工作树干净、HEAD 与 REPO_COMMIT
# HEAD 与 REPO_COMMIT 一致、假 codex 二进制报对版本。 # 一致、假 codex 二进制报对版本。with_vsock 时一并拷入
with tempfile.TemporaryDirectory() as tmp: # vsock_notify.py(round-11 真机修复:服务须随文件建)。
repo = os.path.join(tmp, "repo") repo = os.path.join(tmp, "repo")
guest = os.path.join(repo, "chv-guest") guest = os.path.join(repo, "chv-guest")
bindir = os.path.join(tmp, "bin") bindir = os.path.join(tmp, "bin")
@@ -587,6 +587,11 @@ class BuildDryRunTest(unittest.TestCase):
dest = os.path.join(repo, rel) dest = os.path.join(repo, rel)
os.makedirs(os.path.dirname(dest), exist_ok=True) os.makedirs(os.path.dirname(dest), exist_ok=True)
shutil.copy2(ROOT / rel, dest) shutil.copy2(ROOT / rel, dest)
if with_vsock:
rel = "cloud/chv/guest/vsock_notify.py"
dest = os.path.join(repo, rel)
os.makedirs(os.path.dirname(dest), exist_ok=True)
shutil.copy2(ROOT / rel, dest)
codex = os.path.join(bindir, "codex") codex = os.path.join(bindir, "codex")
with open(codex, "w") as fh: with open(codex, "w") as fh:
fh.write("#!/bin/sh\necho 'codex-cli 0.164.0-alpha.9'\n") fh.write("#!/bin/sh\necho 'codex-cli 0.164.0-alpha.9'\n")
@@ -612,12 +617,21 @@ class BuildDryRunTest(unittest.TestCase):
"REPO_COMMIT": head, "REPO_COMMIT": head,
"UBUNTU_IMAGE_SHA256": "a" * 64, "UBUNTU_IMAGE_SHA256": "a" * 64,
}) })
return repo, guest, env
def _dry_run(self, repo, guest, env):
proc = subprocess.run( proc = subprocess.run(
["bash", os.path.join(guest, "build.sh"), ["bash", os.path.join(guest, "build.sh"),
"--dry-run", "out"], "--dry-run", "out"],
cwd=repo, capture_output=True, text=True, env=env) cwd=repo, capture_output=True, text=True, env=env)
self.assertEqual(proc.returncode, 0, proc.stderr) self.assertEqual(proc.returncode, 0, proc.stderr)
out = proc.stdout + proc.stderr return proc.stdout + proc.stderr
def test_dry_run_contains_p2_steps(self):
# 在隔离的临时 git 仓里跑 build.sh --dry-run。
with tempfile.TemporaryDirectory() as tmp:
repo, guest, env = self._fixture(tmp)
out = self._dry_run(repo, guest, env)
for needle in ("systemd-container", "chv-cell", "chv-broker", for needle in ("systemd-container", "chv-cell", "chv-broker",
"chv-browser", "chv-browser.service", "chv-browser", "chv-browser.service",
"chv-broker.service", "useradd --system", "chv-broker.service", "useradd --system",
@@ -625,6 +639,10 @@ class BuildDryRunTest(unittest.TestCase):
"chv-authd", "git-credential-chv-authd", "chv-authd", "git-credential-chv-authd",
"systemctl enable chv-authd", "systemctl enable chv-authd",
"systemctl start chv-authd", "systemctl start chv-authd",
# round-11 真机修复(缺陷 4):browser.sock
# 移进 chv-browser 自有目录,build.sh 须
# 建目录 + tmpfiles 登记。
"/run/chv-browser",
# round-11 真机修复(层 1):叶子树含 # round-11 真机修复(层 1):叶子树含
# errors.py + stub __init__,不拷真实的 # errors.py + stub __init__,不拷真实的
# cloud/chv/__init__.py。 # cloud/chv/__init__.py。
@@ -637,6 +655,18 @@ class BuildDryRunTest(unittest.TestCase):
self.assertNotIn("credential.helper store", out) self.assertNotIn("credential.helper store", out)
self.assertIn("git config --system user.name chv", out) self.assertIn("git config --system user.name chv", out)
def test_dry_run_with_vsock_creates_service(self):
# round-11 真机修复(vsock 自动启动):vsock_notify.py
# 在场时建 vsock-notify.service 并 enable,不再只警告。
with tempfile.TemporaryDirectory() as tmp:
repo, guest, env = self._fixture(tmp, with_vsock=True)
out = self._dry_run(repo, guest, env)
self.assertNotIn("vsock_notify.py 不在仓库里", out)
for needle in ("vsock-notify.service",
"systemctl enable vsock-notify",
"ExecStart=/usr/local/bin/vsock-notify"):
self.assertIn(needle, out, needle)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()