Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added

- `easy proxy doctor` — read-only pre-flight diagnostic. Flags non-standard
vhost configs (`upstream {}` blocks, deprecated `listen ... http2`), runs the
nginx config test when the proxy is running, and lists the proxy's Docker
networks.

## [2.0.0] — 2026-05-18

A major release: automatic Let's Encrypt SSL with multi-DNS-provider support.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ easy proxy status # → container ID se running
| `easy proxy reload` | Ricarica nginx (dopo new o modifica conf) |
| `easy proxy status` | Container ID se running, vuoto se fermo |
| `easy proxy id` | Container ID (`docker ps` per nome `easy-proxy`, anche se fermo) |
| `easy proxy doctor` | Diagnosi read-only: vhost non-standard, `nginx -t`, reti del proxy |
| `easy proxy start/stop/restart` | Ciclo container |
| `easy proxy sh` | Shell interattiva nel container |
| `easy proxy log` | `docker logs -f` del container |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Run `easy proxy help` for the full list.
| `easy proxy reload` | Reload nginx after adding or changing vhosts |
| `easy proxy status` | Container id if running, empty if stopped |
| `easy proxy id` | Container id (running or stopped) |
| `easy proxy doctor` | Read-only diagnostic: non-standard vhosts, `nginx -t`, proxy networks |
| `easy proxy start` / `stop` / `restart` | Container lifecycle |
| `easy proxy sh` | Interactive shell in the container |
| `easy proxy log` | Follow the container logs |
Expand Down
69 changes: 69 additions & 0 deletions commands/proxy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function __easy_command_proxy_help {
echo " easy proxy new"
echo " easy proxy id"
echo " easy proxy status"
echo " easy proxy doctor"
echo " easy proxy start"
echo " easy proxy stop"
echo " easy proxy destroy"
Expand Down Expand Up @@ -222,6 +223,10 @@ chmod 600 /etc/letsencrypt/ionos.ini"
docker ps -q -f "name=^${EASY_PROXY_NAME}$" 2>/dev/null
return $?
fi
if [[ "doctor" == "$2" ]]; then
__easy_command_proxy_doctor
return $?
fi
if [[ -z "$2" ]]; then
__easy_command_proxy_default
return $?
Expand Down Expand Up @@ -251,6 +256,70 @@ function __easy_command_proxy_create {
return $?
}

# Read-only pre-flight diagnostic: static vhost analysis (host-side) plus,
# when the proxy is running, the nginx config test and the network list.
# Exits non-zero only when nginx -t fails (a definite startup blocker).
function __easy_command_proxy_doctor {
echo "easy proxy doctor — pre-flight check"
echo
local warnings=0

# vhost configs — static analysis, host-side (no Docker needed)
echo "vhost configs (${EASY_DOMAINS_DIR}):"
local confs
confs=$(find "${EASY_DOMAINS_DIR}" -type f -name '*.conf' 2>/dev/null | sort)
if [[ -z "${confs}" ]]; then
echo " no vhost files found"
else
echo " $(printf '%s\n' "${confs}" | wc -l | tr -d ' ') vhost file(s)"
local conf
while IFS= read -r conf; do
if grep -qE '^[[:space:]]*upstream[[:space:]]' "${conf}"; then
echo " WARN ${conf}"
echo " static 'upstream {}' block — resolved at nginx startup; one"
echo " unresolvable host blocks every site. Convert to a variable:"
echo " set \$u <host>; proxy_pass http://\$u;"
warnings=$((warnings + 1))
fi
if grep -qE 'listen[^;]*http2' "${conf}"; then
echo " WARN ${conf}"
echo " deprecated 'listen ... http2' — use the 'http2 on;' directive"
warnings=$((warnings + 1))
fi
done <<< "${confs}"
fi
echo

# runtime checks — need the running container
local proxy_running
proxy_running=$(easy proxy status)

echo "nginx config test:"
local nginx_failed=0
if [[ -z "${proxy_running}" ]]; then
echo " skipped — proxy not running ('easy proxy create' first)"
elif docker exec "${EASY_PROXY_NAME}" nginx -t -c /usr/local/share/easy/nginx.conf >/dev/null 2>&1; then
echo " PASS"
else
echo " FAIL — details: docker exec ${EASY_PROXY_NAME} nginx -t"
nginx_failed=1
fi
echo

echo "proxy networks:"
if [[ -z "${proxy_running}" ]]; then
echo " skipped — proxy not running"
else
local nets
nets=$(docker inspect "${EASY_PROXY_NAME}" --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' 2>/dev/null)
echo " ${nets:-(none)}"
fi
echo

echo "summary: ${warnings} warning(s)"
return "${nginx_failed}"
}

function __easy_command_proxy_default {
__easy_command_proxy_help
return 1
Expand Down
65 changes: 65 additions & 0 deletions test/doctor.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bats
# Tests for `easy proxy doctor` — the read-only pre-flight diagnostic.

load test_helper

setup() { easy_setup; }

@test "easy proxy help lists the doctor command" {
run easy proxy help
[ "$status" -eq 0 ]
[[ "$output" == *"easy proxy doctor"* ]]
}

@test "easy proxy doctor reports no vhost files on a clean setup" {
mock_docker_stopped
run easy proxy doctor
[ "$status" -eq 0 ]
[[ "$output" == *"no vhost files"* ]]
}

@test "easy proxy doctor flags a static upstream block" {
mock_docker_stopped
mkdir -p "$EASY_DOMAINS_DIR/legacy.example.com"
cat > "$EASY_DOMAINS_DIR/legacy.example.com/site.conf" <<'CONF'
upstream backend { server some_container; }
server { listen 80; location / { proxy_pass http://backend; } }
CONF
run easy proxy doctor
[[ "$output" == *"site.conf"* ]]
[[ "$output" == *"upstream"* ]]
[[ "$output" == *"1 warning"* ]]
}

@test "easy proxy doctor flags a deprecated listen ... http2 directive" {
mock_docker_stopped
mkdir -p "$EASY_DOMAINS_DIR/old.example.com"
echo 'server { listen 443 ssl http2; }' > "$EASY_DOMAINS_DIR/old.example.com/site.conf"
run easy proxy doctor
[[ "$output" == *"http2"* ]]
}

@test "easy proxy doctor reports zero warnings for a template-style vhost" {
mock_docker_stopped
mkdir -p "$EASY_DOMAINS_DIR/good.example.com"
cat > "$EASY_DOMAINS_DIR/good.example.com/site.conf" <<'CONF'
server { listen 80; location / { set $u http://app:8080; proxy_pass $u; } }
CONF
run easy proxy doctor
[ "$status" -eq 0 ]
[[ "$output" == *"0 warning(s)"* ]]
}

@test "easy proxy doctor runs the nginx config test when the proxy is running" {
mock_docker_running
run easy proxy doctor
[ "$status" -eq 0 ]
[[ "$output" == *"PASS"* ]]
}

@test "easy proxy doctor exits non-zero when the nginx config test fails" {
mock_docker_nginx_invalid
run easy proxy doctor
[ "$status" -ne 0 ]
[[ "$output" == *"FAIL"* ]]
}
13 changes: 13 additions & 0 deletions test/test_helper.bash
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,16 @@ mock_docker_stopped() {
printf '#!/usr/bin/env bash\nexit 0\n' > "$MOCK_BIN/docker"
chmod +x "$MOCK_BIN/docker"
}

# Mock `docker`: the proxy is running, but `docker exec` (e.g. `nginx -t`) fails.
mock_docker_nginx_invalid() {
cat > "$MOCK_BIN/docker" <<'MOCK'
#!/usr/bin/env bash
case "$1" in
ps) echo "deadbeefcafe1234" ;;
exec) exit 1 ;;
*) exit 0 ;;
esac
MOCK
chmod +x "$MOCK_BIN/docker"
}
Loading