knocker-cli is a static Go CLI service that automatically requests a whitelist for the external IP of the device on IP address changes or when the whitelist expires. It runs in the background to ensure you always have access.
- Automatic IP Whitelisting: Automatically detects IP changes and requests a new whitelist.
- Background Service: Runs as a background service on Linux, macOS, and Windows.
- Cross-Platform: Built to be cross-platform with priority for Linux and macOS.
- Docker Support: Can be run in a Docker container.
- Manual Whitelisting: Manually trigger a whitelist request at any time.
- Structured journald events: Emits machine-readable journald events alongside human logs for desktop integrations.
knocker-cli operates in two distinct modes for handling IP changes:
When the service starts it immediately performs a scheduled knock before arming its cadence timer, so a restart refreshes the whitelist without waiting for the next interval.
This is the default and recommended mode of operation.
- The
knocker-cliservice starts and sends a "knock" request based on the best-known TTL. It begins with the configured TTL and adjusts to the TTL returned by the API response, aiming to refresh the whitelist when roughly 90% of the TTL has elapsed (falling back to a 5-minute cadence if the TTL is unknown or unset). - The service does not check its own IP address.
- Your API server receives the "knock" request, inspects the source IP address of the request, and updates its whitelist accordingly.
In this mode, knocker-cli acts as a simple, periodic "pinger" to keep your whitelist entry fresh.
This mode is for more advanced use cases where you want the client to be responsible for detecting IP changes.
- To enable this mode, you must provide an
ip_check_urlin your configuration. This URL should point to a service that returns the client's public IP in plain text (e.g.,https://ifconfig.me). - The
knocker-cliservice starts and fetches its public IP from theip_check_url. It stores this IP in memory. - At each
check_interval, it fetches the IP again. - It compares the new IP with the one stored in memory.
- If the IP address has changed, and only if it has changed, the service will send a "knock" request to your API server to whitelist the new address.
At startup the service logs the calculated cadence along with its source (source: ttl or source: check_interval) so you can confirm which mechanism is driving the schedule.
To install from source, you will need to have Go installed.
git clone https://github.com/FarisZR/knocker-cli.git
cd knocker-cli
go install ./...You can also run Knocker using Docker.
docker build -t knocker-cli .
docker run -d --name knocker-cli -e KNOCKER_API_URL=... -e KNOCKER_API_KEY=... knocker-cliKnocker can be configured via a configuration file or environment variables.
Create a file named .knocker.yaml in your home directory with the following content:
api_url: "http://your-knocker-api-url"
api_key: "your-api-key"
check_interval: 5 # The interval in minutes to poll for IP changes when ip_check_url is set.
ip_check_url: "" # optional, e.g. "https://ifconfig.me"
ttl: 0 # optional, time to live in seconds for the knock request (0 for server default)You can also configure knocker-cli using environment variables:
KNOCKER_API_URL: The URL of the Knocker API.KNOCKER_API_KEY: Your API key.KNOCKER_CHECK_INTERVAL: The interval in minutes to poll for IP changes whenip_check_urlis set.KNOCKER_IP_CHECK_URL: Optional URL of the external IP checker service.KNOCKER_TTL: Optional time to live in seconds for the knock request (0 for server default).
When running as the packaged systemd user service, these variables can be placed in ~/.config/knocker/env using the standard KEY=value format.
When ip_check_url is unset, the service automatically schedules knocks so that roughly 10% of the TTL remains before expiry. It starts with the configured TTL but updates the cadence whenever the API returns a TTL, falling back to a 5-minute cadence only when no TTL is known. When ip_check_url is provided, the check_interval controls how frequently the client polls for IP changes and only knocks when the IP actually changes.
When the service runs under systemd (for example as knocker.service), every operational log is mirrored to journald with a human-friendly MESSAGE and a stable set of KNOCKER_* fields. These events let desktop integrations such as GNOME shell extensions consume Knocker state without polling a separate API.
- Stream the events:
journalctl --user -u knocker.service -o json -f | jq 'select(.KNOCKER_EVENT != null)'to follow only structured entries, or pin to a specific type withKNOCKER_EVENT=StatusSnapshotas needed (journalctlonly supportsFIELD=valuecomparisons per its manual). - Schema version: All entries include
KNOCKER_SCHEMA_VERSION=1for forward compatibility. - Event types:
ServiceState— lifecycle notifications (started,stopping,stopped) with optionalKNOCKER_VERSION.StatusSnapshot— current whitelist, TTL, and next scheduled knock.WhitelistApplied/WhitelistExpired— whitelist changes with expiry metadata.NextKnockUpdated— upcoming knock timestamp (or0when cleared).KnockTriggered— manual (cli) and scheduled (schedule) knocks with success/failure result.Error— surfaced issues that should be shown in the UI.
Manual invocations of knocker knock produce the same KnockTriggered and WhitelistApplied events so external consumers stay in sync even when the background service is idle.
knocker runEven if the background service is running, you can manually trigger a whitelist request at any time.
knocker knockknocker installNote: On Linux the installer registers a per-user systemd unit at
~/.config/systemd/user/knocker.service. Start it immediately withsystemctl --user enable --now knocker. On macOS the installer writes~/Library/LaunchAgents/knocker.plist; load it withlaunchctl bootstrap gui/$UID ~/Library/LaunchAgents/knocker.plist.
knocker startknocker stopknocker uninstallknocker statusTo build the binary for your current platform, run:
go build -o knocker ./cmd/knockerTo create cross-platform builds, archives, and releases, you can use GoReleaser.
# This will create builds for all platforms defined in .goreleaser.yml
goreleaser release --snapshot --clean