feat: unify source and skills for Dever 0.1.2 installation
This commit is contained in:
1 parent
a2e0436ed4
commit
19f122b4b4
47 files changed
+1951
-63
No files matched your search
@@ -0,0 +1,90 @@
|
||||
"""Standalone Bash contract; subprocesses never install or change services."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
spec = importlib.util.spec_from_file_location("dever_install_entry", ROOT / "sdk/install-entry.py")
|
||||
generator = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(generator)
|
||||
|
||||
|
||||
class InstallEntryTests(unittest.TestCase):
|
||||
def run_bash(self, script, *arguments, **kwargs):
|
||||
return subprocess.run(["/bin/bash", "-s", "--", *arguments], input=script,
|
||||
env={}, text=True, capture_output=True, timeout=10, **kwargs)
|
||||
|
||||
def test_generated_entry_matches_source_and_independent_key(self):
|
||||
expected = generator.render((ROOT / "skills/scripts/install.py").read_text(),
|
||||
(ROOT / "skills/assets/release.pub").read_text())
|
||||
self.assertEqual((ROOT / "install").read_text(), expected)
|
||||
syntax = subprocess.run(["/bin/bash", "-n", str(ROOT / "install")], env={},
|
||||
text=True, capture_output=True, timeout=10)
|
||||
self.assertEqual(syntax.returncode, 0, syntax.stderr)
|
||||
|
||||
def test_file_and_pipe_help_do_not_need_a_checkout(self):
|
||||
with tempfile.TemporaryDirectory(prefix="dever-entry-") as directory:
|
||||
entry = Path(directory) / "install"
|
||||
entry.write_bytes((ROOT / "install").read_bytes())
|
||||
for command, contents in ((["/bin/bash", str(entry), "--help"], None),
|
||||
(["/bin/bash", "-s", "--", "--help"], entry.read_text())):
|
||||
result = subprocess.run(command, input=contents, cwd=directory, env={},
|
||||
text=True, capture_output=True, timeout=10)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("--trusted-key", result.stdout)
|
||||
self.assertIn("--system-root", result.stdout)
|
||||
invalid = self.run_bash((ROOT / "install").read_text(), "--unknown-argument")
|
||||
self.assertEqual(invalid.returncode, 2)
|
||||
|
||||
def test_arguments_python_isolation_and_exit_code_are_preserved(self):
|
||||
source = '''import json
|
||||
import sys
|
||||
def main(default_key):
|
||||
print(json.dumps([sys.argv[1:], default_key, sys.flags.isolated]))
|
||||
raise SystemExit(37)
|
||||
'''
|
||||
key = "ab" * 32
|
||||
script = generator.render(source, key)
|
||||
arguments = ["--release", "/a path/with 'quotes'", "", "$(not executed)", "*", "--no-start"]
|
||||
result = self.run_bash(script, *arguments)
|
||||
self.assertEqual(result.returncode, 37, result.stderr)
|
||||
self.assertEqual(json.loads(result.stdout), [arguments, key, 1])
|
||||
|
||||
def test_truncated_body_does_not_execute_python_or_sudo(self):
|
||||
script = generator.render('def main(default_key):\n print("EXECUTED")\n', "ab" * 32)
|
||||
for end in (script.index("import sys"), script.index("DEVER_INSTALL_PYTHON\n}"),
|
||||
script.index('\ndever_install "$@"')):
|
||||
result = self.run_bash(script[:end])
|
||||
self.assertNotIn("EXECUTED", result.stdout)
|
||||
|
||||
@unittest.skipUnless(os.geteuid() == 0 and Path("/usr/bin/sudo").is_file(),
|
||||
"real nonroot UID fixture requires root and sudo executable")
|
||||
def test_nonroot_help_skips_sudo_and_install_forwards_through_sudo(self):
|
||||
# Replace only the sudo command in this owned Bash process. It records
|
||||
# argv and returns without executing Python or attempting escalation.
|
||||
intercept = '''function /usr/bin/sudo() {
|
||||
printf '<%s>\\n' "$@"
|
||||
return 43
|
||||
}
|
||||
'''
|
||||
script = intercept + (ROOT / "install").read_text()
|
||||
lowered = {"user": 65534, "group": 65534, "extra_groups": []}
|
||||
help_result = self.run_bash(script, "--help", **lowered)
|
||||
self.assertEqual(help_result.returncode, 0, help_result.stderr)
|
||||
self.assertIn("--release", help_result.stdout)
|
||||
install = self.run_bash(script, "--system-root", "/owned image", "--no-start", **lowered)
|
||||
self.assertEqual(install.returncode, 43, install.stderr)
|
||||
self.assertEqual(install.stdout.splitlines(), [
|
||||
"<-->", "</usr/bin/python3>", "<-I>", "<->", "<--system-root>",
|
||||
"</owned image>", "<--no-start>",
|
||||
])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -212,6 +212,8 @@ class InstallerTests(unittest.TestCase):
|
||||
calls = []
|
||||
|
||||
def download(address):
|
||||
self.assertTrue(address.startswith(
|
||||
f"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{self.platform}."))
|
||||
return (assets_path / address.rsplit("/", 1)[-1]).open("rb")
|
||||
|
||||
def extract(command, **kwargs):
|
||||
@@ -298,6 +300,88 @@ class InstallerTests(unittest.TestCase):
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(list(image.iterdir()), [])
|
||||
|
||||
def test_metadata_uses_latest_or_exact_directory_and_never_a_github_fallback(self):
|
||||
for version in ("latest", "1.2.3"):
|
||||
destination = self.directory(version)
|
||||
addresses = []
|
||||
|
||||
def download(address):
|
||||
addresses.append(address)
|
||||
return io.BytesIO(b"metadata")
|
||||
|
||||
with mock.patch.object(installer, "download", side_effect=download):
|
||||
installer.metadata(destination, None, version, self.platform)
|
||||
self.assertEqual(addresses, [
|
||||
f"https://git.dever.cc/shemic/dever/dist/{version}/dever-{self.platform}.{suffix}"
|
||||
for suffix in ("manifest.json", "manifest.sig")
|
||||
])
|
||||
|
||||
def test_redirects_remain_on_the_official_https_site(self):
|
||||
handler = installer.OfficialRedirect()
|
||||
current = "https://git.dever.cc/shemic/dever/dist/latest/a"
|
||||
request = installer.urllib.request.Request(current)
|
||||
allowed = "https://git.dever.cc/shemic/dever/dist/1.2.3/a"
|
||||
redirected = handler.redirect_request(request, None, 302, "Found", {}, allowed)
|
||||
self.assertEqual(redirected.full_url, allowed)
|
||||
for address in (
|
||||
"http://git.dever.cc/a", "https://evil.example/a",
|
||||
"https://git.dever.cc.evil.example/a", "https://user@git.dever.cc/a",
|
||||
"https://git.dever.cc:444/a", "https://git.dever.cc/a#fragment",
|
||||
"https://github.com/a", "https://release-assets.githubusercontent.com/a"):
|
||||
with self.subTest(address=address), self.assertRaises(ValueError):
|
||||
handler.redirect_request(request, None, 302, "Found", {}, address)
|
||||
|
||||
def test_prerequisites_reject_unsupported_hosts_before_download(self):
|
||||
with mock.patch.object(installer.os, "confstr", return_value="glibc 2.39"):
|
||||
installer.prerequisites("linux-x86_64")
|
||||
for libc in ("glibc 2.38", "musl 1.2", ""):
|
||||
with mock.patch.object(installer.os, "confstr", return_value=libc), \
|
||||
self.assertRaisesRegex(ValueError, "glibc"):
|
||||
installer.prerequisites("linux-x86_64")
|
||||
with mock.patch.object(installer.os, "access", return_value=False), \
|
||||
self.assertRaisesRegex(ValueError, "openssl"):
|
||||
installer.prerequisites("linux-x86_64")
|
||||
|
||||
@unittest.skipUnless(installer.os.geteuid() == 0, "owned image installation requires root")
|
||||
def test_standalone_bash_uses_embedded_key_and_cleans_after_signature_failure(self):
|
||||
# Execute an independently signed fixture to observe the entire entry
|
||||
# and bootstrap call chain, without installing a machine or service.
|
||||
generator = load_module("dever_install_entry", ROOT / "sdk/install-entry.py")
|
||||
launcher = self.release / "bootstrap/dever"
|
||||
launcher.write_text('''#!/usr/bin/python3
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
assert sys.argv[1] == "--dever-install"
|
||||
setting = json.loads((Path(sys.argv[2]) / "config/setting.json").read_text())
|
||||
assert setting["activate_service"] is False
|
||||
(Path(setting["system_root"]) / "called.json").write_text(json.dumps(setting))
|
||||
''')
|
||||
content = launcher.read_bytes()
|
||||
manifest = json.loads((self.release / "manifest.json").read_text())
|
||||
for artifact in manifest["artifacts"]:
|
||||
if artifact["path"] == "bootstrap/dever":
|
||||
artifact.update(bytes=len(content), sha256=hashlib.sha256(content).hexdigest())
|
||||
(self.release / "manifest.json").write_text(json.dumps(manifest))
|
||||
self.sign_manifest()
|
||||
entry = generator.render((ROOT / "skills/scripts/install.py").read_text(), self.public.read_text())
|
||||
image = self.directory("image")
|
||||
arguments = ["/bin/bash", "-s", "--", "--release", str(self.release),
|
||||
"--system-root", str(image), "--version", "1.2.3", "--no-start"]
|
||||
completed = subprocess.run(arguments, input=entry, env={}, text=True,
|
||||
capture_output=True, timeout=20)
|
||||
self.assertEqual(completed.returncode, 0, completed.stderr)
|
||||
self.assertTrue((image / "called.json").is_file())
|
||||
self.assertEqual(list(image.glob(".dever-install-*")), [])
|
||||
(image / "called.json").unlink()
|
||||
with (self.release / "manifest.json").open("a") as stream:
|
||||
stream.write(" ")
|
||||
rejected = subprocess.run(arguments, input=entry, env={}, text=True,
|
||||
capture_output=True, timeout=20)
|
||||
self.assertNotEqual(rejected.returncode, 0)
|
||||
self.assertIn("签名校验失败", rejected.stderr)
|
||||
self.assertEqual(list(image.iterdir()), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/python3
|
||||
"""Opt-in real Bash installer acceptance in an owned image, without host services."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from verify_prepared_release import WORKSPACE, execute
|
||||
|
||||
|
||||
def verify(args):
|
||||
result = {
|
||||
"status": "running", "checks": [],
|
||||
"transport": "local signed assets; actual generated Bash entry and Rust bootstrap",
|
||||
"online_install_and_update": "not_run",
|
||||
"entry_sha256": hashlib.sha256(args.entry.read_bytes()).hexdigest(),
|
||||
}
|
||||
|
||||
def passed(name):
|
||||
result["checks"].append(name)
|
||||
args.report.write_text(json.dumps(result, indent=2) + "\n")
|
||||
print(f"PASS {name}", flush=True)
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory(prefix="dever-install-entry-", dir=args.scratch_parent) as directory:
|
||||
scratch = Path(directory)
|
||||
release = scratch / "release"
|
||||
release.mkdir()
|
||||
for name in ("manifest.json", "manifest.sig"):
|
||||
shutil.copyfile(args.release / name, release / name)
|
||||
manifest = json.loads((release / "manifest.json").read_bytes())
|
||||
if manifest["platform"] != "linux-x86_64":
|
||||
raise ValueError("this acceptance fixture requires a Linux x86_64 release")
|
||||
result["payload_version"] = manifest["version"]
|
||||
result["manifest_sha256"] = hashlib.sha256((release / "manifest.json").read_bytes()).hexdigest()
|
||||
key = scratch / "release.pub"
|
||||
shutil.copyfile(WORKSPACE / "skills/assets/release.pub", key)
|
||||
extraction = scratch / "extract.json"
|
||||
extraction.write_text(json.dumps({
|
||||
"release": str(release),
|
||||
"archive": str(args.assets / "dever-linux-x86_64.tar.zst"),
|
||||
"trusted_key": str(key),
|
||||
}))
|
||||
execute([args.extractor, "--dever-extract", extraction])
|
||||
passed("prepared_release_authenticated_by_existing_rust_extractor")
|
||||
|
||||
# A copied standalone entry has no adjacent skill or source checkout.
|
||||
entry = scratch / "install"
|
||||
shutil.copyfile(args.entry, entry)
|
||||
image = scratch / "image"
|
||||
image.mkdir()
|
||||
arguments = ["--release", str(release), "--system-root", str(image), "--no-start"]
|
||||
execute(["/bin/bash", entry, *arguments])
|
||||
machine = image / "opt/dever"
|
||||
launcher = machine / "bin/dever"
|
||||
installed = machine / "versions" / manifest["version"]
|
||||
if execute([launcher, "version"]) != manifest["version"]:
|
||||
raise RuntimeError("installed launcher selected the wrong version")
|
||||
skill = installed / "skills/dever-language"
|
||||
if execute([launcher, "skill", "path"]) != str(skill):
|
||||
raise RuntimeError("installed skill does not match the selected core")
|
||||
if (image / "usr/local/bin/dever").readlink() != Path("/opt/dever/bin/dever"):
|
||||
raise RuntimeError("public launcher symlink was not installed")
|
||||
passed("standalone_bash_first_install_and_matching_skill")
|
||||
|
||||
tracked = [launcher, machine / "bin/deverd",
|
||||
installed / "manifest.json", installed / "dever-core"]
|
||||
baseline = {str(path): hashlib.sha256(path.read_bytes()).hexdigest() for path in tracked}
|
||||
|
||||
def unchanged():
|
||||
current = {str(path): hashlib.sha256(path.read_bytes()).hexdigest() for path in tracked}
|
||||
if current != baseline:
|
||||
raise RuntimeError("existing installed state changed")
|
||||
if list(image.glob(".dever-install-*")):
|
||||
raise RuntimeError("installer left private staging behind")
|
||||
|
||||
repeated = subprocess.run(
|
||||
["/bin/bash", "-s", "--", *arguments], input=entry.read_bytes(),
|
||||
cwd=scratch, env={}, capture_output=True, timeout=180,
|
||||
)
|
||||
if repeated.returncode:
|
||||
raise RuntimeError(repeated.stderr.decode(errors="replace"))
|
||||
unchanged()
|
||||
# Bootstrap records another activation even for the same version.
|
||||
# Compare the selected version here, then freeze the journal for failure checks.
|
||||
if execute([launcher, "version"]) != manifest["version"]:
|
||||
raise RuntimeError("reinstall changed the selected version")
|
||||
active = machine / "state/active-version"
|
||||
tracked.append(active)
|
||||
baseline[str(active)] = hashlib.sha256(active.read_bytes()).hexdigest()
|
||||
passed("pipe_reinstall_preserves_installed_state_and_cleans_staging")
|
||||
|
||||
(release / "manifest.sig").write_text("00" * 64 + "\n")
|
||||
rejected = subprocess.run(
|
||||
["/bin/bash", entry, *arguments], cwd=scratch, env={},
|
||||
capture_output=True, timeout=30,
|
||||
)
|
||||
if rejected.returncode == 0 or "签名校验失败" not in rejected.stderr.decode(errors="replace"):
|
||||
raise RuntimeError("corrupt signature did not fail at authentication")
|
||||
unchanged()
|
||||
if execute([launcher, "version"]) != manifest["version"]:
|
||||
raise RuntimeError("failed install changed the selected version")
|
||||
passed("corrupt_signature_rejected_existing_install_preserved")
|
||||
result["status"] = "passed"
|
||||
except BaseException as error:
|
||||
result["status"] = "failed"
|
||||
result["error"] = str(error)
|
||||
raise
|
||||
finally:
|
||||
args.report.write_text(json.dumps(result, indent=2) + "\n")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
for name in ("release", "assets", "extractor", "scratch-parent", "report"):
|
||||
parser.add_argument(f"--{name}", required=True, type=Path)
|
||||
parser.add_argument("--entry", type=Path, default=WORKSPACE / "install")
|
||||
args = parser.parse_args()
|
||||
if os.geteuid() != 0:
|
||||
parser.error("requires root for its own image; never installs host services")
|
||||
for name, value in vars(args).items():
|
||||
setattr(args, name, value.absolute())
|
||||
args.report.parent.mkdir(parents=True, exist_ok=True)
|
||||
verify(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -202,14 +202,14 @@ def install_assets(assets, image, manifest):
|
||||
stem = f"dever-{manifest['platform']}"
|
||||
base = installer.RELEASES
|
||||
routes = {
|
||||
f"{base}/latest/download/{stem}.manifest.json": assets / f"{stem}.manifest.json",
|
||||
f"{base}/latest/download/{stem}.manifest.sig": assets / f"{stem}.manifest.sig",
|
||||
f"{base}/download/v{manifest['version']}/{stem}.tar.zst": assets / f"{stem}.tar.zst",
|
||||
f"{base}/latest/{stem}.manifest.json": assets / f"{stem}.manifest.json",
|
||||
f"{base}/latest/{stem}.manifest.sig": assets / f"{stem}.manifest.sig",
|
||||
f"{base}/{manifest['version']}/{stem}.tar.zst": assets / f"{stem}.tar.zst",
|
||||
}
|
||||
for artifact in manifest["artifacts"]:
|
||||
if artifact["path"] == "bootstrap/dever" or artifact["path"].startswith("bootstrap/lib/"):
|
||||
name = f"{stem}.blob-{artifact['sha256']}"
|
||||
routes[f"{base}/download/v{manifest['version']}/{name}"] = assets / name
|
||||
routes[f"{base}/{manifest['version']}/{name}"] = assets / name
|
||||
installer.download = lambda address: routes[address].open("rb")
|
||||
installer.install(argparse.Namespace(
|
||||
version="latest", release=None, system_root=image, no_start=True,
|
||||
@@ -225,7 +225,7 @@ def accept(release, assets, image, report, init, extension_helper=None):
|
||||
"version": manifest["version"], "platform": manifest["platform"],
|
||||
"manifest_sha256": hashlib.sha256((release / "manifest.json").read_bytes()).hexdigest(),
|
||||
"checks": [], "global_service_actions": "not_run", "online_update": "not_run",
|
||||
"installation_transport": "local_github_assets" if assets else "local_signed_directory",
|
||||
"installation_transport": "local_official_assets" if assets else "local_signed_directory",
|
||||
}
|
||||
|
||||
def passed(name):
|
||||
|
||||
Reference in new issue
Block a user