Archived
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3d861cb51
|
||
|
|
205bafb94b | ||
|
|
6b3c1e2bd6 | ||
|
|
d18b433d01 | ||
|
|
01e86c78d0 | ||
|
|
0b40e4e799 | ||
|
|
7b1217a2ef |
@@ -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
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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>
|
||||
@@ -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:
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Executable
+24
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ""
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM wordpress:latest
|
||||
# You can add themes/plugins or custom setup here
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user