<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Focused Systems</title><link>https://focused-systems.pages.dev/</link><description>Exploring modern cloud workflows. DevOps, APIs, Git, serverless architectures, pipelines, and software integrations, alongside personal tech solutions.</description><language>en</language><generator>Hugo</generator><image><url>https://focused-systems.pages.dev/images/social.png</url><title>Focused Systems</title><link>https://focused-systems.pages.dev/</link></image><lastBuildDate>Mon, 13 Apr 2026 09:29:26 +0000</lastBuildDate><atom:link href="https://focused-systems.pages.dev/tags/swift/index.xml" rel="self" type="application/rss+xml"/><item><title>ASBMUtil app</title><link>https://focused-systems.pages.dev/asbmutil-app-launch/</link><guid isPermaLink="true">https://focused-systems.pages.dev/asbmutil-app-launch/</guid><pubDate>Mon, 13 Apr 2026 09:29:26 +0000</pubDate><dc:creator>Rod Christiansen</dc:creator><category>macos</category><category>mdm</category><category>swift</category><category>devops</category><description>The Swift CLI for Apple School &amp; Business Manager API now has a native SwiftUI app. Same credentials store, same API client, same bulk operations — inside a native app.</description><content:encoded><![CDATA[<p><img src="https://github.com/rodchristiansen/asbmutil/raw/main/resources/main.png?raw=true" alt="ASBMUtil app"><p>When Apple finally released the API for Apple Business &amp; School Manager last year I was thrilled. I took it as an opportunity to try and create a Swift CLI for it which ended up being <a href="https://github.com/rodchristiansen/asbmutil">asbmutil</a>, a Swift binary that directly communicates with the <a href="https://developer.apple.com/documentation/apple-school-and-business-manager-api">Apple School &amp; Business Manager API</a>. I posted it in the MacAdmins Slack, didn't think much about it, and it ended up getting used by folks — along with <a href="https://github.com/rodchristiansen/asbmutil/issues?q=is%3Aissue%20state%3Aclosed">requests to make it better</a>.</p><p>Today I'm shipping <strong>ASBMUtil.app</strong>, a native SwiftUI front-end on top of the same engine. Same credentials store, same API client, same bulk operations — inside a native Liquid Glass Tahoe app.</p><figure class="kg-card kg-image-card"><img src="ASBMUtil-2-1.png" class="kg-image" alt="ASBMUtil app" loading="lazy" width="256" height="256"></figure><p>Not every Mac admin wants to live in the terminal. Even a well-built CLI carries its own friction — &quot;here's the command, make sure the profile is set, redirect jq somewhere, don't forget the <code>&ndash;mdm</code> flag…&quot;</p><p>Hence the GUI app. Same engine underneath, same credentials store, same API client.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://github.com/rodchristiansen/asbmutil/raw/main/resources/bulk.png?raw=true" class="kg-image" alt="ASBMUtil app" loading="lazy" width="4052" height="2210"></figure><h2 id="whats-in-the-app">What's in the App</h2><p>The main view is a single native table with every managed device in your Apple Business &amp; School Manager account — Macs, iPads, iPhones, Apple TVs, all of it — right next to each other. Click a row and a sidebar slides in with the full device details: serial, order, status, MDM assignment, AppleCare coverage, MAC addresses, the works. Select a handful, right-click, reassign.</p><ul><li><strong>Device browser</strong> — every managed device in one paginated, sortable table with a details sidebar. AppleCare coverage enrichment is a toggle away.</li><li><strong>Powerful filters</strong> — filter by device status, by order number, by model family, by MDM server — stack filters to narrow to exactly the specific devices you want.</li><li><strong>MDM server list and assignments</strong> — see every server and what's assigned to it.</li><li><strong>Bulk assign / unassign</strong> — multi-select in the table, pick a destination server, go. Or import a CSV if that's how your desired state arrives.</li><li><strong>Export to CSV or JSON</strong> — selection or filtered results out through the macOS share sheet.</li><li><strong>Multi-profile switching</strong> — flip between Apple Business &amp; School Manager instances from the sidebar; manage credentials in settings.</li></ul><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://github.com/rodchristiansen/asbmutil/raw/main/resources/profiles.png?raw=true" width="1344" height="782" loading="lazy" alt="ASBMUtil app"></div><div class="kg-gallery-image"><img src="https://github.com/rodchristiansen/asbmutil/raw/main/resources/export.png?raw=true" width="604" height="504" loading="lazy" alt="ASBMUtil app"></div></div></div></figure><h2 id="filters">Filters</h2><p>The filters match the web — same categories, same stacking behaviour — so if you already use the ABM filters, you already know how these work. The difference is that here they run against a native table with every device already loaded, so stacking is instant, the selection carries into bulk assign/unassign in one click, and the filtered set exports to CSV or JSON through the share sheet.</p><p>Filter by order number, device status, model family, or MDM server — and stack them, so <em>&quot;unassigned iPads from the last PO that haven't been routed to Intune yet&quot;</em> is three clicks.</p><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://github.com/rodchristiansen/asbmutil/blob/main/resources/filter_orders.png?raw=true" width="934" height="796" loading="lazy" alt="ASBMUtil app"></div><div class="kg-gallery-image"><img src="https://github.com/rodchristiansen/asbmutil/blob/main/resources/filter_status.png?raw=true" width="936" height="792" loading="lazy" alt="ASBMUtil app"></div></div></div></figure><p>Once you've narrowed to the right cohort, export (CSV/JSON) or bulk-reassign works on the filtered selection — not on the whole fleet.</p><h2 id="what-you-get">What You Get</h2><ul><li><strong>Pure Swift 6 binary</strong> — no external runtime dependencies.</li><li><strong>Credentials stored in the macOS Keychain</strong> — data-protection class, no per-app ACL prompts.</li><li><strong>Multiple profile support</strong> — manage several AxM instances (school-district-east, business-unit-3, whatever) from a dropdown instead of <code>&ndash;profile</code> flags.</li><li><strong>Automatic OAuth 2 client-assertion handling</strong> — token lifecycle handled for you.</li><li><strong>Paginated device fetch</strong> — walks the full inventory without you worrying about page cursors.</li><li><strong>Bulk operations</strong> — CSV import <em>or</em> multi-select directly in the GUI table.</li><li><strong><code>StrictConcurrency</code> enabled</strong> — actor-isolated, race-free by design.</li><li><strong>Bulk device-to-server resolution</strong> — server-side device listing gets the whole fleet in 4–5 API calls regardless of size, instead of per-device lookups.</li><li><strong>Bulk AppleCare enrichment</strong> — <code>list-devices &ndash;include-applecare</code> runs a two-pass fan-out across the whole fleet, no CSV required.</li></ul><h2 id="recent-api-coverage">Recent API Coverage</h2><p>I've kept the tool caught up with the AxM API as Apple has shipped new fields:</p><ul><li><strong>API 1.5</strong> — MAC addresses now support multiple values (array format) for devices with multiple network interfaces.</li><li><strong>API 1.4</strong> — Wi-Fi, Bluetooth, and built-in Ethernet MAC addresses for macOS.</li><li><strong>API 1.3</strong> — AppleCare coverage lookup for devices (single-serial via <code>get-devices-info</code>, whole-fleet via <code>list-devices &ndash;include-applecare</code>).</li><li><strong>API 1.2</strong> — Wi-Fi and Bluetooth MAC addresses for iOS, iPadOS, tvOS, and visionOS.</li></ul><h2 id="running-in-the-cloud">Running in the Cloud</h2><p>Alongside the GUI app, the other side of the coin — fully cloud, headless operations — has gotten a lot of work too. Fully static Swift runtime via <code>&ndash;static-swift-stdlib</code>, URLSession fixes for musl, and keychain-less credential provisioning from env vars so the same codebase runs cleanly on Ubuntu. One Swift 6 binary, one data model, one API client — running unchanged inside <strong>Azure Functions, AWS Lambda, or any Linux container</strong>.</p><p>Cloud automations that the Linux binary offers:</p><ul><li><strong>Reconciliation (inventory → Apple)</strong> — a trigger fires off a desired-state list (a CSV, a DB query, an event), the function asks <code>asbmutil</code> what Apple Business &amp; School Manager currently shows, diffs the two, and issues <code>assign</code>/<code>unassign</code> calls to close the gap. Anything the inventory system considers authoritative — MDM server, department, ownership tags — can drive the comparison.</li><li><strong>Enrichment (Apple → inventory)</strong> — a timer pulls the full device list plus AppleCare coverage via <code>asbmutil</code>, hashes the normalised payload per serial, stores the fingerprint in blob storage, and only writes back to inventory when the Apple-side data has actually changed. Warranty months, coverage status, order number, MDM assignment — all flow in automatically, and the hash cache keeps API traffic and downstream write-load proportional to real change, not fleet size.</li></ul><p>Outcome: inventory and Apple Business &amp; School Manager stay in lockstep without anyone opening either UI. Warranty data flows in one direction, MDM assignments in the other, both driven by the same Swift binary you'd run on your Mac.</p><p>I plan to write in more detail how I'm running these and the code behind them in follow-up posts.</p><h2 id="how-to-use-all-three">How to Use All Three</h2><p>The CLI has its place. It's the thing I'll use to check which MDM a device is assigned to when I need quick operations — <code>asbmutil list-devices-servers &ndash;mdm &quot;Intune&quot;</code> piped through <code>jq</code> and <code>grep</code>, done in a second.</p><p>For bulk operations I reach for the GUI app — load a CSV, select in the table, hit the button. Being able to <em>see</em> what's assigned, what's unassigned, and peruse the fleet side-by-side catches mistakes before they ship and surfaces clean-up opportunities you'd miss on the command line.</p><p>And for the continuous, unattended work — nightly syncs, warranty enrichment, inventory reconciliation — the Linux binary inside a serverless function quietly does it in the background, no interaction, automated.</p><p>Same data model, same trust store, same API calls underneath.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://github.com/rodchristiansen/asbmutil/raw/main/resources/servers.png?raw=true" class="kg-image" alt="ASBMUtil app" loading="lazy" width="4052" height="2210"></figure><h2 id="grab-it">Grab It</h2><p>Repo: <a href="https://github.com/rodchristiansen/asbmutil">github.com/rodchristiansen/asbmutil</a></p><p>Grab the latest <code>.pkg</code> or <code>.dmg</code> from the <a href="https://github.com/rodchristiansen/asbmutil/releases/latest">releases page</a>. The installer drops both the app into <code>/Applications</code> and the <code>asbmutil</code> CLI into <code>/usr/local/bin</code>.</p><p><strong>A note on signing:</strong> the released artifacts are unsigned and not notarized. On first launch Gatekeeper will complain — clear the quarantine attribute and you're good:</p><pre><code class="language-bash">xattr -dr com.apple.quarantine /Applications/ASBMUtil.app
xattr -dr com.apple.quarantine /usr/local/bin/asbmutil
</code></pre><p>Or build and sign it yourself from source — <code>make build</code> in the repo will do the whole signing + notarization dance if you drop your own Developer ID into <code>.env</code>. As Mac admins you know the drill.</p><p>Requirements: macOS 14+, and an AxM API account with a client ID, key ID, and PEM. If you already have the CLI configured, the app reads the same keychain profiles — nothing to migrate.</p><p>More posts coming on the specific things this tool makes trivial that ABM's web UI makes painful — starting with the bulk device-to-server resolver, which I think is the most interesting piece of the codebase. For now: v1 of the app exists, it works, and I'd love to hear where it breaks for you.</p></p>
]]></content:encoded></item><item><title>YAML Quick Look</title><link>https://focused-systems.pages.dev/yamlquicklook/</link><guid isPermaLink="true">https://focused-systems.pages.dev/yamlquicklook/</guid><pubDate>Sun, 01 Mar 2026 09:00:00 +0000</pubDate><dc:creator>Rod Christiansen</dc:creator><category>devops</category><category>macos</category><category>swift</category><category>yaml</category><description>QuickLook in the Mac doesn't support YAML files. All you get is the default app icon. I got tired of that and built a Quick Look extension.</description><content:encoded><![CDATA[<h2 id="a-native-macos-extension-for-yaml-files">A Native macOS Extension for YAML Files</h2><p>QuickLook on the Mac doesn&apos;t support previewing the contents of YAML files. You&apos;re digging through repos, config files, CI pipeline files, playbooks, terraform plan and you want to peek at a YAML file without opening it.</p><p>You hit Space.</p><p>Nothing. Just the generic document icon staring back at you.</p><p>QuickLook on the Mac supports text files, plists, JSON&#x2014;but YAML? Nothing out of the box. I fixed that with <a href="https://github.com/rodchristiansen/yamlquicklook">YAML Quick Look</a>.</p><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="yamlQuickLook-2.png" width="1024" height="1024" loading="lazy" alt srcset="https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w600/2026/03/yamlQuickLook-2.png 600w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w1000/2026/03/yamlQuickLook-2.png 1000w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/2026/03/yamlQuickLook-2.png 1024w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="Finder-2026-03-02-11.56.05-1.png" width="1856" height="1922" loading="lazy" alt srcset="https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w600/2026/03/Finder-2026-03-02-11.56.05-1.png 600w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w1000/2026/03/Finder-2026-03-02-11.56.05-1.png 1000w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w1600/2026/03/Finder-2026-03-02-11.56.05-1.png 1600w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/2026/03/Finder-2026-03-02-11.56.05-1.png 1856w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="Screenshot-2026-03-02-at-11.56.20-1.png" width="1766" height="1400" loading="lazy" alt srcset="https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w600/2026/03/Screenshot-2026-03-02-at-11.56.20-1.png 600w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w1000/2026/03/Screenshot-2026-03-02-at-11.56.20-1.png 1000w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/size/w1600/2026/03/Screenshot-2026-03-02-at-11.56.20-1.png 1600w, https://storage.ghost.io/c/7d/ec/7dec1a6c-13d6-4927-b0f1-9d39fe1b152c/content/images/2026/03/Screenshot-2026-03-02-at-11.56.20-1.png 1766w" sizes="(min-width: 720px) 720px"></div></div></div></figure><h2 id="what-it-does">What It Does</h2><p>It&apos;s a native macOS Quick Look extension that does two things:</p><ol><li><strong>Preview</strong>: Hit Space on any <code>.yaml</code> or <code>.yml</code> file, and you get a clean, scrollable plain-text view&#x2014;same experience as <code>.txt</code> or <code>.plist</code>.</li><li><strong>Thumbnails</strong>: Finder shows file content in the icon itself, so you can tell files apart without opening them.</li></ol><p>Dark mode works. Large files are handled (truncated at 10 MB so it doesn&apos;t eat your RAM). It uses a shared <code>YAMLFileReader</code> module backing both extensions, so the behaviour is consistent.</p><p>It&apos;s not a syntax highlighter&#x2014;that was a deliberate choice. Quick Look is for <em>glancing</em>, not editing. Plain text, monospace, scrollable. That&apos;s it.</p><h2 id="why-i-wrote-this">Why I Wrote This</h2><p>Honestly, it was scratching my own itch. I spend a lot of time in repos and Terraform configs, and the missing YAML preview was a small but consistent annoyance.</p><p>There are third-party alternatives, but most of them are either abandoned, use Homebrew, or are bundled inside larger editor apps. I wanted something clean, native, and deployable via a single pkg.</p><p>It&apos;s available on GitHub under MIT. It requires macOS 14 Sonoma or later.</p><h2 id="the-app">The App</h2><p>The app is three targets:</p><ul><li><code>YamlQuickLook</code> &#x2014; A lightweight container app. You open it once to register the extensions, then mostly ignore it. I added Status, Preview, and Settings tabs to make it feel like a real app rather than a hollow shell.</li><li><code>YamlQuickLookExtension</code> &#x2014; The Quick Look preview extension</li><li><code>YamlQuickLookThumbnailExtension</code> &#x2014; The thumbnail generator</li></ul><p>The shared module (<code>YamlQuickLookShared</code>) handles all the file reading logic, so both extensions stay thin. The test suite covers 38 cases across the reader&#x2014;everything from empty files to malformed YAML to files at the size limit.</p><h2 id="installing-it">Installing It</h2><p>The simplest path: grab the latest zip from <a href="https://github.com/rodchristiansen/yamlquicklook/releases">Releases</a>, move the app to <code>/Applications</code>, and run the post-install script to strip quarantine and activate the extension:</p><pre><code class="language-bash">xattr -cr /Applications/YamlQuickLook.app
pluginkit -a /Applications/YamlQuickLook.app/Contents/PlugIns/YamlQuickLookExtension.appex || true
pluginkit -a /Applications/YamlQuickLook.app/Contents/PlugIns/YamlQuickLookThumbnailExtension.appex || true
qlmanage -r
qlmanage -r cache
</code></pre><h2 id="building-with-code-signing">Building with Code Signing</h2><p>The release build isn&apos;t code-signed&#x2014;I don&apos;t have (yet) a Developer ID. You can sign and notarize with your own cert for your environment. I plan to release it onto the Mac App Store.</p><p>If you want a properly signed and notarized build&#x2014;which I&apos;d recommend for org-wide deploys&#x2014;clone the repo and configure signing in Xcode for all three targets:</p><pre><code class="language-bash">xcodebuild -scheme YamlQuickLook \
  -configuration Release \
  -derivedDataPath build \
  CODE_SIGN_IDENTITY=&quot;Developer ID Application: Your Name (TEAM_ID)&quot; \
  DEVELOPMENT_TEAM=&quot;TEAM_ID&quot; \
  clean build
</code></pre><p>Then notarize it with <code>xcrun notarytool</code> and staple the ticket. The <a href="https://github.com/rodchristiansen/yamlquicklook">README</a> has the full steps. A signed build skips the <code>xattr</code> step entirely and removes the quarantine friction for your users.</p><p>The Makefile also has a <code>make release</code> target that handles the signing and notarization flow if you&apos;ve set up your credentials in <code>.env</code>.</p><hr>
]]></content:encoded></item></channel></rss>