forked from omacom/try-omarchy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall-touch-id-menu-entry.py
More file actions
executable file
·104 lines (89 loc) · 3.25 KB
/
Copy pathinstall-touch-id-menu-entry.py
File metadata and controls
executable file
·104 lines (89 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#!/usr/bin/env python3
"""Install the Try Omarchy Touch ID row into one user's menu extension."""
from __future__ import annotations
import argparse
import os
from pathlib import Path
import secrets
import stat
ENTRY_ID = '"setup.security.touch-id"'
ENTRY = (
' // Try Omarchy host integration.\n'
' "setup.security.touch-id": {"icon":"","label":"Touch ID for sudo",'
'"when":"[[ -x /usr/local/bin/try-omarchy-touch-id ]]",'
'"checked":"/usr/local/bin/try-omarchy-touch-id status --quiet",'
'"action":"omarchy-launch-floating-terminal-with-presentation '
'/usr/local/bin/try-omarchy-touch-id"},\n'
)
MAXIMUM_BYTES = 256 * 1024
def fail(message: str) -> None:
raise SystemExit(f"install-touch-id-menu-entry: {message}")
def install(path: Path, previous_entry: str | None = None) -> None:
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
try:
info = path.stat(follow_symlinks=False)
except FileNotFoundError:
data = b"{\n}\n"
mode = 0o644
else:
if not stat.S_ISREG(info.st_mode) or info.st_uid != os.geteuid():
fail("menu extension is not a safe user-owned regular file")
mode = stat.S_IMODE(info.st_mode)
data = path.read_bytes()
if len(data) > MAXIMUM_BYTES:
fail("menu extension is unexpectedly large")
try:
text = data.decode("utf-8")
except UnicodeDecodeError:
fail("menu extension is not UTF-8")
if ENTRY_ID in text and (previous_entry is None or text.count(previous_entry) != 1):
return
opening = text.find("{")
if opening < 0 or text[:opening].strip():
fail("menu extension does not start with a JSONC object")
if text.rstrip()[-1:] != "}":
fail("menu extension is not a JSONC object")
if ENTRY_ID in text:
updated = text.replace(previous_entry, ENTRY, 1)
else:
updated = text[: opening + 1] + "\n" + ENTRY + text[opening + 1 :]
directory = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
temporary = f".{path.name}.{secrets.token_hex(8)}"
descriptor = -1
try:
descriptor = os.open(
temporary,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
mode,
dir_fd=directory,
)
payload = updated.encode("utf-8")
offset = 0
while offset < len(payload):
written = os.write(descriptor, payload[offset:])
if written <= 0:
fail("menu extension stopped accepting data")
offset += written
os.fchmod(descriptor, mode)
os.fsync(descriptor)
os.close(descriptor)
descriptor = -1
os.replace(temporary, path.name, src_dir_fd=directory, dst_dir_fd=directory)
os.fsync(directory)
finally:
if descriptor >= 0:
os.close(descriptor)
try:
os.unlink(temporary, dir_fd=directory)
except FileNotFoundError:
pass
os.close(directory)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("path", type=Path)
args = parser.parse_args()
if not args.path.is_absolute():
fail("menu extension path must be absolute")
install(args.path)
if __name__ == "__main__":
main()