#!/bin/bash # validate-file-ownership.sh — Check recipe file ownership conflicts # # Reads the optional 'installs' field from recipe.toml [package] sections # and detects conflicts where multiple recipes claim the same path. # # Also cross-references config [[files]] paths against recipe installs # to detect config-layer / package-layer collisions. # # Usage: # scripts/validate-file-ownership.sh # # Exit codes: # 0 — No conflicts or violations found # 1 — Conflicts detected set -euo pipefail failures=0 # Registry: path -> recipe_name declare -A PATH_REGISTRY echo "=== Scanning recipes for installs declarations ===" recipe_count=0 declared_count=0 for recipe_toml in recipes/*/recipe.toml recipes/*/*/recipe.toml; do [ -f "$recipe_toml" ] || continue recipe_count=$((recipe_count + 1)) recipe_dir=$(dirname "$recipe_toml") recipe_name=$(basename "$recipe_dir") # Parse installs field from [package] section # Format: installs = ["/usr/bin/foo", "/usr/lib/init.d/10_bar.service"] in_package=false in_installs=false while IFS= read -r line; do if [[ "$line" =~ ^\[package\] ]]; then in_package=true in_installs=false continue elif [[ "$line" =~ ^\[ ]]; then in_package=false in_installs=false continue fi if $in_package; then if [[ "$line" =~ ^installs ]]; then in_installs=true fi if $in_installs; then paths=$(echo "$line" | grep -oP '"[^"]+"' | tr -d '"' || true) for path in $paths; do declared_count=$((declared_count + 1)) if [ -n "${PATH_REGISTRY[$path]+x}" ]; then existing="${PATH_REGISTRY[$path]}" echo "CONFLICT: '$path' claimed by both '$existing' and '$recipe_name'" if [[ "$path" == *"/init.d/"* ]]; then echo " SEVERITY: init service conflict (critical)" failures=$((failures + 1)) else echo " SEVERITY: non-critical path overlap" fi else PATH_REGISTRY["$path"]="$recipe_name" fi done if [[ "$line" =~ \] ]]; then in_installs=false fi fi fi done < "$recipe_toml" done echo " Scanned $recipe_count recipes, found $declared_count declared install paths" echo "" echo "=== Cross-referencing config [[files]] against recipe installs ===" config_conflicts=0 for config_file in config/redbear-*.toml; do [ -f "$config_file" ] || continue config_name=$(basename "$config_file") in_files=false while IFS= read -r line; do if [[ "$line" =~ ^\[\[files\]\] ]]; then in_files=true continue elif [[ "$line" =~ ^\[ ]]; then in_files=false continue fi if $in_files && [[ "$line" =~ path[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then config_path="${BASH_REMATCH[1]}" for registered_path in "${!PATH_REGISTRY[@]}"; do if [ "$config_path" = "$registered_path" ]; then echo "COLLISION: config '$config_name' creates '$config_path' but recipe '${PATH_REGISTRY[$registered_path]}' also installs it" config_conflicts=$((config_conflicts + 1)) if [[ "$config_path" == *"/usr/lib/init.d/"* ]]; then echo " SEVERITY: init service in /usr/lib/init.d/ (will be overwritten by package)" failures=$((failures + 1)) fi fi done fi done < "$config_file" done echo " Found $config_conflicts config/recipe path collision(s)" echo "" echo "=== Validation complete ===" if [ $declared_count -eq 0 ]; then echo " NOTE: No recipes declare 'installs' yet. Add installs = [...] to [package] sections for full validation." fi if [ $failures -gt 0 ]; then echo "FAILED: $failures conflict(s) found" >&2 exit 1 else echo "PASSED: No conflicts found" fi