Add verify-overlay-integrity.sh and remove stale rbos-info symlink
Create overlay integrity verification script that checks all recipe symlinks, patch symlinks, circular references, critical local/patches/ files, and config/redbear-*.toml files. Supports --repair (calls apply-patches.sh) and --quiet (CI). Fix config name: redbear-minimal.toml not redbear-mini.toml. Remove stale dangling symlink recipes/system/rbos-info (correct name is redbear-info).
This commit is contained in:
@@ -1,143 +1,258 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# verify-overlay-integrity.sh — ensure Red Bear overlay is present and repairable.
|
# verify-overlay-integrity.sh — Verify the Red Bear OS overlay structure is intact.
|
||||||
|
#
|
||||||
|
# Checks:
|
||||||
|
# 1. All recipe symlinks (recipes/ → local/recipes/) resolve correctly
|
||||||
|
# 2. All patch symlinks (recipes/ → local/patches/) resolve correctly
|
||||||
|
# 3. No circular symlink references
|
||||||
|
# 4. Critical local/patches/ files exist
|
||||||
|
# 5. Critical config/redbear-*.toml files exist
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./local/scripts/verify-overlay-integrity.sh
|
# ./local/scripts/verify-overlay-integrity.sh # Check and report
|
||||||
# ./local/scripts/verify-overlay-integrity.sh --repair
|
# ./local/scripts/verify-overlay-integrity.sh --repair # Re-run apply-patches.sh on failures
|
||||||
|
# ./local/scripts/verify-overlay-integrity.sh --quiet # Exit code only (for CI)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 — all checks pass
|
||||||
|
# 1 — one or more checks failed
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
REPAIR=0
|
REPAIR=0
|
||||||
|
QUIET=0
|
||||||
|
|
||||||
if [ "${1:-}" = "--repair" ]; then
|
for arg in "$@"; do
|
||||||
REPAIR=1
|
case "$arg" in
|
||||||
fi
|
--repair) REPAIR=1 ;;
|
||||||
|
--quiet) QUIET=1 ;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--repair] [--quiet]"
|
||||||
|
echo " --repair Re-run apply-patches.sh if overlay checks fail"
|
||||||
|
echo " --quiet Exit code only, no output"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $arg" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
fail() {
|
ERRORS=0
|
||||||
echo "overlay-integrity: ERROR: $*" >&2
|
WARNINGS=0
|
||||||
exit 1
|
|
||||||
|
log() {
|
||||||
|
[ "$QUIET" = "0" ] && echo "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
need_file() {
|
log_error() {
|
||||||
local path="$1"
|
[ "$QUIET" = "0" ] && echo " ERROR: $*" >&2
|
||||||
[ -f "$path" ] || fail "missing file: $path"
|
ERRORS=$((ERRORS + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
need_symlink_target() {
|
log_warn() {
|
||||||
local link="$1"
|
[ "$QUIET" = "0" ] && echo " WARN: $*"
|
||||||
local expected="$2"
|
WARNINGS=$((WARNINGS + 1))
|
||||||
local current
|
}
|
||||||
|
|
||||||
if [ ! -L "$link" ]; then
|
# ── 1. Recipe symlinks (recipes/ → local/recipes/) ──────────────────
|
||||||
return 1
|
log "==> Checking recipe symlinks (recipes/ → local/recipes/)..."
|
||||||
|
RECIPE_SYMLINK_COUNT=0
|
||||||
|
BROKEN_RECIPE_SYMLINKS=0
|
||||||
|
|
||||||
|
while IFS= read -r link; do
|
||||||
|
RECIPE_SYMLINK_COUNT=$((RECIPE_SYMLINK_COUNT + 1))
|
||||||
|
target="$(readlink -f "$link" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [ -z "$target" ]; then
|
||||||
|
log_error "broken symlink: $link (cannot resolve target)"
|
||||||
|
BROKEN_RECIPE_SYMLINKS=$((BROKEN_RECIPE_SYMLINKS + 1))
|
||||||
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
current="$(readlink "$link")"
|
# Check if target is inside local/recipes/
|
||||||
[ "$current" = "$expected" ]
|
if echo "$target" | grep -q "/local/recipes/"; then
|
||||||
}
|
if [ ! -e "$target" ]; then
|
||||||
|
log_error "dangling symlink: $link -> $target (target does not exist)"
|
||||||
ensure_link() {
|
BROKEN_RECIPE_SYMLINKS=$((BROKEN_RECIPE_SYMLINKS + 1))
|
||||||
local link="$1"
|
|
||||||
local target="$2"
|
|
||||||
if ! need_symlink_target "$link" "$target"; then
|
|
||||||
if [ "$REPAIR" -eq 1 ] && [ -x "$REPO_ROOT/local/scripts/apply-patches.sh" ]; then
|
|
||||||
"$REPO_ROOT/local/scripts/apply-patches.sh" >/dev/null
|
|
||||||
fi
|
|
||||||
need_symlink_target "$link" "$target" || fail "bad symlink: $link -> expected $target"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
has_marker() {
|
|
||||||
local file="$1"
|
|
||||||
local pattern="$2"
|
|
||||||
[ -f "$file" ] && grep -q "$pattern" "$file"
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_marker() {
|
|
||||||
local file="$1"
|
|
||||||
local pattern="$2"
|
|
||||||
if ! has_marker "$file" "$pattern"; then
|
|
||||||
if [ "$REPAIR" -eq 1 ]; then
|
|
||||||
if [ -x "$REPO_ROOT/local/scripts/apply-patches.sh" ]; then
|
|
||||||
"$REPO_ROOT/local/scripts/apply-patches.sh" >/dev/null || true
|
|
||||||
fi
|
|
||||||
apply_patch_dir "$REPO_ROOT/local/patches/base" "$REPO_ROOT/recipes/core/base/source"
|
|
||||||
apply_patch_dir "$REPO_ROOT/local/patches/kernel" "$REPO_ROOT/recipes/core/kernel/source"
|
|
||||||
apply_patch_dir "$REPO_ROOT/local/patches/relibc" "$REPO_ROOT/recipes/core/relibc/source"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
has_marker "$file" "$pattern" || fail "missing marker in $file: $pattern"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_local_over_wip_priority() {
|
# Check for circular symlinks
|
||||||
local local_dir
|
if [ -L "$target" ]; then
|
||||||
local rel
|
# The target itself is a symlink — could be circular
|
||||||
local name
|
resolved_inner="$(readlink -f "$target" 2>/dev/null || true)"
|
||||||
local wip_match
|
if [ -n "$resolved_inner" ] && [ "$resolved_inner" = "$(readlink -f "$link" 2>/dev/null)" ]; then
|
||||||
local active_path
|
log_error "circular symlink: $link -> $target -> (circular)"
|
||||||
|
BROKEN_RECIPE_SYMLINKS=$((BROKEN_RECIPE_SYMLINKS + 1))
|
||||||
for local_dir in "$REPO_ROOT"/local/recipes/*/*; do
|
|
||||||
[ -d "$local_dir" ] || continue
|
|
||||||
rel="${local_dir#"$REPO_ROOT/local/recipes/"}"
|
|
||||||
name="${local_dir##*/}"
|
|
||||||
wip_match="$(find "$REPO_ROOT/recipes/wip" -mindepth 2 -maxdepth 2 -type d -name "$name" 2>/dev/null | head -n1 || true)"
|
|
||||||
|
|
||||||
# Policy: if local package conflicts with an upstream WIP package of the same name,
|
|
||||||
# the active recipe must be our local overlay (symlink in recipes/*/<name>).
|
|
||||||
[ -n "$wip_match" ] || continue
|
|
||||||
|
|
||||||
active_path="$(find "$REPO_ROOT/recipes" -mindepth 2 -maxdepth 2 -type l -name "$name" ! -path "$REPO_ROOT/recipes/wip/*" 2>/dev/null | head -n1 || true)"
|
|
||||||
# Only enforce for actively mounted local overlays.
|
|
||||||
[ -n "$active_path" ] || continue
|
|
||||||
|
|
||||||
if [ "$(readlink "$active_path")" != "../../local/recipes/${rel}" ]; then
|
|
||||||
fail "local-over-wip policy violated: '$active_path' does not point to ../../local/recipes/${rel}"
|
|
||||||
fi
|
fi
|
||||||
done
|
fi
|
||||||
}
|
done < <(find recipes -maxdepth 3 -type l 2>/dev/null | grep -v '/target$\|/stage\|/sysroot$\|/source$' | sort)
|
||||||
|
|
||||||
apply_patch_dir() {
|
log " $RECIPE_SYMLINK_COUNT recipe symlinks checked, $BROKEN_RECIPE_SYMLINKS broken"
|
||||||
local patch_dir="$1"
|
|
||||||
local target_dir="$2"
|
|
||||||
local patch_file
|
|
||||||
|
|
||||||
[ -d "$patch_dir" ] || return 0
|
# ── 2. Patch symlinks (recipes/ → local/patches/) ───────────────────
|
||||||
[ -d "$target_dir" ] || return 0
|
log "==> Checking patch symlinks (recipes/ → local/patches/)..."
|
||||||
|
PATCH_SYMLINK_COUNT=0
|
||||||
|
BROKEN_PATCH_SYMLINKS=0
|
||||||
|
|
||||||
for patch_file in "$patch_dir"/*.patch; do
|
EXPECTED_PATCH_SYMLINKS=(
|
||||||
[ -f "$patch_file" ] || continue
|
"recipes/core/kernel/redox.patch"
|
||||||
if patch --dry-run -p1 -d "$target_dir" < "$patch_file" >/dev/null 2>&1; then
|
"recipes/core/base/redox.patch"
|
||||||
patch -p1 -d "$target_dir" < "$patch_file" >/dev/null 2>&1 || true
|
"recipes/core/base/P2-boot-runtime-fixes.patch"
|
||||||
|
"recipes/core/relibc/redox.patch"
|
||||||
|
"recipes/core/installer/redox.patch"
|
||||||
|
"recipes/core/bootloader/redox.patch"
|
||||||
|
"recipes/core/bootloader/P2-live-preload-guard.patch"
|
||||||
|
"recipes/core/bootloader/P3-uefi-live-image-safe-read.patch"
|
||||||
|
"recipes/gui/orbutils/redox.patch"
|
||||||
|
)
|
||||||
|
|
||||||
|
for patch_link in "${EXPECTED_PATCH_SYMLINKS[@]}"; do
|
||||||
|
PATCH_SYMLINK_COUNT=$((PATCH_SYMLINK_COUNT + 1))
|
||||||
|
if [ ! -L "$patch_link" ]; then
|
||||||
|
if [ -f "$patch_link" ]; then
|
||||||
|
log_warn "$patch_link exists as regular file (not a symlink to local/patches/)"
|
||||||
|
else
|
||||||
|
log_error "$patch_link missing (should be symlink to local/patches/)"
|
||||||
|
BROKEN_PATCH_SYMLINKS=$((BROKEN_PATCH_SYMLINKS + 1))
|
||||||
fi
|
fi
|
||||||
done
|
continue
|
||||||
}
|
fi
|
||||||
|
target="$(readlink -f "$patch_link" 2>/dev/null || true)"
|
||||||
|
if [ -z "$target" ] || [ ! -f "$target" ]; then
|
||||||
|
log_error "dangling patch symlink: $patch_link -> $target"
|
||||||
|
BROKEN_PATCH_SYMLINKS=$((BROKEN_PATCH_SYMLINKS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
need_file "local/patches/kernel/redox.patch"
|
log " $PATCH_SYMLINK_COUNT patch symlinks checked, $BROKEN_PATCH_SYMLINKS broken"
|
||||||
need_file "local/patches/base/redox.patch"
|
|
||||||
need_file "local/patches/relibc/redox.patch"
|
|
||||||
|
|
||||||
ensure_link "recipes/core/kernel/redox.patch" "../../../local/patches/kernel/redox.patch"
|
# ── 3. Critical local/patches/ files ────────────────────────────────
|
||||||
ensure_link "recipes/core/base/redox.patch" "../../../local/patches/base/redox.patch"
|
log "==> Checking critical local/patches/ files..."
|
||||||
ensure_link "recipes/core/bootloader/P2-live-preload-guard.patch" "../../../local/patches/bootloader/P2-live-preload-guard.patch"
|
CRITICAL_PATCHES=(
|
||||||
ensure_link "recipes/core/bootloader/P3-uefi-live-image-safe-read.patch" "../../../local/patches/bootloader/P3-uefi-live-image-safe-read.patch"
|
"local/patches/kernel/redox.patch"
|
||||||
ensure_link "recipes/core/grub" "../../local/recipes/core/grub"
|
"local/patches/base/redox.patch"
|
||||||
check_local_over_wip_priority
|
"local/patches/relibc/redox.patch"
|
||||||
|
"local/patches/installer/redox.patch"
|
||||||
|
"local/patches/bootloader/redox.patch"
|
||||||
|
"local/patches/build-system/001-rebrand-and-build.patch"
|
||||||
|
"local/patches/build-system/002-cookbook-fixes.patch"
|
||||||
|
"local/patches/build-system/003-config.patch"
|
||||||
|
"local/patches/build-system/004-docs-and-cleanup.patch"
|
||||||
|
)
|
||||||
|
|
||||||
# Critical runtime markers in source trees (if sources are present locally).
|
MISSING_PATCHES=0
|
||||||
if [ -d "recipes/core/base/source" ]; then
|
for patch_file in "${CRITICAL_PATCHES[@]}"; do
|
||||||
ensure_marker \
|
if [ ! -f "$patch_file" ]; then
|
||||||
"recipes/core/base/source/drivers/acpid/src/acpi.rs" \
|
log_error "missing critical patch: $patch_file"
|
||||||
"AML interpreter requires PCI registration before initialization"
|
MISSING_PATCHES=$((MISSING_PATCHES + 1))
|
||||||
ensure_marker \
|
fi
|
||||||
"recipes/core/base/source/drivers/acpid/src/scheme.rs" \
|
done
|
||||||
"wait_for_pci_ready"
|
|
||||||
ensure_marker \
|
if [ "$MISSING_PATCHES" = "0" ]; then
|
||||||
"recipes/core/base/source/drivers/input/ps2d/src/controller.rs" \
|
log " All critical patches present"
|
||||||
"continuing without second port"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "overlay-integrity: OK"
|
# ── 4. Config files ─────────────────────────────────────────────────
|
||||||
|
log "==> Checking config/redbear-*.toml files..."
|
||||||
|
CRITICAL_CONFIGS=(
|
||||||
|
"config/redbear-full.toml"
|
||||||
|
"config/redbear-live-full.toml"
|
||||||
|
"config/redbear-minimal.toml"
|
||||||
|
"config/redbear-live-minimal.toml"
|
||||||
|
"config/redbear-desktop.toml"
|
||||||
|
"config/redbear-device-services.toml"
|
||||||
|
"config/redbear-legacy-base.toml"
|
||||||
|
"config/redbear-legacy-desktop.toml"
|
||||||
|
"config/redbear-netctl.toml"
|
||||||
|
"config/redbear-greeter-services.toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
MISSING_CONFIGS=0
|
||||||
|
for config_file in "${CRITICAL_CONFIGS[@]}"; do
|
||||||
|
if [ ! -f "$config_file" ]; then
|
||||||
|
log_error "missing config: $config_file"
|
||||||
|
MISSING_CONFIGS=$((MISSING_CONFIGS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
TOTAL_REDBEAR_CONFIGS=$(find config -maxdepth 1 -name 'redbear-*.toml' 2>/dev/null | wc -l)
|
||||||
|
log " $TOTAL_REDBEAR_CONFIGS redbear-*.toml files found, $MISSING_CONFIGS critical configs missing"
|
||||||
|
|
||||||
|
# ── 5. local/ directory structure ───────────────────────────────────
|
||||||
|
log "==> Checking local/ directory structure..."
|
||||||
|
REQUIRED_LOCAL_DIRS=(
|
||||||
|
"local/recipes"
|
||||||
|
"local/recipes/drivers"
|
||||||
|
"local/recipes/gpu"
|
||||||
|
"local/recipes/system"
|
||||||
|
"local/recipes/core"
|
||||||
|
"local/recipes/libs"
|
||||||
|
"local/recipes/branding"
|
||||||
|
"local/recipes/kde"
|
||||||
|
"local/patches"
|
||||||
|
"local/patches/kernel"
|
||||||
|
"local/patches/base"
|
||||||
|
"local/patches/relibc"
|
||||||
|
"local/patches/bootloader"
|
||||||
|
"local/patches/installer"
|
||||||
|
"local/patches/build-system"
|
||||||
|
"local/docs"
|
||||||
|
"local/scripts"
|
||||||
|
"local/Assets"
|
||||||
|
)
|
||||||
|
|
||||||
|
MISSING_DIRS=0
|
||||||
|
for dir in "${REQUIRED_LOCAL_DIRS[@]}"; do
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
log_error "missing directory: $dir"
|
||||||
|
MISSING_DIRS=$((MISSING_DIRS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$MISSING_DIRS" = "0" ]; then
|
||||||
|
log " All required local/ directories present"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Summary ─────────────────────────────────────────────────────────
|
||||||
|
log ""
|
||||||
|
log "=== Overlay Integrity Summary ==="
|
||||||
|
log " Recipe symlinks: $RECIPE_SYMLINK_COUNT ($BROKEN_RECIPE_SYMLINKS broken)"
|
||||||
|
log " Patch symlinks: $PATCH_SYMLINK_COUNT ($BROKEN_PATCH_SYMLINKS broken)"
|
||||||
|
log " Critical patches: ${#CRITICAL_PATCHES[@]} ($MISSING_PATCHES missing)"
|
||||||
|
log " Critical configs: ${#CRITICAL_CONFIGS[@]} ($MISSING_CONFIGS missing)"
|
||||||
|
log " Required dirs: ${#REQUIRED_LOCAL_DIRS[@]} ($MISSING_DIRS missing)"
|
||||||
|
log " Warnings: $WARNINGS"
|
||||||
|
log " Errors: $ERRORS"
|
||||||
|
|
||||||
|
if [ "$ERRORS" -gt 0 ]; then
|
||||||
|
log ""
|
||||||
|
log "!! Overlay integrity check FAILED ($ERRORS errors)"
|
||||||
|
if [ "$REPAIR" = "1" ]; then
|
||||||
|
log "==> Attempting repair via apply-patches.sh..."
|
||||||
|
if [ -f local/scripts/apply-patches.sh ]; then
|
||||||
|
bash local/scripts/apply-patches.sh
|
||||||
|
log "==> Repair complete. Re-running verification..."
|
||||||
|
exec "$0" --quiet
|
||||||
|
else
|
||||||
|
log_error "apply-patches.sh not found — cannot repair"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log " Run with --repair to attempt automatic fix, or:"
|
||||||
|
log " ./local/scripts/apply-patches.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$QUIET" = "0" ]; then
|
||||||
|
log ""
|
||||||
|
log " Overlay integrity check PASSED"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../local/recipes/system/rbos-info
|
|
||||||
Reference in New Issue
Block a user