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:

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.

← All articles