Support reproducible XZ Linux archives without changing runtime contents
This commit is contained in:
1 parent
193ab08f62
commit
a8080f061f
4 files changed
+33
-5
No files matched your search
@@ -133,6 +133,7 @@ an unsigned headless development candidate:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 tools/package_linux.py
|
python3 tools/package_linux.py
|
||||||
|
python3 tools/package_linux.py --compression xz
|
||||||
python3 tools/embeddinggemma2/linux_smoke.py --worker .searchi/runtime/auto/searchi-gemma-worker --models .searchi/models/embeddinggemma2 --output .searchi/packages/linux-worker-smoke.json
|
python3 tools/embeddinggemma2/linux_smoke.py --worker .searchi/runtime/auto/searchi-gemma-worker --models .searchi/models/embeddinggemma2 --output .searchi/packages/linux-worker-smoke.json
|
||||||
python3 tools/embeddinggemma2/linux_smoke.py --worker .searchi/runtime/auto/searchi-gemma-worker --models .searchi/models/embeddinggemma2 --output .searchi/packages/linux-baseline-smoke.json --baseline
|
python3 tools/embeddinggemma2/linux_smoke.py --worker .searchi/runtime/auto/searchi-gemma-worker --models .searchi/models/embeddinggemma2 --output .searchi/packages/linux-baseline-smoke.json --baseline
|
||||||
```
|
```
|
||||||
@@ -148,6 +149,11 @@ They do not exercise GPU providers or certify older physical CPUs, installers
|
|||||||
or desktop behavior. Archive extraction and installed CLI search must also
|
or desktop behavior. Archive extraction and installed CLI search must also
|
||||||
be verified before a release gate is closed.
|
be verified before a release gate is closed.
|
||||||
|
|
||||||
|
The default archive uses gzip; `--compression xz` produces `.tar.xz` with the
|
||||||
|
same normalized TAR members, file checksums and executable modes. XZ uses the
|
||||||
|
standard library's preset 6. The extraction smoke accepts either format. A
|
||||||
|
smaller download does not change runtime behavior or ABI requirements.
|
||||||
|
|
||||||
The optional Linux missing-loader check requires explicit root execution and
|
The optional Linux missing-loader check requires explicit root execution and
|
||||||
util-linux mount namespaces/chroot. It never escalates privileges and verifies
|
util-linux mount namespaces/chroot. It never escalates privileges and verifies
|
||||||
a private namespace before creating an isolated filesystem:
|
a private namespace before creating an isolated filesystem:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def sha256(path):
|
|||||||
|
|
||||||
def extract_checked(archive_path, destination):
|
def extract_checked(archive_path, destination):
|
||||||
from package_linux import safe_name
|
from package_linux import safe_name
|
||||||
with tarfile.open(archive_path, "r:gz") as archive:
|
with tarfile.open(archive_path, "r:*") as archive:
|
||||||
members = archive.getmembers()
|
members = archive.getmembers()
|
||||||
if not 1 <= len(members) <= 128 or len({item.name for item in members}) != len(members):
|
if not 1 <= len(members) <= 128 or len({item.name for item in members}) != len(members):
|
||||||
raise ValueError("Invalid candidate member inventory")
|
raise ValueError("Invalid candidate member inventory")
|
||||||
|
|||||||
+10
-4
@@ -8,6 +8,7 @@ import gzip
|
|||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import lzma
|
||||||
import os
|
import os
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
import re
|
import re
|
||||||
@@ -169,7 +170,9 @@ def media_files(root, media):
|
|||||||
return selected
|
return selected
|
||||||
|
|
||||||
|
|
||||||
def package(root, executable, runtime, media, output, llama_license=None):
|
def package(root, executable, runtime, media, output, llama_license=None, compression="gzip"):
|
||||||
|
if compression not in {"gzip", "xz"}:
|
||||||
|
raise ValueError("Unsupported package compression")
|
||||||
root, executable, runtime, media, output = map(Path, (root, executable, runtime, media, output))
|
root, executable, runtime, media, output = map(Path, (root, executable, runtime, media, output))
|
||||||
regular(executable, executable.parent)
|
regular(executable, executable.parent)
|
||||||
elf_x64(executable)
|
elf_x64(executable)
|
||||||
@@ -199,7 +202,7 @@ def package(root, executable, runtime, media, output, llama_license=None):
|
|||||||
temporary_path = Path(temporary.name)
|
temporary_path = Path(temporary.name)
|
||||||
try:
|
try:
|
||||||
records = []
|
records = []
|
||||||
with temporary_path.open("wb") as raw, gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0) as compressed, tarfile.open(fileobj=compressed, mode="w|") as archive:
|
with temporary_path.open("wb") as raw, (lzma.LZMAFile(raw, "w", preset=6) if compression == "xz" else gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0)) as compressed, tarfile.open(fileobj=compressed, mode="w|") as archive:
|
||||||
for name, (path, expected_record) in sorted(selected.items()):
|
for name, (path, expected_record) in sorted(selected.items()):
|
||||||
safe_name(name)
|
safe_name(name)
|
||||||
regular(path, path.parent)
|
regular(path, path.parent)
|
||||||
@@ -249,9 +252,12 @@ def main():
|
|||||||
parser.add_argument("--runtime", type=Path, default=ROOT / ".searchi/runtime/auto")
|
parser.add_argument("--runtime", type=Path, default=ROOT / ".searchi/runtime/auto")
|
||||||
parser.add_argument("--media", type=Path, default=ROOT / ".searchi/media-runtime")
|
parser.add_argument("--media", type=Path, default=ROOT / ".searchi/media-runtime")
|
||||||
parser.add_argument("--llama-license", type=Path)
|
parser.add_argument("--llama-license", type=Path)
|
||||||
parser.add_argument("--output", type=Path, default=ROOT / ".searchi/packages/searchi-linux-x86_64.tar.gz")
|
parser.add_argument("--compression", choices=["gzip", "xz"], default="gzip")
|
||||||
|
parser.add_argument("--output", type=Path)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
package(ROOT, args.executable, args.runtime, args.media, args.output, args.llama_license)
|
if args.output is None:
|
||||||
|
args.output = ROOT / (".searchi/packages/searchi-linux-x86_64.tar.xz" if args.compression == "xz" else ".searchi/packages/searchi-linux-x86_64.tar.gz")
|
||||||
|
package(ROOT, args.executable, args.runtime, args.media, args.output, args.llama_license, args.compression)
|
||||||
print(f"Packaged Linux x86_64 development candidate: {args.output}")
|
print(f"Packaged Linux x86_64 development candidate: {args.output}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,22 @@ class LinuxPackageTests(unittest.TestCase):
|
|||||||
self.run_package(fixture)
|
self.run_package(fixture)
|
||||||
self.assertEqual(previous, fixture[-1].read_bytes())
|
self.assertEqual(previous, fixture[-1].read_bytes())
|
||||||
|
|
||||||
|
def test_xz_preserves_members_checksums_modes_and_reproducibility(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
fixture = self.fixture(directory)
|
||||||
|
self.run_package(fixture)
|
||||||
|
with tarfile.open(fixture[-1]) as archive:
|
||||||
|
original = {m.name: (archive.extractfile(m).read(), m.mode) for m in archive}
|
||||||
|
with mock.patch.object(pack, "dynamic", side_effect=self.metadata):
|
||||||
|
pack.package(*fixture, compression="xz")
|
||||||
|
first = fixture[-1].read_bytes()
|
||||||
|
pack.package(*fixture, compression="xz")
|
||||||
|
self.assertEqual(first, fixture[-1].read_bytes())
|
||||||
|
self.assertTrue(first.startswith(b"\xfd7zXZ\x00"))
|
||||||
|
with tarfile.open(fixture[-1]) as archive:
|
||||||
|
compressed = {m.name: (archive.extractfile(m).read(), m.mode) for m in archive}
|
||||||
|
self.assertEqual(original, compressed)
|
||||||
|
|
||||||
def test_media_helper_tamper_is_rejected_against_pinned_archive(self):
|
def test_media_helper_tamper_is_rejected_against_pinned_archive(self):
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
fixture = self.fixture(directory)
|
fixture = self.fixture(directory)
|
||||||
|
|||||||
Reference in new issue
Block a user