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

274
setup.sh Executable file
View File

@@ -0,0 +1,274 @@
#!/bin/bash
set -eo pipefail
# WebsiteBox Setup Wizard
# Generates .env file and creates data directories
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
echo ""
echo "═══════════════════════════════════════════════════════════"
echo " WebsiteBox Setup Wizard"
echo "═══════════════════════════════════════════════════════════"
echo ""
# --- Prerequisite Checks ---
check_command() {
if ! command -v "$1" &>/dev/null; then
echo "ERROR: $1 is not installed."
echo "$2"
exit 1
fi
}
check_command docker "Install Docker: https://docs.docker.com/engine/install/"
if ! docker compose version &>/dev/null; then
echo "ERROR: Docker Compose (v2) is not available."
echo "Install it: https://docs.docker.com/compose/install/"
exit 1
fi
# Check if Docker is accessible
if ! docker info &>/dev/null; then
echo "ERROR: Cannot connect to Docker daemon."
echo "Make sure Docker is running and your user has permission."
echo "You may need to: sudo usermod -aG docker \$USER && newgrp docker"
exit 1
fi
# Check ports
for port in 80 443; do
if ss -tlnp 2>/dev/null | grep -q ":${port} " || netstat -tlnp 2>/dev/null | grep -q ":${port} "; then
echo "WARNING: Port ${port} is already in use."
echo "Another service may need to be stopped first."
read -rp "Continue anyway? (y/N) " cont
if [ "$cont" != "y" ] && [ "$cont" != "Y" ]; then
exit 1
fi
fi
done
# --- Generate Random Strings ---
generate_password() {
local length="${1:-32}"
openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c "$length"
}
generate_salt() {
openssl rand -base64 48 | head -c 64
}
# --- Collect Configuration ---
echo "Please provide the following configuration:"
echo ""
# Domain
while true; do
read -rp "Domain name (e.g., example.com): " DOMAIN
if [[ "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$ ]]; then
break
fi
echo "Invalid domain format. Please enter a valid domain (e.g., example.com)"
done
# Site title
read -rp "Site title [My Portfolio]: " SITE_TITLE
SITE_TITLE="${SITE_TITLE:-My Portfolio}"
# Admin username
while true; do
read -rp "WordPress admin username: " ADMIN_USER
if [ -z "$ADMIN_USER" ]; then
echo "Username cannot be empty."
continue
fi
if [ "$ADMIN_USER" = "admin" ]; then
echo "For security, please choose a username other than 'admin'."
continue
fi
break
done
# Admin email
while true; do
read -rp "Admin email (used for SSL & WordPress): " ADMIN_EMAIL
if [[ "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
break
fi
echo "Please enter a valid email address."
done
# Admin password
echo ""
echo "Set your WordPress admin password."
echo "Press Enter to auto-generate a secure 20-character password."
while true; do
read -rsp "Admin password: " ADMIN_PASSWORD
echo ""
if [ -z "$ADMIN_PASSWORD" ]; then
ADMIN_PASSWORD=$(generate_password 20)
PASSWORD_AUTO_GENERATED=true
echo "Password auto-generated."
break
fi
if [ ${#ADMIN_PASSWORD} -lt 12 ]; then
echo "Password must be at least 12 characters."
continue
fi
read -rsp "Confirm password: " ADMIN_PASSWORD_CONFIRM
echo ""
if [ "$ADMIN_PASSWORD" != "$ADMIN_PASSWORD_CONFIRM" ]; then
echo "Passwords do not match. Try again."
continue
fi
PASSWORD_AUTO_GENERATED=false
break
done
# Age Gate
echo ""
read -rp "Enable age verification gate? (Y/n): " AGE_GATE_INPUT
if [ "$AGE_GATE_INPUT" = "n" ] || [ "$AGE_GATE_INPUT" = "N" ]; then
AGE_GATE_ENABLED=false
AGE_GATE_MIN_AGE=18
else
AGE_GATE_ENABLED=true
read -rp "Minimum age (18-21) [18]: " AGE_GATE_MIN_AGE
AGE_GATE_MIN_AGE="${AGE_GATE_MIN_AGE:-18}"
if [ "$AGE_GATE_MIN_AGE" -lt 18 ] 2>/dev/null || [ "$AGE_GATE_MIN_AGE" -gt 21 ] 2>/dev/null; then
echo "Invalid age. Using default: 18"
AGE_GATE_MIN_AGE=18
fi
fi
# SMTP (optional)
echo ""
read -rp "SMTP server (optional, press Enter to skip): " SMTP_HOST
SMTP_PORT=587
SMTP_USER=""
SMTP_PASS=""
SMTP_FROM=""
if [ -n "$SMTP_HOST" ]; then
read -rp "SMTP port [587]: " SMTP_PORT_INPUT
SMTP_PORT="${SMTP_PORT_INPUT:-587}"
read -rp "SMTP username: " SMTP_USER
read -rsp "SMTP password: " SMTP_PASS
echo ""
read -rp "SMTP from address [${ADMIN_EMAIL}]: " SMTP_FROM_INPUT
SMTP_FROM="${SMTP_FROM_INPUT:-$ADMIN_EMAIL}"
fi
# Backup retention
read -rp "Days to keep local backups (1-365) [30]: " BACKUP_RETENTION_DAYS
BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-30}"
# --- Auto-generated Values ---
DB_NAME="websitebox"
DB_USER="websitebox"
DB_PASSWORD=$(generate_password 32)
DB_ROOT_PASSWORD=$(generate_password 32)
AUTH_KEY=$(generate_salt)
SECURE_AUTH_KEY=$(generate_salt)
LOGGED_IN_KEY=$(generate_salt)
NONCE_KEY=$(generate_salt)
AUTH_SALT=$(generate_salt)
SECURE_AUTH_SALT=$(generate_salt)
LOGGED_IN_SALT=$(generate_salt)
NONCE_SALT=$(generate_salt)
# --- Write .env File ---
cat > .env <<ENVEOF
# WebsiteBox Configuration — Generated by setup.sh
# $(date)
DOMAIN='${DOMAIN}'
SITE_TITLE='${SITE_TITLE//\'/\'\\\'\'}'
ADMIN_USER='${ADMIN_USER}'
ADMIN_EMAIL='${ADMIN_EMAIL}'
ADMIN_PASSWORD='${ADMIN_PASSWORD//\'/\'\\\'\'}'
DB_NAME='${DB_NAME}'
DB_USER='${DB_USER}'
DB_PASSWORD='${DB_PASSWORD}'
DB_ROOT_PASSWORD='${DB_ROOT_PASSWORD}'
AGE_GATE_ENABLED=${AGE_GATE_ENABLED}
AGE_GATE_MIN_AGE=${AGE_GATE_MIN_AGE}
SMTP_HOST='${SMTP_HOST}'
SMTP_PORT=${SMTP_PORT}
SMTP_USER='${SMTP_USER}'
SMTP_PASS='${SMTP_PASS//\'/\'\\\'\'}'
SMTP_FROM='${SMTP_FROM}'
BACKUP_RETENTION_DAYS=${BACKUP_RETENTION_DAYS}
AUTH_KEY='${AUTH_KEY}'
SECURE_AUTH_KEY='${SECURE_AUTH_KEY}'
LOGGED_IN_KEY='${LOGGED_IN_KEY}'
NONCE_KEY='${NONCE_KEY}'
AUTH_SALT='${AUTH_SALT}'
SECURE_AUTH_SALT='${SECURE_AUTH_SALT}'
LOGGED_IN_SALT='${LOGGED_IN_SALT}'
NONCE_SALT='${NONCE_SALT}'
ENVEOF
chmod 600 .env
# --- Create Data Directories ---
mkdir -p websitebox-data/{wordpress,database,certs,certbot-webroot,certbot-signal,backups}
# --- Save Auto-generated Password ---
if [ "$PASSWORD_AUTO_GENERATED" = true ]; then
cat > .credentials <<CREDEOF
# WebsiteBox Admin Credentials
# DELETE THIS FILE after recording your password somewhere secure.
#
# Username: ${ADMIN_USER}
# Password: ${ADMIN_PASSWORD}
CREDEOF
chmod 600 .credentials
fi
# --- Get Server IP ---
SERVER_IP=$(curl -s -4 ifconfig.me 2>/dev/null || curl -s -4 icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP")
# --- Print Summary ---
echo ""
echo "═══════════════════════════════════════════════════════════"
echo " WebsiteBox Setup Complete!"
echo "═══════════════════════════════════════════════════════════"
echo ""
echo " Configuration saved to .env"
echo ""
echo " Next steps:"
echo " 1. Point your domain's A record to this server's IP: ${SERVER_IP}"
echo " 2. Wait for DNS propagation (check: dig ${DOMAIN})"
echo " 3. Run: docker compose up -d"
echo " 4. Access your site at: https://${DOMAIN}"
echo " 5. Log in at: https://${DOMAIN}/wp-admin"
echo " Username: ${ADMIN_USER}"
if [ "$PASSWORD_AUTO_GENERATED" = true ]; then
echo " Password: ${ADMIN_PASSWORD}"
echo ""
echo " WARNING: Your auto-generated password has also been saved to: .credentials"
echo " Delete this file after recording your password somewhere secure."
else
echo " Password: ******* (the password you set during setup)"
fi
echo ""
echo "═══════════════════════════════════════════════════════════"