#!/bin/bash # validate-init-services.sh — Post-image init service validation # # Validates that init service files in a built image match expectations: # 1. Config overrides in /etc/init.d/ differ from /usr/lib/init.d/ counterparts # 2. No missing init service files that configs expected to create # 3. Scheme-type services have corresponding binaries # 4. No dependency cycles in service graph # # Requires: redoxfs FUSE mount (or ext4 mount) # # Usage: # scripts/validate-init-services.sh build/x86_64/redbear-full/harddrive.img # scripts/validate-init-services.sh build/x86_64/redbear-mini/harddrive.img # # Exit codes: # 0 — All validations passed # 1 — Validation failures found set -euo pipefail IMAGE_PATH="${1:?Usage: $0 }" if [ ! -f "$IMAGE_PATH" ]; then echo "ERROR: Image not found: $IMAGE_PATH" >&2 exit 1 fi MOUNT_DIR=$(mktemp -d /tmp/redbear-validate-XXXXXX) failures=0 cleanup() { fusermount -u "$MOUNT_DIR" 2>/dev/null || true rmdir "$MOUNT_DIR" 2>/dev/null || true } trap cleanup EXIT echo "=== Mounting image ===" redoxfs "$IMAGE_PATH" "$MOUNT_DIR" sleep 1 echo "" echo "=== 1. Checking /etc/init.d/ override effectiveness ===" if [ -d "$MOUNT_DIR/usr/lib/init.d" ] && [ -d "$MOUNT_DIR/etc/init.d" ]; then etc_count=$(find "$MOUNT_DIR/etc/init.d" -type f 2>/dev/null | wc -l) usr_count=$(find "$MOUNT_DIR/usr/lib/init.d" -type f 2>/dev/null | wc -l) echo " /usr/lib/init.d/: $usr_count files" echo " /etc/init.d/: $etc_count files" for etc_file in "$MOUNT_DIR"/etc/init.d/*; do [ -f "$etc_file" ] || continue basename=$(basename "$etc_file") usr_file="$MOUNT_DIR/usr/lib/init.d/$basename" if [ -f "$usr_file" ]; then if diff -q "$etc_file" "$usr_file" > /dev/null 2>&1; then echo " WARN: /etc/init.d/$basename identical to /usr/lib/init.d/$basename (redundant override)" else echo " OK: /etc/init.d/$basename differs from /usr/lib/init.d/$basename (override active)" fi else echo " OK: /etc/init.d/$basename (new service, no package counterpart)" fi done else echo " One or both init.d directories missing — skipping override check" fi echo "" echo "=== 2. Checking scheme-type services have binaries ===" if [ -d "$MOUNT_DIR/usr/lib/init.d" ] || [ -d "$MOUNT_DIR/etc/init.d" ]; then for svc_file in "$MOUNT_DIR"/usr/lib/init.d/*.service "$MOUNT_DIR"/etc/init.d/*.service; do [ -f "$svc_file" ] || continue scheme_name=$(grep -oP 'type\s*=\s*\{\s*scheme\s*=\s*"([^"]+)"' "$svc_file" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' || true) if [ -n "$scheme_name" ]; then # Scheme daemon binaries use d convention (e.g., scheme "evdev" -> binary "evdevd") found_binary=false for candidate in "$scheme_name" "${scheme_name}d" "redbear-$scheme_name"; do if [ -x "$MOUNT_DIR/usr/bin/$candidate" ]; then echo " OK: scheme '$scheme_name' has binary /usr/bin/$candidate" found_binary=true break fi done if [ "$found_binary" = "false" ]; then echo " FAIL: scheme '$scheme_name' has NO binary at /usr/bin/$scheme_name, /usr/bin/${scheme_name}d, or /usr/bin/redbear-$scheme_name" failures=$((failures + 1)) fi fi done fi echo "" echo "=== 3. Checking service dependency graph for cycles ===" if [ -d "$MOUNT_DIR/usr/lib/init.d" ] || [ -d "$MOUNT_DIR/etc/init.d" ]; then # Collect all services services_dir=$(mktemp -d) for svc_file in "$MOUNT_DIR"/usr/lib/init.d/* "$MOUNT_DIR"/etc/init.d/*; do [ -f "$svc_file" ] || continue basename=$(basename "$svc_file") cp "$svc_file" "$services_dir/$basename" 2>/dev/null || true done # Simple cycle detection: build adjacency list, do DFS python3 -c " import os, re, sys from collections import defaultdict svc_dir = '$services_dir' graph = defaultdict(list) all_nodes = set() for f in os.listdir(svc_dir): all_nodes.add(f) content = open(os.path.join(svc_dir, f)).read() for dep in re.findall(r'requires_weak\s*=\s*\[([^\]]*)\]', content) + re.findall(r'requires\s*=\s*\[([^\]]*)\]', content): for d in re.findall(r'\"([^\"]+)\"', dep): graph[f].append(d) # DFS cycle detection WHITE, GRAY, BLACK = 0, 1, 2 color = {n: WHITE for n in all_nodes} has_cycle = False def dfs(node): global has_cycle color[node] = GRAY for neighbor in graph.get(node, []): if neighbor not in color: continue if color[neighbor] == GRAY: print(f' CYCLE: {node} -> {neighbor}') has_cycle = True elif color[neighbor] == WHITE: dfs(neighbor) color[node] = BLACK for node in sorted(all_nodes): if color[node] == WHITE: dfs(node) if not has_cycle: print(' OK: No dependency cycles detected') else: sys.exit(1) " 2>&1 || { echo " FAIL: Dependency cycle detected!" failures=$((failures + 1)) } rm -rf "$services_dir" fi echo "" echo "=== 4. Checking for missing dependencies ===" if [ -d "$MOUNT_DIR/usr/lib/init.d" ] || [ -d "$MOUNT_DIR/etc/init.d" ]; then all_files=$(mktemp) for svc_file in "$MOUNT_DIR"/usr/lib/init.d/* "$MOUNT_DIR"/etc/init.d/*; do [ -f "$svc_file" ] || continue basename "$svc_file" >> "$all_files" done for svc_file in "$MOUNT_DIR"/usr/lib/init.d/* "$MOUNT_DIR"/etc/init.d/*; do [ -f "$svc_file" ] || continue svc_name=$(basename "$svc_file") for dep in $(grep -oP 'requires_weak\s*=\s*\[([^\]]*)\]' "$svc_file" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' || true); do if ! grep -q "^${dep}$" "$all_files" 2>/dev/null; then echo " WARN: $svc_name requires '$dep' but file not found in any init.d directory" fi done for dep in $(grep -oP 'requires\s*=\s*\[([^\]]*)\]' "$svc_file" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' || true); do if ! grep -q "^${dep}$" "$all_files" 2>/dev/null; then echo " FAIL: $svc_name requires '$dep' (hard dep) but file not found" failures=$((failures + 1)) fi done done rm -f "$all_files" fi echo "" echo "=== Validation complete ===" if [ $failures -gt 0 ]; then echo "FAILED: $failures validation failure(s) found" >&2 exit 1 else echo "PASSED: All validations passed" fi