mirror of
https://github.com/unraid/webgui.git
synced 2026-05-04 16:59:27 -05:00
90d05d04c8
- Purpose: backport the Docker fixed MAC assignment fix from master to 7.2. - Before: fixed MACs were handled through legacy container-level extra parameters or were lost during network reconnects. - Problem: Docker endpoint MAC assignment needs to happen on the selected network endpoint so bridge and custom networks keep the intended address. - Change: add a fixed MAC template field, normalize and migrate legacy extra parameters, and restore MACs during Docker network reconnects. - Implementation: store MyMAC in templates, build endpoint-aware --network arguments for container creation, and use Docker network connect API calls when restoring fixed MAC endpoints.
790 lines
26 KiB
Docker
Executable File
790 lines
26 KiB
Docker
Executable File
#!/bin/bash
|
|
#
|
|
# script: rc.docker
|
|
#
|
|
# Short-Description: Create lightweight, portable, self-sufficient containers.
|
|
# Description:
|
|
# Docker is an open-source project to easily create lightweight, portable,
|
|
# self-sufficient containers from any application. The same container that a
|
|
# developer builds and tests on a laptop can run at scale, in production, on
|
|
# VMs, bare metal, OpenStack clusters, public clouds and more.
|
|
#
|
|
# LimeTech - modified for Unraid OS
|
|
# Bergware - modified for Unraid OS, June 2025
|
|
|
|
DAEMON="Docker daemon"
|
|
UNSHARE="/usr/bin/unshare"
|
|
SYSTEM="/sys/class/net"
|
|
CONF6="/proc/sys/net/ipv6/conf"
|
|
STOCK="bond br eth wlan"
|
|
ACTIVE=$(ls --indicator-style=none $SYSTEM | awk "/^(${STOCK// /|})[0-9]/" ORS=' ')
|
|
NICS=$(ls --indicator-style=none $SYSTEM | awk '/^(eth|wlan)[0-9]+$/')
|
|
|
|
DOCKERD="dockerd"
|
|
DOCKER="/usr/bin/$DOCKERD"
|
|
DOCKER_PIDFILE="/var/run/$DOCKERD.pid"
|
|
DOCKER_LOG="/var/log/docker.log"
|
|
DOCKER_ROOT="/var/lib/docker"
|
|
DOCKER_CFG="/boot/config/docker.cfg"
|
|
DOCKER_TIMEOUT=$(awk -F'"' '/^DOCKER_TIMEOUT=/{print $2}' $DOCKER_CFG 2>/dev/null)
|
|
|
|
# network file references
|
|
INI=/var/local/emhttp/network.ini
|
|
STA=/var/local/emhttp/statics.ini
|
|
TMP=/var/tmp/network.tmp
|
|
|
|
# run & log functions
|
|
. /etc/rc.d/rc.runlog
|
|
|
|
# return active interface
|
|
active(){
|
|
if [[ -e $SYSTEM/${1/eth/br} ]]; then
|
|
echo ${1/eth/br}
|
|
elif [[ -e $SYSTEM/${1/eth/bond} ]]; then
|
|
echo ${1/eth/bond}
|
|
else
|
|
echo $1
|
|
fi
|
|
}
|
|
|
|
# wait for interface to go up
|
|
carrier(){
|
|
local n e
|
|
[[ -e $SYSTEM/$1 ]] && e=${2:-10} || return 1
|
|
for ((n=0; n<$e; n++)); do
|
|
[[ $(cat $SYSTEM/$1/carrier 2>/dev/null) == 1 ]] && return 0
|
|
[[ $e -gt 1 ]] && sleep 1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# add entry to watch list
|
|
add_entry(){
|
|
rm -f /var/tmp/${1%% *}.down
|
|
[[ -e $STA ]] && echo "$1" >>$STA
|
|
}
|
|
|
|
# delete enty from watch list
|
|
del_entry(){
|
|
touch /var/tmp/$1.down
|
|
[[ -e $STA ]] && sed -i "/^$1 .*/d" $STA
|
|
sleep 0.5
|
|
}
|
|
|
|
# initialize docker settings
|
|
docker_read_options(){
|
|
# determine active port name
|
|
PORT=$(active eth0)
|
|
[[ ! $(carrier $PORT) && $(carrier wlan0 1) ]] && PORT=wlan0
|
|
|
|
# Set defaults used by the docker daemon
|
|
if [[ -f $DOCKER_CFG ]]; then
|
|
for NIC in $NICS; do
|
|
[[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
|
|
CFG=($(grep -Pom2 "_SUBNET_|_${NIC^^}(_[0-9]+)?=" $DOCKER_CFG))
|
|
if [[ ${CFG[0]} == _SUBNET_ && -z ${CFG[1]} ]]; then
|
|
# interface has changed, update configuration
|
|
X=${NIC//[^0-9]/}
|
|
sed -ri "s/_(BR|BOND|ETH|WLAN)$X(_[0-9]+)?=/_${NIC^^}\2=/; s/(br|bond|eth|wlan)$X(\.[0-9]+)? /$NIC\2 /g" $DOCKER_CFG
|
|
fi
|
|
done
|
|
# Read (updated) Unraid docker configuration file
|
|
. $DOCKER_CFG
|
|
fi
|
|
|
|
# set storage driver to overlay2 if config value is found, otherwise fall back to native FS driver
|
|
if [[ $(awk -F'"' '/^DOCKER_BACKINGFS=/{print $2}' $DOCKER_CFG 2>/dev/null) == overlay2 ]]; then
|
|
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2"
|
|
else
|
|
BACKINGFS=$(findmnt --output FSTYPE --noheadings $DOCKER_ROOT)
|
|
if [[ $BACKINGFS == btrfs ]]; then
|
|
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=btrfs"
|
|
elif [[ $BACKINGFS == xfs ]]; then
|
|
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2"
|
|
elif [[ $BACKINGFS == zfs ]]; then
|
|
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=zfs"
|
|
fi
|
|
fi
|
|
|
|
# Less verbose logging by default
|
|
DOCKER_OPTS="--log-level=fatal $DOCKER_OPTS"
|
|
|
|
# Enable global docker LOG rotation
|
|
if [[ $DOCKER_LOG_ROTATION == yes ]]; then
|
|
[[ -z $DOCKER_LOG_SIZE ]] && DOCKER_LOG_SIZE=10m
|
|
[[ -z $DOCKER_LOG_FILES ]] && DOCKER_LOG_FILES=1
|
|
DOCKER_OPTS="--log-opt max-size=$DOCKER_LOG_SIZE --log-opt max-file=$DOCKER_LOG_FILES $DOCKER_OPTS"
|
|
fi
|
|
|
|
# Adjust MTU size if non-default
|
|
MTU=$(ip link show $PORT | grep -Po 'mtu \K\d+')
|
|
[[ -n $MTU && $MTU -ne 1500 ]] && DOCKER_OPTS="--mtu=$MTU $DOCKER_OPTS"
|
|
|
|
# Enable IPv6 for docker bridge network
|
|
if [[ -n $(ip -6 route show to default dev $PORT) ]]; then
|
|
DOCKER0='fd17::/64'
|
|
DOCKER_OPTS="--ipv6 --fixed-cidr-v6=$DOCKER0 $DOCKER_OPTS"
|
|
IPV6_FORWARD=${IPV6_FORWARD:=accept}
|
|
# create IPv6 NAT rule for docker0
|
|
[[ -z $(ip6tables -t nat -S | grep -o "$DOCKER0") ]] && run ip6tables -t nat -A POSTROUTING -s $DOCKER0 ! -o docker0 -j MASQUERADE
|
|
else
|
|
# ipv6 disabled
|
|
[[ -d $CONF6/docker0 ]] && echo 1 > $CONF6/docker0/disable_ipv6
|
|
fi
|
|
|
|
export DOCKER_RAMDISK=true
|
|
}
|
|
|
|
# Get docker daemon PID (if existing)
|
|
docker_pid(){
|
|
cat $DOCKER_PIDFILE 2>/dev/null
|
|
}
|
|
|
|
# Verify if docker daemon running
|
|
docker_running(){
|
|
sleep 0.1
|
|
[[ -S /var/run/docker.sock ]] || return 1
|
|
[[ $(docker info 2>&1) =~ "Cannot connect to the Docker daemon" ]] && return 1 || return 0
|
|
}
|
|
|
|
# Wait max 30s to daemon start
|
|
wait_daemon(){
|
|
for n in {1..30}; do
|
|
if docker_running; then return 0; else sleep 1; fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# All existing containers
|
|
all_containers(){
|
|
docker ps -a --format='{{.Names}}' 2>/dev/null
|
|
}
|
|
|
|
# Running containers
|
|
running_containers(){
|
|
docker ps --format='{{.Names}} {{.Labels}}' 2>/dev/null | grep 'net.unraid.docker.managed=' | awk '{print $1}'
|
|
}
|
|
|
|
# Network driver
|
|
driver(){
|
|
# user selection when bridge is enabled
|
|
if [[ -z $DOCKER_NETWORK_TYPE ]]; then
|
|
DETACH='ipvlan'
|
|
ATTACH='macvlan'
|
|
MODE='bridge'
|
|
else
|
|
DETACH='macvlan'
|
|
ATTACH='ipvlan'
|
|
MODE='l2 bridge'
|
|
fi
|
|
# fixed selection when bridge is disabled
|
|
if [[ $1 != br ]]; then
|
|
DETACH='ipvlan'
|
|
ATTACH='macvlan'
|
|
MODE='bridge'
|
|
fi
|
|
# wlan0 has forced ipvlan
|
|
[[ $1 == wlan && $2 == forced ]] && ATTACH=ipvlan
|
|
}
|
|
|
|
# Custom networks
|
|
network(){
|
|
docker network ls --filter driver="$1" --format='{{.Name}}' 2>/dev/null | grep -P "^[a-z]+$2(\$|\.)" | tr '\n' ' '
|
|
}
|
|
|
|
# Is container running?
|
|
container_running(){
|
|
local CONTAINER
|
|
for CONTAINER in $(running_containers); do
|
|
[[ $CONTAINER == $1 ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Does the container exist?
|
|
container_exist(){
|
|
local CONTAINER
|
|
for CONTAINER in $(all_containers); do
|
|
[[ $CONTAINER == $1 ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
container_paths_exist(){
|
|
local CONTAINER=$1
|
|
while IFS=| read -r HOSTPATH; do
|
|
# Make sure hostpath exists
|
|
if [[ ! -e "$HOSTPATH" ]]; then
|
|
log "container \"$CONTAINER\" hostpath \"$HOSTPATH\" does not exist"
|
|
return 1
|
|
fi
|
|
done <<< $(docker inspect --format='{{range .Mounts}}{{.Source}}|{{end}}' $CONTAINER)
|
|
return 0
|
|
}
|
|
|
|
read_dom(){
|
|
local IFS=\>
|
|
read -d \< ENTITY CONTENT
|
|
}
|
|
|
|
netrestore_add(){
|
|
local NETWORK=$1
|
|
local CONTAINER=$2
|
|
local IPS=$3
|
|
local MAC=$4
|
|
local ENTRY="${CONTAINER}|${IPS}|${MAC}"
|
|
if [[ -n ${NETRESTORE[$NETWORK]} ]]; then
|
|
NETRESTORE[$NETWORK]+=$'\n'
|
|
fi
|
|
NETRESTORE[$NETWORK]+=$ENTRY
|
|
}
|
|
|
|
netrestore_connect(){
|
|
local NETWORK=$1
|
|
local CONTAINER=$2
|
|
local MY_TT=$3
|
|
local MY_MAC=$4
|
|
local MY_IP=
|
|
local MY_IPV4=
|
|
local MY_IPV6=
|
|
local IP=
|
|
local IPAM_JSON=
|
|
local ENDPOINT_JSON=
|
|
local CONNECT_JSON=
|
|
local CODE=
|
|
local BODY=
|
|
local ENDPOINT_ID=
|
|
local ENDPOINT_MAC=
|
|
local OUT=
|
|
|
|
container_exist "$CONTAINER" || return 0
|
|
docker network inspect "$NETWORK" >/dev/null 2>&1 || return 0
|
|
|
|
if [[ -n ${REBUILD_CONTAINERS[$CONTAINER]} && ${PRIMARY_NETWORK[$CONTAINER]} == $NETWORK ]]; then
|
|
log "rebuild container $CONTAINER"
|
|
if OUT=$(/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container "$CONTAINER" 2>&1); then
|
|
unset REBUILD_CONTAINERS[$CONTAINER]
|
|
return 0
|
|
fi
|
|
log "failed to rebuild container $CONTAINER: $OUT"
|
|
return 1
|
|
fi
|
|
|
|
for IP in ${MY_TT//;/ }; do
|
|
[[ -n $IP ]] || continue
|
|
if [[ $IP =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
|
MY_IPV4=$IP
|
|
MY_IP="$MY_IP --ip $IP"
|
|
elif [[ $IP =~ : ]]; then
|
|
MY_IPV6=$IP
|
|
MY_IP="$MY_IP --ip6 $IP"
|
|
else
|
|
log "skipping invalid stored IP for $CONTAINER on network $NETWORK: $IP"
|
|
fi
|
|
done
|
|
|
|
ENDPOINT_ID=$(docker inspect --format="{{with index .NetworkSettings.Networks \"$NETWORK\"}}{{.EndpointID}}{{end}}" "$CONTAINER" 2>/dev/null)
|
|
if [[ -n $ENDPOINT_ID ]]; then
|
|
[[ -n $MY_MAC ]] || return 0
|
|
ENDPOINT_MAC=$(docker inspect --format="{{with index .NetworkSettings.Networks \"$NETWORK\"}}{{.MacAddress}}{{end}}" "$CONTAINER" 2>/dev/null)
|
|
[[ ${ENDPOINT_MAC,,} == ${MY_MAC,,} ]] && return 0
|
|
log "reconnecting $CONTAINER to network $NETWORK to restore MAC $MY_MAC"
|
|
if ! OUT=$(docker network disconnect -f "$NETWORK" "$CONTAINER" 2>&1); then
|
|
log "failed to disconnect $CONTAINER from network $NETWORK: $OUT"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -n $MY_MAC ]]; then
|
|
[[ -n $MY_IPV4 ]] && IPAM_JSON="\"IPv4Address\":\"$MY_IPV4\""
|
|
[[ -n $MY_IPV6 ]] && IPAM_JSON="${IPAM_JSON:+$IPAM_JSON,}\"IPv6Address\":\"$MY_IPV6\""
|
|
ENDPOINT_JSON="\"MacAddress\":\"$MY_MAC\""
|
|
[[ -n $IPAM_JSON ]] && ENDPOINT_JSON="\"IPAMConfig\":{$IPAM_JSON},$ENDPOINT_JSON"
|
|
CONNECT_JSON="{\"Container\":\"$CONTAINER\",\"EndpointConfig\":{$ENDPOINT_JSON}}"
|
|
OUT=$(curl --unix-socket /var/run/docker.sock -sS -w $'\n%{http_code}' -X POST -H "Content-Type: application/json" --data "$CONNECT_JSON" "http://localhost/networks/$NETWORK/connect" 2>&1)
|
|
CODE=${OUT##*$'\n'}
|
|
BODY=${OUT%$'\n'$CODE}
|
|
if [[ $CODE != 2* ]]; then
|
|
log "failed to connect $CONTAINER to network $NETWORK: $BODY"
|
|
return 1
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
log "connecting $CONTAINER to network $NETWORK"
|
|
if ! OUT=$(docker network connect $MY_IP $NETWORK $CONTAINER 2>&1); then
|
|
log "failed to connect $CONTAINER to network $NETWORK: $OUT"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
container_add_route(){
|
|
local CT=($(docker inspect --format='{{.State.Pid}} {{.NetworkSettings.Networks}}' $1))
|
|
local PID=${CT[0]}
|
|
local NET=${CT[1]#*[}
|
|
local LAN=${NET%:*}
|
|
if [[ $PID -gt 0 && "eth0 br0 bond0 wlan0" =~ $LAN ]]; then
|
|
local THISIP=$(ip -4 -br addr show scope global primary dev $LAN | awk '{print $3;exit}')
|
|
for CFG in /etc/wireguard/wg*.cfg ; do
|
|
local NETWORK=$(ip -4 route show to $THISIP dev $LAN | awk '{print $1;exit}')
|
|
[[ -n $NETWORK ]] && nsenter -n -t $PID ip -4 route add $NETWORK via ${THISIP%/*} dev $LAN 2>/dev/null
|
|
done
|
|
fi
|
|
}
|
|
|
|
docker_network_start(){
|
|
log "Starting network..."
|
|
# ensure br_netfilter modules is loaded, re: https://github.com/moby/moby/issues/48948
|
|
modprobe br_netfilter
|
|
# create list of possible custom networks
|
|
EXCLUDE=; INCLUDE=$(ls --indicator-style=none $SYSTEM | awk '/^br[0-9]+/' ORS=' ')
|
|
while IFS=$'\n' read -r NETWORK; do
|
|
if [[ ${NETWORK:0:4} == bond ]]; then
|
|
if [[ $INCLUDE =~ "${NETWORK/bond/br} " ]]; then
|
|
EXCLUDE="${EXCLUDE}${NETWORK} ${NETWORK/bond/eth} "
|
|
else
|
|
INCLUDE="${INCLUDE}${NETWORK} "
|
|
EXCLUDE="${EXCLUDE}${NETWORK/bond/eth} "
|
|
fi
|
|
elif [[ ${NETWORK:0:3} == eth ]]; then
|
|
if [[ $INCLUDE =~ "${NETWORK/eth/br} " || $INCLUDE =~ "${NETWORK/eth/bond} " ]]; then
|
|
[[ $EXCLUDE =~ "$NETWORK " ]] || EXCLUDE="${EXCLUDE}${NETWORK} "
|
|
else
|
|
INCLUDE="${INCLUDE}${NETWORK} "
|
|
fi
|
|
else
|
|
INCLUDE="${INCLUDE}${NETWORK} "
|
|
fi
|
|
done <<< $(ls --indicator-style=none $SYSTEM | grep -P '^(bond|eth|wlan)[0-9]+')
|
|
if ! docker_running; then return 1; fi
|
|
# get container settings for custom networks to reconnect later
|
|
declare -A NETRESTORE PRIMARY_NETWORK REBUILD_CONTAINERS USED_SUBNETS4 USED_SUBNETS6 RESTORED_NETWORKS
|
|
for CONTAINER in $(docker container ls -a --format='{{.Names}}'); do
|
|
# the file case (due to fat32) might be different so use find to match
|
|
XMLFILE=$(find /boot/config/plugins/dockerMan/templates-user -maxdepth 1 -iname my-${CONTAINER}.xml)
|
|
if [[ -n $XMLFILE ]]; then
|
|
REBUILD=
|
|
MAIN=
|
|
# update custom network reference (if changed)
|
|
for NIC in $NICS; do
|
|
[[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
|
|
X=${NIC//[^0-9]/}
|
|
REF=$(grep -Pom1 "<Network>\K(br|bond|eth|wlan)$X" $XMLFILE)
|
|
if [[ $X == 0 ]] && ! carrier $NIC 1; then
|
|
continue
|
|
fi
|
|
[[ $X == 0 && $NIC != wlan0 ]] && MAIN=$NIC
|
|
[[ $NIC == wlan0 && -n $MAIN ]] && continue
|
|
if [[ -n $REF && $REF != $NIC ]]; then
|
|
sed -ri "s/<Network>(br|bond|eth|wlan)$X(\.[0-9]+)?<\/Network>/<Network>$NIC\2<\/Network>/" $XMLFILE
|
|
REBUILD=1
|
|
fi
|
|
done
|
|
MY_NETWORK= MY_IP= MY_MAC= XML_MAC= TEMPLATE_MAC= CUSTOM_PRIMARY=
|
|
while read_dom; do
|
|
[[ $ENTITY == Network ]] && MY_NETWORK=$CONTENT
|
|
[[ $ENTITY == MyIP ]] && MY_IP=${CONTENT// /,} && MY_IP=$(echo "$MY_IP" | tr -s "," ";")
|
|
[[ $ENTITY == MyMAC ]] && XML_MAC=${CONTENT// /}
|
|
done <$XMLFILE
|
|
# only restore valid networks
|
|
if [[ -n $MY_NETWORK ]]; then
|
|
[[ $MY_NETWORK =~ ^(br|bond|eth|wlan)[0-9]+(\.[0-9]+)?$ ]] && CUSTOM_PRIMARY=1
|
|
TEMPLATE_MAC=$(sed -nE 's@.*<ExtraParams>.*--mac-address(=|[[:space:]]+)([^ <]+).*@\2@p' "$XMLFILE" | head -n1)
|
|
if [[ -n $XML_MAC ]]; then
|
|
MY_MAC=$XML_MAC
|
|
else
|
|
MY_MAC=$(docker inspect --format="{{with index .NetworkSettings.Networks \"$MY_NETWORK\"}}{{.MacAddress}}{{end}}" $CONTAINER 2>/dev/null)
|
|
[[ -n $MY_MAC ]] || MY_MAC=$TEMPLATE_MAC
|
|
fi
|
|
netrestore_add "$MY_NETWORK" "$CONTAINER" "$MY_IP" "$MY_MAC"
|
|
PRIMARY_NETWORK[$CONTAINER]=$MY_NETWORK
|
|
[[ -n $REBUILD || (-z $XML_MAC && -n $TEMPLATE_MAC && -n $CUSTOM_PRIMARY) ]] && REBUILD_CONTAINERS[$CONTAINER]=1
|
|
fi
|
|
fi
|
|
# restore user defined networks
|
|
USER_NETWORKS=$(docker inspect --format='{{range $key,$value:=.NetworkSettings.Networks}}{{printf "%s;%s;" $key $value.MacAddress}}{{if $value.IPAMConfig}}{{if $value.IPAMConfig.IPv4Address}}{{$value.IPAMConfig.IPv4Address}}{{end}}{{if $value.IPAMConfig.IPv6Address}},{{$value.IPAMConfig.IPv6Address}}{{end}}{{end}}{{println}}{{end}}' $CONTAINER)
|
|
while IFS=';' read -r USER_NETWORK USER_MAC USER_IP; do
|
|
USER_IP=${USER_IP//,/;}
|
|
if [[ -n $USER_NETWORK && $USER_NETWORK != $MY_NETWORK ]]; then
|
|
LABEL=${USER_NETWORK//[0-9.]/}
|
|
IF_NO_PARTS=${USER_NETWORK#"$LABEL"}
|
|
IF_NO=${IF_NO_PARTS%%.*}
|
|
if [[ $STOCK =~ $LABEL && $IF_NO -gt 0 ]]; then
|
|
USER_NETWORK=$USER_NETWORK
|
|
elif [[ $STOCK =~ $LABEL && $LABEL != ${PORT:0:-1} ]]; then
|
|
USER_NETWORK=${USER_NETWORK/$LABEL/${PORT:0:-1}}
|
|
fi
|
|
log "container $CONTAINER has an additional network that will be restored: $USER_NETWORK"
|
|
netrestore_add "$USER_NETWORK" "$CONTAINER" "$USER_IP" "$USER_MAC"
|
|
fi
|
|
done <<< "$USER_NETWORKS"
|
|
done
|
|
# detach custom networks
|
|
for NIC in $NICS; do
|
|
[[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
|
|
X=${NIC//[^0-9]/}
|
|
driver ${NIC//[0-9]/}
|
|
for NETWORK in $(network $DETACH $X); do
|
|
[[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK &>/dev/null
|
|
done
|
|
# get existing custom networks
|
|
for NETWORK in $(network $ATTACH $X); do
|
|
if [[ $STOCK =~ ${NETWORK%%[0-9]*} ]]; then
|
|
[[ $EXCLUDE =~ "$NETWORK " || ! $ACTIVE =~ "$NETWORK " ]] && docker network rm $NETWORK &>/dev/null
|
|
else
|
|
[[ $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK &>/dev/null
|
|
fi
|
|
done
|
|
NETWORKS=$(network $ATTACH $X)
|
|
done
|
|
# add or remove custom network
|
|
for NETWORK in $INCLUDE; do
|
|
if [[ ! $DOCKER_CUSTOM_NETWORKS =~ "$NETWORK " ]]; then
|
|
# automatic assignment
|
|
AUTO=${NETWORK/./_}
|
|
AUTO=DOCKER_AUTO_${AUTO^^}
|
|
if [[ ${!AUTO} == no ]]; then
|
|
[[ $NETWORKS =~ "$NETWORK " ]] && docker network rm $NETWORK &>/dev/null
|
|
continue
|
|
fi
|
|
# add auto defined networks
|
|
SUBNET=; GATEWAY=; SERVER=; RANGE=;
|
|
[[ -z ${!AUTO} || ${!AUTO} =~ "4" ]] && IPV4=$(ip -4 -br addr show scope global primary dev $NETWORK | awk '{print $3;exit}') || IPV4=
|
|
if [[ -n $IPV4 ]]; then
|
|
SUBNET=$(ip -4 route show dev $NETWORK | sort | awk -v ORS=" " '$1 !~ /^default/ {print $1}' | sed 's/ $//')
|
|
GATEWAY=$(ip -4 route show to default dev $NETWORK | awk '{print $3;exit}')
|
|
SERVER=${IPV4%/*}
|
|
DHCP=${NETWORK/./_}
|
|
DHCP=DOCKER_DHCP_${DHCP^^}
|
|
RANGE=${!DHCP}
|
|
fi
|
|
SUBNET6=; GATEWAY6=;
|
|
# get IPv6 address - ignore any /128 networks
|
|
[[ -z ${!AUTO} || ${!AUTO} =~ "6" ]] && IPV6=$(ip -6 -br addr show scope global primary -deprecated dev $NETWORK | awk '{print $3;exit}') || IPV6=
|
|
if [[ -n $IPV6 ]]; then
|
|
SUBNET6=$(ip -6 route show dev $NETWORK | sort | awk -v ORS=" " '$1 !~ /^(default|fe80)/ {print $1}' | sed 's/ $//')
|
|
GATEWAY6=$(ip -6 route show to default dev $NETWORK | awk '{print $3;exit}')
|
|
fi
|
|
else
|
|
# add user defined networks
|
|
IPV4=
|
|
IPV6=
|
|
DEVICE=${NETWORK/./_}
|
|
DEVICE=${DEVICE^^}
|
|
SUBNET=DOCKER_SUBNET_$DEVICE
|
|
SUBNET=${!SUBNET}
|
|
GATEWAY=DOCKER_GATEWAY_$DEVICE
|
|
GATEWAY=${!GATEWAY}
|
|
SERVER=
|
|
RANGE=DOCKER_RANGE_$DEVICE
|
|
RANGE=${!RANGE}
|
|
SUBNET6=DOCKER_SUBNET6_$DEVICE
|
|
SUBNET6=${!SUBNET6}
|
|
GATEWAY6=DOCKER_GATEWAY6_$DEVICE
|
|
GATEWAY6=${!GATEWAY6}
|
|
fi
|
|
SKIP_NETWORK=
|
|
for CANDIDATE in $SUBNET; do
|
|
if [[ -n ${USED_SUBNETS4[$CANDIDATE]} ]]; then
|
|
log "skipping network $NETWORK: IPv4 subnet $CANDIDATE is already used by ${USED_SUBNETS4[$CANDIDATE]}"
|
|
SKIP_NETWORK=1
|
|
break
|
|
fi
|
|
done
|
|
if [[ -z $SKIP_NETWORK ]]; then
|
|
for CANDIDATE in $SUBNET6; do
|
|
if [[ -n ${USED_SUBNETS6[$CANDIDATE]} ]]; then
|
|
log "skipping network $NETWORK: IPv6 subnet $CANDIDATE is already used by ${USED_SUBNETS6[$CANDIDATE]}"
|
|
SKIP_NETWORK=1
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
[[ -n $SKIP_NETWORK ]] && continue
|
|
# set parameters for custom network creation
|
|
[[ -n $SUBNET ]] && SET4=1 || SET4=0
|
|
[[ -n $SUBNET6 ]] && SET6=1 || SET6=0
|
|
if [[ $SET4 == 1 ]]; then
|
|
SUBNET="--subnet=${SUBNET// / --subnet=}"
|
|
[[ -n $GATEWAY ]] && GATEWAY="--gateway=$GATEWAY"
|
|
[[ -n $SERVER ]] && SERVER="--aux-address=server=$SERVER"
|
|
[[ -n $RANGE ]] && RANGE="--ip-range=$RANGE"
|
|
else
|
|
GATEWAY=
|
|
SERVER=
|
|
RANGE=
|
|
fi
|
|
if [[ $SET6 == 1 ]]; then
|
|
SUBNET6="--ipv6 --subnet=${SUBNET6// / --ipv6 --subnet=}"
|
|
[[ -n $GATEWAY6 && ${GATEWAY6:0:4} != fe80 ]] && GATEWAY6="--gateway=$GATEWAY6" || GATEWAY6=
|
|
else
|
|
GATEWAY6=
|
|
fi
|
|
if [[ $SET4 == 1 || $SET6 == 1 ]]; then
|
|
TYPE=${NETWORK//[0-9.]/}
|
|
driver $TYPE forced
|
|
if [[ $TYPE == br || $TYPE == wlan ]]; then
|
|
VHOST=$NETWORK
|
|
else
|
|
[[ -n $IPV4 && $DOCKER_ALLOW_ACCESS == yes ]] && VHOST=vhost${NETWORK//[^0-9.]/} || VHOST=$NETWORK
|
|
fi
|
|
# delete and recreate unconditionally
|
|
log "Processing... $NETWORK"
|
|
docker network rm $NETWORK &>/dev/null
|
|
docker network create -d $ATTACH $SUBNET $GATEWAY $SERVER $RANGE $SUBNET6 $GATEWAY6 -o parent=$VHOST $NETWORK | xargs docker network inspect -f "created network $ATTACH {{.Name}} with subnets: {{range .IPAM.Config}}{{.Subnet}}; {{end}}" 2>/dev/null | log
|
|
for CANDIDATE in ${SUBNET//--subnet=/ }; do
|
|
[[ -n $CANDIDATE ]] && USED_SUBNETS4[$CANDIDATE]=$NETWORK
|
|
done
|
|
for CANDIDATE in ${SUBNET6//--ipv6 / }; do
|
|
[[ $CANDIDATE == --subnet=* ]] && USED_SUBNETS6[${CANDIDATE#--subnet=}]=$NETWORK
|
|
done
|
|
# connect containers to this new network
|
|
while IFS='|' read -r CONTAINER MY_TT MY_MAC; do
|
|
[[ -n $CONTAINER ]] || continue
|
|
netrestore_connect "$NETWORK" "$CONTAINER" "$MY_TT" "$MY_MAC"
|
|
done <<< "${NETRESTORE[$NETWORK]}"
|
|
RESTORED_NETWORKS[$NETWORK]=1
|
|
# hack to let containers talk to host
|
|
if [[ $TYPE == br ]]; then
|
|
SHIM=shim-$NETWORK
|
|
if [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]]; then
|
|
# create shim interface
|
|
if [[ ! -e $SYSTEM/$SHIM ]]; then
|
|
run ip link add link $NETWORK name $SHIM type $ATTACH mode $MODE
|
|
run ip link set $SHIM up
|
|
fi
|
|
# disable IPv6 on shim interface
|
|
echo 1 >$CONF6/$SHIM/disable_ipv6
|
|
run ip -6 addr flush dev $SHIM
|
|
# copy parent IPv4 address to shim interface
|
|
run ip -4 addr add $IPV4 dev $SHIM metric 0
|
|
add_entry "$SHIM $IPV4 metric 0"
|
|
GW4=$(ip -4 route show to default dev $NETWORK | awk '{print $3;exit}')
|
|
if [[ -n $GW4 ]]; then
|
|
run ip -4 route add default via $GW4 dev $SHIM metric 0
|
|
add_entry "$SHIM GW4 default via $GW4 metric 0"
|
|
fi
|
|
log "created network $SHIM for host access"
|
|
elif [[ -e $SYSTEM/$SHIM ]]; then
|
|
# remove shim interface assignment
|
|
del_entry $SHIM
|
|
run ip -4 addr del $IPV4 dev $SHIM metric 0
|
|
fi
|
|
else
|
|
if [[ $TYPE == wlan ]]; then
|
|
VHOST=shim-$NETWORK
|
|
else
|
|
VHOST=vhost${NETWORK//[^0-9.]/}
|
|
fi
|
|
if [[ -n $IPV4 && $DOCKER_ALLOW_ACCESS == yes ]]; then
|
|
# disable IPv6 on vhost interface
|
|
echo 1 >$CONF6/$VHOST/disable_ipv6
|
|
run ip -6 addr flush dev $VHOST
|
|
# copy parent IPv4 address to vhost interface
|
|
run ip -4 addr add $IPV4 dev $VHOST metric 0
|
|
add_entry "$VHOST $IPV4 metric 0"
|
|
GW4=$(ip -4 route show to default dev $NETWORK | awk '{print $3;exit}')
|
|
if [[ -n $GW4 ]]; then
|
|
run ip -4 route add default via $GW4 dev $VHOST metric 0
|
|
add_entry "$VHOST GW4 default via $GW4 metric 0"
|
|
fi
|
|
log "created network $VHOST for host access"
|
|
elif [[ -n $IPV4 && -e $SYSTEM/$VHOST ]]; then
|
|
# remove vhost interface assignment
|
|
del_entry $VHOST
|
|
run ip -4 addr del $IPV4 dev $VHOST metric 0
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
for NETWORK in "${!NETRESTORE[@]}"; do
|
|
[[ -n ${RESTORED_NETWORKS[$NETWORK]} ]] && continue
|
|
while IFS='|' read -r CONTAINER MY_TT MY_MAC; do
|
|
[[ -n $CONTAINER ]] || continue
|
|
netrestore_connect "$NETWORK" "$CONTAINER" "$MY_TT" "$MY_MAC"
|
|
done <<< "${NETRESTORE[$NETWORK]}"
|
|
done
|
|
# # create IPv6 forward accept rule
|
|
# if [[ $IPV6_FORWARD == accept ]]; then
|
|
# ip6tables -P FORWARD ACCEPT
|
|
# log "created forward accept rule for IPv6 network"
|
|
# fi
|
|
log "Network started."
|
|
}
|
|
|
|
docker_network_stop(){
|
|
log "Stopping network..."
|
|
if ! docker_running; then return 1; fi
|
|
# Read docker configuration file
|
|
[[ -f $DOCKER_CFG ]] && . $DOCKER_CFG
|
|
for NIC in $NICS; do
|
|
[[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
|
|
driver ${NIC//[0-9]/} forced
|
|
for NETWORK in $(network $ATTACH ${NIC//[^0-9]/}); do
|
|
IPV4=$(ip -4 -br addr show scope global primary dev $NETWORK | awk '{print $3;exit}')
|
|
[[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK &>/dev/null
|
|
TYPE=${NETWORK//[0-9.]/}
|
|
if [[ $TYPE == br || $TYPE == wlan ]]; then
|
|
SHIM=shim-$NETWORK
|
|
if [[ -e $SYSTEM/$SHIM ]]; then
|
|
del_entry $SHIM
|
|
run ip -4 addr del $IPV4 dev $SHIM metric 0
|
|
fi
|
|
else
|
|
VHOST=vhost${NETWORK//[^0-9.]/}
|
|
if [[ -e $SYSTEM/$VHOST ]]; then
|
|
del_entry $VHOST
|
|
run ip -4 addr del $IPV4 dev $VHOST metric 0
|
|
fi
|
|
fi
|
|
done
|
|
done
|
|
log "Network stopped."
|
|
}
|
|
|
|
docker_container_start(){
|
|
log "Starting containers..."
|
|
local CONTAINER
|
|
if ! docker_running; then return 1; fi
|
|
if [[ -f $DOCKER_ROOT/unraid-autostart ]]; then
|
|
while read -r CONTAINER; do
|
|
CONTAINER=($CONTAINER)
|
|
WAIT=${CONTAINER[1]}
|
|
if container_exist $CONTAINER && ! container_running $CONTAINER && container_paths_exist $CONTAINER; then
|
|
OUT=$(docker start $CONTAINER 2>&1)
|
|
if [[ $OUT =~ "Error:" ]]; then
|
|
log "$CONTAINER: $OUT" &
|
|
else
|
|
container_add_route $CONTAINER
|
|
log "$CONTAINER: started successfully!" &
|
|
if [[ $WAIT -gt 0 ]]; then
|
|
log "$CONTAINER: wait $WAIT seconds" &
|
|
sleep $WAIT
|
|
fi
|
|
fi
|
|
fi
|
|
done <$DOCKER_ROOT/unraid-autostart
|
|
fi
|
|
log "Containers started."
|
|
}
|
|
|
|
docker_container_stop(){
|
|
log "Stopping containers..."
|
|
if ! docker_running; then return 1; fi
|
|
[[ -n $(running_containers) ]] && docker stop --time=${DOCKER_TIMEOUT:-10} $(running_containers) >/dev/null
|
|
log "Unraid managed containers stopped."
|
|
}
|
|
|
|
docker_service_start(){
|
|
log "Starting $DAEMON..."
|
|
local REPLY
|
|
[[ -x $DOCKER ]] && REPLY= || REPLY="Failed"
|
|
if [[ -z $REPLY ]]; then
|
|
if ! mountpoint $DOCKER_ROOT &>/dev/null; then
|
|
REPLY="No image mounted at $DOCKER_ROOT"
|
|
elif docker_running; then
|
|
REPLY="Already started"
|
|
fi
|
|
fi
|
|
if [[ -z $REPLY ]]; then
|
|
# If there is an old PID file (no docker running), clean it up:
|
|
if [[ -r $DOCKER_PIDFILE ]]; then
|
|
if ! docker_running; then
|
|
rm -f $DOCKER_PIDFILE
|
|
fi
|
|
fi
|
|
nohup $UNSHARE --propagation slave -- $DOCKER -p $DOCKER_PIDFILE $DOCKER_OPTS >>$DOCKER_LOG 2>&1 &
|
|
wait_daemon
|
|
# after docket started, continue to accept non-docker IPv6 traffic on br0
|
|
ip6tables -P FORWARD ACCEPT
|
|
# log "created forward accept rule for IPv6 network"
|
|
if docker_running; then REPLY="Started"; else REPLY="Failed"; fi
|
|
fi
|
|
log "$DAEMON... $REPLY."
|
|
}
|
|
|
|
docker_service_stop(){
|
|
log "Stopping $DAEMON..."
|
|
local REPLY
|
|
# If there is no PID file, ignore this request...
|
|
if [[ -r $DOCKER_PIDFILE ]]; then
|
|
# Try to stop dockerd gracefully
|
|
kill $(docker_pid) 2>/dev/null
|
|
# show waiting message
|
|
echo "Waiting 30 seconds for $DAEMON to die."
|
|
TIMER=30
|
|
# must ensure daemon has exited
|
|
while [[ $TIMER -gt 0 ]]; do
|
|
sleep 1
|
|
if [[ $(ps -p $(docker_pid) -o comm= 2>/dev/null) != $DOCKERD ]]; then
|
|
rm -f $DOCKER_PIDFILE
|
|
# tear down the bridge
|
|
if [[ -e $SYSTEM/docker0 ]]; then
|
|
run ip link set docker0 down
|
|
run ip link del docker0
|
|
fi
|
|
REPLY="Stopped"
|
|
# signal successful stop
|
|
TIMER=-1
|
|
else
|
|
((TIMER--))
|
|
fi
|
|
done
|
|
if [[ $TIMER -eq 0 ]]; then
|
|
log "Error: process will not die!"
|
|
# Send SIGKILL to dockerd
|
|
kill -SIGKILL $(docker_pid) 2>/dev/null
|
|
# Remove .sock and .pid
|
|
rm -f /var/run/docker.sock $DOCKER_PIDFILE
|
|
REPLY="Killed"
|
|
fi
|
|
else
|
|
REPLY="Already stopped"
|
|
fi
|
|
log "$DAEMON... $REPLY."
|
|
}
|
|
|
|
docker_status(){
|
|
if docker_running; then
|
|
echo "$DAEMON is currently running."
|
|
else
|
|
echo "$DAEMON is not running."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
case "$1" in
|
|
'start')
|
|
docker_read_options
|
|
docker_service_start
|
|
docker_network_start
|
|
docker_container_start &>/dev/null &
|
|
;;
|
|
'stop')
|
|
docker_container_stop
|
|
docker_network_stop
|
|
docker_service_stop
|
|
;;
|
|
'force_stop')
|
|
docker_container_stop
|
|
docker_service_stop
|
|
;;
|
|
'restart')
|
|
docker_container_stop
|
|
docker_network_stop
|
|
docker_service_stop
|
|
sleep 1
|
|
docker_read_options
|
|
docker_service_start
|
|
docker_network_start
|
|
docker_container_start &>/dev/null &
|
|
;;
|
|
'status')
|
|
docker_status
|
|
;;
|
|
*)
|
|
echo "Usage: $BASENAME start|stop|force_stop|restart|status"
|
|
exit 1
|
|
esac
|
|
exit 0
|