diff --git a/README.md b/README.md
index 2a1a804..2cd0f77 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ curl -fsSL https://raw.githubusercontent.com/antomanc/agbox/main/install.sh | ba
[](https://github.com/antomanc/agbox/actions)
[](LICENSE)
+

+
## Why
Running agents on a headless Linux box usually means a pile of small failures:
diff --git a/bin/agbox b/bin/agbox
index 1f747c7..52ed6ed 100755
--- a/bin/agbox
+++ b/bin/agbox
@@ -108,8 +108,12 @@ _SUDO_KEEPALIVE_PID=''
preflight() {
if ! is_root; then
has_cmd sudo || die "sudo is not installed: run agbox as root (it will create '$AGBOX_USER' for you)"
- log_info "agbox needs sudo; you may be asked for your password once"
- sudo -v || die "sudo authentication failed"
+ # Try non-interactively first: `sudo -v` asks for a password whenever any sudoers
+ # rule needs one (Debian's %sudo does), even if a NOPASSWD rule also matches.
+ if ! sudo -n true 2>/dev/null; then
+ log_info "agbox needs sudo; you may be asked for your password once"
+ sudo -v || die "sudo authentication failed"
+ fi
# Keep the sudo timestamp fresh for long runs; stops when agbox exits.
(while kill -0 "$$" 2>/dev/null; do
sudo -n true 2>/dev/null || true
diff --git a/docs/assets/demo.gif b/docs/assets/demo.gif
new file mode 100644
index 0000000..0fe3f8c
Binary files /dev/null and b/docs/assets/demo.gif differ
diff --git a/docs/assets/demo.tape b/docs/assets/demo.tape
new file mode 100644
index 0000000..bace503
--- /dev/null
+++ b/docs/assets/demo.tape
@@ -0,0 +1,31 @@
+# Hero GIF for the README. Regenerate with: vhs docs/assets/demo.tape
+# Needs vhs, ttyd and ffmpeg, and a machine already set up with agbox.
+
+Output docs/assets/demo.gif
+
+Set Shell bash
+Set FontFamily "JetBrains Mono"
+Set FontSize 15
+Set LineHeight 1.15
+Set Width 820
+Set Height 1200
+Set Padding 24
+Set Theme "Catppuccin Mocha"
+Set TypingSpeed 60ms
+Set WindowBar Colorful
+
+# Tidy prompt; skip the SSH check (this box uses Tailscale SSH) and hide the tailnet name.
+Hide
+Type `export PS1='\[\e[32m\]antomanc@home-server\[\e[0m\]:\[\e[34m\]~\[\e[0m\]$ '`
+Enter
+Type `agbox() { "$PWD/bin/agbox" "$@" --skip ssh | sed -E 's/\.[a-z0-9-]+\.ts\.net/.tailnet.ts.net/'; }`
+Enter
+Type "clear"
+Enter
+Show
+
+Sleep 800ms
+Type "agbox doctor"
+Sleep 400ms
+Enter
+Sleep 5s
diff --git a/docs/assets/doctor.png b/docs/assets/doctor.png
new file mode 100644
index 0000000..391a811
Binary files /dev/null and b/docs/assets/doctor.png differ
diff --git a/modules/system.sh b/modules/system.sh
index 0f66b34..27e5073 100644
--- a/modules/system.sh
+++ b/modules/system.sh
@@ -4,7 +4,7 @@
SYSTEM_PACKAGES=(
sudo curl ca-certificates gnupg git build-essential pkg-config
jq ripgrep fd-find tmux unzip zip htop rsync bubblewrap socat
- lsb-release dnsutils file less nano
+ lsb-release bind9-dnsutils file less nano
)
system::describe() { echo "base packages, upgrades, hostname, timezone, zram, auto-updates"; }
diff --git a/tests/cli.bats b/tests/cli.bats
index 7e45ccd..fc65761 100644
--- a/tests/cli.bats
+++ b/tests/cli.bats
@@ -86,3 +86,15 @@ setup() {
[ "$(grep -c 'Apply?' <<<"$output")" -eq 2 ]
! id agbox-dryrun-user 2>/dev/null
}
+
+@test "setup works with passwordless sudo when sudo -v would prompt" {
+ [ "$EUID" -ne 0 ] || skip "root never calls sudo"
+ # Fake sudo: NOPASSWD for commands, but -v demands a password (Debian's %sudo rule).
+ mkdir -p "$BATS_TEST_TMPDIR/bin"
+ printf '#!/bin/sh\n[ "$1" = -v ] && exit 1\n[ "$1" = -n ] && shift\n[ "$1" = -- ] && shift\nexec "$@"\n' \
+ >"$BATS_TEST_TMPDIR/bin/sudo"
+ chmod +x "$BATS_TEST_TMPDIR/bin/sudo"
+ PATH="$BATS_TEST_TMPDIR/bin:$PATH" run "$AGBOX" setup --dry-run --yes --set USER=agbox-dryrun-user --only system
+ [ "$status" -eq 0 ]
+ [[ "$output" != *"sudo authentication failed"* ]]
+}