chore: blog full QR output + dashboard screenshot, hero script phone setup scene

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Razvan Dimescu
2026-04-10 22:32:11 +03:00
parent 2c20c56421
commit 6524b389c5
3 changed files with 52 additions and 22 deletions

View File

@@ -132,20 +132,41 @@ $ numa setup-phone
Numa Phone Setup Numa Phone Setup
Profile URL: http://192.168.1.16:8765/mobileconfig Profile URL: http://192.168.1.10:8765/mobileconfig
▀▀▀▀▀▀▀█▀▀██ ██ ▀█▀▀▀▀▀▀▀ ██████████████████████████████████
█▀▀▀█ █▀▄▀▀▀▀▄▄█ █▀▀▀█ ███████████████████████████████████
... ████ ▄▄▄▄▄ ██ ▀█ ▀▀▀▄▀ ▀▀█ ▄▄▄▄▄ ████
████ █ █ █ ▄▀ ▄█▀▄▀█▄▀█ █ █ ████
████ █▄▄▄█ █ ▀▄▄ ▀ █▄▀▀█▀█ █▄▄▄█ ████
████▄▄▄▄▄▄▄█ ▀▄▀▄█▄█ █▄█▄█▄▄▄▄▄▄▄████
████ ▀▄▄▄▄▄█▀ ▀██▄ ▄ ▄▀█▀█ ▄ ▄▄█▀████
█████▄▄▀▄▀▄▄█▄ ▀████▀▄▄▀█▀▀▄ ██▀█████
████▄██▄ ▀▄ █ █ █▀█▄▄██ ▄▄▀▄▀▄ █▀████
█████ ▀ ▄▀ ▄▀▄ ▄▄▀ ██ ▄▀██▄▀█████
████ ▀▀ █▄█▄▀ ▄ █▄ ▄█▀▄ ▀█▀▀ █▀████
████ ██▀█ ▄▄▀█▄▄██▀▄▀ ▀█▄▀ █▀▄▄▀█████
████▄█▄▄▄▄▄█▀▄█▄█▀▀ ▀██▀ ▄▄▄ ▀ ████
████ ▄▄▄▄▄ █▀▀▀▀ ▄█▀ ▀▄ █▄█ ▄▄▀█████
████ █ █ █ ▄ ██▀▄ ▄▄██ ▄ ▄▄▄██████
████ █▄▄▄█ █▄ ▄▀▀▄▄█▀▄▀▄ ▀▄▀ ▄█ █████
████▄▄▄▄▄▄▄█▄▄█▄▄▄█▄█▄▄██████▄▄██████
█████████████████████████████████████
On your iPhone: On your iPhone:
1. Open Camera, point at the QR code, tap the yellow banner 1. Open Camera, point at the QR code, tap the yellow banner
2. Allow the download when Safari asks 2. Allow the download when Safari asks
3. Settings "Profile Downloaded" → Install 3. Open Settings — tap "Profile Downloaded" near the top
4. Settings → General → About → Certificate Trust Settings (or: Settings → General → VPN & Device Management → Numa DNS)
4. Tap Install (top right), enter passcode, Install again
5. Settings → General → About → Certificate Trust Settings
Toggle ON "Numa Local CA" — required for DoT to work Toggle ON "Numa Local CA" — required for DoT to work
``` ```
The same QR is available in the dashboard — click "Phone Setup" in the header and the popover renders an SVG QR code pointing at the mobileconfig URL. On mobile viewports it shows a direct download link instead.
<img src="../phone-setup-dashboard.png" alt="Numa dashboard with Phone Setup popover showing QR code and install instructions">
Step 4 is non-negotiable. Even though the CA is bundled in the same profile that installs the DNS settings, iOS still requires the user to explicitly toggle trust in Certificate Trust Settings. It's a deliberate iOS policy to prevent profile-based trust injection — annoying, and correct. Step 4 is non-negotiable. Even though the CA is bundled in the same profile that installs the DNS settings, iOS still requires the user to explicitly toggle trust in Certificate Trust Settings. It's a deliberate iOS policy to prevent profile-based trust injection — annoying, and correct.
I've been dogfooding this since v0.10 shipped in early April. The phone resolves through Numa over DoT whenever I'm home; persistent connections are visible in the log as a single source port living through dozens of queries. The one real caveat: if the laptop's LAN IP changes, the profile breaks. [RFC 9462 DDR](https://datatracker.ietf.org/doc/html/rfc9462) fixes that — Numa can respond to `_dns.resolver.arpa IN SVCB` with its current IP and iOS picks it up on each network join. Next piece of work. I've been dogfooding this since v0.10 shipped in early April. The phone resolves through Numa over DoT whenever I'm home; persistent connections are visible in the log as a single source port living through dozens of queries. The one real caveat: if the laptop's LAN IP changes, the profile breaks. [RFC 9462 DDR](https://datatracker.ietf.org/doc/html/rfc9462) fixes that — Numa can respond to `_dns.resolver.arpa IN SVCB` with its current IP and iOS picks it up on each network join. Next piece of work.

View File

@@ -7,18 +7,19 @@
# The script: # The script:
# 1. Opens the dashboard in Chrome --app mode (clean, no address bar) # 1. Opens the dashboard in Chrome --app mode (clean, no address bar)
# 2. Generates DNS traffic (forward, cache hit, blocked) # 2. Generates DNS traffic (forward, cache hit, blocked)
# 3. Types "peekm" / "6419" into the Local Services form on camera # 3. Opens Phone Setup QR popover
# 4. Shows LAN accessibility badge ("local only" / "LAN") # 4. Types "peekm" / "6419" into the Local Services form on camera
# 5. Checks a blocked domain # 5. Shows LAN accessibility badge ("local only" / "LAN")
# 6. Opens peekm.numa to show the proxy working # 6. Checks a blocked domain
# 7. Records via ffmpeg and converts to optimized GIF # 7. Opens peekm.numa to show the proxy working
# 8. Records via ffmpeg and converts to optimized GIF
set -euo pipefail set -euo pipefail
# --------------- Configuration --------------- # --------------- Configuration ---------------
OUTPUT="${1:-assets/hero-demo.gif}" OUTPUT="${1:-assets/hero-demo.gif}"
PORT=5380 PORT=5380
RECORD_SECONDS=20 RECORD_SECONDS=24
VIEWPORT_W=1800 VIEWPORT_W=1800
VIEWPORT_H=1100 VIEWPORT_H=1100
FPS=12 FPS=12
@@ -230,8 +231,16 @@ dig @127.0.0.1 github.com +short > /dev/null 2>&1
dig @127.0.0.1 ad.doubleclick.net +short > /dev/null 2>&1 dig @127.0.0.1 ad.doubleclick.net +short > /dev/null 2>&1
sleep 3 sleep 3
# --------------- Scene 2: Add peekm service via UI (3-7s) --------------- # --------------- Scene 2: Phone Setup popover (3-7s) ---------------
log "Scene 2: Adding peekm.numa service..." log "Scene 2: Phone Setup QR popover..."
run_js "document.querySelector('#phoneSetup button').click();"
sleep 3
# Dismiss popover
run_js "document.getElementById('phoneSetupPopover').style.display = 'none';"
sleep 1
# --------------- Scene 3: Add peekm service via UI (7-11s) ---------------
log "Scene 3: Adding peekm.numa service..."
# Services panel is now first — scroll to it # Services panel is now first — scroll to it
run_js " run_js "
@@ -249,18 +258,18 @@ sleep 0.3
run_js "document.querySelector('#serviceForm .btn-add').click();" run_js "document.querySelector('#serviceForm .btn-add').click();"
sleep 2 sleep 2
# --------------- Scene 3: Open peekm.numa (7-11s) --------------- # --------------- Scene 4: Open peekm.numa (11-15s) ---------------
log "Scene 3: Opening peekm.numa in browser..." log "Scene 4: Opening peekm.numa in browser..."
open "http://peekm.numa/view/peekm/README.md" 2>/dev/null || true open "http://peekm.numa/view/peekm/README.md" 2>/dev/null || true
sleep 4 sleep 4
# --------------- Scene 4: Back to dashboard (11-14s) --------------- # --------------- Scene 5: Back to dashboard (15-18s) ---------------
log "Scene 4: Back to dashboard — LAN badges + LOCAL queries visible..." log "Scene 5: Back to dashboard — LAN badges + LOCAL queries visible..."
osascript -e "tell application \"System Events\" to set frontmost of (first process whose unix id is $CHROME_PID) to true" 2>/dev/null || true osascript -e "tell application \"System Events\" to set frontmost of (first process whose unix id is $CHROME_PID) to true" 2>/dev/null || true
sleep 3 sleep 3
# --------------- Scene 5: Check Domain blocker (14-17s) --------------- # --------------- Scene 6: Check Domain blocker (18-21s) ---------------
log "Scene 5: Check Domain — blocked tracker..." log "Scene 6: Check Domain — blocked tracker..."
# Scroll down to blocking panel # Scroll down to blocking panel
run_js " run_js "
var blockPanel = document.getElementById('blockingPanel'); var blockPanel = document.getElementById('blockingPanel');
@@ -273,8 +282,8 @@ sleep 0.3
run_js "document.querySelector('#checkDomainInput').closest('form').querySelector('.btn').click();" run_js "document.querySelector('#checkDomainInput').closest('form').querySelector('.btn').click();"
sleep 2 sleep 2
# --------------- Scene 6: Terminal-style dig overlay (17-20s) --------------- # --------------- Scene 7: Terminal-style dig overlay (21-24s) ---------------
log "Scene 6: dig proof overlay..." log "Scene 7: dig proof overlay..."
DIG_RESULT=$(dig @127.0.0.1 peekm.numa +short 2>/dev/null | head -1) DIG_RESULT=$(dig @127.0.0.1 peekm.numa +short 2>/dev/null | head -1)
run_js " run_js "
var overlay = document.createElement('div'); var overlay = document.createElement('div');

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB