|
Server IP : 149.13.87.7 / Your IP : 185.174.145.2 Web Server : nginx System : Linux h2 3.10.0-1160.76.1.el7.x86_64 #1 SMP Wed Aug 10 16:21:17 UTC 2022 x86_64 User : root ( 0) PHP Version : 8.0.28 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON Directory (0755) : /var/www/ |
| [ Home ] | [ C0mmand ] | [ Upload File ] |
|---|
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
=============================================================
MASS WEBSITE GENERATOR v3 — Real Hugo Themes
Uses 12 different pre-cloned Hugo themes for
maximum structural uniqueness across domains.
REQUIRES: run setup_themes.sh first to cache themes
Usage:
export CF_ACCOUNT_ID="xxx"
export CF_API_TOKEN="xxx"
python3 build_mass_v3.py domains.txt
python3 build_mass_v3.py food.com drink.com
=============================================================
"""
import os, sys, json, time, shutil, subprocess
import requests, hashlib, csv
from datetime import datetime
# ─────────────────────────────────────────────
# CONFIG
# ─────────────────────────────────────────────
CF_ACCOUNT_ID = os.environ.get("CF_ACCOUNT_ID", "39a058510fc70b590cd19740cd601ab9")
CF_API_TOKEN = os.environ.get("CF_API_TOKEN", "cfut_NEgUuC3EBxftU0cRNvpk2jXDYDxLM1YBxIBnSNLe1132fde9")
UNSPLASH_KEY = "D4Afb_uLn2foDIf6OehLo2Eq_u_6XaKfK7DdKgRj9Mg"
CF_MODEL = "@cf/meta/llama-3.2-1b-instruct"
WEBROOT = "/var/www"
THEMES_CACHE = "/var/www/_themes_cache"
LOG_FILE = "/var/www/build_log.csv"
# Themes requiring Hugo Extended (SCSS) — incompatible with standard Hugo on CentOS 7
SCSS_THEMES = {"hello-friend-ng", "coder", "terminal", "ficurinia", "ananke", "beautiful-hugo", "mainroad"}
# ─────────────────────────────────────────────
# THEME REGISTRY
# Each entry defines exactly how to configure
# that theme — params, content structure, etc.
# ─────────────────────────────────────────────
THEMES = {
"papermod": {
"repo": "papermod",
"name": "PaperMod",
"config_extra": """
[params]
author = "{brand}"
description = "{desc}"
ShowReadingTime = true
ShowPostNavLinks = true
ShowBreadCrumbs = true
ShowCodeCopyButtons = true
ShowShareButtons = false
defaultTheme = "dark"
homeInfoParams.Title = "{tagline}"
homeInfoParams.Content = "{hero_text}"
[[params.socialIcons]]
name = "email"
url = "mailto:{email}"
""",
"menu_extra": """
[[menu.main]]
identifier = "search"
name = "Search"
url = "/search/"
weight = 10
""",
"needs_search": True,
"content_format": "standard",
"index_type": "home_info", # PaperMod home-info mode
},
"ananke": {
"repo": "ananke",
"name": "Ananke",
"config_extra": """
[params]
description = "{desc}"
featured_image = "{hero_img}"
recent_posts_number = 3
text_color = ""
background_color_class = "bg-near-black"
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"terminal": {
"repo": "terminal",
"name": "Terminal",
"config_extra": """
[params]
themeColor = "{accent}"
fullWidthTheme = false
centerTheme = true
showMenuItems = 5
showLanguageSelector = false
[params.twitter]
creator = ""
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"hello-friend-ng": {
"repo": "hello-friend-ng",
"name": "Hello Friend NG",
"config_extra": """
[author]
name = "{brand} Team"
[params]
dateform = "Jan 2, 2006"
dateformFull = "Mon Jan 2, 2006"
description = "{desc}"
[params.logo]
logoText = "{brand}"
logoHomeLink = "/"
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"coder": {
"repo": "coder",
"name": "Coder",
"config_extra": """
[params]
author = "{brand}"
description = "{desc}"
keywords = "blog,developer,personal"
info = "{tagline}"
avatarurl = "{hero_img}"
footerLeft = "Powered by {brand}"
footerRight = "{year}"
colorScheme = "dark"
hidecolorschemetoggle = false
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"archie": {
"repo": "archie",
"name": "Archie",
"config_extra": """
[params]
mode = "dark"
useCDN = false
subtitle = "{tagline}"
description= "{desc}"
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"paper": {
"repo": "paper",
"name": "Paper",
"config_extra": """
[params]
color = "linen"
twitter = ""
description= "{desc}"
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"mainroad": {
"repo": "mainroad",
"name": "Mainroad",
"config_extra": """
[params]
description = "{desc}"
opengraph = true
schema = true
twitter_cards = false
readmore = false
authorbox = true
toc = false
pager = true
[params.sidebar]
home = "right"
list = "left"
single = false
widgets = ["recent", "categories", "taglist"]
[params.widgets]
recent_num = 5
tags_counter = false
[params.author]
name = "{brand} Team"
""",
"menu_extra": """
[[menu.side]]
name = "About"
pre = "<i class='fa fa-heart'></i>"
url = "/about/"
weight = 1
""",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"beautiful-hugo": {
"repo": "beautiful-hugo",
"name": "Beautiful Hugo",
"config_extra": """
[params]
subtitle = "{tagline}"
logo = ""
description = "{desc}"
bigimg = [{{src = "{hero_img}", desc = "{tagline}"}}]
useHLJS = true
pygmentsStyle = "trac"
pygmentsCodeFences = true
show_reading_time = true
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"mini": {
"repo": "mini",
"name": "Mini",
"config_extra": """
[params]
author = "{brand}"
description = "{desc}"
keywords = "blog, {brand}"
readMore = false
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"monochrome": {
"repo": "monochrome",
"name": "Monochrome",
"config_extra": """
[params]
description = "{desc}"
listPagePreviewMode = "summary"
navbar = "normal"
author = "{brand}"
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
"ficurinia": {
"repo": "ficurinia",
"name": "Ficurinia",
"config_extra": """
[params]
author = "{brand}"
description = "{desc}"
logo = ""
favicon = ""
iconTheme = "filled"
headerRandomize = false
""",
"menu_extra": "",
"needs_search": False,
"content_format": "standard",
"index_type": "standard",
},
}
THEME_KEYS = list(THEMES.keys())
# ─────────────────────────────────────────────
# NICHES
# ─────────────────────────────────────────────
NICHES = [
("Email Marketing Agency", "professional email marketing and deliverability",
["email marketing best practices","how to improve email deliverability",
"understanding SPF DKIM and DMARC","domain warmup complete guide","list hygiene best practices"]),
("Digital Marketing Studio", "full-service digital marketing and growth",
["digital marketing trends 2025","how to grow your online presence",
"content marketing that converts","paid advertising best practices","measuring marketing ROI"]),
("SaaS Growth Agency", "B2B SaaS marketing and demand generation",
["saas growth strategies","demand generation for b2b startups",
"product led growth explained","reducing churn in saas","building a go to market strategy"]),
("Brand Communications", "strategic brand communications and messaging",
["building a strong brand identity","why brand consistency matters",
"content strategy for brands","earned media strategies","brand messaging framework"]),
("Performance Marketing", "performance-based digital advertising and ROI",
["performance marketing fundamentals","ppc campaign optimization",
"retargeting strategies that work","landing page best practices","ab testing guide for marketers"]),
("Growth Hacking Studio", "rapid growth experimentation and user acquisition",
["growth hacking strategies 2025","viral loop design for startups",
"referral program best practices","funnel optimization tips","user acquisition on a budget"]),
("Marketing Automation", "marketing automation and CRM integration specialists",
["marketing automation guide","lead scoring best practices",
"crm integration for marketers","email sequence strategy","behavioral marketing explained"]),
("Data Analytics Agency", "data-driven marketing and analytics consulting",
["data driven marketing guide","attribution modeling explained",
"customer segmentation strategies","predictive analytics in marketing","building marketing dashboards"]),
]
TAGLINES = [
"Results You Can Measure.", "Built for Performance.",
"Growth. Strategy. Execution.", "Smarter Marketing Starts Here.",
"Precision at Scale.", "Where Data Meets Creativity.",
"Your Growth Partner.", "Performance Without Compromise.",
"Strategy Meets Execution.", "Engineered for Results.",
"Marketing That Moves Needles.", "Beyond Clicks. Real Growth.",
"Scale Smarter. Grow Faster.", "The Agency That Delivers.",
"Impact at Every Touchpoint.", "Bold Ideas. Proven Results.",
]
ACCENT_COLORS = [
"#4f8cff","#e0a030","#2ecc71","#e53060",
"#8b5cf6","#06b6d4","#ec4899","#10b981",
"#f59e0b","#6366f1","#d97706","#58a6ff",
]
BLOG_ANGLES = [
"practical how-to guide with actionable steps",
"opinion piece with a strong point of view",
"beginner-friendly explainer with real examples",
"advanced deep-dive for experienced professionals",
]
AI_TONES = [
"authoritative and confident",
"friendly and approachable",
"data-driven and analytical",
"bold and direct",
"warm and educational",
]
UNSPLASH_HERO = [
"dark server technology","night city digital abstract",
"dark office professional","technology network dark blue",
"dark minimal workspace","server room dark","abstract dark digital",
"dark data center","dark modern architecture","dark blue abstract",
]
# ─────────────────────────────────────────────
# HELPERS
# ─────────────────────────────────────────────
def dseed(domain):
return int(hashlib.md5(domain.encode()).hexdigest(), 16)
def pick(lst, seed, offset=0):
return lst[(seed + offset) % len(lst)]
def log(msg, icon=" →"): print(f"{icon} {msg}")
def step(t): print(f"\n{'─'*52}\n {t}\n{'─'*52}")
def ask_ai(prompt, retries=3, seed=0, call_idx=0):
if not CF_ACCOUNT_ID or not CF_API_TOKEN:
return "We deliver world-class solutions tailored to your business needs."
url = (f"https://api.cloudflare.com/client/v4/accounts/"
f"{CF_ACCOUNT_ID}/ai/run/{CF_MODEL}")
headers = {"Authorization": f"Bearer {CF_API_TOKEN}",
"Content-Type": "application/json"}
payload = {
"messages": [
{"role": "system", "content":
"You are a senior copywriter. Write compelling website copy. "
"Plain paragraphs only — no bullet points, no markdown headers."},
{"role": "user", "content": prompt}
],
"max_tokens": 220
}
for attempt in range(1, retries + 1):
try:
r = requests.post(url, headers=headers, json=payload, timeout=45)
r.raise_for_status()
result = r.json()["result"]["response"].strip()
time.sleep(3 + (seed % 2))
return result
except Exception as e:
err = str(e)[:50]
log(f"AI attempt {attempt}: {err}", " ⚠")
if attempt < retries:
time.sleep(8)
# Fallback — never block the build
fallbacks = [
"We deliver world-class solutions that drive real results for growing businesses.",
"Our team combines deep expertise with proven strategies to help you scale faster and smarter.",
"We are dedicated to delivering measurable outcomes through innovative and data-driven approaches.",
"We have helped hundreds of businesses achieve their goals through focused execution.",
"Great results start with understanding your unique challenges and building the right strategy.",
]
return fallbacks[seed % len(fallbacks)]
def unsplash(queries, dest):
os.makedirs(os.path.dirname(dest), exist_ok=True)
for q in queries + ["dark technology abstract"]:
try:
r = requests.get(
"https://api.unsplash.com/photos/random",
params={"query": q, "orientation": "landscape", "w": 1400, "h": 800},
headers={"Authorization": f"Client-ID {UNSPLASH_KEY}"},
timeout=15)
if r.status_code == 200:
url = r.json()["urls"]["regular"]
data = requests.get(url, timeout=25).content
with open(dest, "wb") as f: f.write(data)
time.sleep(0.5)
return True
except Exception: pass
time.sleep(1)
return False
def write_file(path, content):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
f.write(content)
def md(fm, body):
out = "---\n"
for k, v in fm.items():
out += f'{k}: "{str(v).replace(chr(34), chr(39))}"\n'
return out + "---\n\n" + body
def log_result(domain, status, theme, niche, pages):
new = not os.path.isfile(LOG_FILE)
with open(LOG_FILE, "a", newline="") as f:
w = csv.writer(f)
if new: w.writerow(["domain","status","theme","niche","pages","ts"])
w.writerow([domain, status, theme, niche, pages,
datetime.now().strftime("%Y-%m-%d %H:%M:%S")])
def already_built(domain):
if not os.path.isfile(LOG_FILE): return False
with open(LOG_FILE) as f:
for row in csv.reader(f):
if row and row[0] == domain and row[1] == "ok":
return True
return False
def theme_available(theme_key):
if theme_key in SCSS_THEMES:
return False # needs Hugo Extended — skip on CentOS 7
path = f"{THEMES_CACHE}/{THEMES[theme_key]['repo']}"
return os.path.isdir(path)
def copy_theme(theme_key, dest_themes_dir):
src = f"{THEMES_CACHE}/{THEMES[theme_key]['repo']}"
dst = f"{dest_themes_dir}/{theme_key}"
if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(src, dst)
return dst
# ─────────────────────────────────────────────
# MAIN BUILD
# ─────────────────────────────────────────────
def build_domain(domain, index=0):
if already_built(domain):
print(f" ⏭ SKIP {domain}")
return "skip"
seed = dseed(domain)
hugo_root = f"{WEBROOT}/{domain}"
brand = domain.split(".")[0].capitalize()
year = datetime.now().year
date_str = datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00")
# Pick unique combination
# Filter to only available themes
available = [k for k in THEME_KEYS if theme_available(k)]
if not available:
log("No themes cached! Run setup_themes.sh first.", " ✗")
return False
theme_key = pick(available, seed, 0)
niche_data = pick(NICHES, seed, 1)
tagline = pick(TAGLINES, seed, 2)
accent = pick(ACCENT_COLORS, seed, 3)
blog_angle = pick(BLOG_ANGLES, seed, 4)
ai_tone = pick(AI_TONES, seed, 5)
niche_label, niche_desc, blog_topics = niche_data
theme_cfg = THEMES[theme_key]
email = f"hello@{domain}"
print(f"\n{'═'*52}")
print(f" [{index+1}] {domain}")
print(f" Theme : {theme_cfg['name']} ({theme_key})")
print(f" Niche : {niche_label}")
print(f" Tone : {ai_tone}")
print(f"{'═'*52}")
# ── 1. Scaffold ───────────────────────────────
if os.path.exists(hugo_root):
shutil.rmtree(hugo_root)
r = subprocess.run(["hugo","new","site", hugo_root],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
if r.returncode != 0:
log(f"Scaffold failed: {r.stderr[:80]}", " ✗")
log_result(domain, "fail_scaffold", theme_key, niche_label, 0)
return False
log("Hugo scaffold OK", " ✅")
# ── 2. Copy theme ──────────────────────────────
try:
copy_theme(theme_key, f"{hugo_root}/themes")
log(f"Theme {theme_cfg['name']} copied", " ✅")
except Exception as e:
log(f"Theme copy failed: {e}", " ✗")
log_result(domain, "fail_theme", theme_key, niche_label, 0)
return False
# ── 3. Images ─────────────────────────────────
img_dir = f"{hugo_root}/static/img"
os.makedirs(img_dir, exist_ok=True)
hi = seed % len(UNSPLASH_HERO)
queries = [UNSPLASH_HERO[hi], UNSPLASH_HERO[(hi+1) % len(UNSPLASH_HERO)]]
hero_ok = unsplash(queries, f"{img_dir}/hero.jpg")
hero_url = f"https://{domain}/img/hero.jpg" if hero_ok else f"https://picsum.photos/seed/{seed}/1400/800"
log(f"Hero image: {'✓' if hero_ok else '✗ (placeholder)'}")
# ── 4. Config ─────────────────────────────────
# Build theme-specific extra config
extra = theme_cfg["config_extra"].format(
brand=brand, desc=niche_desc, tagline=tagline,
email=email, year=year, hero_img=hero_url,
accent=accent, hero_text="" # filled after AI gen
)
menu_extra = theme_cfg.get("menu_extra", "")
config = f"""baseURL = "https://{domain}/"
languageCode = "en-us"
title = "{brand}"
theme = "{theme_key}"
[pagination]
pagerSize = 6
[sitemap]
changefreq = "weekly"
priority = 0.5
[[menu.main]]
name = "Home"
url = "/"
weight = 1
[[menu.main]]
name = "Blog"
url = "/posts/"
weight = 2
[[menu.main]]
name = "About"
url = "/about/"
weight = 3
[[menu.main]]
name = "Contact"
url = "/contact/"
weight = 4
{menu_extra}
{extra}
"""
write_file(f"{hugo_root}/hugo.toml", config)
# robots.txt
write_file(f"{hugo_root}/static/robots.txt",
f"User-agent: *\nAllow: /\nSitemap: https://{domain}/sitemap.xml\n")
# Search page for PaperMod
if theme_cfg.get("needs_search"):
write_file(f"{hugo_root}/content/search.md",
'---\ntitle: "Search"\nlayout: "search"\n---\n')
# ── 5. AI Content ─────────────────────────────
log(f"Generating content via AI...")
call_idx = 0
def gen(prompt):
nonlocal call_idx
r = ask_ai(prompt, seed=seed, call_idx=call_idx)
call_idx += 1
return r
# Home
hero_text = gen(
f"Write 2 short punchy sentences for the homepage of {brand}, "
f"a {niche_desc} company. Tone: {ai_tone}. No bullet points.")
write_file(f"{hugo_root}/content/_index.md",
md({"title": brand, "description": tagline}, hero_text))
# About
about = gen(
f"Write 2 paragraphs for the About page of {brand}, a {niche_desc} company. "
f"Cover mission and why clients trust us. Tone: {ai_tone}.")
write_file(f"{hugo_root}/content/about/index.md",
md({"title": "About Us", "description": f"About {brand}"}, about))
# Contact
contact = gen(
f"Write 2 short sentences for the contact page of {brand}. "
f"Email is {email}. Team responds within 24 hours.")
write_file(f"{hugo_root}/content/contact/index.md",
md({"title": "Contact", "description": "Get in touch"}, contact))
# Privacy Policy
privacy = gen(
f"Write a short privacy policy for {brand} at {domain}. "
f"Cover data collection, cookies, GDPR, third parties. 3 short paragraphs.")
write_file(f"{hugo_root}/content/privacy-policy/index.md",
md({"title": "Privacy Policy", "description": "Privacy"}, privacy))
# Unsubscribe
unsub = gen(f"Write 2 friendly sentences confirming email unsubscribe for {brand}.")
write_file(f"{hugo_root}/content/unsubscribe/index.md",
md({"title": "Unsubscribed", "description": "Unsubscribed"}, unsub))
# Blog posts with internal links
log(f"Generating {len(blog_topics)} blog posts...")
post_urls = [f"/posts/{t.replace(' ','-').lower()}/" for t in blog_topics]
for i, topic in enumerate(blog_topics):
slug = topic.replace(" ", "-").lower()
others = [(blog_topics[j], post_urls[j])
for j in range(len(blog_topics)) if j != i][:2]
links_hint = " ".join([f"Mention and link to '{t}'." for t, _ in others])
body = gen(
f"Write a blog post as a {blog_angle} about '{topic}'. "
f"3 paragraphs, plain text, no bullet points. {links_hint}")
# Inject markdown internal links
for ot, ou in others:
first_word = ot.split()[0]
if first_word in body:
body = body.replace(first_word, f"[{first_word}]({ou})", 1)
write_file(f"{hugo_root}/content/posts/{slug}.md", md(
{"title": topic.title(), "date": date_str,
"description": f"Guide on {topic}", "author": "The Team"},
body))
log("Content written", " ✅")
# ── 6. Fix PaperMod home-info config ─────────
# Re-write config with actual hero_text now that we have it
if theme_key == "papermod":
safe_hero = hero_text.replace('"', "'").replace("\n", " ")[:120]
config2 = config.replace(
'homeInfoParams.Content = ""',
f'homeInfoParams.Content = "{safe_hero}"')
write_file(f"{hugo_root}/hugo.toml", config2)
# ── 7. Hugo build ─────────────────────────────
r = subprocess.run(
["hugo", "--source", hugo_root, "--minify"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
combined = r.stdout + r.stderr
if r.returncode != 0 and "Error" in combined:
if "TOCSS" in combined or "libsass" in combined or "scss" in combined.lower():
log(f"Theme {theme_key} needs Hugo Extended, auto-blacklisted", " ⚠")
SCSS_THEMES.add(theme_key)
log_result(domain, "fail_scss", theme_key, niche_label, 0)
return False
log("Hugo build FAILED:", " ✗")
# Show first real error line
for line in combined.split("\n"):
if "Error" in line or "error" in line:
print(f" {line}")
break
log_result(domain, "fail_hugo", theme_key, niche_label, 0)
return False
pages_lines = [l for l in combined.split("\n") if "Pages" in l]
pages_n = pages_lines[0].strip() if pages_lines else "?"
log(f"Hugo OK — {pages_n}", " ✅")
# ── 8. Nginx ───────────────────────────────────
nginx = f"""server {{
listen 80;
server_name {domain} www.{domain};
root /var/www/{domain}/public;
index index.html;
charset utf-8;
gzip on;
gzip_types text/plain text/css application/javascript image/svg+xml;
location / {{ try_files $uri $uri/ =404; }}
location ~* \\.(jpg|jpeg|png|gif|ico|css|js|svg|woff2)$ {{
expires 30d;
add_header Cache-Control "public, immutable";
}}
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
}}
"""
try:
with open(f"/etc/nginx/conf.d/{domain}.conf", "w") as f:
f.write(nginx)
log("Nginx config written", " ✅")
except Exception as e:
log(f"Nginx: {e}", " ⚠")
log_result(domain, "ok", theme_key, niche_label, pages_n)
return True
# ─────────────────────────────────────────────
# ENTRY POINT
# ─────────────────────────────────────────────
if __name__ == "__main__":
# Check themes cache
available = [k for k in THEME_KEYS if theme_available(k)]
if not available:
print(f"""
✗ No themes found in {THEMES_CACHE}
Run this FIRST to download all themes:
bash setup_themes.sh
Then run this script again.
""")
sys.exit(1)
print(f"\n Available themes ({len(available)}): {', '.join(available)}")
if len(sys.argv) < 2:
print("""
Usage:
python3 build_mass_v3.py domains.txt
python3 build_mass_v3.py food.com drink.com news.com
Resume: already-built domains skipped automatically
Log: /var/www/build_log.csv
""")
sys.exit(1)
domains = []
for arg in sys.argv[1:]:
if os.path.isfile(arg):
with open(arg) as f:
for line in f:
d = line.strip().lower()
if d and not d.startswith("#"):
domains.append(d)
else:
domains.append(arg.strip().lower())
domains = list(dict.fromkeys(domains))
print(f"\n Domains to build: {len(domains)}")
ok, skipped, fail = 0, 0, []
for i, domain in enumerate(domains):
result = build_domain(domain, i)
if result == "skip": skipped += 1
elif result: ok += 1
else: fail.append(domain)
# Reload nginx
try:
subprocess.run(["nginx","-t"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(["systemctl","reload","nginx"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print("\n ✅ Nginx reloaded")
except Exception: pass
if fail:
with open("/var/www/failed_domains.txt","w") as f:
f.write("\n".join(fail))
print(f"\n ⚠ Failed list → /var/www/failed_domains.txt")
print(f" Retry: python3 build_mass_v3.py /var/www/failed_domains.txt")
print(f"""
{'═'*52}
SUMMARY
Built : {ok}
Skipped : {skipped}
Failed : {len(fail)}
Log : {LOG_FILE}
{'═'*52}
""")
AnonSec - 2021