From e59e3258fc741051ccf3b2d4e6b728f980cf1ae3 Mon Sep 17 00:00:00 2001 From: juvdiaz Date: Wed, 27 May 2026 14:16:33 -0600 Subject: [PATCH] Narrow Gitea Actions deploy guardrail --- .gitea/workflows/homelab-main.yml | 88 +++++++++++++++++++++++++++++-- README.md | 11 ++-- 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/.gitea/workflows/homelab-main.yml b/.gitea/workflows/homelab-main.yml index 7695865..e450861 100644 --- a/.gitea/workflows/homelab-main.yml +++ b/.gitea/workflows/homelab-main.yml @@ -147,7 +147,7 @@ jobs: tofu -chdir="${stack}" validate done - - name: Block automatic deploy for high-impact changes + - name: Block automatic deploy for Raspberry Pi Gitea changes run: | set -euo pipefail @@ -156,14 +156,92 @@ jobs: )" if [[ -n "${event_before}" && ! "${event_before}" =~ ^0+$ ]]; then - changed_files="$(git diff --name-only "${event_before}" HEAD)" + base_ref="${event_before}" else - changed_files="$(git diff-tree --no-commit-id --name-only -r HEAD)" + base_ref="$(git rev-parse HEAD^ 2>/dev/null || git hash-object -t tree /dev/null)" fi + changed_files="$(git diff --name-only "${base_ref}" HEAD)" printf '%s\n' "${changed_files}" - if printf '%s\n' "${changed_files}" | grep -Eq '^(bootstrap/(provisioning|cluster|platform|edge)/|infra/gitea/|lab[.]sh|[.]gitea/workflows/)'; then - echo "High-impact bootstrap, runner, or workflow changes require a manual Debian run." + blocked_files="$(printf '%s\n' "${changed_files}" | grep -E '^infra/gitea/' || true)" + if printf '%s\n' "${changed_files}" | grep -qx 'lab.sh' && + python3 - "${base_ref}" <<'PY' + import re + import subprocess + import sys + + base_ref = sys.argv[1] + path = "lab.sh" + target_functions = { + "deploy_gitea", + "install_gitea_backup_timer", + "backup_gitea", + "drill_gitea_restore", + } + + + def function_ranges(content): + starts = [] + for index, line in enumerate(content.splitlines(), 1): + match = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\(\) \{", line) + if match: + starts.append((index, match.group(1))) + + ranges = [] + for offset, (start, name) in enumerate(starts): + end = starts[offset + 1][0] - 1 if offset + 1 < len(starts) else len(content.splitlines()) + if name in target_functions: + ranges.append((start, end)) + return ranges + + + current_ranges = function_ranges(open(path, encoding="utf-8").read()) + try: + base_content = subprocess.check_output( + ["git", "show", f"{base_ref}:{path}"], + stderr=subprocess.DEVNULL, + text=True, + ) + except subprocess.CalledProcessError: + base_ranges = [] + else: + base_ranges = function_ranges(base_content) + + diff = subprocess.check_output( + ["git", "diff", "--unified=0", base_ref, "HEAD", "--", path], + text=True, + ) + + + def overlaps(start, length, ranges): + if length == 0: + end = start + else: + end = start + length - 1 + return any(start <= range_end and end >= range_start for range_start, range_end in ranges) + + + for line in diff.splitlines(): + match = re.match(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@", line) + if not match: + continue + old_start = int(match.group(1)) + old_length = int(match.group(2) or "1") + new_start = int(match.group(3)) + new_length = int(match.group(4) or "1") + if overlaps(old_start, old_length, base_ranges) or overlaps(new_start, new_length, current_ranges): + sys.exit(0) + + sys.exit(1) + PY + then + blocked_files="${blocked_files}"$'\n'"lab.sh" + fi + blocked_files="$(printf '%s\n' "${blocked_files}" | sed '/^$/d' | sort -u)" + + if [[ -n "${blocked_files}" ]]; then + printf '%s\n' "${blocked_files}" + echo "Raspberry Pi Gitea service changes require a manual Debian run." exit 1 fi diff --git a/README.md b/README.md index 6f752d9..efb4615 100644 --- a/README.md +++ b/README.md @@ -407,12 +407,11 @@ This repo includes a Gitea Actions workflow at a repository-scoped Debian host runner with the label `homelab-debian`. The workflow validates shell syntax, Kubernetes manifests, and all OpenTofu -stacks before deployment. It automatically stops when high-impact files under -`bootstrap/provisioning`, `bootstrap/cluster`, `bootstrap/platform`, -`bootstrap/edge`, `infra/gitea`, `lab.sh`, or `.gitea/workflows` change; those -changes still require a manual Debian run. Lower-risk app changes proceed to -`./lab.sh apps` after validation passes, which skips Gitea, Pimox, cluster, -platform, and edge changes. +stacks before deployment. Automatic deploy is blocked only for Raspberry Pi +Gitea service changes: files under `infra/gitea/`, or edits inside the +`deploy_gitea`, `install_gitea_backup_timer`, `backup_gitea`, or +`drill_gitea_restore` functions in `lab.sh`. Other changes proceed to +`./lab.sh apps` after validation passes. `./lab.sh bootstrap-gitea-repo` also registers the Debian host SSH public key with the Gitea repository and switches the Debian working copy's `gitea` remote