blob: a025ee1783ee2c6e5cb5659d04f19148df3fe181 [file] [log] [blame]
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +02001#!/usr/bin/env bash
2# _modules/scripts/ipv6_controller.sh
3# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
4# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
5
6# 1) Check if the host supports IPv6
7get_ipv6_support() {
8 # ---- helper: probe external IPv6 connectivity without DNS ----
9 _probe_ipv6_connectivity() {
10 # Use literal, always-on IPv6 echo responders (no DNS required)
11 local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111")
12 local ip rc=1
13
14 for ip in "${PROBE_IPS[@]}"; do
15 if command -v ping6 &>/dev/null; then
16 ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null
17 rc=$?
18 elif command -v ping &>/dev/null; then
19 ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null
20 rc=$?
21 else
22 rc=1
23 fi
24 [[ $rc -eq 0 ]] && return 0
25 done
26 return 1
27 }
28
29 if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then
30 DETECTED_IPV6=false
31 echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}"
32 return
33 fi
34
35 if ip -6 route show default 2>/dev/null | grep -qE '^default'; then
36 echo -e "${YELLOW}Default IPv6 route found – testing external IPv6 connectivity...${NC}"
37 if _probe_ipv6_connectivity; then
38 DETECTED_IPV6=true
39 echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
40 else
41 DETECTED_IPV6=false
42 echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
43 fi
44 return
45 fi
46
47 if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then
48 DETECTED_IPV6=false
49 echo -e "${YELLOW}Global IPv6 address present but no default route – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
50 return
51 fi
52
53 if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then
54 echo -e "${YELLOW}Only link-local IPv6 addresses found – testing external IPv6 connectivity...${NC}"
55 if _probe_ipv6_connectivity; then
56 DETECTED_IPV6=true
57 echo -e "External IPv6 connectivity available – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
58 else
59 DETECTED_IPV6=false
60 echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
61 fi
62 return
63 fi
64
65 DETECTED_IPV6=false
66 echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
67}
68
69# 2) Ensure Docker daemon.json has (or create) the required IPv6 settings
70docker_daemon_edit(){
71 DOCKER_DAEMON_CONFIG="/etc/docker/daemon.json"
72 DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
73 MISSING=()
74
75 _has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; }
76
77 if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then
78
79 # reject empty or whitespace-only file immediately
80 if [[ ! -s "$DOCKER_DAEMON_CONFIG" ]] || ! grep -Eq '[{}]' "$DOCKER_DAEMON_CONFIG"; then
81 echo -e "${RED}ERROR: $DOCKER_DAEMON_CONFIG exists but is empty or contains no JSON braces – please initialize it with valid JSON (e.g. {}).${NC}"
82 exit 1
83 fi
84
85 # Validate JSON if jq is present
86 if command -v jq &>/dev/null && ! jq empty "$DOCKER_DAEMON_CONFIG" &>/dev/null; then
87 echo -e "${RED}ERROR: Invalid JSON in $DOCKER_DAEMON_CONFIG – please correct manually.${NC}"
88 exit 1
89 fi
90
91 # Gather missing keys
92 ! _has_kv ipv6 true && MISSING+=("ipv6: true")
93
94 # For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines)
95 if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
96 ! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \
97 && MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"')
98 fi
99
100 # For Docker < 27, ip6tables needed and was tied to experimental in older releases
101 if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
102 _has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true")
103 ! _has_kv experimental true && MISSING+=("experimental: true")
104 fi
105
106 # Fix if needed
107 if ((${#MISSING[@]}>0)); then
108 echo -e "${MAGENTA}Your daemon.json is missing: ${YELLOW}${MISSING[*]}${NC}"
109 if [[ -n "$FORCE" ]]; then
110 ans=Y
111 else
112 read -p "Would you like to update $DOCKER_DAEMON_CONFIG now? [Y/n] " ans
113 ans=${ans:-Y}
114 fi
115
116 if [[ $ans =~ ^[Yy]$ ]]; then
117 cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak"
118 if command -v jq &>/dev/null; then
119 TMP=$(mktemp)
120 # Base filter: ensure ipv6 = true
121 JQ_FILTER='.ipv6 = true'
122
123 # Add fixed-cidr-v6 only for Docker < 28
124 if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
125 JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")'
126 fi
127
128 # Add ip6tables/experimental only for Docker < 27
129 if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
130 JQ_FILTER+=' | .ip6tables = true | .experimental = true'
131 fi
132
133 jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG"
134 echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}"
135 (command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
136 echo -e "${YELLOW}Docker restarted.${NC}"
137 else
138 echo -e "${RED}Please install jq or manually update daemon.json and restart Docker.${NC}"
139 exit 1
140 fi
141 else
142 echo -e "${YELLOW}User declined Docker update – please insert these changes manually:${NC}"
143 echo "${MISSING[*]}"
144 exit 1
145 fi
146 fi
147
148 else
149 # Create new daemon.json if missing
150 if [[ -n "$FORCE" ]]; then
151 ans=Y
152 else
153 read -p "$DOCKER_DAEMON_CONFIG not found. Create it with IPv6 settings? [Y/n] " ans
154 ans=${ans:-Y}
155 fi
156
157 if [[ $ans =~ ^[Yy]$ ]]; then
158 mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")"
159 if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
160 cat > "$DOCKER_DAEMON_CONFIG" <<EOF
161{
162 "ipv6": true,
163 "fixed-cidr-v6": "fd00:dead:beef:c0::/80",
164 "ip6tables": true,
165 "experimental": true
166}
167EOF
168 elif [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
169 cat > "$DOCKER_DAEMON_CONFIG" <<EOF
170{
171 "ipv6": true,
172 "fixed-cidr-v6": "fd00:dead:beef:c0::/80"
173}
174EOF
175 else
176 # Docker 28+: ipv6 works without fixed-cidr-v6
177 cat > "$DOCKER_DAEMON_CONFIG" <<EOF
178{
179 "ipv6": true
180}
181EOF
182 fi
183 echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
184 echo "Restarting Docker..."
185 (command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
186 echo "Docker restarted."
187 else
188 echo "User declined to create daemon.json – please manually merge the docker daemon with these configs:"
189 echo "${MISSING[*]}"
190 exit 1
191 fi
192 fi
193}
194
195# 3) Main wrapper for generate_config.sh and update.sh
196configure_ipv6() {
197 # detect manual override if mailcow.conf is present
198 if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
199 MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
200 elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
201 MANUAL_SETTING="$ENABLE_IPV6"
202 else
203 MANUAL_SETTING=""
204 fi
205
206 get_ipv6_support
207
208 # if user manually set it, check for mismatch
209 if [[ "$DETECTED_IPV6" != "true" ]]; then
210 if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
211 if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
212 sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
213 else
214 echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
215 fi
216 else
217 export IPV6_BOOL=false
218 fi
219 echo "Skipping Docker IPv6 configuration because host does not support IPv6."
220 echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
221 echo "IPv6 configuration complete: ENABLE_IPV6=false"
222 sleep 2
223 return
224 fi
225
226 docker_daemon_edit
227
228 if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
229 if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
230 sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
231 else
232 echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
233 fi
234 else
235 export IPV6_BOOL=true
236 fi
237
238 echo "IPv6 configuration complete: ENABLE_IPV6=true"
239}