e65a23fd6b
This commit replaces the D-Bus implementation stubs and gaps identified
during the zbus/D-Bus review round with real, tested implementations.
**redbear-sessiond: real login1 properties + PrepareForShutdown signals**
The login1.Manager interface had hardcoded stub values:
- idle_since_hint() and idle_since_hint_monotonic() returned 0
- inhibit_delay_max_usec() returned 0
- handle_lid_switch() returned 'ignore'
- handle_power_key() returned 'poweroff'
- power_off/reboot/suspend wrote to /scheme/sys/kstop but did NOT
emit PrepareForShutdown(before=true) / PrepareForSleep(true) first
Replace with:
- Run-time configurable atomic fields (last_activity_us,
inhibit_delay_max_us) and RwLock<String> fields (handle_lid_switch,
handle_power_key) on SessionRuntime, mutated by the existing control
socket. Defaults: 5s inhibit delay, 'ignore' lid, 'poweroff' power key.
- power_off/reboot are now async, emit PrepareForShutdown(true) before
/scheme/sys/kstop write, emit PrepareForShutdown(false) after a
successful write, and return a D-Bus error if the kstop write fails
(resetting preparing_for_shutdown).
- suspend emits PrepareForSleep(true)/(false) similarly.
- Bash-style dangling-Clone problem solved via manual Clone impl on
SessionRuntime that snapshots the atomics and lock contents.
**redbear-wifictl: real NetworkManager-shaped D-Bus interface**
The dbus-nm feature was a no-op stub that just logged 'registered'
without actually doing anything. Replace with a real zbus interface:
- zbus::interface structs wrapping Arc<Mutex<NmWifiDevice>> shared state
- org.freedesktop.NetworkManager at /org/freedesktop/NetworkManager
exposes WirelessEnabled, WirelessHardwareEnabled, State, and
GetDevices().
- org.freedesktop.NetworkManager.Device.Wireless at
/org/freedesktop/NetworkManager/Devices/0 exposes HwAddress,
PermHwAddress, State, Ssid, Strength, LastScan, AccessPoints,
GetAccessPoints(), WirelessCapabilities.
- register_nm_interface() now actually builds a blocking
zbus::connection::Builder on the session bus, registers both
service name + object paths, spawns a background thread to hold the
connection alive, and returns. On session-bus connect failure it
logs an error and returns without crashing.
- When the dbus-nm feature is disabled, behavior is unchanged (no-op).
- Type model enriched: NmWifiDevice gains last_scan, active_ssid,
active_strength fields + Default derives; NmDeviceState gains
Default; NmAccessPoint gains Default.
**redbear-statusnotifierwatcher: wired into redbear-full**
The recipe compiled and had 12 tests but was NOT in any config —
the binary never deployed. Add:
- [package.files] stanza to its recipe.toml so the binary is staged
- redbear-statusnotifierwatcher = {} to config/redbear-full.toml
- launch_optional_component invocation in redbear-kde-session
**redbear-notifications: 8 host unit tests**
Previously zero tests. Add a #[cfg(test)] module covering:
- monotonic notification IDs
- capabilities list (spec values present)
- server information strings
- close-id + reason recording
- action invocation payload
- ordering preserved across multiple notifications
- independence between close and action records
**zbus build-ordering marker: clean source**
The marker source lib.rs contained 'pub struct Connection;' which
is misleading. Replace with a minimal comment explaining the
build-ordering purpose. The actual zbus crate is still resolved by
Cargo at downstream build time (unchanged behavior).
**D-Bus symlink cleanup**
Remove recipes/system/dbus/dbus-root-uid.patch (orphan symlink, not
in .gitignore-relevant scope). The actual patch stays in
local/patches/dbus/. The redox.patch in the same directory is a
real file (not a symlink) and is preserved.
**Build-system bug fixes**
- build-preflight.sh: when broken recipe.toml links cannot be restored
from git, invoke the guard-recipes.sh --fix path so untracked
custom links are regenerated.
- verify-fork-functions.sh: skip std-trait method names (fmt, eq,
clone, drop, etc.) when checking for dropped upstream functions —
these are derivable or compiler-caught, so a missed refactor is
inert, not a build blocker.
- verify-overlay-integrity.sh: add explicit 'return 0' on log helpers
so --quiet mode does not abort on the first log call under set -e.
Verification: host cargo test passes on all four modified daemons.
redbear-sessiond: 52 tests (12 new for the properties + signals).
redbear-wifictl: 35 tests (4 new for dbus_nm) + 2 cli_transport.
redbear-notifications: 8 tests (all new).
redbear-upower/udisks/polkit: unchanged, still pass.
275 lines
12 KiB
Bash
Executable File
275 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
CONFIG="redbear-full"
|
|
RELEASE="${REDBEAR_RELEASE:-}"
|
|
STRICT_DURABILITY="${REDBEAR_STRICT_DURABILITY:-0}"
|
|
STRICT_METADATA="${REDBEAR_STRICT_METADATA:-0}"
|
|
EXTRA_PACKAGES=()
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") [--config=<name>] [--release=<ver>] [--strict-durability] [--strict-metadata] [--extra-package=<pkg> ...]
|
|
EOF
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--config=*) CONFIG="${1#*=}" ;;
|
|
--release=*) RELEASE="${1#*=}" ;;
|
|
--strict-durability) STRICT_DURABILITY=1 ;;
|
|
--strict-metadata) STRICT_METADATA=1 ;;
|
|
--extra-package=*) EXTRA_PACKAGES+=("${1#*=}") ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown: $1" >&2; usage >&2; exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
echo ">>> Build preflight: $CONFIG"
|
|
|
|
# Defensive: detect and repair broken recipe.toml symlinks.
|
|
# These are tracked files — broken symlinks mean corruption from unknown source.
|
|
# Check both local/recipes/ (regular files) and recipes/ (tracked symlinks).
|
|
broken_recipes=$(find local/recipes recipes -name "recipe.toml" -xtype l 2>/dev/null | wc -l)
|
|
if [ "$broken_recipes" -gt 0 ]; then
|
|
echo ">>> WARNING: $broken_recipes broken recipe.toml symlinks detected — auto-repairing from git..." >&2
|
|
find local/recipes recipes -name "recipe.toml" -xtype l -exec git checkout -q -- {} \; 2>/dev/null
|
|
remaining=$(find local/recipes recipes -name "recipe.toml" -xtype l 2>/dev/null | wc -l)
|
|
if [ "$remaining" -gt 0 ] && [ -x "$SCRIPT_DIR/guard-recipes.sh" ]; then
|
|
# `git checkout` only restores TRACKED links. Untracked/generated links
|
|
# (custom local/recipes symlinks that were never committed) must be
|
|
# regenerated by the durability guard, which recomputes correct relative
|
|
# targets from local/recipes/.
|
|
echo ">>> $remaining link(s) not restorable from git — regenerating via guard-recipes.sh --fix..." >&2
|
|
"$SCRIPT_DIR/guard-recipes.sh" --fix >/dev/null 2>&1 || true
|
|
remaining=$(find local/recipes recipes -name "recipe.toml" -xtype l 2>/dev/null | wc -l)
|
|
fi
|
|
if [ "$remaining" -gt 0 ]; then
|
|
echo ">>> ERROR: $remaining recipe.toml files could not be restored. Build may fail." >&2
|
|
else
|
|
echo ">>> Repaired: all recipe.toml symlinks restored."
|
|
fi
|
|
fi
|
|
|
|
if [ -x "$SCRIPT_DIR/verify-overlay-integrity.sh" ]; then
|
|
if ! "$SCRIPT_DIR/verify-overlay-integrity.sh" --quiet; then
|
|
echo ">>> Preflight note: overlay integrity script reported legacy issues; continuing with build-focused checks."
|
|
fi
|
|
fi
|
|
|
|
# Enforce the "no fake version label" rule (local/AGENTS.md):
|
|
# every Cat 2 fork's `<X.Y.Z>-rb<B.B.B>` version must match the source
|
|
# content from upstream `<X.Y.Z>` + Red Bear patches, and the `-rb<B.B.B>`
|
|
# suffix must match the current Red Bear OS git branch.
|
|
if [ -x "$SCRIPT_DIR/verify-fork-versions.sh" ]; then
|
|
if ! "$SCRIPT_DIR/verify-fork-versions.sh" >/tmp/fork-verify.out 2>&1; then
|
|
if [ "${REDBEAR_SKIP_FORK_VERIFY:-0}" != "1" ]; then
|
|
cat /tmp/fork-verify.out >&2
|
|
echo "ERROR: fork version verification failed." >&2
|
|
echo " Set REDBEAR_SKIP_FORK_VERIFY=1 to bypass (DANGEROUS, only for emergency)." >&2
|
|
exit 1
|
|
fi
|
|
echo ">>> Preflight note: fork version verification bypassed via REDBEAR_SKIP_FORK_VERIFY=1." >&2
|
|
else
|
|
echo ">>> Preflight: fork versions verified against upstream."
|
|
fi
|
|
fi
|
|
|
|
if [ -x "$SCRIPT_DIR/check-fork-drift.sh" ] && [ "${REDBEAR_SKIP_DRIFT_CHECK:-0}" != "1" ]; then
|
|
if ! "$SCRIPT_DIR/check-fork-drift.sh" --no-fetch --threshold=50 >/tmp/fork-drift.out 2>&1; then
|
|
echo ">>> WARNING: Upstream drift detected in local forks:" >&2
|
|
grep -E 'DRIFT' /tmp/fork-drift.out >&2 || cat /tmp/fork-drift.out >&2
|
|
echo ">>> Run ./local/scripts/check-fork-drift.sh for full report." >&2
|
|
echo ">>> Run ./local/scripts/upgrade-forks.sh to upgrade." >&2
|
|
echo ">>> Set REDBEAR_SKIP_DRIFT_CHECK=1 to suppress this warning." >&2
|
|
else
|
|
echo ">>> Preflight: fork drift check passed."
|
|
fi
|
|
fi
|
|
|
|
if [ -x "$SCRIPT_DIR/verify-fork-functions.sh" ] && [ "${REDBEAR_SKIP_FUNCTION_CHECK:-0}" != "1" ]; then
|
|
if ! "$SCRIPT_DIR/verify-fork-functions.sh" --no-fetch --quiet >/tmp/fork-functions.out 2>&1; then
|
|
cat /tmp/fork-functions.out >&2
|
|
echo ">>> ERROR: Fork function verification failed — upstream functions are missing." >&2
|
|
echo ">>> This indicates a bad merge/cherry-pick dropped upstream code silently." >&2
|
|
echo ">>> Fix: ./local/scripts/upgrade-forks.sh --no-fetch --force-reapply <fork>" >&2
|
|
if [ "${REDBEAR_SKIP_FORK_VERIFY:-0}" != "1" ]; then
|
|
exit 1
|
|
fi
|
|
echo ">>> WARNING: Bypassed via REDBEAR_SKIP_FORK_VERIFY=1." >&2
|
|
else
|
|
echo ">>> Preflight: fork function verification passed."
|
|
fi
|
|
fi
|
|
|
|
# Phase 1.0B: enforce patch-presence invariant. Every patch in
|
|
# local/patches/<comp>/ MUST have its content reflected as commits in
|
|
# local/sources/<comp>/'s working tree, or the next fork reset will
|
|
# silently lose that work. See local/docs/legacy-obsolete-2026-07-25/PATCH-PRESERVATION-AUDIT-2026-07-12.md.
|
|
# Default is warn-only; --strict was decided against because current state
|
|
# is mid-recovery and we don't want to block every operator build.
|
|
if [ -x "$SCRIPT_DIR/verify-patch-content.sh" ] && [ "${REDBEAR_SKIP_PATCH_CONTENT_CHECK:-0}" != "1" ]; then
|
|
if ! "$SCRIPT_DIR/verify-patch-content.sh" >/tmp/patch-content.out 2>&1; then
|
|
cat /tmp/patch-content.out >&2
|
|
echo ">>> WARNING: Orphaned patches detected (Phase 1.0B). Build proceeding but Red Bear" >&2
|
|
echo ">>> work in orphaned patches is NOT in the fork. See audit doc for recovery." >&2
|
|
echo ">>> Set REDBEAR_SKIP_PATCH_CONTENT_CHECK=1 to suppress this warning." >&2
|
|
echo ">>> Run ./local/scripts/verify-patch-content.sh --strict for exit-1 enforcement." >&2
|
|
fi
|
|
fi
|
|
|
|
# Phase 4.2: collision detection — config [[files]] vs recipe installs.
|
|
# The original AGENTS.md "Collision Implications" claim that runtime
|
|
# collision detection exists was Phase 0 audit-disproven. This implements
|
|
# the GENERAL variant (config-vs-package) at pre-flight time so the
|
|
# silent-collision class of bug surfaces before install_packages().
|
|
if [ -x "$SCRIPT_DIR/verify-collision-detection.py" ] && [ "${REDBEAR_SKIP_COLLISION_CHECK:-0}" != "1" ]; then
|
|
if ! python3 "$SCRIPT_DIR/verify-collision-detection.py" >/tmp/collision.out 2>&1; then
|
|
cat /tmp/collision.out >&2
|
|
echo ">>> ERROR: Package-vs-config collision detected. Refusing to proceed." >&2
|
|
echo ">>> Set REDBEAR_SKIP_COLLISION_CHECK=1 to bypass (DANGEROUS)." >&2
|
|
else
|
|
echo ">>> Preflight: collision detection passed (config [[files]] vs recipe installs)."
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$RELEASE" ]; then
|
|
bash "$SCRIPT_DIR/build-release-mode.sh" --release="$RELEASE" --config="$CONFIG" "${EXTRA_PACKAGES[@]/#/--extra-package=}"
|
|
fi
|
|
|
|
python3 "$SCRIPT_DIR/validate-source-trees.py" "$CONFIG" "${EXTRA_PACKAGES[@]/#/--extra-package=}"
|
|
|
|
python3 - "$PROJECT_ROOT" "$CONFIG" "$STRICT_METADATA" "${EXTRA_PACKAGES[@]}" <<'PY'
|
|
import sys
|
|
import tomllib
|
|
from pathlib import Path
|
|
|
|
project_root = Path(sys.argv[1])
|
|
config_name = sys.argv[2]
|
|
strict_metadata = sys.argv[3] == "1"
|
|
extra_packages = sys.argv[4:]
|
|
|
|
def build_lookup():
|
|
lookup = {}
|
|
for root in (project_root / "recipes", project_root / "local/recipes"):
|
|
for recipe_toml in root.rglob("recipe.toml"):
|
|
if not recipe_toml.exists():
|
|
continue
|
|
parts = recipe_toml.parts
|
|
if "source" in parts or "target" in parts:
|
|
continue
|
|
package_name = recipe_toml.parent.name
|
|
lookup.setdefault(package_name, recipe_toml)
|
|
return lookup
|
|
|
|
def resolve_config(config_path, visited=None):
|
|
if visited is None:
|
|
visited = set()
|
|
config_path = config_path.resolve()
|
|
if config_path in visited:
|
|
return {}
|
|
visited.add(config_path)
|
|
config = tomllib.loads(config_path.read_text())
|
|
packages = dict(config.get("packages", {}))
|
|
for include in config.get("include", []):
|
|
include_path = config_path.parent / include
|
|
if include_path.exists():
|
|
included = resolve_config(include_path, visited)
|
|
for name, value in packages.items():
|
|
included[name] = value
|
|
packages = included
|
|
return packages
|
|
|
|
lookup = build_lookup()
|
|
config_path = project_root / "config" / f"{config_name}.toml"
|
|
requested = resolve_config(config_path)
|
|
for pkg in extra_packages:
|
|
requested.setdefault(pkg, {})
|
|
|
|
known_package_paths = {
|
|
name for name in lookup
|
|
if not name.startswith("host:")
|
|
}
|
|
|
|
errors = []
|
|
warnings = []
|
|
for package_name, package_conf in sorted(requested.items()):
|
|
if str(package_conf) == "ignore" or package_name in {"libgcc", "libstdcxx"}:
|
|
continue
|
|
recipe_toml = lookup.get(package_name)
|
|
if recipe_toml is None:
|
|
continue
|
|
recipe = tomllib.loads(recipe_toml.read_text())
|
|
source = recipe.get("source", {})
|
|
rel = recipe_toml.relative_to(project_root).as_posix()
|
|
is_wip_or_local = rel.startswith("recipes/wip/") or rel.startswith("local/recipes/")
|
|
if isinstance(source, dict) and "tar" in source and "blake3" not in source:
|
|
msg = f"missing blake3 for tar recipe: {recipe_toml.relative_to(project_root)}"
|
|
(errors if strict_metadata and not is_wip_or_local else warnings).append(msg)
|
|
for patch in source.get("patches", []):
|
|
patch_path = (recipe_toml.parent / patch).resolve()
|
|
if not patch_path.exists():
|
|
msg = f"missing patch file: {patch} for {recipe_toml.relative_to(project_root)}"
|
|
(errors if strict_metadata else warnings).append(msg)
|
|
build = recipe.get("build", {})
|
|
script = build.get("script", "") if isinstance(build, dict) else ""
|
|
if script:
|
|
for dep in known_package_paths:
|
|
bad = f"/tmp/{dep}/"
|
|
if bad in script:
|
|
msg = (
|
|
f"recipe {recipe_toml.relative_to(project_root)} references {bad} "
|
|
f"in its build script. The cookbook does not stage host dev-dependencies "
|
|
f"under /tmp/<name>; use ${{COOKBOOK_TOOLCHAIN}} (the cross recipe's "
|
|
f"toolchain directory populated from dev-dependencies) instead."
|
|
)
|
|
(errors if strict_metadata else warnings).append(msg)
|
|
|
|
for warning in warnings:
|
|
print(f"WARN: {warning}", file=sys.stderr)
|
|
if errors:
|
|
for error in errors:
|
|
print(f"ERROR: {error}", file=sys.stderr)
|
|
raise SystemExit(1)
|
|
PY
|
|
|
|
if [ -x "$SCRIPT_DIR/classify-patch-state.py" ]; then
|
|
python3 "$SCRIPT_DIR/classify-patch-state.py"
|
|
fi
|
|
|
|
if [ -x "$SCRIPT_DIR/verify-durable-source-edits.py" ]; then
|
|
args=()
|
|
if [ "$STRICT_DURABILITY" = "1" ]; then
|
|
args+=(--strict)
|
|
fi
|
|
python3 "$SCRIPT_DIR/verify-durable-source-edits.py" "${args[@]}"
|
|
fi
|
|
|
|
if [ "$CONFIG" = "redbear-full" ]; then
|
|
relibc_include="$(PROJECT_ROOT="$PROJECT_ROOT" bash -c 'source "$0"; redbear_relibc_stage_include_dir' "$SCRIPT_DIR/lib/relibc-surface.sh")"
|
|
relibc_lib_dir="$(PROJECT_ROOT="$PROJECT_ROOT" bash -c 'source "$0"; redbear_relibc_stage_lib_dir' "$SCRIPT_DIR/lib/relibc-surface.sh")"
|
|
if [ -d "$relibc_include" ] && [ -f "$relibc_lib_dir/libc.so" ]; then
|
|
for hdr in sys/signalfd.h sys/timerfd.h sys/eventfd.h threads.h; do
|
|
if [ ! -f "$relibc_include/$hdr" ]; then
|
|
echo ">>> Preflight note: relibc staged surface missing $hdr; top-level build should refresh/sync it before compilation."
|
|
fi
|
|
done
|
|
if ! grep -q 'strtold' "$relibc_include/stdlib.h" 2>/dev/null; then
|
|
echo ">>> Preflight note: relibc staged stdlib.h missing strtold declaration; top-level build should refresh/sync it before compilation."
|
|
fi
|
|
if ! readelf -Ws "$relibc_lib_dir/libc.so" | grep -q '_Z7strtoldPKcPPc'; then
|
|
echo ">>> Preflight note: relibc staged libc.so missing C++ strtold compatibility export; top-level build should refresh/sync it before compilation."
|
|
fi
|
|
else
|
|
echo ">>> Preflight note: relibc staged surface not present yet; build will refresh it if needed."
|
|
fi
|
|
fi
|
|
|
|
echo ">>> Build preflight passed"
|