⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.48
Server IP:
78.40.11.138
Server:
Linux tango.o2switch.net 4.18.0-553.30.1.lve.el8.x86_64 #1 SMP Tue Dec 3 01:21:19 UTC 2024 x86_64
Server Software:
Apache
PHP Version:
7.4.33
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
home
/
sc1voni9410
/
sb-gate
/
scripts
/
Edit File: qa-conformite.sh
#!/usr/bin/env bash # qa-conformite.sh — v1.1 — Volet v3 du gate : checks NOUVEAUX sur un site LIVE, filtrés par phase. # v1.1 : câble les 7 mécanismes jadis fantômes (P-003 home / P-021 exit-intent / P-024 lead-insert / # P-039 réservoir / P-050 plan / P-059 claude-md / P-010 breadcrumbs bad) + P-063 verticales + # chk_length_bounds branché par _sb_type + brief mapping réel. Portable (pas de process-substitution). set -uo pipefail HERE="$(cd "$(dirname "$0")" && pwd)"; ROOT="$(cd "$HERE/.." && pwd)" source "$HERE/sb-checks-v3.sh" [[ -f "$ROOT/sb.env" ]] && source "$ROOT/sb.env" 2>/dev/null || true DOCROOT="${1:?usage: qa-conformite.sh <docroot> [--phase N] [--sitedir DIR]}"; shift || true PHASE=""; SITEDIR="" while [[ $# -gt 0 ]]; do case "$1" in --phase) PHASE="$2"; shift 2;; --sitedir) SITEDIR="$2"; shift 2;; *) shift;; esac; done WP(){ wp --path="$DOCROOT" --skip-plugins --skip-themes "$@" 2>/dev/null; } WPF(){ wp --path="$DOCROOT" "$@" 2>/dev/null; } # avec plugins (meta, db) SITEURL="$(WP option get siteurl)"; FAIL=0 ok(){ echo " ✅ $1"; }; bad(){ echo " ❌ $1"; FAIL=1; }; warn(){ echo " ⚠️ $1"; } GET(){ curl -fsS -m 25 -A "Mozilla/5.0" "$1" 2>/dev/null; } P(){ [[ -z "$PHASE" || "${PHASE}" -ge "$1" ]]; } TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT NETLIST="${SB_NETWORK_DOMAINS_FILE:-$HERE/network-domains.txt}" SELF="$(printf '%s' "$SITEURL" | sed -E 's#https?://(www\.)?##; s#/.*##')" MV1="${SB_MIN_V1:-10}"; MV2="${SB_MIN_V2:-5}"; MV3="${SB_MIN_V3:-8}" echo "── CONFORMITÉ v3 (v1.1) : $SITEURL ${PHASE:+(phase $PHASE)} ──────────────" # ── Phase 0 : socle + PLAN-ULTIME (P-050 : >=50 pages [V1/V2/V3] + minima) ─── if P 0 && [[ -n "$SITEDIR" ]]; then chk_socle_files "$SITEDIR" >/dev/null 2>&1 && ok "P-013/027/036 socle complet" || bad "socle incomplet ($SITEDIR)" chk_plan_ultime "$SITEDIR/PLAN-ULTIME.md" "$MV1" "$MV2" "$MV3" >/dev/null 2>&1 \ && ok "P-050 plan ≥50 pages + minima verticales" || bad "P-050 plan <50 pages ou minima non tenus" elif P 0; then warn "socle/P-050 non vérifiés — passe --sitedir sites/<dom>"; fi # ── Phase 1 : thème + home landing (P-003 #17) + permalink ─────────────────── if P 1; then GET "$SITEURL" > "$TMP/home.html"; cp "$TMP/home.html" "$TMP/home_css.html" grep -aoiE "<link[^>]+stylesheet[^>]*>" "$TMP/home.html" | grep -aoiE "href=['\"][^'\"]+['\"]" | sed -E "s/href=['\"]//; s/['\"]\$//" | head -6 | while read -r css; do case "$css" in http*) : ;; /*) css="${SITEURL%/}$css";; *) css="${SITEURL%/}/$css";; esac GET "$css" >> "$TMP/home_css.html" 2>/dev/null; done chk_nav_no_bullets "$TMP/home_css.html" >/dev/null 2>&1 && ok "P-004 nav non-puces" || bad "P-004 nav puces brutes" chk_no_pagebuilder "$DOCROOT/wp-content/plugins" >/dev/null 2>&1 && ok "P-005 aucun page-builder" || bad "P-005 page-builder présent" PS=$(WP option get permalink_structure); [[ "$PS" == *"%postname%"* ]] && ok "P-012 permalink /%postname%/" || bad "P-012 permalink non propre ($PS)" # P-003 #17 home = landing (show_on_front=page + hero), pas un blog roll SOF=$(WP option get show_on_front) chk_home_landing "$SOF" "$TMP/home.html" >/dev/null 2>&1 && ok "P-003 home = landing (show_on_front=$SOF + hero)" || bad "P-003 home est un blog roll (show_on_front=$SOF ou pas de hero)" fi # ── Phase 2 : archi (breadcrumbs BAD, socle-pages, mentions noindex) ───────── if P 2; then U1=$(WP post list --post_status=publish --post_type=post --field=url | head -1) [[ -n "$U1" ]] && { GET "$U1" > "$TMP/one.html"; grep -qa 'BreadcrumbList' "$TMP/one.html" && ok "P-010 breadcrumbs (schema BreadcrumbList)" || bad "P-010 pas de schema BreadcrumbList (le thème doit l'émettre)"; } miss=""; for s in mention contact propos; do WP post list --post_type=page --field=url | grep -qiE "$s" || miss+="$s "; done [[ -z "$miss" ]] && ok "P-030 socle-pages (mentions+contact+à-propos)" || bad "P-030 socle-pages absente(s): $miss" ML=$(WP post list --post_type=page --field=url | grep -iE 'mention|legal' | head -1) [[ -n "$ML" ]] && { GET "$ML" > "$TMP/ml.html"; chk_mentions_noindex "$TMP/ml.html" >/dev/null 2>&1 && ok "P-031 mentions noindex" || bad "P-031 mentions sans noindex"; } || bad "P-031 mentions absente" fi # ── Phase 3 : contenu + longueur par _sb_type + brief mapping + verticales ── if P 3; then WP post list --post_status=publish --post_type=post,page --field=url 2>/dev/null | head -8 > "$TMP/urls.txt" URLS=(); while IFS= read -r u; do [[ -n "$u" ]] && URLS+=("$u"); done < "$TMP/urls.txt" WP post list --post_status=publish --post_type=post --field=url 2>/dev/null | head -8 > "$TMP/aurls.txt" AURLS=(); while IFS= read -r u; do [[ -n "$u" ]] && AURLS+=("$u"); done < "$TMP/aurls.txt" run_on_posts(){ local fn="$1"; shift; local any=0 for u in "${URLS[@]}"; do GET "$u" > "$TMP/p.html" || continue; any=1 "$fn" "$TMP/p.html" "$@" >/dev/null 2>&1 || { bad "$fn KO sur $u"; return; }; done (( any )) && ok "$fn OK sur ${#URLS[@]} pages" || warn "$fn : aucune page"; } gi_any=0; gi_fail=0 for u in "${AURLS[@]}"; do GET "$u" > "$TMP/a.html" || continue; gi_any=1 chk_generic_intro "$TMP/a.html" >/dev/null 2>&1 || { bad "P-018 intro/lede sur $u"; gi_fail=1; }; done (( gi_any && !gi_fail )) && ok "P-018 réponse directe (${#AURLS[@]} articles)" # P-020 faux-pro : mitigation = présence d'une page « comment-nous-verifions » MIT=0; WP post list --post_type=page --field=url | grep -qiE 'comment-nous|verifions|methodologie' && MIT=1 run_on_posts chk_faux_pro "$MIT" run_on_posts chk_img_alt run_on_posts chk_faqpage_schema [[ -s "$NETLIST" ]] && run_on_posts chk_network_interlink "$NETLIST" "$SELF" || warn "P-001 liste réseau absente" # P-016 chk_length_bounds BRANCHÉ live selon _sb_type (pilier/article) lb_fail=0; lb_any=0 WPF post list --post_status=publish --post_type=post --field=ID 2>/dev/null | head -12 | while read -r pid; do :; done for pid in $(WPF post list --post_status=publish --post_type=post --field=ID 2>/dev/null | head -12); do t=$(WPF post meta get "$pid" _sb_type 2>/dev/null); [[ "$t" == "pilier" ]] || t="article" WPF post get "$pid" --field=post_content > "$TMP/c.txt" 2>/dev/null || continue chk_length_bounds "$TMP/c.txt" "$t" >/dev/null 2>&1 || echo "$pid:$t" >> "$TMP/lbfail" done if [[ -s "$TMP/lbfail" ]]; then warn "P-016 longueur = garde-fou (hors bornes, à surveiller) : $(tr '\n' ' ' < "$TMP/lbfail")"; else ok "P-016 longueur dans les bornes (par _sb_type)"; fi # P-013 brief mapping réel (si SITEDIR) if [[ -n "$SITEDIR" ]]; then WPF post list --post_status=publish --post_type=post --field=post_name > "$TMP/slugs.txt" 2>/dev/null chk_brief_mapping "$SITEDIR/briefs" "$TMP/slugs.txt" >/dev/null 2>&1 && ok "P-013 1 brief = 1 article" || { chk_brief_mapping "$SITEDIR/briefs" "$TMP/slugs.txt" 2>&1 | sed 's/^/ /'; bad "P-013 posts sans brief"; } else warn "P-013 brief mapping non vérifié (--sitedir)"; fi # P-063 couverture verticales par _sb_vertical — sur le PUBLIÉ uniquement (vérité live ; # le réservoir 'future' ne compte pas : sinon la matrice ment en comptant des pages non-live). c1=$(WPF post list --post_type=post,page --post_status=publish --meta_key=_sb_vertical --meta_value=v1_local --field=ID 2>/dev/null | grep -c .) c2=$(WPF post list --post_type=post,page --post_status=publish --meta_key=_sb_vertical --meta_value=v2_pillar --field=ID 2>/dev/null | grep -c .) c3=$(WPF post list --post_type=post,page --post_status=publish --meta_key=_sb_vertical --meta_value=v3_review --field=ID 2>/dev/null | grep -c .) if (( c1+c2+c3 == 0 )); then warn "P-063 aucun post tagué _sb_vertical (verticales non mesurables)" else chk_verticale_counts "$c1" "$c2" "$c3" "$MV1" "$MV2" "$MV3" >/dev/null 2>&1 && ok "P-063 verticales V1=$c1 V2=$c2 V3=$c3" || bad "P-063 verticale sous seuil (V1=$c1≥$MV1 V2=$c2≥$MV2 V3=$c3≥$MV3)"; fi fi # ── Phase 4 : conversion (P-021 exit-intent, P-024 lead-insert) ───────────── if P 4; then # P-021 exit-intent sur la home grep -qaiE 'exit-intent|sb-exit|data-exit' "$TMP/home.html" 2>/dev/null && ok "P-021 pop-up exit-intent détecté" || warn "P-021 exit-intent non détecté sur la home" # P-024 lead-insert : POST test → wp_sb_leads → suppression ACT="${SB_LEAD_ACTION:-}"; TBL="${SB_LEADS_TABLE:-wp_sb_leads}"; EP="${SB_LEAD_ENDPOINT:-$SITEURL/wp-admin/admin-post.php}" if [[ -n "$ACT" ]]; then MARK="qa-lead-insert-$(date +%s)@example.invalid" curl -fsS -m 20 -X POST "$EP" --data-urlencode "action=$ACT" --data-urlencode "email=$MARK" --data-urlencode "message=qa" >/dev/null 2>&1 || true ROW=$(WPF db query "SELECT COUNT(*) FROM \`$TBL\` WHERE email LIKE '%$MARK%'" --skip-column-names 2>/dev/null | tr -d ' ') if [[ "${ROW:-0}" -ge 1 ]]; then ok "P-024 lead-insert : ligne créée dans $TBL"; WPF db query "DELETE FROM \`$TBL\` WHERE email LIKE '%$MARK%'" >/dev/null 2>&1 else bad "P-024 lead-insert : aucune ligne dans $TBL après POST (formulaire non câblé ?)"; fi else warn "P-024 lead-insert : SB_LEAD_ACTION non défini (sb.env) — test non exécuté"; fi fi # ── Phase 5 : GEO/LLM (bots IA, llms.txt) + réservoir (P-039 #19) ──────────── if P 5; then GET "$SITEURL/robots.txt" > "$TMP/robots.txt" && { chk_bots_ia "$TMP/robots.txt" >/dev/null 2>&1 && ok "P-028 robots autorise les bots IA" || bad "P-028 robots bloque un bot IA"; } GET "$SITEURL/llms.txt" >/dev/null 2>&1 && ok "P-028 llms.txt présent" || bad "P-028 llms.txt absent" # P-039 #19 future-reservoir : >=100 pages programmées, 1/jour WPF post list --post_status=future --field=post_date --posts_per_page=-1 2>/dev/null | grep -aoE '^[0-9]{4}-[0-9]{2}-[0-9]{2}' > "$TMP/res.txt" if [[ -s "$TMP/res.txt" ]]; then chk_reservoir_dates "$TMP/res.txt" "${SB_RESERVOIR_MIN:-100}" >/dev/null 2>&1 && ok "P-039 réservoir ≥100, 1/jour" || bad "P-039 réservoir insuffisant ou cadence >1/jour" else warn "P-039 aucune page programmée (future) — réservoir à constituer"; fi fi # ── COUCHE COMMERCIALE (P-111/P-113) : un site 🟢 mais qui ne peut pas vendre est un échec ── # P-111 : chaque MONEY-PAGE (déclarée dans MONETISATION.md du sitedir) existe (200), contient une # CAPTURE (form admin-post), et est accessible depuis la HOME (lien direct). # P-113 : la page contact ROUTE vers au moins une money-page (le contact est un carrefour d'argent). if [[ -n "$SITEDIR" && -f "$SITEDIR/MONETISATION.md" ]]; then MP=$(awk '/^## MONEY-PAGES/{f=1;next} f&&/^## /{f=0} f&&/^- \//{print $2}' "$SITEDIR/MONETISATION.md") if [[ -n "$MP" ]]; then GET "$SITEURL" > "$TMP/homem.html" mfail="" for mp in $MP; do mhtml=$(GET "${SITEURL%/}$mp") mcode=$? if [[ -z "$mhtml" ]]; then mfail+="$mp(KO) "; continue; fi printf '%s' "$mhtml" | grep -qE 'admin-post\.php' || mfail+="$mp(sans-capture) " grep -q "$mp" "$TMP/homem.html" || mfail+="$mp(pas-depuis-home) " done [[ -z "$mfail" ]] && ok "P-111 money-pages : 200 + capture + accessibles depuis la home" || bad "P-111 money-pages KO : $mfail" CURL_CONTACT=$(GET "${SITEURL%/}/contact/") hit=0; for mp in $MP; do printf '%s' "$CURL_CONTACT" | grep -q "$mp" && hit=1; done printf '%s' "$CURL_CONTACT" | grep -qE 'admin-post\.php' && hit=1 [[ $hit -eq 1 ]] && ok "P-113 contact route vers l'argent" || bad "P-113 contact ne route vers AUCUNE money-page ni capture" else warn "P-111 pas de bloc ## MONEY-PAGES dans MONETISATION.md"; fi elif [[ -n "$SITEDIR" ]]; then warn "P-111 MONETISATION.md absent du sitedir (la monétisation n'est pas définie !)"; fi # ── Transverse : indexabilité, porte IA, canonical, slop (P-109/P-028b/P-103/P-108) ── # P-109 : le site est PUBLIC (blog_public=1) et la home n'est PAS noindex (l'inverse de P-031). BP=$(WP option get blog_public) [[ "$BP" == "1" ]] && ok "P-109 blog_public=1" || bad "P-109 blog_public=$BP (site caché des moteurs !)" GET "$SITEURL" > "$TMP/homex.html" grep -qiE '<meta[^>]+robots[^>]+noindex' "$TMP/homex.html" && bad "P-109 home en noindex !" || ok "P-109 home indexable (0 noindex)" grep -qaiE 'moteur de recherche le plus|plateforme la plus avancée|algorithme (avancé|propriétaire)|base de données en temps réel' "$TMP/homex.html" \ && bad "P-106 claim produit fictif sur la home" || ok "P-106 aucun claim produit fictif (home)" # P-028b : LA PORTE, pas le fichier — chaque fetcher IA doit recevoir 200 (curl ignore robots → teste le SERVEUR). door_fail="" for ua in "GPTBot/1.2" "ClaudeBot/1.0" "Claude-User" "ChatGPT-User" "Perplexity-User"; do code=$(curl -s -o /dev/null -w '%{http_code}' -m 20 -A "$ua" "$SITEURL/") [[ "$code" == "200" ]] || door_fail+="$ua=$code " done [[ -z "$door_fail" ]] && ok "P-028b porte ouverte aux 5 fetchers IA (200)" || bad "P-028b fetcher(s) bloqué(s) au serveur : $door_fail" # P-103 : canonical — les 4 variantes (http/https × www/non-www) aboutissent sur la même URL finale. canon_fail="" for v in "http://$SELF/" "http://www.$SELF/" "https://$SELF/" "https://www.$SELF/"; do fin=$(curl -s -o /dev/null -w '%{url_effective}' -L -m 25 -A "Mozilla/5.0" "$v") [[ "$fin" == "${SITEURL%/}/" ]] || canon_fail+="$v→$fin " done [[ -z "$canon_fail" ]] && ok "P-103 canonical : 4 variantes → ${SITEURL%/}/" || bad "P-103 variante(s) non canonisée(s) : $canon_fail" # P-108 : slop — slugs suffixés -N dupliqués + emoji dans les titles. WP post list --post_type=post,page --post_status=publish --field=post_name > "$TMP/slugsx.txt" 2>/dev/null dupn=$(sed -nE 's/-(2|3|4)$//p' "$TMP/slugsx.txt" | while read -r b; do grep -qx "$b" "$TMP/slugsx.txt" && echo "$b"; done | head -3 | tr '\n' ' ') [[ -z "$dupn" ]] && ok "P-108 zéro slug dupliqué (-N)" || bad "P-108 slugs dupliqués : $dupn" if echo x | grep -qP 'x' 2>/dev/null; then EMO=$(WP post list --post_type=post,page --post_status=publish --field=post_title 2>/dev/null | grep -cP '[\x{1F300}-\x{1FAFF}\x{2700}-\x{27BF}\x{2600}-\x{26FF}]' 2>/dev/null); EMO=${EMO:-0} [[ "$EMO" -eq 0 ]] && ok "P-108 zéro emoji dans les titles" || bad "P-108 $EMO title(s) avec emoji" else warn "P-108 emoji-titles non testé (grep -P absent)"; fi # ── Transverse : DISABLE_WP_CRON + CLAUDE.md (P-059) ───────────────────────── WPF config get DISABLE_WP_CRON 2>/dev/null | grep -qiE 'true|1' && ok "P-040 disable-wpcron vrai" || { P 4 && warn "P-040 disable-wpcron non positionné (+ cron OS → REVUE-HUMAINE)"; } [[ -s "$ROOT/CLAUDE.md" ]] && ok "P-059 claude-md présent" || warn "P-059 CLAUDE.md absent du kit" echo "──────────────────────────────────────────────" (( FAIL )) && { echo "🔴 CONFORMITÉ v3 : ÉCHEC"; exit 1; } || { echo "🟢 CONFORMITÉ v3 : OK"; exit 0; }
Simpan