feat(node): ship managed environment startup and USB recovery
This commit is contained in:
@@ -7,6 +7,7 @@ import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
@@ -24,11 +25,44 @@ PROFILES = {
|
||||
}
|
||||
|
||||
|
||||
def sync_directory(path):
|
||||
directory = os.open(path, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(directory)
|
||||
finally:
|
||||
os.close(directory)
|
||||
|
||||
|
||||
def private(path):
|
||||
path.mkdir(mode=0o700, exist_ok=True)
|
||||
info = path.lstat()
|
||||
if path.is_symlink() or info.st_uid != os.geteuid() or info.st_mode & 0o077:
|
||||
raise RuntimeError("Release directory is not private and owned")
|
||||
sync_directory(path.parent)
|
||||
|
||||
|
||||
def stage_file(path, data, mode):
|
||||
"""Publish complete, durable installer files before opening the sudo UI."""
|
||||
if path.is_symlink():
|
||||
raise ValueError("Unexpected release symlink")
|
||||
if path.exists():
|
||||
with path.open("rb") as stream:
|
||||
if stream.read() != data:
|
||||
raise ValueError("Existing release was modified")
|
||||
os.fsync(stream.fileno())
|
||||
else:
|
||||
descriptor, temporary = tempfile.mkstemp(prefix="." + path.name + ".", dir=path.parent)
|
||||
try:
|
||||
with os.fdopen(descriptor, "wb") as stream:
|
||||
os.fchmod(stream.fileno(), mode)
|
||||
stream.write(data)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
# Do not overwrite another launcher or a modified staged file.
|
||||
os.link(temporary, path)
|
||||
finally:
|
||||
os.unlink(temporary)
|
||||
sync_directory(path.parent)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -74,16 +108,7 @@ def main():
|
||||
raise ValueError("Release payload changed")
|
||||
files["release.json"] = raw
|
||||
for name, data in files.items():
|
||||
path = folder / name
|
||||
if path.is_symlink():
|
||||
raise ValueError("Unexpected release symlink")
|
||||
if path.exists():
|
||||
if path.read_bytes() != data:
|
||||
raise ValueError("Existing release was modified")
|
||||
else:
|
||||
with path.open("xb") as stream:
|
||||
stream.write(data)
|
||||
path.chmod(0o700 if name == "install" else 0o600)
|
||||
stage_file(folder / name, data, 0o700 if name == "install" else 0o600)
|
||||
print(json.dumps({"release_id": identifier, "directory": str(folder)}), flush=True)
|
||||
if sys.argv[1] == "--plan":
|
||||
result = subprocess.run(
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Installer staging survives interrupted writes without admitting corrupt files."""
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"owner_release_entry", Path(__file__).with_name("owner_release_entry.py")
|
||||
)
|
||||
entry = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(entry)
|
||||
|
||||
|
||||
class ReleaseStagingTests(unittest.TestCase):
|
||||
def test_sync_failure_never_publishes_partial_payload(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
target = Path(directory) / "package.deb"
|
||||
with patch.object(entry.os, "fsync", side_effect=OSError("disk failure")):
|
||||
with self.assertRaises(OSError):
|
||||
entry.stage_file(target, b"complete package", 0o600)
|
||||
self.assertEqual(list(Path(directory).iterdir()), [])
|
||||
entry.stage_file(target, b"complete package", 0o600)
|
||||
self.assertEqual(target.read_bytes(), b"complete package")
|
||||
self.assertEqual(target.stat().st_mode & 0o777, 0o600)
|
||||
|
||||
def test_valid_staging_can_be_repeated_without_replacing_inode(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
target = Path(directory) / "install"
|
||||
entry.stage_file(target, b"installer", 0o700)
|
||||
inode = target.stat().st_ino
|
||||
entry.stage_file(target, b"installer", 0o700)
|
||||
self.assertEqual(target.stat().st_ino, inode)
|
||||
self.assertEqual(target.stat().st_mode & 0o777, 0o700)
|
||||
|
||||
def test_truncated_existing_file_is_preserved_and_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
target = Path(directory) / "package.deb"
|
||||
target.write_bytes(b"partial")
|
||||
with self.assertRaisesRegex(ValueError, "modified"):
|
||||
entry.stage_file(target, b"complete package", 0o600)
|
||||
self.assertEqual(target.read_bytes(), b"partial")
|
||||
|
||||
def test_symlink_never_changes_its_target(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
real = Path(directory) / "real"
|
||||
real.write_bytes(b"keep")
|
||||
target = Path(directory) / "package.deb"
|
||||
target.symlink_to(real)
|
||||
with self.assertRaisesRegex(ValueError, "symlink"):
|
||||
entry.stage_file(target, b"replacement", 0o600)
|
||||
self.assertEqual(real.read_bytes(), b"keep")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user