23 changed files with 647 additions and 850 deletions
-24
View File
@@ -1,24 +0,0 @@
# Environment variables for OCF WordPress Hosting Platform
# Copy this file to .env and customize as needed
# Database Configuration
MYSQL_ROOT_PASSWORD=root_password_change_me
MYSQL_DATABASE=wp_master
MYSQL_USER=wp_user
MYSQL_PASSWORD=wp_password_change_me
# Apache Configuration
APACHE_HTTP_PORT=80
APACHE_HTTPS_PORT=443
# phpMyAdmin Configuration
PMA_PORT=8080
# WordPress Configuration (for user sites)
WORDPRESS_DB_HOST=db:3306
# Development vs Production
ENVIRONMENT=development
# Logging
LOG_LEVEL=info
-26
View File
@@ -1,26 +0,0 @@
# Ignore environment files with secrets
.env
# Docker volumes and user data
volumes/
# Backups
*.tar.gz
*.sql
db_backup_*.sql
backup_data*.tar.gz
# Logs
*.log
# macOS
.DS_Store
# IDE
.vscode/
.idea/
# Temporary files
*.tmp
*.swp
*~
+1 -151
View File
@@ -1,152 +1,2 @@
# Secure OCF-Style Multi-User WordPress Hosting Platform
A Docker-based hosting platform modeled after UC Berkeley's Open Computing Facility (OCF), enabling multiple users to host their own WordPress sites through a shared infrastructure with isolated volumes.
Each user volume contains:
- `site_conf/` - Apache vhost configuration
- `wordpress_content/` - WordPress files (themes, plugins, uploads)
- `database/` - SQL dumps and backups
## Quick Start
### 1. Build and Start Services
```bash
# Build and start all services
docker-compose up -d --build
# Check service status
docker-compose ps
# View logs
docker-compose logs -f apache
```
### 2. Access Your Test Site
Add this entry to `/etc/hosts` (or `C:\Windows\System32\drivers\etc\hosts` on Windows):
```
127.0.0.1 localhost testuser.local
```
Then visit:
- **Test Site**: http://localhost (shows PHP info)
- **phpMyAdmin**: http://localhost:8080 (user: `wp_user`, password: `wp_password`)
### 3. Register a New User
```bash
# Register a new user with their domain
./register_user.sh alice alice.local
# The script will:
# - Create volume structure
# - Download WordPress
# - Generate vhost configuration
# - Provide next steps
```
After running the script, you need to:
1. **Add the volume to `docker-compose.yml`**:
```yaml
volumes:
- ./volumes/data2-volume:/var/www/data2
```
2. **Restart Apache**:
```bash
docker-compose restart apache
```
3. **Add DNS entry to `/etc/hosts`**:
```
127.0.0.1 alice.local
```
4. **Visit the site**: http://alice.local
## 📁 Project Structure
```
Secure-OCF-Web-Hosting/
├── apache/
│ ├── Dockerfile # PHP 8.2 + Apache image
│ ├── conf/
│ │ └── httpd.conf # Main Apache config
│ └── vhost-templates/
│ └── user_vhost.template # Template for user sites
├── wordpress/
│ ├── Dockerfile # (Legacy - now Apache handles PHP)
│ └── wp-config.php # (Legacy)
├── volumes/
│ └── data1-volume/ # User volumes
│ ├── site_conf/
│ │ └── site.conf # User's Apache vhost
│ ├── wordpress_content/ # WordPress installation
│ └── database/ # SQL dumps
├── docker-compose.yml # Service orchestration
├── register_user.sh # User registration script
└── README.md # This file
```
## 🛠️ Management Commands
### Container Management
```bash
docker-compose up -d
docker-compose down
docker-compose restart apache
docker-compose logs -f [service_name]
docker-compose exec apache bash
```
### Database Management
```bash
docker-compose exec db mysql -u wp_user -p
docker-compose exec db mysqldump -u wp_user -p wp_master > backup.sql
docker-compose exec -T db mysql -u wp_user -p wp_master < backup.sql
```
### User Site Management
```bash
ls -la volumes/
docker-compose exec apache apachectl configtest
docker-compose exec apache apachectl graceful
docker-compose exec apache php -v
cat volumes/data1-volume/site_conf/error.log
```
## 📝 Environment Variables
Create a `.env` file for customization:
```env
# Database Configuration
MYSQL_ROOT_PASSWORD=your_secure_root_password
MYSQL_DATABASE=wp_master
MYSQL_USER=wp_user
MYSQL_PASSWORD=your_secure_password
# Apache Configuration
APACHE_HTTP_PORT=80
APACHE_HTTPS_PORT=443
# phpMyAdmin
PMA_PORT=8080
```
Then update `docker-compose.yml` to use these variables.
## 📚 Additional Resources
- [Docker Documentation](https://docs.docker.com/)
- [WordPress Codex](https://codex.wordpress.org/)
- [Apache HTTP Server Documentation](https://httpd.apache.org/docs/)
- [OCF Documentation](https://www.ocf.berkeley.edu/docs/)
# Secure-OCF-Web-Hosting
+95
View File
@@ -0,0 +1,95 @@
# WordPress Docker Setup
This repository provides a Dockerized environment for running WordPress locally using Docker Compose. It's designed for quick development and testing without complex installations.
## Architecture
- **Apache Server**: Single Apache container with `mod_userdir` enabled
- **User Data**: Shared volume containing student org home directories
- **WordPress**: Separate WordPress + MariaDB containers for testing
- **Structure**: Each org has `/home/<orgname>/public_html/` served at `http://localhost:8080/~<orgname>`
## 🚀 Quick Start
1. **Clone the repository:**
```bash
git clone https://git.1337.berkeley.edu/Committee/Secure-OCF-Web-Hosting.git
cd Secure-OCF-Web-Hosting/WordPress-Docker
```
2. **Install Docker Desktop:**
- [macOS](https://docs.docker.com/desktop/setup/install/mac-install/)
- [Windows](https://docs.docker.com/desktop/setup/install/windows-install/)
3. **Build and start the containers:**
```bash
docker-compose up -d
```
4. **Access WordPress:**
Open your browser and navigate to:
```
http://localhost:8081
```
Follow the on-screen instructions to complete the WordPress setup.
### Access sites:
- **Student org sites**:
- http://localhost:8080/~studentorg1
- http://localhost:8080/~studentorg2
- http://localhost:8080/~studentorg3
- **WordPress**: http://localhost:8081
### Add new student org:
```bash
docker exec -it ocf_apache_server bash
mkdir -p /home/neworg/public_html
echo "<h1>New Org</h1>" > /home/neworg/public_html/index.html
chown -R www-data:www-data /home/neworg
```
### View Apache logs:
```bash
docker logs ocf_apache_server
```
## ⚙️ Managing Containers
* **Start containers:**
```bash
docker-compose start
```
* **Stop containers:**
```bash
docker-compose stop
```
* **Stop and remove containers:**
```bash
docker-compose down
```
Add the `-v` flag to also remove volumes:
```bash
docker-compose down -v
```
## 🔐 Accessing phpMyAdmin
If phpMyAdmin is included in your setup:
* Open your browser and navigate to:
```
http://localhost:8081
```
+28
View File
@@ -0,0 +1,28 @@
# Dockerfile (apache-static)
FROM httpd:2.4
LABEL maintainer="random@gmail.com"
# Enable required Apache modules for user directories and PHP
RUN sed -i \
-e 's/#LoadModule userdir_module/LoadModule userdir_module/' \
-e 's/#LoadModule vhost_alias_module/LoadModule vhost_alias_module/' \
-e 's/#LoadModule rewrite_module/LoadModule rewrite_module/' \
-e 's/#Include conf\/extra\/httpd-userdir.conf/Include conf\/extra\/httpd-userdir.conf/' \
/usr/local/apache2/conf/httpd.conf
# Add custom configuration for user directories
COPY httpd-userdir.conf /usr/local/apache2/conf/extra/httpd-userdir.conf
# Create example student org directories with basic content
RUN mkdir -p /home/studentorg1/public_html && \
echo "<h1>Welcome to Student Org 1's OCF Page</h1><p>This is a test page.</p>" > /home/studentorg1/public_html/index.html && \
mkdir -p /home/studentorg2/public_html && \
echo "<h1>Welcome to Student Org 2's OCF Page</h1><p>This is a test page.</p>" > /home/studentorg2/public_html/index.html && \
mkdir -p /home/studentorg3/public_html && \
echo "<h1>Welcome to Student Org 3's OCF Page</h1><p>This is a test page.</p>" > /home/studentorg3/public_html/index.html
# Set proper ownership
RUN chown -R www-data:www-data /home
EXPOSE 80
@@ -0,0 +1,20 @@
# Settings for user home directories
#
# Required module: mod_userdir
UserDir /home/*/public_html
#
# Control access to UserDir directories.
# Mimics OCF structure where user directories are in /home
#
<Directory "/home/*/public_html">
AllowOverride All
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
Require all granted
# Allow .htaccess usage for student customization
<IfModule mod_rewrite.c>
RewriteEngine On
</IfModule>
</Directory>
+54
View File
@@ -0,0 +1,54 @@
version: '3.8'
services:
apache:
build:
context: ./apache
ports:
- "8080:80"
volumes:
- userdata:/home
depends_on:
- userdata
container_name: apache-static
restart: unless-stopped
userdata:
image: busybox
volumes:
- userdata:/home
container_name: user_data
command: /bin/true
db:
image: mariadb:11
container_name: wp_db
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: wp_db
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp_pass
volumes:
- db_data:/var/lib/mysql
restart: unless-stopped
wordpress:
image: wordpress:latest
container_name: wp_site
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp_pass
WORDPRESS_DB_NAME: wp_db
ports:
- "8081:80" # WordPress site -> http://localhost:8081
volumes:
- wp_data:/var/www/html
restart: unless-stopped
volumes:
userdata:
db_data:
wp_data:
+19
View File
@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Local Apache Static Landing</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; background:#0f172a; color:#e6eef8; text-align:center; padding-top:8vh; }
h1 { color:#10b981; }
p { color:#cbd5e1; }
a { color:#93c5fd; text-decoration:none; }
</style>
</head>
<body>
<h1>Apache Static Host</h1>
<p>This is the static landing page container.</p>
<p><a href="http://localhost:8081/wp-admin" target="_blank">Open WordPress setup (wp-admin)</a></p>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
# WordPress Metrics
## scrape_wordpress_plugins.py
- Uses requests to scrape the whole plugins list using https://api.wordpress.org/plugins/
- Scrape in pages of 100 plugins, takes about ~30 mins to complete
- Stores as output.json
## analyze_data.py
- Calculate basic statistics (percentiles, mean, median, sd ...)
- Visualize them with matplot in a logrithrimic scale
## query_plugin.py
- Change slug to equal the target plugins (should be able to directly plug the outputs from detect_plugins.sh)
- TODO: Implement warning system
## security_rating.py
- Implements some sort of basic rating system factoring in number of active installations, last-update date, and ratings
- sample use case: python3 security_rating.py {plugin name}
+74
View File
@@ -0,0 +1,74 @@
import json
import matplotlib.pyplot as plt
import numpy as np
def print_info(name, data):
# Convert the list to a NumPy array
data_np = np.array(data)
# Calculate basic statistics
mean = np.mean(data_np)
median = np.median(data_np)
std_dev = np.std(data_np)
variance = np.var(data_np)
min_val = np.min(data_np)
max_val = np.max(data_np)
sum_val = np.sum(data_np)
# Calculate percentiles
percentile_25 = np.percentile(data_np, 25)
percentile_50 = np.percentile(data_np, 50) # This is the same as the median
percentile_75 = np.percentile(data_np, 75)
percentile_90 = np.percentile(data_np, 90)
# Print the results
print(f"\n\nDescriptive Statistics for {name}:")
print(f"Mean: {mean}")
print(f"Median: {median}")
print(f"Standard Deviation: {std_dev}")
print(f"Variance: {variance}")
print(f"Minimum: {min_val}")
print(f"Maximum: {max_val}")
print(f"Sum: {sum_val}")
print("\nPercentiles:")
print(f"25th Percentile: {percentile_25}")
print(f"50th Percentile (Median): {percentile_50}")
print(f"75th Percentile: {percentile_75}")
print(f"90th Percentile: {percentile_90}")
with open('output.json', 'r') as f:
json_data = json.load(f)
# print(len(data))
downloaded = [plugin["downloaded"] for plugin in json_data]
rating = [plugin["rating"] for plugin in json_data]
active_installs = [plugin["active_installs"] for plugin in json_data]
num_ratings = [plugin["num_ratings"] for plugin in json_data]
print_info("downloaded", downloaded)
print_info("rating", rating)
print_info("active_installs", active_installs)
print_info("num_ratings", num_ratings)
# plt.hist(downloaded, bins=500, log=True)
# plt.show()
fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(8, 6))
axs[0,0].hist(downloaded, bins=100, log=True)
axs[0,0].set_title("Distribution of downloaded")
axs[0,1].hist(rating, bins=20, log=True)
axs[0,1].set_title("Distribution of rating")
axs[1,0].hist(active_installs, bins=10, log=True)
axs[1,0].set_title("Distribution of active_installs")
axs[1,1].hist(num_ratings, bins=100, log=True)
axs[1,1].set_title("Distribution of num_ratings")
# Add labels and title
# plt.xlabel("Value")
# plt.ylabel("Frequency (log)")
# Display the plot
plt.show()
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""Analyze WordPress plugins for security vulnerabilities using security ratings."""
import argparse
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
# Import security_rating functions
from security_rating import get_plugin_info, calculate_security_score
def parse_args() -> argparse.Namespace:
"""Parse command-line arguments.
Returns:
Parsed arguments namespace.
"""
parser = argparse.ArgumentParser(
description="Analyze WordPress plugins for security issues using security ratings."
)
parser.add_argument(
"--input",
"-i",
required=True,
help="Path to input JSON file (from output_recursive_plugins)"
)
parser.add_argument(
"--output",
"-o",
help="Path to output JSON file (if not specified, prints to stdout)",
default=None
)
parser.add_argument(
"--detector-name",
"-d",
help="Name of detector to use in output (default: Security Rating Analyzer)",
default="Security Rating Analyzer"
)
return parser.parse_args()
def analyze_plugin(slug: str) -> dict:
"""Analyze a single plugin and return its status.
Args:
slug: Plugin slug to analyze.
Returns:
Dict with status and reason fields.
"""
plugin_info = get_plugin_info(slug)
if not plugin_info:
return {
"status": "unknown",
"reason": f"Could not fetch plugin information from WordPress.org API"
}
score = calculate_security_score(plugin_info)
# Determine status based on score
# Safe: score >= 65 (Excellent or Good)
# Vulnerable: score < 65 (Moderate or Poor)
if score >= 85:
status = "safe"
reason = f"Excellent security rating ({score}/100) — Actively maintained and trusted"
elif score >= 65:
status = "safe"
reason = f"Good security rating ({score}/100) — Generally safe but monitor updates"
elif score >= 45:
status = "vulnerable"
reason = f"Moderate security rating ({score}/100) — May be outdated or less popular"
else:
status = "vulnerable"
reason = f"Poor security rating ({score}/100) — High risk of security issues"
result = {
"status": status,
"reason": reason,
"score": score
}
# Add version if available
version = plugin_info.get("version")
if version:
result["version"] = version
return result
def analyze_directory(directory_name: str, plugins_dict: dict, detector_name: str) -> dict:
"""Analyze all plugins in a directory.
Args:
directory_name: Name of the directory being analyzed.
plugins_dict: Dict mapping plugin slug to absolute path.
detector_name: Name of the detector.
Returns:
Analysis result in the specified format.
"""
timestamp = datetime.now(timezone.utc).isoformat()
analyzed_plugins = {}
print(f"\nAnalyzing plugins for directory: {directory_name}")
print(f"Found {len(plugins_dict)} plugins to analyze")
for slug, path in plugins_dict.items():
print(f" Analyzing {slug}...", end=" ", flush=True)
result = analyze_plugin(slug)
analyzed_plugins[slug] = result
print(f"{result['status']}")
return {
"directory": directory_name,
"wordpress": True,
"name_of_detector": detector_name,
"timestamp": timestamp,
"plugins": analyzed_plugins
}
def main():
"""Main entry point for the plugin analyzer."""
args = parse_args()
# Load input JSON
try:
with open(args.input, "r", encoding="utf-8") as f:
input_data = json.load(f)
except FileNotFoundError:
print(f"Error: Input file '{args.input}' not found.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.input}': {e}", file=sys.stderr)
sys.exit(1)
# Analyze each directory
results = []
for directory_name, plugins_dict in input_data.items():
if not plugins_dict:
print(f"Skipping {directory_name} (no plugins found)")
continue
analysis = analyze_directory(directory_name, plugins_dict, args.detector_name)
results.append(analysis)
# Output results
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"\nWrote analysis results to {args.output}")
else:
print("\nAnalysis results:")
print(json.dumps(results, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
+14
View File
@@ -0,0 +1,14 @@
import requests
slug = "akismet"
response = requests.get("https://api.wordpress.org/plugins/info/1.2/?action=query_plugins"
f"&slug={slug}")
response_json = response.json()
response_json = response_json['plugins'][0]
# print(response_json)
print(response_json['downloaded'])
print(response_json['rating'])
print(response_json['active_installs'])
print(response_json['num_ratings'])
# TODO: Implement warning system
@@ -0,0 +1,37 @@
from time import sleep
import requests
import json
from tqdm import tqdm
starting_page = 1
request_per_page = 100
response = requests.get(f"https://api.wordpress.org/plugins/info/1.2/?action=query_plugins"
f"&request[page]={starting_page}"
f"&request[per_page]={request_per_page}")
response_json = response.json()
# print(response_json)
page_no = response_json['info']['pages']
plugins_info_list = []
plugins_info_str = ""
for i in tqdm(range(page_no)):
response = requests.get(f"https://api.wordpress.org/plugins/info/1.2/?action=query_plugins"
f"&request[page]={i+1}"
f"&request[per_page]={request_per_page}")
response_json = response.json()
plugins_info_list.append(response_json['plugins'])
# plugins_info_str += str(response_json['plugins'])
sleep(0.1)
flattened_list = [item for sublist in plugins_info_list for item in sublist]
plugins_info_json = json.dumps(flattened_list, indent=4)
print(plugins_info_json)
filename = "output.json"
# Open the file in write mode ('w') and use json.dump() to write the data
with open(filename, 'w') as f:
f.write(plugins_info_json)
+101
View File
@@ -0,0 +1,101 @@
import requests
import datetime
import sys
from dateutil import parser
import datetime
def get_plugin_info(slug):
"""Fetch plugin info from the WordPress API."""
url = f"https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]={slug}"
response = requests.get(url)
if response.status_code != 200:
print(f"Error: Unable to fetch data for plugin '{slug}'.")
return None
return response.json()
def calculate_security_score(plugin):
"""Calculate a security score (0-100) for a WordPress plugin."""
score = 0
# last updated
last_updated_str = plugin.get("last_updated")
if last_updated_str:
try:
last_updated = parser.parse(last_updated_str)
now = datetime.datetime.now(datetime.timezone.utc)
if last_updated.tzinfo is None:
last_updated = last_updated.replace(tzinfo=datetime.timezone.utc)
days_since_update = (now - last_updated).days
if days_since_update <= 90:
score += 40
elif days_since_update <= 180:
score += 30
elif days_since_update <= 365:
score += 20
else:
score += 10
except Exception as e:
print(f"Warning: Could not parse date '{last_updated_str}': {e}")
score += 0
else:
score += 0
# user rating
rating = plugin.get("rating")
if isinstance(rating, (int, float)):
score += (rating / 100) * 30
else:
score += 0
# active installs
installs = plugin.get("active_installs")
if isinstance(installs, int):
if installs >= 1_000_000:
score += 20
elif installs >= 100_000:
score += 15
elif installs >= 10_000:
score += 10
elif installs > 0:
score += 5
else:
score += 0
return round(score, 1)
def rate_plugin(slug):
plugin = get_plugin_info(slug)
if not plugin:
return
score = calculate_security_score(plugin)
name = plugin.get("name", slug)
last_updated = plugin.get("last_updated", "Unknown")
rating = plugin.get("rating", "Unknown")
installs = plugin.get("active_installs", "Unknown")
print(f"\n🔍 Security Rating for '{name}'")
print("=" * (25 + len(name)))
print(f"Last Updated: {last_updated}")
print(f"User Rating: {rating}/100")
print(f"Active Installs: {installs}")
print(f"→ Security Score: {score}/100")
if score >= 85:
print("Excellent — Actively maintained and trusted")
elif score >= 65:
print("Good — Generally safe but monitor updates")
elif score >= 45:
print("Moderate — May be outdated or less popular")
else:
print("Poor — High risk of security issues")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python plugin_security_rating.py <plugin-slug>")
sys.exit(1)
slug = sys.argv[1]
rate_plugin(slug)
-30
View File
@@ -1,30 +0,0 @@
FROM php:8.2-apache
# Install required PHP extensions for WordPress
RUN apt-get update && apt-get install -y \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libzip-dev \
libicu-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd \
mysqli \
pdo_mysql \
zip \
intl \
opcache \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Enable Apache modules
RUN a2enmod rewrite headers expires
# Copy custom Apache configuration
COPY conf/httpd.conf /etc/apache2/sites-available/000-default.conf
# Set working directory
WORKDIR /var/www
EXPOSE 80
-22
View File
@@ -1,22 +0,0 @@
# Main Apache configuration for multi-user WordPress hosting
<VirtualHost *:80>
ServerAdmin admin@localhost
DocumentRoot /var/www
<Directory /var/www>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
# Include user site configs from each user volume
IncludeOptional /var/www/*/site_conf/site.conf
@@ -1,13 +0,0 @@
<VirtualHost *:80>
ServerName {USER_DOMAIN}
ServerAlias www.{USER_DOMAIN}
DocumentRoot /var/www/{USER_VOLUME}/wordpress_content
<Directory "/var/www/{USER_VOLUME}/wordpress_content">
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/www/{USER_VOLUME}/site_conf/error.log
CustomLog /var/www/{USER_VOLUME}/site_conf/access.log combined
</VirtualHost>
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
# Usage:
# ./export-wp-plugins.sh [base_directory] [output_file]
BASE_DIR="${1:-/var/www}"
OUTPUT_FILE="${2:-plugins.csv}"
# Check for WP-CLI
if ! command -v wp &>/dev/null; then
echo "WP-CLI not found. Install WP-CLI first: https://wp-cli.org/"
exit 1
fi
# Loop through subdirectories
for dir in "$BASE_DIR"/*/; do
if [ -f "$dir/wp-config.php" ]; then
site=$(basename "$dir")
echo "=== $site ===" | tee -a "$OUTPUT_FILE"
wp --allow-root --path="$dir" plugin list --format=csv>> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
fi
done
echo "Plugins saved to $OUTPUT_FILE"
-58
View File
@@ -1,58 +0,0 @@
version: "3.8"
services:
apache:
build:
context: ./apache
dockerfile: Dockerfile
ports:
- "80:80"
- "443:443"
volumes:
- wordpress_master:/var/www/master
# - ./volumes/data1-volume:/var/www/data1
# - ./volumes/data2-volume:/var/www/data2
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp_password
WORDPRESS_DB_NAME: wp_master
depends_on:
- db
restart: unless-stopped
networks:
- wordpress_network
db:
image: mariadb:latest
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: wp_master
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp_password
restart: unless-stopped
networks:
- wordpress_network
phpmyadmin:
image: phpmyadmin:latest
ports:
- "8080:80"
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: root_password
depends_on:
- db
restart: unless-stopped
networks:
- wordpress_network
volumes:
db_data:
wordpress_master:
networks:
wordpress_network:
driver: bridge
-371
View File
@@ -1,371 +0,0 @@
#!/usr/bin/env bash
# Helper script for managing the OCF-style WordPress hosting platform
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
print_header() {
echo -e "${BLUE}=========================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}=========================================${NC}"
}
print_success() {
echo -e "${GREEN}✓ $1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠ $1${NC}"
}
print_error() {
echo -e "${RED}✗ $1${NC}"
}
# Help function
show_help() {
cat << EOF
OCF WordPress Hosting Manager
Usage: ./manage.sh [command]
Commands:
start Build and start all services
stop Stop all services
restart Restart all services
status Show status of all services
logs [service] Show logs (optionally for specific service)
user-add Register a new user
user-list List all registered users
user-remove Remove a user (with backup)
db-backup Backup the database
db-restore Restore database from backup
db-shell Open database shell
test-php Test PHP functionality
test-db Test database connectivity
clean Stop and remove all containers, volumes, and networks
clean-all Clean everything including user data (DANGEROUS!)
help Show this help message
Examples:
./manage.sh start
./manage.sh user-add
./manage.sh logs apache
./manage.sh db-backup
EOF
}
# Start services
cmd_start() {
print_header "Starting Services"
docker-compose up -d --build
print_success "Services started"
echo ""
cmd_status
}
# Stop services
cmd_stop() {
print_header "Stopping Services"
docker-compose down
print_success "Services stopped"
}
# Restart services
cmd_restart() {
print_header "Restarting Services"
docker-compose restart
print_success "Services restarted"
}
# Show status
cmd_status() {
print_header "Service Status"
docker-compose ps
echo ""
echo "Access URLs:"
echo " • Test Site: http://localhost"
echo " • phpMyAdmin: http://localhost:8080"
}
# Show logs
cmd_logs() {
local service=${1:-""}
if [ -n "$service" ]; then
docker-compose logs -f "$service"
else
docker-compose logs -f
fi
}
# Add new user
cmd_user_add() {
print_header "Register New User"
read -p "Enter username: " username
read -p "Enter domain (e.g., alice.local): " domain
if [ -z "$username" ] || [ -z "$domain" ]; then
print_error "Username and domain are required"
exit 1
fi
./register_user.sh "$username" "$domain"
echo ""
print_warning "Don't forget to:"
echo " 1. Add volume to docker-compose.yml"
echo " 2. Run: ./manage.sh restart"
echo " 3. Add '$domain' to /etc/hosts pointing to 127.0.0.1"
}
# List users
cmd_user_list() {
print_header "Registered Users"
if [ ! -d "./volumes" ] || [ -z "$(ls -A ./volumes 2>/dev/null)" ]; then
print_warning "No users registered yet"
return
fi
echo -e "${BLUE}Volume\t\t\tDomain${NC}"
echo "----------------------------------------"
for vol_dir in ./volumes/*/; do
if [ -d "$vol_dir" ]; then
vol_name=$(basename "$vol_dir")
conf_file="${vol_dir}site_conf/site.conf"
if [ -f "$conf_file" ]; then
domain=$(grep "ServerName" "$conf_file" | awk '{print $2}')
echo -e "${vol_name}\t\t${domain}"
fi
fi
done
}
# Remove user
cmd_user_remove() {
print_header "Remove User"
cmd_user_list
echo ""
read -p "Enter volume name to remove (e.g., data1-volume): " vol_name
if [ -z "$vol_name" ]; then
print_error "Volume name required"
exit 1
fi
vol_path="./volumes/${vol_name}"
if [ ! -d "$vol_path" ]; then
print_error "Volume not found: $vol_path"
exit 1
fi
print_warning "This will backup and remove: $vol_path"
read -p "Are you sure? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
print_warning "Cancelled"
exit 0
fi
# Create backup
backup_name="backup_${vol_name}_$(date +%Y%m%d_%H%M%S).tar.gz"
tar -czf "$backup_name" "$vol_path"
print_success "Backup created: $backup_name"
# Remove volume
rm -rf "$vol_path"
print_success "Volume removed: $vol_path"
print_warning "Remember to remove the volume mount from docker-compose.yml"
}
# Database backup
cmd_db_backup() {
print_header "Database Backup"
backup_file="db_backup_$(date +%Y%m%d_%H%M%S).sql"
docker-compose exec -T db mysqldump -u wp_user -pwp_password wp_master > "$backup_file"
print_success "Database backed up to: $backup_file"
}
# Database restore
cmd_db_restore() {
print_header "Database Restore"
# List available backups
echo "Available backups:"
ls -1 db_backup_*.sql 2>/dev/null || print_warning "No backups found"
echo ""
read -p "Enter backup file name: " backup_file
if [ ! -f "$backup_file" ]; then
print_error "Backup file not found: $backup_file"
exit 1
fi
print_warning "This will restore database from: $backup_file"
read -p "Are you sure? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
print_warning "Cancelled"
exit 0
fi
docker-compose exec -T db mysql -u wp_user -pwp_password wp_master < "$backup_file"
print_success "Database restored from: $backup_file"
}
# Database shell
cmd_db_shell() {
print_header "Database Shell"
echo "Connecting to MariaDB..."
docker-compose exec db mysql -u wp_user -pwp_password wp_master
}
# Test PHP
cmd_test_php() {
print_header "Testing PHP"
if ! docker-compose ps | grep -q apache.*Up; then
print_error "Apache container is not running"
exit 1
fi
echo "Testing PHP version..."
docker-compose exec apache php -v
echo ""
echo "Testing PHP extensions..."
docker-compose exec apache php -m | grep -E "(mysqli|gd|zip)"
echo ""
print_success "PHP is working correctly"
}
# Test database
cmd_test_db() {
print_header "Testing Database Connection"
if ! docker-compose ps | grep -q db.*Up; then
print_error "Database container is not running"
exit 1
fi
result=$(docker-compose exec -T apache php -r "echo mysqli_connect('db', 'wp_user', 'wp_password', 'wp_master') ? 'success' : 'failed';")
if [ "$result" = "success" ]; then
print_success "Database connection successful"
else
print_error "Database connection failed"
exit 1
fi
}
# Clean containers
cmd_clean() {
print_header "Cleaning Containers"
print_warning "This will stop and remove containers, networks, and anonymous volumes"
read -p "Are you sure? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
print_warning "Cancelled"
exit 0
fi
docker-compose down -v
print_success "Cleaned up containers and volumes"
}
# Clean everything
cmd_clean_all() {
print_header "DANGER: Clean Everything"
print_error "This will remove ALL containers, volumes, AND user data"
print_error "This action CANNOT be undone!"
read -p "Type 'DELETE EVERYTHING' to confirm: " confirm
if [ "$confirm" != "DELETE EVERYTHING" ]; then
print_warning "Cancelled"
exit 0
fi
docker-compose down -v
rm -rf ./volumes/*
print_success "Everything cleaned"
}
# Main command router
case "${1:-help}" in
start)
cmd_start
;;
stop)
cmd_stop
;;
restart)
cmd_restart
;;
status)
cmd_status
;;
logs)
cmd_logs "$2"
;;
user-add)
cmd_user_add
;;
user-list)
cmd_user_list
;;
user-remove)
cmd_user_remove
;;
db-backup)
cmd_db_backup
;;
db-restore)
cmd_db_restore
;;
db-shell)
cmd_db_shell
;;
test-php)
cmd_test_php
;;
test-db)
cmd_test_db
;;
clean)
cmd_clean
;;
clean-all)
cmd_clean_all
;;
help|--help|-h)
show_help
;;
*)
print_error "Unknown command: $1"
echo ""
show_help
exit 1
;;
esac
-134
View File
@@ -1,134 +0,0 @@
#!/usr/bin/env bash
# Usage: ./register_user.sh USER_NAME USER_DOMAIN
# Example: ./register_user.sh alice alice.example.com
set -e
USER_NAME="$1"
USER_DOMAIN="$2"
if [ -z "$USER_NAME" ] || [ -z "$USER_DOMAIN" ]; then
echo "Usage: $0 USER_NAME USER_DOMAIN"
echo "Example: $0 alice alice.example.com"
exit 1
fi
# Get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Create volumes directory if it doesn't exist
mkdir -p ./volumes
# Decide next data folder number
if [ -d "./volumes" ] && [ "$(ls -A ./volumes)" ]; then
NEXT_INDEX=$(ls -1d volumes/data* 2>/dev/null | wc -l)
NEXT_INDEX=$((NEXT_INDEX + 1))
else
NEXT_INDEX=1
fi
USER_VOLUME="data${NEXT_INDEX}"
VOLUME_DIR="./volumes/${USER_VOLUME}-volume"
echo "========================================="
echo "Creating new user site:"
echo " User: ${USER_NAME}"
echo " Domain: ${USER_DOMAIN}"
echo " Volume: ${USER_VOLUME}"
echo "========================================="
# Create volume directory structure
mkdir -p "${VOLUME_DIR}/site_conf"
mkdir -p "${VOLUME_DIR}/wordpress_content"
mkdir -p "${VOLUME_DIR}/database"
# Copy vhost template and replace placeholders
TEMPLATE_FILE="./apache/vhost-templates/user_vhost.template"
TARGET_CONF="${VOLUME_DIR}/site_conf/site.conf"
if [ ! -f "$TEMPLATE_FILE" ]; then
echo "ERROR: Template file not found: $TEMPLATE_FILE"
exit 1
fi
sed \
-e "s/{USER_DOMAIN}/${USER_DOMAIN}/g" \
-e "s/{USER_VOLUME}/${USER_VOLUME}/g" \
"${TEMPLATE_FILE}" > "${TARGET_CONF}"
echo "✓ Created site configuration at ${TARGET_CONF}"
# Download WordPress into the user's content directory
echo "Downloading WordPress..."
cd "${VOLUME_DIR}/wordpress_content"
curl -O https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz --strip-components=1
rm latest.tar.gz
# Create wp-config.php with database connection
cat > wp-config.php << 'EOF'
<?php
define('DB_NAME', getenv('WORDPRESS_DB_NAME') ?: 'wp_master');
define('DB_USER', getenv('WORDPRESS_DB_USER') ?: 'wp_user');
define('DB_PASSWORD', getenv('WORDPRESS_DB_PASSWORD') ?: 'wp_password');
define('DB_HOST', getenv('WORDPRESS_DB_HOST') ?: 'db:3306');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');
// Unique keys and salts (generate from https://api.wordpress.org/secret-key/1.1/salt/)
define('AUTH_KEY', 'put your unique phrase here');
define('SECURE_AUTH_KEY', 'put your unique phrase here');
define('LOGGED_IN_KEY', 'put your unique phrase here');
define('NONCE_KEY', 'put your unique phrase here');
define('AUTH_SALT', 'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT', 'put your unique phrase here');
define('NONCE_SALT', 'put your unique phrase here');
$table_prefix = 'wp_';
define('WP_DEBUG', false);
if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/');
require_once(ABSPATH . 'wp-settings.php');
EOF
cd "$SCRIPT_DIR"
echo "✓ WordPress installed to ${VOLUME_DIR}/wordpress_content"
# Create initial database dump placeholder
cat > "${VOLUME_DIR}/database/README.md" << EOF
# Database Directory for ${USER_NAME}
This directory can contain:
- SQL dump files for backup/restore
- Database migration scripts
- User-specific database notes
The WordPress installation uses the shared database defined in docker-compose.yml.
Each user site should use a unique table prefix in wp-config.php.
EOF
echo "✓ Created database directory with README"
echo ""
echo "========================================="
echo "✓ User ${USER_NAME} registered successfully!"
echo "========================================="
echo ""
echo "Next steps:"
echo "1. Add this volume mount to docker-compose.yml:"
echo " - ./volumes/${USER_VOLUME}-volume:/var/www/${USER_VOLUME}"
echo ""
echo "2. Restart the Apache container:"
echo " docker-compose restart apache"
echo ""
echo "3. Add DNS/hosts entry for ${USER_DOMAIN} pointing to your server"
echo ""
echo "4. Visit http://${USER_DOMAIN} to complete WordPress setup"
echo ""
-3
View File
@@ -1,3 +0,0 @@
FROM wordpress:latest
# You can add themes/plugins or custom setup here
-18
View File
@@ -1,18 +0,0 @@
<?php
// Basic WordPress config example
define('DB_NAME', getenv('WORDPRESS_DB_NAME'));
define('DB_USER', getenv('WORDPRESS_DB_USER'));
define('DB_PASSWORD', getenv('WORDPRESS_DB_PASSWORD'));
define('DB_HOST', getenv('WORDPRESS_DB_HOST'));
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
$table_prefix = 'wp_';
define('WP_DEBUG', false);
if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/');
require_once(ABSPATH . 'wp-settings.php');