cmd / hostctl
hostctl manages /etc/hosts for local development and DNS-level ad blocking.
Run it with:
hostctl
Local apps
Browsers treat http://localhost and http://*.localhost as
potentially trustworthy origins,
so Secure Context features work without TLS.
I run apps on different subdomains and ports
(blog.localhost:2000, htmz.localhost:2002) to test
cross-origin security:
SameSite=StrictcookiesSec-Fetch-Site: same-originandOriginheadersReferrer-Policy: strict-origin-when-cross-originheader
Ad blocking
A browser extension blocks ads in one browser. A hosts file blocks them in every app on the laptop. A DNS sinkhole such as Pi-hole covers every device on the network, but needs an always-on device at home. The hosts file works when I take the laptop away.
hostctl blocks ads and trackers by default:
hostctl
To disable blocking and keep the local app entries:
hostctl --unblock
Script:
#!/bin/bash
set -euo pipefail
custom_hosts_entries() {
cat <<EOF
# macOS defaults
255.255.255.255 broadcasthost
# local apps
127.0.0.1 blog.localhost # :2000
127.0.0.1 htmz.localhost # :2002
127.0.0.1 neogit.localhost # :2003
127.0.0.1 thefieldhouse.localhost # :2004
127.0.0.1 fixthecfp.localhost # :2005
127.0.0.1 duckduckbay.localhost # :2006
127.0.0.1 app.localhost # :3000
EOF
}
if [[ "${1:-}" == "--unblock" ]]; then
cat <<EOF | sudo tee /etc/hosts >/dev/null
# IPv4
127.0.0.1 localhost
# IPv6
::1 localhost
$(custom_hosts_entries)
EOF
else
# Block ads, trackers, and malicious websites at the DNS host level.
curl -s https://winhelp2002.mvps.org/hosts.txt | tr -d '\r' | sudo tee /etc/hosts >/dev/null
# Append custom entries
(echo && custom_hosts_entries) | sudo tee -a /etc/hosts >/dev/null
fi
# Flush DNS cache
sudo killall -HUP mDNSResponder
Server configuration
A Go server binds to a port. The hosts file resolves the subdomain:
fmt.Println("Serving at http://blog.localhost:2000")
http.ListenAndServe(":2000", handler)
Puma can bind to the hostname:
if env == "development"
c.bind "tcp://app.localhost:3000"
else
c.bind "tcp://0.0.0.0:#{ENV.fetch("PORT")}"
end
I use the 2000 port range for Go servers
and the 3000 port range for Ruby servers.