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:
1 parent
f093558c0d
commit
4a7ca25334
4 files changed
+103
-61
No files matched your search
+9
-5
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -562,62 +562,76 @@ class BuildInfoTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class BuildDryRunTest(unittest.TestCase):
|
class BuildDryRunTest(unittest.TestCase):
|
||||||
|
def _fixture(self, tmp, with_vsock=False):
|
||||||
|
# 隔离的临时 git 仓:工作树干净、HEAD 与 REPO_COMMIT
|
||||||
|
# 一致、假 codex 二进制报对版本。with_vsock 时一并拷入
|
||||||
|
# vsock_notify.py(round-11 真机修复:服务须随文件建)。
|
||||||
|
repo = os.path.join(tmp, "repo")
|
||||||
|
guest = os.path.join(repo, "chv-guest")
|
||||||
|
bindir = os.path.join(tmp, "bin")
|
||||||
|
os.makedirs(guest)
|
||||||
|
os.makedirs(os.path.join(bindir, "codex-resources"))
|
||||||
|
for name in ("build.sh", "buildinfo.py", "check_lockfile.py",
|
||||||
|
"generate_lockfile.py", "chv-sync.sh",
|
||||||
|
"chv-cell.py", "chv-broker.py", "chv-browser.py",
|
||||||
|
"authd.py", "git-credential-chv-authd",
|
||||||
|
"chv-authd.service", "authd_init_stub.py"):
|
||||||
|
shutil.copy2(ROOT / "chv-guest" / name,
|
||||||
|
os.path.join(guest, name))
|
||||||
|
shutil.copy2(ROOT / "host.py", os.path.join(repo, "host.py"))
|
||||||
|
# round-11:build.sh 要求 P3 云端叶子模块在仓里(fail-fast),
|
||||||
|
# 夹具须一并拷入,否则干跑在 P3 预检就失败。
|
||||||
|
for rel in ("cloud/chv/authd.py", "cloud/chv/sentinel.py",
|
||||||
|
"cloud/chv/errors.py",
|
||||||
|
"cloud/__init__.py", "cloud/chv/__init__.py"):
|
||||||
|
dest = os.path.join(repo, rel)
|
||||||
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
|
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")
|
||||||
|
with open(codex, "w") as fh:
|
||||||
|
fh.write("#!/bin/sh\necho 'codex-cli 0.164.0-alpha.9'\n")
|
||||||
|
os.chmod(codex, 0o755)
|
||||||
|
host_bin = os.path.join(bindir, "codex-code-mode-host")
|
||||||
|
with open(host_bin, "w") as fh:
|
||||||
|
fh.write("#!/bin/sh\nexit 0\n")
|
||||||
|
os.chmod(host_bin, 0o755)
|
||||||
|
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
|
||||||
|
subprocess.run(["git", "add", "-A"], cwd=repo, check=True)
|
||||||
|
subprocess.run(
|
||||||
|
["git", "-c", "user.email=t@t", "-c", "user.name=t",
|
||||||
|
"commit", "-qm", "p2 dry-run fixture"],
|
||||||
|
cwd=repo, check=True)
|
||||||
|
head = subprocess.run(
|
||||||
|
["git", "rev-parse", "--short", "HEAD"], cwd=repo,
|
||||||
|
capture_output=True, text=True, check=True).stdout.strip()
|
||||||
|
env = dict(os.environ)
|
||||||
|
env.update({
|
||||||
|
"CODEX_BIN_DIR": bindir,
|
||||||
|
"UBUNTU_VERSION": "24.04",
|
||||||
|
"CODEX_VERSION": "0.164.0-alpha.9",
|
||||||
|
"REPO_COMMIT": head,
|
||||||
|
"UBUNTU_IMAGE_SHA256": "a" * 64,
|
||||||
|
})
|
||||||
|
return repo, guest, env
|
||||||
|
|
||||||
|
def _dry_run(self, repo, guest, env):
|
||||||
|
proc = subprocess.run(
|
||||||
|
["bash", os.path.join(guest, "build.sh"),
|
||||||
|
"--dry-run", "out"],
|
||||||
|
cwd=repo, capture_output=True, text=True, env=env)
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||||
|
return proc.stdout + proc.stderr
|
||||||
|
|
||||||
def test_dry_run_contains_p2_steps(self):
|
def test_dry_run_contains_p2_steps(self):
|
||||||
# 在隔离的临时 git 仓里跑 build.sh --dry-run:工作树干净、
|
# 在隔离的临时 git 仓里跑 build.sh --dry-run。
|
||||||
# HEAD 与 REPO_COMMIT 一致、假 codex 二进制报对版本。
|
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
repo = os.path.join(tmp, "repo")
|
repo, guest, env = self._fixture(tmp)
|
||||||
guest = os.path.join(repo, "chv-guest")
|
out = self._dry_run(repo, guest, env)
|
||||||
bindir = os.path.join(tmp, "bin")
|
|
||||||
os.makedirs(guest)
|
|
||||||
os.makedirs(os.path.join(bindir, "codex-resources"))
|
|
||||||
for name in ("build.sh", "buildinfo.py", "check_lockfile.py",
|
|
||||||
"generate_lockfile.py", "chv-sync.sh",
|
|
||||||
"chv-cell.py", "chv-broker.py", "chv-browser.py",
|
|
||||||
"authd.py", "git-credential-chv-authd",
|
|
||||||
"chv-authd.service", "authd_init_stub.py"):
|
|
||||||
shutil.copy2(ROOT / "chv-guest" / name,
|
|
||||||
os.path.join(guest, name))
|
|
||||||
shutil.copy2(ROOT / "host.py", os.path.join(repo, "host.py"))
|
|
||||||
# round-11:build.sh 要求 P3 云端叶子模块在仓里(fail-fast),
|
|
||||||
# 夹具须一并拷入,否则干跑在 P3 预检就失败。
|
|
||||||
for rel in ("cloud/chv/authd.py", "cloud/chv/sentinel.py",
|
|
||||||
"cloud/chv/errors.py",
|
|
||||||
"cloud/__init__.py", "cloud/chv/__init__.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")
|
|
||||||
with open(codex, "w") as fh:
|
|
||||||
fh.write("#!/bin/sh\necho 'codex-cli 0.164.0-alpha.9'\n")
|
|
||||||
os.chmod(codex, 0o755)
|
|
||||||
host_bin = os.path.join(bindir, "codex-code-mode-host")
|
|
||||||
with open(host_bin, "w") as fh:
|
|
||||||
fh.write("#!/bin/sh\nexit 0\n")
|
|
||||||
os.chmod(host_bin, 0o755)
|
|
||||||
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
|
|
||||||
subprocess.run(["git", "add", "-A"], cwd=repo, check=True)
|
|
||||||
subprocess.run(
|
|
||||||
["git", "-c", "user.email=t@t", "-c", "user.name=t",
|
|
||||||
"commit", "-qm", "p2 dry-run fixture"],
|
|
||||||
cwd=repo, check=True)
|
|
||||||
head = subprocess.run(
|
|
||||||
["git", "rev-parse", "--short", "HEAD"], cwd=repo,
|
|
||||||
capture_output=True, text=True, check=True).stdout.strip()
|
|
||||||
env = dict(os.environ)
|
|
||||||
env.update({
|
|
||||||
"CODEX_BIN_DIR": bindir,
|
|
||||||
"UBUNTU_VERSION": "24.04",
|
|
||||||
"CODEX_VERSION": "0.164.0-alpha.9",
|
|
||||||
"REPO_COMMIT": head,
|
|
||||||
"UBUNTU_IMAGE_SHA256": "a" * 64,
|
|
||||||
})
|
|
||||||
proc = subprocess.run(
|
|
||||||
["bash", os.path.join(guest, "build.sh"),
|
|
||||||
"--dry-run", "out"],
|
|
||||||
cwd=repo, capture_output=True, text=True, env=env)
|
|
||||||
self.assertEqual(proc.returncode, 0, proc.stderr)
|
|
||||||
out = proc.stdout + proc.stderr
|
|
||||||
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()
|
||||||
Reference in new issue
Block a user