Private
Public Access
1
0

Initial commit: complete WebsiteBox project

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>
This commit is contained in:
constantprojects
2026-02-20 15:24:23 -07:00
commit a440026701
32 changed files with 3397 additions and 0 deletions

400
websitebox-diagram (1).jsx Normal file
View File

@@ -0,0 +1,400 @@
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://&lt;INSTALL_URL_TBD&gt;/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;