If you have HomePods and worse-than-expected battery life on iPhone, try turning off Settings > General > AirPlay > “Transfer to HomePod.” It’s expensive in power as it’s constantly ranging.
In the screenshot, I turned off the setting during the middle charge. First time I have seen flat battery drain in months.
Another unexpected drain: the Finder device setting “Show this iPhone when on Wi-Fi” causes a large amount of energy usage by lockdownd, via saagar.
Discovered this using his excellent sysdiagnose energy log viewer.
Put together an app to compile user profiles from Slack into a Fediverse directory.
I tried out GitHub’s Copilot and I was pleasantly surprised at its helpful and timely suggestions. Navigated around a lot of Python-isms for me.
The Slack API was a bit more frustrating. To see custom fields you must issue 1 API call per user and the rate limit ends up at about 3 requests per second. Not so speedy in a nearly 4000 member one.
In this article, one of the SF Standard journalists sneaks into the Open AI office to try and find a subject to interview but ends up being asked to leave. It really paints them in a weird light, I wonder why they published it.
With unlimited options in the unlaunched gTLD .music
they chose belem.music (live, despite Mastodon not thinking so) as their domain name.
Random bug of the day:
The MatterAddDeviceRequest API new in MatterSupport requires adding the _matter._tcp
value to the Info.plist list of allowed Bonjour services, otherwise it errors with “Local browse failed; unable to start an add device operation” with error code -65555.
Fun new Seasonal post about artichokes and their wild history de-domesticating in California.
I needed to put Cloudflare in front of Caddy and realized that the X-Forwarded-For
header became less trustworthy since Cloudflare will pass along the value from the original request.
This is how I cleaned up the header without messing with Cloudflare rules.
X-Forwarded-For sanitization in Caddy
When reverse proxies like Cloudflare proxy a request, they communicate the original request’s source using the X-Forwarded-For
header. This can be dangerous: they will augment a value from the original request producing a value like Spoofed, Cloudflare-Given
.
When adding another reverse-proxy like Caddy into the mix you send upstream a value like Spoofed, Cloudflare-Given, Caddy-Given
. This is messy; I don’t want to teach upstream what the history of the request is and which proxies are valid.
The Caddy docs on reverse_proxy
indicate a way to fix this is by creating transformation rules within Cloudflare to strip the Spoofed
value in certain situations. This requires too much work and remembering to it when configuring a new site.
A tactical way to accomplish this is to use the semantics of the header value itself. Our downstream, trusted proxies are either setting the value to IP
or appending , IP
so we can disregard the earlier parts of the header value entirely. In a Caddyfile, this is done using the request_header
directive:
# Remove older X-Forwarded-For values since we only want to
# trust the last one iif we're going to trust at all.
# Regex information: https://regex101.com/r/KZknzS
request_header X-Forwarded-For "^([^,]*,)*\s*([^,]+)$" "$2"
# Later on in the server configuration somewhere
reverse_proxy upstream:1234 {
# Which proxies allow X-Forwarded-For to go upstream
trusted_proxies 198.51.100.0/24
}
When a direct request to Caddy comes in with a fake X-Forwarded-For
header, it’s not passed upstream because it’s not a trusted proxy. This is the normal behavior.
When a request comes in via a trusted proxy with an X-Forwarded-For
value like Spoofed, Cloudflare-Given
we pass to upstream Cloudflare-Given, Caddy-Given
without the Spoofed
part.
Interesting cascading effect of the Twitter sale: the .social gTLD saw a 435% spike in registrations.
The Defunctland video on Disney Channel’s Theme is enthralling. It’s an hour and a half of great storytelling surrounding the question: who wrote the theme, and when?
This opinion piece by Olivia Maki beautifully introspects the “post”-pandemic service industry:
Now, more than ever, perpetuating a service style that subjugates one person to another — server to served — only reinforces and exacerbates hierarchical thinking and encourages customers to dehumanize service workers. Modern service should be built on respect, which needs to go both ways to be sustained long term.
How landline telephones became a must-have in old San Francisco contains a neat tidbit: Shared 20-person outbound-only lines were the hook that drove adoption of more individual phone lines. Customers dipped their toes in the water and discovered they liked it.
A Retrospective on the IANA Transition describes how control of the DNS root servers were transitioned from IANA to ICANN as well the potential problems which were anticipated and occurred. Interesting insight into internet management bureaucracy.
Just finished listening to the first season of the podcast uswithoutThem discussing the first album of mewithoutYou, A→B Life. The depth of the philosophical, historical, and musical discussions were impressively entertaining and make me appreciate the album all the more.
Sending text and files securely
My desires for sharing something like credentials are all over the place:
- End-to-end encrypted is a must, no way around it; the options for less-than-encrypted is a lot larger and well-served by e.g. my employer.
- Self-hostable because the history of this app segment revolves around shutdowns. I can spin up a new Ansible role for most containerized things in a blink.
- Command-line interface since I’m inclined to make helper scripts for most workflows.
- Easy to use for the receiver because not everybody in my life can install something on the command line, making the web the optimal delivery mechanism.
- Expiring uploads because an out-of-band send shouldn’t exist long-term and because there’s better options for perpetual file hosting.
Firefox Send was fantastic. Mozilla killed it off due to moderation issues, and likely because they’re unable to ship and maintain products that aren’t Firefox itself these days. It sets the tone for the rest of these services because it leveled up the space. I miss it.
Wormhole.app is the spiritual successor of Firefox Send but lacks a command-line interface and is not open source. Normally I’m not one to bemoan something like this being closed source and free but I’d much rather pay for a service than watch yet another thing shut down.
magic-wormhole is a great peer-to-peer-only command-line option, and I often use it to send files between my own machines and servers, but it requires both sides be online at the same time and requires installing it on the device. Wormhole.app – almost certainly stealing the name from this one – also does peer to peer through WebRTC.
FileSend by Standard Notes works well, is self-hostable, but has a rather limited scope of impact and community around it, eliminating any possible extension into the command-line world. Difficult to host publicly without allowing anonymous uploads.
Bitwarden Send is self-hostable, most easily via Vaultwarden which is what I use, supports large file sizes, has a decent command-line integration, and has a great option for text-only sends as well. The biggest issue is interface: authenticating into a Bitwarden instance via master password has to happen before you can send the files, which adds overhead to working with it both on the command line and web/clients.
Send (with many hosted instances) is a fork of the defunct Firefox Send codebase. It is also difficult to host without allowing anonymous uploads. ffsend
by the same author is a capable command line interface, and supports using Basic Auth in front of the instance. In my testing, requiring auth on /api/ws
is enough to prevent anonymous upload but still allow downloading.
For my personal use, I went with self-hosting Send on a cheap VPS that I’ve already got a few services running on.
Were I picking something for sharing within a team/family/company I’d probably go with Bitwarden, since it’s so extensible and likely a good choice for general password storage/sharing as well.
I’ll probably recommend Wormhole.app for one-off sends from others as long as it exists. Hopefully that’s for a little bit longer than Firefox’s attempt lasted.
IPv6 in a home environment
IPv6 addresses are broken up into two halves: the network prefix and the interface identifier. It looks something like this, in hex notation:
aaaa:bbbb:cccc:dddd:1111:2222:3333:4444
The alphabetical and bold parts of this are the network prefix and the numerical are the interface identifier.
The network prefix is then divided into a routing prefix (controlled by the ISP) and a subnet prefix (controlled by home network). ISPs will allocate a routing prefix of (in CIDR notation) /56, /60 or /64—meaning the first 56, 60 or 64 bits are reserved—and the rest are available to play with for the subnet. Routers generally request a /64 from upstream by default, which is one IPv6 network.
The interface identifier is picked by the device connecting to the network using SLAAC after being told which network prefixes are available from a router announcement. DHCPv6 is typically overkill and unnecessary on a home network.
In IPv4 a private address space like 192.168.0.0/16
is used locally and translated to a public address on the internet via NAT since there’s way too few IPv4 addresses for the number of devices that need to get on the internet. This NAT requirement also turned into a benefit in a home network as it is easier to use and memorize these shorter addresses.
Every IPv6 guide bemoans this private address desire and instead suggests adopting and using global addresses instead. The advice on the internet about IPv6 addressing is almost entirely focused on businesses who request and receive a static IPv6 prefix from a regional registry and completely overlook the benefits to home users without major infrastructure to handle configurations. However, as a home user:
- The global address may change. In fact, it’s guaranteed on my home network because the AT&T modem randomly hands out one of its non-reserved /64s depending on the mood it’s in.
- It’s a lot to memorize and type. Focusing on just the interface identifier portion is all that really matters in a local network. That part’s unlikely to change, and can be hard-coded on individual devices if desired.
Unlike IPv4, devices can and will have multiple simultaneous IPv6 addresses:
- One or two (or more) global addresses.
- One or more local addresses (ULAs). Yes, local addresses are a thing!
For global addresses, non-end-user devices will likely choose a random interface identifier and rotate it regularly to avoid external tracking. For servers and for local addresses, devices will use a modified version of their MAC address (EUI) to have a stable interface identifier.
It’s important, then, to allow devices to have a local address. This is shockingly not the default on consumer routers. The router should announce:
- A global address prefix allocated by the ISP. For example,
2001:db8:496b:942::/64
. - A local address prefix. For example,
fd00::/64
. There’s a suggested algorithm for generating these but that’s overkill for a home network unlikely to ever need to network with another privately. Keep it simple.
To implement this on pfSense, assuming “turn on IPv6” is checked:
- Set the WAN interface to use DHCPv6 to get a /64 prefix delegation.
- Set the LAN interface to “Track Interface” from the WAN interface.
- Add a virtual IP address (like
fd00::1/64
) to the router. - Add a router announcement subnet (under DHCPv6/RA) of
fd00::/64
. The one the LAN tracks will automatically be announced, even if not entered. - Set the DNS server (under DHCPv6/RA) to
fd00::1/64
. - Make sure you’ve got IPv6-allowed-to-
*
rules in your LAN.
The router will now periodically and when queried announce to the network which address prefixes to use, what DNS server to talk to, and various other configuration.
The LAN interface of the router can now be addressed at fd00::1
and network devices will pick stable addresses in the fd00::/64
network (e.g. fd00::a9cd:efff:feab:cdef
); they can be used in configurations without worrying about changing, and IPv6 will continue to work locally even without internet.
Some devices may also advertise their own local prefixes; Thread uses IPv6 so aggressively that its devices will often send out an RA of a random local prefix if they do not pick one up on their own. Apple HomePod minis are particularly guilty of this, and it was tough to narrow down that they were the cause.
The version of less
which ships in macOS Monterey (12.0) is 487, which was released in March 2017. Since then, it’s gained some features:
- Line marking. Marking a line allows you to assign a shortcut letter to it to jump back.
- Search result clearing.
- Adds a status column, to show marks and indicate lines where search results are found.
- Mouse support, so e.g. scrolling works in the document using the mouse.
At first it seemed like this may be the general problem of Apple disallowing GPL version 3 software, but less is dual-licensed to a more Apple-favorable one as well. It’s somewhat inexplicable that such a commonly-used command line library sees no updates.
Fortunately, it’s included in Homebrew. And even better, unlike the Apple distribution, they do not disable lesskey
support, which allows specifying additional command and keyboard shortcuts for less
to use.
Creating iOS simulators in bulk
To work around Xcode’s disinclination for creating new simulators, I wrote a script which deletes all the current simulators and then creates every possible simulator. It’s relatively straightforward because simctl
has a decent JSON interface which makes processing the state a lot nicer:
#!/usr/bin/env fish
# Just to make it obvious when using the wrong version
printf "Using Xcode at %s\n\n" (xcode-select -p)
echo "Deleting all simulators..."
xcrun simctl shutdown all >/dev/null
xcrun simctl delete all >/dev/null
printf "...done\n\n"
echo "Creating new simulators..."
# You could also add 'appletv' to this list
for runtime in ios watch
set -l runtimes (xcrun simctl list runtimes $runtime available -j | jq -c '.runtimes[]')
for runtime in $runtimes
set -l runtime_version (echo $runtime | jq -r '.version')
set -l runtime_identifier (echo $runtime | jq -r '.identifier')
set -l supported_devices (echo $runtime | jq -c '.supportedDeviceTypes[]')
for device in $supported_devices
set -l device_name (echo $device | jq -r '.name')
set -l device_identifier (echo $device | jq -r '.identifier')
set -l display_name "$device_name ($runtime_version)"
printf \t%s\n $display_name
xcrun simctl create $display_name $device_identifier $runtime_identifier >/dev/null
end
end
end
printf "...done\n\n"
The only thing missing here is device pairing – connecting a watch and phone together. Since there’s limitations around the number of devices which can be paired together, I find this a bit easier to still do manually.
Making shift-space send page up and other key mappings in iTerm2
A common problem when I am paging through less
output is that, while the space key will go down a page, the shift-space shortcut does not go up. The underlying reason is terminals are strings of text and date back decades and many key combinations are archaic sequences.
Shift-space is is one of these cases. While the space key inserts visible text, the shift-space variant doesn’t have a unique character. This requires hoping particular app can handle the sequence CSI 32;2 u
(which may look like ^[[32;2u
; this is part of a proposal known as CSI u), or changing the key map in the terminal. I went for the latter.
I use iTerm2, which is stellar for many reason. Its key mapping control can do what we want here, and looks like so:
You can figure out what escape sequence to send for a particular existing key combination using your shell by entering a key-reading mode and then the shortcut.
For fish, this looks like:
$ fish_key_reader
Press a key: <page up>
hex: 1B char: \c[ (or \e)
( 0.037 ms) hex: 5B char: [
( 0.018 ms) hex: 35 char: 5
( 0.020 ms) hex: 7E char: ~
bind -k ppage 'do something'
bind \e\[5~ 'do something'
For bash or zsh, this looks like:
$ <ctrl-v><page up>
# transitions to
$ ^[[5~
Both of these tell us that the escape sequence is CSI 5 ~
, so that’s the sequence we want to tell iTerm to send. This looks like ESC+[5~
in its UI.
iTerm2 also has Dynamic Profiles, which allows text-based management of its profile settings; this lets me keep changes intentional and preserves the history in git. Adding this same keymap there looks something like this (with other content elided):
{
"Profiles": [
{
"Guid": "22b93c98-1383-440b-8224-d1c3f653a850",
"Name": "Profile Name",
"Keyboard Map": {
"0x20-0x20000-0x31": {
"Version": 1,
"Action": 10,
"Text": "[5~",
"Label": "PageUp for Shift-Space"
}
}
}
]
}
Managing preference plists under Chezmoi
Chezmoi handles my dotfiles, and it allows me to painlessly go to great levels of configuration management across machines. There’s one difficult type of configuration to deal with on Mac, though: binary .plist
files.
Take Soulver, for example, which stores its font settings in UserDefaults. This results in a binary plist in ~/Library/Preferences
which contains information changed on each run of the app. It is a great example of what not to check into source control.
My solution is using a modify
script, which Chezmoi calls with the current version of the file looking for a modified version in response. The script for Soulver is stored in modify_private_app.soulver.mac.plist
:
source "$(chezmoi source-path)/path/to/plist.sh"
pl SV_CUSTOM_FONT_NAME -string CUSTOM_FONT
pl SV_CUSTOM_FONT_POST_SCRIPT_NAME -string "Input-Regular"
pl SV_FONT_SIZE -integer 16
This is relatively stable, but more prone to breaking than a concretely-defined settings file format, of course. This uses a helper script named plist.sh
which acts as a thin wrapper around plutil
while minimizing the amount of boilerplate:
set -e
TMPFILE=$(mktemp)
trap "cat $TMPFILE; rm $TMPFILE" EXIT
function pl() {
# test before setting because plutil _will_ mutate the file
# macOS 12.+, you can use plutil like:
# CURRENT=$(plutil -extract $1 raw $TMPFILE 2>/dev/null || :)
CURRENT=$(/usr/libexec/PlistBuddy -c "Print :$1" $TMPFILE 2>/dev/null || :)
if [ "$CURRENT" != "$3" ]; then
plutil -replace $* "$TMPFILE"
fi
}
cat <&0 >$TMPFILE
if [ ! -s $TMPFILE ]; then
# plutil will error if it encounters an empty file
# macOS 12.+ you can use plutil like:
# plutil -create binary1 $TMPFILE
echo "{}" | plutil -convert binary1 -o $TMPFILE -
fi
Now my Soulver settings are synced and updated using Chezmoi, and I don’t have to keep track of which apps I need to visit if I decide I want to play with a different font.
I recently looked at every programming font I could find; here are my favorites in order: