Skip to content

Commit 3de4448

Browse files
committed
chore: automated sync of local changes
1 parent b509fd8 commit 3de4448

7 files changed

Lines changed: 381 additions & 7 deletions

File tree

.github/workflows/hypatia-scan.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ jobs:
8686
if: steps.scan.outputs.findings_count > 0
8787
env:
8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89+
FLEET_PUSH_TOKEN: ${{ secrets.HYPATIA_DISPATCH_PAT }}
90+
FLEET_DISPATCH_TOKEN: ${{ secrets.HYPATIA_DISPATCH_PAT }}
8991
GITHUB_REPOSITORY: ${{ github.repository }}
9092
GITHUB_SHA: ${{ github.sha }}
9193
run: |
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Scheduled/dispatch pipeline for supervised-repo scanning via Hypatia + gitbot-fleet.
3+
name: Supervised Fleet Scan
4+
5+
on:
6+
schedule:
7+
- cron: '0 3 * * 1'
8+
workflow_dispatch:
9+
inputs:
10+
limit:
11+
description: 'Maximum repos to scan (0 = all resolved)'
12+
required: false
13+
default: '0'
14+
inventory_file:
15+
description: 'Optional inventory file path (.git-private-repos or .git-private-farm.scm)'
16+
required: false
17+
default: ''
18+
process_findings:
19+
description: 'Run fleet process-findings after scan'
20+
required: false
21+
type: boolean
22+
default: true
23+
24+
permissions:
25+
contents: read
26+
27+
jobs:
28+
supervised-scan:
29+
runs-on: ubuntu-latest
30+
31+
steps:
32+
- name: Checkout gitbot-fleet
33+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
34+
35+
- name: Clone Hypatia scanner
36+
run: |
37+
set -euo pipefail
38+
git clone --depth=1 https://github.com/hyperpolymath/hypatia.git ../hypatia
39+
40+
- name: Run supervised fleet scan
41+
env:
42+
LIMIT: ${{ github.event.inputs.limit || '0' }}
43+
INVENTORY_FILE: ${{ github.event.inputs.inventory_file || '' }}
44+
PROCESS_FINDINGS: ${{ github.event.inputs.process_findings || 'true' }}
45+
FLEET_SUPERVISED_REPOS_FILE: ${{ github.event.inputs.inventory_file || '' }}
46+
run: |
47+
set -euo pipefail
48+
ARGS=(--limit "$LIMIT")
49+
if [[ -n "$INVENTORY_FILE" ]]; then
50+
ARGS+=(--inventory "$INVENTORY_FILE")
51+
fi
52+
if [[ "$PROCESS_FINDINGS" == "true" ]]; then
53+
ARGS+=(--process)
54+
fi
55+
56+
echo "Running: fleet-coordinator.sh scan-supervised ${ARGS[*]}"
57+
bash fleet-coordinator.sh scan-supervised "${ARGS[@]}"

Justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ build-shared:
2626
coordinate *ARGS:
2727
bash fleet-coordinator.sh {{ARGS}}
2828

29+
# Scan supervised inventory (supports ~/.git-private-farm.scm and ~/.git-private-repos)
30+
scan-supervised *ARGS:
31+
bash fleet-coordinator.sh scan-supervised --process {{ARGS}}
32+
2933
# Run dispatch runner with a manifest
3034
dispatch manifest:
3135
bash scripts/dispatch-runner.sh "{{manifest}}"

README.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ Use these commands to run maintenance gating across repos:
2424
----
2525
just maintenance-hard-pass /absolute/path/to/repo
2626
just enroll-repos
27+
just scan-supervised
2728
----
2829

2930
`maintenance-hard-pass` enforces fail-on-warn release gating using the target repo maintenance script.
3031

3132
`enroll-repos` refreshes repository coverage metadata for gitbot-fleet and hypatia. Use `just enroll-repos /var$REPOS_DIR true` to also write enrollment directives into repos that already have `.machine_readable/`.
3233

34+
`scan-supervised` runs Hypatia across the supervised inventory (`~/.git-private-farm.scm`, `~/.git-private-repos`, or enrollment registry) and then processes findings for fleet dispatch.
35+
3336
== Bot Roster
3437

3538
[cols="1,3,1"]

fleet-coordinator.sh

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ USAGE:
3333
3434
COMMANDS:
3535
run-scan <repo> Run full scan on repository
36+
scan-supervised Run hypatia scan across supervised repos inventory
3637
process-findings Process pending findings and coordinate fixes
3738
generate-rules Generate new rules from observed patterns
3839
status Show fleet status
@@ -109,6 +110,76 @@ run_hypatia_scan() {
109110
fi
110111
}
111112

113+
scan_supervised_repos() {
114+
local process_after=false
115+
local limit=0
116+
local inventory=""
117+
local repos_root="/var/mnt/eclipse/repos"
118+
119+
while [[ $# -gt 0 ]]; do
120+
case "$1" in
121+
--process)
122+
process_after=true
123+
shift
124+
;;
125+
--limit)
126+
limit="${2:-0}"
127+
shift 2
128+
;;
129+
--inventory)
130+
inventory="${2:-}"
131+
shift 2
132+
;;
133+
--repos-root)
134+
repos_root="${2:-}"
135+
shift 2
136+
;;
137+
*)
138+
log_error "Unknown scan-supervised option: $1"
139+
return 2
140+
;;
141+
esac
142+
done
143+
144+
local list_script="$FLEET_DIR/scripts/list-supervised-repos.sh"
145+
if [[ ! -x "$list_script" ]]; then
146+
log_error "Missing inventory resolver: $list_script"
147+
return 1
148+
fi
149+
150+
local -a args
151+
args=(--repos-root "$repos_root" --limit "$limit")
152+
if [[ -n "$inventory" ]]; then
153+
args+=(--inventory "$inventory")
154+
fi
155+
156+
local -a repo_paths
157+
mapfile -t repo_paths < <("$list_script" "${args[@]}")
158+
159+
if [[ "${#repo_paths[@]}" -eq 0 ]]; then
160+
log_warn "No supervised repositories resolved"
161+
return 0
162+
fi
163+
164+
log_info "Scanning ${#repo_paths[@]} supervised repos"
165+
local scanned=0
166+
local failed=0
167+
168+
for repo_path in "${repo_paths[@]}"; do
169+
if run_hypatia_scan "$repo_path" >/dev/null; then
170+
scanned=$((scanned + 1))
171+
else
172+
failed=$((failed + 1))
173+
fi
174+
done
175+
176+
log_info "Supervised scan complete: scanned=$scanned failed=$failed"
177+
178+
if [[ "$process_after" == true ]]; then
179+
process_findings
180+
fi
181+
}
182+
112183
run_panicbot_scan() {
113184
local repo_path="$1"
114185
local repo_name
@@ -761,6 +832,11 @@ main() {
761832
run_hypatia_scan "$repo"
762833
;;
763834

835+
scan-supervised)
836+
shift
837+
scan_supervised_repos "$@"
838+
;;
839+
764840
process-findings)
765841
process_findings
766842
;;

scripts/list-supervised-repos.sh

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: PMPL-1.0-or-later
3+
#
4+
# Emit one supervised repository path per line.
5+
# Sources (first existing source wins unless --inventory is provided):
6+
# 1) --inventory / $FLEET_SUPERVISED_REPOS_FILE
7+
# 2) ~/.git-private-repos (newline list)
8+
# 3) ~/.git-private-farm.scm (SCM config with (repos . (...)))
9+
# 4) shared-context/enrollment/repos.json
10+
11+
set -euo pipefail
12+
13+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14+
FLEET_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
15+
16+
DEFAULT_REPOS_ROOT="/var/mnt/eclipse/repos"
17+
REPOS_ROOT="${REPOS_ROOT:-$DEFAULT_REPOS_ROOT}"
18+
LIMIT=0
19+
INVENTORY_FILE="${FLEET_SUPERVISED_REPOS_FILE:-}"
20+
21+
usage() {
22+
cat <<'EOF'
23+
usage: list-supervised-repos.sh [options]
24+
25+
Options:
26+
--repos-root <path> Base path for repo names (default: /var/mnt/eclipse/repos)
27+
--inventory <path> Inventory file to parse (.git-private-repos or .git-private-farm.scm)
28+
--limit <n> Emit at most n repositories (0 = unlimited)
29+
--help, -h Show this help
30+
EOF
31+
}
32+
33+
while [[ $# -gt 0 ]]; do
34+
case "$1" in
35+
--repos-root)
36+
REPOS_ROOT="${2:-}"
37+
shift 2
38+
;;
39+
--inventory)
40+
INVENTORY_FILE="${2:-}"
41+
shift 2
42+
;;
43+
--limit)
44+
LIMIT="${2:-0}"
45+
shift 2
46+
;;
47+
--help|-h)
48+
usage
49+
exit 0
50+
;;
51+
*)
52+
echo "error: unknown argument: $1" >&2
53+
usage >&2
54+
exit 2
55+
;;
56+
esac
57+
done
58+
59+
if [[ ! -d "$REPOS_ROOT" ]]; then
60+
echo "error: repos root does not exist: $REPOS_ROOT" >&2
61+
exit 1
62+
fi
63+
64+
declare -A seen
65+
declare -a repos=()
66+
67+
resolve_repo_path() {
68+
local candidate="$1"
69+
candidate="${candidate%/}"
70+
[[ -z "$candidate" ]] && return 1
71+
72+
# Expand placeholders sometimes used in generated enrollment metadata.
73+
candidate="${candidate//\/var\$REPOS_DIR/$REPOS_ROOT}"
74+
candidate="${candidate//\$REPOS_DIR/${REPOS_ROOT#/var/}}"
75+
76+
if [[ "$candidate" != /* ]]; then
77+
candidate="$REPOS_ROOT/$candidate"
78+
fi
79+
80+
if [[ -d "$candidate/.git" ]]; then
81+
printf '%s\n' "$candidate"
82+
return 0
83+
fi
84+
85+
return 1
86+
}
87+
88+
add_repo() {
89+
local raw="$1"
90+
local resolved=""
91+
resolved="$(resolve_repo_path "$raw" 2>/dev/null || true)"
92+
[[ -z "$resolved" ]] && return 0
93+
94+
if [[ -z "${seen[$resolved]:-}" ]]; then
95+
seen["$resolved"]=1
96+
repos+=("$resolved")
97+
fi
98+
}
99+
100+
add_from_plain_list() {
101+
local file="$1"
102+
while IFS= read -r line; do
103+
line="${line%%#*}"
104+
line="$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
105+
[[ -z "$line" ]] && continue
106+
add_repo "$line"
107+
done < "$file"
108+
}
109+
110+
add_from_private_farm_scm() {
111+
local file="$1"
112+
while IFS= read -r repo_name; do
113+
add_repo "$repo_name"
114+
done < <(
115+
awk '
116+
/\(repos[[:space:]]+\.[[:space:]]+\(/ {in_repos=1; next}
117+
in_repos && /\)\)/ {in_repos=0}
118+
in_repos {
119+
while (match($0, /"[^"]+"/)) {
120+
raw = substr($0, RSTART + 1, RLENGTH - 2)
121+
print raw
122+
$0 = substr($0, RSTART + RLENGTH)
123+
}
124+
}
125+
' "$file"
126+
)
127+
}
128+
129+
add_from_registry_json() {
130+
local file="$1"
131+
if ! command -v jq >/dev/null 2>&1; then
132+
return 0
133+
fi
134+
135+
while IFS= read -r item; do
136+
add_repo "$item"
137+
done < <(jq -r '.repos[]?.path // .repos[]?.name // empty' "$file" 2>/dev/null)
138+
}
139+
140+
SOURCE_USED=""
141+
142+
if [[ -n "$INVENTORY_FILE" && -f "$INVENTORY_FILE" ]]; then
143+
SOURCE_USED="$INVENTORY_FILE"
144+
elif [[ -f "$HOME/.git-private-repos" ]]; then
145+
INVENTORY_FILE="$HOME/.git-private-repos"
146+
SOURCE_USED="$INVENTORY_FILE"
147+
elif [[ -f "$HOME/.git-private-farm.scm" ]]; then
148+
INVENTORY_FILE="$HOME/.git-private-farm.scm"
149+
SOURCE_USED="$INVENTORY_FILE"
150+
elif [[ -f "$FLEET_DIR/shared-context/enrollment/repos.json" ]]; then
151+
INVENTORY_FILE="$FLEET_DIR/shared-context/enrollment/repos.json"
152+
SOURCE_USED="$INVENTORY_FILE"
153+
fi
154+
155+
if [[ -n "$INVENTORY_FILE" && -f "$INVENTORY_FILE" ]]; then
156+
case "$INVENTORY_FILE" in
157+
*.scm)
158+
add_from_private_farm_scm "$INVENTORY_FILE"
159+
;;
160+
*.json)
161+
add_from_registry_json "$INVENTORY_FILE"
162+
;;
163+
*)
164+
add_from_plain_list "$INVENTORY_FILE"
165+
;;
166+
esac
167+
fi
168+
169+
# Last-resort fallback: include current repo only to keep CI paths functional.
170+
if [[ "${#repos[@]}" -eq 0 ]]; then
171+
add_repo "$FLEET_DIR"
172+
fi
173+
174+
emitted=0
175+
for repo_path in "${repos[@]}"; do
176+
if [[ "$LIMIT" -gt 0 && "$emitted" -ge "$LIMIT" ]]; then
177+
break
178+
fi
179+
printf '%s\n' "$repo_path"
180+
emitted=$((emitted + 1))
181+
done

0 commit comments

Comments
 (0)