mirror of
https://github.com/kfattic/cupsadmin.git
synced 2026-11-13 19:06:43 -05:00
6.2 KiB
6.2 KiB
cupsadmin
A native replacement for the CUPS web interface (http://localhost:631) that Apple removed in
macOS 27. cupsd, lpadmin, lpstat, lpoptions and IPP Everywhere queues still work; only the
browser admin pages are gone. Two front ends share one library:
- CupsKit (
Sources/CupsKit) — IPP client, PPD parser, CUPS tool wrappers, read-back checks. Pure logic: returns values or throws, never prints. cupsadmin(Sources/cupsadmin) — the CLI, a thin layer over CupsKit.- CUPS Admin.app (
Sources/CUPSAdminApp) — the SwiftUI app. The target isCUPSAdminApp, notCUPSAdmin: APFS is case-insensitive, soSources/CUPSAdminis the same folder asSources/cupsadminand the two executables would collide in.build/.
Private maintainer notes, if present, are in CLAUDE.local.md (gitignored).
Architecture
- Reads are IPP with a small hand-written encoder/decoder (RFC 8010,
CupsKit/IPP): CUPS-Get-Printers, Get-Printer-Attributes, Get-Jobs, Get-Job-Attributes, Cancel-Job, CUPS-Get-Default. This gives typed data (state reasons,*-default,*-supported, job state) instead of localizedlpstattext. - Transport is the Unix socket
/private/var/run/cupsd, not TCP 631. On macOS 27 launchd socket-activates cupsd only on that socket; cupsd binds port 631 itself while running and exits after about a minute idle, so TCP fails whenever it's asleep. URLSession can't use AF_UNIX, soUnixSocketHTTP.swiftspeaks minimal HTTP/1.1 over POSIX sockets. PPDs are fetched the same way (GET /printers/<queue>.ppd). - Writes shell out through
CupsTools:lpadmin,cupsenable/cupsdisable,cupsaccept/cupsreject,cancel,lp,lpmove,lpinfo.lpadminalready handles_lpadminauthorization and IPP Everywhere PPD generation. - Queue defaults are written with
lpadmin -p <queue> -o key=value(into the queue's PPD / printers.conf, for every user) — neverlpoptions.~/.cups/lpoptionsis only read, to point out a per-user override. - PPD options keep their
*OpenGroup, UI type and*ParamCustomtype/range. Custom values are written asCustom.<value>and quoted when they contain spaces (unquoted,lpadminsilently truncates). - Quick actions are vendor-neutral.
QuickActions.swiftdefines the actions; what each writes comes fromResources/driver-profiles.json(embedded in code — the CLI is a single binary), matched on the PPD's*Manufacturer/*NickName, vendor profiles first andgeneric(standard PPD keywords, IPP Everywhere*-defaultattributes) last. A pair is used only if that queue supports the keyword and value; otherwise the action is unavailable with the reason.cupsadmin ppdreport <queue>shows a driver's keywords and which actions its profile enables.
Build and test
swift build # debug build of everything
.build/debug/cupsadmin printers # run the CLI
./test.sh # Swift Testing via the Command Line Tools (see flags inside)
./build.sh --build-only # universal CLI + app bundle, ad-hoc signed, no identities needed
./build.sh --screenshots # regenerate docs/screenshots/app-jobs.png from temporary demo queues
./build.sh # release: sign, payload-free PKG, notarize, staple, spctl
- Only the Command Line Tools are required. Universal builds are per-triple
(
swift build -c release --triple arm64-apple-macosx14.0andx86_64-…) pluslipo;--arch arm64 --arch x86_64needs full Xcode. - Package: tools-version 6.0, Swift 5 language mode, macOS 14 minimum.
build.shreadsCODESIGN_APP_IDENTITY,CODESIGN_PKG_IDENTITY,NOTARY_PROFILE(and optionalCODESIGN_TEAM_ID) from the environment or an untrackedbuild.env. Never hard-code identities.- The PKG is payload-free (
pkgbuild --nopayload --scripts;pkg/postinstallcopies the binary), because any payload's "." entry would reset an existing/usr/local/bin's ownership. - The app icon master is
Icon/AppIcon-1024.png;build.shgeneratesAppIcon.icnsfrom it. Don't redraw it. - Live tests are opt-in and write to queues:
CUPSKIT_LIVE_QUEUE=<queue>,CUPSKIT_LIVE_JOBS=<a>,<b>,CUPSKIT_LIVE_QUICK=<ricoh>,<generic>. Use throwaway queues (e.g.lpd://127.0.0.1/…, paused) and delete them afterwards; the live tests restore what they change. Offline tests use vendor PPDs from/Library/Printers/PPDsand skip when they aren't installed.
Conventions
- US spelling everywhere — code, UI, CLI output, docs (color, canceled, gray).
- No third-party dependencies. No Xcode project.
- CLI status lines on every exit path:
started HH:MM:SS, exactly oneOK: …orERROR: …, andfinished HH:MM:SS (total N min)— on stderr, so tables on stdout pipe cleanly. Exit 0 success, 1 runtime error or not applied, 2 usage error. Existing command output is a contract: don't change it casually. Scripts (build.sh,test.sh) print the same started/finished lines. - Read back after every write. Re-query cupsd and compare; a change that didn't take is reported
(
NOT APPLIED, non-zero exit, or a red X with the reason) — never silent. - App feedback: every action updates the bottom status line with its timing ("Set 3 options on
Office in 0.4 s", "ERROR: …") and logs the same through
os_log(subsystemedu.wku.cupsadmin.app). Failures show the tool output and, for permission errors, the_lpadminfix. The exact command is visible before it runs. Destructive actions confirm and name the object. - Secrets: passcode/password options written as queue defaults land in a world-readable PPD — warn,
and mask them in displayed commands. Never commit
build.env,dist/, notary output orCLAUDE.local.md. - Native SwiftUI and macOS idioms only:
NavigationSplitView, sortable/resizableTable,Formwith.formStyle(.grouped), standard sheets, alerts and confirmation dialogs, SF Symbols, system fonts, semantic colors (.primary,.secondary,.green/.blue/.redstate dots),ContentUnavailableViewempty states. No custom chrome, no web-style cards, no emoji. Must look right in light and dark mode. Tables and text are selectable and copyable.