1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2025-12-21 22:01:31 +00:00

scripts: ipv6_controller improvement + fix modules handling (#6722)

* Fix subscript handling for modules

* ipv6: detect case when link local is present

* v6-controller: removed fixed-cidr for docker 28+
This commit is contained in:
DerLinkman
2025-09-10 16:20:58 +02:00
committed by GitHub
parent 262fe04286
commit 94c1a6c4e1
4 changed files with 145 additions and 42 deletions

View File

@@ -5,14 +5,65 @@
# 1) Check if the host supports IPv6 # 1) Check if the host supports IPv6
get_ipv6_support() { get_ipv6_support() {
if grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null \ # ---- helper: probe external IPv6 connectivity without DNS ----
|| ! ip -6 route show default &>/dev/null; then _probe_ipv6_connectivity() {
# Use literal, always-on IPv6 echo responders (no DNS required)
local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111")
local ip rc=1
for ip in "${PROBE_IPS[@]}"; do
if command -v ping6 &>/dev/null; then
ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null
rc=$?
elif command -v ping &>/dev/null; then
ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null
rc=$?
else
rc=1
fi
[[ $rc -eq 0 ]] && return 0
done
return 1
}
if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then
DETECTED_IPV6=false DETECTED_IPV6=false
echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}"
else return
DETECTED_IPV6=true
echo -e "IPv6 detected on host ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
fi fi
if ip -6 route show default 2>/dev/null | grep -qE '^default'; then
echo -e "${YELLOW}Default IPv6 route found testing external IPv6 connectivity...${NC}"
if _probe_ipv6_connectivity; then
DETECTED_IPV6=true
echo -e "IPv6 detected on host ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else
DETECTED_IPV6=false
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
fi
return
fi
if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then
DETECTED_IPV6=false
echo -e "${YELLOW}Global IPv6 address present but no default route ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
return
fi
if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then
echo -e "${YELLOW}Only link-local IPv6 addresses found testing external IPv6 connectivity...${NC}"
if _probe_ipv6_connectivity; then
DETECTED_IPV6=true
echo -e "External IPv6 connectivity available ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else
DETECTED_IPV6=false
echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
fi
return
fi
DETECTED_IPV6=false
echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
} }
# 2) Ensure Docker daemon.json has (or create) the required IPv6 settings # 2) Ensure Docker daemon.json has (or create) the required IPv6 settings
@@ -21,7 +72,7 @@ docker_daemon_edit(){
DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1) DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
MISSING=() MISSING=()
_has_kv() { grep -Eq "\"$1\"\s*:\s*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; } _has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; }
if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then
@@ -38,12 +89,18 @@ docker_daemon_edit(){
fi fi
# Gather missing keys # Gather missing keys
! _has_kv ipv6 true && MISSING+=("ipv6: true") ! _has_kv ipv6 true && MISSING+=("ipv6: true")
! grep -Eq '"fixed-cidr-v6"\s*:\s*".+"' "$DOCKER_DAEMON_CONFIG" \
&& MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"') # For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines)
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -le 27 ]]; then if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \
&& MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"')
fi
# For Docker < 27, ip6tables needed and was tied to experimental in older releases
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
_has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true") _has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true")
! _has_kv experimental true && MISSING+=("experimental: true") ! _has_kv experimental true && MISSING+=("experimental: true")
fi fi
# Fix if needed # Fix if needed
@@ -60,9 +117,19 @@ docker_daemon_edit(){
cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak" cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak"
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
TMP=$(mktemp) TMP=$(mktemp)
JQ_FILTER='.ipv6 = true | .["fixed-cidr-v6"] = "fd00:dead:beef:c0::/80"' # Base filter: ensure ipv6 = true
[[ "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]] \ JQ_FILTER='.ipv6 = true'
&& JQ_FILTER+=' | .ip6tables = true | .experimental = true'
# Add fixed-cidr-v6 only for Docker < 28
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")'
fi
# Add ip6tables/experimental only for Docker < 27
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
JQ_FILTER+=' | .ip6tables = true | .experimental = true'
fi
jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG" jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG"
echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}" echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}"
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart (command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
@@ -97,12 +164,19 @@ docker_daemon_edit(){
"experimental": true "experimental": true
} }
EOF EOF
else elif [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
cat > "$DOCKER_DAEMON_CONFIG" <<EOF cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{ {
"ipv6": true, "ipv6": true,
"fixed-cidr-v6": "fd00:dead:beef:c0::/80" "fixed-cidr-v6": "fd00:dead:beef:c0::/80"
} }
EOF
else
# Docker 28+: ipv6 works without fixed-cidr-v6
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{
"ipv6": true
}
EOF EOF
fi fi
echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}" echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
@@ -122,7 +196,7 @@ configure_ipv6() {
# detect manual override if mailcow.conf is present # detect manual override if mailcow.conf is present
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2) MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
elif [[ -z "$MAILCOW_CONF" ]] && [[ ! -z "${ENABLE_IPV6:-}" ]]; then elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
MANUAL_SETTING="$ENABLE_IPV6" MANUAL_SETTING="$ENABLE_IPV6"
else else
MANUAL_SETTING="" MANUAL_SETTING=""
@@ -131,38 +205,34 @@ configure_ipv6() {
get_ipv6_support get_ipv6_support
# if user manually set it, check for mismatch # if user manually set it, check for mismatch
if [[ -n "$MANUAL_SETTING" ]]; then if [[ "$DETECTED_IPV6" != "true" ]]; then
if [[ "$MANUAL_SETTING" == "false" && "$DETECTED_IPV6" == "true" ]]; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
echo -e "${RED}ERROR: You have ENABLE_IPV6=false but your host and Docker support IPv6.${NC}" if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
echo -e "${RED}This can create an open relay. Please set ENABLE_IPV6=true in your mailcow.conf and re-run.${NC}" sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
exit 1 else
elif [[ "$MANUAL_SETTING" == "true" && "$DETECTED_IPV6" == "false" ]]; then echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
echo -e "${RED}ERROR: You have ENABLE_IPV6=true but your host does not support IPv6.${NC}" fi
echo -e "${RED}Please disable or fix your host/Docker IPv6 support, or set ENABLE_IPV6=false.${NC}"
exit 1
else else
return export IPV6_BOOL=false
fi fi
fi
# no manual override: proceed to set or export
if [[ "$DETECTED_IPV6" == "true" ]]; then
docker_daemon_edit
else
echo "Skipping Docker IPv6 configuration because host does not support IPv6." echo "Skipping Docker IPv6 configuration because host does not support IPv6."
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
echo "IPv6 configuration complete: ENABLE_IPV6=false"
sleep 2
return
fi fi
# now write into mailcow.conf or export docker_daemon_edit
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
LINE="ENABLE_IPV6=$DETECTED_IPV6"
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i "s/^ENABLE_IPV6=.*/$LINE/" "$MAILCOW_CONF" sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
else else
echo "$LINE" >> "$MAILCOW_CONF" echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
fi fi
else else
export IPV6_BOOL="$DETECTED_IPV6" export IPV6_BOOL=true
fi fi
echo "IPv6 configuration complete: ENABLE_IPV6=$DETECTED_IPV6" echo "IPv6 configuration complete: ENABLE_IPV6=true"
} }

View File

@@ -1,5 +1,26 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Ensure the script is run from the directory that contains a link of .env
# Resolve the directory this script lives in for consistent behavior when invoked from elsewhere
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" >/dev/null 2>&1 && pwd)"
# Ensure script is executed in the mailcow installation directory by checking for a .env symlink that points to mailcow.conf
if [ ! -L "${PWD}/.env" ]; then
echo -e "\e[33mPlease run this script from the mailcow installation directory.\e[0m"
echo -e " \e[36mcd /path/to/mailcow && ./generate_config.sh\e[0m"
exit 1
fi
# Verify the .env symlink points to a mailcow.conf file
env_target="$(readlink -f "${PWD}/.env" 2>/dev/null || true)"
if [ -z "$env_target" ] || [ "$(basename "$env_target")" != "mailcow.conf" ]; then
echo -e "\e[31mThe found .env symlink does not point to a mailcow.conf file.\e[0m"
echo -e "\e[33mPlease create a symbolic link .env -> mailcow.conf inside the mailcow directory and run this script there.\e[0m"
echo -e "\e[33mNote: 'ln -s mailcow.conf .env' will create the symlink even if mailcow.conf does not yet exist.\e[0m"
echo -e " \e[36mcd /path/to/mailcow && ln -s mailcow.conf .env && ./generate_config.sh\e[0m"
exit 1
fi
# Load mailcow Generic Scripts # Load mailcow Generic Scripts
source _modules/scripts/core.sh source _modules/scripts/core.sh
source _modules/scripts/ipv6_controller.sh source _modules/scripts/ipv6_controller.sh

View File

@@ -293,7 +293,7 @@ if ! ssh -o StrictHostKeyChecking=no \
-i "${REMOTE_SSH_KEY}" \ -i "${REMOTE_SSH_KEY}" \
${REMOTE_SSH_HOST} \ ${REMOTE_SSH_HOST} \
-p ${REMOTE_SSH_PORT} \ -p ${REMOTE_SSH_PORT} \
${SCRIPT_DIR}/../update.sh -f --gc ; then "cd \"${SCRIPT_DIR}/../\" && ./update.sh -f --gc" ; then
>&2 echo -e "\e[31m[ERR]\e[0m - Could not cleanup old images on remote" >&2 echo -e "\e[31m[ERR]\e[0m - Could not cleanup old images on remote"
fi fi

View File

@@ -3,6 +3,20 @@
############## Begin Function Section ############## ############## Begin Function Section ##############
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
MAILCOW_CONF="${SCRIPT_DIR}/mailcow.conf"
# Ensure the script is run from the directory that contains mailcow.conf
if [ ! -f "${PWD}/mailcow.conf" ]; then
if [ -f "${SCRIPT_DIR}/mailcow.conf" ]; then
echo -e "\e[33mPlease run this script directly from the mailcow installation directory:\e[0m"
echo -e " \e[36mcd ${SCRIPT_DIR} && ./update.sh\e[0m"
exit 1
else
echo -e "\e[31mmailcow.conf not found in current directory or script directory (\e[36m${SCRIPT_DIR}\e[31m).\e[0m"
echo -e "\e[33mRun this script directly from your mailcow installation directory.\e[0m"
exit 1
fi
fi
BRANCH="$(cd "${SCRIPT_DIR}" && git rev-parse --abbrev-ref HEAD)" BRANCH="$(cd "${SCRIPT_DIR}" && git rev-parse --abbrev-ref HEAD)"
MODULE_DIR="${SCRIPT_DIR}/_modules" MODULE_DIR="${SCRIPT_DIR}/_modules"
@@ -27,8 +41,6 @@ if [ "$(id -u)" -ne "0" ]; then
exit 1 exit 1
fi fi
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Run pre-update-hook # Run pre-update-hook
if [ -f "${SCRIPT_DIR}/pre_update_hook.sh" ]; then if [ -f "${SCRIPT_DIR}/pre_update_hook.sh" ]; then
bash "${SCRIPT_DIR}/pre_update_hook.sh" bash "${SCRIPT_DIR}/pre_update_hook.sh"