Docker-based self-hosted WordPress deployment system with: - Four-container stack (nginx, wordpress/php-fpm, mariadb, certbot) - Automatic SSL via Let's Encrypt with self-signed fallback - First-boot WordPress setup via WP-CLI (GeneratePress + child theme, plugins) - Interactive setup wizard and one-line install script - Backup, update, healthcheck, and SSL renewal scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
401 lines
19 KiB
JavaScript
401 lines
19 KiB
JavaScript
import React, { useState } from 'react';
|
||
|
||
const WebsiteBoxDiagram = () => {
|
||
const [activeView, setActiveView] = useState('flow');
|
||
|
||
const UserStep = ({ number, title, description }) => (
|
||
<div className="flex items-start gap-3 p-4 bg-blue-50 border-2 border-blue-300 rounded-lg">
|
||
<div className="flex-shrink-0 w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold text-sm">
|
||
{number}
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="font-semibold text-blue-900">{title}</div>
|
||
<div className="text-sm text-blue-700 mt-1">{description}</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const AutomatedStep = ({ title, items }) => (
|
||
<div className="p-4 bg-green-50 border-2 border-green-300 rounded-lg">
|
||
<div className="font-semibold text-green-900 flex items-center gap-2">
|
||
<span className="text-green-500">⚡</span> {title}
|
||
</div>
|
||
<ul className="mt-2 space-y-1">
|
||
{items.map((item, i) => (
|
||
<li key={i} className="text-sm text-green-700 flex items-start gap-2">
|
||
<span className="text-green-400 mt-0.5">✓</span>
|
||
<span>{item}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
);
|
||
|
||
const Arrow = ({ label }) => (
|
||
<div className="flex flex-col items-center py-2">
|
||
{label && <span className="text-xs text-gray-500 mb-1">{label}</span>}
|
||
<div className="text-gray-400 text-xl">↓</div>
|
||
</div>
|
||
);
|
||
|
||
const FlowView = () => (
|
||
<div className="space-y-2">
|
||
{/* Phase 1: Setup */}
|
||
<div className="bg-gray-100 rounded-xl p-4">
|
||
<h3 className="text-sm font-bold text-gray-600 uppercase tracking-wide mb-4">Phase 1: Initial Setup</h3>
|
||
|
||
<UserStep
|
||
number="1"
|
||
title="Provision VPS"
|
||
description="Sign up for BuyVM/Vultr/etc, create Ubuntu VPS, get IP address and SSH access"
|
||
/>
|
||
|
||
<Arrow label="SSH into server" />
|
||
|
||
<UserStep
|
||
number="2"
|
||
title="Run Install Command"
|
||
description={<span><span className="font-mono text-xs bg-blue-100 px-1 py-0.5 rounded">curl -fsSL https://<INSTALL_URL_TBD>/install.sh | bash</span></span>}
|
||
/>
|
||
|
||
<Arrow />
|
||
|
||
<AutomatedStep
|
||
title="install.sh runs automatically"
|
||
items={[
|
||
"Detects OS (Ubuntu/Debian)",
|
||
"Installs Docker & Docker Compose if missing",
|
||
"Adds user to docker group (activates via sg docker — no logout needed)",
|
||
"Clones WebsiteBox repository",
|
||
"Launches setup wizard"
|
||
]}
|
||
/>
|
||
|
||
<Arrow />
|
||
|
||
<UserStep
|
||
number="3"
|
||
title="Answer Setup Wizard"
|
||
description="Enter domain, site title, admin username, email, set your password, configure age gate"
|
||
/>
|
||
|
||
<Arrow />
|
||
|
||
<AutomatedStep
|
||
title="setup.sh generates configuration"
|
||
items={[
|
||
"Generates secure database passwords",
|
||
"Creates .env file with all settings",
|
||
"Generates WordPress security salts",
|
||
"Creates websitebox-data/ directory structure with correct permissions",
|
||
"Saves credentials to .credentials (only if password was auto-generated)"
|
||
]}
|
||
/>
|
||
</div>
|
||
|
||
{/* Phase 2: DNS */}
|
||
<div className="bg-gray-100 rounded-xl p-4">
|
||
<h3 className="text-sm font-bold text-gray-600 uppercase tracking-wide mb-4">Phase 2: Domain Configuration</h3>
|
||
|
||
<UserStep
|
||
number="4"
|
||
title="Configure DNS"
|
||
description="Point domain's A record to VPS IP address at your domain registrar"
|
||
/>
|
||
|
||
<div className="flex items-center justify-center py-3">
|
||
<div className="bg-yellow-100 border border-yellow-300 rounded-lg px-4 py-2 text-sm text-yellow-800">
|
||
⏳ Wait for DNS propagation (usually 5-30 min, can take up to 48h)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Phase 3: Launch */}
|
||
<div className="bg-gray-100 rounded-xl p-4">
|
||
<h3 className="text-sm font-bold text-gray-600 uppercase tracking-wide mb-4">Phase 3: Launch</h3>
|
||
|
||
<UserStep
|
||
number="5"
|
||
title="Start WebsiteBox"
|
||
description={<span><span className="font-mono text-xs bg-blue-100 px-1 py-0.5 rounded">docker compose up -d</span></span>}
|
||
/>
|
||
|
||
<Arrow />
|
||
|
||
<AutomatedStep
|
||
title="Containers start and self-configure"
|
||
items={[
|
||
"Pulls/builds container images",
|
||
"MariaDB initializes and passes healthcheck",
|
||
"WordPress entrypoint: waits for DB → installs core, themes, plugins via WP-CLI",
|
||
"nginx entrypoint: detects no SSL cert → serves HTTP → runs certbot → reloads with HTTPS",
|
||
"Age Gate configured, default content removed, permalinks set"
|
||
]}
|
||
/>
|
||
|
||
<div className="flex items-center justify-center py-3">
|
||
<div className="bg-amber-50 border border-amber-300 rounded-lg px-4 py-2 text-sm text-amber-800">
|
||
⚠ If SSL fails (DNS not ready yet), nginx serves an HTTP page explaining the issue.
|
||
<div className="text-xs mt-1">Fix: wait for DNS to propagate, then <span className="font-mono bg-amber-100 px-1 rounded">docker compose restart nginx</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<Arrow />
|
||
|
||
<UserStep
|
||
number="6"
|
||
title="Visit Your Site"
|
||
description="https://yourdomain.com shows your site with age gate. Log in at /wp-admin to start customizing."
|
||
/>
|
||
</div>
|
||
|
||
{/* Phase 4: Customize */}
|
||
<div className="bg-gray-100 rounded-xl p-4">
|
||
<h3 className="text-sm font-bold text-gray-600 uppercase tracking-wide mb-4">Phase 4: Customize & Use</h3>
|
||
|
||
<UserStep
|
||
number="7"
|
||
title="Customize Site"
|
||
description="Add content, upload images, customize theme — all via WordPress admin GUI"
|
||
/>
|
||
|
||
<div className="mt-4 p-3 bg-white rounded-lg border border-gray-200">
|
||
<div className="text-sm font-medium text-gray-700 mb-2">Ongoing automated maintenance:</div>
|
||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||
<div className="bg-green-50 p-2 rounded text-green-700">🔄 SSL auto-renewal (12h check)</div>
|
||
<div className="bg-green-50 p-2 rounded text-green-700">💾 Daily DB + weekly full backups</div>
|
||
<div className="bg-green-50 p-2 rounded text-green-700">🔒 Wordfence monitoring</div>
|
||
<div className="bg-green-50 p-2 rounded text-green-700">🚀 Container auto-restart on reboot</div>
|
||
<div className="bg-green-50 p-2 rounded text-green-700">🔧 WordPress minor auto-updates</div>
|
||
<div className="bg-green-50 p-2 rounded text-green-700">🗑️ Backup retention cleanup</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const ArchitectureView = () => (
|
||
<div className="space-y-6">
|
||
{/* Server diagram */}
|
||
<div className="bg-gray-800 rounded-xl p-6 text-white">
|
||
<h3 className="text-sm font-bold text-gray-400 uppercase tracking-wide mb-4">VPS Server Architecture</h3>
|
||
|
||
<div className="flex items-center justify-center mb-4">
|
||
<div className="text-center">
|
||
<div className="text-3xl mb-1">🌐</div>
|
||
<div className="text-sm text-gray-400">Internet</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex justify-center mb-2">
|
||
<div className="w-0.5 h-6 bg-gray-600"></div>
|
||
</div>
|
||
|
||
<div className="text-center text-xs text-gray-500 mb-2">Ports 80, 443 only</div>
|
||
|
||
<div className="flex justify-center mb-2">
|
||
<div className="w-0.5 h-6 bg-gray-600"></div>
|
||
</div>
|
||
|
||
{/* Docker container stack */}
|
||
<div className="border-2 border-dashed border-gray-600 rounded-xl p-4">
|
||
<div className="text-xs text-gray-500 mb-1 text-center">Docker Compose Stack</div>
|
||
<div className="text-xs text-gray-600 mb-3 text-center italic">restart: unless-stopped on all containers</div>
|
||
|
||
{/* Nginx */}
|
||
<div className="bg-green-700 rounded-lg p-3 mb-3">
|
||
<div className="font-semibold flex items-center gap-2">
|
||
<span>📦</span> nginx (custom image w/ entrypoint)
|
||
</div>
|
||
<div className="text-xs text-green-200 mt-1">SSL termination • Auto-acquires certs on first boot • Security headers • Static files</div>
|
||
<div className="text-xs text-green-300 mt-1 opacity-75">Healthcheck: curl localhost/nginx-health</div>
|
||
</div>
|
||
|
||
<div className="flex justify-center mb-2">
|
||
<div className="text-gray-500 text-sm">↓ FastCGI</div>
|
||
</div>
|
||
|
||
{/* WordPress */}
|
||
<div className="bg-blue-700 rounded-lg p-3 mb-3">
|
||
<div className="font-semibold flex items-center gap-2">
|
||
<span>📦</span> wordpress (custom image w/ WP-CLI)
|
||
</div>
|
||
<div className="text-xs text-blue-200 mt-1">PHP-FPM • First-boot entrypoint installs themes, plugins, configures site via WP-CLI</div>
|
||
<div className="text-xs text-blue-300 mt-1 opacity-75">Healthcheck: php-fpm-healthcheck • depends_on: db (healthy)</div>
|
||
</div>
|
||
|
||
<div className="flex justify-center mb-2">
|
||
<div className="text-gray-500 text-sm">↓ MySQL protocol</div>
|
||
</div>
|
||
|
||
{/* MariaDB */}
|
||
<div className="bg-orange-700 rounded-lg p-3 mb-3">
|
||
<div className="font-semibold flex items-center gap-2">
|
||
<span>📦</span> mariadb (database)
|
||
</div>
|
||
<div className="text-xs text-orange-200 mt-1">Internal only • Not exposed to host</div>
|
||
<div className="text-xs text-orange-300 mt-1 opacity-75">Healthcheck: healthcheck --connect --innodb_initialized</div>
|
||
</div>
|
||
|
||
{/* Certbot */}
|
||
<div className="bg-purple-700 rounded-lg p-3">
|
||
<div className="font-semibold flex items-center gap-2">
|
||
<span>📦</span> certbot (SSL renewal)
|
||
</div>
|
||
<div className="text-xs text-purple-200 mt-1">Renewal loop every 12h • Shares certbot-webroot volume with nginx</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Volumes */}
|
||
<div className="mt-4">
|
||
<div className="text-center text-xs text-gray-400 mb-2">./websitebox-data/ — single bind mount directory, all persistent data</div>
|
||
<div className="grid grid-cols-5 gap-2">
|
||
{['wordpress/', 'database/', 'certs/', 'certbot-webroot/', 'backups/'].map(name => (
|
||
<div key={name} className="bg-gray-700 rounded p-2 text-center text-xs">
|
||
<div className="text-yellow-400 mb-1">💾</div>
|
||
{name}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* What's automated vs manual */}
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="bg-green-50 border-2 border-green-200 rounded-xl p-4">
|
||
<h4 className="font-bold text-green-800 mb-3 flex items-center gap-2">
|
||
<span>⚡</span> WebsiteBox Handles
|
||
</h4>
|
||
<ul className="space-y-2 text-sm text-green-700">
|
||
<li className="flex items-start gap-2"><span>✓</span> Docker installation</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> Server configuration</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> SSL certificates (acquire + renew)</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> WordPress + theme + plugin install</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> Security hardening</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> Age gate setup</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> Automatic backups + retention</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> WordPress minor auto-updates</li>
|
||
<li className="flex items-start gap-2"><span>✓</span> Container auto-restart on reboot</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="bg-blue-50 border-2 border-blue-200 rounded-xl p-4">
|
||
<h4 className="font-bold text-blue-800 mb-3 flex items-center gap-2">
|
||
<span>👤</span> User Does Manually
|
||
</h4>
|
||
<ul className="space-y-2 text-sm text-blue-700">
|
||
<li className="flex items-start gap-2"><span>•</span> Provision VPS ($3-6/mo)</li>
|
||
<li className="flex items-start gap-2"><span>•</span> Register domain ($10-15/yr)</li>
|
||
<li className="flex items-start gap-2"><span>•</span> Configure DNS A record</li>
|
||
<li className="flex items-start gap-2"><span>•</span> Run install command</li>
|
||
<li className="flex items-start gap-2"><span>•</span> Answer setup wizard</li>
|
||
<li className="flex items-start gap-2"><span>•</span> Create site content</li>
|
||
<li className="flex items-start gap-2"><span>•</span> Update plugins (via WP admin GUI)</li>
|
||
<li className="flex items-start gap-2"><span>•</span> Run update.sh (occasionally)</li>
|
||
<li className="flex items-start gap-2 text-blue-400"><span>•</span> Configure remote backups (optional)</li>
|
||
<li className="flex items-start gap-2 text-blue-400"><span>•</span> Set up SMTP (optional)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const FilesView = () => (
|
||
<div className="bg-gray-900 rounded-xl p-4 font-mono text-sm">
|
||
<div className="text-gray-400 mb-4"># Repository structure</div>
|
||
<div className="space-y-1 text-gray-300">
|
||
<div className="text-yellow-400">websitebox/</div>
|
||
<div className="pl-4">├── <span className="text-green-400">docker-compose.yml</span></div>
|
||
<div className="pl-4">├── <span className="text-green-400">.env.example</span></div>
|
||
<div className="pl-4">├── <span className="text-cyan-400">install.sh</span> <span className="text-gray-500">← curl this to start</span></div>
|
||
<div className="pl-4">├── <span className="text-cyan-400">setup.sh</span> <span className="text-gray-500">← interactive wizard</span></div>
|
||
<div className="pl-4">├── README.md</div>
|
||
<div className="pl-4">│</div>
|
||
<div className="pl-4">├── <span className="text-gray-600">websitebox-data/</span> <span className="text-gray-500">← all persistent data (gitignored)</span></div>
|
||
<div className="pl-8 text-gray-500">├── wordpress/ database/ certs/ certbot-webroot/ backups/</div>
|
||
<div className="pl-4">│</div>
|
||
<div className="pl-4">├── <span className="text-yellow-400">nginx/</span></div>
|
||
<div className="pl-8">├── Dockerfile</div>
|
||
<div className="pl-8">├── <span className="text-cyan-400">entrypoint.sh</span> <span className="text-gray-500">← SSL auto-bootstrap</span></div>
|
||
<div className="pl-8">├── nginx.conf</div>
|
||
<div className="pl-8">├── wordpress.conf</div>
|
||
<div className="pl-8">├── wordpress-ssl.conf</div>
|
||
<div className="pl-8">└── ssl-params.conf</div>
|
||
<div className="pl-4">│</div>
|
||
<div className="pl-4">├── <span className="text-yellow-400">wordpress/</span></div>
|
||
<div className="pl-8">├── Dockerfile <span className="text-gray-500">← includes WP-CLI</span></div>
|
||
<div className="pl-8">├── <span className="text-cyan-400">entrypoint.sh</span> <span className="text-gray-500">← first-boot WP-CLI setup</span></div>
|
||
<div className="pl-8">├── wp-config-docker.php</div>
|
||
<div className="pl-8">├── uploads.ini</div>
|
||
<div className="pl-8">└── <span className="text-yellow-400">wp-content/</span></div>
|
||
<div className="pl-12">├── <span className="text-yellow-400">themes/websitebox/</span> <span className="text-gray-500">← child theme</span></div>
|
||
<div className="pl-12">└── <span className="text-yellow-400">mu-plugins/</span></div>
|
||
<div className="pl-16">└── websitebox-setup.php <span className="text-gray-500">← status checker + XML-RPC disable</span></div>
|
||
<div className="pl-4">│</div>
|
||
<div className="pl-4">├── <span className="text-yellow-400">scripts/</span></div>
|
||
<div className="pl-8">├── <span className="text-cyan-400">ssl-renew.sh</span></div>
|
||
<div className="pl-8">├── <span className="text-cyan-400">backup.sh</span> <span className="text-gray-500">← manual backup + --prune-only</span></div>
|
||
<div className="pl-8">├── <span className="text-cyan-400">update.sh</span> <span className="text-gray-500">← update WebsiteBox</span></div>
|
||
<div className="pl-8">└── <span className="text-cyan-400">healthcheck.sh</span></div>
|
||
<div className="pl-4">│</div>
|
||
<div className="pl-4">└── <span className="text-yellow-400">docs/</span></div>
|
||
<div className="pl-8">├── SECURITY.md</div>
|
||
<div className="pl-8">├── TROUBLESHOOTING.md</div>
|
||
<div className="pl-8">└── UPDATING.md</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 p-6">
|
||
<div className="max-w-3xl mx-auto">
|
||
{/* Header */}
|
||
<div className="text-center mb-6">
|
||
<h1 className="text-3xl font-bold text-gray-900 mb-2">📦 WebsiteBox</h1>
|
||
<p className="text-gray-600">Self-hosted WordPress portfolio in minutes</p>
|
||
</div>
|
||
|
||
{/* View tabs */}
|
||
<div className="flex gap-2 mb-6 bg-white rounded-lg p-1 shadow-sm">
|
||
{[
|
||
{ id: 'flow', label: 'User Flow' },
|
||
{ id: 'architecture', label: 'Architecture' },
|
||
{ id: 'files', label: 'File Structure' }
|
||
].map(tab => (
|
||
<button
|
||
key={tab.id}
|
||
onClick={() => setActiveView(tab.id)}
|
||
className={`flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors ${
|
||
activeView === tab.id
|
||
? 'bg-blue-500 text-white'
|
||
: 'text-gray-600 hover:bg-gray-100'
|
||
}`}
|
||
>
|
||
{tab.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Content */}
|
||
{activeView === 'flow' && <FlowView />}
|
||
{activeView === 'architecture' && <ArchitectureView />}
|
||
{activeView === 'files' && <FilesView />}
|
||
|
||
{/* Legend */}
|
||
<div className="mt-6 flex justify-center gap-6 text-sm">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-4 h-4 rounded bg-blue-300 border-2 border-blue-400"></div>
|
||
<span className="text-gray-600">User action required</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-4 h-4 rounded bg-green-300 border-2 border-green-400"></div>
|
||
<span className="text-gray-600">Automated by WebsiteBox</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default WebsiteBoxDiagram;
|