Skip to content

Instantly share code, notes, and snippets.

@islishude
Last active April 17, 2026 05:58
Show Gist options
  • Select an option

  • Save islishude/23a795015744bd9d62e31299ced573e7 to your computer and use it in GitHub Desktop.

Select an option

Save islishude/23a795015744bd9d62e31299ced573e7 to your computer and use it in GitHub Desktop.
Mount EBS and install golang/aws-cli/docker
#cloud-config
repo_update: true
repo_upgrade: all
write_files:
- path: /tmp/mount-ebs.sh
permissions: "0755"
content: |
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE=/var/log/mount-ebs.log
MAX_WAIT_SECONDS=${MOUNT_EBS_MAX_WAIT_SECONDS:-300}
POLL_INTERVAL_SECONDS=${MOUNT_EBS_POLL_INTERVAL_SECONDS:-2}
STABLE_PASSES_REQUIRED=${MOUNT_EBS_STABLE_PASSES_REQUIRED:-3}
exec > >(
while IFS= read -r line; do
printf '[%s] %s\n' "$(date --iso-8601=seconds)" "$line"
done | tee -a "$LOG_FILE"
) 2>&1
log() {
echo "$*"
}
fail() {
log "ERROR: $*"
exit 1
}
command_exists() {
command -v "$1" >/dev/null 2>&1
}
mount_point_in_use() {
local mount_point=$1
if awk -v mount_point="$mount_point" '$1 !~ /^#/ && $2 == mount_point { found = 1 } END { exit found ? 0 : 1 }' /etc/fstab; then
return 0
fi
mountpoint -q "$mount_point"
}
next_mount_point() {
local index=1
local mount_point
while :; do
mount_point=/data
if [ "$index" -gt 1 ]; then
mount_point="/data${index}"
fi
if ! mount_point_in_use "$mount_point"; then
printf '%s\n' "$mount_point"
return 0
fi
index=$((index + 1))
done
}
list_candidate_disks() {
lsblk -dn -o NAME,TYPE | awk -v root_device="$ROOT_DEVICE" '$2 == "disk" && $1 != root_device { print $1 }' | sort
}
wait_for_candidate_disks() {
local deadline=$((SECONDS + MAX_WAIT_SECONDS))
local previous_snapshot=
local stable_passes=0
local current_snapshot=
local -a detected_disks=()
while [ "$SECONDS" -lt "$deadline" ]; do
if command_exists udevadm; then
udevadm settle || true
fi
mapfile -t detected_disks < <(list_candidate_disks)
current_snapshot=$(printf '%s ' "${detected_disks[@]}")
current_snapshot=${current_snapshot%% }
if [ -n "$current_snapshot" ]; then
if [ "$current_snapshot" = "$previous_snapshot" ]; then
stable_passes=$((stable_passes + 1))
else
stable_passes=1
previous_snapshot=$current_snapshot
fi
log "Detected non-root disks: ${current_snapshot:-<none>} (stable pass ${stable_passes}/${STABLE_PASSES_REQUIRED})"
if [ "$stable_passes" -ge "$STABLE_PASSES_REQUIRED" ]; then
CANDIDATE_DISKS=("${detected_disks[@]}")
return 0
fi
else
stable_passes=0
previous_snapshot=
log "No non-root disks detected yet; retrying in ${POLL_INTERVAL_SECONDS}s"
fi
sleep "$POLL_INTERVAL_SECONDS"
done
return 1
}
ensure_filesystem_tools() {
if ! command_exists mkfs.xfs; then
log "Installing xfsprogs because mkfs.xfs is missing"
apt-get update -y
apt-get install -y xfsprogs
fi
}
mount_disk() {
local disk_name=$1
local disk_path="/dev/${disk_name}"
local data_device
local filesystem_type
local data_uuid
local mount_point
local existing_mount_point
log "Processing disk ${disk_path}"
lsblk -fp "$disk_path" || true
data_device=$(lsblk -nrpo NAME,TYPE "$disk_path" | awk '$2 == "part" { print $1; exit }')
if [ -z "$data_device" ]; then
data_device="$disk_path"
fi
log "Using block device ${data_device} for ${disk_path}"
filesystem_type=$(blkid -s TYPE -o value "$data_device" || true)
if [ -z "$filesystem_type" ]; then
ensure_filesystem_tools
log "No filesystem detected on ${data_device}; creating xfs"
mkfs.xfs -f "$data_device"
filesystem_type=xfs
else
log "Detected filesystem ${filesystem_type} on ${data_device}"
fi
data_uuid=$(blkid -s UUID -o value "$data_device" || true)
if [ -z "$data_uuid" ]; then
fail "Unable to determine filesystem UUID for ${data_device}"
fi
existing_mount_point=$(awk -v uuid="$data_uuid" '$1 == "UUID=\"" uuid "\"" { print $2; exit }' /etc/fstab)
if [ -n "$existing_mount_point" ]; then
mount_point=$existing_mount_point
log "Reusing existing mount point ${mount_point} for UUID ${data_uuid}"
else
mount_point=$(next_mount_point)
log "Selected new mount point ${mount_point} for UUID ${data_uuid}"
fi
mkdir -p "$mount_point"
if ! awk -v uuid="$data_uuid" -v mount_point="$mount_point" '$1 == "UUID=\"" uuid "\"" && $2 == mount_point { found = 1 } END { exit found ? 0 : 1 }' /etc/fstab; then
printf 'UUID="%s" %s %s defaults,nofail 0 2\n' "$data_uuid" "$mount_point" "$filesystem_type" >> /etc/fstab
log "Added fstab entry for UUID ${data_uuid} at ${mount_point}"
else
log "fstab entry for UUID ${data_uuid} at ${mount_point} already exists"
fi
if mountpoint -q "$mount_point"; then
log "${mount_point} is already mounted"
else
log "Mounting ${mount_point}"
mount "$mount_point"
fi
}
log "mount-ebs bootstrap starting"
log "Configuration: MAX_WAIT_SECONDS=${MAX_WAIT_SECONDS}, POLL_INTERVAL_SECONDS=${POLL_INTERVAL_SECONDS}, STABLE_PASSES_REQUIRED=${STABLE_PASSES_REQUIRED}"
log "Initial lsblk snapshot:"
lsblk -fp || true
log "Initial blkid snapshot:"
blkid || true
ROOT_PARTITION=$(realpath "$(findmnt -nro SOURCE /)")
if [ ! -b "$ROOT_PARTITION" ]; then
fail "Unable to resolve root block device from $ROOT_PARTITION"
fi
ROOT_DEVICE=$(lsblk -dn -o PKNAME "$ROOT_PARTITION")
if [ -z "$ROOT_DEVICE" ]; then
ROOT_DEVICE=$(basename "$ROOT_PARTITION")
fi
ROOT_DEVICE=${ROOT_DEVICE#/dev/}
if [ -z "$ROOT_DEVICE" ]; then
fail "Unable to determine root disk for $ROOT_PARTITION"
fi
log "Resolved root partition ${ROOT_PARTITION} and root disk ${ROOT_DEVICE}"
declare -a CANDIDATE_DISKS=()
if ! wait_for_candidate_disks; then
log "Timed out waiting for non-root disks after ${MAX_WAIT_SECONDS}s"
log "Final lsblk snapshot before exit:"
lsblk -fp || true
exit 0
fi
log "Stable disk set detected: ${CANDIDATE_DISKS[*]}"
for disk_name in "${CANDIDATE_DISKS[@]}"; do
mount_disk "$disk_name"
done
log "Final lsblk snapshot after mounting:"
lsblk -fp || true
log "mount-ebs bootstrap completed"
- path: /tmp/install-docker.sh
permissions: "0775"
content: |
#!/usr/bin/env bash
set -euo pipefail
command -v docker >/dev/null 2>&1 && exit 0 || echo "continuing with Docker installation"
# Add Docker's official GPG key:
apt-get update -y
apt-get install ca-certificates curl jq -y
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update -y
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Add ubuntu user to docker group
usermod -aG docker ubuntu
# Add docker-compose
DOCKER_COMPOSE_VERSION=`curl -sSL -H 'Accept: application/json' https://github.com/docker/compose/releases/latest | jq -r .tag_name`
curl -sSLf "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-`uname -m`" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
- path: /tmp/install-golang.sh
permissions: "0775"
content: |
#!/usr/bin/env bash
set -euo pipefail
command -v go >/dev/null 2>&1 && exit 0 || echo "continuing with Go installation"
apt-get update -y
apt-get install -y build-essential curl
GO_VERSION=`curl -sSLf 'https://go.dev/VERSION?m=text' | head -n 1`
arch=$(uname -m)
if [ "$arch" = "x86_64" ]; then
arch="amd64"
elif [ "$arch" = "aarch64" ]; then
arch="arm64"
else
echo "Unsupported architecture: $arch"
exit 1
fi
curl -sSLf https://go.dev/dl/${GO_VERSION}.linux-${arch}.tar.gz | tar -C /usr/local -xz
echo 'export PATH=$PATH:$HOME/go/bin:/usr/local/go/bin' | tee -a /etc/profile
- path: /tmp/install-aws-cli.sh
permissions: "0775"
content: |
#!/usr/bin/env bash
set -euo pipefail
command -v aws >/dev/null 2>&1 && exit 0 || echo "continuing with AWS CLI installation"
apt-get update -y
apt-get install -y curl unzip
curl -sSLf "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install -i /usr/local/aws-cli -b /usr/local/bin
rm -rf aws awscliv2.zip
runcmd:
- ["/tmp/install-docker.sh"]
- ["/tmp/install-aws-cli.sh"]
- ["/tmp/install-golang.sh"]
- ["/tmp/mount-ebs.sh"]
output: { all: "| tee -a /var/log/cloud-init-output.log" }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment