Getting Started
This guide walks you through installing agh-cli, configuring your AdGuard Home instances, and running your first commands.
Prerequisites
- One or more AdGuard Home instances reachable from your machine
- The admin username and password for those instances, if they require HTTP Basic Auth
- A running credential store session for the operating system credential store: the macOS Keychain, the Linux and BSD Secret Service, or the Windows Credential Manager. Hosts without one, such as most containers and many headless servers, can use a mounted secret file or an environment variable instead
- Go 1.27+ (if installing from source)
Installation
Install script
tmp=$(mktemp)
curl -sSfL https://raw.githubusercontent.com/nicholas-fedor/agh-cli/main/scripts/install.sh -o "$tmp" && sh "$tmp"
rm -f "$tmp"
agh-cli versionThe script downloads the latest release artifacts, verifies their SHA-256 checksums, and installs agh-cli using a native package when one is available or the release archive otherwise. On Linux this prefers a .deb / .rpm / .apk / Arch package when sudo is available, otherwise it installs the release archive into $HOME/go/bin.
| Variable | Default | Description |
|---|---|---|
VERSION |
Latest release | Release tag such as v0.1.0 or 0.1.0 |
INSTALL_DIR |
$HOME/go/bin or the previous directory |
Destination for archive installations |
INSTALL_TYPE |
auto |
Installation strategy: auto, package, archive |
Windows users should download the .zip asset from the latest GitHub Release.
From Source
go install github.com/nicholas-fedor/agh-cli@latestPre-built Binary
Download the latest archive or distro package from the GitHub releases page.
Docker
docker pull ghcr.io/nicholas-fedor/agh-cli:latest
# or
docker pull nickfedor/agh-cli:latestRun it against a mounted configuration file:
docker run --rm \
-v "$(pwd)/config.yaml:/config.yaml:ro" \
ghcr.io/nicholas-fedor/agh-cli:latest \
--config /config.yaml rewrite list --allA container normally has no credential store session, so configure a file source and mount the secret read-only. See Headless Hosts and External Sources.
Quick Start
By default, agh-cli looks for ./config.yaml first and then ~/.config/agh-cli/config.yaml. Add an instance without a password, then store the secret in the operating system credential store:
agh-cli instance add default adguard.example.com --username admin
agh-cli instance credentials set defaultPassword:
Store this credential for instance "default"? [y/N]: y
Stored credential for instance "default" in keyring service "agh-cli" with key "default".The hidden prompt keeps the password out of your shell history and out of any process listing. The command writes the secret to the credential store first and then rewrites config.yaml to reference it, so the file ends up without a plaintext password:
credentials:
service: agh-cli
instances:
default:
host: adguard.example.com
scheme: https
username: admin
credential:
source: keyring
key: defaultContinue with the configuration details below for additional instances and field descriptions, and with Credentials for the full credential workflow.
Configuration
agh-cli reads a YAML configuration file to discover AdGuard Home instances.
| Flag | Default | Description |
|---|---|---|
-c, --config |
config.yaml |
Path to configuration file |
$HOME/.config/agh-cli/config.yaml |
Fallback if local file is missing |
Config File
credentials:
service: agh-cli
instances:
default:
host: adguard.example.com
scheme: https
username: admin
credential:
source: keyring
key: default
office:
host: adguard.office.example.com
scheme: http
username: admin
password: office-password| Field | Required | Default | Description |
|---|---|---|---|
host |
Yes | — | AdGuard Home host or IP |
scheme |
No | https |
http or https |
username |
No | — | Admin username for HTTP Basic Auth |
password |
No | — | Legacy plaintext password for HTTP Basic Auth |
credential |
No | — | Credential reference; when omitted the password is used |
Manage the instance definitions themselves with the instance command:
agh-cli instance list --all
agh-cli instance add default adguard.example.com
agh-cli instance remove backupagh-cli instance add --password is deprecated. The flag still works so existing scripts keep running, but an argument value is visible in process listings and shell history, so agh-cli prints a deprecation notice on standard error. Add the instance without a password and run agh-cli instance credentials set <name> instead.
Credential Sources
The credential block selects exactly one source per instance. A configured source is the only source that is read: a failed lookup is an error rather than a silent fallback to another source, to the plaintext password, or to a different key.
source |
Required field | Reads the password from | agh-cli owns the secret |
|---|---|---|---|
keyring |
key |
The OS credential store, under service credentials.service |
Yes |
file |
path |
A read-only mounted secret file | No, another system does |
env |
env |
An environment variable | No, the environment does |
plaintext |
password |
The password field of the same instance |
Yes, but it stays in the file |
none |
— | Nothing; the instance sends no authentication | Not applicable |
Each source accepts only the field it consumes. A keyring stanza that also sets path, or a file stanza without an absolute path, is rejected when the configuration loads rather than partially applied. An unset env variable is an error, never a fallback.
credentials.service is the credential store namespace shared by every instance of the same operating system user. It defaults to agh-cli, and --all and the per-instance credential commands never touch any other service, so credentials belonging to other applications are never affected.
Instance Selection
Most commands accept the following flags to choose which instances to operate against:
| Flag | Description |
|---|---|
-a, --all |
Target all configured instances |
-i, --instance <names> |
Target specific named instances (repeatable) |
Selection precedence:
--all--instancenamesdefaultinstance (if present)- single-instance resolution (error if ambiguous)
Credentials
agh-cli instance credentials manages instance passwords in the operating system credential store.
| Command | Description |
|---|---|
agh-cli instance credentials set <name> |
Store a secret and point the instance at it |
agh-cli instance credentials status [name] |
Report source, target, and presence, never the secret |
agh-cli instance credentials clear <name> |
Remove a stored credential and its configuration reference |
agh-cli instance credentials clear --all |
Remove every credential of the configured service |
agh-cli instance credentials migrate |
Move legacy plaintext passwords into the credential store |
| Flag | Commands | Description |
|---|---|---|
--key |
set |
Credential key; defaults to the instance name |
-y, --yes |
set, clear |
Skip the confirmation, for non-interactive use |
--all |
clear |
Remove every credential of the configured service |
--json |
status |
Render the report as JSON |
--dry-run |
migrate |
Report the change without writing to the store or the config file |
There is deliberately no get command. No command prints a stored secret, so status is safe to run in a shared terminal or a captured log. When a request needs the password, agh-cli reads it from the store and sends it.
Storing a Credential
Add the instance without a password, then store the secret:
agh-cli instance add default adguard.example.com --username admin
agh-cli instance credentials set defaultset reads the password from a hidden prompt. When standard input is redirected, such as in a script, it reads the secret from standard input instead and echoes nothing:
printf '%s' "$AGH_ADMIN_PASSWORD" | agh-cli instance credentials set default --yesThe secret is read before the confirmation, so declining the prompt stores nothing and the value is dropped immediately. --yes pre-accepts the confirmation, which is the only way a redirected workflow can proceed, because a prompt needs a terminal. One trailing newline is stripped from a redirected secret, so a password that genuinely ends in a newline round-trips through a double redirect. An empty secret is refused before the store is touched.
The credential store write happens first and config.yaml is rewritten only afterwards, so a failed write leaves the instance exactly as it was. Pass --key to store the secret under a key other than the instance name.
Inspecting Credentials
# Every configured instance
agh-cli instance credentials status
# One instance, as JSON
agh-cli instance credentials status default --jsonbackend: keyring
service: agh-cli
available: true
instance "default": source keyring, key "default", present
instance "office": source file, path "/run/secrets/agh-cli/office", unknown
instance "backup": source plaintext, presentPresence is present, absent, or unknown. A file or env source reports unknown, because agh-cli does not read a secret owned by another system just to answer a status question, and available: false means a credential store read failed.
Rotating a Credential
Rotation is a second set for the same instance. The store is read first, so a write refuses to guess whether it would replace an existing credential, and the report names the outcome:
agh-cli instance credentials set defaultPassword:
Replaced credential for instance "default" in keyring service "agh-cli" with key "default".If the configuration file could not be rewritten, agh-cli warns that the plaintext password is still on disk and that the command is retryable. Run it again and the second attempt reports Replaced and saves successfully.
Removing a Credential
agh-cli instance credentials clear default
agh-cli instance credentials clear --all --yesclear deletes the store entry first and removes the configuration reference afterwards, so the file never points at a secret that still exists. Clearing an absent credential is not an error, which makes a repeated clear safe. --all deletes everything under the configured service and deliberately leaves the configuration alone, so a surviving reference becomes a visible error instead of a silent change of source; detach each instance with its own clear.
A file or env instance is refused, because agh-cli owns neither that secret nor the decision to stop using it. Change those instances by editing the configuration.
Migrating Legacy Plaintext Passwords
A configuration that stores passwords in the file keeps working unchanged. Preview the move first:
agh-cli instance credentials migrate --dry-runwould migrate "legacy" to keyring key "legacy"
would migrate "backup" to keyring key "backup"
backend: keyring
service: agh-cli
nothing was written: 2 credential(s) would be migratedThen apply it:
agh-cli instance credentials migrateMigration is explicit, because a normal read never migrates implicitly. It selects every instance that has no credential block and a non-empty password, and skips any instance that already declares a source, so a configured source is never changed silently. For each instance the password reaches the credential store before it is removed from the configuration, so a failed write leaves that instance unchanged and its plaintext password keeps working. The file is written once at the end, and no password is ever printed.
Until you migrate, a legacy plaintext password sits in config.yaml in the clear. Keep the file readable only by its owner:
chmod 600 config.yamlagh-cli rewrites the file with mode 600 when it saves, so a migrated configuration is never left group- or world-readable.
Credential Service and Key Schema
| Setting | Default | Description |
|---|---|---|
credentials.service |
agh-cli |
Credential store namespace shared by every instance of the same OS user |
credential.key |
instance name | Credential identity within that service |
The key resolves in one order: the --key value passed to set, then the credential.key already configured for the instance, then the instance name. The key never embeds a host, so changing a host never requires re-entering a password.
The service is global to the operating system user rather than per configuration path, so a credential written through ./config.yaml is visible to a later run that resolves ~/.config/agh-cli/config.yaml.
Headless Hosts and External Sources
A headless host, an SSH session, or a container usually has no usable credential store session, and a Linux host without a running Secret Service session reports the store as unavailable rather than falling back to another store. Point the instance at the system that already holds the secret instead:
instances:
dockerized:
host: adguard.internal
username: admin
credential:
source: file
path: /run/secrets/agh-cli/admin
headless:
host: adguard.lan
username: admin
credential:
source: env
env: AGH_ADMIN_PASSWORDfile reads a mounted secret, which is how Docker, Kubernetes, systemd, and Vault hand over a password. The path must be absolute, must point at a regular file, and must be 64 KiB or smaller. agh-cli opens the file read-only, never writes to it, never changes its permissions, and never trims its contents, so a trailing newline written by the external system stays part of the password. Docker and Kubernetes normally mount secrets below /run/secrets, so the mount and the container user are part of the trust boundary.
docker run --rm \
-v "$(pwd)/config.yaml:/config.yaml:ro" \
--mount type=bind,src="$(pwd)/admin-password",dst=/run/secrets/agh-cli/admin,readonly \
ghcr.io/nicholas-fedor/agh-cli:latest \
--config /config.yaml filtering status --instance dockerizedset and clear also need a terminal for the hidden prompt and the confirmation. In a non-interactive workflow, redirect the secret into set and pass --yes; on a host with no credential store at all, configure file or env instead of keyring.
Basic Usage
Managing Clients
# List clients across all instances
agh-cli client list --all
# Add a client to a specific instance
agh-cli client add laptop \
--id AA-BB-CC-DD-EE-FF \
--instance default
# Update and delete clients
agh-cli client update laptop --name my-laptop --instance default
agh-cli client delete laptop --instance defaultManaging Filtering
# Show filtering status per instance
agh-cli filtering status --all
# Set filtering parameters
agh-cli filtering config --all
# Add and remove filter URLs
agh-cli filtering add-url adguard-default "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt" --all
agh-cli filtering remove-url "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt" --allManaging Rewrite Rules
# List rewrite rules
agh-cli rewrite list --all
# Add a rewrite rule
agh-cli rewrite add --domain ads.example.com --answer 192.0.2.10 --instance default
# Update and delete rules
agh-cli rewrite update --domain ads.example.com --answer 192.0.2.20 --instance default
agh-cli rewrite delete --domain ads.example.com --instance default
# Diff rules between two configured instances
agh-cli rewrite diff --instance default,backup
# Read and update rewrite settings
agh-cli rewrite settings get --instance default
agh-cli rewrite settings update --instance defaultWildcard Rewrite Rules
agh-cli rewrite wildcard add --domain "*.example.com" --answer 192.0.2.10 --instance default
agh-cli rewrite wildcard delete --domain "*.example.com" --instance defaultVersion
agh-cli version
agh-cli version --verbose
agh-cli version --jsonUpdating
Update using the installation script:
tmp=$(mktemp)
curl -sSfL https://raw.githubusercontent.com/nicholas-fedor/agh-cli/main/scripts/install.sh -o "$tmp" && sh "$tmp" update
rm -f "$tmp"Uninstalling
Uninstall using the installation script:
tmp=$(mktemp)
curl -sSfL https://raw.githubusercontent.com/nicholas-fedor/agh-cli/main/scripts/install.sh -o "$tmp" && sh "$tmp" uninstall
rm -f "$tmp"Go Library
The repository publishes a reusable Go client at pkg/adguard that covers the complete AdGuard Home HTTP API independently of the CLI command layer.
package main
import (
"context"
"log"
"github.com/nicholas-fedor/agh-cli/pkg/adguard"
)
func main() {
client, err := adguard.NewClient(
"https://adguard.example.com",
adguard.WithBasicAuth("admin", "password"),
)
if err != nil {
log.Fatal(err)
}
status, err := client.Status(context.Background())
if err != nil {
log.Fatal(err)
}
log.Printf("AdGuard Home %s is running", status.Version)
}Next Steps
See the full CLI reference for all commands, flags, and advanced usage.