Appendix Q: NightGardDDNS (Shop Manual)
Status: v1.0.0 production ready. Self-distributed — not available on the App Store (the App Store doesn't allow this kind of app; see "Why not the App Store" below). GPL v3. Public repo at github.com/fluhartyml/NightGard under Modules/NightGardDDNS. Distributed as a Swift Package (reusable library) plus a demo app. NightGardDDNS provides automatic Dynamic DNS (DDNS) update functionality for macOS and iOS. It monitors the user's public IP address and updates a DDNS provider (currently DuckDNS) when the IP changes.
What it does
A user with a dynamic IP address from their ISP — meaning the public IP can change without warning — needs a way to keep a domain name pointing at their current address. Dynamic DNS providers (DuckDNS, No-IP, DynDNS) give the user a domain name; the user's job is to tell the provider whenever the IP changes. NightGardDDNS automates that telling-step.
The flow:
- App launches; the user configures their DuckDNS domain and token in Settings.
- The app reads the local network IP via BSD sockets (
getifaddrsonen0/en1). - The app fetches the public IP via fallback services (ipify.org, icanhazip.com, ifconfig.me).
- If the public IP has changed since last check, the app POSTs an update to DuckDNS with the user's domain + token + new IP.
- The app repeats steps 2–4 on a configurable timer (default: every 5 minutes).
Architecture overview
Small project. Swift Package + demo app pattern.
- App entry:
NightGardDDNSApp.swift—@mainscene. - Root:
ContentView.swift— status display + start / stop service controls. - Settings:
SettingsView.swift— configuration form (domain, token, update interval). - Service:
DDNSService.swift— the@Observable@MainActorclass that does the IP detection + DuckDNS update + timer scheduling. - Notes:
NightGardDDNS_DeveloperNotes.swift— canonical reference with the full architecture writeup.
Settings persistence
Configuration (domain, token, update interval) persists in UserDefaults. No external dependencies, no SwiftData, no networking beyond the public-IP probe and the DuckDNS update. The token is sensitive (it's the key to updating the user's DDNS record) but the app stores it in UserDefaults for simplicity. A production hardening pass would migrate the token to the system keychain.
Why not the App Store
App Store policy effectively disallows this kind of app. Three concrete reasons:
- Background networking on a timer is not the App Store's preferred model. NightGardDDNS runs a foreground-app timer that pings external services and posts updates to DuckDNS every few minutes. The App Store wants apps that respond to user actions; daemon-like always-on networking belongs in launchd or a network extension, neither of which fits a standard App Store app.
- The app's primary purpose duplicates router / network-appliance functionality. Most routers ship with built-in DDNS clients; running one in a phone or laptop app is a workaround for routers that don't, or for users who want it portable. App Store guideline 4.2 (Minimum Functionality) and the broader "this should be a system tool, not an app" pattern push apps like this toward direct distribution.
- It's a developer / sysadmin utility, not a consumer product. The configuration requires understanding what a DDNS provider is, what a token is, and what a domain points at. The App Store is positioned as a consumer storefront; this app's audience is people running their own network infrastructure.
None of these are absolute App Store rejections every time, but together they make App Store distribution costly to attempt and likely to be rejected on review. Self-distribution sidesteps all three.
How to get it (self-distribution path)
NightGardDDNS is distributed through three paths:
- GitHub clone-and-build. The repo is public; readers clone, open in Xcode, and build for their own device. This is the default path for the book's roster and the canonical way to get NightGardDDNS.
- Swift Package import. Other Mac / iOS projects can add NightGardDDNS as a Swift Package dependency and use the library code directly — the demo app is the example, not the only deliverable. The package follows the "Lego brick" reuse pattern of the NightGard Module Library.
- Mac Developer ID signed direct download (future). Mac distribution outside the App Store uses Apple's Developer ID signing + notarization process. The app gets signed and notarized, the user downloads it from a website, and macOS Gatekeeper accepts it because it's signed and notarized. iOS direct distribution is more constrained — AltStore, TestFlight, or enterprise certificates are the options — and NightGardDDNS doesn't currently target any of them for end-user iOS distribution.
The Module Library context
NightGardDDNS was extracted from a larger project (wWw NGPortal) in November 2025 as part of the NightGard Module Library initiative — a project to break common app functionality into reusable Swift packages developers can use to rapidly build applications without reinventing the same code. NightGardDDNS is one Lego brick; PlaylistLocker (Appendix P) is another. The pattern is: build the feature once for a real app, harden it, extract the reusable part into a package, ship both the demo app (to prove it works) and the package (so others can use it).
Build + deployment
- Name: NightGardDDNS.
- Distribution: Self-distributed via GitHub clone-and-build and Swift Package import. Not on App Store.
- Platforms: iOS 17+, macOS 14+.
- Swift: 6.2+.
- Frameworks: SwiftUI (UI), Foundation (
URLSessionfor IP probes and DuckDNS POSTs, BSD sockets for local IP viagetifaddrs), Observation (@Observablemacro). - External services: DuckDNS (requires the user's free DuckDNS account), plus three public-IP-detection services (ipify.org, icanhazip.com, ifconfig.me) used as a fallback chain.
Smart update logic
The service is intentionally conservative about hitting DuckDNS:
- Only updates when the public IP actually changed. Comparing the new IP against the last-known IP avoids spurious API calls when nothing has changed.
- Immediate update on service start. The first run after launching the app posts an update so the user gets a quick "yes, it's working" feedback.
- Periodic checks on the timer interval. Default 5 minutes. Configurable in Settings.
Known gotchas
- The token in
UserDefaultsis convenience-over-security. A hardening pass would migrate the DuckDNS token to the system keychain. For a single-user self-distributed app on the user's own device, the convenience is reasonable; for any wider distribution it should change. - Background timer is foreground-only. On iOS, the timer only fires while the app is in the foreground. If the user backgrounds the app or locks the device, updates stop until the next foreground session. On macOS, the timer fires as long as the app is running, which is the normal Mac expectation.
- Public-IP probe services can fail. The fallback chain (ipify → icanhazip → ifconfig.me) handles single-service outages, but a network with all three blocked produces no update. The service surfaces the failure in the UI rather than retrying forever.
- DuckDNS is the only provider currently supported. The API surface is provider-specific; supporting No-IP, DynDNS, or Cloudflare's DNS API would require a per-provider adapter. Worth doing for any wider audience; not in v1.0.0 scope.
- App Store policies can change. The "why not App Store" reasoning here is current as of v1.0.0. If Apple's policies evolve to include DDNS utilities, the distribution model could revisit App Store submission — but the architecture (timer, background networking) would need its own pass too.
Sources
- Project source: github.com/fluhartyml/NightGard under
Modules/NightGardDDNS(verified 2026-05-10). - Canonical roadmap:
NightGardDDNS/NightGardDDNS_DeveloperNotes.swiftin the project repo. - DuckDNS provider documentation: duckdns.org/spec.jsp.
- Related: NightGard Module Library initiative (covers the "reusable Lego brick" packaging pattern).