diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index dd956b2f3..8d36a1b74 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -1,10 +1,5 @@ version: "2.1" services: - certs: - build: tls - volumes: - - ./certs:/certs - tink-server: image: quay.io/tinkerbell/tink:latest restart: unless-stopped @@ -31,7 +26,7 @@ services: timeout: 2s retries: 30 volumes: - - ./certs:/certs/${FACILITY} + - ./state/certs:/certs/${FACILITY} logging: driver: fluentd options: @@ -84,6 +79,11 @@ services: REGISTRY_USERNAME: $TINKERBELL_REGISTRY_USERNAME REGISTRY_PASSWORD: $TINKERBELL_REGISTRY_PASSWORD restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "curl --cacert /certs/ca.pem https://127.0.0.1"] + interval: 5s + timeout: 1s + retries: 5 environment: REGISTRY_HTTP_ADDR: 0.0.0.0:443 REGISTRY_HTTP_TLS_CERTIFICATE: /certs/server.pem @@ -92,8 +92,8 @@ services: REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm" REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd volumes: - - ./certs:/certs - - /var/tinkerbell/registry:/var/lib/registry + - ./state/certs:/certs + - ./state/registry:/var/lib/registry depends_on: fluentbit: condition: service_started @@ -152,7 +152,7 @@ services: ports: - $TINKERBELL_NGINX_IP:80:80/tcp volumes: - - /var/tinkerbell/nginx/:/usr/share/nginx/html/ + - ./state/webroot:/usr/share/nginx/html/ logging: driver: fluentd options: @@ -187,7 +187,7 @@ services: depends_on: - elasticsearch volumes: - - ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf + - ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro cacher: image: quay.io/packet/cacher:workflow @@ -207,7 +207,7 @@ services: PGUSER: tinkerbell ROLLBAR_TOKEN: ${ROLLBAR_TOKEN-ignored} volumes: - - ./certs:/certs/${FACILITY} + - ./state/certs:/certs/${FACILITY} logging: driver: fluentd options: diff --git a/deploy/registry/Dockerfile b/deploy/registry/Dockerfile index 62a40bb45..58c2c9243 100644 --- a/deploy/registry/Dockerfile +++ b/deploy/registry/Dockerfile @@ -1,6 +1,7 @@ FROM registry:2 +RUN apk add --no-cache --update curl ARG REGISTRY_USERNAME ARG REGISTRY_PASSWORD RUN mkdir -p /certs /auth RUN htpasswd -Bbn ${REGISTRY_USERNAME} ${REGISTRY_PASSWORD} > /auth/htpasswd -EXPOSE 443 \ No newline at end of file +EXPOSE 443 diff --git a/deploy/tls/entrypoint.sh b/deploy/tls/entrypoint.sh index 52016bb2b..a172eaa7d 100755 --- a/deploy/tls/entrypoint.sh +++ b/deploy/tls/entrypoint.sh @@ -4,12 +4,9 @@ if [ -z "${TINKERBELL_TLS_CERT:-}" ]; then ( - FACILITY=$(echo "$FACILITY" | tr '[:upper:]' '[:lower:]') echo "creating directory" mkdir -p "certs" - FACILITY=$FACILITY sh gencerts.sh - rm server.csr server-csr.json - rm ca.csr ca.json + ./gencerts.sh ) fi diff --git a/deploy/tls/gencerts.sh b/deploy/tls/gencerts.sh old mode 100644 new mode 100755 index 0d7913314..66731dfea --- a/deploy/tls/gencerts.sh +++ b/deploy/tls/gencerts.sh @@ -1,29 +1,30 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh -if ! { [[ -r ca.json ]] && [[ -r ca.pem ]] && [[ -r ca-key.pem ]]; }; then - sed "s|@FACILITY@|$FACILITY|g" ca.json +set -eux + +cd /certs + +if [ ! -f ca-key.pem ]; then cfssl gencert \ -initca ca.json | cfssljson -bare ca - rm -f server-csr.json server-*.pem fi -if ! { [[ -r server-csr.json ]] && [[ -r server.pem ]] && [[ -r server-key.pem ]]; }; then - sed "s|@FACILITY@|$FACILITY|g" server-csr.json + +if [ ! -f server.pem ]; then cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ - -config=ca-config.json \ + -config=/ca-config.json \ -profile=server \ - server-csr.json | cfssljson -bare server - cat server.pem ca.pem | tee bundle.pem + server-csr.json | + cfssljson -bare server fi +cat server.pem ca.pem >bundle.pem.tmp + # only "modify" the file if truly necessary since workflow will serve it with # modtime info for client caching purposes -cat server.pem ca.pem >bundle.pem.tmp if ! cmp -s bundle.pem.tmp bundle.pem; then mv bundle.pem.tmp bundle.pem else rm bundle.pem.tmp fi - -mv *.pem certs/ diff --git a/deploy/tls/server-csr.in.json b/deploy/tls/server-csr.in.json index fdb3ca2c0..86c1008d4 100644 --- a/deploy/tls/server-csr.in.json +++ b/deploy/tls/server-csr.in.json @@ -1,7 +1,6 @@ { "CN": "tinkerbell", "hosts": [ - "tinkerbell.@FACILITY@.packet.net", "tinkerbell.registry", "tinkerbell.tinkerbell", "tinkerbell", diff --git a/generate-envrc.sh b/generate-envrc.sh new file mode 100755 index 000000000..bbea823ff --- /dev/null +++ b/generate-envrc.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +# stops the execution if a command or pipeline has an error +set -eu + +if command -v tput >/dev/null && tput setaf 1 >/dev/null 2>&1; then + # color codes + RED="$(tput setaf 1)" + RESET="$(tput sgr0)" +fi + +ERR="${RED:-}ERROR:${RESET:-}" + +err() ( + if [ -z "${1:-}" ]; then + cat >&2 + else + echo "$ERR " "$@" >&2 + fi +) + +candidate_interfaces() ( + ip -o link show | + awk -F': ' '{print $2}' | + sed 's/[ \t].*//;/^\(lo\|bond0\|\|\)$/d' | + sort +) + +validate_tinkerbell_network_interface() ( + local tink_interface=$1 + + if ! candidate_interfaces | grep -q "^$tink_interface$"; then + err "Invalid interface ($tink_interface) selected, must be one of:" + candidate_interfaces | err + return 1 + else + return 0 + fi +) + +generate_password() ( + head -c 12 /dev/urandom | sha256sum | cut -d' ' -f1 +) + +generate_envrc() ( + local tink_interface=$1 + + validate_tinkerbell_network_interface "$tink_interface" + + local registry_password + registry_password=$(generate_password) + cat < envrc" + exit 1 + fi + + generate_envrc "$1" +) + +main "$@" diff --git a/setup.sh b/setup.sh index 1253e722a..68eb40f3d 100755 --- a/setup.sh +++ b/setup.sh @@ -1,7 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash # stops the execution if a command or pipeline has an error -set -e +set -eu # Tinkerbell stack Linux setup script # @@ -10,514 +10,502 @@ set -e # file to hold all environment variables ENV_FILE=envrc -if which tput >>/dev/null; then +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)" -else - echo "color coding will not happen as tput command not found." fi -INFO="${GREEN}INFO:$RESET" -ERR="${RED}ERROR:$RESET" -WARN="${YELLOW}WARNING:$RESET" +INFO="${GREEN:-}INFO:${RESET:-}" +ERR="${RED:-}ERROR:${RESET:-}" +WARN="${YELLOW:-}WARNING:${RESET:-}" BLANK=" " -NEXT="${GREEN}NEXT:$RESET" +NEXT="${GREEN:-}NEXT:${RESET:-}" -get_distribution() { - lsb_dist="" +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" -} + echo "$lsb_dist" | tr '[:upper:]' '[:lower:]' +) -list_network_interfaces() { - if [ -z $TB_INTERFACE ]; then - echo "Following network interfaces found on the system:" - ip -o link show | awk -F': ' '{print $2}' | grep '^[e]' +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 -} -get_tinkerbell_network_interface() { - # read for network interface if TB_INTERFACE is not defined - if [ -z $TB_INTERFACE ]; then - read -p 'Which one would you like to use for with Tinkerbell? ' tink_interface - else - tink_interface=$TB_INTERFACE - fi + echo "$lsb_version" +) - ip -o link show | awk -F': ' '{print $2}' | sed 's/[ \t].*//;/^\(lo\|\)$/d' | sed 's/[ \t].*//;/^\(bond0\|\)$/d' | grep "$tink_interface" - if [ $? -ne 0 ]; then - echo "$ERR Invalid interface selected. Exiting setup." - exit 1 - fi - echo -e "# Network interface for Tinkerbell \nexport TINKERBELL_NETWORK_INTERFACE=$tink_interface" >>"$ENV_FILE" -} - -get_tinkerbell_ips() { - # read for tinkerbell network if TB_NETWORK is not defined - if [ -z $TB_NETWORK ]; then - read -p 'Select the subnet for Tinkerbell ecosystem: [default 192.168.1.0/29]: ' tink_network - tink_network=${tink_network:-"192.168.1.0/29"} - else - tink_network=$TB_NETWORK +is_network_configured() ( + # Require the provisioner interface have both the host and nginx IP + if ! ip addr show "$TINKERBELL_NETWORK_INTERFACE" | + grep -q "$TINKERBELL_HOST_IP"; then + return 1 fi - # read for tinkerbell IP address if TB_IPADDR is not defined - if [ -z $TB_IPADDR ]; then - read -p 'Select the IP address for Tinkerbell [default 192.168.1.1]: ' ip_addr - ip_addr=${ip_addr:-"192.168.1.1"} - else - ip_addr=$TB_IPADDR + if ! ip addr show "$TINKERBELL_NETWORK_INTERFACE" | + grep -q "$TINKERBELL_NGINX_IP"; then + return 1 fi - host=$(($(echo $ip_addr | cut -d "." -f 4 | xargs) + 1)) - nginx_ip="$(echo $ip_addr | cut -d "." -f 1).$(echo $ip_addr | cut -d "." -f 2).$(echo $ip_addr | cut -d "." -f 3).$host" - - # calculate network and broadcast based on supplied provide IP range - if [[ $tink_network =~ ^([0-9\.]+)/([0-9]+)$ ]]; then - # CIDR notation - IPADDR=${BASH_REMATCH[1]} - NETMASKLEN=${BASH_REMATCH[2]} - zeros=$((32 - NETMASKLEN)) - NETMASKNUM=0 - for ((i = 0; i < $zeros; i++)); do - NETMASKNUM=$(((NETMASKNUM << 1) ^ 1)) - done - NETMASKNUM=$((NETMASKNUM ^ 0xFFFFFFFF)) - toaddr $NETMASKNUM NETMASK - else - IPADDR=${1:-192.168.1.1} - NETMASK=${2:-255.255.255.248} - fi + return 0 +) - tonum $IPADDR IPADDRNUM - tonum $NETMASK NETMASKNUM +identify_network_strategy() ( + local distro=$1 + local version=$2 - # The logic to calculate network and broadcast - INVNETMASKNUM=$((0xFFFFFFFF ^ NETMASKNUM)) - NETWORKNUM=$((IPADDRNUM & NETMASKNUM)) - BROADCASTNUM=$((INVNETMASKNUM | NETWORKNUM)) + 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 +) - toaddr $NETWORKNUM NETWORK - toaddr $BROADCASTNUM BROADCAST +setup_networking() ( + local distro=$1 + local version=$2 - echo -e "\n# Subnet (IP block) used by Tinkerbell ecosystem \nexport TINKERBELL_NETWORK=$tink_network" >>"$ENV_FILE" - echo -e "\n# Host IP is used by provisioner to expose different services such as tink, boots, etc. \nexport TINKERBELL_HOST_IP=$ip_addr" >>"$ENV_FILE" - echo -e "\n# NGINX IP is used by provisioner to serve files required for iPXE boot \nexport TINKERBELL_NGINX_IP=$nginx_ip" >>"$ENV_FILE" - echo -e "\n# Netmask for Tinkerbell network \nexport TINKERBELL_NETMASK=$NETMASK" >>"$ENV_FILE" - echo -e "\n# Broadcast IP for Tinkerbell network \nexport TINKERBELL_BROADCAST_IP=$BROADCAST" >>"$ENV_FILE" -} + setup_network_forwarding -tonum() { - if [[ $1 =~ ([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+) ]]; then - addr=$(((${BASH_REMATCH[1]} << 24) + (${BASH_REMATCH[2]} << 16) + (${BASH_REMATCH[3]} << 8) + ${BASH_REMATCH[4]})) - eval "$2=\$addr" - fi -} - -toaddr() { - b1=$((($1 & 0xFF000000) >> 24)) - b2=$((($1 & 0xFF0000) >> 16)) - b3=$((($1 & 0xFF00) >> 8)) - b4=$(($1 & 0xFF)) - eval "$2=\$b1.\$b2.\$b3.\$b4" -} - -get_registry_credentials() { - # read for registry username if TB_REGUSER is not defined - if [ -z $TB_REGUSER ]; then - read -p 'Create a Docker registry username [default admin]? ' username - username=${username:-"admin"} - else - username=$TB_REGUSER - fi - password=$(head -c 12 /dev/urandom | sha256sum | cut -d' ' -f1) - echo -e "\n# We host a private Docker registry on provisioner which is used by different workers" >>"$ENV_FILE" - echo -e "# Registry username \nexport TINKERBELL_REGISTRY_USERNAME=$username" >>"$ENV_FILE" - echo -e "\n# Registry password \nexport TINKERBELL_REGISTRY_PASSWORD=$password" >>"$ENV_FILE" - echo "" -} - -generate_envrc() { - # backup existing environment config if any - if [ -f "$ENV_FILE" ]; then - echo "$INFO found existing $ENV_FILE, moving it to $ENV_FILE.bak" - mv "$ENV_FILE" "$ENV_FILE".bak + if is_network_configured; then + echo "$INFO tinkerbell network interface is already configured" + return 0 fi - list_network_interfaces - tink_interface=$(get_tinkerbell_network_interface) - get_tinkerbell_ips - get_registry_credentials - - # the following envs will eventually goaway but are required for now - echo -e "\nexport FACILITY=onprem" >>"$ENV_FILE" - echo -e "export ROLLBAR_TOKEN=ignored" >>"$ENV_FILE" - echo -e "export ROLLBAR_DISABLE=1\n" >>"$ENV_FILE" -} + local strategy + strategy=$(identify_network_strategy "$distro" "$version") -command_exists() { - command -v "$@" >/dev/null 2>&1 -} + "${strategy}" "$distro" "$version" # execute the strategy -setup_docker() { - if command_exists docker; then - echo "$INFO docker already installed, found $(docker -v)" + if is_network_configured; then + echo "$INFO tinkerbell network interface configured successfully" else - if [ -f /etc/redhat-release ] && [ $(. /etc/os-release && echo "$VERSION_ID") == 8 ]; then - echo "$INFO installing docker for CentOS8" - yum install 'dnf-command(config-manager)' -y - dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo - if dnf list docker-ce >>/dev/null; then - dnf install docker-ce --nobest -y - if systemctl start docker; then - systemctl enable docker - echo "$INFO $(docker -v) installed successfully" - else - echo "$ERR docker is not installed successfully" - exit 1 - fi - else - echo "$ERR docker-ce package not found" - exit 1 - fi - else - echo "$INFO installing docker" - curl -L get.docker.com | bash >>/dev/null && echo "$INFO $(docker -v) installed successfully" + echo "$ERR tinkerbell network interface configuration failed" + 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" \ + --arg nginx_ip "$TINKERBELL_NGINX_IP" \ + '{ + network: { + renderer: "networkd", + ethernets: { + ($interface): { + addresses: [ + "\($host_ip)/\($cidr)", + "\($nginx_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 command_exists docker-compose; then - echo "$INFO docker-compose already installed, found $(docker-compose -v)" + 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:0" + echo "$BLANK ifdown $TINKERBELL_NETWORK_INTERFACE:1" + echo "$BLANK ifup $TINKERBELL_NETWORK_INTERFACE:0" + echo "$BLANK ifup $TINKERBELL_NETWORK_INTERFACE:1" + exit 1 else - echo "$INFO installing docker-compose" - curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose + generate_iface_config >>/etc/network/interfaces + ip link set "$TINKERBELL_NETWORK_INTERFACE" nomaster + ifdown "$TINKERBELL_NETWORK_INTERFACE:0" + ifdown "$TINKERBELL_NETWORK_INTERFACE:1" + ifup "$TINKERBELL_NETWORK_INTERFACE:0" + ifup "$TINKERBELL_NETWORK_INTERFACE:1" fi -} - -is_network_configured() { - ip addr show $TINKERBELL_PROVISIONER_INTERFACE | grep $TINKERBELL_HOST_IP >>/dev/null && ip addr show $TINKERBELL_PROVISIONER_INTERFACE | grep $TINKERBELL_NGINX_IP >>/dev/null -} - -write_iface_config() { - iface_config="$( - cat <>/etc/network/interfaces - write_iface_config - else - echo -e "\nauto $TINKERBELL_NETWORK_INTERFACE\n" >>/etc/network/interfaces - write_iface_config - fi - ip link set $TINKERBELL_NETWORK_INTERFACE nomaster - ifdown "$TINKERBELL_NETWORK_INTERFACE:0" - ifdown "$TINKERBELL_NETWORK_INTERFACE:1" - ifup "$TINKERBELL_NETWORK_INTERFACE:0" - ifup "$TINKERBELL_NETWORK_INTERFACE:1" - fi - ;; - centos) - if [ -f /etc/sysconfig/network-scripts/ifcfg-$TINKERBELL_NETWORK_INTERFACE ]; then - sed -i '/^ONBOOT.*no$/s/no/yes/; /^BOOTPROTO.*none$/s/none/static/; /^MASTER/d; /^SLAVE/d' /etc/sysconfig/network-scripts/ifcfg-$TINKERBELL_NETWORK_INTERFACE - else - touch /etc/sysconfig/network-scripts/ifcfg-$TINKERBELL_NETWORK_INTERFACE - HWADDRESS=$(ip addr show $TINKERBELL_NETWORK_INTERFACE | grep ether | awk -F 'ether' '{print $2}' | cut -d" " -f2) - cat <>/etc/sysconfig/network-scripts/ifcfg-$TINKERBELL_NETWORK_INTERFACE +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 <>/etc/sysconfig/network-scripts/ifcfg-$TINKERBELL_NETWORK_INTERFACE IPADDR0=$TINKERBELL_HOST_IP -NETMASK0=$TINKERBELL_NETMASK +PREFIX0=$TINKERBELL_CIDR IPADDR1=$TINKERBELL_NGINX_IP -NETMASK1=$TINKERBELL_NETMASK +PREFIX1=$TINKERBELL_CIDR EOF - ip link set $TINKERBELL_NETWORK_INTERFACE nomaster - ifup $TINKERBELL_NETWORK_INTERFACE - ;; - esac - if is_network_configured; then - echo "$INFO tinkerbell network interface configured successfully" - else - echo "$ERR tinkerbell network interface configuration failed" - fi - else - echo "$INFO tinkerbell network interface is already configured" + ) + + 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 -} -setup_osie() { - osie_current=/var/tinkerbell/nginx/misc/osie/current - tink_workflow=/var/tinkerbell/nginx/workflow/ + 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 /tmp + pushd "$SCRATCH" if [ -z "${TB_OSIE_TAR:-}" ]; then - curl 'https://tinkerbell-oss.s3.amazonaws.com/osie-uploads/latest.tar.gz' -o osie.tar.gz + curl 'https://tinkerbell-oss.s3.amazonaws.com/osie-uploads/latest.tar.gz' -o ./osie.tar.gz tar -zxf osie.tar.gz else tar -zxf "$TB_OSIE_TAR" fi - if pushd /tmp/osie*/; then + if pushd osie*/; then if mv workflow-helper.sh workflow-helper-rc "$tink_workflow"; then cp -r ./* "$osie_current" - rm /tmp/latest -rf else echo "$ERR failed to move 'workflow-helper.sh' and 'workflow-helper-rc'" exit 1 fi popd fi - popd - rm -f /tmp/osie.tar.gz 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 }}') -check_container_status() { - docker ps | grep "$1" >>/dev/null - if [ "$?" -ne "0" ]; then - echo "$ERR failed to start container $1" + 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 - fi -} - -gen_certs() { - sed -i -e "s/localhost\"\,/localhost\"\,\n \"$TINKERBELL_HOST_IP\"\,/g" "$deploy"/tls/server-csr.in.json - docker-compose -f "$deploy"/docker-compose.yml up --build -d certs - sleep 2 - docker ps -a | grep certs | grep "Exited (0)" >>/dev/null - if [ "$?" -eq "0" ]; then - sleep 2 - else - echo "$ERR failed to generate certificates" + ;; + *) + echo "$ERR $container_name is already running but its state is a mystery. status: $current_status" + exit 1 + ;; + esac + + local status + read 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 - certs_dir="/etc/docker/certs.d/$TINKERBELL_HOST_IP" - if [ ! -d "$certs_dir" ]; then - mkdir -p "$certs_dir" + 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 - # update host to trust registry certificate - cp "$deploy"/certs/ca.pem "$certs_dir"/ca.crt - # copy public key to NGINX for workers - cp "$deploy"/certs/ca.pem /var/tinkerbell/nginx/workflow/ca.pem -} + docker build --tag "tinkerbell-certs" "$DEPLOYDIR/tls" + docker run --rm \ + --volume "$STATEDIR/certs:/certs" \ + --user "$UID:$(id -g)" \ + tinkerbell-certs -generate_certificates() { - deploy="$(pwd)"/deploy - if [ ! -d "$deploy"/tls ]; then - echo "$ERR directory 'tls' does not exist" - exit 1 + 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 - if [ -d "$deploy"/certs ]; then - echo "$WARN found certs directory" - if grep -q "\"$TINKERBELL_HOST_IP\"" "$deploy"/tls/server-csr.in.json; then - echo "$WARN found server entry in TLS" - echo "$INFO found existing certificates for host $TINKERBELL_HOST_IP, skipping certificate generation" - else - gen_certs + # update host to trust registry certificate + if ! cmp --quiet "$STATEDIR/certs/ca.pem" "$certs_dir/tinkerbell.crt"; then + 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 - else - gen_certs fi -} +) -start_registry() { - docker-compose -f "$(pwd)"/deploy/docker-compose.yml up --build -d registry - sleep 5 +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" +) - # push latest worker image to registry - docker pull quay.io/tinkerbell/tink-worker:latest - docker tag quay.io/tinkerbell/tink-worker:latest "$TINKERBELL_HOST_IP"/tink-worker:latest - docker pull fluent/fluent-bit:1.3 - docker tag fluent/fluent-bit:1.3 "$TINKERBELL_HOST_IP"/fluent-bit:1.3 - echo -n "$TINKERBELL_REGISTRY_PASSWORD" | docker login -u="$TINKERBELL_REGISTRY_USERNAME" --password-stdin "$TINKERBELL_HOST_IP" - docker push "$TINKERBELL_HOST_IP"/tink-worker:latest - docker push "$TINKERBELL_HOST_IP"/fluent-bit:1.3 -} +# This function supposes that the registry is up and running. +# It configures with the required dependencies. +bootstrap_docker_registry() ( + docker_login + + docker_mirror_image "quay.io/tinkerbell/tink-worker:latest" "${TINKERBELL_HOST_IP}/tink-worker:latest" + docker_mirror_image "fluent/fluent-bit:1.3" "${TINKERBELL_HOST_IP}/fluent-bit:1.3" +) -setup_docker_registry() { - registry_images=/var/tinkerbell/registry +setup_docker_registry() ( + local registry_images="$STATEDIR/registry" if [ ! -d "$registry_images" ]; then mkdir -p "$registry_images" fi - if [ -f ~/.docker/config.json ]; then - if grep -q "$TINKERBELL_HOST_IP" ~/.docker/config.json; then - echo "$INFO found existing docker auth token for registry $TINKERBELL_HOST_IP, using existing registry" - else - start_registry - fi - else - start_registry - fi -} + start_registry + bootstrap_docker_registry +) -start_components() { - components=(db cacher hegel tink-server boots tink-cli nginx kibana) +start_components() ( + local components=(db cacher hegel tink-server boots tink-cli nginx kibana) for comp in "${components[@]}"; do - docker-compose -f "$(pwd)"/deploy/docker-compose.yml up --build -d "$comp" + docker-compose -f "$DEPLOYDIR/docker-compose.yml" up --build -d "$comp" sleep 3 check_container_status "$comp" done -} +) -check_prerequisites() { - echo "$INFO verifying prerequisites" - case "$1" in - ubuntu) - if command_exists git; then - echo "$BLANK- git already installed, found $(git --version)" - else - echo "$BLANK- updating packages" - apt-get update >>/dev/null - echo "$BLANK- installing git" - apt-get install -y --no-install-recommends git >>/dev/null - echo "$BLANK- $(git --version) installed successfully" - fi - if command_exists ifdown; then - echo "$BLANK- ifupdown already installed" - else - echo "$BLANK- installing ifupdown" - apt-get install -y ifupdown >>/dev/null && echo "$BLANK- ifupdown installed successfully" - fi +command_exists() ( + command -v "$@" >/dev/null 2>&1 +) + +check_command() ( + if command_exists "$1"; then + echo "$BLANK Found prerequisite: $1" + return 0 + else + echo "$ERR Prerequisite command not installed: $1" + return 1 + fi +) + +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 git || 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 ;; - centos) - if command_exists git; then - echo "$BLANK- git already installed, found $(git --version)" - else - echo "$BLANK- updating packages" - yum update -y >>/dev/null - echo "$BLANK- installing git" - yum install -y git >>/dev/null - echo "$BLANK- $(git --version) installed successfully" - fi + "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 - setup_docker - # get resources - echo "$INFO getting https://github.com/tinkerbell/tink for latest artifacts" - if [ -d tink ]; then - cd tink - git checkout master && git pull >>/dev/null - else - git clone --single-branch -b master https://github.com/tinkerbell/tink - cd tink + if [ $failed -eq 1 ]; then + echo "$ERR Prerequisites not met. Please install the missing commands and re-run $0." + exit 1 fi - # TODO: verify if all required ports are available -} +) -whats_next() { - echo "$NEXT With the provisioner setup successfully, you can now try executing your first workflow." - echo "$BLANK Follow the steps described in https://tinkerbell.org/examples/hello-world/ to say 'Hello World!' with a workflow." -} +whats_next() ( + echo "$NEXT 1. Enter ./deploy and run: source ../envrc; docker-compose up" + 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() { +do_setup() ( # perform some very rudimentary platform detection lsb_dist=$(get_distribution) - lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" + lsb_version=$(get_distro_version) echo "$INFO starting tinkerbell stack setup" - check_prerequisites "$lsb_dist" - generate_envrc - source $ENV_FILE + check_prerequisites "$lsb_dist" "$lsb_version" - # Run setup for each distro accordingly - case "$lsb_dist" in - ubuntu) - setup_networking "$lsb_dist" - setup_osie - generate_certificates - setup_docker_registry - start_components - echo "" - until docker-compose -f "$(pwd)"/deploy/docker-compose.yml ps; do - sleep 3 - echo "" - done - echo "$INFO tinkerbell stack setup completed successfully on $lsb_dist server" - whats_next - exit 0 - ;; - centos) - systemctl start docker - # enable IP forwarding for docker - echo "net.ipv4.ip_forward=1" >>/etc/sysctl.conf - setup_networking "$lsb_dist" - setup_osie - generate_certificates - setup_docker_registry - start_components - until docker-compose -f "$(pwd)"/deploy/docker-compose.yml ps; do - sleep 3 - echo "" - done - echo "$INFO tinkerbell stack setup completed successfully on $lsb_dist server" - whats_next - exit 0 - ;; - *) - echo - echo "$ERR unsupported distribution '$lsb_dist'" - echo + if [ ! -f "$ENV_FILE" ]; then + echo "$ERR Run './generate-envrc.sh network-interface > \"$ENV_FILE\"' before continuing." exit 1 - ;; - esac - echo "$INFO tinkerbell stack setup failed on $lsb_dist server" - 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 +) # wrapped up in a function so that we have some protection against only getting # half the file during "curl | sh" diff --git a/shell.nix b/shell.nix index 5b68f8290..ea1a2e519 100644 --- a/shell.nix +++ b/shell.nix @@ -13,6 +13,7 @@ mkShell { buildInputs = [ gnumake go + jq nodePackages.prettier pythonPackages.codespell shfmt