You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 2eb3fe4
Browse filesBrowse the repository at this point in the historyBrowse files
Documents the Apple model for opt-in services: shipped plist with
Disabled=true, separate /var/db/.../overrides.plist for persistent
user intent, launchctl load -w / enable / disable to write the
overrides. Concrete scenario: sshd disabled by default in a system
install, user enables via CLI or GUI.
Status table shows what's implemented (basic launchctl load/unload),
what's not (overrides file mechanism, -w flag, modern enable/disable
verbs, manual start/stop). ~250 LOC Phase 5 batch lands the missing
parts alongside EnvironmentVariables / StartCalendarInterval / etc.
Pragmatic Phase 4 recommendation: ship sshd enabled-by-default for
the immediate proof-of-life milestone (avoids blocking on the
override mechanism); add the override layer as a focused Phase 5
commit before the system is considered installable.
Also adds a Disabled row to the §6.3 plist-key support table with a
pointer back to §12.7 for the override-mechanism gap.
Copy file name to clipboardExpand all lines: freebsd-launchd-plan.html
+72Lines changed: 72 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -358,6 +358,7 @@ <h3>6.3 Plist-key support — current state vs Apple's full surface</h3>
358
358
<tr><td><code>StandardOutPath</code> / <code>StandardErrorPath</code></td><td><spanclass="verdict no">unverified, likely not</span></td><td>Redirect spawned-child stdio. Add when a per-job log file is wanted.</td></tr>
359
359
<tr><td><code>Umask</code></td><td><spanclass="verdict no">unverified, likely not</span></td><td>Per-job umask before exec.</td></tr>
360
360
<tr><td><code>ThrottleInterval</code></td><td><spanclass="verdict no">unverified, likely not</span></td><td>KeepAlive currently respawns immediately on exit; no rate-limit. Add when a flapping daemon makes us want backoff.</td></tr>
361
+
<tr><td><code>Disabled</code></td><td><spanclass="verdict maybe">unverified — likely partial</span></td><td>Per-job "skip me at scan time" boolean. Apple-shipped plists ship with <code>Disabled=true</code> for opt-in services (sshd, nfsd, etc.) so the user has to explicitly enable them. <strong>Today, even if our launchd parses this key, the persistent override mechanism that lets users flip it without editing the shipped plist is not yet implemented</strong> — see §12.7.</td></tr>
361
362
<tr><td><code>RequiresPhase</code> (proposed extension)</td><td><spanclass="verdict no">NOT implemented</span></td><td>Project-specific schema extension per §11.3 — block job until a named phase stamp file exists. Will land alongside the configd / kmodloader work since those need ordering.</td></tr>
362
363
</tbody>
363
364
</table>
@@ -988,6 +989,77 @@ <h3>12.5 Out of scope (won't port — server scope)</h3>
988
989
<h3>12.6 The "anything else becomes a one-shot" rule</h3>
989
990
<p>Single most important translation rule: <strong>anything <code>rc.d</code> does that isn't "run a long-lived daemon" becomes a one-shot plist</strong> with <code>RunAtLoad=true, KeepAlive=false, LaunchOnlyOnce=true</code>. Examples: <code>sysctl</code> from <code>/etc/sysctl.conf</code>, <code>kldload</code> from <code>kld_list</code>, <code>fsck -p</code> + <code>mount -a</code>, <code>swapon -a</code>, <code>/var/run</code> + <code>/tmp</code> cleanup. Live config-file reload (<code>service foo reload</code>) is a strict improvement in launchd via <code>WatchPaths</code> / <code>QueueDirectories</code>.</p>
990
991
992
+
<h3>12.7 Service enable / disable UX</h3>
993
+
994
+
<p>Concrete scenario: <code>sshd</code> ships <strong>disabled by default</strong>. Admin enables it on a freshly-installed system. How?</p>
995
+
996
+
<h4>Apple's model (the target)</h4>
997
+
998
+
<p>The shipped plist at <code>/System/Library/LaunchDaemons/org.freebsd.sshd.plist</code> contains:</p>
<p>At every boot, launchd scans, sees the key, skips the job. <strong>The shipped plist is never edited</strong> by users or admins — it stays bit-for-bit stable across upgrades.</p>
1002
+
1003
+
<p>State changes go through one of three paths:</p>
<tr><td><code>launchctl load</code> (CLI, one-shot)</td><td>Lost on reboot</td><td><code>sudo launchctl load /System/Library/LaunchDaemons/org.freebsd.sshd.plist</code></td></tr>
1010
+
<tr><td>GUI toggle (e.g., gershwin "Sharing" pane)</td><td>Survives reboots</td><td>GUI talks to launchd over IPC, flips the same persistent state the <code>-w</code> flag does.</td></tr>
1011
+
</tbody>
1012
+
</table>
1013
+
1014
+
<p>The <strong>persistence trick</strong> is a separate <strong>overrides file</strong>:</p>
<p>At boot, launchd merges <strong>(a)</strong> the shipped plist's <code>Disabled</code> key (default state) with <strong>(b)</strong> the overrides file (user's persistent intent). The overrides file is a plist of <code>{label: bool}</code> entries — only present when a user has expressed an opt-in or opt-out for a specific job.</p>
1022
+
1023
+
<p>Modern <code>launchctl</code> verbs (macOS 10.10+) make this explicit:</p>
1024
+
<preclass="shell"><code>sudo launchctl enable system/org.freebsd.sshd # set persistent "enabled"
1025
+
sudo launchctl disable system/org.freebsd.sshd # set persistent "disabled"
1026
+
sudo launchctl bootstrap system /System/Library/LaunchDaemons/org.freebsd.sshd.plist
<tr><td>Overrides-file mechanism</td><td><spanclass="verdict no">NOT implemented</span> — launchd doesn't read or merge any override file today</td></tr>
1036
+
<tr><td><code>launchctl load <path></code> / <code>unload <path></code></td><td><spanclass="verdict go">basic</span> — works in the vendored 178-line <code>launchctl.c</code></td></tr>
<tr><td><code>launchctl start <label></code> / <code>stop <label></code></td><td><spanclass="verdict no">probably NOT implemented</span> — no manual one-shot start</td></tr>
1040
+
</tbody>
1041
+
</table>
1042
+
1043
+
<h4>Implementation plan (Phase 5 batch)</h4>
1044
+
1045
+
<p>~250 LOC of focused launchd surgery, lands alongside the <code>EnvironmentVariables</code> / <code>StartCalendarInterval</code> / etc. work in §6.3:</p>
1046
+
1047
+
<ol>
1048
+
<li><strong><code>core.m</code></strong> (~150 LOC): at plist-scan time, read <code>/var/db/org.freebsd.launchd/overrides.plist</code>. For each scanned job, merge <code>shipped.Disabled XOR override[label]</code>. Apply the merged value. Add a vnode source on the overrides file so changes flush to running state without a daemon restart.</li>
1049
+
<li><strong><code>launchctl.c</code></strong> (~80 LOC): implement the <code>-w</code> flag and the modern <code>enable</code> / <code>disable</code> verbs. They write to the overrides file via launchd's IPC (or directly with file lock + send a "reload" IPC).</li>
1050
+
<li><strong><code>ipc.c</code></strong> (~30 LOC): new IPC verbs for "set persistent enable", "set persistent disable", "manual start", "manual stop".</li>
1051
+
<li><strong>varrun plist</strong> (one line): <code>mkdir -p /var/db/org.freebsd.launchd</code> so the overrides file has a home.</li>
1052
+
</ol>
1053
+
1054
+
<h4>Pragmatic Phase 4 path</h4>
1055
+
1056
+
<p>For the <em>immediate</em> sshd milestone (Phase 4 in §14), two options:</p>
1057
+
<ul>
1058
+
<li><strong>A. Defer the override mechanism; ship sshd enabled by default.</strong><code>org.freebsd.sshd.plist</code> with <code>RunAtLoad=true</code>, no <code>Disabled</code> key. Fastest to the proof-of-life milestone. Acceptable for a livecd; less acceptable for a secure-server posture.</li>
1059
+
<li><strong>B. Implement the override mechanism first; ship sshd disabled by default.</strong> User runs <code>sudo launchctl load -w /System/Library/LaunchDaemons/org.freebsd.sshd.plist</code> to enable. The right Apple-shaped answer. Adds ~250 LOC to Phase 4's scope.</li>
1060
+
</ul>
1061
+
<p><strong>Recommendation:</strong> A for Phase 4 (get configd + sshd-as-supervised-daemon working end-to-end), B as a focused Phase 5 commit before we'd consider the system installable. Live ISO with sshd-on-by-default is fine — every boot has the same default-password posture, and that's already accepted.</p>
1062
+
991
1063
<h2id="license">13. Licensing</h2>
992
1064
993
1065
<p>The repo is a composition of three source families. They coexist cleanly under a single top-level <code>LICENSE</code> with per-file headers preserved — <strong>no dual-licensing needed</strong>. Top-level: <strong>BSD-2-Clause</strong> (matches the FreeBSD ecosystem and your other repos; aligns with FreeBSD base itself).</p>
0 commit comments