The installer
Every step install.sh takes, why it takes it, and every flag it accepts.
Seven steps, each of which handles something that actually goes wrong. It is safe to run twice: every step checks for what it is about to create.
Watching it run#
On a fresh Pi with nothing installed, start to finish. The pauses are real; most of the elapsed time is Docker pulling images.
Against a Pi-hole that is already running, start to finish. Shorter than the AdGuard path by exactly the two steps it no longer has to do: there is no resolver to install and no argument about port 53.
What each step does#
- 01
Checks the machine
Architecture, distribution and free disk. It warns rather than stops when the box is unusual, and stops only where carrying on would break something: an unsupported OS, no root and no sudo. - 02
Docker
If missing, offers the officialget.docker.comscript. If present but not running, it says so and stops rather than trying to start a service it did not install. It adds the invoking user to thedockergroup when run under sudo. - 03
AdGuard HomePi-hole
Skipped if one is already answering on 3001 or 3000, or if you passed--agh-url. Otherwise it runs the official image on the host network so it can bind , with work and conf directories bind-mounted under the install directory. If holds it, the installer offers to disable the stub listener.Never installed, only found. It reads/api/info/versionand stops here if the answer is v5, because every later step would fail in a less obvious way. Port 53 is not touched: Pi-hole is already bound to it, and the stub-resolver dance was settled when you installed Pi-hole. - 04
Checks credentials before writing them
One authenticatedGET /control/status.OnePOST /api/authto exchange the password for a session, then oneGET /api/queriesto prove the session can actually read the log — authenticating and being allowed to read are separate failures on Pi-hole, and only the second one matters here. A 401 stops the install with a message about the password rather than leaving you with a container that restarts forever. A connection failure is reported as unreachable, which is a different problem. - 05
Writes the configuration
.envatchmod 600because it holds your admin password, and a compose file with the history bind-mounted to./dataso it is somewhere you can find and copy. Existing files are never overwritten without asking. - 06
Pulls and starts
docker compose pullthenup -d. - 07
Waits for evidence
Polls/api/healthfor up to eighty seconds, then reads/api/ingest/statusand reports how many queries were already ingested, so the install ends with a fact rather than a claim.
Flags#
| Flag | Effect |
|---|---|
--with-adguard | Install AdGuard Home without asking |
--dashboard-only | Assume AdGuard Home exists; never install it |
--agh-url URL | Point at an existing instance, e.g. http://192.168.1.10:3000 |
--agh-user / --agh-pass | Supply credentials non-interactively |
--port N | Dashboard port, default 3000 |
--agh-port N | AdGuard Home admin port, default 3001 |
--dir PATH | Install directory, default /opt/elele-dns |
--image REF | Container image, for pinning a version or a fork |
-y, --yes | Take every default, ask nothing |
--dry-run | Print every action, change nothing |
--uninstall | Stop and remove the dashboard, keeping data and AdGuard Home |
| Flag | Effect |
|---|---|
--pihole-url URL | Point at your Pi-hole, e.g. http://192.168.1.10. Implies --dashboard-only |
--pihole-pass | Supply the app password non-interactively |
--dashboard-only | Never install a resolver. Implied by --pihole-url |
--port N | Dashboard port, default 3000 |
--dir PATH | Install directory, default /opt/elele-dns |
--image REF | Container image, for pinning a version or a fork |
-y, --yes | Take every default, ask nothing |
--dry-run | Print every action, change nothing |
--uninstall | Stop and remove the dashboard, keeping data and Pi-hole |
The AdGuard flags (--with-adguard, --agh-url, --agh-port, --agh-user, --agh-pass) still exist and still work; they are simply not part of this path, and passing both sets is refused rather than silently resolved in one direction.
If you would rather not pipe it into a root shell#
Every example so far begins curl … | sudo bash, which asks you to give a URL root on the machine that answers DNS for your whole house. Declining that is a reasonable instinct, and nothing here depends on it. The installer is one self-contained file, so fetching it, reading it, and then running it gets you to exactly the same place:
curl -fsSL https://dns.elele.dev/install.sh -o install.sh
less install.sh # or open it in whatever you read code in
chmod +x install.sh
sudo ./install.shThe copy you read is the copy that runs. That file is authored in the application repository as public/install.sh and mirrored out to every place that serves it, so what is published here and what you just reviewed are the same bytes. Add --dry-run if you would rather watch it decide before letting it act.
Common invocations#
Everything, on a fresh box, unattended
sudo ./install.sh --with-adguard --yesDashboard only, against an existing resolver
sudo ./install.sh --dashboard-only --agh-url http://192.168.1.10:3000See what it would do
./install.sh --dry-run --with-adguardMove to the newest release
sudo /opt/elele-dns/install.sh --updateRemove the dashboard, keep everything else
sudo ./install.sh --uninstallAgainst your Pi-hole, unattended
sudo ./install.sh --pihole-url http://192.168.1.10 --yesPi-hole on a non-standard port
sudo ./install.sh --pihole-url http://192.168.1.10:8080See what it would do
./install.sh --dry-run --pihole-url http://192.168.1.10Move to the newest release
sudo /opt/elele-dns/install.sh --updateRemove the dashboard, keep everything else
sudo ./install.sh --uninstallIf it fails#
Every run writes a log to /tmp/elele-install-TIMESTAMP.log and the error trap prints the path and the failing line number. Most failures are one of four things, all covered in Troubleshooting: port 53 occupied, wrong AdGuard Home credentialsa Pi-hole still on v5, a password that authenticates but cannot read the log, all the session seats taken, Docker not running, or the image failing to pull on a slow connection.