ffbb92909b
This way we can better gaurd against empty files as seen in the previous commits message. Signed-off-by: Manuel Mendez <mmendez@equinix.com>
514 lines
13 KiB
Bash
Executable File
514 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# stops the execution if a command or pipeline has an error
|
|
set -euxo pipefail
|
|
|
|
# Tinkerbell stack Linux setup script
|
|
#
|
|
# See https://tinkerbell.org/setup for the installation steps.
|
|
|
|
# file to hold all environment variables
|
|
ENV_FILE=.env
|
|
|
|
SCRATCH=$(mktemp -d -t tmp.XXXXXXXXXX)
|
|
readonly SCRATCH
|
|
function finish() (
|
|
rm -rf "$SCRATCH"
|
|
)
|
|
trap finish EXIT
|
|
|
|
DEPLOYDIR=$(pwd)/deploy
|
|
readonly DEPLOYDIR
|
|
readonly STATEDIR=$DEPLOYDIR/state
|
|
|
|
if command -v tput >/dev/null && tput setaf 1 >/dev/null 2>&1; then
|
|
# color codes
|
|
RED="$(tput setaf 1)"
|
|
GREEN="$(tput setaf 2)"
|
|
YELLOW="$(tput setaf 3)"
|
|
RESET="$(tput sgr0)"
|
|
fi
|
|
|
|
INFO="${GREEN:-}INFO:${RESET:-}"
|
|
ERR="${RED:-}ERROR:${RESET:-}"
|
|
WARN="${YELLOW:-}WARNING:${RESET:-}"
|
|
BLANK=" "
|
|
NEXT="${GREEN:-}NEXT:${RESET:-}"
|
|
|
|
get_distribution() (
|
|
local lsb_dist=""
|
|
# Every system that we officially support has /etc/os-release
|
|
if [[ -r /etc/os-release ]]; then
|
|
# shellcheck disable=SC1091
|
|
lsb_dist="$(. /etc/os-release && echo "$ID")"
|
|
fi
|
|
# Returning an empty string here should be alright since the
|
|
# case statements don't act unless you provide an actual value
|
|
echo "$lsb_dist" | tr '[:upper:]' '[:lower:]'
|
|
)
|
|
|
|
get_distro_version() (
|
|
local lsb_version="0"
|
|
# Every system that we officially support has /etc/os-release
|
|
if [[ -r /etc/os-release ]]; then
|
|
# shellcheck disable=SC1091
|
|
lsb_version="$(. /etc/os-release && echo "$VERSION_ID")"
|
|
fi
|
|
|
|
echo "$lsb_version"
|
|
)
|
|
|
|
is_network_configured() (
|
|
# Require the provisioner interface have the host IP
|
|
if ! ip addr show "$TINKERBELL_NETWORK_INTERFACE" |
|
|
grep -q "$TINKERBELL_HOST_IP"; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
)
|
|
|
|
identify_network_strategy() (
|
|
local distro=$1
|
|
local version=$2
|
|
|
|
case "$distro" in
|
|
ubuntu)
|
|
if jq -n --exit-status '$distro_version >= 17.10' --argjson distro_version "$version" >/dev/null 2>&1; then
|
|
echo "setup_networking_netplan"
|
|
else
|
|
echo "setup_networking_ubuntu_legacy"
|
|
fi
|
|
;;
|
|
centos)
|
|
echo "setup_networking_centos"
|
|
;;
|
|
*)
|
|
echo "setup_networking_manually"
|
|
;;
|
|
esac
|
|
)
|
|
|
|
setup_networking() (
|
|
local distro=$1
|
|
local version=$2
|
|
|
|
setup_network_forwarding
|
|
|
|
if is_network_configured; then
|
|
echo "$INFO tinkerbell network interface is already configured"
|
|
return 0
|
|
fi
|
|
|
|
local strategy
|
|
strategy=$(identify_network_strategy "$distro" "$version")
|
|
|
|
"${strategy}" "$distro" "$version" # execute the strategy
|
|
|
|
if is_network_configured; then
|
|
echo "$INFO tinkerbell network interface configured successfully"
|
|
else
|
|
echo "$ERR tinkerbell network interface configuration failed"
|
|
fi
|
|
|
|
NAT_INTERFACE=""
|
|
if [[ -r .nat_interface ]]; then
|
|
NAT_INTERFACE=$(cat .nat_interface)
|
|
fi
|
|
if [[ -n $NAT_INTERFACE ]] && ip addr show "$NAT_INTERFACE" &>/dev/null; then
|
|
# TODO(nshalman) the terraform code would just run these commands as-is once
|
|
# but it would be nice to make these more persistent based on OS
|
|
iptables -A FORWARD -i "$TINKERBELL_NETWORK_INTERFACE" -o "$NAT_INTERFACE" -j ACCEPT
|
|
iptables -A FORWARD -i "$NAT_INTERFACE" -o "$TINKERBELL_NETWORK_INTERFACE" -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
iptables -t nat -A POSTROUTING -o "$NAT_INTERFACE" -j MASQUERADE
|
|
fi
|
|
)
|
|
|
|
setup_networking_manually() (
|
|
local distro=$1
|
|
local version=$2
|
|
|
|
echo "$ERR this setup script cannot configure $distro ($version)"
|
|
echo "$BLANK please read this script's source and configure it manually."
|
|
exit 1
|
|
)
|
|
|
|
setup_network_forwarding() (
|
|
# enable IP forwarding for docker
|
|
if (($(sysctl -n net.ipv4.ip_forward) != 1)); then
|
|
if [[ -d /etc/sysctl.d ]]; then
|
|
echo "net.ipv4.ip_forward=1" >/etc/sysctl.d/99-tinkerbell.conf
|
|
elif [[ -f /etc/sysctl.conf ]]; then
|
|
echo "net.ipv4.ip_forward=1" >>/etc/sysctl.conf
|
|
fi
|
|
|
|
sysctl net.ipv4.ip_forward=1
|
|
fi
|
|
)
|
|
|
|
setup_networking_netplan() (
|
|
jq -n \
|
|
--arg interface "$TINKERBELL_NETWORK_INTERFACE" \
|
|
--arg cidr "$TINKERBELL_CIDR" \
|
|
--arg host_ip "$TINKERBELL_HOST_IP" \
|
|
'{
|
|
network: {
|
|
renderer: "networkd",
|
|
ethernets: {
|
|
($interface): {
|
|
addresses: [
|
|
"\($host_ip)/\($cidr)"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}' >"/etc/netplan/${TINKERBELL_NETWORK_INTERFACE}.yaml"
|
|
|
|
ip link set "$TINKERBELL_NETWORK_INTERFACE" nomaster
|
|
netplan apply
|
|
echo "$INFO waiting for the network configuration to be applied by systemd-networkd"
|
|
sleep 3
|
|
)
|
|
|
|
setup_networking_ubuntu_legacy() (
|
|
if ! [[ -f /etc/network/interfaces ]]; then
|
|
echo "$ERR file /etc/network/interfaces not found"
|
|
exit 1
|
|
fi
|
|
|
|
if grep -q "$TINKERBELL_NETWORK_INTERFACE" /etc/network/interfaces; then
|
|
echo "$ERR /etc/network/interfaces already has an entry for $TINKERBELL_NETWORK_INTERFACE."
|
|
echo "$BLANK To prevent breaking your network, please edit /etc/network/interfaces"
|
|
echo "$BLANK and configure $TINKERBELL_NETWORK_INTERFACE as follows:"
|
|
generate_iface_config
|
|
echo ""
|
|
echo "$BLANK Then run the following commands:"
|
|
echo "$BLANK ip link set $TINKERBELL_NETWORK_INTERFACE nomaster"
|
|
echo "$BLANK ifdown $TINKERBELL_NETWORK_INTERFACE"
|
|
echo "$BLANK ifup $TINKERBELL_NETWORK_INTERFACE"
|
|
exit 1
|
|
else
|
|
generate_iface_config >>/etc/network/interfaces
|
|
ip link set "$TINKERBELL_NETWORK_INTERFACE" nomaster
|
|
ifdown "$TINKERBELL_NETWORK_INTERFACE"
|
|
ifup "$TINKERBELL_NETWORK_INTERFACE"
|
|
fi
|
|
)
|
|
|
|
generate_iface_config() (
|
|
cat <<EOF
|
|
|
|
auto $TINKERBELL_NETWORK_INTERFACE
|
|
iface $TINKERBELL_NETWORK_INTERFACE inet static
|
|
address $TINKERBELL_HOST_IP/$TINKERBELL_CIDR
|
|
pre-up sleep 4
|
|
EOF
|
|
)
|
|
|
|
setup_networking_centos() (
|
|
local HWADDRESS
|
|
local content
|
|
|
|
HWADDRESS=$(ip addr show "$TINKERBELL_NETWORK_INTERFACE" | grep ether | awk -F 'ether' '{print $2}' | cut -d" " -f2)
|
|
content=$(
|
|
cat <<EOF
|
|
DEVICE=$TINKERBELL_NETWORK_INTERFACE
|
|
ONBOOT=yes
|
|
HWADDR=$HWADDRESS
|
|
BOOTPROTO=static
|
|
|
|
IPADDR=$TINKERBELL_HOST_IP
|
|
PREFIX=$TINKERBELL_CIDR
|
|
EOF
|
|
)
|
|
|
|
local cfgfile="/etc/sysconfig/network-scripts/ifcfg-$TINKERBELL_NETWORK_INTERFACE"
|
|
|
|
if [[ -f $cfgfile ]]; then
|
|
echo "$ERR network config already exists: $cfgfile"
|
|
echo "$BLANK Please update it to match this configuration:"
|
|
echo "$content"
|
|
echo ""
|
|
echo "$BLANK Then, run the following commands:"
|
|
echo "ip link set $TINKERBELL_NETWORK_INTERFACE nomaster"
|
|
echo "ifup $TINKERBELL_NETWORK_INTERFACE"
|
|
fi
|
|
|
|
echo "$content" >"$cfgfile"
|
|
|
|
ip link set "$TINKERBELL_NETWORK_INTERFACE" nomaster
|
|
ifup "$TINKERBELL_NETWORK_INTERFACE"
|
|
)
|
|
|
|
setup_osie() (
|
|
mkdir -p "$STATEDIR/webroot"
|
|
|
|
local osie_current=$STATEDIR/webroot/misc/osie/current
|
|
local tink_workflow=$STATEDIR/webroot/workflow/
|
|
if [[ ! -d $osie_current ]] || [[ ! -d $tink_workflow ]]; then
|
|
mkdir -p "$osie_current"
|
|
mkdir -p "$tink_workflow"
|
|
pushd "$SCRATCH"
|
|
|
|
if [[ -z ${TB_OSIE_TAR:-} ]]; then
|
|
curl "${OSIE_DOWNLOAD_LINK}" -o ./osie.tar.gz
|
|
tar -zxf osie.tar.gz
|
|
else
|
|
tar -zxf "$TB_OSIE_TAR"
|
|
fi
|
|
|
|
if pushd osie*/; then
|
|
if mv workflow-helper.sh workflow-helper-rc "$tink_workflow"; then
|
|
cp -r ./* "$osie_current"
|
|
else
|
|
echo "$ERR failed to move 'workflow-helper.sh' and 'workflow-helper-rc'"
|
|
exit 1
|
|
fi
|
|
popd
|
|
fi
|
|
else
|
|
echo "$INFO found existing osie files, skipping osie setup"
|
|
fi
|
|
)
|
|
|
|
check_container_status() (
|
|
local container_name="$1"
|
|
local container_id
|
|
container_id=$(docker-compose -f "$DEPLOYDIR/docker-compose.yml" ps -q "$container_name")
|
|
|
|
local start_moment
|
|
local current_status
|
|
start_moment=$(docker inspect "${container_id}" --format '{{ .State.StartedAt }}')
|
|
current_status=$(docker inspect "${container_id}" --format '{{ .State.Health.Status }}')
|
|
|
|
case "$current_status" in
|
|
starting)
|
|
: # move on to the events check
|
|
;;
|
|
healthy)
|
|
return 0
|
|
;;
|
|
unhealthy)
|
|
echo "$ERR $container_name is already running but not healthy. status: $current_status"
|
|
exit 1
|
|
;;
|
|
*)
|
|
echo "$ERR $container_name is already running but its state is a mystery. status: $current_status"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
local status
|
|
read -r status < <(docker events \
|
|
--since "$start_moment" \
|
|
--filter "container=$container_id" \
|
|
--filter "event=health_status" \
|
|
--format '{{.Status}}')
|
|
|
|
if [[ $status != "health_status: healthy" ]]; then
|
|
echo "$ERR $container_name is not healthy. status: $status"
|
|
exit 1
|
|
fi
|
|
)
|
|
|
|
generate_certificates() (
|
|
mkdir -p "$STATEDIR/certs"
|
|
|
|
if ! [[ -f "$STATEDIR/certs/ca.json" ]]; then
|
|
jq \
|
|
'.
|
|
| .names[0].L = $facility
|
|
' \
|
|
"$DEPLOYDIR/tls/ca.in.json" \
|
|
--arg ip "$TINKERBELL_HOST_IP" \
|
|
--arg facility "$FACILITY" \
|
|
>"$STATEDIR/certs/ca.json"
|
|
fi
|
|
|
|
if ! [[ -f "$STATEDIR/certs/server-csr.json" ]]; then
|
|
jq \
|
|
'.
|
|
| .hosts += [ $ip, "tinkerbell.\($facility).packet.net" ]
|
|
| .names[0].L = $facility
|
|
| .hosts = (.hosts | sort | unique)
|
|
' \
|
|
"$DEPLOYDIR/tls/server-csr.in.json" \
|
|
--arg ip "$TINKERBELL_HOST_IP" \
|
|
--arg facility "$FACILITY" \
|
|
>"$STATEDIR/certs/server-csr.json"
|
|
fi
|
|
|
|
docker build --tag "tinkerbell-certs" "$DEPLOYDIR/tls"
|
|
docker run --rm \
|
|
--volume "$STATEDIR/certs:/certs" \
|
|
--user "$UID:$(id -g)" \
|
|
tinkerbell-certs
|
|
|
|
local certs_dir="/etc/docker/certs.d/$TINKERBELL_HOST_IP"
|
|
|
|
# copy public key to NGINX for workers
|
|
if ! cmp --quiet "$STATEDIR/certs/ca.pem" "$STATEDIR/webroot/workflow/ca.pem"; then
|
|
cp "$STATEDIR/certs/ca.pem" "$STATEDIR/webroot/workflow/ca.pem"
|
|
fi
|
|
|
|
# update host to trust registry certificate
|
|
if ! cmp --quiet "$STATEDIR/certs/ca.pem" "$certs_dir/tinkerbell.crt"; then
|
|
if ! [[ -d "$certs_dir/" ]]; then
|
|
# The user will be told to create the directory
|
|
# in the next block, if copying the certs there
|
|
# fails.
|
|
mkdir -p "$certs_dir" || true >/dev/null 2>&1
|
|
fi
|
|
if ! cp "$STATEDIR/certs/ca.pem" "$certs_dir/tinkerbell.crt"; then
|
|
echo "$ERR please copy $STATEDIR/certs/ca.pem to $certs_dir/tinkerbell.crt"
|
|
echo "$BLANK and run $0 again:"
|
|
|
|
if ! [[ -d $certs_dir ]]; then
|
|
echo "sudo mkdir -p '$certs_dir'"
|
|
fi
|
|
echo "sudo cp '$STATEDIR/certs/ca.pem' '$certs_dir/tinkerbell.crt'"
|
|
|
|
exit 1
|
|
fi
|
|
fi
|
|
)
|
|
|
|
docker_login() (
|
|
echo -n "$TINKERBELL_REGISTRY_PASSWORD" | docker login -u="$TINKERBELL_REGISTRY_USERNAME" --password-stdin "$TINKERBELL_HOST_IP"
|
|
)
|
|
|
|
# This function takes an image specified as first parameter and it tags and
|
|
# push it using the second one. useful to proxy images from a repository to
|
|
# another.
|
|
docker_mirror_image() (
|
|
local from=$1
|
|
local to=$2
|
|
|
|
docker pull "$from"
|
|
docker tag "$from" "$to"
|
|
docker push "$to"
|
|
)
|
|
|
|
start_registry() (
|
|
docker-compose -f "$DEPLOYDIR/docker-compose.yml" up --build -d registry
|
|
check_container_status "registry"
|
|
)
|
|
|
|
# This function supposes that the registry is up and running.
|
|
# It configures with the required dependencies.
|
|
bootstrap_docker_registry() (
|
|
docker_login
|
|
|
|
# osie looks for tink-worker:latest, so we have to play with it a bit
|
|
# https://github.com/tinkerbell/osie/blob/master/apps/workflow-helper.sh#L66
|
|
docker_mirror_image "${TINKERBELL_TINK_WORKER_IMAGE}" "${TINKERBELL_HOST_IP}/tink-worker:latest"
|
|
)
|
|
|
|
setup_docker_registry() (
|
|
local registry_images="$STATEDIR/registry"
|
|
if ! [[ -d $registry_images ]]; then
|
|
mkdir -p "$registry_images"
|
|
fi
|
|
start_registry
|
|
bootstrap_docker_registry
|
|
)
|
|
|
|
start_components() (
|
|
local components=(db hegel tink-server boots tink-cli nginx)
|
|
for comp in "${components[@]}"; do
|
|
docker-compose -f "$DEPLOYDIR/docker-compose.yml" up --build -d "$comp"
|
|
sleep 3
|
|
check_container_status "$comp"
|
|
done
|
|
)
|
|
|
|
command_exists() (
|
|
command -v "$@" >/dev/null 2>&1
|
|
)
|
|
|
|
check_command() (
|
|
if ! command_exists "$1"; then
|
|
echo "$ERR Prerequisite executable command not found: $1"
|
|
return 1
|
|
fi
|
|
if ! [[ -s "$(which "$1")" ]]; then
|
|
echo "$ERR Prerequisite command is an empty file: $1"
|
|
fi
|
|
echo "$BLANK Found prerequisite: $1"
|
|
return 0
|
|
)
|
|
|
|
check_prerequisites() (
|
|
distro=$1
|
|
version=$2
|
|
|
|
echo "$INFO verifying prerequisites for $distro ($version)"
|
|
failed=0
|
|
check_command docker || failed=1
|
|
check_command docker-compose || failed=1
|
|
check_command ip || failed=1
|
|
check_command jq || failed=1
|
|
|
|
strategy=$(identify_network_strategy "$distro" "$version")
|
|
case "$strategy" in
|
|
"setup_networking_netplan")
|
|
check_command netplan || failed=1
|
|
;;
|
|
"setup_networking_ubuntu_legacy")
|
|
check_command ifdown || failed=1
|
|
check_command ifup || failed=1
|
|
;;
|
|
"setup_networking_centos")
|
|
check_command ifdown || failed=1
|
|
check_command ifup || failed=1
|
|
;;
|
|
"setup_networking_manually")
|
|
echo "$WARN this script cannot automatically configure your network."
|
|
;;
|
|
*)
|
|
echo "$ERR bug: unhandled network strategy: $strategy"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if ((failed == 1)); then
|
|
echo "$ERR Prerequisites not met. Please install the missing commands and re-run $0."
|
|
exit 1
|
|
fi
|
|
)
|
|
|
|
whats_next() (
|
|
echo "$NEXT 1. Enter /vagrant/deploy and run: source ../.env; docker-compose up -d"
|
|
echo "$BLANK 2. Try executing your fist workflow."
|
|
echo "$BLANK Follow the steps described in https://tinkerbell.org/examples/hello-world/ to say 'Hello World!' with a workflow."
|
|
)
|
|
|
|
do_setup() (
|
|
# perform some very rudimentary platform detection
|
|
lsb_dist=$(get_distribution)
|
|
lsb_version=$(get_distro_version)
|
|
|
|
echo "$INFO starting tinkerbell stack setup"
|
|
check_prerequisites "$lsb_dist" "$lsb_version"
|
|
|
|
if ! [[ -f $ENV_FILE ]]; then
|
|
echo "$ERR Run './generate-env.sh network-interface > \"$ENV_FILE\"' before continuing."
|
|
exit 1
|
|
fi
|
|
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
|
|
setup_networking "$lsb_dist" "$lsb_version"
|
|
setup_osie
|
|
generate_certificates
|
|
setup_docker_registry
|
|
|
|
echo "$INFO tinkerbell stack setup completed successfully on $lsb_dist server"
|
|
whats_next | tee /tmp/post-setup-message
|
|
)
|
|
|
|
# wrapped up in a function so that we have some protection against only getting
|
|
# half the file during "curl | sh"
|
|
do_setup
|