Created
June 6, 2025 06:05
-
-
Save moshiurH/a7813253263740fcd10c8bcda5c1d375 to your computer and use it in GitHub Desktop.
AWS Airgap Prep Script with k0rdent Pre-requisites installed on all the airgapped nodes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# ############################################################################################################ | |
# | |
# This script prepares airgapped nodes deployed with TF stack located within this folder. | |
# | |
# See README.md for more details. | |
# | |
# Usage: ./airgap-prepare.sh | |
# | |
# ############################################################################################################ | |
set -e | |
#comment out if on linux | |
#if ! command -v ggrep 2>&1 >/dev/null | |
#then | |
# echo "ggrep could not be found; install it with 'brew install grep'" | |
# echo "if you're on linux, not macos, find and replace 'ggrep' with 'grep', sorry about that" | |
# exit 1 | |
#fi | |
: "${TMPDIR:=/tmp}" | |
: "${WITH_MKE3:=false}" | |
: "${BUNDLE_NAME:="mke_bundle_dev.tar.gz"}" | |
timestamp() { | |
echo "[INFO] $(date -u "+%Y-%m-%dT%H:%M:%SZ") \"$1\"" | |
} | |
with_retry () { | |
local max_retries=3 | |
local count=0 | |
local sleep_time=5 | |
until "$@"; do | |
count=$((count + 1)) | |
if [ "$count" -ge "$max_retries" ]; then | |
echo "Command failed after $max_retries attempts: $*" | |
return 1 | |
fi | |
sleep $((sleep_time * count)) | |
done | |
} | |
remote_command() { | |
with_retry ssh -F ./.ssh_config "$1" "${@:2}" | |
} | |
join() { | |
local IFS=" " | |
shift | |
echo "$*" | |
} | |
timestamp "Set variables" | |
files_folder=airgap-files | |
binary_name=mkectl-linux | |
ssh_key_path=$(terraform output -raw ssh_key_path) | |
msr_version=v4.0.2 | |
bundle_name=$BUNDLE_NAME | |
remote_home_dir=$(remote_command airgapped-manager-0 "pwd") | |
mke3_version="3.8.4" | |
mke3_bundle_url="https://packages.mirantis.com/caas/ucp_images_$mke3_version.tar.gz" | |
mke3_bundle_name="ucp_images_$mke3_version.tar.gz" | |
mke3_kubectl_version='v1.32.3' | |
skopeo_version="v1.18.0" | |
skopeo_image="quay.io/skopeo/stable:$skopeo_version" | |
skopeo_tar="skopeo_$skopeo_version.tar.gz" | |
run_skopeo="docker run --rm -v $remote_home_dir/.docker/config.json:/config.json -v ./bundle:/bundle $skopeo_image" | |
# following substituion does not work | |
# run_skopeo="/usr/local/bin/skopeo" | |
first_manager_ip=$(yq '.spec.hosts[0].ssh.address' mke4.yaml) | |
ingress_https_port=$(yq '.spec.ingressController.nodePorts.https' mke4.yaml) | |
msr4_port=8080 | |
admin_password=orcaorcaorca | |
registry_hostname="registry.mke4dev.mirantis.net" | |
registry_address="$registry_hostname:$msr4_port" | |
zone_filename="db.$registry_hostname" | |
cosign_version="v2.4.1" | |
helm_version="v3.16.3" | |
kubectl_version="v1.30.0" | |
timestamp "Verifying presence of the expected files" | |
required_files=( | |
"$files_folder/$bundle_name" | |
"$files_folder/harbor/tls.crt" | |
"$files_folder/harbor/tls.key" | |
) | |
for file in "${required_files[@]}"; do | |
if [ ! -f "$file" ]; then | |
echo "$(basename "$file") must be present at $file" | |
echo "See README.md for details" | |
exit 1 | |
fi | |
done | |
timestamp "Compile mkectl for linux" | |
BINARY_NAME=$binary_name make --directory ../.. build-amd64 | |
timestamp "Prepare files for transferring" | |
mkdir -p "./$files_folder" | |
remote_command bastion "mkdir -p ~/$files_folder/docker && mkdir -p ~/$files_folder/skopeo" | |
# rename mkectl-linux to mkectl | |
mv "../../bin/$binary_name" "./$files_folder/mkectl" | |
# rename ssh key | |
cp "$ssh_key_path" "./$files_folder/ssh_key.pem" | |
# replace ssh path in mke4.yaml with the correct path on the remote machine | |
sed "s|$ssh_key_path|$remote_home_dir/ssh_key.pem|g" mke4.yaml > "$files_folder/mke4.yaml" | |
sed "s|$ssh_key_path|$remote_home_dir/ssh_key.pem|g" nodes.yaml > "$files_folder/nodes.yaml" | |
if [[ "$WITH_MKE3" == true ]]; then | |
timestamp "Download kubectl and bundle for MKE 3" | |
remote_command bastion "stat ~/$files_folder/$mke3_bundle_name >/dev/null || wget $mke3_bundle_url -O ~/$files_folder/$mke3_bundle_name" | |
remote_command bastion "curl -L https://dl.k8s.io/release/$mke3_kubectl_version/bin/linux/amd64/kubectl -o ~/$files_folder/kubectl && chmod +x ~/$files_folder/kubectl" | |
fi | |
timestamp "Download required binaries: kubectl, helm, cosign" | |
# Download kubectl | |
remote_command bastion "curl -L https://dl.k8s.io/release/$kubectl_version/bin/linux/amd64/kubectl -o ~/$files_folder/kubectl && chmod +x ~/$files_folder/kubectl" | |
# Download helm | |
remote_command bastion "curl -L https://get.helm.sh/helm-$helm_version-linux-amd64.tar.gz -o ~/$files_folder/helm.tar.gz && \ | |
tar -xzvf ~/$files_folder/helm.tar.gz -C ~/$files_folder && \ | |
mv ~/$files_folder/linux-amd64/helm ~/$files_folder/helm && \ | |
chmod +x ~/$files_folder/helm" | |
# Download cosign | |
remote_command bastion "curl -L https://github.com/sigstore/cosign/releases/download/$cosign_version/cosign-linux-amd64 -o ~/$files_folder/cosign && chmod +x ~/$files_folder/cosign" | |
timestamp "Installing Go on bastion" | |
GO_VERSION="1.24.4" | |
GO_TARBALL="go${GO_VERSION}.linux-amd64.tar.gz" | |
GO_URL="https://go.dev/dl/${GO_TARBALL}" | |
remote_command bastion " | |
set -e | |
echo '[INFO] Downloading Go \$GO_VERSION from ${GO_URL}...' | |
wget -q '${GO_URL}' -O /tmp/${GO_TARBALL} | |
echo '[INFO] Removing previous Go installation if any...' | |
sudo rm -rf /usr/local/go | |
echo '[INFO] Extracting Go...' | |
sudo tar -C /usr/local -xzf /tmp/${GO_TARBALL} | |
echo '[INFO] Updating PATH...' | |
echo 'export PATH=\$PATH:/usr/local/go/bin' >> ~/.bashrc | |
export PATH=\$PATH:/usr/local/go/bin | |
echo '[INFO] Verifying Go installation...' | |
go version | |
" | |
timestamp "Installing skopeo build dependencies (including make) on bastion" | |
remote_command bastion " | |
sudo apt-get update && \ | |
sudo apt-get install -y build-essential pkg-config \ | |
libgpgme-dev libassuan-dev libbtrfs-dev \ | |
libdevmapper-dev libglib2.0-dev libseccomp-dev \ | |
libostree-dev libselinux1-dev uidmap btrfs-progs libgpg-error-dev | |
" | |
timestamp "Compiling required binaries: skopeo" | |
remote_command bastion " | |
export PATH=\$PATH:/usr/local/go/bin && \ | |
cd ~ && \ | |
rm -rf skopeo && \ | |
git clone --depth 1 --branch $skopeo_version https://github.com/containers/skopeo.git && \ | |
cd skopeo && \ | |
make bin/skopeo | |
" | |
remote_command bastion "cp ~/skopeo/bin/skopeo ~/$files_folder/skopeo-bin && chmod +x ~/$files_folder/skopeo-bin" | |
timestamp "Install skopeo on bastion" | |
remote_command bastion "sudo cp ~/$files_folder/skopeo-bin /usr/local/bin/skopeo && sudo chmod +x /usr/local/bin/skopeo" | |
timestamp "Download docker" | |
# TODO: this is a temporary solution until we need to get AMI with everything pre-installed | |
# URL of the HTML page with the list of Docker files | |
HTML_URL="https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/" | |
# Fetch the list of available packages | |
PACKAGE_LIST=$(curl -s "$HTML_URL" | grep -oP '(?<=href=")[^"]+(?=")' | grep -oP '.*\.deb' | sort -Vr) | |
# Function to download a package with a progress bar | |
download_package() { | |
FILENAME=$1 | |
URL="${HTML_URL}${FILENAME}" | |
timestamp "Downloading $FILENAME..." | |
remote_command bastion "wget \"$URL\" -x -O \"$files_folder/docker/$FILENAME\"" | |
} | |
# Download the latest package for each type | |
TYPES=("containerd.io" "docker-ce" "docker-ce-cli" "docker-buildx-plugin" "docker-compose-plugin") | |
PACKAGES=() | |
for TYPE in "${TYPES[@]}"; do | |
PACKAGE=$(echo "$PACKAGE_LIST" | grep "$TYPE" | head -n 1) | |
if [ -n "$PACKAGE" ]; then | |
download_package "$PACKAGE" | |
PACKAGES+=("$PACKAGE") | |
fi | |
done | |
timestamp "Docker packages downloaded successfully!" | |
timestamp "Install Docker on Bastion" | |
remote_command bastion "docker || sudo dpkg -i $(printf " ~/$files_folder/docker/%s" "${PACKAGES[@]}") && sudo usermod -aG docker ubuntu" | |
timestamp "Download skopeo via docker" | |
remote_command bastion "stat ~/$files_folder/$skopeo_tar >/dev/null || docker pull $skopeo_image && docker save $skopeo_image > ~/$files_folder/$skopeo_tar" | |
timestamp "Prepping for additional packages" | |
remote_command bastion "sudo NEEDRESTART_MODE=a apt-get -y update" | |
ADD_PACKAGES=("dpkg-dev" "jq" "zip" "bind9" "bind9utils" "bind9-doc" "net-tools" "passwd") | |
for PKG in "${ADD_PACKAGES[@]}"; do | |
remote_command bastion "sudo NEEDRESTART_MODE=a apt-get -y install $PKG" | |
done | |
remote_command bastion "sudo mkdir -p ~/$files_folder/add_pkg_repo && sudo cp -a /var/cache/apt/archives/*.deb ~/$files_folder/add_pkg_repo && cd ~/$files_folder/add_pkg_repo && sudo dpkg-scanpackages ./ /dev/null | sudo tee ./Packages >/dev/null" | |
timestamp "Download MSR 4" | |
if [ ! -f $files_folder/harbor/harbor.yml ]; then | |
curl -L -o $files_folder/msr4.tar.gz https://s3-us-east-2.amazonaws.com/packages-mirantis.com/msr/harbor-offline-installer-$msr_version.tgz | |
tar -xzf $files_folder/msr4.tar.gz -C $files_folder | |
rm $files_folder/msr4.tar.gz | |
fi | |
timestamp "Prepare MSR 4" | |
if [ ! -f "$files_folder/harbor/harbor.yml" ]; then | |
mv $files_folder/harbor/harbor.yml.tmpl $files_folder/harbor/harbor.yml | |
fi | |
yq -i ".hostname = \"$registry_hostname\"" $files_folder/harbor/harbor.yml | |
yq -i ".data_volume = \"$remote_home_dir/harbor/data\"" $files_folder/harbor/harbor.yml | |
yq -i ".http.port = \"8079\"" $files_folder/harbor/harbor.yml | |
yq -i ".https.port = \"$msr4_port\"" $files_folder/harbor/harbor.yml | |
yq -i ".https.certificate = \"$remote_home_dir/harbor/tls.crt\"" $files_folder/harbor/harbor.yml | |
yq -i ".https.private_key = \"$remote_home_dir/harbor/tls.key\"" $files_folder/harbor/harbor.yml | |
yq -i ".harbor_admin_password = \"$admin_password\"" $files_folder/harbor/harbor.yml | |
yq -i ".spec.registries.chartRegistry.url = \"oci://$registry_address/mke\"" $files_folder/mke4.yaml | |
yq -i ".spec.registries.imageRegistry.url = \"$registry_address/mke\"" $files_folder/mke4.yaml | |
# FIXME: AWS LB is very slow; we need to fix it before we can use it here | |
# until then, replace it with the first manager IP | |
yq -i ".spec.apiServer.externalAddress = \"$first_manager_ip:$ingress_https_port\"" $files_folder/mke4.yaml | |
remote_command airgapped-manager-0 "mkdir -p ~/harbor/data" | |
timestamp "Prepare DNS zone for MSR 4" | |
FIRST_MANAGER_IP="$first_manager_ip" MSR_HOSTNAME="$registry_hostname" envsubst '${FIRST_MANAGER_IP} ${MSR_HOSTNAME}' < "airgap-dns/zone.tpl" > "$files_folder/$zone_filename" | |
timestamp "Transfer files" | |
# transfer all files as one archive using rsync | |
with_retry rsync -a -e "ssh -F ./.ssh_config" --progress "./$files_folder/" airgapped-manager-0:~/ | |
for host in $(yq '.spec.hosts[].ssh.address' mke4.yaml); do | |
with_retry rsync -a -e "ssh -F ./.ssh_config" --progress "./$files_folder/ssh_key.pem" bastion:~/ | |
remote_command bastion "rsync -a -e \"ssh -i ssh_key.pem -o StrictHostKeyChecking=no\" --progress \"$files_folder/\" \"ubuntu@$host:~/\"" | |
done | |
timestamp "Setup nodes" | |
add_zone=$(cat <<EOF | |
zone \"$registry_hostname\" { | |
type master; | |
file \"/etc/bind/zones/db.$registry_hostname\"; | |
}; | |
EOF | |
) | |
default_dns="172.31.0.2" | |
named_conf_options=$(cat <<EOF | |
options { | |
directory \"/var/cache/bind\"; | |
forwarders { | |
$default_dns; | |
}; | |
allow-query { any; }; | |
recursion yes; | |
dnssec-validation no; | |
}; | |
EOF | |
) | |
resolved_conf=$(cat <<EOF | |
DNS=$first_manager_ip | |
FallbackDNS= | |
Domains=~. | |
EOF | |
) | |
hosts=$(cat .ssh_config | grep "airgapped-" | awk '{print $2}') | |
for host in $hosts; do | |
timestamp "Install packages on $host" | |
remote_command "$host" "sudo sh -c \"echo 'deb [trusted=yes] file://$remote_home_dir/add_pkg_repo ./' > /etc/apt/sources.list.d/myrepo.list && rm -f /etc/apt/sources.list && apt-get update -y && apt install -y $(join "${ADD_PACKAGES[@]}") --allow-unauthenticated\"" | |
timestamp "Install Docker on $host" | |
remote_command "$host" "docker || sudo dpkg -i $(printf " ~/docker/%s" "${PACKAGES[@]}") && sudo usermod -aG docker ubuntu" | |
timestamp "Install kubectl, helm, and cosign on $host" | |
remote_command "$host" "sudo mv ~/kubectl /usr/local/bin/kubectl" | |
remote_command "$host" "sudo mv ~/helm /usr/local/bin/helm" | |
remote_command "$host" "sudo mv ~/cosign /usr/local/bin/cosign" | |
remote_command "$host" "sudo mv ~/skopeo-bin /usr/local/bin/skopeo" | |
if [[ "$WITH_MKE3" == true ]]; then | |
remote_command "$host" "docker image inspect mirantis/ucp-dsinfo:3.8.4 >/dev/null || docker load -i ~/$mke3_bundle_name" | |
fi | |
# try resolving registry hostname; the output should contain the first manager IP | |
# if it doesn't, the DNS isn't configured | |
dig_registry=$(remote_command "$host" "dig $registry_hostname @127.0.0.1") | |
if [[ "$dig_registry" != *"$first_manager_ip"* ]] ; then | |
# install and configure DNS server for private registry | |
timestamp "Install and configure DNS server on $host" | |
rsync -a -e "ssh -F ./.ssh_config" --progress "./$files_folder/$zone_filename" "$host:~/" | |
remote_command "$host" "sudo grep \"$registry_hostname\" /etc/bind/named.conf.local || sudo sh -c \"echo '$add_zone' > /etc/bind/named.conf.local\"" | |
remote_command "$host" "stat '/etc/bind/zones/$zone_filename' >/dev/null || sudo mkdir -p /etc/bind/zones && sudo cp '$remote_home_dir/$zone_filename' '/etc/bind/zones/$zone_filename'" | |
remote_command "$host" "sudo grep \"$default_dns\" /etc/bind/named.conf.options || sudo sh -c \"echo '$named_conf_options' > /etc/bind/named.conf.options\"" | |
remote_command "$host" "sudo systemctl restart bind9" | |
remote_command "$host" "sudo grep \"$first_manager_ip\" /etc/systemd/resolved.conf || sudo sh -c \"echo '$resolved_conf' >> /etc/systemd/resolved.conf\"" | |
remote_command "$host" "sudo systemctl restart systemd-resolved" | |
fi | |
if [[ "$host" == "airgapped-manager-0" ]]; then | |
timestamp "Install skopeo on $host" | |
remote_command "$host" "docker image inspect $skopeo_image >/dev/null || docker load -i ~/$skopeo_tar" | |
fi | |
done | |
# move binaries to /usr/local/bin | |
remote_command airgapped-manager-0 "sudo mv ~/mkectl /usr/local/bin/mkectl" | |
timestamp "Setup MSR 4 and create 'mke' project" | |
msr_exists=$(remote_command airgapped-manager-0 "[[ \$(docker ps -f name=harbor-core -q) ]] && echo 'exists' || echo 'please install'") | |
if [[ "$msr_exists" == "exists" ]]; then | |
timestamp "MSR 4 is already installed. Skipping installation." | |
else | |
remote_command airgapped-manager-0 "cd ~/harbor && sudo ./install.sh" | |
# let MSR 4 start | |
sleep 10 | |
fi | |
remote_command airgapped-manager-0 "curl -s -X POST -H \"Content-Type: application/json\" \ | |
-d '{\"project_name\": \"mke\",\"public\": true}' \ | |
https://admin:$admin_password@$registry_address/api/v2.0/projects || true" | |
timestamp "Unpack MKE 4 charts and images" | |
#removed --multi-arch all flag | |
remote_command airgapped-manager-0 "docker login $registry_address -u admin -p $admin_password && \ | |
tar -xzf $bundle_name -C ./ && \ | |
for archive in \$(find ./bundle -print | grep \".tar\"); do \ | |
img=\$(basename \"\$archive\" | sed 's~\.tar~~' | tr '&' '/' | tr '@' ':'); \ | |
echo \"Uploading \$img\"; \ | |
$run_skopeo copy --authfile=/config.json -q --retry-times 3 --multi-arch all oci-archive:\${archive#\".\"} docker://$registry_address/mke/\$img; \ | |
done;" | |
echo '' | |
echo '------------------------------- SUCCESS!!! -------------------------------' | |
echo '' | |
echo "Here are some handy commands for interacting with your airgapped environment:" | |
echo "- 'ssh -F ./.ssh_config airgapped-manager-0' to connect to the manager node" | |
echo "- 'ssh -F ./.ssh_config -L \"8080:localhost:$msr4_port\" ubuntu@airgapped-manager-0' to port-forward MSR 4 and access it at https://localhost:8080" | |
echo "- Airgap Installation: SSH into airgapped-manager-0 and run 'mkectl apply'" | |
echo "- Airgap Upgrade: Follow the instructions in the README.md file to install MKE 3 and upgrade to MKE 4" | |
echo "- When MKE 4 is running, use 'ssh -F ./.ssh_config -L \"3000:localhost:$ingress_https_port\" ubuntu@airgapped-manager-0' to port-forward MKE 4 Dashboard and access it at https://localhost:3000" | |
echo '' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment