agh-cli

command module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: AGPL-3.0 Imports: 5 Imported by: 0

README

agh-cli

agh-cli Logo

CLI for managing multiple AdGuard Home instances

Latest Version GoDoc GitHub go.mod Go version GitHub License


Table of Contents


Features

  • Public Go library — pkg/adguard exposes the complete AdGuard Home API with context-aware calls, typed errors, bounded responses, and feature-scoped interfaces
  • Multi-instance management — target any subset of configured AdGuard Home servers in one command
  • Client management — list, add, update, and delete DHCP/DNS clients across instances
  • Filtering management — read and modify filtering status, enabled/disabled services, and block/allow filter URLs
  • DNS rewrite rules — full CRUD plus diff, wildcard rules, and rewrite settings
  • OS credential store — instance passwords live in the macOS Keychain, the Linux and BSD Secret Service, or the Windows Credential Manager, behind agh-cli instance credentials
  • YAML configuration — single human-readable config file for all instance credentials and targets
  • Structured terminal output — formatted tables for list operations
  • Multi-arch binaries — built for linux, windows, and darwin across amd64, arm64, riscv64, and more
  • Cosign & SBOM — release artifacts are signed and ship with SPDX SBOMs via GoReleaser

Installation

Install Script

The install 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.

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 version

Update or uninstall an existing archive installation:

tmp=$(mktemp)
curl -sSfL https://raw.githubusercontent.com/nicholas-fedor/agh-cli/main/scripts/install.sh -o "$tmp"
sh "$tmp" update
# or
sh "$tmp" uninstall
rm -f "$tmp"
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.

Docker
docker pull ghcr.io/nicholas-fedor/agh-cli:latest
# or
docker pull nickfedor/agh-cli:latest
Go Install
go install github.com/nicholas-fedor/agh-cli@latest

Quick Start

By default, agh-cli looks for ./config.yaml first and then ~/.config/agh-cli/config.yaml. Add an instance without a password and store the secret in the operating system credential store:

agh-cli instance add default adguard.example.com --username admin
agh-cli instance credentials set default

credentials set reads the password from a hidden prompt, so it never appears in your shell history or in a process listing, then rewrites config.yaml to reference the credential store instead of a plaintext password. See Credentials for the complete workflow.

Compare DNS rewrite rules between AdGuard Home instances:

agh-cli rewrite diff --all

Or manage a specific instance:

agh-cli filtering status --instance default

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

credentials.service sets the credential store namespace for every instance and defaults to agh-cli. See example/config.yaml for a commented template covering every source.

Credential Sources

The credential block selects exactly one source per instance. A configured source is the only source that is read, so 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, such as /run/secrets/… 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 at load time rather than partially applied. A file source must point at an absolute path to a regular file of 64 KiB or less; agh-cli opens it read-only, never writes to it, and never trims its contents, so a trailing newline written by the external system stays part of the password. An unset env variable is an error, never a fallback.

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:

  1. --all
  2. --instance names
  3. default instance (if present)
  4. single-instance resolution (error if ambiguous)

Credentials

agh-cli instance credentials stores instance passwords in the operating system credential store: the macOS Keychain, the Linux and BSD Secret Service, or the Windows Credential Manager, depending on the platform. A Linux host without a running Secret Service session reports the store as unavailable rather than falling back to another 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 and presence, never the secret
agh-cli instance credentials clear <name> Remove a stored credential and its reference
agh-cli instance credentials clear --all Remove every credential of the service
agh-cli instance credentials migrate Move legacy plaintext passwords into the store

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 an AdGuard Home request needs the password, agh-cli reads it from the store and sends it; the value never appears in output.

Storing a Credential

Add the instance first, without a password, then store the secret:

agh-cli instance add default adguard.example.com --username admin
agh-cli instance credentials set default
Password:
Store this credential for instance "default"? [y/N]: y
Stored credential for instance "default" in keyring service "agh-cli" with key "default".

set 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 --yes

The secret is read before the confirmation, so declining the prompt stores nothing and the value is dropped immediately. --yes (-y) 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 command writes the credential store entry first and rewrites config.yaml only afterwards, so a failed write leaves the instance exactly as it was. Use --key to store the secret under a key other than the instance name.

agh-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, and agh-cli prints a deprecation notice on standard error when you use it.

Inspecting Credentials
# Every configured instance
agh-cli instance credentials status

# One instance, as JSON
agh-cli instance credentials status default --json
backend: 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, present

Presence 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 keyring 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 default
Password:
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 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 --yes

clear 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. Preview the move first:

agh-cli instance credentials migrate --dry-run
would 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 migrated

Then apply it:

agh-cli instance credentials migrate

Migration 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 is stored in config.yaml in the clear. Keep the file readable only by its owner, and keep the template, example/config.yaml, free of real values:

chmod 600 config.yaml

agh-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 Keyring credential identity within that service

The key is resolved 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.

--all and the per-instance commands only ever touch the configured service, so credentials belonging to other applications are never affected. 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 and External Secret Sources

A headless host, an SSH session, or a container usually has no usable credential store session. Two options keep the secret out of the file:

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_PASSWORD

set 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.


Command Reference

agh-cli [global flags] <command> [subcommand] [flags]
Global Flags
Flag Description
-c, --config string Path to config file (default config.yaml)
-h, --help Help for agh-cli
Commands
instance

Manage the AdGuard Home instance definitions in your config file.

Command Description
agh-cli instance list [--all] List configured instance names
agh-cli instance add <name> <host> Add a new instance
agh-cli instance remove <name> Remove an instance

instance add --password is deprecated. Add the instance without a password and store the secret with instance credentials set instead.

instance credentials

Manage the credentials of the configured instances in the operating system credential store. See Credentials for the full workflow.

Command Description
agh-cli instance credentials set <name> Store a secret from a hidden prompt or standard input
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 no get command; no command prints a stored secret.

client

Manage clients across AdGuard Home instances.

Command Description
agh-cli client list [--all | --instance <name>] List clients
agh-cli client add [--all | --instance <name>] Add a client
agh-cli client delete [--all | --instance <name>] Delete clients
agh-cli client update [--all | --instance <name>] Update clients
filtering

Manage DNS filtering configuration per instance.

Command Description
agh-cli filtering status Show filtering status per instance
agh-cli filtering config Set filtering parameters
agh-cli filtering add-url <name> <url> Add a filter URL
agh-cli filtering remove-url <url> Remove a filter URL
rewrite

Manage DNS rewrite rules and their associated settings.

Command Description
agh-cli rewrite list List rewrite rules
agh-cli rewrite add Add a rewrite rule
agh-cli rewrite delete Delete rewrite rules
agh-cli rewrite update Update a rewrite rule
agh-cli rewrite diff Diff rules between instances or a local file
agh-cli rewrite settings get Return rewrite settings
agh-cli rewrite settings update Update rewrite settings
agh-cli rewrite wildcard add Add wildcard rewrite rules
agh-cli rewrite wildcard delete Delete wildcard rewrite rules
version

Print version and build information.

Command Description
agh-cli version [--verbose | --json] Print version and build information

Examples

# List configured instances
agh-cli instance list --all

# Store the credential of an instance in the OS credential store
agh-cli instance credentials set default

# Report credential presence for every instance
agh-cli instance credentials status

# Show filtering status across all instances
agh-cli filtering status --all

# Add a client to the default instance
agh-cli client add laptop \
  --id AA-BB-CC-DD-EE-FF \
  --instance default

# Compare rewrite rules between two configured instances
agh-cli rewrite diff --instance default,backup

Docker

Using the published image
docker run --rm \
  -v "$(pwd)/config.yaml:/config.yaml:ro" \
  ghcr.io/nicholas-fedor/agh-cli:latest \
  --config /config.yaml rewrite list --all

A container usually has no credential store session, so read the password from a mounted secret instead of the keyring:

instances:
  home:
    host: adguard.example.com
    username: admin
    credential:
      source: file
      path: /run/secrets/agh-cli/admin
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 home

set, clear, and migrate need a credential store session, so run them on a host that has one. Inside a container, the secret belongs to whatever mounted it.

Building locally
make docker-build
docker run --rm agh-cli version

The Docker image is a minimal scratch-based binary with static linking. CA certificates and timezone data are layered via an intermediate Alpine stage.


Building from Source

Prerequisites
  • Go 1.27+
  • make or task (optional)
Build

Using Make:

make build

Using Task:

task build

Binary Output:

bin/agh-cli

Documentation

The full CLI reference is generated from the Cobra command tree and published with the Hugo documentation site.

Regenerate or preview the site locally:

task docs
task docs-serve

task docs-check fails when the committed reference differs from the current command tree.


Go Library

The repository publishes a reusable Go client at pkg/adguard. It covers the complete AdGuard Home HTTP API and is independent 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)
}

Library highlights:

  • Context-first APIs with configurable request timeouts.
  • Basic authentication and secure redirect handling.
  • Bounded JSON and binary response reads.
  • Typed, inspectable errors.
  • Explicit optional-field presence for filters, clients, and rewrite rules.
  • Feature-scoped interfaces for clients, filtering, rewriting, DNS, DHCP, TLS, safety services, and more.
  • Generated Mockery doubles for consumer-level unit tests.
  • Offline fuzz, integration, and HTTP contract test coverage.

Contributing

See CONTRIBUTING.md for development setup, code conventions, testing, documentation, commits, and release expectations.

Automated contributors must also follow AGENTS.md.


License

This project is licensed under the GNU Affero General Public License v3.0. See LICENSE.md for details.

Documentation

Overview

Package main provides the agh-cli entrypoint for managing multiple AdGuard Home instances.

Directories

Path Synopsis
cmd
Package cmd provides the root Cobra command for agh-cli, wiring global flags, Viper config initialization, and top-level subcommands for client, filtering, instance, and rewrite operations.
Package cmd provides the root Cobra command for agh-cli, wiring global flags, Viper config initialization, and top-level subcommands for client, filtering, instance, and rewrite operations.
client
Package client provides Cobra commands for managing AdGuard Home clients, including listing, adding, updating, and deleting client configurations across one or more instances.
Package client provides Cobra commands for managing AdGuard Home clients, including listing, adding, updating, and deleting client configurations across one or more instances.
filtering
Package filtering provides Cobra commands for managing AdGuard Home filtering configuration, filter rules, and filter URLs across one or more instances.
Package filtering provides Cobra commands for managing AdGuard Home filtering configuration, filter rules, and filter URLs across one or more instances.
instance
Package instance provides Cobra commands for managing configured AdGuard Home instances, including listing, adding, and removing instance configurations and storing instance credentials.
Package instance provides Cobra commands for managing configured AdGuard Home instances, including listing, adding, and removing instance configurations and storing instance credentials.
instance/credentials
Package credentials provides the Cobra commands that manage the operating system credential store entries backing AdGuard Home instances.
Package credentials provides the Cobra commands that manage the operating system credential store entries backing AdGuard Home instances.
rewrite
Package rewrite provides Cobra commands for managing DNS rewrite rules on one or more AdGuard Home instances, including CRUD operations, diffing against a file, wildcard management, and rewrite settings.
Package rewrite provides Cobra commands for managing DNS rewrite rules on one or more AdGuard Home instances, including CRUD operations, diffing against a file, wildcard management, and rewrite settings.
rewrite/settings
Package settings provides Cobra commands for viewing and updating AdGuard Home rewrite settings across one or more instances.
Package settings provides Cobra commands for viewing and updating AdGuard Home rewrite settings across one or more instances.
rewrite/wildcard
Package wildcard provides Cobra commands for adding and removing wildcard rewrite entries across one or more AdGuard Home instances.
Package wildcard provides Cobra commands for adding and removing wildcard rewrite entries across one or more AdGuard Home instances.
version
Package version provides the CLI command for displaying application version information.
Package version provides the CLI command for displaying application version information.
internal
app
Package app wires command-facing use cases to domain and infrastructure packages.
Package app wires command-facing use cases to domain and infrastructure packages.
client
Package client defines AdGuard Home client-management use cases.
Package client defines AdGuard Home client-management use cases.
config
Package config loads, mutates, and saves the agh-cli YAML configuration.
Package config loads, mutates, and saves the agh-cli YAML configuration.
credentials
Package credentials owns AdGuard Home credential storage and resolution.
Package credentials owns AdGuard Home credential storage and resolution.
execution
Package execution provides deterministic, ordered execution across multiple targets with explicit outcomes and aggregate failures.
Package execution provides deterministic, ordered execution across multiple targets with explicit outcomes and aggregate failures.
filtering
Package filtering defines AdGuard Home filtering-management use cases.
Package filtering defines AdGuard Home filtering-management use cases.
flags
Package flags defines command flag containers and their pflag bindings.
Package flags defines command flag containers and their pflag bindings.
instance
Package instance owns AdGuard Home instance configuration and catalog selection semantics.
Package instance owns AdGuard Home instance configuration and catalog selection semantics.
metadata
Package metadata provides application version and build information.
Package metadata provides application version and build information.
output
Package output provides reusable terminal output utilities for the CLI.
Package output provides reusable terminal output utilities for the CLI.
rewrite
Package rewrite contains DNS rewrite-management use cases and value shaping.
Package rewrite contains DNS rewrite-management use cases and value shaping.
pkg
adguard
Package adguard provides a small, project-owned client for AdGuard Home's HTTP API.
Package adguard provides a small, project-owned client for AdGuard Home's HTTP API.
tools
docgen command
Package main provides the documentation generator for the agh-cli command tree.
Package main provides the documentation generator for the agh-cli command tree.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL