mirror of
https://github.com/andsyrovatko/s4k-admin-toolbox.git
synced 2026-04-21 21:58:54 +02:00
feat(docker): Implemented a robust Bash utility for managing Docker bridge networks.
Key improvements and features:
- Automated IPAM: Scans for available /24 subnets within a defined BASE_NET range.
- Native OS Integration: Forces static bridge names using 'com.docker.network.bridge.name' for easier netfilter/iptables rules.
- Infrastructure Persistence: Tracks managed networks via a flat-file database (NET_FILE).
- Safety Mechanisms:
- Enforced 15-char limit for Linux interface compatibility.
- ShellCheck-validated code with 'set -euo pipefail' (Strict Mode).
- Interactive confirmation for bulk decommissioning.
- Comprehensive Dashboard: Provides 'info' command for real-time network status and IP range overview.
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Script Name : docker-network-manager.sh
|
||||
# Description : Manage Docker external networks with automatic subnet allocation and tracking.
|
||||
# Usage: : ./docker-network-manager.sh <COMMAND> [ARGUMENTS]
|
||||
# Commands:
|
||||
# create [NAME] - Create a specific network by name or all networks from file if NO NAME provided.
|
||||
# delete [NAME] - Delete a specific network by name or all networks from file if NO NAME provided.
|
||||
# info - Show current status and configuration.
|
||||
# For details - see README.md
|
||||
# Author : syr4ok (Andrii Syrovatko)
|
||||
# Version : 1.1.0
|
||||
# =============================================================================
|
||||
|
||||
# --- STRICT MODE ---
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration Loader
|
||||
CONFIG_FILE="$(dirname "$0")/docker-network-manager.conf"
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$CONFIG_FILE"
|
||||
else
|
||||
echo "[Error]: Configuration file not found. Create ip_manager.conf from example."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
touch "$NET_FILE"
|
||||
|
||||
log() {
|
||||
local level="$1"; shift
|
||||
printf '%s [%s] %s\n' "$(date -Is)" "$level" "$*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
confirm_action() {
|
||||
[[ "${FORCE:-false}" == "true" ]] && return 0
|
||||
|
||||
read -rp "Are you sure you want to delete ALL networks from $NET_FILE? (y/N): " response
|
||||
case "$response" in
|
||||
[yY][eE][sS]|[yY]) return 0 ;;
|
||||
*) log INFO "Operation cancelled by user"; exit 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Network check
|
||||
network_exists() {
|
||||
docker network ls --format '{{.Name}}' | grep -Fxq "$1"
|
||||
}
|
||||
|
||||
used_subnets() {
|
||||
local escaped_base="${BASE_NET//./\\.}"
|
||||
|
||||
# Get all subnets from existing Docker networks and filter those that match our base pattern
|
||||
docker network ls -q | xargs -r docker network inspect 2>/dev/null \
|
||||
| grep -oP '"Subnet":\s*"\K'"${escaped_base}"'\.[0-9]+\.0/24' || echo ""
|
||||
}
|
||||
|
||||
next_free_subnet() {
|
||||
local used="$1"
|
||||
for i in $(seq "$START_OCTET" "$END_OCTET"); do
|
||||
local subnet="${BASE_NET}.${i}.0/24"
|
||||
if ! grep -q "$subnet" <<<"$used"; then
|
||||
echo "$subnet"
|
||||
return
|
||||
fi
|
||||
done
|
||||
log ERROR "no free /24 subnet found in ${BASE_NET}.x.x"
|
||||
exit 1
|
||||
}
|
||||
|
||||
name_length_check() {
|
||||
local name="$1"
|
||||
if [[ ${#name} -gt 15 ]]; then
|
||||
log WARN "Network name '$name' is too long for a Linux bridge (max 15 chars). It might be truncated."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
provision_single() {
|
||||
local name="$1"
|
||||
if ! name_length_check "$name"; then
|
||||
return 1
|
||||
fi
|
||||
if network_exists "$name"; then
|
||||
log INFO "network already exists name=$name"
|
||||
return
|
||||
fi
|
||||
|
||||
local used
|
||||
local subnet
|
||||
used="$(used_subnets)"
|
||||
subnet="$(next_free_subnet "$used")"
|
||||
local gw="${subnet%0/24}1"
|
||||
|
||||
log INFO "creating network name=$name subnet=$subnet gateway=$gw"
|
||||
docker network create --driver bridge --subnet "$subnet" --gateway "$gw" \
|
||||
--opt com.docker.network.bridge.name="$name" "$name" >/dev/null
|
||||
|
||||
if ! grep -Fxq "$name" "$NET_FILE"; then
|
||||
echo "$name" >> "$NET_FILE"
|
||||
log INFO "network name=$name added to $NET_FILE"
|
||||
sed -i '/^$/d' "$NET_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
provision_from_file() {
|
||||
[[ ! -s "$NET_FILE" ]] && { log WARN "NET_FILE is empty"; return; }
|
||||
log INFO "starting mass provisioning from $NET_FILE"
|
||||
while read -u 3 -r line; do
|
||||
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
||||
provision_single "$line" || true
|
||||
done 3< "$NET_FILE"
|
||||
}
|
||||
|
||||
delete_network() {
|
||||
local name="$1"
|
||||
if network_exists "$name"; then
|
||||
log INFO "deleting network name=$name"
|
||||
docker network rm "$name" >/dev/null
|
||||
log INFO "network name=$name deleted from docker"
|
||||
else
|
||||
log WARN "network not found in docker name=$name — cleanup config anyway"
|
||||
fi
|
||||
|
||||
# Remove from NET_FILE if exists
|
||||
if grep -Fxq "$name" "$NET_FILE"; then
|
||||
sed -i "/^$name$/d" "$NET_FILE"
|
||||
log INFO "network name=$name removed from $NET_FILE"
|
||||
else
|
||||
log INFO "network name=$name not found in $NET_FILE, no cleanup needed"
|
||||
fi
|
||||
}
|
||||
|
||||
delete_all_from_file() {
|
||||
log INFO "deleting all networks listed in $NET_FILE"
|
||||
while read -u 3 -r line; do
|
||||
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
||||
delete_network "$line"
|
||||
done 3< "$NET_FILE"
|
||||
}
|
||||
|
||||
show_info() {
|
||||
local tracked_count
|
||||
tracked_count=$(grep -cE '^\s*[^#]' "$NET_FILE" || true)
|
||||
|
||||
echo "--- Docker Network Manager v1.1.0 ---"
|
||||
echo "Configured Base : ${BASE_NET}.0.0/16 (Range: .${START_OCTET}.x to .${END_OCTET}.x)"
|
||||
echo "Config File : $(basename "$NET_FILE")"
|
||||
echo "Log File : $(basename "$LOG_FILE")"
|
||||
echo "Tracked Networks: $tracked_count"
|
||||
|
||||
echo -e "\n[Current Status]"
|
||||
if [[ "$tracked_count" -gt 0 ]]; then
|
||||
printf "%-20s %-15s %-10s\n" "NETWORK NAME" "IP RANGE" "STATUS"
|
||||
echo "--------------------------------------------------------"
|
||||
while read -u 3 -r line; do
|
||||
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
||||
|
||||
local status="OFFLINE"
|
||||
local subnet="N/A"
|
||||
|
||||
if network_exists "$line"; then
|
||||
status="ONLINE"
|
||||
subnet=$(docker network inspect "$line" --format '{{(index .IPAM.Config 0).Subnet}}' 2>/dev/null || echo "error")
|
||||
fi
|
||||
printf "%-20s %-15s %-10s\n" "$line" "$subnet" "$status"
|
||||
done 3< "$NET_FILE"
|
||||
else
|
||||
echo "No networks tracked in $NET_FILE"
|
||||
fi
|
||||
|
||||
echo -e "\n[Usage]"
|
||||
echo " $0 create (c) [name] - Create single network and add to file"
|
||||
echo " $0 create (c) - Create all networks from file"
|
||||
echo " $0 delete (d) [name] - Remove network and clean file"
|
||||
echo " $0 delete (d) - Remove all networks from file"
|
||||
}
|
||||
|
||||
case "${1:-info}" in
|
||||
[Cc][Rr][Ee][Aa][Tt][Ee]|[Cc])
|
||||
if [[ ${2:-} ]]; then
|
||||
provision_single "$2"
|
||||
else
|
||||
provision_from_file
|
||||
fi
|
||||
;;
|
||||
[Dd][Ee][Ll][Ee][Tt][Ee]|[Dd])
|
||||
if [[ ${2:-} ]]; then
|
||||
delete_network "$2"
|
||||
else
|
||||
count=$( (grep -vE '^\s*(#|$)' "$NET_FILE" || true) | wc -l | xargs)
|
||||
|
||||
if (( count == 0 )); then
|
||||
log INFO "Nothing to delete. $NET_FILE is empty or contains only comments."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
confirm_action
|
||||
delete_all_from_file
|
||||
fi
|
||||
;;
|
||||
[Ii][Nn][Ff][Oo]|[Ii])
|
||||
show_info
|
||||
;;
|
||||
*)
|
||||
log ERROR "Unknown command: $1"
|
||||
show_info
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user