Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions guest/packages.lock.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"architecture": "aarch64",
"packages": {
"abseil-cpp": "20260817.0-1",
"abseil-cpp": "20260817.0-2",
"acl": "2.4.0-1",
"adwaita-cursors": "50.0-1",
"adwaita-fonts": "50.0-1",
Expand Down Expand Up @@ -36,7 +36,7 @@
"ca-certificates-utils": "20240618-1",
"cairo": "1.18.4-1",
"cfitsio": "1:4.7.0-1",
"chromium": "151.0.7922.137-1",
"chromium": "152.0.7977.64-1",
"coreutils": "9.11-2",
"cpptrace": "1.0.4-2",
"cryptsetup": "2.8.7-1",
Expand All @@ -54,18 +54,18 @@
"diffutils": "3.12-2",
"dkms": "3.4.3-2",
"double-conversion": "3.4.0-1",
"dua-cli": "2.43.1-1",
"dua-cli": "2.44.0-1",
"duktape": "2.7.0-7",
"e2fsprogs": "1.47.4-1",
"elfutils": "0.196-1",
"exempi": "2.6.6-3",
"exiv2": "0.28.8-2",
"exiv2": "0.28.9-1",
"expat": "2.8.3-1",
"eza": "0.23.5-2",
"fakeroot": "1:1.37.2-3",
"fastfetch": "2.67.1-1",
"fcft": "3.3.3-1",
"fd": "10.4.2-2",
"fd": "10.5.0-2",
"ffmpeg": "2:9.0.1-4",
"fftw": "3.3.11-1",
"file": "5.48-1",
Expand Down Expand Up @@ -124,7 +124,7 @@
"gst-plugins-bad-libs": "1.28.6-3",
"gst-plugins-base-libs": "1.28.6-3",
"gstreamer": "1.28.6-3",
"gtest": "1.17.0-2",
"gtest": "1.18.0-1",
"gtk-update-icon-cache": "1:4.22.4-1",
"gtk3": "1:3.24.52-1",
"gtk4": "1:4.22.4-1",
Expand All @@ -136,6 +136,7 @@
"gvfs": "1.60.2-4",
"gzip": "1.14-2",
"harfbuzz": "14.4.0-1",
"hdf5": "2.2.0-1",
"hicolor-icon-theme": "0.18-1",
"hidapi": "0.15.0-1",
"highway": "1.4.0-1",
Expand Down Expand Up @@ -181,6 +182,7 @@
"leancrypto": "1.8.0-1",
"less": "1:704-1",
"libadwaita": "1:1.9.3-1",
"libaec": "1.1.7-1",
"libarchive": "3.8.9-1",
"libasan": "16.1.1+r12+g301eb08fa2c5-1",
"libass": "0.17.5-1",
Expand Down Expand Up @@ -270,14 +272,15 @@
"liblsan": "16.1.1+r12+g301eb08fa2c5-1",
"libluv": "1.52.1-1",
"libmakepkg-dropins": "20-2",
"libmatio": "1.5.30-1",
"libmd": "1.2.0-1",
"libmm-glib": "1.24.2-1",
"libmng": "2.0.3-4",
"libmnl": "1.0.5-2",
"libmodplug": "0.8.9.0-7",
"libmpc": "1.4.1-1",
"libmysofa": "1.3.5-1",
"libnautilus-extension": "50.2.2-1",
"libnautilus-extension": "50.3-1",
"libndp": "1.9-1",
"libnetfilter_conntrack": "1.1.1-1",
"libnewt": "0.52.25-2",
Expand Down Expand Up @@ -313,6 +316,7 @@
"libpulse": "17.0+r98+gb096704c0-1",
"libquadmath": "16.1.1+r12+g301eb08fa2c5-1",
"libraqm": "0.11.0-1",
"libraw": "0.22.2-1",
"libraw1394": "2.1.2-4",
"librsvg": "2:2.62.3-1",
"libsamplerate": "0.2.2-3",
Expand Down Expand Up @@ -352,13 +356,13 @@
"libva": "2.24.1-1",
"libvdpau": "1.5-4",
"libverto": "0.3.2-6",
"libvips": "8.18.5-1",
"libvips": "8.18.6-1",
"libvorbis": "1.3.7-4",
"libvpx": "1.17.0-1",
"libvterm": "0.3.3-2",
"libwacom": "2.19.1-1",
"libwebp": "1.6.0-2",
"libwireplumber": "0.5.15-1",
"libwireplumber": "0.5.16-1",
"libx11": "1.8.13-1",
"libxau": "1.0.12-1",
"libxcb": "1.17.0-1",
Expand Down Expand Up @@ -394,14 +398,14 @@
"libzip": "1.11.4-1",
"licenses": "20240728-1",
"lilv": "0.28.0-1",
"linux-aarch64": "7.2.2-1",
"linux-aarch64-headers": "7.2.2-1",
"linux-aarch64": "7.2.2-2",
"linux-aarch64-headers": "7.2.2-2",
"linux-api-headers": "7.2-1",
"llhttp": "9.3.1-1",
"llvm-libs": "22.1.8-2",
"lm_sensors": "1:3.6.2-1",
"lmdb": "0.9.35-1",
"localsearch": "3.11.1-2",
"localsearch": "3.11.2-1",
"lua": "5.5.1-1",
"lua51-lpeg": "1.1.0-5",
"lua54": "5.4.9-1",
Expand All @@ -425,13 +429,13 @@
"mtdev": "1.1.7-1",
"mujs": "1.3.9-1",
"muparser": "2.3.5-2",
"nautilus": "50.2.2-1",
"nautilus": "50.3-1",
"ncurses": "6.6-2",
"neovim": "0.12.5-1",
"nettle": "4.0-1",
"networkmanager": "1.58.1-1",
"nftables": "1:1.1.6-3",
"noto-fonts": "1:2026.08.01-1",
"noto-fonts": "1:2026.09.01-1",
"noto-fonts-emoji": "1:2.051-1",
"npth": "1.8-1",
"nspr": "4.40-1",
Expand Down Expand Up @@ -550,7 +554,7 @@
"uchardet": "0.0.8-4",
"udiskie": "2.7.0-2",
"udisks2": "2.11.2-1",
"unibilium": "2.1.2-1",
"unibilium": "2.1.4-1",
"unzip": "6.0-23",
"upower": "1.91.3-1",
"util-linux": "2.42.2-1",
Expand All @@ -568,7 +572,7 @@
"wayland": "1.26.0-1",
"wayland-protocols": "1.49-1",
"webrtc-audio-processing-1": "1.3-5",
"wireplumber": "0.5.15-1",
"wireplumber": "0.5.16-1",
"wl-clipboard": "1:2.3.0-1",
"woff2-font-awesome": "7.3.1-1",
"wpa_supplicant": "2:2.12-1",
Expand Down
103 changes: 82 additions & 21 deletions guest/scripts/apply-omarchy-backports.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import tempfile
from pathlib import Path, PurePosixPath


Expand All @@ -33,18 +36,38 @@ def contained_file(base: Path, relative: str, label: str) -> Path:
return candidate


def staged_target_file(root: Path, omarchy_root: Path, relative: str) -> Path:
logical = PurePosixPath(relative)
if logical.is_absolute() or ".." in logical.parts or logical.as_posix() != relative:
fail(f"unsafe target path: {relative}")

candidate = omarchy_root.joinpath(*logical.parts)
if candidate.is_symlink():
link = PurePosixPath(os.readlink(candidate))
if not link.is_absolute() or ".." in link.parts:
fail(f"target has an unsafe staged-root symlink: {relative}")
candidate = root.joinpath(*link.parts[1:])

if candidate.is_symlink() or not candidate.is_file():
fail(f"target is not a regular file: {relative}")
try:
candidate.resolve().relative_to(root.resolve())
except ValueError:
fail(f"target escapes the staged root: {relative}")
return candidate


def require_digest(value: object, label: str) -> str:
rendered = str(value or "")
if len(rendered) != 64 or any(character not in "0123456789abcdef" for character in rendered):
fail(f"invalid {label}: {rendered}")
return rendered


def verify_target(omarchy_root: Path, target: dict, digest_key: str, backport_id: str) -> None:
def verify_target(path: Path, target: dict, digest_key: str, backport_id: str) -> None:
if not isinstance(target, dict):
fail(f"backport {backport_id} has a non-object target")
relative = str(target.get("path", ""))
path = contained_file(omarchy_root, relative, "target")
expected = require_digest(target.get(digest_key), f"{backport_id} {relative} {digest_key}")
actual = sha256(path)
if actual != expected:
Expand All @@ -54,7 +77,7 @@ def verify_target(omarchy_root: Path, target: dict, digest_key: str, backport_id
)


def apply_backport(spec_dir: Path, omarchy_root: Path, backport: dict) -> None:
def apply_backport(spec_dir: Path, root: Path, omarchy_root: Path, backport: dict) -> None:
if not isinstance(backport, dict):
fail("backport metadata must contain JSON objects")
backport_id = str(backport.get("id", ""))
Expand All @@ -76,23 +99,60 @@ def apply_backport(spec_dir: Path, omarchy_root: Path, backport: dict) -> None:
targets = backport.get("targets")
if not isinstance(targets, list) or not targets:
fail(f"backport {backport_id} must declare at least one target")
target_paths: dict[str, Path] = {}
for target in targets:
verify_target(omarchy_root, target, "beforeSha256", backport_id)

result = subprocess.run(
["git", "apply", "--no-index", "--whitespace=error", str(patch)],
cwd=omarchy_root,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
if result.returncode != 0:
detail = result.stderr.strip() or result.stdout.strip() or "git apply failed"
fail(f"backport {backport_id} did not apply: {detail}")

for target in targets:
verify_target(omarchy_root, target, "afterSha256", backport_id)
if not isinstance(target, dict):
fail(f"backport {backport_id} has a non-object target")
relative = str(target.get("path", ""))
if relative in target_paths:
fail(f"backport {backport_id} repeats target: {relative}")
target_path = staged_target_file(root, omarchy_root, relative)
verify_target(target_path, target, "beforeSha256", backport_id)
target_paths[relative] = target_path

# Patch isolated regular-file copies so package-path symlinks such as
# /usr/share/omarchy/bin/* remain intact and no undeclared staged file can
# be changed by a reviewed patch.
with tempfile.TemporaryDirectory() as temporary:
patch_root = Path(temporary)
for relative, target_path in target_paths.items():
destination = patch_root.joinpath(*PurePosixPath(relative).parts)
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(target_path, destination)

result = subprocess.run(
["git", "apply", "--no-index", "--whitespace=error", str(patch)],
cwd=patch_root,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
if result.returncode != 0:
detail = result.stderr.strip() or result.stdout.strip() or "git apply failed"
fail(f"backport {backport_id} did not apply: {detail}")

actual_paths = {
path.relative_to(patch_root).as_posix()
for path in patch_root.rglob("*")
if path.is_file() or path.is_symlink()
}
if actual_paths != set(target_paths):
fail(f"backport {backport_id} changed files outside its declared targets")

for target in targets:
relative = str(target["path"])
verify_target(
patch_root.joinpath(*PurePosixPath(relative).parts),
target,
"afterSha256",
backport_id,
)
for relative, target_path in target_paths.items():
shutil.copy2(
patch_root.joinpath(*PurePosixPath(relative).parts),
target_path,
)
print(f"Applied Omarchy backport {backport_id}")


Expand All @@ -107,7 +167,8 @@ def main() -> None:
spec = args.spec.resolve()
if not spec.is_file():
fail(f"spec not found: {spec}")
omarchy_root = args.root.resolve() / "usr/share/omarchy"
root = args.root.resolve()
omarchy_root = root / "usr/share/omarchy"
if not omarchy_root.is_dir():
fail(f"materialized Omarchy tree not found: {omarchy_root}")

Expand All @@ -120,7 +181,7 @@ def main() -> None:
fail("authenticity.backports must be an array")

for backport in backports:
apply_backport(spec.parent, omarchy_root, backport)
apply_backport(spec.parent, root, omarchy_root, backport)


if __name__ == "__main__":
Expand Down
28 changes: 25 additions & 3 deletions guest/scripts/write-provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import hashlib
import json
import os
from pathlib import Path
from pathlib import Path, PurePosixPath


def digest_path(path: Path) -> str:
Expand Down Expand Up @@ -40,6 +40,27 @@ def digest_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()


def installed_target_file(root: Path, omarchy: Path, relative: str) -> Path:
logical = PurePosixPath(relative)
if logical.is_absolute() or ".." in logical.parts or logical.as_posix() != relative:
raise SystemExit(f"unsafe backport target path: {relative}")

installed = omarchy.joinpath(*logical.parts)
if installed.is_symlink():
link = PurePosixPath(os.readlink(installed))
if not link.is_absolute() or ".." in link.parts:
raise SystemExit(f"unsafe backport target symlink: {relative}")
installed = root.joinpath(*link.parts[1:])

if installed.is_symlink() or not installed.is_file():
raise SystemExit(f"missing installed backport target: {relative}")
try:
installed.resolve().relative_to(root.resolve())
except ValueError:
raise SystemExit(f"backport target escapes staged root: {relative}")
return installed


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--root", required=True, type=Path)
Expand All @@ -48,7 +69,8 @@ def main() -> None:
args = parser.parse_args()

spec = json.loads(args.spec.read_text())
omarchy = args.root / "usr/share/omarchy"
root = args.root.resolve()
omarchy = root / "usr/share/omarchy"
authenticity = spec["authenticity"]
verbatim_trees = authenticity["verbatimRuntimeTrees"]
backported_trees = authenticity.get("backportedRuntimeTrees", [])
Expand All @@ -70,7 +92,7 @@ def main() -> None:
if patch_digest != backport["patchSha256"]:
raise SystemExit(f"backport patch digest mismatch: {backport['id']}")
for target in backport["targets"]:
installed = omarchy / target["path"]
installed = installed_target_file(root, omarchy, target["path"])
if digest_file(installed) != target["afterSha256"]:
raise SystemExit(f"backport target digest mismatch: {backport['id']} {target['path']}")

Expand Down
Loading
Loading