Files
spinifex/scripts/images/common/rds-datadir
T
Julian Sommer 2ad9d64c48 feat: add the MariaDB RDS image preset
Adds scripts/images/rds-mariadb/: the manifest with every mariadb package pinned =~11.8, setup.sh, the rds-datadir and rds-agent services, and the static include drop-in that points MariaDB at the generated configuration on the data volume. The AMI carries engine=mariadb, engine-version=11.8 and the data-volume contract tag, so resolveEngineAMI can select it.

The include is named zz- rather than 00-: MariaDB walks an include directory in byte order and takes the last occurrence of a setting, so a digit prefix would sort ahead of Alpine's packaged mariadb-server.cnf and let a distribution default beat a platform-owned one. setup.sh asserts the resulting order against the real directory at build time. The include target is baked empty as well, because MariaDB treats an includedir it cannot open as a fatal defaults-parsing error and a boot with no data volume would otherwise lose the client alongside the server.

The packaged drop-in also sets skip-networking, which would leave the endpoint resolving and the health probe passing while nothing on the customer ENI could connect. setup.sh disables it at the source and asserts no configuration file still does.

The mariadb service is deliberately not enabled: the packaged service only warns about an empty datadir rather than refusing, so an engine enabled before the bootstrap exists would launch mysqld_safe against an unbootstrapped volume.

rds-datadir moves to scripts/images/common/ rather than being copied. Volume selection, format authorization and mount are already engine-neutral; the mount point and owning user are the only per-engine facts and now come from each preset's own service. The script fails closed when either is unset, so no image can inherit another engine's layout by omission.
2026-08-11 13:39:49 +10:00

207 lines
8.5 KiB
Bash

#!/bin/sh
# rds-datadir — select, authorize, prepare, and mount the exact data volume
# assigned by the control plane before rds-init touches the engine's datadir.
#
# Shared by every RDS image preset. The mount point and the OS user that owns it
# are the one engine-specific pair here, and both are supplied by the preset's
# own service rather than defaulted, so no image can inherit another's layout.
set -eu
DATA_MOUNT="${RDS_DATA_MOUNT:-}"
ENGINE_USER="${RDS_ENGINE_USER:-}"
SYS_BLOCK="${RDS_SYS_BLOCK:-/sys/block}"
DEV_DIR="${RDS_DEV_DIR:-/dev}"
MOUNTS_FILE="${RDS_MOUNTS_FILE:-/proc/mounts}"
WAIT_SECS="${RDS_DATA_VOLUME_WAIT:-120}"
FS_LABEL="${RDS_DATA_FS_LABEL:-rds-data}"
HANDOFF_ENV="${RDS_HANDOFF_ENV:-${RDS_HANDOFF_DIR:-/run/spinifex-rds}/bootstrap.env}"
log() { echo "[rds-datadir] $*"; }
fail() { echo "[rds-datadir] ERROR: $*" >&2; exit 1; }
load_handoff() {
[ -f "${HANDOFF_ENV}" ] || fail "no bootstrap handoff at ${HANDOFF_ENV}; refusing to discover or format a volume"
# Root-owned content written with shell-safe quoting by rds-agent.
# shellcheck disable=SC1090
. "${HANDOFF_ENV}"
RDS_DATA_VOLUME_ID="${RDS_DATA_VOLUME_ID:-}"
RDS_DATA_VOLUME_SERIAL="${RDS_DATA_VOLUME_SERIAL:-}"
RDS_VM_GENERATION="${RDS_VM_GENERATION:-}"
RDS_FORMAT_AUTHORIZED="${RDS_FORMAT_AUTHORIZED:-false}"
[ -n "${RDS_DATA_VOLUME_ID}" ] || fail "bootstrap handoff has no RDS_DATA_VOLUME_ID"
[ -n "${RDS_DATA_VOLUME_SERIAL}" ] || fail "bootstrap handoff has no RDS_DATA_VOLUME_SERIAL"
case "${RDS_VM_GENERATION}" in
''|*[!0-9]*|0) fail "bootstrap handoff has invalid RDS_VM_GENERATION '${RDS_VM_GENERATION}'" ;;
esac
case "${RDS_FORMAT_AUTHORIZED}" in
true|false) ;;
*) fail "bootstrap handoff has invalid RDS_FORMAT_AUTHORIZED '${RDS_FORMAT_AUTHORIZED}'" ;;
esac
}
mounted_source() {
awk -v mnt="${DATA_MOUNT}" '$2 == mnt { print $1; found = 1; exit } END { exit !found }' "${MOUNTS_FILE}"
}
# Only an exact serial match is a candidate; other vol* devices are unrelated
# attachments and are deliberately ignored. The loop status must not depend on
# the last device examined, or set -e would abort the caller's wait and report.
data_disks() {
for _p in "${SYS_BLOCK}"/*; do
[ -r "${_p}/serial" ] || continue
_serial=$(cat "${_p}/serial" 2>/dev/null || true)
if [ "${_serial}" = "${RDS_DATA_VOLUME_SERIAL}" ]; then
basename "${_p}"
fi
done
return 0
}
await_data_disk() {
_waited=0
while :; do
_found=$(data_disks)
_count=$(printf '%s\n' "${_found}" | grep -c . || true)
if [ "${_count}" -gt 1 ]; then
fail "found ${_count} devices with expected serial ${RDS_DATA_VOLUME_SERIAL}; refusing to choose one"
fi
# Sysfs appears before the device node. A probe cannot classify a node
# that is not present yet, so both must exist before the wait ends.
if [ "${_count}" = "1" ] && [ -e "${DEV_DIR}/${_found}" ]; then
printf '%s\n' "${_found}"
return 0
fi
if [ "${_waited}" -ge "${WAIT_SECS}" ]; then
fail "volume ${RDS_DATA_VOLUME_ID} with serial ${RDS_DATA_VOLUME_SERIAL} was not attached after ${WAIT_SECS}s"
fi
sleep 1
_waited=$((_waited + 1))
done
}
has_partitions() {
for _part in "${SYS_BLOCK}/$1/$1"*; do
[ -d "${_part}" ] && return 0
done
return 1
}
consume_format_authorization() {
[ "${RDS_FORMAT_AUTHORIZED}" = "true" ] || return 0
_tmp="${HANDOFF_ENV}.consume.$$"
if ! awk '!/^RDS_FORMAT_AUTHORIZED=/' "${HANDOFF_ENV}" > "${_tmp}"; then
rm -f "${_tmp}"
fail "could not remove the consumed format authorization from ${HANDOFF_ENV}"
fi
chmod 0600 "${_tmp}" || {
rm -f "${_tmp}"
fail "could not secure the rewritten bootstrap handoff"
}
mv -f "${_tmp}" "${HANDOFF_ENV}" || {
rm -f "${_tmp}"
fail "could not install the bootstrap handoff without its consumed format authorization"
}
RDS_FORMAT_AUTHORIZED=false
}
main() {
# An unset layout is a broken image, not something to guess at: mounting the
# customer's volume where the engine is not looking would read as an empty
# datadir and bootstrap a second, empty database beside their data.
[ -n "${DATA_MOUNT}" ] ||
fail "RDS_DATA_MOUNT is not set; refusing to guess where this engine's datadir belongs"
[ -n "${ENGINE_USER}" ] ||
fail "RDS_ENGINE_USER is not set; refusing to guess which user owns this engine's datadir"
load_handoff
command -v blkid >/dev/null 2>&1 ||
fail "blkid is not installed; refusing to classify the expected volume"
_disk=$(await_data_disk)
_dev="${DEV_DIR}/${_disk}"
log "volume ${RDS_DATA_VOLUME_ID} generation ${RDS_VM_GENERATION} attached as ${_dev}"
if _mounted=$(mounted_source 2>/dev/null); then
[ "${_mounted}" = "${_dev}" ] ||
fail "${DATA_MOUNT} is mounted from ${_mounted}, not expected device ${_dev}"
consume_format_authorization
log "${DATA_MOUNT} is already mounted from the expected device"
return 0
fi
# Low-level probing exposes partition tables and returns a distinct failure
# for ambiguous signatures. Exit 2 also covers read failures, so stderr must
# be empty before it can be treated as a signature-free device.
_probe_err="${HANDOFF_ENV}.blkid.$$"
umask 077
# A redirection that cannot be created leaves blkid unrun and returns the
# shell's own status, which some shells report as the blank-device exit 2.
# `true` is a regular builtin, so the failure is catchable rather than fatal.
true > "${_probe_err}" ||
fail "could not create the probe error capture at ${_probe_err}; refusing to classify ${_dev}"
if _probe=$(blkid -p -o export "${_dev}" 2>"${_probe_err}"); then
_probe_rc=0
else
_probe_rc=$?
fi
_probe_stderr=$(cat "${_probe_err}" 2>/dev/null || true)
rm -f "${_probe_err}"
if [ "${_probe_rc}" -eq 2 ] && [ -n "${_probe_stderr}" ]; then
fail "could not read ${_dev} while probing it; refusing to classify it as blank"
fi
case "${_probe_rc}" in
0)
_pttype=$(printf '%s\n' "${_probe}" | awk -F= '$1 == "PTTYPE" { print $2 }')
_types=$(printf '%s\n' "${_probe}" | awk -F= '$1 == "TYPE" { print $2 }')
_type_count=$(printf '%s\n' "${_types}" | grep -c . || true)
if [ -n "${_pttype}" ] || has_partitions "${_disk}"; then
fail "${_dev} carries a partition table or partitions; refusing to mount or format it"
fi
[ "${_type_count}" = "1" ] ||
fail "${_dev} has conflicting or incomplete filesystem signatures; refusing to use it"
case "${_types}" in
ext4|xfs) _fstype="${_types}" ;;
*) fail "${_dev} holds unsupported filesystem ${_types}; refusing to use it" ;;
esac
# A grant left after mkfs but before local consumption must not
# survive once the newly recognizable filesystem is observed.
consume_format_authorization
log "${_dev} already holds a supported ${_fstype} filesystem"
;;
2)
if has_partitions "${_disk}"; then
fail "${_dev} carries partitions but no supported whole-disk filesystem; refusing to format it"
fi
[ "${RDS_FORMAT_AUTHORIZED}" = "true" ] ||
fail "${_dev} has no recognizable filesystem and this boot has no matching format authorization"
log "formatting authorized volume ${RDS_DATA_VOLUME_ID} as ext4"
mkfs.ext4 -q -L "${FS_LABEL}" -m 0 "${_dev}" ||
fail "mkfs.ext4 failed on ${_dev}"
# Consume before mount. If mounting later fails, the next run sees
# ext4 and mounts it without receiving another chance to format.
consume_format_authorization
_fstype=ext4
;;
8)
fail "${_dev} has ambiguous or conflicting signatures; refusing to use it"
;;
*)
fail "could not probe ${_dev} (blkid exit ${_probe_rc}); refusing to classify it as blank"
;;
esac
mount -t "${_fstype}" "${_dev}" "${DATA_MOUNT}" ||
fail "could not mount ${_dev} at ${DATA_MOUNT}"
chown "${ENGINE_USER}:${ENGINE_USER}" "${DATA_MOUNT}" ||
fail "could not set ${ENGINE_USER} ownership on ${DATA_MOUNT}"
chmod 0750 "${DATA_MOUNT}"
log "${DATA_MOUNT} ready on ${_dev}"
}
main "$@"