fix(install): backups exist on a default install; retention resolves real world dirs (#6)

Three faces of one gap, all on the supported install path:

- Backup/restore answered 503 out of the box: nothing ever rendered the
  archive PVC, so FELIS_BACKUP_PVC was unset. The bundle now renders the
  PVC (Minecraft namespace, RWO 10Gi, cluster default class) and
  'felis manifests' names it by default (--backup-pvc= is the explicit
  no-store shape); bootstrap passes it through so the generated felis.toml
  [archive] local_path and the jobs' mount path come from one variable.
- Retention was unreachable: bootstrap never passed the reaper flags. It
  now forwards FELIS_WORLDS_HOST_PATH/FELIS_ARCHIVE_LOCAL_PATH, so one
  env enables the daily CronJob; unset keeps today's fail-safe (no reaper,
  nothing deleted).
- Even when enabled it could not find a world on a stock install:
  resolveWorldDir now also resolves the exact local-path directory
  <pv-name>_<ns>_<pvc-name> read from the live PVC's volumeName (never a
  glob, so a stale deleted PV's bytes can't be archived in place of the
  current world). Reaper Role gains persistentvolumeclaims:get (weaker
  than the delete it already held).

README (zh/en) stops promising automatic/scheduled backups and states
retention is opt-in. bootstrap_test covers the env->flag contract.
This commit is contained in:
Lemon-miaow committed 2026-09-22 20:48:07 +08:00
1 parent ff7c57cf9c
commit fd33fd05e1
14 files changed
+394 -82

No files matched your search

+42 -6
View File
@@ -63,6 +63,15 @@
# FELIS_ROOT_DOMAIN deployment root domain (default: <node-ip>.nip.io)
# FELIS_PANEL_NODEPORT local HTTPS panel/API NodePort (default: 30443)
# FELIS_EGRESS_MODE loadbalancer|nodeport (default: nodeport — no MetalLB on a demo box)
# FELIS_BACKUP_PVC world-archive PVC the installer renders and felis-api hands to its
# backup/restore Jobs (default: felis-backups; empty string disables
# backups — the endpoints answer 503)
# FELIS_ARCHIVE_LOCAL_PATH path that PVC is mounted at inside those Jobs; written into
# felis.toml [archive] local_path (default: /var/lib/felis/archives)
# FELIS_WORLDS_HOST_PATH node directory holding the world volumes (on the k3s this
# installer provisions: /var/lib/rancher/k3s/storage). Setting it
# enables the daily retention reaper, which archives and then deletes
# worlds idle beyond the retention window (default: unset = no reaper)
# PKG_LOCK_TIMEOUT seconds to wait for package-manager locks (default: 900)
# APT_LOCK_TIMEOUT legacy alias for PKG_LOCK_TIMEOUT
set -Eeuo pipefail
@@ -106,6 +115,18 @@ FELIS_VERSION_BASE=""
FELIS_IMAGE="${FELIS_IMAGE:-felis:demo}"
FELIS_EGRESS_MODE="${FELIS_EGRESS_MODE:-nodeport}"
FELIS_PANEL_NODEPORT="${FELIS_PANEL_NODEPORT:-30443}"
# World-archive storage. The installer renders this PVC (minecraft namespace) and felis-api
# advertises it to its backup/restore Jobs; emptying it disables backups (503). The archive
# path is written into felis.toml so the Jobs' mount and [archive] local_path agree by
# construction — a mismatch would leave tarLocal's absolute archive refs unresolvable.
FELIS_BACKUP_PVC="${FELIS_BACKUP_PVC:-felis-backups}"
FELIS_ARCHIVE_LOCAL_PATH="${FELIS_ARCHIVE_LOCAL_PATH:-/var/lib/felis/archives}"
# Retention is opt-in because it DELETES worlds (after a verified archive): point this at the
# node directory the world volumes live under. On the k3s this installer provisions that is
# /var/lib/rancher/k3s/storage — the reaper resolves each PVC's local-path directory exactly
# from its volumeName. Left unset, no reaper CronJob renders and archives accumulate until
# the backup PVC fills (then backups fail loudly; nothing is deleted).
FELIS_WORLDS_HOST_PATH="${FELIS_WORLDS_HOST_PATH:-}"
INSTALL_MODE="${FELIS_INSTALL_MODE:-}"
# Loopback by default: hasJoined is an unauthenticated endpoint by protocol (Velocity
# sends no token), so a public bind is a free auth relay — anyone can point their own
@@ -2047,7 +2068,7 @@ build_namespace = "${BUILD_NS}"
[archive]
store = "tarLocal"
local_path = "/var/lib/felis/archives"
local_path = "${FELIS_ARCHIVE_LOCAL_PATH}"
[auth]
admin_hostname = "op.console.${FELIS_ROOT_DOMAIN}"
@@ -2137,11 +2158,26 @@ deploy_bundle() {
--dry-run=client -o yaml | kube apply -f -
log "rendering + applying the control-plane bundle"
"$HOST_BIN" manifests \
--felis-image "$FELIS_IMAGE" \
--panel-node-port "$FELIS_PANEL_NODEPORT" \
--velocity-cidr "${NODE_IP}/32" \
| kube apply -f -
local -a manifest_args=(
--felis-image "$FELIS_IMAGE"
--panel-node-port "$FELIS_PANEL_NODEPORT"
--velocity-cidr "${NODE_IP}/32"
)
# Backups are on by default (the renderer's own default names felis-backups); an emptied
# FELIS_BACKUP_PVC asks for the no-backup shape explicitly, and a custom name must be
# passed through or the api would advertise a PVC the bundle never created.
if [ -n "$FELIS_BACKUP_PVC" ]; then
manifest_args+=(--backup-pvc "$FELIS_BACKUP_PVC")
else
manifest_args+=(--backup-pvc=)
fi
# Retention renders only when the operator names where the worlds live; the archive path
# always travels with it because it must equal the [archive] local_path written above.
if [ -n "$FELIS_WORLDS_HOST_PATH" ]; then
log "retention enabled: the daily reaper will read worlds from ${FELIS_WORLDS_HOST_PATH}"
manifest_args+=(--worlds-host-path "$FELIS_WORLDS_HOST_PATH" --archive-local-path "$FELIS_ARCHIVE_LOCAL_PATH")
fi
"$HOST_BIN" manifests "${manifest_args[@]}" | kube apply -f -
restart_existing_control_plane "$had_api" "$had_operator"
log "waiting for control-plane rollouts"
+39
View File
@@ -574,6 +574,45 @@ mkdir -p "$sdir/src/.git"
expect "a failed fetch into an existing checkout names the token" "set FELIS_GITHUB_TOKEN" \
"$(run_fetch "$sdir/src")"
# --- default install keeps backups, and retention envs reach the renderer ----------------
# A default install must render the world-archive PVC (without one, backup/restore answer an
# honest 503), and FELIS_WORLDS_HOST_PATH must turn into the reaper's two flags or an
# operator's retention enablement silently renders no CronJob. Extracted, not retyped.
mblock="$(awk '/^ local -a manifest_args=\(/,/kube apply -f -/' "$BS")"
[ -n "$mblock" ] || { echo "FAIL: no manifest_args block found in $BS"; exit 1; }
[ "$(printf '%s\n' "$mblock" | wc -l)" -lt 30 ] \
|| { echo "FAIL: the extracted block is not the manifest_args block -- did it move?"; exit 1; }
run_bundle_flags() { # backup-pvc worlds-host-path
FELIS_IMAGE=reg/felis:test FELIS_PANEL_NODEPORT=30443 NODE_IP=10.0.0.5 \
FELIS_BACKUP_PVC="$1" FELIS_WORLDS_HOST_PATH="$2" FELIS_ARCHIVE_LOCAL_PATH=/var/lib/felis/archives \
HOST_BIN=myManifests bash -c '
log() { :; }
kube() { cat; }
myManifests() { printf "%s\n" "$@"; }
run_bundle() {
'"$mblock"'
}
run_bundle'
}
out="$(run_bundle_flags felis-backups '')"
expect "a default install asks the renderer for the archive PVC" "--backup-pvc
felis-backups" "$out"
case "$out" in
*--worlds-host-path*) echo "FAIL: no reaper flags may render without FELIS_WORLDS_HOST_PATH"; fails=$((fails + 1)) ;;
esac
out="$(run_bundle_flags '' '')"
expect "an emptied FELIS_BACKUP_PVC is the explicit no-backup shape" "--backup-pvc=" "$out"
out="$(run_bundle_flags felis-backups /var/lib/rancher/k3s/storage)"
expect "enabling retention passes the worlds root" "--worlds-host-path
/var/lib/rancher/k3s/storage" "$out"
expect "enabling retention passes the archive mount that must match felis.toml" "--archive-local-path
/var/lib/felis/archives" "$out"
# ---------------------------------------------------------------------------------------
if [ "$fails" -eq 0 ]; then
echo "ALL PASS"