From 5a8d6cfd4fe458ff967edaab3614b0abd1b3918f Mon Sep 17 00:00:00 2001 From: root Date: Wed, 20 May 2026 19:29:20 +0200 Subject: [PATCH] homelab: stop sops-policy scan resetting state on the next rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical bug — the loop reset age_block_last_idx unconditionally on every '- path_regex:' line. So after finding the target rule's age block, encountering the NEXT rule wiped the result and the function returned False. _grant_shared_secrets / _revoke_shared_secrets both silently no-op'd because of this. Fix: break out of the scan once we've collected the target rule's data. Refactored the remove helper to share a find_target_age_lines inner so the dangling-trailing-comma fixup uses the same logic. Co-Authored-By: Claude Opus 4.7 (1M context) --- bin/homelab | 95 ++++++++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/bin/homelab b/bin/homelab index 3df8d6a..09ada69 100755 --- a/bin/homelab +++ b/bin/homelab @@ -141,6 +141,10 @@ def _add_recipient_to_sops_policy(sops_path: Path, path_regex_pattern: str, pubk for i, line in enumerate(lines): stripped = line.strip() if stripped.startswith("- path_regex:"): + # If we've already collected what we need from the target rule, + # stop — don't let the next rule wipe age_block_last_idx. + if in_target_rule and age_block_last_idx is not None: + break in_target_rule = path_regex_pattern in line age_block_start = None age_block_last_idx = None @@ -152,13 +156,12 @@ def _add_recipient_to_sops_policy(sops_path: Path, path_regex_pattern: str, pubk continue if age_block_start is None: continue - # Inside the age block; track the last age1... line. if "age1" in stripped: if pubkey in line: return True # already a recipient age_block_last_idx = i elif stripped == "" or stripped.startswith("#"): - continue # blank / comment inside the block + continue else: break # next key, age block ended if age_block_last_idx is None: @@ -204,59 +207,47 @@ def _remove_recipient_from_sops_policy(sops_path: Path, path_regex_pattern: str, if not sops_path.exists(): return False lines = sops_path.read_text().splitlines(keepends=True) - in_target_rule = False - age_block_start = None - target_idx = None - for i, line in enumerate(lines): - stripped = line.strip() - if stripped.startswith("- path_regex:"): - in_target_rule = path_regex_pattern in line - age_block_start = None - target_idx = None - continue - if not in_target_rule: - continue - if "age: >-" in line: - age_block_start = i - continue - if age_block_start is None: - continue - if "age1" in stripped: - if pubkey in line: - target_idx = i + + def find_target_age_lines() -> tuple[list[int], bool]: + """Scan and return (indices_of_age1_lines_in_target_rule, found_rule).""" + in_target = False + age_block_start = None + age_idxs: list[int] = [] + for i, line in enumerate(lines): + stripped = line.strip() + if stripped.startswith("- path_regex:"): + if in_target and age_idxs: + return age_idxs, True + in_target = path_regex_pattern in line + age_block_start = None + age_idxs = [] + continue + if not in_target: + continue + if "age: >-" in line: + age_block_start = i + continue + if age_block_start is None: + continue + if "age1" in stripped: + age_idxs.append(i) + elif stripped == "" or stripped.startswith("#"): + continue + else: break - elif stripped == "" or stripped.startswith("#"): - continue - else: - break # next key, age block ended + return age_idxs, in_target + + age_idxs, found = find_target_age_lines() + if not found: + return False + target_idx = next((i for i in age_idxs if pubkey in lines[i]), None) if target_idx is None: - return True # nothing to remove — already absent + return True # already absent del lines[target_idx] - # Fix a now-dangling trailing comma on the new last age line if any. - # Find the new last age line in this rule's age block. - in_target_rule = False - age_block_start = None - last_age_idx = None - for i, line in enumerate(lines): - stripped = line.strip() - if stripped.startswith("- path_regex:"): - in_target_rule = path_regex_pattern in line - age_block_start = None - continue - if not in_target_rule: - continue - if "age: >-" in line: - age_block_start = i - continue - if age_block_start is None: - continue - if "age1" in stripped: - last_age_idx = i - elif stripped == "" or stripped.startswith("#"): - continue - else: - break - if last_age_idx is not None: + # After deletion, fix trailing comma on the new last age line. + new_age_idxs, _ = find_target_age_lines() + if new_age_idxs: + last_age_idx = new_age_idxs[-1] last_line = lines[last_age_idx] if last_line.rstrip().endswith(","): lines[last_age_idx] = last_line.rstrip().rstrip(",") + "\n"