Last active
April 17, 2026 05:58
-
-
Save islishude/23a795015744bd9d62e31299ced573e7 to your computer and use it in GitHub Desktop.
Mount EBS and install golang/aws-cli/docker
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
| #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