Files
spinifex/scripts/images/rds-postgres/rds-datadir
T
2026-07-29 10:38:22 +10:00

154 lines
5.9 KiB
Bash

#!/bin/sh
# rds-datadir — prepare and mount the instance's data volume at the datadir
# mount point, before rds-init bootstraps the cluster onto it.
#
# The volume is hot-plugged after launch, so it cannot be in /etc/fstab and the
# guest has to discover it. Data volumes are attached as virtio-blk with
# serial = volume-id, and the boot volume carries no serial — so a `vol*` serial
# identifies the data volume and nothing else.
#
# This is the one place in the image that may format a block device, so every
# branch that is not "an empty disk we just attached" refuses:
#
# - a disk carrying a partition table or an existing filesystem is mounted as
# found, never re-made (a replaced VM comes back on its existing volume);
# - more than one candidate disk is an error, not a guess;
# - no candidate within the wait window is an error, and rds-init then fails
# on the missing mount rather than initialising on the boot volume.
set -eu
# Mount point of the data volume. rds-init puts the datadir one level inside it.
DATA_MOUNT="${RDS_DATA_MOUNT:-/var/lib/postgresql}"
PG_USER="${RDS_PG_USER:-postgres}"
SYS_BLOCK="${RDS_SYS_BLOCK:-/sys/block}"
DEV_DIR="${RDS_DEV_DIR:-/dev}"
MOUNTS_FILE="${RDS_MOUNTS_FILE:-/proc/mounts}"
# How long to wait for the attach. The control plane hot-plugs the volume as
# soon as the VM launches, so this covers the boot racing the attach, not an
# attach that never happens.
WAIT_SECS="${RDS_DATA_VOLUME_WAIT:-120}"
# Filesystem label written at format time. Purely diagnostic: discovery is by
# serial, so a volume whose label was changed out of band still mounts.
FS_LABEL="${RDS_DATA_FS_LABEL:-rds-data}"
log() { echo "[rds-datadir] $*"; }
fail() { echo "[rds-datadir] ERROR: $*" >&2; exit 1; }
# An already-mounted DATA_MOUNT makes the whole script a no-op; an OpenRC restart
# must not try to mount twice.
mounted() {
awk -v mnt="${DATA_MOUNT}" '$2 == mnt { found = 1 } END { exit !found }' "${MOUNTS_FILE}"
}
# The boot volume is attached with no serial and so never appears here.
data_disks() {
for _p in "${SYS_BLOCK}"/*; do
[ -r "${_p}/serial" ] || continue
case "$(cat "${_p}/serial" 2>/dev/null)" in
vol*) basename "${_p}" ;;
esac
done
}
# Two disks is a configuration this image cannot resolve safely, so it fails
# rather than picking one.
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} attached data volumes ($(printf '%s\n' "${_found}" | tr '\n' ' ')); expected exactly one"
fi
# The sysfs entry appears before the device node, and blkid on a missing
# node reports the same "no filesystem" a blank disk does — so the wait
# is not over until both exist.
if [ "${_count}" = "1" ] && [ -e "${DEV_DIR}/${_found}" ]; then
printf '%s\n' "${_found}"
return 0
fi
if [ "${_waited}" -ge "${WAIT_SECS}" ]; then
fail "no data volume attached after ${WAIT_SECS}s"
fi
sleep 1
_waited=$((_waited + 1))
done
}
# Prints the filesystem on dev, nothing if it holds none, and returns non-zero
# when it could not find out: a blkid that is missing, errored or pointed at an
# absent node prints exactly what a blank disk prints, so only the status decides.
fstype_of() {
if _out=$(blkid "$1" 2>/dev/null); then
_rc=0
else
_rc=$?
fi
# 0 is a signature found, 2 a clean probe that found none. Anything else
# means the probe did not run, which is not an answer.
[ "${_rc}" -eq 0 ] || [ "${_rc}" -eq 2 ] || return 1
printf '%s' "${_out}" | sed -n 's/.*[[:space:]]TYPE="\([^"]*\)".*/\1/p'
}
# Partitions appear as child directories of the disk in sysfs, which needs no
# tool and no signature guessing.
has_partitions() {
for _part in "${SYS_BLOCK}/$1/$1"*; do
[ -d "${_part}" ] && return 0
done
return 1
}
main() {
if mounted; then
log "${DATA_MOUNT} is already a mount point; nothing to do"
return 0
fi
# Checked before anything else: without it the probe below cannot run, and a
# probe that cannot run looks identical to a blank disk.
command -v blkid >/dev/null 2>&1 ||
fail "blkid is not installed in this image; refusing to tell a blank volume from a customer's data without it"
_disk=$(await_data_disk)
_dev="${DEV_DIR}/${_disk}"
log "data volume attached as ${_dev} (serial $(cat "${SYS_BLOCK}/${_disk}/serial"))"
if ! _fstype=$(fstype_of "${_dev}"); then
fail "could not probe ${_dev} for a filesystem; refusing to decide whether it is blank"
fi
if [ -z "${_fstype}" ]; then
# A partition table with no whole-disk filesystem is somebody else's
# disk — a restored image, a mis-targeted attach — so formatting would
# destroy it.
if has_partitions "${_disk}"; then
fail "${_dev} carries a partition table but no filesystem; refusing to format it"
fi
log "formatting ${_dev} (ext4, label ${FS_LABEL})"
# -m 0: no reserved-for-root blocks. The customer paid for the whole
# volume and nothing but PostgreSQL writes to it.
mkfs.ext4 -q -L "${FS_LABEL}" -m 0 "${_dev}" ||
fail "mkfs.ext4 failed on ${_dev}"
_fstype=ext4
else
log "${_dev} already holds a ${_fstype} filesystem; mounting as found"
fi
mount "${_dev}" "${DATA_MOUNT}" || fail "could not mount ${_dev} at ${DATA_MOUNT}"
# The mount does not inherit the image mount point's ownership: a freshly
# formatted volume's root is root-owned 0755, which initdb (as postgres)
# cannot create the datadir under.
chown "${PG_USER}:${PG_USER}" "${DATA_MOUNT}" ||
fail "could not set ${PG_USER} ownership on ${DATA_MOUNT}"
chmod 0750 "${DATA_MOUNT}"
log "${DATA_MOUNT} ready on ${_dev}"
}
main "$@"