Files
auto-publisher/core/deploy_sites.py

335 lines
11 KiB
Python

"""
Deploy all 8 auto-publisher sites to Proxmox LXC containers.
Creates CTs, installs nginx, configures cloudflared tunnels, and pushes code.
"""
import os
import sys
import time
import json
import subprocess
from pathlib import Path
PROXMOX = "root@10.30.20.85"
PROXMOX_PASS = "czapiewski"
STORAGE = "poolmaster"
BRIDGE = "vmbr0"
GATEWAY = "10.30.20.1"
TEMPLATE = "local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst"
DOMAIN = "thetempleofdoom.com"
GITEA_HOST = "10.30.20.149:3000"
GITEA_TOKEN = "dec25a837e0d85573706c4d5d608c2df215f3320"
# Site definitions: name → (ct_id, ip)
SITES = {
"ai": (135, "10.30.20.240"),
"tech": (136, "10.30.20.241"),
"science": (137, "10.30.20.242"),
"crypto": (138, "10.30.20.243"),
"linux": (139, "10.30.20.244"),
"gaming": (140, "10.30.20.246"),
"diy": (141, "10.30.20.247"),
"guides": (142, "10.30.20.248"),
}
def ssh(cmd: str, timeout: int = 30) -> str:
"""Run command on Proxmox host."""
result = subprocess.run(
["ssh", "-o", "StrictHostKeyChecking=accept-new", PROXMOX, cmd],
capture_output=True, text=True, timeout=timeout
)
return result.stdout.strip()
def pct_exec(vmid: int, cmd: str, timeout: int = 30) -> str:
"""Run command inside a CT."""
return ssh(f"pct exec {vmid} -- bash -c '{cmd}'", timeout=timeout)
def create_ct(name: str, vmid: int, ip: str) -> bool:
"""Create a Proxmox LXC container."""
print(f" Creating CT {vmid} ({name}) at {ip}...")
# Check if CT already exists
existing = ssh(f"pct list | grep '^{vmid} '")
if existing:
print(f" CT {vmid} already exists — skipping creation")
return True
cmd = (
f"pct create {vmid} {TEMPLATE} "
f"--hostname {name}-publisher "
f"--storage {STORAGE} "
f"--memory 2048 --swap 512 --cores 2 "
f"--net0 name=eth0,bridge={BRIDGE},ip={ip}/24,gw={GATEWAY} "
f"--unprivileged 1 --password {PROXMOX_PASS} "
f"--features nesting=1 --onboot 1"
)
out = ssh(cmd)
print(f" Created: {out}")
# Start it
ssh(f"pct start {vmid}")
time.sleep(8)
# Get assigned IP
ip_check = ssh(f"pct exec {vmid} -- hostname -I")
print(f" IP: {ip_check}")
return True
def setup_ct(name: str, vmid: int, ip: str) -> bool:
"""Install nginx and configure the site on a CT."""
print(f" Setting up CT {vmid} ({name})...")
# Update packages
pct_exec(vmid, "apt update -qq && apt install -y -qq nginx curl python3 python3-pip git 2>&1 | tail -5",
timeout=120)
# Create web root
pct_exec(vmid, "mkdir -p /var/www/html/articles /var/www/html/assets/images")
# Copy shared assets
local_assets = Path(__file__).resolve().parent.parent / "shared" / "assets"
if local_assets.exists():
# Create tarball of shared assets
subprocess.run(
f"cd {local_assets.parent} && tar czf /tmp/shared_assets.tar.gz assets/",
shell=True, capture_output=True
)
subprocess.run(
f"scp /tmp/shared_assets.tar.gz {PROXMOX}:/tmp/shared_assets.tar.gz",
shell=True, capture_output=True
)
ssh(f"pct push {vmid} /tmp/shared_assets.tar.gz /tmp/shared_assets.tar.gz")
pct_exec(vmid, "cd /var/www/html && tar xzf /tmp/shared_assets.tar.gz")
# Create nginx config
nginx_conf = f"""server {{
listen 80 default_server;
server_name {name}.{DOMAIN};
root /var/www/html;
index index.html;
location / {{
try_files $uri $uri/ $uri.html =404;
}}
location /assets/ {{
expires 30d;
add_header Cache-Control "public, immutable";
}}
location /rss.xml {{
add_header Content-Type "application/rss+xml";
}}
location /sitemap.xml {{
add_header Content-Type "application/xml";
}}
# Gzip
gzip on;
gzip_types text/plain text/html text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
}}"""
# Write nginx config via base64 to avoid quoting issues
import base64
encoded = base64.b64encode(nginx_conf.encode()).decode()
pct_exec(vmid, f"echo '{encoded}' | base64 -d > /etc/nginx/sites-available/default")
pct_exec(vmid, "rm -f /etc/nginx/sites-enabled/default && "
"ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default")
# Verify and restart nginx
result = pct_exec(vmid, "nginx -t 2>&1 && systemctl restart nginx && echo 'NGINX_OK'")
print(f" Nginx: {'OK' if 'NGINX_OK' in result else 'FAILED'}")
# Create a simple health endpoint
pct_exec(vmid, f"echo '<html><body><h1>{name}.{DOMAIN}</h1><p>Auto Publisher — coming soon</p></body></html>' > /var/www/html/index.html")
return True
def create_gitea_repo(name: str) -> bool:
"""Create a Gitea repository for a site."""
print(f" Creating Gitea repo: {name}-publisher...")
# Check if repo already exists
result = subprocess.run(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
f"http://{GITEA_HOST}/api/v1/repos/drjones/{name}-publisher"],
capture_output=True, text=True
)
if result.stdout.strip() == "200":
print(f" Repo {name}-publisher already exists")
return True
# Create via API
result = subprocess.run(
["curl", "-s", "-X", "POST",
f"http://{GITEA_HOST}/api/v1/user/repos",
"-H", "Content-Type: application/json",
"-H", f"Authorization: token {GITEA_TOKEN}",
"-d", json.dumps({
"name": f"{name}-publisher",
"description": f"Auto Publisher site: {name}.thetempleofdoom.com",
"private": False,
"auto_init": True,
})],
capture_output=True, text=True
)
if "200" in result.stdout or "201" in result.stdout or "409" in result.stdout:
print(f" Repo {name}-publisher created")
return True
else:
print(f" Gitea API failed: {result.stdout[:200]}")
# Fallback: create via filesystem + SQLite
ssh(f"pct exec 525 -- mkdir -p /var/lib/gitea/data/gitea-repositories/drjones/{name}-publisher.git")
ssh(f"pct exec 525 -- git init --bare /var/lib/gitea/data/gitea-repositories/drjones/{name}-publisher.git")
ssh(f"pct exec 525 -- chown -R gitea:gitea /var/lib/gitea/data/gitea-repositories/drjones/{name}-publisher.git")
return True
def verify_site(name: str, ip: str) -> bool:
"""Verify a site is serving content."""
try:
result = subprocess.run(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
f"http://{ip}:80/"],
capture_output=True, text=True, timeout=5
)
code = result.stdout.strip()
print(f" HTTP status: {code}")
return code == "200"
except Exception as e:
print(f" Verify failed: {e}")
return False
def deploy_site_code(name: str, vmid: int) -> bool:
"""Push site code to CT and to Gitea."""
print(f" Pushing code to CT {vmid}...")
site_dir = Path(__file__).resolve().parent.parent / "sites" / name
# Build a placeholder site if directory is empty
if not site_dir.exists() or not list(site_dir.glob("*.html")):
# Create placeholder
site_dir.mkdir(parents=True, exist_ok=True)
(site_dir / "articles").mkdir(exist_ok=True)
placeholder = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{name}.thetempleofdoom.com</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<header>
<nav>
<a href="/" class="logo">{name}.thetempleofdoom.com</a>
</nav>
</header>
<main>
<section class="hero">
<h1>{name.title()} Insights & Guides</h1>
<p>Expert {name} articles, tutorials, and deep dives. Updated daily.</p>
<p style="margin-top:2rem;color:var(--text2);">🚀 First article coming soon...</p>
</section>
<section class="articles-grid">
<div class="card">
<h2>Coming Soon</h2>
<p class="meta">The autonomous publishing pipeline is being set up.</p>
<p>Fresh {name} content will be published here automatically every day.</p>
</div>
</section>
</main>
<footer class="site-footer">
<p>&copy; 2026 {name}.thetempleofdoom.com</p>
</footer>
</body>
</html>"""
(site_dir / "index.html").write_text(placeholder)
# Tar and push
subprocess.run(
f"cd {site_dir.parent} && tar czf /tmp/{name}_site.tar.gz {name}/",
shell=True, capture_output=True
)
subprocess.run(
f"scp /tmp/{name}_site.tar.gz {PROXMOX}:/tmp/{name}_site.tar.gz",
shell=True, capture_output=True
)
ssh(f"pct push {vmid} /tmp/{name}_site.tar.gz /tmp/{name}_site.tar.gz")
pct_exec(vmid, f"cd /var/www/html && tar xzf /tmp/{name}_site.tar.gz --strip-components=1 && chown -R www-data:www-data /var/www/html")
# Push to Gitea
git_dir = site_dir
subprocess.run(
f"cd {git_dir} && "
f"git init 2>/dev/null; "
f"git config user.email 'drjones@thetempleofdoom.com'; "
f"git config user.name 'drjones'; "
f"git add . 2>/dev/null; "
f"git commit -m 'Initial: {name} publisher site' 2>/dev/null; "
f"git remote remove origin 2>/dev/null; "
f"git remote add origin http://drjones:{GITEA_TOKEN}@{GITEA_HOST}/drjones/{name}-publisher.git; "
f"git push -u origin main --force 2>&1",
shell=True, capture_output=True
)
return True
def main():
print("=" * 60)
print("AUTO PUBLISHER — Site Deployment")
print("=" * 60)
print()
for name, (vmid, ip) in SITES.items():
print(f"\n{'='*40}")
print(f" 📡 {name}.thetempleofdoom.com (CT {vmid}, {ip})")
print(f"{'='*40}")
# Step 1: Create CT
if not create_ct(name, vmid, ip):
print(f" ❌ Failed to create CT for {name}")
continue
# Step 2: Setup nginx
if not setup_ct(name, vmid, ip):
print(f" ❌ Failed to setup {name}")
continue
# Step 3: Create Gitea repo
create_gitea_repo(name)
# Step 4: Push initial code
deploy_site_code(name, vmid)
# Step 5: Verify
time.sleep(2)
if verify_site(name, ip):
print(f"{name}.thetempleofdoom.com is LIVE (http://{ip}:80)")
else:
print(f" ⚠️ {name} may need nginx restart")
print(f"\n{'='*60}")
print("DEPLOYMENT COMPLETE")
print(f"{'='*60}")
print("\nSites deployed:")
for name, (vmid, ip) in SITES.items():
print(f" {name}.thetempleofdoom.com → CT {vmid} @ {ip}")
print("\nNext: Set up Cloudflare tunnels for public access")
print("Run: python3 core/cloudflare_setup.py")
if __name__ == "__main__":
main()