Core Feature: Permanent Toggles
Relevant source files
The following files were used as context for generating this wiki page:The "Permanent Toggles" feature provides a global, persistent kill switch for DNS-based ad-blocking across all configured Pi-hole instances. It is designed to be controlled via a simple HomeKit switch, allowing non-technical users to easily enable or disable all DNS filtering on the network with a single touch. The "permanent" nature refers to the state persisting until it is explicitly changed again, unlike temporary toggles which may have a timer.
This feature is exposed through a HomeKit switch, which communicates with the backend system via MQTT messages. These messages are processed by Node-RED, which in turn calls a dedicated set of API endpoints on the FastAPI server. The server then orchestrates the state change across all connected Pi-hole devices using the pihole6api library.
Sources: README.md:5-11, README.md:65-73, node_red_flows/dns.json:113-125, lib/pihole/alldns.py
Architecture and Data Flow
The end-to-end flow for a permanent toggle action begins with a user interaction in the HomeKit app and results in a state change on the Pi-hole servers. The system uses MQTT as a message broker and Node-RED for orchestration between the user interface and the backend API.
The following sequence diagram illustrates the interactions between the components when a user toggles the global DNS switch.
This diagram shows the command and status update flow for the permanent toggle feature.
Sources: README.md:104-129, node_red_flows/dns.json, lib/pihole/pihole_router.py:65-79, lib/pihole/alldns.py:69-82
Components
HomeKit Integration
The feature is exposed to users via a HomeKit switch configured using the homebridge-mqttthing plugin. This switch subscribes to a status topic and publishes commands to a separate topic.
A sample configuration for a permanent toggle switch named "ADs On(♾️)" is provided below. Note the use of perm_change in the setOn topic, which distinguishes it as a permanent toggle.
{
"type": "switch",
"name": "ADs On(♾️)",
"topics": {
"getOnline": "stat/dns_controller/master/service_status",
"getOn": "stat/dns_controller/master/status",
"setOn": "cmnd/dns_controller/master/perm_change"
},
"accessory": "mqttthing"
}
Sources: README.md:65-73
MQTT Topics
The communication between Homebridge, Node-RED, and the backend relies on a set of defined MQTT topics for master control.
| Topic | Direction | Purpose |
|---|---|---|
cmnd/dns_controller/master/perm_change | Homebridge -> Node-RED | Command to change the permanent blocking state. Payload is true (disable blocking) or false (enable blocking). |
stat/dns_controller/master/status | Node-RED -> Homebridge | Reports the current global blocking status. Payload is true or false. |
stat/dns_controller/master/service_status | FastAPI -> Homebridge | Reports the online status of the backend service (via MQTT LWT). |
Sources: README.md:65-73, node_red_flows/dns.json:113-118, 161-166, cgi-bin/controller.py:169-181
Node-RED Orchestration
Node-RED acts as the middleware, listening for MQTT commands and translating them into API calls. The flow for "Master Control" is defined in dns.json.
The following diagram illustrates the logic within the Node-RED flow for handling permanent toggle commands.
This flow shows how an incoming MQTT message is processed to make the correct API call and then publish the resulting status back.
Sources: node_red_flows/dns.json:113-125, 138-160
Backend API: Global DNS Control
The FastAPI server exposes a set of endpoints under the /alldns prefix to manage the global DNS blocking state. These endpoints are defined in pihole_router.py and handled by the MasterEnabler class.
| Method | Endpoint | Description | Handler Function |
|---|---|---|---|
GET | /alldns/ | Retrieves the current global DNS blocking status across all Pi-holes. | all_dns.get() |
POST | /alldns/ | Disables DNS blocking on all Pi-holes. Accepts an optional timer query parameter. | all_dns.disable_dns_blocking(timer) |
DELETE | /alldns/ | Enables DNS blocking on all Pi-holes. | all_dns.enable_dns_blocking() |
Sources: lib/pihole/pihole_router.py:65-79, AGENTS.md:43-45
Core Logic: MasterEnabler
The MasterEnabler class, located in lib/pihole/alldns.py, contains the business logic for controlling the global DNS state. It inherits from BaseHTTPHandler to manage sessions with multiple Pi-hole instances.
Key Methods
__init__(self, app_config): Initializes the handler, setting up connections to all Pi-hole instances defined in the configuration.flip_mode(self, status: bool): The core method that iterates through all connected Pi-hole sessions and callsset_blocking_status()on each one via thepihole6apiclient. It also checks for inconsistent states among the devices.disable_dns_blocking(self, timer=None): A convenience method that callsflip_mode(status=False)to disable blocking.enable_dns_blocking(self, timer=30): A convenience method that callsflip_mode(status=True)to enable blocking.get(self): Retrieves the blocking status from all Pi-hole instances and returns a consolidated status. It will report "unknown" if the states are inconsistent.
The class ensures that an action is performed on every Pi-hole listed in the remote_pi_list configuration.
Sources: lib/pihole/alldns.py:22-120, lib/pihole/base.py:29-51
Configuration
To use the Permanent Toggles feature, the following configurations must be set in config.ini and environment variables. The application reads these settings on startup.
| File | Section | Key | Description |
|---|---|---|---|
config.ini | [general] | pihole_control_enabled | Must be true to enable any Pi-hole functionality. |
config.ini | [general] | remote_pi_list | A space-separated list of Pi-hole IP addresses or hostnames. |
config.ini | [general] | remote_pi_password | The API password for the Pi-hole instances. |
These values are loaded by the init_config function in cgi-bin/controller.py.
Sources: cgi-bin/controller.py:79-103
Summary
The Permanent Toggles feature is a powerful yet simple mechanism for global network ad-blocking control. By integrating with HomeKit through homebridge-mqttthing, it abstracts the complexity of managing multiple Pi-hole instances behind a single, user-friendly switch. The architecture, leveraging MQTT for messaging, Node-RED for orchestration, and a FastAPI backend for the core logic, creates a responsive and robust system for home network management.
Sources: README.md:5-11, AGENTS.md:7-10