Skip to content

Instantly share code, notes, and snippets.

@troykelly
Last active September 30, 2024 07:13
Show Gist options
  • Save troykelly/c9537a5cd381d4da6c21e1ae59cbecc9 to your computer and use it in GitHub Desktop.
Save troykelly/c9537a5cd381d4da6c21e1ae59cbecc9 to your computer and use it in GitHub Desktop.
Docker IPv6 Support on Debian 12

Install Docker on Debian 12 Headless Machines

This script automates the installation of Docker on Debian 12 headless machines, including the configuration of IPv6 settings and required packages. It performs validation checks, installs necessary components, and generates a markdown-formatted installation report.

Prerequisites

  • A Debian 12 headless machine.
  • A user account with sudo privileges that does not require a password for sudo commands.

Important Notice

Warning: Running scripts from the internet without reviewing them can be risky. The script will execute commands with sudo privileges, which could potentially harm your system if the script is malicious or contains errors.

It is highly recommended to download and review the script before executing it.

Quick Start

Using curl

To download and run the script with curl:

bash <(curl -fsSL https://gist.githubusercontent.com/troykelly/c9537a5cd381d4da6c21e1ae59cbecc9/raw/install.sh)

Using wget

To download and run the script with wget:

bash <(wget -qO- https://gist.githubusercontent.com/troykelly/c9537a5cd381d4da6c21e1ae59cbecc9/raw/install.sh)

Recommended Usage

For security, it is recommended to download the script, review its contents, and then execute it:

wget https://gist.githubusercontent.com/troykelly/c9537a5cd381d4da6c21e1ae59cbecc9/raw/install.sh

Or with curl:

curl -fsSL -O https://gist.githubusercontent.com/troykelly/c9537a5cd381d4da6c21e1ae59cbecc9/raw/install.sh

Review the Script

Open the script in a text editor to review it:

vim install.sh

Execute the Script

After reviewing, make the script executable and run it:

chmod +x install.sh
./install.sh

What Does the Script Do?

  • Validation Checks:
    • Ensures it is not run as the root user.
    • Checks for sudo privileges without password prompts.
  • System Updates:
    • Updates package lists.
    • Performs a full system upgrade.
  • Package Installation:
    • Installs required packages: sipcalc, gpg, ca-certificates, curl, wget, vim, jq.
  • IPv6 Configuration:
    • Determines the default network interface.
    • Configures IPv6 settings in /etc/network/interfaces.
  • Docker Installation:
    • Adds Docker GPG key and repository.
    • Installs Docker packages.
    • Adds the current user to the docker group.
    • Enables Docker services.
  • Configuration Files:
    • Retrieves additional configuration files required for Docker and IPv6 from provided URLs.
  • Report Generation:
    • Generates a markdown-formatted installation report.
  • System Restart:
    • Restarts the system upon successful installation.

Support

For any issues or questions, please contact:

#!/usr/bin/env bash
#
# DHCPv6 hook for Docker
#
# /etc/dhcp/dhclient-enter-hooks.d/docker-ipv6
#
# This hook will configure Docker to use a /64 IPv6 subnet
# from the Prefix we got through DHCPv6 Prefix Delegation.
#
# dhclient will run this hook after it obtains the lease.
#
# This script requires sipcalc and jq to function.
# Check for required commands and log if they're missing
if ! command -v sipcalc >/dev/null 2>&1; then
logger -t 'dhclient-script' 'sipcalc command not found. Docker IPv6 hook cannot proceed.'
return
fi
if ! command -v jq >/dev/null 2>&1; then
logger -t 'dhclient-script' 'jq command not found. Docker IPv6 hook cannot proceed.'
return
fi
SUBNET_SIZE=64
DOCKER_ETC_DIR="/etc/docker"
DOCKER_DAEMON_CONFIG="${DOCKER_ETC_DIR}/daemon.json"
# Ensure /etc/docker directory exists and is owned by the docker group
if [ ! -d "$DOCKER_ETC_DIR" ]; then
if ! mkdir -p "$DOCKER_ETC_DIR"; then
logger -t 'dhclient-script' "Failed to create directory $DOCKER_ETC_DIR"
return
fi
chown root:docker "$DOCKER_ETC_DIR"
chmod 755 "$DOCKER_ETC_DIR"
fi
# Create daemon.json file if it doesn't exist
if [ ! -f "$DOCKER_DAEMON_CONFIG" ]; then
if ! echo "{}" > "$DOCKER_DAEMON_CONFIG"; then
logger -t 'dhclient-script' "Failed to create $DOCKER_DAEMON_CONFIG"
return
fi
chown root:docker "$DOCKER_DAEMON_CONFIG"
chmod 644 "$DOCKER_DAEMON_CONFIG"
fi
if [ -n "$new_ip6_prefix" ]; then
SUBNET=$(sipcalc -S $SUBNET_SIZE $new_ip6_prefix 2>/dev/null | grep Network | head -n 1 | awk '{print $3}')
if [ -z "$SUBNET" ]; then
logger -t 'dhclient-script' "Failed to calculate subnet from prefix $new_ip6_prefix"
return
fi
SUBNET_WITH_MASK="${SUBNET}/${SUBNET_SIZE}"
# Extract the current fixed-cidr-v6 value
CURRENT_SUBNET=$(jq -r '.["fixed-cidr-v6"] // empty' "$DOCKER_DAEMON_CONFIG" 2>/dev/null)
if [ -z "$CURRENT_SUBNET" ]; then
CURRENT_SUBNET=""
fi
# Only update if the subnet has changed or not set
if [ "$CURRENT_SUBNET" != "$SUBNET_WITH_MASK" ]; then
if ! jq --arg subnet "$SUBNET_WITH_MASK" \
'.ipv6 = true | .ip6tables = true | .["fixed-cidr-v6"] = $subnet' \
"$DOCKER_DAEMON_CONFIG" > "${DOCKER_DAEMON_CONFIG}.tmp"; then
logger -t 'dhclient-script' "Failed to update Docker daemon.json with new subnet $SUBNET_WITH_MASK"
rm -f "${DOCKER_DAEMON_CONFIG}.tmp"
return
fi
mv "${DOCKER_DAEMON_CONFIG}.tmp" "$DOCKER_DAEMON_CONFIG" && \
chown root:docker "$DOCKER_DAEMON_CONFIG" && \
chmod 644 "$DOCKER_DAEMON_CONFIG"
# Restart Docker if the prefix changed
if ! service docker restart 2>&1 | logger -t 'dhclient-script'; then
logger -t 'dhclient-script' "Failed to restart Docker service."
return
else
logger -t 'dhclient-script' "Docker service restarted successfully."
fi
# Ensure the interface variable is set
if [ ! -z "$interface" ]; then
# Attempt to disable IPv6 forwarding on the interface and log the result
if ! sysctl -w net.ipv6.conf."$interface".forwarding=0 2>&1 | logger -t 'dhclient-script'; then
logger -t 'dhclient-script' "Failed to disable IPv6 forwarding on interface $interface."
return
else
logger -t 'dhclient-script' "IPv6 forwarding disabled successfully on interface $interface."
fi
else
logger -t 'dhclient-script' "Interface not specified, unable to disable IPv6 forwarding."
exit 1
fi
fi
fi
#!/usr/bin/env bash
# install.sh
# Purpose: Install Docker on Debian 12 headless machines and configure IPv6 settings.
# Author: Troy Kelly
# Email: [email protected]
# Date: Monday, 30 September 2024
# Description: This script performs validation, installs required packages, configures IPv6,
# removes legacy files, installs Docker if not already installed, performs necessary configurations,
# generates a markdown report, and restarts the server upon successful completion.
# Version: 1.1
set -euo pipefail
# Error handling function
error_handler() {
local EXIT_CODE=$?
echo "An error occurred during the installation. Exit code: $EXIT_CODE"
generate_report "Failed"
exit $EXIT_CODE
}
trap error_handler ERR
# Signal handling for interruption
trap "echo 'Script interrupted.'; exit 1" INT TERM
# Array to keep track of completed steps
declare -a COMPLETED_STEPS=()
# Function to generate installation report
generate_report() {
local STATUS="$1"
local REPORT_FILE="install_report.md"
echo "Generating installation report at $REPORT_FILE"
{
echo "# Installation Report"
echo
echo "Date: $(date)"
echo
echo "Installation Status: **$STATUS**"
echo
echo "## Steps Completed:"
echo
for STEP in "${COMPLETED_STEPS[@]}"; do
echo "- [x] $STEP"
done
echo
if [[ "$STATUS" == "Failed" ]]; then
echo "## Error Details:"
echo
echo "**An error occurred during the installation. Please check the console output for details.**"
fi
} >"$REPORT_FILE"
echo "Installation report generated at $REPORT_FILE"
}
# Ensure the script is not running as root
if [[ "$EUID" -eq 0 ]]; then
echo "This script must not be run as root. Please run as a regular user with sudo privileges."
exit 1
fi
COMPLETED_STEPS+=("Verified the script is not running as root.")
# Ensure the user has sudo privileges without password prompt
if sudo -n true 2>/dev/null; then
echo "User has sudo privileges."
else
echo "Sudo password is required. Please configure sudo to not require a password for this user to run this script unattended."
exit 1
fi
COMPLETED_STEPS+=("Confirmed user has sudo privileges without password prompt.")
# Update package lists and perform full upgrade
echo "Updating package lists..."
sudo apt-get update
COMPLETED_STEPS+=("Updated package lists.")
echo "Performing full upgrade..."
sudo DEBIAN_FRONTEND=noninteractive apt-get -y full-upgrade
COMPLETED_STEPS+=("Performed full system upgrade.")
# Ensure required packages are installed
REQUIRED_PACKAGES=(
sipcalc
gpg
ca-certificates
curl
wget
vim
jq
)
echo "Ensuring required packages are installed..."
for pkg in "${REQUIRED_PACKAGES[@]}"; do
if dpkg -s "$pkg" >/dev/null 2>&1; then
echo "Package $pkg is already installed."
else
echo "Installing package $pkg..."
sudo apt-get install -y "$pkg"
fi
done
COMPLETED_STEPS+=("Installed required packages: ${REQUIRED_PACKAGES[*]}.")
# Determine the default network interface
DEFAULT_INTERFACE=$(ip route | awk '/default/ {print $5}' | head -n 1)
if [[ -z "$DEFAULT_INTERFACE" ]]; then
echo "Unable to determine default network interface."
exit 1
else
echo "Default network interface is $DEFAULT_INTERFACE."
fi
COMPLETED_STEPS+=("Identified default network interface: $DEFAULT_INTERFACE.")
# Backup /etc/network/interfaces
echo "Backing up /etc/network/interfaces to /etc/network/interfaces.bak..."
sudo cp /etc/network/interfaces /etc/network/interfaces.bak
COMPLETED_STEPS+=("Backed up /etc/network/interfaces.")
# Configure IPv6 settings
echo "Configuring IPv6 settings for $DEFAULT_INTERFACE..."
INTERFACE_CONFIG_EXISTS=$(grep -A5 "iface $DEFAULT_INTERFACE inet6 dhcp" /etc/network/interfaces || true)
if [[ -n "$INTERFACE_CONFIG_EXISTS" ]]; then
echo "Existing IPv6 configuration found for $DEFAULT_INTERFACE. Updating configuration."
sudo sed -i "/iface $DEFAULT_INTERFACE inet6 dhcp/,/^\s*$/d" /etc/network/interfaces
else
echo "No IPv6 configuration found for $DEFAULT_INTERFACE. Adding configuration."
fi
sudo tee -a /etc/network/interfaces >/dev/null <<EOF
iface $DEFAULT_INTERFACE inet6 dhcp
accept_ra 2
request_prefix 1
autoconf 1 # Enable automatic configuration of addresses from Router Advertisements
forwarding 0
EOF
COMPLETED_STEPS+=("Configured IPv6 settings for $DEFAULT_INTERFACE.")
# Remove legacy files if they exist
LEGACY_FILES=(
"/etc/sysctl.d/99-ipv6.conf"
"/etc/systemd/system/dhclient6-pd.service"
"/etc/systemd/system/docker.service.d/ipv6.conf"
"/etc/docker/ipv6.prefix"
)
echo "Checking for and removing legacy files if they exist..."
for FILE in "${LEGACY_FILES[@]}"; do
if [[ -f "$FILE" ]]; then
echo "Removing legacy file $FILE..."
sudo rm -f "$FILE"
COMPLETED_STEPS+=("Removed legacy file $FILE.")
else
echo "Legacy file $FILE does not exist. Skipping."
fi
done
# Check if Docker is already installed
if command -v docker >/dev/null 2>&1; then
echo "Docker is already installed. Skipping Docker installation steps."
COMPLETED_STEPS+=("Docker is already installed. Skipped Docker installation.")
else
# Install Docker
echo "Installing Docker..."
echo "Creating /etc/apt/keyrings directory..."
sudo install -m 0755 -d /etc/apt/keyrings
echo "Downloading Docker GPG key..."
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
echo "Setting permissions for Docker GPG key..."
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "Adding Docker repository..."
OS_VERSION_CODENAME=$(source /etc/os-release && echo "$VERSION_CODENAME")
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $OS_VERSION_CODENAME stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
echo "Updating package lists..."
sudo apt-get update
echo "Installing Docker packages..."
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
COMPLETED_STEPS+=("Installed Docker packages.")
# Ensure the current user is a member of the docker group
if groups "$USER" | grep -q '\bdocker\b'; then
echo "User $USER is already in the docker group."
else
echo "Adding user $USER to the docker group..."
sudo usermod -aG docker "$USER"
fi
COMPLETED_STEPS+=("Ensured user $USER is in the docker group.")
# Enable Docker services
echo "Enabling Docker services..."
sudo systemctl enable docker.service
sudo systemctl enable containerd.service
COMPLETED_STEPS+=("Enabled Docker services.")
fi
# Retrieve br_netfilter.conf
echo "Retrieving br_netfilter.conf..."
sudo curl -fsSL "https://gist.githubusercontent.com/troykelly/c9537a5cd381d4da6c21e1ae59cbecc9/raw/br_netfilter.conf" -o /etc/modules-load.d/br_netfilter.conf
COMPLETED_STEPS+=("Retrieved br_netfilter.conf.")
# Retrieve docker-ipv6 hook script
echo "Retrieving docker-ipv6 hook script..."
sudo curl -fsSL "https://gist.githubusercontent.com/troykelly/c9537a5cd381d4da6c21e1ae59cbecc9/raw/docker-ipv6" -o /etc/dhcp/dhclient-enter-hooks.d/docker-ipv6
COMPLETED_STEPS+=("Retrieved docker-ipv6 hook script.")
# Make docker-ipv6 hook script executable
echo "Making docker-ipv6 hook script executable..."
sudo chmod +x /etc/dhcp/dhclient-enter-hooks.d/docker-ipv6
COMPLETED_STEPS+=("Made docker-ipv6 hook script executable.")
# Retrieve Docker service override configuration
echo "Retrieving Docker service override configuration..."
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo curl -fsSL "https://gist.githubusercontent.com/troykelly/c9537a5cd381d4da6c21e1ae59cbecc9/raw/override.conf" -o /etc/systemd/system/docker.service.d/override.conf
COMPLETED_STEPS+=("Retrieved Docker service override configuration.")
# Generate installation report and restart the server
echo "Installation completed successfully."
generate_report "Successful"
echo "The system will restart in 10 seconds..."
sleep 10
sudo reboot
[Service]
ExecStartPost=/bin/sh -c 'for iface in $$(ip -6 route show default | sed -n "s/^default.* dev \\([^ ]*\\).*/\\1/p"); do /usr/sbin/sysctl -w net.ipv6.conf.$${iface}.forwarding=0; done'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment