#cloud-config # First-boot bootstrap: base packages + Docker + git. App layer via ansible. package_update: true packages: - apt-transport-https - ca-certificates - curl - gnupg - lsb-release - jq - unzip - git write_files: # Docker installation - path: /tmp/install-docker.sh permissions: "0755" content: | #!/bin/bash set -euo pipefail curl -fsSL https://get.docker.com -o /tmp/get-docker.sh sh /tmp/get-docker.sh systemctl enable docker systemctl start docker usermod -aG docker ubuntu # Docker daemon config (log rotation) - path: /etc/docker/daemon.json content: | { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "storage-driver": "overlay2" } # Encrypted-at-rest data volume setup (runs BEFORE Docker is installed). # Graceful no-op when no volume is attached (data_volume_size_gb = 0 default # for aibook-fun stateless marketing site). - path: /tmp/setup-data-volume.sh permissions: "0755" content: | #!/bin/bash set -euo pipefail DEV="/dev/disk/by-id/scsi-0DO_Volume_aibook-fun-data" MNT="/mnt/${project_name}_data" for i in $(seq 1 24); do [ -e "$DEV" ] && break sleep 5 done if [ ! -e "$DEV" ]; then echo "no data volume attached after 120s - using droplet local disk (stateless mode)" exit 0 fi blkid "$DEV" >/dev/null 2>&1 || mkfs.ext4 -q "$DEV" mkdir -p "$MNT" grep -q "scsi-0DO_Volume_aibook-fun-data" /etc/fstab || \ echo "$DEV $MNT ext4 defaults,noatime,nofail,discard 0 2" >> /etc/fstab mountpoint -q "$MNT" || mount "$MNT" mkdir -p "$MNT/docker" jq --arg dr "$MNT/docker" '. + {"data-root": $dr}' /etc/docker/daemon.json > /tmp/daemon.json.new mv /tmp/daemon.json.new /etc/docker/daemon.json mkdir -p /opt/${project_name} echo "data volume mounted at $MNT (docker data-root relocated)" runcmd: # Data volume setup only when a block volume is attached - 'bash -c "[ -e /dev/disk/by-id/scsi-0DO_Volume_aibook-fun-data ] && bash /tmp/setup-data-volume.sh || echo no-data-volume"' - [bash, /tmp/install-docker.sh] - [systemctl, restart, docker] # Ensure the app dir exists for ansible to land the compose + Caddyfile - [mkdir, -p, /opt/${project_name}] # Marker so ansible/deploy.yml can confirm the host is ready - [touch, /opt/${project_name}/.bootstrap-complete] # Enable automatic security updates - dpkg-reconfigure -plow unattended-upgrades final_message: | aibook-fun bootstrap complete. Next step from the operator workstation: ansible-playbook aibook-fun/ansible/deploy.yml -i 'root@,' Ansible will clone the repo, build the Docker image locally on this droplet, and bring up the compose stack. Site served at https://aibook.fun.