#!/bin/bash
#===============================================================================
# felhom-backup-target-apply — the ONLY path the felhom-agent sudoers permits for creating the
# whole-guest backup TARGET storage and granting the agent access to it (E-2a).
#
# WHY A WRAPPER AT ALL. Creating a PVE storage needs `Datastore.Allocate` at `/storage`, and the ACL
# grant needs `Permissions.Modify`. The agent holds NEITHER by design — its token is scoped per
# storage path for blast-radius containment, and `Permissions.Modify` would let it rewrite its own
# authority. Widening the PVE role to make the move possible would trade the entire containment model
# for one feature. So the privileged half lives here: a minimal, auditable root shim with a fixed
# vocabulary, exactly like felhom-mkfs-guarded and felhom-pbs-apply.
#
# THE NO-DELETE LAW (inherited from felhom-pbs-apply, same reasoning class). This wrapper contains NO
# storage-removal path of any kind. `pvesm remove` on a dir storage does not delete the archives, but
# it DOES silently orphan a configured backup tier, and a "cleanup" verb here would be reachable by
# any bug in the agent. Retiring a target is a deliberate operator op, not this tool. Grep-assertable;
# do not add one.
#
# THE TWO LAWS E-1 PAID FOR ON LIVE HARDWARE, both enforced here rather than trusted to the caller:
#
#   F-1  the storage path must BE the drive's own mountpoint. A subdirectory fails the agent's
#        exactMount check, so the target reports `disconnected` FOREVER and its durable id degrades
#        off the filesystem UUID. Enforced: `mountpoint -q` must pass on the exact path given.
#
#   F-2  --is_mountpoint 1 is not optional. Without it, an unplugged or late-mounting drive leaves a
#        bare directory on the ROOT filesystem and vzdump writes the whole-guest backup onto the
#        system drive — the exact device the whole change exists to escape — while PVE reports the
#        storage `active` and advertises the root filesystem's free space. Proven live: the unguarded
#        form had already created dump/ on pve-root. Hardcoded below; not a caller-supplied flag.
#
# Ops (all non-secret; nothing here touches a credential, so nothing arrives on stdin):
#   create <id> <mountpoint>
#           Create a `dir` storage with content=backup at <mountpoint>, is_mountpoint 1.
#           IDEMPOTENT: an existing entry with the SAME path is accepted (re-run safe, and the
#           installer re-run path depends on it). An existing entry with a DIFFERENT path is REFUSED
#           — silently repointing a live backup target is the failure this whole arc closes.
#   grant  <id>
#           The dual grant: FelhomAgentStore on /storage/<id> to the agent user AND token (privsep
#           intersection — a token's rights are the intersection, so granting one is granting neither).
#           Without it every backup 403s on first run (E-1 finding F-3, found by the first real backup).
#===============================================================================
set -euo pipefail

die() { echo "felhom-backup-target-apply: REFUSED: $*" >&2; exit 1; }

op="${1:-}"; id="${2:-}"
[[ -n "$op" && -n "$id" ]] || die "usage: felhom-backup-target-apply <create|grant> <storage-id> [mountpoint]"

# Storage id: PVE grammar, conservative. Also the ACL path component — no slashes possible.
[[ "$id" =~ ^[A-Za-z][A-Za-z0-9_.-]{0,27}$ ]] || die "bad storage id ($id)"

STORECFG=/etc/pve/storage.cfg

# current_path_of <id> — the configured `path` of dir storage <id>, or "" when absent/not-a-dir.
current_path_of() {
  awk -v want="dir: $1" '
    $0 == want { found=1; next }
    found && /^[a-z]+: / { exit }
    found && $1 == "path" { print $2; exit }
  ' "$STORECFG" 2>/dev/null || true
}

case "$op" in
create)
  [[ $# -eq 3 ]] || die "create takes <id> <mountpoint>"
  mp="$3"
  # Absolute, normalized, no traversal, no shell metacharacters. The value reaches pvesm and the
  # filesystem, so it is validated here rather than assumed well-formed.
  [[ "$mp" = /* ]]            || die "mountpoint must be absolute ($mp)"
  [[ "$mp" != *".."* ]]       || die "mountpoint must not contain .. ($mp)"
  [[ "$mp" =~ ^[A-Za-z0-9/_.-]+$ ]] || die "mountpoint has unexpected characters ($mp)"
  [[ "$mp" != "/" ]]          || die "refusing / as a backup target"

  # F-1 + F-2, checked as one: the path must BE a mountpoint right now. A bare directory here is
  # precisely the silent-retarget shape, and is_mountpoint would make PVE refuse it later anyway —
  # better to refuse now, with a reason, than to create a storage that can never activate.
  mountpoint -q "$mp" || die "$mp is not a mountpoint — the backup target must be the drive's OWN mountpoint (F-1), and an unmounted path would silently retarget onto the system drive (F-2)"

  # Never the system disk: a target on the root filesystem is not drive-loss protection, it is the
  # thing we are escaping. The root device and the candidate's device are compared, not their paths.
  root_dev="$(findmnt -no SOURCE / 2>/dev/null || true)"
  mp_dev="$(findmnt -no SOURCE "$mp" 2>/dev/null || true)"
  [[ -n "$mp_dev" ]] || die "could not resolve the backing device of $mp"
  [[ "$mp_dev" != "$root_dev" ]] || die "$mp is backed by the ROOT device ($root_dev) — a backup target there protects against corruption only, never drive loss"

  existing="$(current_path_of "$id")"
  if [[ -n "$existing" ]]; then
    if [[ "$existing" == "$mp" ]]; then
      echo "felhom-backup-target-apply: storage $id already exists at $mp — nothing to do (idempotent)" >&2
      exit 0
    fi
    die "storage $id already exists at $existing — refusing to repoint it at $mp (a live backup target is never silently moved)"
  fi

  # is_mountpoint 1 is HARDCODED (F-2). content=backup only: this storage exists for vzdump archives
  # and must never become a place guests are allocated on.
  pvesm add dir "$id" --path "$mp" --content backup --is_mountpoint 1 >&2
  echo "felhom-backup-target-apply: created dir storage $id at $mp (content=backup, is_mountpoint 1)" >&2
  ;;
grant)
  [[ $# -eq 2 ]] || die "grant takes only <id>"
  # BOTH, always. A privsep token's rights are the intersection of the user's and the token's ACLs,
  # so granting one of the two grants nothing usable.
  pveum acl modify "/storage/$id" --users felhom-agent@pve --roles FelhomAgentStore >&2
  pveum acl modify "/storage/$id" --tokens 'felhom-agent@pve!agent' --roles FelhomAgentStore >&2
  echo "felhom-backup-target-apply: granted FelhomAgentStore on /storage/$id (user + token)" >&2
  ;;
*)
  die "unknown op ($op)"
  ;;
esac
