diff --git a/bin/sync.py b/bin/sync.py index 65afe28..d54e463 100644 --- a/bin/sync.py +++ b/bin/sync.py @@ -43,6 +43,26 @@ def run(*args, capture=False, check=False): ) +def ensure_exec_bits() -> None: + """Force +x on every path git tracks as executable (mode 100755), straight + from `git ls-files` rather than a hardcoded list - self-maintaining as + scripts are added. Guards against core.fileMode=false (repo-local or a + machine-wide /etc/gitconfig) silently dropping exec bits on checkout, which + otherwise surfaces later as a confusing "Permission denied" on whichever + script happens to run next (fetch-ollama.sh, the ncp symlink target, ...) + rather than as an obvious failure right here.""" + if os.name == "nt": + return + result = run("git", "ls-files", "-s", capture=True) + for line in result.stdout.splitlines(): + mode, _, rest = line.partition(" ") + if mode != "100755": + continue + path = ROOT / rest.split("\t", 1)[1] + if path.exists(): + path.chmod(path.stat().st_mode | 0o111) + + def linux_stage(script: str, *args) -> None: """Run one of the Linux-only bash stages. A no-op on Windows, where apt, xfconf, plank and the rest have nothing to act on.""" @@ -252,6 +272,7 @@ def cmd_restore(args) -> int: if run("git", "pull", "--ff-only", "origin", "main").returncode: print("Pull failed (diverged? stash/commit local changes).") return 1 + ensure_exec_bits() restore_db() rebuild_env() linux_stage("restore-linux.sh", "runtime")