diff --git a/Makefile b/Makefile
index 7766699af76f4f9a8db3e6e2077eade2598558e7..66f1bd68dd0c3657c6e4bdf523c997d7a2c3ac3a 100644
--- a/Makefile
+++ b/Makefile
@@ -7,11 +7,12 @@ sharedir = $(prefix)/share/firewall
 INSTALL = install
 TABLES = filter nat mangle raw
 
-all:
+all: update-firewall
 
 clean:
+	-rm -f update-firewall
 
-install:
+install: all
 	$(INSTALL) -d $(DESTDIR)$(sbindir)
 	$(INSTALL) -d $(DESTDIR)$(sharedir)
 	$(INSTALL) -d $(DESTDIR)$(fwconfdir)
@@ -34,3 +35,6 @@ install:
 	    $(INSTALL) -d $(DESTDIR)$(fwconfdir)/blocked/$$type/$$proto ; \
 	  done ; \
 	done)
+
+%: %.in
+	sed -e s,@PREFIX@,$(prefix),g -e s,@FWCONFDIR@,$(fwconfdir),g $< > $@
diff --git a/README.md b/README.md
index 8ae1bd54216e4fbf62c8fd31636ed96a72918ddf..2668ebe52e3b3f929eb203a86eea1c8b279dd0bf 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,11 @@ configured independently from its own subdirectory named after the
 table, with a `.d` extension. Individual files from each directory are
 loaded in lexicographical order using *run-parts(8)*.
 
+The default configuration of this package delegates large-scale IP
+blocking to *ipset*, using configuration files under
+/etc/firewall/blocked, supporting single IPs and netblocks for both
+IPv4 and IPv6.
+
 Once the new rules have been loaded, the firewall script will invoke
 all executable scripts from the */etc/firewall/reload-hooks* directory
 (again using *run-parts*). Use this to reload daemons that maintain
@@ -67,11 +72,45 @@ iptables options (the default is simply `-j ALLOW`).
 Allow incoming traffic to the specified ports. *PORT_SPEC*
 should be a comma-separated list of destination ports.
 
+## Default configuration
+
+The default configuration shipped with this Debian package (found in
+the *conf-dist* directory in this repository) implements some simple
+functionality to build upon:
+
+* allow incoming SSH connections on port 22
+* support for all essential ICMP types
+* support DHCP and DHCPv6
+* large-scale efficient blocking of IPs and netblocks via *ipset*
+* integration with fail2ban via dedicated ipsets
+* preserves Docker-owned iptables rules
+
+A few chains are defined, of which the most interesting one is
+*user-input*, where you're supposed to add rules controlling incoming
+traffic to the host.
+
+## IP blocking
+
+By putting IPs and network ranges (in CIDR syntax) in files below
+/etc/firewall/blocked it is possible to block (drop) incoming traffic
+from them.
+
+The following files are supported, one IP or network range per line:
+
+* /etc/firewall/blocked/ip/ipv4 - IPv4 addresses
+* /etc/firewall/blocked/ip/ipv6 - IPv6 addresses
+* /etc/firewall/blocked/net/ipv4 - IPv4 network ranges
+* /etc/firewall/blocked/net/ipv6 - IPv6 network ranges
+
 # Usage
 
 Run *update-firewall* to set up iptables whenever the rules below
 /etc/firewall change.
 
+It is possible to run just *update-ipset* if one only desires to
+quickly update the ipset lists. Invoking *update-firewall* will also
+call *update-ipset* so it is not necessary to call them both.
+
 # Notes
 
 The firewall script will always attempt to setup IPv6 rules, even if
diff --git a/conf-dist/filter.d/00base b/conf-dist/filter.d/00base
index 6c3a4b9c1e7c47499b237d1db178750d4b9eb8fa..bc59422548d0a04bde2e5fe0d51b541dfea268c5 100644
--- a/conf-dist/filter.d/00base
+++ b/conf-dist/filter.d/00base
@@ -3,7 +3,7 @@
 # This snippet should run before the others.
 
 # Set up a chain that will drop noisy unwanted traffic
-# without even logging it. 
+# without even logging it.
 create_chain drop-noise
 add_rule -A drop-noise -p tcp --dport 113 -j REJECT
 add_rule -A drop-noise -p tcp -m multiport --dports 139,445 -j DROP
@@ -19,7 +19,7 @@ add_rule -A base-input -i lo -j ACCEPT
 
 # Some IPv6-specific ICMP setup.
 add_rule6 -A base-input -m rt --rt-type 0 --rt-segsleft 0 -j DROP 
-for icmp6type in 133 134 135 136 ; do 
+for icmp6type in 133 134 135 136 ; do
     add_rule6 -A base-input -p ipv6-icmp -m icmp6 \
               --icmpv6-type ${icmp6type} -m hl --hl-eq 255 -j ACCEPT 
 done
diff --git a/debian/changelog b/debian/changelog
index 349465c707e3b3db1d42ffde1f372af4daff3b41..c17f2ff15aa5f1273261fcfa4708236d96c37679 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+firewall (0.3) unstable; urgency=medium
+
+  * Unify update-firewall and update-ipset
+
+ -- Autistici/Inventati <debian@autistici.org>  Sat, 26 Aug 2023 08:14:40 +0100
+
 firewall (0.2) unstable; urgency=medium
 
   * New package.
diff --git a/debian/firewall.service b/debian/firewall.service
index 7206e89356d304ce943cf667d35fa2a6e1cc0642..54a6478d7284506130a1568fca69e49a50956c3f 100644
--- a/debian/firewall.service
+++ b/debian/firewall.service
@@ -4,7 +4,6 @@ Description=Set up firewall
 [Service]
 Type=oneshot
 EnvironmentFile=-/etc/default/firewall
-ExecStart=/usr/sbin/update-ipset
 ExecStart=/usr/sbin/update-firewall
 
 [Install]
diff --git a/update-firewall b/update-firewall.in
similarity index 91%
rename from update-firewall
rename to update-firewall.in
index 316fbf528912eea3a44f087bdca63f83c31995e9..6fcd3ab8e5e91a73eb4bf0568d4effaa98ab9c04 100755
--- a/update-firewall
+++ b/update-firewall.in
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 # Directory containing the configuration snippets.
-CONFIG_DIR="${CONFIG_DIR:-/etc/firewall}"
+CONFIG_DIR="${CONFIG_DIR:-@FWCONFDIR@}"
 
 # List of tables to manage.
 TABLES="filter nat mangle raw"
@@ -18,7 +18,7 @@ resolve_addr() {
         ipv4) af=AF_INET ;;
         ipv6) af=AF_INET6 ;;
     esac
-    python -c "import socket ; print '\n'.join(x[4][0] for x in socket.getaddrinfo('${addr}', 0, socket.${af}, socket.SOCK_STREAM))" 2>/dev/null || true
+    python3 -c "import socket ; print '\n'.join(x[4][0] for x in socket.getaddrinfo('${addr}', 0, socket.${af}, socket.SOCK_STREAM))" 2>/dev/null || true
 }
 
 resolve_addr4() {
@@ -226,7 +226,7 @@ while [ $# -gt 0 ]; do
             exit 0
             ;;
         --version)
-            echo "firewall v0.2"
+            echo "firewall v0.3"
             exit 0
             ;;
         *)
@@ -238,6 +238,16 @@ while [ $# -gt 0 ]; do
     shift
 done
 
+# We need to run update-ipset first, so that we can reference the sets
+# from iptables "-m set" rules.
+if [ -x @PREFIX@/sbin/update-ipset ]; then
+    @PREFIX@/sbin/update-ipset
+    if [ $? -gt 0 ]; then
+        echo "update-ipset failed, aborting..." >&2
+        exit 1
+    fi
+fi
+
 load_firewall
 
 exit 0