Skip to content

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

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

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

name: AI Website Guardian
on:
schedule:
- cron: '0 */3 * * *' # Every 3 hours
workflow_dispatch:
push:
branches: [main]
jobs:
guard:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
task: [pages, api, downloads, seo, security]
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Pages — check every HTML page loads
if: matrix.task == 'pages'
env:
OPENROUTER_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: |
python3 << 'PYEOF'
import subprocess, json, os, re
base_url = "https://acreetionos.org"
pages = []
for f in sorted(os.listdir('.')):
if f.endswith('.html') and os.path.isfile(f):
pages.append(f.replace('.html', ''))
issues = []
for page in pages:
url = f"{base_url}/{page}.html"
try:
r = subprocess.run(['curl', '-s', '-o', '/tmp/page.html', '-w', '%{http_code}',
'--max-time', '10', '-L', url], capture_output=True, text=True, timeout=15)
code = r.stdout.strip()
if code != '200' and code != '301' and code != '302':
issues.append(f"{page}: HTTP {code}")
continue
with open('/tmp/page.html') as f:
html = f.read()
# Basic checks
if '<title>' not in html: issues.append(f"{page}: missing title")
if '</html>' not in html: issues.append(f"{page}: truncated HTML")
if '404' in html[:500] and 'Not Found' in html[:500]: issues.append(f"{page}: 404 content")
except Exception as e:
issues.append(f"{page}: {e}")
report = {'pages_checked': len(pages), 'issues': issues, 'healthy': len(issues) == 0}
with open('/tmp/guard-report.json', 'w') as f:
json.dump(report, f, indent=2)
if issues:
print(f"Found {len(issues)} issues:")
for i in issues: print(f" ❌ {i}")
# AI fix
if os.environ.get('OPENROUTER_KEY'):
prompt = f"The AcreetionOS website has {len(issues)} page issues:\n" + "\n".join(issues)
prompt += "\n\nSuggest specific fixes. For each issue, provide: FILE:, LINE:, REPLACE:, WITH:"
r = subprocess.run(['curl', '-s', 'https://openrouter.ai/api/v1/chat/completions',
'-H', 'Content-Type: application/json',
'-H', f'Authorization: Bearer {os.environ["OPENROUTER_KEY"]}',
'-H', 'HTTP-Referer: https://acreetionos.org',
'-d', json.dumps({
'model': 'meta-llama/llama-3.2-3b-instruct:free',
'messages': [{'role': 'user', 'content': prompt}],
'max_tokens': 1500
})], capture_output=True, text=True, timeout=30)
try:
data = json.loads(r.stdout)
fixes = data['choices'][0]['message']['content']
with open('/tmp/ai-fixes.txt', 'w') as f:
f.write(fixes)
print(f"\nAI suggested fixes:\n{fixes[:500]}")
except: pass
else:
print(f"✅ All {len(pages)} pages healthy!")
PYEOF
- name: API — test all endpoints
if: matrix.task == 'api'
run: |
python3 << 'PYEOF'
import subprocess, json
endpoints = [
('GET', '/api/cve/status'),
('GET', '/api/hosting/count'),
('GET', '/api/hosting/providers'),
('GET', '/api/mirror/best?edition=cinnamon'),
]
issues = []
for method, path in endpoints:
url = f"https://acreetionos.org{path}"
try:
r = subprocess.run(['curl', '-s', '-w', '%{http_code}', '--max-time', '10', '-X', method, url],
capture_output=True, text=True, timeout=15)
body = r.stdout[:-3] if r.stdout else ''
code = r.stdout[-3:] if len(r.stdout) >= 3 else '000'
if code not in ['200', '301', '302']:
issues.append(f"{method} {path}: HTTP {code}")
except Exception as e:
issues.append(f"{method} {path}: {e}")
print(f"Tested {len(endpoints)} API endpoints")
if issues:
print(f"Issues: {len(issues)}")
for i in issues: print(f" ❌ {i}")
else:
print("✅ All endpoints healthy!")
PYEOF
- name: Downloads — verify all ISO links work
if: matrix.task == 'downloads'
run: |
python3 << 'PYEOF'
import subprocess, re, json
with open('flash.html') as f:
html = f.read()
editions_match = re.search(r'const EDITIONS\s*=\s*(\[[\s\S]*?\]);', html, re.DOTALL)
if not editions_match:
print("Could not find EDITIONS in flash.html")
exit(1)
js_text = editions_match.group(1)
# Convert JS object syntax to valid JSON (single quotes → double quotes)
js_text = re.sub(r"'", '"', js_text)
# Convert boolean values
js_text = re.sub(r':\s*true', ': true', js_text)
js_text = re.sub(r':\s*false', ': false', js_text)
# Remove trailing commas before ] and }
js_text = re.sub(r',\s*([\]}])', r'\1', js_text)
editions = json.loads(js_text)
issues = []
for ed in editions:
url = ed.get('iso_url', '')
if not url: continue
try:
r = subprocess.run(['curl', '-s', '-o', '/dev/null', '-w', '%{http_code}', '--max-time', '10', '-I', url],
capture_output=True, text=True, timeout=15)
code = r.stdout.strip()
if code in ['200', '206', '301', '302']:
continue # Healthy
elif code in ['401', '403']:
# Auth-protected — might still be valid (R2 buckets)
print(f" 🔒 {ed['id']}: HTTP {code} (may be valid)")
elif code in ['404']:
issues.append(f"{ed['id']}: HTTP {code} NOT FOUND — {url[:80]}")
elif code in ['429']:
print(f" ⏳ {ed['id']}: HTTP {code} rate limited (retry later)")
else:
issues.append(f"{ed['id']}: HTTP {code} — {url[:80]}")
except Exception as e:
issues.append(f"{ed['id']}: {e}")
print(f"Checked {len(editions)} edition ISO links")
if issues:
print(f"Broken: {len(issues)}")
for i in issues: print(f" ❌ {i}")
# AI fix using OpenRouter
api_key = os.environ.get('OPENROUTER_KEY')
if api_key and issues:
prompt = f"Fix these broken AcreetionOS ISO download URLs:\n" + "\n".join(issues)
prompt += "\nSuggest correct replacement URLs from archive.org, sourceforge, or github releases."
r = subprocess.run(['curl', '-s', 'https://openrouter.ai/api/v1/chat/completions',
'-H', 'Content-Type: application/json',
'-H', f'Authorization: Bearer {api_key}',
'-H', 'HTTP-Referer: https://acreetionos.org',
'-d', json.dumps({
'model': 'meta-llama/llama-3.2-3b-instruct:free',
'messages': [{'role': 'user', 'content': prompt}],
'max_tokens': 1000
})], capture_output=True, text=True, timeout=30)
with open('/tmp/download-fixes.txt', 'w') as f:
f.write(r.stdout)
print(f"\nAI suggestions saved to /tmp/download-fixes.txt")
else:
print("✅ All ISO download links healthy!")
PYEOF
- name: SEO — check meta tags
if: matrix.task == 'seo'
run: |
python3 << 'PYEOF'
import os, re
issues = []
for f in sorted(os.listdir('.')):
if not f.endswith('.html') or not os.path.isfile(f): continue
with open(f) as fh:
html = fh.read()
if '<title>' not in html: issues.append(f + ': no title')
if 'name="description"' not in html and "name='description'" not in html: issues.append(f + ': no description')
if 'rel="canonical"' not in html: issues.append(f + ': no canonical')
if 'rel="icon"' not in html and "rel='icon'" not in html: issues.append(f + ': no icon')
if 'og:title' not in html: issues.append(f + ': no OG title')
if 'twitter:card' not in html: issues.append(f + ': no Twitter card')
if issues:
print(f"SEO issues: {len(issues)}")
for i in issues: print(f" ⚠ {i}")
else:
print("✅ All SEO checks passed!")
PYEOF
- name: Create PR with findings via Worker
if: failure()
run: |
python3 << 'PYEOF'
import json, os, re, glob, urllib.request
# Collect all issues from the various task outputs
issues = []
fixes_applied = []
# Check for download fixes
for fixfile in ['/tmp/ai-fixes.txt', '/tmp/download-fixes.txt']:
if os.path.exists(fixfile):
with open(fixfile) as f:
content = f.read()
replacements = re.findall(r'REPLACE:\s*(.+?)\s*WITH:\s*(.+?)(?=REPLACE:|\Z)', content, re.DOTALL)
for old_url, new_url in replacements:
old_url = old_url.strip()
new_url = new_url.strip()
for html_file in glob.glob('*.html'):
with open(html_file) as f:
data = f.read()
if old_url in data:
data = data.replace(old_url, new_url)
with open(html_file, 'w') as f:
f.write(data)
fixes_applied.append(f"{html_file}: {old_url[:60]} → {new_url[:60]}")
issues.append(f"Fixed URL in {html_file}")
# Collect all changed files
files_to_pr = []
for html_file in glob.glob('*.html'):
with open(html_file) as f:
content = f.read()
files_to_pr.append({"path": html_file, "content": content})
if not issues:
issues.append("Unknown issue — check workflow logs for details")
branch = f"fix/ai-guardian-{os.popen('date +%Y%m%d-%H%M%S').read().strip()}"
pr_body = "🤖 **AI Website Guardian Report**\n\n"
pr_body += f"**Issues detected:** {len(issues)}\n\n"
for i in issues:
pr_body += f"- ❌ {i}\n"
if fixes_applied:
pr_body += "\n**Fixes applied:**\n"
for f in fixes_applied:
pr_body += f"- ✅ {f}\n"
else:
pr_body += "\n**Fixes applied:** ⚠ None — manual review needed\n"
pr_body += "\n---\n_Created automatically by the AI Website Guardian workflow._"
payload = json.dumps({
"branch": branch,
"title": f"Website Guardian: {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