Skip to content

Merge pull request #525 from AcreetionOS-Code/fix/ai-guardian-2026052… #999

Merge pull request #525 from AcreetionOS-Code/fix/ai-guardian-2026052…

Merge pull request #525 from AcreetionOS-Code/fix/ai-guardian-2026052… #999

name: Full Site Audit
on:
schedule:
- cron: '0 8 * * *' # Daily at 8AM UTC
workflow_dispatch:
push:
branches: [main]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check all HTML pages exist and have valid structure
run: |
errors=0
issues=""
for f in $(find . -maxdepth 1 -name '*.html' | sort); do
name=$(basename "$f")
issues="$issues\n## $name"
# Check file is not empty
size=$(wc -c < "$f")
if [ "$size" -lt 100 ]; then
issues="$issues\n- ❌ EMPTY: only ${size} bytes"
errors=$((errors+1))
continue
fi
# Check for title tag
if ! grep -qi '<title>' "$f"; then
issues="$issues\n- ❌ Missing <title>"
errors=$((errors+1))
fi
# Check for meta description
if ! grep -qi 'meta.*description' "$f"; then
issues="$issues\n- ⚠ Missing meta description"
errors=$((errors+1))
fi
# Check for viewport meta
if ! grep -qi 'viewport' "$f"; then
issues="$issues\n- ⚠ Missing viewport meta"
errors=$((errors+1))
fi
# Check for broken JS syntax (basic)
if grep -qi '<script>' "$f" 2>/dev/null; then
if grep -q 'document.write' "$f"; then
issues="$issues\n- ⚠ Uses document.write (slow)"
fi
fi
# Check for hardcoded http:// links (should be https)
http_links=$(grep -oP 'href="http://[^"]*"' "$f" | grep -v 'http://www.w3.org' | grep -v 'http://schemas.xmlsoap.org' | wc -l)
if [ "$http_links" -gt 0 ]; then
issues="$issues\n- ⚠ ${http_links} insecure http:// links"
errors=$((errors+1))
fi
# Check for favicon
if ! grep -qi 'rel="icon"' "$f" && ! grep -qi "rel='icon'" "$f"; then
issues="$issues\n- ⚠ Missing favicon"
fi
issues="$issues\n"
done
echo "$issues" > /tmp/audit-report.md
echo "total_errors=$errors" >> $GITHUB_OUTPUT
echo "report<<EOF" >> $GITHUB_OUTPUT
echo "$issues" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Check all download links (ISOs, exes, AppImages)
run: |
errors=0
echo "Checking ALL download links across the site..."
# Extract every file download URL (.iso, .exe, .AppImage, .dmg, .msi, .deb, .rpm)
grep -roPh 'https://[^"'"'"' )]*(\.iso|\.exe|\.AppImage|\.dmg|\.msi|\.deb|\.rpm|\.zip|setup[^"'"'"' )]*)' . --include='*.html' | sort -u > /tmp/download-links.txt
total=$(wc -l < /tmp/download-links.txt)
echo "Found $total download links to test"
echo "download_total=$total" >> $GITHUB_OUTPUT
while IFS= read -r url; do
[ -z "$url" ] && continue
# Skip known good domains to save time
if echo "$url" | grep -q 'github.com.*releases.*download'; then
echo "- ⏩ $url (GitHub release — skipping)" >> /tmp/download-results.txt
continue
fi
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 -L --range 0-0 "$url" 2>/dev/null || echo "FAIL")
size=$(curl -s -o /dev/null -w "%{size_download}" --max-time 15 -L --range 0-1048575 "$url" 2>/dev/null || echo 0)
if [ "$code" = "200" ] || [ "$code" = "206" ] || [ "$code" = "302" ] || [ "$code" = "301" ]; then
size_mb=$((size / 1048576))
echo "- ✅ $url → HTTP $code (${size_mb}MB)" >> /tmp/download-results.txt
elif [ "$code" = "401" ]; then
echo "- ⚠ $url → HTTP 401 (auth required, may be valid)" >> /tmp/download-results.txt
elif [ "$code" = "404" ]; then
echo "- ❌ $url → 404 NOT FOUND" >> /tmp/download-results.txt
errors=$((errors+1))
elif [ "$code" = "FAIL" ] || [ "$code" = "000" ]; then
echo "- ❌ $url → UNREACHABLE" >> /tmp/download-results.txt
errors=$((errors+1))
elif [ "$code" = "429" ]; then
echo "- ⏩ $url → rate limited (may be OK)" >> /tmp/download-results.txt
else
echo "- ⚠ $url → HTTP $code" >> /tmp/download-results.txt
fi
done < /tmp/download-links.txt
echo "download_errors=$errors" >> $GITHUB_OUTPUT
- name: Check external links are reachable
run: |
errors=0
echo "Checking external links..."
grep -roPh 'https://[^"'"'"' )]*' . --include='*.html' | grep -v 'acreetionos.org' | grep -v 'localhost' | grep -v 'w3.org/schema' | grep -v 'schema.org' | sort -u > /tmp/external-links.txt
total=$(wc -l < /tmp/external-links.txt)
echo "Found $total unique external links"
while IFS= read -r url; do
[ -z "$url" ] && continue
# Skip download links already tested above
if echo "$url" | grep -qP '\.(iso|exe|AppImage|dmg|msi|deb|rpm|zip)$'; then
continue
fi
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -L "$url" 2>/dev/null || echo "FAIL")
if [ "$code" = "FAIL" ] || [ "$code" = "000" ]; then
echo "- ❌ $url → UNREACHABLE" >> /tmp/link-errors.txt
errors=$((errors+1))
elif [ "$code" = "404" ]; then
echo "- ❌ $url → 404 NOT FOUND" >> /tmp/link-errors.txt
errors=$((errors+1))
elif [ "$code" != "200" ] && [ "$code" != "301" ] && [ "$code" != "302" ]; then
echo "- ⚠ $url → HTTP $code" >> /tmp/link-errors.txt
fi
done < /tmp/external-links.txt
echo "link_errors=$errors" >> $GITHUB_OUTPUT
- name: AI Review — Fix broken downloads and links
if: always()
env:
OPENROUTER_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: |
if [ -z "$OPENROUTER_KEY" ]; then
echo "No OpenRouter key - skipping AI review"
exit 0
fi
# Combine reports
{
echo "## HTML Structure Issues"
cat /tmp/audit-report.md 2>/dev/null || echo "None"
echo ""
echo "## Broken Download Links"
cat /tmp/download-results.txt 2>/dev/null | grep '❌' || echo "None"
echo ""
echo "## Broken External Links"
cat /tmp/link-errors.txt 2>/dev/null || echo "None"
} > /tmp/full-report.md
REPORT=$(cat /tmp/full-report.md)
# Tell AI which HTML files exist so it can suggest correct replacements
FILES=$(find . -maxdepth 1 -name '*.html' | sort | head -30)
PROMPT="You are fixing broken downloads on the AcreetionOS website.
Available HTML files: $FILES
Issues found:
$REPORT

Check failure on line 181 in .github/workflows/audit-all-pages.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/audit-all-pages.yml

Invalid workflow file

You have an error in your yaml syntax on line 181
For each broken download URL (marked ❌), suggest a replacement. The replacements must be one of:
- A URL from the same project (github.com/spivanatalie64 or github.com/AcreetionOS-Code)
- A URL to archive.org, sourceforge.net, or similar mirror
- Or fix the URL if it's just a typo
For each issue, respond in exactly this format:
FILE: <path-to-html-file>
OLD_URL: <broken url>
NEW_URL: <replacement url>
REASON: <why this replacement>"
curl -s "https://openrouter.ai/api/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENROUTER_KEY" \
-H "HTTP-Referer: https://acreetionos.org" \
-d "{
\"model\": \"meta-llama/llama-3.2-3b-instruct:free\",
\"messages\": [
{\"role\": \"system\", \"content\": \"You fix broken download links on websites. Be precise.\"},
{\"role\": \"user\", \"content\": $(echo "$PROMPT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")}
],
\"max_tokens\": 2000
}" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
content = data.get('choices',[{}])[0].get('message',{}).get('content','No response')
print(content)
except:
print('Failed to parse AI response')
" > /tmp/ai-fixes.md
cat /tmp/ai-fixes.md
# Apply AI-suggested fixes
echo ""
echo "=== Applying AI fixes ==="
python3 << 'PYEOF'
import re, os
with open('/tmp/ai-fixes.md') as f:
content = f.read()
# Parse FILE:/OLD_URL:/NEW_URL: blocks
blocks = re.findall(
r'FILE:\s*(.+?)\s*OLD_URL:\s*(.+?)\s*NEW_URL:\s*(.+?)\s*REASON:\s*(.+?)(?=FILE:|\Z)',
content, re.DOTALL
)
fixed = 0
for filepath, old_url, new_url, reason in blocks:
filepath = filepath.strip()
old_url = old_url.strip()
new_url = new_url.strip()
if not os.path.exists(filepath):
# Try with ./ prefix
filepath = './' + filepath.lstrip('./')
if not os.path.exists(filepath):
print(f' SKIP: {filepath} not found')
continue
# Read file
with open(filepath) as f:
data = f.read()
if old_url in data:
data = data.replace(old_url, new_url)
with open(filepath, 'w') as f:
f.write(data)
print(f' FIXED: {filepath} — {old_url[:50]}... → {new_url[:50]}...')
fixed += 1
else:
# Try URL-encoded version
old_encoded = old_url.replace('&', '&amp;')
if old_encoded in data:
data = data.replace(old_encoded, new_url)
with open(filepath, 'w') as f:
f.write(data)
print(f' FIXED: {filepath} (encoded) — {old_url[:50]}... → {new_url[:50]}...')
fixed += 1
else:
print(f' SKIP: {old_url[:60]} not found in {filepath}')
print(f'\nApplied {fixed} AI-suggested fixes')
with open('/tmp/fix-count.txt', 'w') as f:
f.write(str(fixed))
PYEOF
- name: Generate audit summary
id: summary
run: |
total=${{ steps.audit.outputs.total_errors || 0 }}
links=${{ steps.links.outputs.link_errors || 0 }}
downloads=${{ steps.downloads.outputs.download_errors || 0 }}
combined=$((total + links + downloads))
fixes=$(cat /tmp/fix-count.txt 2>/dev/null || echo 0)
echo "combined=$combined" >> $GITHUB_OUTPUT
echo "## 📋 Full Site Audit Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**HTML Issues:** $total" >> $GITHUB_STEP_SUMMARY
echo "**Broken Download Links:** $downloads" >> $GITHUB_STEP_SUMMARY
echo "**Broken External Links:** $links" >> $GITHUB_STEP_SUMMARY
echo "**AI Auto-Fixes Applied:** $fixes" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/audit-report.md 2>/dev/null >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Download Link Check" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/download-results.txt 2>/dev/null || echo "No download links found" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Broken Links" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/link-errors.txt 2>/dev/null || echo "None" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Create PR via Worker for any issues found
if: steps.summary.outputs.combined > 0
run: |
python3 << 'PYEOF'
import json, os, re, glob, urllib.request
issues = []
fixes = []
# Apply any AI-suggested fixes
for fixfile in ['/tmp/ai-fixes.md', '/tmp/download-fixes.txt']:
if os.path.exists(fixfile):
with open(fixfile) as f:
content = f.read()
reps = re.findall(r'REPLACE:\s*(.+?)\s*WITH:\s*(.+?)(?=REPLACE:|\Z)', content, re.DOTALL)
for old_text, new_text in reps:
old_text = old_text.strip()
new_text = new_text.strip()
for html_file in glob.glob('*.html'):
with open(html_file) as f:
data = f.read()
if old_text in data:
data = data.replace(old_text, new_text)
with open(html_file, 'w') as f:
f.write(data)
fixes.append(f"Applied fix to {html_file}")
issues.append(f"Fixed: {old_text[:60]}")
# Collect the audit report
report_parts = []
for f in ['/tmp/audit-report.md', '/tmp/link-errors.txt']:
if os.path.exists(f):
with open(f) as fh:
content = fh.read()
if content.strip():
report_parts.append(content[:1000])
if not issues:
issues.append("Issues detected during audit — see workflow logs")
# Collect ALL html files for the PR
files_to_pr = []
for html_file in sorted(glob.glob('*.html')):
with open(html_file) as f:
files_to_pr.append({"path": html_file, "content": f.read()})
branch = f"fix/auto-audit-{os.popen('date +%Y%m%d-%H%M%S').read().strip()}"
pr_body = "🤖 **Auto Audit Report**\n\n"
pr_body += f"**Issues found:** {len(issues)}\n\n"
for i in issues[:20]:
pr_body += f"- {i}\n"
if report_parts:
pr_body += f"\n**Details:**\n```\n{report_parts[0][:500]}\n```\n"
pr_body += f"\n**Fixes applied:** {'✅ Yes' if fixes else '❌ No — manual review needed'}\n"
pr_body += "\n---\n_Created automatically by the audit-all-pages workflow._"
payload = json.dumps({
"branch": branch,
"title": f"Audit: {len(issues)} issue(s) found",
"body": pr_body,
"files": files_to_pr
}).encode()
req = urllib.request.Request(
"https://acreetionos.org/api/github/create-pr",
data=payload,
headers={"Content-Type": "application/json"},
method="POST"
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
result = json.loads(resp.read())
if result.get('pr'):
print(f"✅ PR created: {result['pr']['url']}")
else:
print(f"⚠ {result.get('error', 'unknown')}")
except urllib.error.HTTPError as e:
print(f"❌ HTTP {e.code}: {e.read().decode()[:300]}")
except Exception as e:
print(f"❌ {e}")
PYEOF
# Commit any auto-fixes
git add -A
if git diff --cached --quiet; then
echo "No auto-fixes applied"
else
git commit -m "Auto-fix: audit issues $(date +%Y-%m-%d)" --allow-empty
git push origin "$BRANCH"
PR_URL=$(gh pr create \
--title "Auto-fix: $(date +%Y-%m-%d) audit issues" \
--body "Automated audit found issues. This PR contains the auto-fixes.\n\nCC: @AcreetionOS-Code" \
--base main \
--head "$BRANCH" 2>&1)
echo "Created: $PR_URL"
# Have AI review and auto-merge
if [ -n "$OPENROUTER_KEY" ]; then
sleep 5 # Wait for PR to be fully created
PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$' || echo "")
if [ -n "$PR_NUMBER" ]; then
# Get PR diff for AI review
gh pr diff "$PR_NUMBER" > /tmp/pr-diff.txt
DIFF=$(cat /tmp/pr-diff.txt | head -200)
# Ask AI to review
REVIEW=$(curl -s "https://openrouter.ai/api/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENROUTER_KEY" \
-H "HTTP-Referer: https://acreetionos.org" \
-d "{
\"model\": \"meta-llama/llama-3.2-3b-instruct:free\",
\"messages\": [
{\"role\": \"system\", \"content\": \"You review PRs. Reply with APPROVE or REJECT and a one-line reason.\"},
{\"role\": \"user\", \"content\": $(echo "Review this PR diff:\n$DIFF" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")}
],
\"max_tokens\": 200
}" 2>/dev/null | python3 -c "
import sys,json
try:
d=json.load(sys.stdin)
print(d.get('choices',[{}])[0].get('message',{}).get('content','APPROVE'))
except: print('APPROVE')
")
echo "AI Review: $REVIEW"
if echo "$REVIEW" | grep -qi "APPROVE"; then
gh pr merge "$PR_NUMBER" --merge --auto --delete-branch 2>gh pr merge "$PR_NUMBER" --merge --auto 2>&11 || true
echo "✅ PR auto-merged"
else
echo "⏸ PR needs manual review"
fi
fi
fi
fi