#!/bin/sh
# bty-state-init - format a target disk for bty's state and mount it.
#
# Usage: bty-state-init [--yes] /dev/sdX
#
# Wipes, partitions, formats (ext4, label ``BTY_IMAGE_STORE``), and
# mounts ``DEVICE`` at ``/var/lib/bty`` so bty's durable state
# (state.db, images/, boot/, backups/, session-secret) lives on its
# own disk -- the foundation that makes an OS reflash non-destructive.
#
# Sister tool to ``bty-state-migrate``: same wipe + format + fstab +
# mount flow, but does NOT copy an existing /var/lib/bty onto the
# disk. Use this when you're preparing a FRESH disk for a fresh
# appliance (or after deciding to reset bty entirely); use
# ``bty-state-migrate`` when you want to relocate an existing state
# directory.
#
# Populate step: bty-web's lifespan stamps ``state.db`` + writes
# ``images/.bty-storage.json`` on first start after the mount, so
# this script intentionally leaves the disk empty (just the
# top-level ``images/`` / ``boot/`` / ``backups/`` directories) --
# one populate path, one place to keep current, no drift between
# the script and the running code.
#
# Safety rails (identical to bty-state-migrate):
#   - Refuses to format the rootfs disk.
#   - Refuses a device with a currently-mounted partition.
#   - No-ops if /var/lib/bty is ALREADY a separate mount.
#   - Confirmation prompt unless ``--yes`` / ``-y`` is given.

set -eu

DEVICE=
ASSUME_YES=
STATE_DIR=/var/lib/bty
LABEL=BTY_IMAGE_STORE

usage() {
    cat <<'EOF'
Usage: bty-state-init [--yes|-y] DEVICE

Format DEVICE (e.g. /dev/sdb, /dev/nvme1n1) for bty's durable state
and mount it at /var/lib/bty. DEVICE is WIPED, partitioned (GPT,
single partition), formatted ext4 with label BTY_IMAGE_STORE, and
mounted at /var/lib/bty (auto-mounts on subsequent boots).

The mounted directory is left empty; bty-web's first-start
lifespan stamps state.db + writes images/.bty-storage.json so the
populate path lives in one place.

  --yes, -y    skip the confirmation prompt
  -h, --help   show this help and exit
EOF
}

while [ $# -gt 0 ]; do
    case "$1" in
        --yes|-y) ASSUME_YES=1; shift ;;
        --help|-h) usage; exit 0 ;;
        --) shift; break ;;
        -*) echo "bty-state-init: unknown flag: $1" >&2; usage >&2; exit 2 ;;
        *) DEVICE="$1"; shift; break ;;
    esac
done

if [ -z "${DEVICE:-}" ]; then
    echo "bty-state-init: DEVICE argument required" >&2
    usage >&2
    exit 2
fi

if [ "$(id -u)" -ne 0 ]; then
    echo "bty-state-init: must be run as root (try sudo)" >&2
    exit 2
fi

if [ ! -b "$DEVICE" ]; then
    echo "bty-state-init: $DEVICE is not a block device" >&2
    exit 2
fi

# Already a separate mount? Refuse rather than wipe; the operator
# probably meant a different device. bty-state-migrate has the same
# guard but the no-op semantic; for init the safer default is to
# stop and let the operator confirm intent.
if mountpoint -q "$STATE_DIR"; then
    echo "bty-state-init: $STATE_DIR is already a separate mount:" >&2
    findmnt -no SOURCE,FSTYPE "$STATE_DIR" >&2
    echo "Refusing to wipe $DEVICE while the state dir is already on a separate disk." >&2
    echo "If you really want to re-init, unmount $STATE_DIR first." >&2
    exit 2
fi

# Refuse to format the rootfs disk. (Same logic as bty-state-migrate.)
ROOTFS_PART=$(findmnt -no SOURCE / 2>/dev/null || true)
case "$ROOTFS_PART" in
    /dev/nvme*|/dev/mmcblk*)
        ROOTFS_DISK=$(printf '%s' "$ROOTFS_PART" | sed 's/p[0-9]\+$//')
        ;;
    /dev/*)
        ROOTFS_DISK=$(printf '%s' "$ROOTFS_PART" | sed 's/[0-9]\+$//')
        ;;
    *)
        ROOTFS_DISK=
        ;;
esac
if [ -n "$ROOTFS_DISK" ] && [ "$DEVICE" = "$ROOTFS_DISK" ]; then
    echo "bty-state-init: $DEVICE is the rootfs disk; refusing to format" >&2
    exit 2
fi

# Refuse if any partition of DEVICE is currently mounted somewhere.
MOUNTED=$(lsblk -no MOUNTPOINT "$DEVICE" 2>/dev/null | sed '/^[[:space:]]*$/d' || true)
if [ -n "$MOUNTED" ]; then
    echo "bty-state-init: $DEVICE has currently-mounted partitions:" >&2
    printf '  %s\n' $MOUNTED >&2
    echo "Unmount them first, then re-run." >&2
    exit 2
fi

echo
echo "About to:"
echo "  - STOP bty-web.service for the duration of the init"
echo "  - WIPE all existing data on $DEVICE"
echo "  - Create a GPT label + single partition spanning the whole disk"
echo "  - Format with ext4, label $LABEL"
echo "  - Mount the disk at $STATE_DIR (and on every subsequent boot)"
echo "  - Start bty-web.service (lifespan stamps state.db + storage marker)"
echo
echo "DEVICE: $DEVICE"
echo

if [ -z "$ASSUME_YES" ]; then
    printf 'Type "yes" to continue: '
    read -r CONFIRM
    if [ "$CONFIRM" != "yes" ]; then
        echo "aborted." >&2
        exit 1
    fi
fi

echo "Stopping bty-web.service ..."
systemctl stop bty-web.service 2>/dev/null || true

echo "Wiping $DEVICE ..."
wipefs -a "$DEVICE" >/dev/null

echo "Partitioning $DEVICE (GPT, single partition) ..."
parted --script "$DEVICE" mklabel gpt
parted --script "$DEVICE" mkpart bty-state ext4 0% 100%
partprobe "$DEVICE" 2>/dev/null || true
udevadm settle

# Resolve partition device path (``/dev/sda1`` vs ``/dev/nvme0n1p1``).
PART=$(lsblk -lno NAME,TYPE "$DEVICE" | awk '$2 == "part" {print "/dev/" $1; exit}')
if [ -z "${PART:-}" ] || [ ! -b "$PART" ]; then
    echo "bty-state-init: cannot find partition 1 on $DEVICE after partprobe" >&2
    exit 2
fi

echo "Formatting $PART as ext4 (label $LABEL) ..."
mkfs.ext4 -F -L "$LABEL" "$PART" >/dev/null

# Move the existing rootfs $STATE_DIR aside so the new mount lands
# on a fresh empty point. The rootfs copy is discarded (init flow:
# operator chose to start fresh). If $STATE_DIR doesn't exist yet
# (fresh appliance pre-first-boot), this is just the mkdir.
if [ -e "$STATE_DIR" ]; then
    if [ -n "$(ls -A "$STATE_DIR" 2>/dev/null)" ]; then
        DISCARD="${STATE_DIR}.discard.$$"
        echo "Existing $STATE_DIR is non-empty; moving aside to $DISCARD (will be deleted after mount) ..."
        mv "$STATE_DIR" "$DISCARD"
    else
        DISCARD=
    fi
else
    DISCARD=
fi
mkdir -p "$STATE_DIR"

# Add the fstab line if absent. ``nofail`` so a future diskless
# boot doesn't wedge the system.
if ! grep -qE "^LABEL=$LABEL[[:space:]]" /etc/fstab; then
    echo "Adding fstab line ..."
    printf 'LABEL=%s %s ext4 nofail,x-systemd.device-timeout=10s 0 2\n' \
        "$LABEL" "$STATE_DIR" >> /etc/fstab
fi
systemctl daemon-reload

echo "Mounting $PART at $STATE_DIR ..."
mount "$STATE_DIR"

if ! mountpoint -q "$STATE_DIR"; then
    echo "bty-state-init: $STATE_DIR did not mount; aborting" >&2
    # Best-effort restore of any pre-existing rootfs copy.
    if [ -n "$DISCARD" ] && [ -d "$DISCARD" ]; then
        rmdir "$STATE_DIR" 2>/dev/null || true
        mv "$DISCARD" "$STATE_DIR"
    fi
    exit 1
fi

# Discard the pre-init rootfs copy now that the disk mount is live.
if [ -n "$DISCARD" ] && [ -d "$DISCARD" ]; then
    echo "Removing pre-init rootfs copy ..."
    rm -rf "$DISCARD"
fi

# Ownership: bty-web runs as the ``bty`` user; the freshly-mounted
# ext4 belongs to root by default. Match what the appliance bake
# expects so the first lifespan write doesn't EACCES.
if id -u bty >/dev/null 2>&1; then
    chown bty:bty "$STATE_DIR"
fi

echo "Starting bty-web.service ..."
systemctl start bty-web.service 2>/dev/null || true

echo
echo "Done. $STATE_DIR is a fresh BTY_IMAGE_STORE volume on $DEVICE."
echo "bty-web's first-start lifespan stamps state.db + writes"
echo "images/.bty-storage.json so the populate path lives in one place."
echo "Subsequent boots auto-mount the disk."
