mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 02:43:17 +00:00
154 lines
5.9 KiB
Bash
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 "$@"
|