build: add checked Windows desktop archive packaging

This commit is contained in:
karl committed 2026-10-12 21:36:17 +02:00
1 parent 99c423c0fd
commit d9336db1d5
5 files changed
+187

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
"""Build a Windows desktop archive from already compiled, staged resources.
This packages an unsigned development build; it does not publish or certify
an installer. No model cache, user settings or local index enters the archive.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import struct
import tempfile
import zipfile
ROOT = Path(__file__).resolve().parents[1]
def pe_machine(path):
with path.open("rb") as stream:
header = stream.read(64)
if len(header) != 64 or header[:2] != b"MZ":
raise ValueError(f"Not a Windows executable: {path.name}")
offset = struct.unpack_from("<I", header, 60)[0]
if offset > path.stat().st_size - 6:
raise ValueError(f"Invalid executable header: {path.name}")
stream.seek(offset)
signature = stream.read(6)
if signature[:4] != b"PE\0\0":
raise ValueError(f"Invalid PE signature: {path.name}")
return struct.unpack_from("<H", signature, 4)[0]
def package(root, executable, output):
resources = root / "apps/desktop/src-tauri/resources"
selected = [(executable, "searchi.exe"), (root / "LICENSE", "LICENSE.txt")]
for name in ["searchi-gemma-worker.exe", "LLAMA-LICENSE.txt", "media-runtime/manifest.json",
"media-runtime/bin/ffmpeg.exe", "media-runtime/bin/ffprobe.exe"]:
selected.append((resources / name, name))
for directory in ["licenses", "media-runtime/licenses"]:
notices = list((resources / directory).rglob("*.txt"))
if not notices:
raise ValueError(f"Required notices are missing: {directory}")
selected.extend((path, path.relative_to(resources).as_posix()) for path in notices)
machine = pe_machine(executable)
architecture = {0x8664: "x86_64", 0xAA64: "aarch64"}.get(machine)
if architecture is None:
raise ValueError("Unsupported Windows architecture")
for path, name in selected:
if not path.is_file():
raise ValueError(f"Required package file is missing: {name}")
if path != executable and path != root / "LICENSE" and not path.resolve().is_relative_to(resources.resolve()):
raise ValueError("Resource resolves outside the staging directory")
if name.endswith(".exe") and pe_machine(path) != machine:
raise ValueError(f"Executable architecture differs: {name}")
records = []
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(dir=output.parent, suffix=".partial", delete=False) as temporary:
temporary_path = Path(temporary.name)
try:
with zipfile.ZipFile(temporary_path, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
for path, name in selected:
with path.open("rb") as stream:
checksum = hashlib.file_digest(stream, "sha256").hexdigest()
archive.write(path, name)
records.append({"path": name, "bytes": path.stat().st_size, "sha256": checksum})
archive.writestr("PACKAGE.json", json.dumps({"platform": "windows", "architecture": architecture,
"status": "unsigned development package; clean-machine verification pending", "files": records}, indent=2))
archive.writestr("START-HERE.txt", "searchi — Find what matters. Locally.\n\n"
"Extract the entire folder, then open searchi.exe. Keep its helpers alongside it.\n"
"Windows needs WebView2 and the applicable Microsoft C++ runtime.\n"
"First launch downloads approximately 865 MB of verified Google model files.\n"
"Choose Add folder to index your own files; ordinary search stays local.\n"
"This is an unsigned development build, not a certified production release.\n")
os.replace(temporary_path, output)
finally:
temporary_path.unlink(missing_ok=True)
return architecture
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--executable", type=Path, default=ROOT / "apps/desktop/src-tauri/target/release/searchi-desktop.exe")
parser.add_argument("--output", type=Path, default=ROOT / ".searchi/packages/searchi-windows.zip")
args = parser.parse_args()
architecture = package(ROOT, args.executable, args.output)
print(f"Packaged Windows {architecture}: {args.output}")
if __name__ == "__main__":
main()