fix(build): make local/recipes/* sources unconditionally immutable

Internal Red Bear subprojects (tlc, redbear-*, redbear-greeter, etc.) live
under local/recipes/* and have no upstream source — they are committed to
our own gitea only. If lost, they cannot be recovered from any public
source.

The previous guard used is_local_overlay() && !redbear_allow_local_unfetch()
which could be bypassed by setting REDBEAR_ALLOW_LOCAL_UNFETCH=1. This was
triggered inadvertently (exact trigger unknown) and destroyed the source
tree of local/recipes/tui/tlc/source/.

This commit makes the protection UNCONDITIONAL:

- is_local_overlay() already correctly identifies any path under
  local/recipes/ as internal.
- The handle_clean unfetch path now refuses ALL local/recipes/* sources
  with a clear error message. No env var can override this.
- The fetch() path's git-reset/git-clean-ffdx and source-wipe guards now
  also refuse local overlays unconditionally.
- The dead redbear_allow_local_unfetch() function is removed.
- Makefile distclean-nuclear target is documented as a no-op for local/.

distclean still works for non-local recipes (upstream sources from
sources/redbear-0.1.0/ or git mirrors can be safely re-fetched).
This commit is contained in:
2026-06-18 15:13:11 +03:00
parent f54ac13045
commit cb8b093564
111 changed files with 39634 additions and 27 deletions
+7 -12
View File
@@ -39,7 +39,10 @@ use termion::{color, style};
// A repo manager, to replace repo.sh
/// Check if a recipe directory is a local Red Bear overlay (symlink into local/).
/// Local overlay recipes must never have their source/ deleted by unfetch/clean.
/// Local overlay recipes are INTERNAL Red Bear subprojects (tlc, redbear-*, etc.).
/// They have no upstream apart from our gitea — losing them is not recoverable
/// from a public source. They must NEVER be deleted by any command, regardless
/// of env vars or flags. The check is unconditional.
fn is_local_overlay(recipe_dir: &Path) -> bool {
if let Ok(resolved) = recipe_dir.canonicalize() {
let resolved_str = resolved.to_string_lossy();
@@ -48,14 +51,6 @@ fn is_local_overlay(recipe_dir: &Path) -> bool {
false
}
/// Check if the operator has explicitly allowed destructive operations on local overlays.
fn redbear_allow_local_unfetch() -> bool {
matches!(
std::env::var("REDBEAR_ALLOW_LOCAL_UNFETCH").ok().as_deref(),
Some("1" | "true" | "TRUE" | "yes" | "YES")
)
}
const REPO_HELP_STR: &str = r#"
Usage: repo <command> [flags] <recipe1> <recipe2> ...
@@ -853,10 +848,10 @@ fn handle_clean(
}
}
if dir.exists() && matches!(*command, CliCommand::Unfetch) {
if is_local_overlay(&recipe.dir) && !redbear_allow_local_unfetch() {
if is_local_overlay(&recipe.dir) {
eprintln!(
"[WARN] skipping unfetch for local overlay recipe {} \
(source lives in local/; set REDBEAR_ALLOW_LOCAL_UNFETCH=1 to override)",
"[WARN] refusing to unfetch local overlay recipe {} \
(local/recipes/* sources are immutable; internal Red Bear subprojects have no upstream)",
recipe.name.name()
);
} else {
+8 -4
View File
@@ -294,6 +294,10 @@ fn redbear_ensure_offline_git_source(
}
/// Check if a recipe directory is a local Red Bear overlay (symlink into local/).
/// Local overlay recipes are INTERNAL Red Bear subprojects (tlc, redbear-*, etc.).
/// They have no upstream apart from our gitea — losing them is not recoverable
/// from a public source. They must NEVER be deleted by any command, regardless
/// of env vars or flags. The check is unconditional.
fn is_local_overlay(recipe_dir: &Path) -> bool {
if let Ok(resolved) = recipe_dir.canonicalize() {
let resolved_str = resolved.to_string_lossy();
@@ -645,11 +649,11 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result
}
if !patches.is_empty() || script.is_some() {
if is_local_overlay(recipe_dir) && !redbear_allow_protected_fetch() {
if is_local_overlay(recipe_dir) {
log_to_pty!(
logger,
"[WARN] skipping git reset --hard for local overlay recipe at {} \
(set REDBEAR_ALLOW_PROTECTED_FETCH=1 to override)",
(local overlay sources are immutable)",
recipe_dir.display()
);
} else {
@@ -772,11 +776,11 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result
let mut cached = true;
if source_dir.is_dir() {
if tar_updated || fetch_is_patches_newer(recipe_dir, patches, &source_dir)? {
if is_local_overlay(recipe_dir) && !redbear_allow_protected_fetch() {
if is_local_overlay(recipe_dir) {
log_to_pty!(
logger,
"[WARN] refusing to wipe source for local overlay recipe at {} \
(set REDBEAR_ALLOW_PROTECTED_FETCH=1 to override)",
(local overlay sources are immutable)",
recipe_dir.display()
);
} else {