fix(uninstall): purge 前预检 felis 角色在其他库的依赖,DROP ROLE 不再半途失败
This commit is contained in:
3 files changed
+76
-2
No files matched your search
@@ -340,6 +340,39 @@ remove_hba_block() { # file
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
# check_database_purge runs before anything is removed: DROP ROLE refuses a role that
|
||||
# still owns a database or holds anything in one besides felis (a felis_pgint left by
|
||||
# `felis db pgint`, a grant made by hand), and by then the units and k3s are already gone.
|
||||
# It lists what holds the role instead, so the purge either runs to the end or not at all.
|
||||
check_database_purge() {
|
||||
[ "$PURGE" = 1 ] || return 0
|
||||
systemctl is-active --quiet postgresql 2>/dev/null || return 0
|
||||
local sql held err
|
||||
read -r -d '' sql <<SQL || true
|
||||
WITH r AS (SELECT oid FROM pg_roles WHERE rolname = '${DB_USER}'),
|
||||
f AS (SELECT oid FROM pg_database WHERE datname = '${DB_NAME}')
|
||||
SELECT DISTINCT CASE
|
||||
WHEN s.classid = 'pg_database'::regclass THEN 'database ' || (SELECT datname FROM pg_database WHERE oid = s.objid)
|
||||
WHEN s.classid = 'pg_tablespace'::regclass THEN 'tablespace ' || (SELECT spcname FROM pg_tablespace WHERE oid = s.objid)
|
||||
ELSE 'objects in database ' || (SELECT datname FROM pg_database WHERE oid = s.dbid)
|
||||
END || CASE s.deptype WHEN 'o' THEN ' (owned)' ELSE ' (privileges)' END
|
||||
FROM pg_shdepend s
|
||||
WHERE s.refclassid = 'pg_authid'::regclass AND s.refobjid = (SELECT oid FROM r)
|
||||
AND s.dbid IS DISTINCT FROM (SELECT oid FROM f)
|
||||
AND NOT (s.classid = 'pg_database'::regclass AND s.objid IS NOT DISTINCT FROM (SELECT oid FROM f))
|
||||
ORDER BY 1;
|
||||
SQL
|
||||
err="$(mktemp)"
|
||||
if ! held="$(as_postgres psql -v ON_ERROR_STOP=1 -tAq 2>"$err" <<<"$sql")"; then
|
||||
held="$(cat "$err")"
|
||||
rm -f "$err"
|
||||
die "could not ask PostgreSQL what the ${DB_USER} role still holds, so nothing was removed: ${held}"
|
||||
fi
|
||||
rm -f "$err"
|
||||
[ -n "$held" ] || return 0
|
||||
die "the ${DB_USER} role still holds $(printf '%s' "$held" | paste -sd ';' - | sed 's/;/; /g'), so DROP ROLE would fail halfway through the purge; nothing was removed. Hand them to postgres first (sudo -u postgres psql -c 'ALTER DATABASE <name> OWNER TO postgres', or REASSIGN OWNED BY ${DB_USER} TO postgres; DROP OWNED BY ${DB_USER}; inside that database), or rerun without --purge"
|
||||
}
|
||||
|
||||
purge_database() {
|
||||
[ "$PURGE" = 1 ] || return 0
|
||||
if ! systemctl is-active --quiet postgresql 2>/dev/null; then
|
||||
@@ -395,6 +428,7 @@ main() {
|
||||
[ -e "$STATE_DIR" ] || [ -e "$HOST_BIN" ] || [ -e "$OPT_DIR" ] \
|
||||
|| die "no Felis install here (${STATE_DIR}, ${HOST_BIN} and ${OPT_DIR} are all absent)"
|
||||
decide_k3s
|
||||
check_database_purge
|
||||
print_plan
|
||||
confirm
|
||||
final_backup
|
||||
|
||||
@@ -89,7 +89,17 @@ run_uninstall() {
|
||||
shift 3
|
||||
case "$*" in
|
||||
*"SHOW hba_file"*) echo "$ROOT/h/hba.conf" ;;
|
||||
*) echo "PSQL $* $(cat)" >> "$calls" ;;
|
||||
*)
|
||||
sql="$(cat)"
|
||||
case "$sql" in
|
||||
# What the felis role still holds: PG_HELD, one line each; PG_CHECK=fail for a
|
||||
# server that refuses the query.
|
||||
*pg_shdepend*)
|
||||
echo "PSQL-CHECK" >> "$calls"
|
||||
[ "${PG_CHECK:-}" = fail ] && { echo "psql: error: connection refused" >&2; return 2; }
|
||||
[ -z "${PG_HELD:-}" ] || printf "%s\n" "$PG_HELD" ;;
|
||||
*) echo "PSQL $* $sql" >> "$calls" ;;
|
||||
esac ;;
|
||||
esac
|
||||
}
|
||||
docker() { echo "DOCKER $*" >> "$calls"; }
|
||||
@@ -161,6 +171,7 @@ fresh_host
|
||||
out="$(run_uninstall "default felis minecraft" --purge --yes)"
|
||||
calls="$(cat "$root/calls")"
|
||||
refute "purge takes no bundle" "db backup" "$calls"
|
||||
expect "purge asks what the role holds first" "PSQL-CHECK" "$calls"
|
||||
expect "purge drops the database" "DROP DATABASE IF EXISTS felis;" "$calls"
|
||||
expect "purge drops the role" "DROP ROLE IF EXISTS felis;" "$calls"
|
||||
expect "purge puts listen_addresses back" "ALTER SYSTEM RESET listen_addresses;" "$calls"
|
||||
@@ -179,6 +190,35 @@ case "$hba" in
|
||||
esac
|
||||
expect "purge cleans Docker's build cache" "DOCKER builder prune -af" "$calls"
|
||||
|
||||
# --- a purge DROP ROLE would refuse -------------------------------------------------------
|
||||
# The VM drill: `felis db pgint` had left felis_pgint owned by felis, the purge removed the
|
||||
# units and k3s, then stopped at DROP ROLE with half the host gone.
|
||||
untouched() { # label
|
||||
[ -d "$root/h/opt" ] && [ -f "$root/h/units/felis-velocity.service" ] && [ -d "$root/h/etc" ] \
|
||||
&& ! grep -q "k3s-uninstall.sh\|DROP DATABASE\|SYSTEMCTL disable" "$root/calls" \
|
||||
&& echo "PASS $1" \
|
||||
|| { echo "FAIL $1: $(cat "$root/calls")"; fails=$((fails + 1)); }
|
||||
}
|
||||
fresh_host
|
||||
out="$(PG_HELD="database felis_pgint (owned)
|
||||
objects in database shop (privileges)" run_uninstall "default felis minecraft" --purge --yes)"
|
||||
expect "a purge the role cannot survive is refused" "the felis role still holds database felis_pgint (owned); objects in database shop (privileges)" "$out"
|
||||
expect "with the way to hand the database over" "ALTER DATABASE <name> OWNER TO postgres" "$out"
|
||||
untouched "nothing is removed when DROP ROLE would fail"
|
||||
fresh_host
|
||||
out="$(PG_HELD="database felis_pgint (owned)" CONFIRM_TTY="$root/no-tty/x" run_uninstall "default felis minecraft" --purge)"
|
||||
expect "the check comes before the plan and the prompt" "the felis role still holds database felis_pgint" "$out"
|
||||
refute "so nobody confirms a purge that cannot finish" "this will remove" "$out"
|
||||
fresh_host
|
||||
out="$(PG_CHECK=fail run_uninstall "default felis minecraft" --purge --yes)"
|
||||
expect "a server that cannot be asked stops the purge" "could not ask PostgreSQL what the felis role still holds, so nothing was removed: psql: error: connection refused" "$out"
|
||||
untouched "nothing is removed when the check cannot run"
|
||||
fresh_host
|
||||
PG_HELD="database felis_pgint (owned)" run_uninstall "default felis minecraft" --yes >/dev/null
|
||||
calls="$(cat "$root/calls")"
|
||||
refute "keep-data drops no role, so it asks nothing" "PSQL-CHECK" "$calls"
|
||||
expect "and goes on" "RUN k3s-uninstall.sh" "$calls"
|
||||
|
||||
# --- the pieces read before they are removed ---------------------------------------------
|
||||
fresh_host
|
||||
lib() { STATE_DIR="$root/h/etc" OPT_DIR="$root/h/opt" UNIT_DIR="$root/h/units" FELIS_UNINSTALL_SOURCED=1 \
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ the cluster holds nothing but Felis's namespaces; when it runs anything else onl
|
||||
| | keep data (default) | `--purge` |
|
||||
|---|---|---|
|
||||
| Final database bundle | taken first (`felis db backup -label manual`); a failure stops the uninstall before anything is removed. `--no-backup` skips it | none |
|
||||
| `felis` database and role | kept | dropped; `listen_addresses` and `pg_hba.conf` go back to how they were |
|
||||
| `felis` database and role | kept | dropped; `listen_addresses` and `pg_hba.conf` go back to how they were. Checked before anything is removed: a role that still owns another database (a `felis_pgint` left by `felis db pgint`) or holds grants elsewhere stops the purge up front with the list and the `ALTER DATABASE … OWNER TO postgres` to run |
|
||||
| `/etc/felis` (secrets, `felis.toml`, `offsite.env`, tunnel config) | kept; `bootstrap.done` and the per-run records go | deleted, with the tunnel's credentials file |
|
||||
| `/var/lib/felis` (database bundles) | kept | deleted |
|
||||
| Worlds, archives, registry, uploads | moved to `/var/lib/felis/retained/k3s-storage-<stamp>/` (with `--keep-k3s`: their volumes switch to `Retain` and stay in place) | deleted |
|
||||
|
||||
Reference in new issue
Block a user