#!/usr/bin/env bash # box revoke [--purge] — take the restricted tier back (#74). # # Two strengths, deliberately: # · bare revoke removes the user from the 'incus' group. That closes the # socket — the only path their certificate can travel — so access ends at # their next login, while their project and boxes stay intact (and their # boxes stay RUNNING: revoking a person does not kill their workloads). # 'box grant' restores everything untouched. # · --purge also deletes what the tier created: their boxes, their images, # their project, the private bridge, the trust-store certificate, the # incus-user state. Irreversible, so it asks first. set -euo pipefail usage() { echo "usage: box revoke [--purge]" >&2; exit 2; } user=""; purge=0 for a in "$@"; do case "$a" in --purge) purge=1 ;; -*) usage ;; *) [ -z "$user" ] || usage; user="$a" ;; esac done [ -n "$user" ] || usage if [ "$(id -u)" -eq 0 ]; then SUDO="" elif command -v sudo >/dev/null 2>&1; then SUDO="sudo" else echo "ERROR: box revoke needs root and 'sudo' was not found." >&2 exit 1 fi getent passwd "$user" >/dev/null || { echo "box revoke: no such user: $user" >&2; exit 1; } uid="$(id -u "$user")" project="user-$uid" # incus-user's own naming rule, mirrored exactly: the bridge is incusbr- # unless that would not fit in an interface name (15 chars), then user-. bridge="incusbr-$uid" [ "${#bridge}" -gt 15 ] && bridge="user-$uid" if [ "$purge" -eq 1 ]; then # Destructive and irreversible: a TTY to ask on, or BOX_YES=1, or refuse — # the same non-interactive contract as install.sh. if [ -z "${BOX_YES:-}" ]; then if [ -t 0 ]; then printf 'box revoke: delete ALL of %s'\''s boxes, images and their project %s? this cannot be undone. [y/N] ' "$user" "$project" read -r reply case "$reply" in y|Y|yes|YES|Yes) : ;; *) echo "box revoke: aborted." >&2; exit 1 ;; esac else echo "box revoke: refusing to --purge without a terminal to confirm on. BOX_YES=1 means yes." >&2 exit 2 fi fi fi # The group, first — access ends even if a purge step below trips. if id -nG "$user" | tr ' ' '\n' | grep -qx incus; then $SUDO gpasswd -d "$user" incus >/dev/null echo "group: removed $user from 'incus' — the socket closes with their next login" else echo "group: $user was not in 'incus'" fi if [ "$purge" -eq 0 ]; then if incus project show "$project" >/dev/null 2>&1 /dev/null 2>&1 /dev/null) while IFS=, read -r fp _; do [ -n "$fp" ] || continue incus --project "$project" image delete "$fp" /dev/null) incus --project "$project" profile delete box-net >/dev/null 2>&1 &2; exit 1; } echo "purge: project $project removed" fi if incus network delete "$bridge" >/dev/null 2>&1 . while IFS=, read -r name fp _; do [ "$name" = "incus-user-$uid" ] || continue incus config trust remove "$fp" /dev/null) # incus-user's per-user client state (their key pair). Removed so a future # re-grant starts clean instead of trusting a key the purge revoked. if [ -d "/var/lib/incus/users/$uid" ]; then $SUDO rm -rf "/var/lib/incus/users/$uid" echo "purge: incus-user state for uid $uid removed" fi # Assert absence rather than trusting exit codes — the wipe.sh discipline. leftover="" incus project show "$project" >/dev/null 2>&1 /dev/null 2>&1 &2 exit 1 fi echo "revoked: $user is out, and everything the tier created is gone."