domain/architecture/system/deployment/nix_darwin/setting/effects.bend
near-v-framework@0.4.0.0 · 287 lines · 30 definitions
1import Base
2import bend-kit-files@0.1.0.0/files.bend as Fs
3import near-v-framework@0.4.0.0/domain/architecture/system/effs/io.bend as X
4import near-v-framework@0.4.0.0/domain/architecture/system/plan/type.bend as P
5import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/setting/type.bend as St
6import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/setting/ops.bend as StO
7
8
9
10
11
12
13def read_domains(ds: List<&2, String>, +xs: List<&2, St.Setting>) -> IO(List<&2, St.Setting>):
14 match ds:
15 case []:
16 IO.pure(List<&2, St.Setting>, [])
17 case +d <> rest:
18 do IO<List<&2, St.Setting>>:
19 xml : String <- X.run("defaults", StO.export_args(d))
20 more : List<&2, St.Setting> <- read_domains(rest, xs)
21 return List.append(&2, St.Setting, StO.read_domain(xs, d, StO.text_lines(xml)), more)
22
23
24def defaults_now(+xs: List<&2, St.Setting>) -> IO(List<&2, St.Setting>):
25 read_domains(StO.domains(xs), xs)
26
27
28def power() -> IO(List<&2, St.Setting>):
29 do IO<List<&2, St.Setting>>:
30 pm : String <- X.run("pmset", ["-g", "custom"])
31 return StO.power_settings(pm)
32
33
34def power_button() -> IO(List<&2, St.Setting>):
35 do IO<List<&2, St.Setting>>:
36 pm : String <- X.run("pmset", ["-g"])
37 return StO.button_setting(pm)
38
39def name(+option: String, +key: String) -> IO(List<&2, St.Setting>):
40 do IO<List<&2, St.Setting>>:
41 +v : String <- X.run("scutil", ["--get", key])
42 return StO.when_filled(v, StO.str_setting(option, String.trim(v)))
43
44def names() -> IO(List<&2, St.Setting>):
45 do IO<List<&2, St.Setting>>:
46 a : List<&2, St.Setting> <- name("networking.computerName", "ComputerName")
47 b : List<&2, St.Setting> <- name("networking.localHostName", "LocalHostName")
48 c : List<&2, St.Setting> <- name("networking.hostName", "HostName")
49 return List.concat(&2, St.Setting, [a, b, c])
50
51
52def socketfilterfw(+flag: String) -> IO(String):
53 X.run("/usr/libexec/ApplicationFirewall/socketfilterfw", [flag])
54
55def firewall_now() -> IO(St.Firewall):
56 do IO<St.Firewall>:
57 a : String <- socketfilterfw("--getglobalstate")
58 b : String <- socketfilterfw("--getallowsigned")
59 c : String <- socketfilterfw("--getblockall")
60 d : String <- socketfilterfw("--getstealthmode")
61 return StO.firewall_of(a ++ b ++ c ++ d)
62
63
64def field(+plist: String, +tile: String, +f: String) -> IO(String):
65 do IO<String>:
66 out : String <- X.run("plutil", ["-extract", tile ++ "." ++ f, "raw", plist])
67 return String.trim(out)
68
69def tiles(n: Nat, +plist: String, +key: String, +i: Nat) -> IO(List<&2, String>):
70 match n:
71 case 0n:
72 IO.pure(List<&2, String>, [])
73 case 1n+m:
74 do IO<List<&2, String>>:
75 +tile : String = key ++ "." ++ Nat.show(i)
76 t : String <- field(plist, tile, "tile-type")
77 u : String <- field(plist, tile, "tile-data.file-data._CFURLString")
78 a : String <- field(plist, tile, "tile-data.arrangement")
79 d : String <- field(plist, tile, "tile-data.displayas")
80 s : String <- field(plist, tile, "tile-data.showas")
81 rest : List<&2, String> <- tiles(m, plist, key, 1n+i)
82 return StO.tile_nix(key, t, u, a, d, s) <> rest
83
84
85def dock_list(+plist: String, +key: String) -> IO(List<&2, St.Setting>):
86 do IO<List<&2, St.Setting>>:
87 +n : String <- X.run("plutil", ["-extract", key, "raw", plist])
88 ts : List<&2, String> <- tiles(StO.nat_of(n), plist, key, 0n)
89 return StO.dock_setting(key, n, ts)
90
91
92def per_service(ss: List<&2, String>, +get: String) -> IO(List<&2, String>):
93 match ss:
94 case []:
95 IO.pure(List<&2, String>, [])
96 case s <> rest:
97 do IO<List<&2, String>>:
98 out : String <- X.run("networksetup", [get, s])
99 more : List<&2, String> <- per_service(rest, get)
100 return StO.per_service(out) <> more
101
102def network() -> IO(List<&2, St.Setting>):
103 do IO<List<&2, St.Setting>>:
104 out : String <- X.run("networksetup", ["-listallnetworkservices"])
105 +ss : List<&2, String> = StO.services(out)
106 dns : List<&2, String> <- per_service(ss, "-getdnsservers")
107 search : List<&2, String> <- per_service(ss, "-getsearchdomains")
108 return [StO.nix_setting("networking.knownNetworkServices", StO.nix_list(ss)),
109 StO.nix_setting("networking.dns", StO.same_for_all("DNS servers", dns)),
110 StO.nix_setting("networking.search", StO.same_for_all("search domains", search))]
111
112
113
114def nix_link(+path: String) -> IO(Bool):
115 do IO<Bool>:
116 t : String <- X.run("readlink", [path])
117 return StO.is_nix_link(t)
118
119def security() -> IO(List<&2, St.Setting>):
120 do IO<List<&2, St.Setting>>:
121 +pam : String <- X.read("/etc/pam.d/sudo_local")
122 pam_nix : Bool <- nix_link("/etc/pam.d/sudo_local")
123 +sudo : String <- X.read("/etc/sudoers.d/10-nix-darwin-extra-config")
124 pki : Bool <- nix_link("/etc/ssl/certs/ca-certificates.crt")
125 return [StO.bool_setting("security.pam.services.sudo_local.enable", pam_nix),
126 StO.bool_setting("security.pam.services.sudo_local.touchIdAuth", String.contains(pam, "pam_tid.so")),
127 StO.bool_setting("security.pam.services.sudo_local.watchIdAuth", String.contains(pam, "pam_watchid")),
128 StO.bool_setting("security.pam.services.sudo_local.reattach", String.contains(pam, "pam_reattach")),
129 StO.bool_setting("security.sudo.keepTerminfo", String.contains(sudo, "TERMINFO_DIRS")),
130 StO.str_setting("security.sudo.extraConfig", StO.sudo_extra(sudo)),
131 StO.bool_setting("security.pki.installCACerts", pki)]
132
133def keyboard() -> IO(List<&2, St.Setting>):
134 do IO<List<&2, St.Setting>>:
135 km : String <- X.run("hidutil", ["property", "--get", "UserKeyMapping"])
136 return StO.keyboard_settings(km)
137
138
139def names_of(r: Result<&1, &1, U32 & String, List<&2, String>>) -> List<&2, String>:
140 match r:
141 case Done{ns}:
142 ns
143 case Fail{_}:
144 []
145
146def system() -> IO(List<&2, St.Setting>):
147 do IO<List<&2, St.Setting>>:
148 +mute : String <- X.run("nvram", ["StartupMute"])
149 tz : String <- X.run("readlink", ["/etc/localtime"])
150 user : String <- X.run("stat", ["-f", "%Su", "/dev/console"])
151 nix : Bool <- nix_link("/etc/nix/nix.conf")
152 shells : String <- X.read("/etc/shells")
153 fonts : Result<&1, &1, U32 & String, List<&2, String>> <- Fs.list_dir("/Library/Fonts/Nix Fonts")
154 zsh : Bool <- nix_link("/etc/zshrc")
155 bash : Bool <- nix_link("/etc/bashrc")
156 fish : Bool <- nix_link("/etc/fish/config.fish")
157 ssh : Bool <- X.succeeds("launchctl", ["print", "system/com.openssh.sshd"])
158 return List.append(&2, St.Setting, StO.chime_setting(mute),
159 [StO.str_setting("time.timeZone", StO.time_zone(tz)),
160 StO.str_setting("system.primaryUser", String.trim(user)),
161 StO.bool_setting("nix.enable", nix),
162 StO.nix_setting("environment.shells", StO.nix_list(StO.extra_shells(shells))),
163 StO.fonts_setting(names_of(fonts)),
164 StO.bool_setting("programs.zsh.enable", zsh),
165 StO.bool_setting("programs.bash.enable", bash),
166 StO.bool_setting("programs.fish.enable", fish),
167 StO.bool_setting("services.openssh.enable", ssh)])
168
169
170def dscl(+record: String, +attr: String) -> IO(String):
171 do IO<String>:
172 out : String <- X.run("dscl", [".", "-read", record, attr])
173 return StO.dscl_value(attr, out)
174
175def user_list(ns: List<&2, String>) -> IO(List<&2, St.Setting>):
176 match ns:
177 case []:
178 IO.pure(List<&2, St.Setting>, [])
179 case +n <> rest:
180 do IO<List<&2, St.Setting>>:
181 uid : String <- dscl("/Users/" ++ n, "UniqueID")
182 gid : String <- dscl("/Users/" ++ n, "PrimaryGroupID")
183 dir : String <- dscl("/Users/" ++ n, "NFSHomeDirectory")
184 real : String <- dscl("/Users/" ++ n, "RealName")
185 shell : String <- dscl("/Users/" ++ n, "UserShell")
186 hidden : String <- dscl("/Users/" ++ n, "IsHidden")
187 more : List<&2, St.Setting> <- user_list(rest)
188 return List.append(&2, St.Setting, StO.user_settings(n, uid, gid, dir, real, shell, hidden), more)
189
190def group_list(ns: List<&2, String>) -> IO(List<&2, St.Setting>):
191 match ns:
192 case []:
193 IO.pure(List<&2, St.Setting>, [])
194 case +n <> rest:
195 do IO<List<&2, St.Setting>>:
196 gid : String <- dscl("/Groups/" ++ n, "PrimaryGroupID")
197 real : String <- dscl("/Groups/" ++ n, "RealName")
198 members : String <- dscl("/Groups/" ++ n, "GroupMembership")
199 more : List<&2, St.Setting> <- group_list(rest)
200 return List.append(&2, St.Setting, StO.group_settings(n, gid, real, members), more)
201
202def users() -> IO(List<&2, St.Setting>):
203 do IO<List<&2, St.Setting>>:
204 us : String <- X.run("dscl", [".", "-list", "/Users", "UniqueID"])
205 gs : String <- X.run("dscl", [".", "-list", "/Groups", "PrimaryGroupID"])
206 a : List<&2, St.Setting> <- user_list(StO.accounts(us))
207 b : List<&2, St.Setting> <- group_list(StO.accounts(gs))
208 return List.concat(&2, St.Setting, [[StO.nix_setting("users.knownUsers", "[ ]"), StO.nix_setting("users.knownGroups", "[ ]")], a, b])
209
210
211
212def regular(k: Fs.FileType) -> Bool:
213 match k:
214 case Fs.Regular{}:
215 True{}
216 case _:
217 False{}
218
219def is_file(r: Result<&1, &1, U32 & String, Fs.Info>) -> Bool:
220 match r:
221 case Done{i}:
222 match i:
223 case Fs.Info{k, _, _}:
224 regular(k)
225 case Fail{_}:
226 False{}
227
228def launch_files(fs: List<&2, String>, +opt: String, +dir: String, +labels: List<&2, String>) -> IO(List<&2, St.Setting>):
229 match fs:
230 case []:
231 IO.pure(List<&2, St.Setting>, [])
232 case +f <> rest:
233 do IO<List<&2, St.Setting>>:
234 info : Result<&1, &1, U32 & String, Fs.Info> <- Fs.stat(dir ++ "/" ++ f)
235 +keep : Bool = is_file(info) && StO.launch_kept(f, labels)
236 sum : String <- Bool.pick(IO(String), keep, X.run("shasum", ["-a", "256", dir ++ "/" ++ f]), IO.pure(String, ""))
237 more : List<&2, St.Setting> <- launch_files(rest, opt, dir, labels)
238 return List.append(&2, St.Setting, Bool.pick(List<&2, St.Setting>, keep, [StO.launch_setting(opt, f, sum)], []), more)
239
240def launch_dir(+opt: String, +dir: String, +labels: List<&2, String>) -> IO(List<&2, St.Setting>):
241 do IO<List<&2, St.Setting>>:
242 r : Result<&1, &1, U32 & String, List<&2, String>> <- Fs.list_dir(dir)
243 launch_files(names_of(r), opt, dir, labels)
244
245
246def launch(+labels: List<&2, String>) -> IO(List<&2, St.Setting>):
247 do IO<List<&2, St.Setting>>:
248 h : String <- X.home()
249 a : List<&2, St.Setting> <- launch_dir("environment.launchDaemons", "/Library/LaunchDaemons", labels)
250 b : List<&2, St.Setting> <- launch_dir("environment.launchAgents", "/Library/LaunchAgents", labels)
251 c : List<&2, St.Setting> <- launch_dir("environment.userLaunchAgents", h ++ "/Library/LaunchAgents", labels)
252 return List.concat(&2, St.Setting, [a, b, c])
253
254
255
256
257def observed_from(+xs: List<&2, St.Setting>, +labels: List<&2, String>) -> IO(List<&2, St.Setting>):
258 do IO<List<&2, St.Setting>>:
259 prefs : List<&2, St.Setting> <- defaults_now(xs)
260 pw : List<&2, St.Setting> <- power()
261 nm : List<&2, St.Setting> <- names()
262 +h : String <- X.home()
263 apps : List<&2, St.Setting> <- dock_list(h ++ "/Library/Preferences/com.apple.dock.plist", "persistent-apps")
264 others : List<&2, St.Setting> <- dock_list(h ++ "/Library/Preferences/com.apple.dock.plist", "persistent-others")
265 net : List<&2, St.Setting> <- network()
266 btn : List<&2, St.Setting> <- power_button()
267 sec : List<&2, St.Setting> <- security()
268 kb : List<&2, St.Setting> <- keyboard()
269 sys : List<&2, St.Setting> <- system()
270 us : List<&2, St.Setting> <- users()
271 ld : List<&2, St.Setting> <- launch(labels)
272 return List.concat(&2, St.Setting, [prefs, pw, nm, apps, others, net, btn, sec, kb, sys, us, ld])
273
274
275
276def observed(+options_tsv: String, +labels: List<&2, String>) -> IO(List<&2, St.Setting>):
277 do IO<List<&2, St.Setting>>:
278 tsv : String <- X.read(options_tsv)
279 observed_from(StO.options(tsv), labels)
280
281
282
283def changes(+settings: List<&2, St.Setting>, fw: St.Firewall) -> IO(List<&2, P.Step>):
284 do IO<List<&2, P.Step>>:
285 now : List<&2, St.Setting> <- observed_from(settings, [])
286 f : St.Firewall <- firewall_now()
287 return List.append(&2, P.Step, StO.firewall_change(fw, f), StO.diff(settings, now))