initial commit

This commit is contained in:
2024-12-30 19:16:20 +09:00
commit d22b377f73
15 changed files with 619 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
caddy/*
wordpress/*
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Thuan Bui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+56
View File
@@ -0,0 +1,56 @@
# **CaddyWP**
**CaddyWP** is an open-source tool designed to simplify the deployment and management of multiple WordPress sites behind a Caddy reverse proxy. It uses Docker and Bash scripts to automate configuration, allowing you to easily set up and scale your WordPress instances with minimal hassle.
---
## **Features**
- **Multiple WordPress Sites**: Manage as many WordPress sites as you want, all running on a single reverse proxy server.
- **Caddy Integration**: Seamlessly integrates Caddy as the reverse proxy for efficient traffic routing and SSL management.
- **Dockerized**: Fully containerized environment for easy setup and management.
- **Bash Automation**: Automate your WordPress site configurations using simple Bash scripts.
- **Scalable**: Easily add new sites and scale your infrastructure as needed.
---
## **Getting Started**
### **Prerequisites**
Ensure you have the following installed:
- [Docker](https://www.docker.com/)
- [Docker Compose](https://docs.docker.com/compose/)
- A Unix-based environment (Linux or macOS) or WSL on Windows
### **Installation**
1. **Clone the repository**:
```bash
git clone https://github.com/your-username/CaddyWP.git
cd CaddyWP
```
2. **Run the install script**:
```bash
./install.sh
```
## How It Works
Caddy: Serves as a reverse proxy, automatically obtaining SSL certificates and routing traffic to the correct WordPress container.
WordPress: Each WordPress site is hosted in its own Docker container, ensuring that each site runs in isolation with its own environment and database.
MariaDB: A single MariaDB container serves as the database for all WordPress sites. Each site uses a unique database user and password.
## License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgements
Caddy: https://caddyserver.com/
WordPress: https://wordpress.org/
Docker: https://www.docker.com/
MariaDB: https://mariadb.org/
<p align="right">(<a href="#top">back to top</a>)</p>
Executable
+117
View File
@@ -0,0 +1,117 @@
#!/bin/bash
# Get the directory where the script is located
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Source library files in specific order
source "${SCRIPT_DIR}/lib/colors.sh"
source "${SCRIPT_DIR}/lib/config.sh"
source "${SCRIPT_DIR}/lib/utils.sh"
source "${SCRIPT_DIR}/lib/validation.sh"
source "${SCRIPT_DIR}/lib/docker.sh"
source "${SCRIPT_DIR}/lib/caddy.sh"
source "${SCRIPT_DIR}/lib/wordpress.sh"
echo "WordPress Site Setup Script"
echo "=========================="
setup_directories
FIRST_TIME=false
if ! check_container_running "caddy"; then
FIRST_TIME=true
fi
while true; do
read -p "Enter domain (e.g., example.com): " DOMAIN
if validate_domain "$DOMAIN"; then
break
fi
done
while true; do
read -p "Enter admin email: " ADMIN_EMAIL
if validate_email "$ADMIN_EMAIL"; then
break
fi
done
read -p "Enter admin username: " ADMIN_USER
read -s -p "Enter password (press Enter for random password): " ADMIN_PASSWORD
echo
if [ -z "$ADMIN_PASSWORD" ]; then
ADMIN_PASSWORD=$(generate_password)
echo "Generated password: $ADMIN_PASSWORD"
fi
read -p "Enter site title: " SITE_TITLE
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32)
MYSQL_PASSWORD=$(openssl rand -base64 32)
WP_PROJECT_DIR="${WORDPRESS_DIR}/${DOMAIN}"
if [ -d "$WP_PROJECT_DIR" ]; then
echo -e "${RED}Directory ${WP_PROJECT_DIR} already exists!${NC}"
exit 1
fi
mkdir -p "$WP_PROJECT_DIR"
cd "$WP_PROJECT_DIR"
create_docker_compose "$DOMAIN" "$MYSQL_ROOT_PASSWORD" "$MYSQL_PASSWORD"
create_caddy_config "$DOMAIN"
create_wp_setup "$DOMAIN" "$ADMIN_USER" "$ADMIN_PASSWORD" "$ADMIN_EMAIL" "$SITE_TITLE"
create_env_file "$DOMAIN" "$ADMIN_USER" "$ADMIN_PASSWORD" "$ADMIN_EMAIL" "$MYSQL_ROOT_PASSWORD" "$MYSQL_PASSWORD"
while true; do
read -p "Do you want to (1) start services and set up WordPress automatically or (2) do it manually later? [1/2]: " SETUP_CHOICE
case $SETUP_CHOICE in
1)
if start_services "$FIRST_TIME" "$DOMAIN"; then
if run_wp_setup "$DOMAIN"; then
echo -e "${GREEN}Complete setup finished successfully!${NC}"
else
echo -e "${RED}WordPress setup failed. You may need to run setup manually later.${NC}"
fi
else
echo -e "${RED}Service startup failed. You may need to start services manually.${NC}"
fi
break
;;
2)
echo -e "\n${BLUE}Manual setup instructions:${NC}"
if [ "$FIRST_TIME" = true ]; then
echo "1. Start Caddy:"
echo " cd ${CADDY_DIR} && docker compose up -d"
fi
echo "2. Start WordPress:"
echo " cd ${WP_PROJECT_DIR} && docker compose up -d"
echo "3. Run the WordPress setup script:"
echo " ./wp-setup.sh"
break
;;
*)
echo -e "${RED}Invalid choice. Please enter 1 or 2.${NC}"
;;
esac
done
echo -e "\n${BLUE}WordPress Site Information:${NC}"
echo "----------------------------------------"
echo "Domain: https://$DOMAIN"
echo "Admin URL: https://$DOMAIN/wp-admin"
echo "Username: $ADMIN_USER"
if [ "$PASSWORD_CHOICE" = "2" ]; then
echo "Password: $ADMIN_PASSWORD (SAVE THIS PASSWORD!)"
fi
echo "Email: $ADMIN_EMAIL"
echo "----------------------------------------"
save_credentials "$WP_PROJECT_DIR" "$DOMAIN" "$ADMIN_USER" "$ADMIN_PASSWORD" "$ADMIN_EMAIL"
echo -e "\nCredentials have been saved to: ${WP_PROJECT_DIR}/credentials.txt"
exit 0
+76
View File
@@ -0,0 +1,76 @@
#!/bin/bash
reload_caddy() {
echo "Reloading Caddy configuration..."
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
}
create_caddy_config() {
export DOMAIN=$1
export CONFIG_FILE="${CADDY_DIR}/sites/${DOMAIN}.caddy"
mkdir -p "${CADDY_DIR}/sites"
envsubst < "${SCRIPT_DIR}/templates/caddy.template" > "$CONFIG_FILE"
if ! grep -q "import sites/\*.caddy" "${CADDY_DIR}/Caddyfile"; then
echo 'import sites/*.caddy' >> "${CADDY_DIR}/Caddyfile"
fi
}
create_caddy_docker_compose() {
mkdir -p "${CADDY_DIR}"
# Create necessary directories
mkdir -p "${CADDY_DIR}/sites"
mkdir -p "${CADDY_DIR}/caddy_data"
mkdir -p "${CADDY_DIR}/caddy_config"
# Create initial Caddyfile
cat > "${CADDY_DIR}/Caddyfile" <<EOL
{
# Global options
admin off
persist_config off
}
# Site configurations will be imported below
import sites/*.caddy
EOL
# Create docker-compose.yaml
cat > "${CADDY_DIR}/compose.yaml" <<EOL
services:
caddy:
container_name: caddy
image: caddy:latest
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./sites:/etc/caddy/sites
- ./caddy_data:/data
- ./caddy_config:/config
- ../wordpress:/var/www
networks:
- caddy_net
networks:
caddy_net:
name: caddy_net
EOL
}
setup_directories() {
mkdir -p "${WORDPRESS_DIR}"
mkdir -p "${CADDY_DIR}/sites"
mkdir -p "${CADDY_DIR}/caddy_data"
mkdir -p "${CADDY_DIR}/caddy_config"
if [ ! -f "${CADDY_DIR}/Caddyfile" ]; then
echo "Creating initial Caddy configuration..."
create_caddy_docker_compose
fi
}
+4
View File
@@ -0,0 +1,4 @@
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
#BASE_DIR="/home"
BASE_DIR="${SCRIPT_DIR}"
WORDPRESS_DIR="${BASE_DIR}/wordpress"
CADDY_DIR="${BASE_DIR}/caddy"
+49
View File
@@ -0,0 +1,49 @@
#!/bin/bash
check_container_running() {
docker ps --format '{{.Names}}' | grep -q "^$1$"
return $?
}
create_docker_compose() {
export DOMAIN=$1
export MYSQL_ROOT_PASSWORD=$2
export MYSQL_PASSWORD=$3
envsubst < "${SCRIPT_DIR}/templates/docker-compose.yml.template" > compose.yaml
#envsubst '$DOMAIN $MYSQL_ROOT_PASSWORD $MYSQL_PASSWORD' < "${SCRIPT_DIR}/templates/docker-compose.yml.template" > compose.yaml
}
start_services() {
local FIRST_TIME=$1
local DOMAIN=$2
if [ "$FIRST_TIME" = true ]; then
echo "Starting Caddy server..."
cd "${CADDY_DIR}"
docker compose up -d
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to start Caddy server${NC}"
return 1
fi
else
# Check if Caddy is running and reload configuration
if check_container_running "caddy"; then
reload_caddy
else
echo -e "${RED}Caddy is not running. Please start it first${NC}"
return 1
fi
fi
echo "Starting WordPress for ${DOMAIN}..."
cd "${WORDPRESS_DIR}/${DOMAIN}"
docker compose up -d
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to start WordPress services${NC}"
return 1
fi
return 0
}
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
function generate_password() {
openssl rand -base64 12
}
export -f generate_password
function save_credentials() {
local WP_PROJECT_DIR=$1
local DOMAIN=$2
local ADMIN_USER=$3
local ADMIN_PASSWORD=$4
local ADMIN_EMAIL=$5
cat > "${WP_PROJECT_DIR}/credentials.txt" <<EOL
WordPress Site Credentials
========================
Domain: https://${DOMAIN}
Admin URL: https://${DOMAIN}/wp-admin
Username: ${ADMIN_USER}
Password: ${ADMIN_PASSWORD}
Email: ${ADMIN_EMAIL}
------------------------
Directory Information:
WordPress: ${WP_PROJECT_DIR}
Caddy Config: ${CADDY_DIR}/sites/${DOMAIN}.caddy
========================
Generated on: $(date)
EOL
chmod 600 "${WP_PROJECT_DIR}/credentials.txt"
}
export -f save_credentials
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
validate_domain() {
if [[ ! $1 =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$ ]]; then
echo -e "${RED}Invalid domain format${NC}"
return 1
fi
return 0
}
validate_email() {
if [[ ! $1 =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo -e "${RED}Invalid email format${NC}"
return 1
fi
return 0
}
+89
View File
@@ -0,0 +1,89 @@
#!/bin/bash
wait_for_wordpress() {
local DOMAIN=$1
local MAX_ATTEMPTS=30
local ATTEMPT=1
echo "Waiting for WordPress to be ready..."
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
#if docker run --rm --network container:wordpress_${DOMAIN} wordpress:cli-php8.1 wp core is-installed > /dev/null 2>&1; then
if docker compose run --rm wpcli core is-installed > /dev/null 2>&1; then
return 0
fi
echo "Attempt $ATTEMPT of $MAX_ATTEMPTS..."
sleep 10
ATTEMPT=$((ATTEMPT + 1))
done
return 1
}
create_env_file() {
export DOMAIN=$1
export ADMIN_USER=$2
export ADMIN_PASSWORD=$3
export ADMIN_EMAIL=$4
export MYSQL_ROOT_PASSWORD=$5
export MYSQL_PASSWORD=$6
# Output file
ENV_FILE=".env"
# Generate .env file
echo "Generating $ENV_FILE..."
cat <<EOL > "${WP_PROJECT_DIR}/$ENV_FILE"
DOMAIN_NAME = ${DOMAIN}
## Wordpress ##
WORDPRESS_DB_USER=wordpress
WORDPRESS_DB_PASSWORD=${DB_PASSWORD}
WORDPRESS_DB_NAME=wordpress
WORDPRESS_DB_HOST=db_${DOMAIN}:3306
# Website Credentials
WORDPRESS_ADMIN_USER=${ADMIN_USER}
WORDPRESS_ADMIN_PASSWORD=${ADMIN_PASSWORD}
WORDPRESS_ADMIN_EMAIL=${ADMIN_EMAIL}
## MYSQL ##
MYSQL_USER=wordpress
MYSQL_PASSWORD=${MYSQL_PASSWORD}
MYSQL_DATABASE=wordpress
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
EOL
echo "$ENV_FILE generated successfully!"
}
create_wp_setup() {
export DOMAIN=$1
export ADMIN_USER=$2
export ADMIN_PASSWORD=$3
export ADMIN_EMAIL=$4
export SITE_TITLE=$5
envsubst '$DOMAIN $ADMIN_USER $ADMIN_PASSWORD $ADMIN_EMAIL $SITE_TITLE' < "${SCRIPT_DIR}/templates/wp-setup.sh.template" > wp-setup.sh
chmod +x wp-setup.sh
}
run_wp_setup() {
local DOMAIN=$1
echo "Running WordPress setup..."
cd "${WORDPRESS_DIR}/${DOMAIN}"
#if ! wait_for_wordpress "$DOMAIN"; then
# echo -e "${RED}WordPress failed to start properly${NC}"
# return 1
#fi
if ./wp-setup.sh 2>&1; then
echo -e "${GREEN}WordPress setup completed successfully${NC}"
return 0
else
echo -e "${RED}WordPress setup failed${NC}"
return 1
fi
}
+22
View File
@@ -0,0 +1,22 @@
${DOMAIN} {
#reverse_proxy wordpress_${DOMAIN}:80
tls internal
root * /var/www/${DOMAIN}/html
encode zstd gzip
# Serve WordPress PHP files through php-fpm:
php_fastcgi wordpress_${DOMAIN}:9000 {
root /var/www/html
}
# Enable the static file server:
file_server {
precompressed gzip
}
header / {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
}
+61
View File
@@ -0,0 +1,61 @@
services:
db_${DOMAIN}:
container_name: db_${DOMAIN}
image: mariadb:11.2-jammy
volumes:
- ./db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "--silent"]
interval: 10s
timeout: 5s
retries: 3
networks:
- ${DOMAIN}_net
wordpress_${DOMAIN}:
container_name: wordpress_${DOMAIN}
depends_on:
- db_${DOMAIN}
image: wordpress:fpm-alpine
volumes:
- ./html/:/var/www/html
restart: always
environment:
WORDPRESS_DB_HOST: db_${DOMAIN}:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_DB_NAME: wordpress
networks:
- ${DOMAIN}_net
- caddy_net
wpcli:
depends_on:
- db_${DOMAIN}
- wordpress_${DOMAIN}
environment:
WORDPRESS_DB_HOST: db_${DOMAIN}:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_DB_NAME: wordpress
image: wordpress:cli
entrypoint: wp
command: "--info"
volumes_from:
- wordpress_${DOMAIN}
networks:
- ${DOMAIN}_net
- caddy_net
networks:
${DOMAIN}_net:
driver: bridge
caddy_net:
external: true
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
wp_cli() {
docker run --rm \
--network container:wordpress_${DOMAIN} \
-v "$(pwd):/var/www/html" \
wordpress:cli-php8.1 \
wp "$@"
}
wpcli() {
docker compose run --rm wpcli "$@"
}
echo "Waiting for MySQL to be ready..."
#while ! docker exec db_${DOMAIN} mysqladmin ping -h localhost --silent; do
# sleep 1
#done
while [ "$(docker inspect --format='{{.State.Health.Status}}' db_${DOMAIN})" != "healthy" ]; do
echo "MariaDB is not healthy yet. Retrying..."
sleep 5
done
echo "Installing WordPress..."
wpcli core install \
--url="https://${DOMAIN}" \
--title="${SITE_TITLE}" \
--admin_user="${ADMIN_USER}" \
--admin_password="${ADMIN_PASSWORD}" \
--admin_email="${ADMIN_EMAIL}" \
--skip-email
echo "Installing and activating plugins..."
wpcli plugin install wordfence --activate
wpcli theme install twentytwentyfour --activate
wpcli plugin update --all
wpcli theme update --all
echo "WordPress setup completed!"
Executable
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# Ask for confirmation before proceeding with the uninstallation
read -p "Are you sure you want to uninstall everything? This will stop and remove all containers, and delete files. (y/n): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
# Stop all running containers
echo "Stopping all containers..."
docker stop $(docker ps -a -q)
# Remove all containers
echo "Removing all containers..."
docker rm $(docker ps -a -q)
# Remove WordPress files
echo "Removing WordPress files..."
rm -rf wordpress
# Remove Caddy files
echo "Removing Caddy files..."
rm -rf caddy
echo "Uninstallation complete."
else
echo "Uninstallation aborted."
fi