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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
vhost configs (`upstream {}` blocks, deprecated `listen ... http2`), runs the
nginx config test when the proxy is running, and lists the proxy's Docker
networks.
- `EASY_PROXY_NETWORK` — when set, `easy proxy create` joins that Docker network
(created if missing), so recreating the proxy keeps its connectivity to the
backends. New commands: `easy proxy attach`/`detach <container>` to connect a
site to the edge network, and `easy proxy networks [prune]` to audit and clean
up the proxy's network membership.

## [2.0.0] — 2026-05-18

Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ mkdir -p "$EASY_LETSENCRYPT_DIR" "$EASY_DOMAINS_DIR"

Aggiungi queste righe a `~/.zshrc` o `~/.bashrc` per averle sempre disponibili.

Opzionale — `EASY_PROXY_NETWORK`: se impostata, `easy proxy create` aggancia il
container a quella rete Docker (creata se non esiste) e ricrearlo non perde la
connettività verso i backend. I siti si collegano con `easy proxy attach`.

### Build immagine locale

Il `Dockerfile` è self-contained (`FROM certbot/certbot:latest`): un solo build,
Expand Down Expand Up @@ -143,6 +147,8 @@ easy proxy status # → container ID se running
| `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 attach\|detach <container>` | Collega/scollega un container alla rete edge `EASY_PROXY_NETWORK` |
| `easy proxy networks [prune]` | Mostra le reti del proxy; `prune` scollega quelle non-edge |
| `easy proxy start/stop/restart` | Ciclo container |
| `easy proxy sh` | Shell interattiva nel container |
| `easy proxy log` | `docker logs -f` del container |
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ Run `easy proxy help` for the full list.
| `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 attach` / `detach <container>` | Connect/disconnect a site container to the edge network |
| `easy proxy networks [prune]` | Show the proxy's networks; `prune` disconnects non-edge ones |
| `easy proxy start` / `stop` / `restart` | Container lifecycle |
| `easy proxy sh` | Interactive shell in the container |
| `easy proxy log` | Follow the container logs |
Expand Down
89 changes: 89 additions & 0 deletions commands/proxy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ function __easy_command_proxy_help {
echo " easy proxy id"
echo " easy proxy status"
echo " easy proxy doctor"
echo " easy proxy attach <container>"
echo " easy proxy detach <container>"
echo " easy proxy networks [prune]"
echo " easy proxy start"
echo " easy proxy stop"
echo " easy proxy destroy"
Expand Down Expand Up @@ -227,6 +230,18 @@ chmod 600 /etc/letsencrypt/ionos.ini"
__easy_command_proxy_doctor
return $?
fi
if [[ "attach" == "$2" ]]; then
__easy_command_proxy_attach "$3"
return $?
fi
if [[ "detach" == "$2" ]]; then
__easy_command_proxy_detach "$3"
return $?
fi
if [[ "networks" == "$2" ]]; then
__easy_command_proxy_networks "$3"
return $?
fi
if [[ -z "$2" ]]; then
__easy_command_proxy_default
return $?
Expand All @@ -245,8 +260,19 @@ function __easy_command_proxy_create {
echo "Invalid EASY_LETSENCRYPT_DIR"
return 1
fi
# Join the edge network when EASY_PROXY_NETWORK is set, so recreating the
# container keeps its connectivity to the backends (see #23). Unset = default.
local network_args=()
if [[ -n "${EASY_PROXY_NETWORK}" ]]; then
if ! docker network inspect "${EASY_PROXY_NETWORK}" >/dev/null 2>&1; then
echo "Creating Docker network ${EASY_PROXY_NETWORK}"
docker network create "${EASY_PROXY_NETWORK}" >/dev/null || return 1
fi
network_args=(--network "${EASY_PROXY_NETWORK}")
fi
docker run -d \
--name "${EASY_PROXY_NAME}" \
"${network_args[@]}" \
-v "${EASY_DOMAINS_DIR}:/domains" \
-v "${EASY_LETSENCRYPT_DIR}:/etc/letsencrypt" \
-v "${EASY_DIR}/easyhome:/usr/local/share/easy" \
Expand Down Expand Up @@ -320,6 +346,69 @@ function __easy_command_proxy_doctor {
return "${nginx_failed}"
}

# Connect a site container to the edge network so the proxy can reach it.
function __easy_command_proxy_attach {
if [[ -z "$1" ]]; then
echo "Usage: easy proxy attach <container>"
return 1
fi
if [[ -z "${EASY_PROXY_NETWORK}" ]]; then
echo "EASY_PROXY_NETWORK is not set — set it to the proxy's edge network first."
return 1
fi
docker network connect "${EASY_PROXY_NETWORK}" "$1"
return $?
}

# Disconnect a site container from the edge network.
function __easy_command_proxy_detach {
if [[ -z "$1" ]]; then
echo "Usage: easy proxy detach <container>"
return 1
fi
if [[ -z "${EASY_PROXY_NETWORK}" ]]; then
echo "EASY_PROXY_NETWORK is not set."
return 1
fi
docker network disconnect "${EASY_PROXY_NETWORK}" "$1"
return $?
}

# Audit the networks the proxy is joined to; `prune` disconnects it from every
# network other than EASY_PROXY_NETWORK.
function __easy_command_proxy_networks {
if [[ -z "$(easy proxy id)" ]]; then
echo "Proxy container not found — run 'easy proxy create' first."
return 1
fi
local nets
nets=$(docker inspect "${EASY_PROXY_NAME}" --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' 2>/dev/null)
local -a netlist
read -ra netlist <<< "${nets}"
local net
if [[ "$1" == "prune" ]]; then
if [[ -z "${EASY_PROXY_NETWORK}" ]]; then
echo "EASY_PROXY_NETWORK is not set — refusing to prune (no edge network to keep)."
return 1
fi
for net in "${netlist[@]}"; do
if [[ "${net}" != "${EASY_PROXY_NETWORK}" ]]; then
echo "disconnecting ${EASY_PROXY_NAME} from ${net}"
docker network disconnect "${net}" "${EASY_PROXY_NAME}"
fi
done
else
echo "${EASY_PROXY_NAME} is connected to:"
for net in "${netlist[@]}"; do
if [[ "${net}" == "${EASY_PROXY_NETWORK}" ]]; then
echo " ${net} (edge)"
else
echo " ${net} (extra — 'easy proxy networks prune' to disconnect)"
fi
done
fi
}

function __easy_command_proxy_default {
__easy_command_proxy_help
return 1
Expand Down
86 changes: 86 additions & 0 deletions test/network.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env bats
# Tests for `easy proxy` edge-network management — EASY_PROXY_NETWORK,
# attach/detach, and the proxy network audit/prune.

load test_helper

setup() { easy_setup; }

@test "easy proxy help lists the network commands" {
run easy proxy help
[ "$status" -eq 0 ]
[[ "$output" == *"easy proxy attach"* ]]
[[ "$output" == *"easy proxy networks"* ]]
}

@test "easy proxy create joins EASY_PROXY_NETWORK and auto-creates it" {
export EASY_PROXY_NETWORK=ethicnet
export DOCKER_LOG="$BATS_TEST_TMPDIR/docker.log"
mock_docker_record
run easy proxy create
[ "$status" -eq 0 ]
grep -q "network create ethicnet" "$DOCKER_LOG"
grep -q -- "--network ethicnet" "$DOCKER_LOG"
}

@test "easy proxy create stays on the default network when EASY_PROXY_NETWORK is unset" {
export DOCKER_LOG="$BATS_TEST_TMPDIR/docker.log"
mock_docker_record
run easy proxy create
[ "$status" -eq 0 ]
! grep -q -- "--network" "$DOCKER_LOG"
}

@test "easy proxy attach without a container prints usage" {
mock_docker_stopped
export EASY_PROXY_NETWORK=ethicnet
run easy proxy attach
[ "$status" -ne 0 ]
[[ "$output" == *"Usage"* ]]
}

@test "easy proxy attach without EASY_PROXY_NETWORK fails" {
mock_docker_stopped
run easy proxy attach mysite
[ "$status" -ne 0 ]
[[ "$output" == *"EASY_PROXY_NETWORK"* ]]
}

@test "easy proxy attach connects a container to the edge network" {
export EASY_PROXY_NETWORK=ethicnet
export DOCKER_LOG="$BATS_TEST_TMPDIR/docker.log"
mock_docker_record
run easy proxy attach mysite
[ "$status" -eq 0 ]
grep -q "network connect ethicnet mysite" "$DOCKER_LOG"
}

@test "easy proxy detach disconnects a container from the edge network" {
export EASY_PROXY_NETWORK=ethicnet
export DOCKER_LOG="$BATS_TEST_TMPDIR/docker.log"
mock_docker_record
run easy proxy detach mysite
[ "$status" -eq 0 ]
grep -q "network disconnect ethicnet mysite" "$DOCKER_LOG"
}

@test "easy proxy networks lists the proxy networks and flags the extras" {
export EASY_PROXY_NETWORK=ethicnet
mock_docker_multinet
run easy proxy networks
[ "$status" -eq 0 ]
[[ "$output" == *"ethicnet"* ]]
[[ "$output" == *"edge"* ]]
[[ "$output" == *"extra"* ]]
}

@test "easy proxy networks prune disconnects the proxy from non-edge networks" {
export EASY_PROXY_NETWORK=ethicnet
export DOCKER_LOG="$BATS_TEST_TMPDIR/docker.log"
mock_docker_multinet
run easy proxy networks prune
[ "$status" -eq 0 ]
grep -q "network disconnect bridge easy-proxy" "$DOCKER_LOG"
grep -q "network disconnect legacynet easy-proxy" "$DOCKER_LOG"
! grep -q "network disconnect ethicnet" "$DOCKER_LOG"
}
30 changes: 29 additions & 1 deletion test/test_helper.bash
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ easy_setup() {
export PATH="$MOCK_BIN:$EASY_CLI_DIR:$PATH"

# Deterministic: never inherit real credentials/config from the host shell.
unset IONOS_API_KEY IONOS_API_SECRET EASY_LETSENCRYPT_EMAIL EASY_LETSENCRYPT_DOMAIN
unset IONOS_API_KEY IONOS_API_SECRET EASY_LETSENCRYPT_EMAIL EASY_LETSENCRYPT_DOMAIN EASY_PROXY_NETWORK
}

# Mock `docker` so `docker ps` reports a fake running easy-proxy container.
Expand Down Expand Up @@ -68,3 +68,31 @@ esac
MOCK
chmod +x "$MOCK_BIN/docker"
}

# Mock `docker` that records every invocation to $DOCKER_LOG. `ps` returns
# nothing (proxy absent); `network inspect` fails (network absent).
mock_docker_record() {
cat > "$MOCK_BIN/docker" <<'MOCK'
#!/usr/bin/env bash
echo "$*" >> "${DOCKER_LOG:-/dev/null}"
case "$1 $2" in
"network inspect"*) exit 1 ;;
*) exit 0 ;;
esac
MOCK
chmod +x "$MOCK_BIN/docker"
}

# Mock `docker`: proxy running and joined to several networks (for `networks`).
mock_docker_multinet() {
cat > "$MOCK_BIN/docker" <<'MOCK'
#!/usr/bin/env bash
echo "$*" >> "${DOCKER_LOG:-/dev/null}"
case "$1 $2" in
"ps "*) echo "deadbeefcafe1234" ;;
"inspect "*) echo "ethicnet bridge legacynet " ;;
*) exit 0 ;;
esac
MOCK
chmod +x "$MOCK_BIN/docker"
}
Loading