diff --git a/kazewp.sh b/kazewp.sh index b4643d1..1cfbe86 100755 --- a/kazewp.sh +++ b/kazewp.sh @@ -1,19 +1,18 @@ #!/bin/bash # Get the directory where the script is located -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/src" # 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/colors.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" source "${SCRIPT_DIR}/lib/menu.sh" -#source "${SCRIPT_DIR}/lib/log.sh" - # Check if Docker is installed if ! command_exists docker; then @@ -27,9 +26,18 @@ if ! command_exists docker; then fi # Check if Docker service is running -if ! systemctl is-active --quiet docker; then +if [ "$(uname)" == "Darwin" ]; then + # macOS + if ! docker info > /dev/null 2>&1; then + echo "Docker service is not running. Starting Docker..." + open -a Docker + fi +else + # Linux + if ! systemctl is-active --quiet docker; then echo "Docker service is not running. Starting Docker..." sudo systemctl start docker + fi fi # Main script diff --git a/lib/menu.sh b/lib/menu.sh deleted file mode 100644 index 3f273ad..0000000 --- a/lib/menu.sh +++ /dev/null @@ -1,690 +0,0 @@ -#!/bin/bash - -# lib/menu.sh - -# Show the main interactive menu -show_interactive_menu() { - print_header "KazeWP - WordPress Site Management" - print_separator - print_subheader "Quick Usage:" - print_info " $0 list - List all installed WordPress sites" - print_info " $0 install - Install a new WordPress site" - print_info " $0 stop - Stop running WordPress sites" - print_info " $0 start - Start an installed WordPress sites" - print_info " $0 restart - Restart an installed WordPress sites" - print_info " $0 restart all - Restart all WordPress sites and Caddy" - print_info " $0 delete - Delete a WordPress site" - print_info " $0 delete all - Delete everything" - - - # Get and display existing sites - local -a sites=() - - list_sites - - - print_subheader "Available Actions" - echo -e "${BWHITE}Select an option:${NC}" - print_menu_action "n" "Install new WordPress site" - if [ -d "$WORDPRESS_DIR" ] && [ -n "$(find "$WORDPRESS_DIR" -mindepth 1 -type d 2>/dev/null)" ]; then - print_menu_action "m" "Manage multiple sites" - fi - print_menu_action "q" "Quit" - print_separator - - read -p "$(echo -e ${BCYAN}⮕${NC}) " CHOICE - - case $CHOICE in - "q"|"Q") - exit 0 - ;; - "n"|"N") - show_action_menu "new" - ;; - "m"|"M") - show_multi_site_menu "${sites[@]}" - ;; - *) - if [[ $CHOICE =~ ^[0-9]+$ ]] && [ "$CHOICE" -le "${#sites[@]}" ] && [ "$CHOICE" -gt 0 ]; then - show_single_site_menu "${sites[$CHOICE-1]}" - else - echo -e "${RED}Invalid selection. Please try again.${NC}" - show_interactive_menu - fi - ;; - esac -} - - - -# Show the menu for specific actions -show_action_menu() { - local action=$1 - shift - local -a sites=("$@") - - case $action in - "brand-new") - while true; do - read -p "Do you want to install new WordPress site now? (y/N): " confirm - if [[ "$confirm" =~ ^[Yy]$ ]]; then - show_action_menu "new" - return - else - exit - fi - done - ;; - "new") - while true; do - read -p "Enter domain for new WordPress site (or 'q' to quit): " domain - if [ "$domain" = "q" ]; then - show_interactive_menu - return - fi - if validate_domain "$domain"; then - install_site "$domain" - break - fi - done - ;; - "delete") - echo -e "\n${YELLOW}Selected sites to delete:${NC}" - printf '%s\n' "${sites[@]}" - read -p "Are you sure you want to delete these sites? (y/N): " confirm - if [[ "$confirm" =~ ^[Yy]$ ]]; then - for site in "${sites[@]}"; do - delete_sites "$site" - done - else - show_interactive_menu - fi - ;; - "stop") - echo -e "\n${YELLOW}Stopping selected sites:${NC}" - printf '%s\n' "${sites[@]}" - for site in "${sites[@]}"; do - stop_sites "$site" - done - ;; - "all") - echo -e "\n${BLUE}Available actions for all sites:${NC}" - echo "1) Stop all sites" - echo "2) Delete all sites" - echo "3) Back to main menu" - read -p "Enter your choice: " choice - case $choice in - 1) stop_sites "all" ;; - 2) delete_sites "all" ;; - 3) show_interactive_menu ;; - *) echo -e "${RED}Invalid choice${NC}" ;; - esac - ;; - esac -} - -# Get the current status of a site - -get_site_status() { - local compose_file=$1 - local status="" - local running_count=0 - local total_count=0 - - # Get the total number of containers defined in the compose file - total_count=$(docker compose -f "$compose_file" ps --quiet | wc -l) - - # Get the number of running containers - running_count=$(docker compose -f "$compose_file" ps --status running --quiet | wc -l) - - if [ $total_count -eq 0 ]; then - status="${YELLOW}[Not Started]${NC}" - elif [ $running_count -eq 0 ]; then - status="${RED}[Stopped]${NC}" - elif [ $running_count -eq $total_count ]; then - status="${GREEN}[Running]${NC}" - else - status="${YELLOW}[Partial: $running_count/$total_count]${NC}" - fi - - echo -e "$status" -} - - -# Function to install a new WordPress site -install_site() { - - FIRST_TIME=false - if ! check_container_running "caddy"; then - FIRST_TIME=true - fi - - setup_directories - - DOMAIN="$1" - - WP_PROJECT_DIR="${WORDPRESS_DIR}/${DOMAIN}" - if [ -d "$WP_PROJECT_DIR" ]; then - print_error "${RED}Directory ${WP_PROJECT_DIR} already exists!${NC}" - exit 1 - fi - - while true; do - read -p "Enter admin email: " ADMIN_EMAIL - if validate_email "$ADMIN_EMAIL"; then - break - fi - done - - while true; do - read -p "Enter admin username: " ADMIN_USER - if validate_username "$ADMIN_USER"; then - break - fi - done - - 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) - - 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 - print_success "${GREEN}Complete setup finished successfully!${NC}" - else - print_error "${RED}WordPress setup failed. You may need to run setup manually later.${NC}" - fi - else - print_error "${RED}Service startup failed. You may need to start services manually.${NC}" - fi - break - ;; - 2) - print_info "\n${BLUE}Manual setup instructions:${NC}" - if [ "$FIRST_TIME" = true ]; then - print_info "1. Start Caddy:" - print_info " cd ${CADDY_DIR} && docker compose up -d" - fi - reload_caddy - print_info "2. Start WordPress:" - print_info " cd ${WP_PROJECT_DIR} && docker compose up -d" - print_info "3. Run the WordPress setup script:" - print_info " ./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" - echo "Password: $ADMIN_PASSWORD (SAVE THIS PASSWORD!)" - 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 -} - -# Function to list all installed WordPress sites -list_sites_sss() { - if [ ! -d "$WORDPRESS_DIR" ]; then - echo "There are no websites setup yet!" - exit 1 - fi - echo "Installed WordPress Sites:" - echo "-------------------------" - subfolders=($(find "$WORDPRESS_DIR" -mindepth 1 -maxdepth 1 -type d)) - if [ ${#subfolders[@]} -eq 0 ]; then - echo "There are no websites setup yet!" - else - for site in "$WORDPRESS_DIR"/*; do - if [ -d "$site" ]; then - DOMAIN=$(basename "$site") - if [ -f "$site/compose.yaml" ]; then - echo "Site: $DOMAIN" - echo "Path: $site" - get_containers_status "$site/compose.yaml" - #echo "Status: $(docker compose -f $site/compose.yaml ps --status running | grep -v 'Name' | wc -l) containers running" - echo "-------------------------" - fi - fi - done - fi -} - -list_sites() { - local i=1 - print_subheader "Installed WordPress Sites" - - # First check if any sites exist - if [ ! -d "$WORDPRESS_DIR" ] || [ -z "$(ls -A "$WORDPRESS_DIR" 2>/dev/null)" ]; then - print_warning "No WordPress sites installed yet." - #show_action_menu "brand-new" - #return - else - while IFS= read -r site; do - if [ -f "$site/compose.yaml" ]; then - sites+=("$(basename "$site")") - domain=$(basename "$site") - status=$(get_site_status "$site/compose.yaml") - print_menu_item "$i" "$domain" "$status" - ((i++)) - fi - done < <(find "$WORDPRESS_DIR" -mindepth 1 -maxdepth 1 -type d | sort) - fi - -} - - -# Function to stop WordPress sites -stop_sites() { - local target="$1" - - if [ ! -d "$WORDPRESS_DIR" ]; then - echo "No WordPress installations found!" - exit 1 - fi - - case "$target" in - "all") - echo "Stopping all WordPress containers..." - # Stop Caddy first - if [ -f "${CADDY_DIR}/compose.yaml" ]; then - echo "Stopping Caddy reverse proxy..." - docker compose -f "${CADDY_DIR}/compose.yaml" stop - fi - - # Stop all WordPress installations - for site in "$WORDPRESS_DIR"/*; do - if [ -d "$site" ] && [ -f "$site/compose.yaml" ]; then - domain=$(basename "$site") - echo "Stopping site: $domain" - docker compose -f "$site/compose.yaml" stop - fi - done - echo "All containers have been stopped." - ;; - - *) - # Stop specific site - WP_PROJECT_DIR="${WORDPRESS_DIR}/${target}" - if [ ! -d "$WP_PROJECT_DIR" ] || [ ! -f "$WP_PROJECT_DIR/compose.yaml" ]; then - echo "Error: Site '$target' not found" - exit 1 - fi - echo "Stopping site: $target" - docker compose -f "$WP_PROJECT_DIR/compose.yaml" stop - echo "Site containers have been stopped." - ;; - esac -} - -# Function to start WordPress sites -start_sites() { - local target="$1" - - if [ ! -d "$WORDPRESS_DIR" ]; then - echo "No WordPress installations found!" - exit 1 - fi - - case "$target" in - "all") - echo "Starting all WordPress containers..." - # Start Caddy first - if [ -f "${CADDY_DIR}/compose.yaml" ]; then - echo "Starting Caddy reverse proxy..." - docker compose -f "${CADDY_DIR}/compose.yaml" up -d - fi - - # Start all WordPress installations - for site in "$WORDPRESS_DIR"/*; do - if [ -d "$site" ] && [ -f "$site/compose.yaml" ]; then - domain=$(basename "$site") - echo "Starting site: $domain" - docker compose -f "$site/compose.yaml" up -d - fi - done - echo "All containers have been started." - ;; - - *) - # Start specific site - WP_PROJECT_DIR="${WORDPRESS_DIR}/${target}" - if [ ! -d "$WP_PROJECT_DIR" ] || [ ! -f "$WP_PROJECT_DIR/compose.yaml" ]; then - echo "Error: Site '$target' not found" - exit 1 - fi - echo "Starting site: $target" - docker compose -f "$WP_PROJECT_DIR/compose.yaml" up -d - echo "Site containers have been started." - ;; - esac -} - -# Function to delete a WordPress site -delete_sites() { - DOMAIN="$1" - WP_PROJECT_DIR="${WORDPRESS_DIR}/${DOMAIN}" - - case "$DOMAIN" in - "all") - 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..." - find . -name 'compose.yaml' -execdir docker compose down \; - - # Remove WordPress files - echo "Removing WordPress files..." - rm -rf wordpress - - # Remove Caddy files - echo "Removing Caddy files..." - rm -rf caddy - - # Remove log files - rm -rf kazewp.log - - docker system prune -af - - echo "Uninstallation complete." - else - echo "Uninstallation aborted." - fi - ;; - - *) - if [ ! -d "$WP_PROJECT_DIR" ]; then - echo "Error: Site '$DOMAIN' not found" - return 1 - else - echo "Warning: This will permanently delete the site: $DOMAIN" - read -p "Are you sure you want to continue? (y/N): " confirm - - if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then - echo "Operation cancelled" - return 0 - fi - - echo "Stopping Docker containers..." - docker compose -f "$WP_PROJECT_DIR/compose.yaml" down - - echo "Removing site directory..." - rm -rf "$WP_PROJECT_DIR" - - echo "Removing Caddy configuration..." - rm -f "${CADDY_DIR}/sites/${DOMAIN}.caddy" - - echo "Reloading Caddy..." - reload_caddy - - echo "Site '$DOMAIN' has been successfully deleted" - fi - ;; - esac - - - - -} - - -show_single_site_menu() { - local domain=$1 - local compose_file="${WORDPRESS_DIR}/${domain}/compose.yaml" - local status=$(get_site_status "$compose_file") - - while true; do - print_header "Site Management" - print_info "Managing site: ${BGREEN}${domain}${NC} ${status}" - print_separator - - print_menu_item "1" "Start site" - print_menu_item "2" "Stop site" - print_menu_item "3" "Restart site" - print_menu_item "4" "View site details" - print_menu_item "5" "Delete site" - print_menu_action "b" "Back to main menu" - print_menu_action "q" "Quit" - print_separator - - read -p "$(echo -e ${BCYAN}⮕${NC}) " action - - case $action in - 1) - start_sites "$domain" - status=$(get_site_status "$compose_file") - ;; - 2) - stop_sites "$domain" - status=$(get_site_status "$compose_file") - ;; - 3) - restart_sites "$domain" - status=$(get_site_status "$compose_file") - ;; - 4) - show_site_details "$domain" - read -p "Press Enter to continue..." - ;; - 5) - delete_sites "$domain" - show_interactive_menu - return - ;; - "b"|"B") - show_interactive_menu - return - ;; - "q"|"Q") - exit 0 - ;; - *) - echo -e "${RED}Invalid choice${NC}" - ;; - esac - done -} - -show_multi_site_menu() { - local -a sites=("$@") - - print_header "Site Management" - print_separator - echo "Select sites (e.g., 1,3,5 or 1-4 or all):" - - local i=1 - for domain in "${sites[@]}"; do - status=$(get_site_status "${WORDPRESS_DIR}/${domain}/compose.yaml") - echo -e "$i) $domain ${status}" - ((i++)) - done - - echo "----------------------------------------" - read -p "Enter site selection (or 'b' for back): " selection - - [ "$selection" = "b" ] && show_interactive_menu && return - - if [ "$selection" = "all" ]; then - selected_sites=("${sites[@]}") - else - # Convert selection to array of sites - local -a selected_sites=() - IFS=',' read -ra NUMS <<< "$selection" - for num in "${NUMS[@]}"; do - if [[ $num =~ ^[0-9]+-[0-9]+$ ]]; then - # Handle range (e.g., "1-3") - start="${num%-*}" - end="${num#*-}" - for ((i=start; i<=end; i++)); do - if [ $i -le ${#sites[@]} ]; then - selected_sites+=("${sites[$i-1]}") - fi - done - elif [[ $num =~ ^[0-9]+$ ]] && [ "$num" -le "${#sites[@]}" ]; then - selected_sites+=("${sites[$num-1]}") - fi - done - fi - - if [ ${#selected_sites[@]} -eq 0 ]; then - echo -e "${RED}No valid sites selected${NC}" - sleep 2 - show_multi_site_menu "${sites[@]}" - return - fi - - show_bulk_action_menu "${selected_sites[@]}" -} - -show_bulk_action_menu() { - local -a sites=("$@") - - echo -e "\n${BLUE}Bulk Actions for Selected Sites:${NC}" - echo "Selected sites: ${sites[*]}" - echo "----------------------------------------" - echo "1) Start sites" - echo "2) Stop sites" - echo "3) Restart sites" - echo "4) Delete sites" - echo "b) Back to site selection" - echo "q) Quit" - echo "----------------------------------------" - - read -p "Enter your choice: " action - - case $action in - 1) - for site in "${sites[@]}"; do - start_sites "$site" - done - ;; - 2) - for site in "${sites[@]}"; do - stop_sites "$site" - done - ;; - 3) - for site in "${sites[@]}"; do - restart_sites "$site" - done - ;; - 4) - echo -e "${YELLOW}Are you sure you want to delete these sites?${NC}" - printf '%s\n' "${sites[@]}" - read -p "Confirm deletion (y/N): " confirm - if [[ "$confirm" =~ ^[Yy]$ ]]; then - for site in "${sites[@]}"; do - delete_sites "$site" - done - fi - show_interactive_menu - return - ;; - "b"|"B") - show_multi_site_menu "${sites[@]}" - return - ;; - "q"|"Q") - exit 0 - ;; - *) - echo -e "${RED}Invalid choice${NC}" - sleep 2 - show_bulk_action_menu "${sites[@]}" - ;; - esac - - show_multi_site_menu "${sites[@]}" -} - -show_site_details() { - local domain=$1 - local compose_file="${WORDPRESS_DIR}/${domain}/compose.yaml" - - echo -e "\n${BLUE}Site Details for ${GREEN}${domain}${NC}" - echo "----------------------------------------" - echo -e "Status: $(get_site_status "$compose_file")" - echo "Installation Directory: ${WORDPRESS_DIR}/${domain}" - echo -e "\nContainer Status:" - docker compose -f "$compose_file" ps - #echo -e "\nContainer Logs (last 5 lines):" - #docker compose -f "$compose_file" logs --tail=5 -} - - - -# Function to restart WordPress sites -restart_sites() { - local target="$1" - - if [ ! -d "$WORDPRESS_DIR" ]; then - echo "No WordPress installations found!" - exit 1 - fi - - case "$target" in - "all") - print_subheader "Restarting all containers..." - # Restart Caddy first - if [ -f "${CADDY_DIR}/compose.yaml" ]; then - print_info "Restarting Caddy reverse proxy..." - docker compose -f "${CADDY_DIR}/compose.yaml" restart - fi - - # Restart all WordPress installations - for site in "$WORDPRESS_DIR"/*; do - if [ -d "$site" ] && [ -f "$site/compose.yaml" ]; then - domain=$(basename "$site") - echo "Starting site: $domain" - docker compose -f "$site/compose.yaml" restart - fi - done - echo "All containers have been started." - ;; - - *) - # Restart specific site - WP_PROJECT_DIR="${WORDPRESS_DIR}/${target}" - if [ ! -d "$WP_PROJECT_DIR" ] || [ ! -f "$WP_PROJECT_DIR/compose.yaml" ]; then - echo "Error: Site '$target' not found" - exit 1 - fi - echo "Starting site: $target" - docker compose -f "$WP_PROJECT_DIR/compose.yaml" restart - echo "Site containers have been started." - ;; - esac -} \ No newline at end of file diff --git a/images/kazewp.png b/src/images/kazewp.png similarity index 100% rename from images/kazewp.png rename to src/images/kazewp.png diff --git a/lib/caddy.sh b/src/lib/caddy.sh similarity index 100% rename from lib/caddy.sh rename to src/lib/caddy.sh diff --git a/lib/colors.sh b/src/lib/colors.sh similarity index 100% rename from lib/colors.sh rename to src/lib/colors.sh diff --git a/lib/config.sh b/src/lib/config.sh similarity index 77% rename from lib/config.sh rename to src/lib/config.sh index 08c186b..3bd3d01 100644 --- a/lib/config.sh +++ b/src/lib/config.sh @@ -1,6 +1,6 @@ #!/bin/bash #BASE_DIR="/home" -BASE_DIR="${SCRIPT_DIR}" +BASE_DIR="${SCRIPT_DIR}/.." WORDPRESS_DIR="${BASE_DIR}/wordpress" CADDY_DIR="${BASE_DIR}/caddy" \ No newline at end of file diff --git a/lib/docker.sh b/src/lib/docker.sh similarity index 65% rename from lib/docker.sh rename to src/lib/docker.sh index f133430..a86d84f 100644 --- a/lib/docker.sh +++ b/src/lib/docker.sh @@ -11,25 +11,25 @@ install_docker() { # Function to install curl if missing install_curl() { - if ! command -v curl &> /dev/null; then + if ! command -v curl &>/dev/null; then echo "curl is not installed. Installing curl..." case "$OS" in - ubuntu|debian) - sudo apt update && sudo apt install -y curl - ;; - centos|rhel|rocky|alma) - sudo yum install -y curl - ;; - fedora) - sudo dnf install -y curl - ;; - arch) - sudo pacman -Sy --noconfirm curl - ;; - *) - echo "Unsupported OS: $OS. Please install curl manually." - exit 1 - ;; + ubuntu | debian) + sudo apt update && sudo apt install -y curl + ;; + centos | rhel | rocky | alma) + sudo yum install -y curl + ;; + fedora) + sudo dnf install -y curl + ;; + arch) + sudo pacman -Sy --noconfirm curl + ;; + *) + echo "Unsupported OS: $OS. Please install curl manually." + exit 1 + ;; esac fi } @@ -38,7 +38,7 @@ install_docker() { install_curl # Check if Docker is already installed - if command -v docker &> /dev/null; then + if command -v docker &>/dev/null; then echo "Docker is already installed." return fi @@ -64,8 +64,6 @@ install_docker() { fi } - - check_container_running() { docker ps --format '{{.Names}}' | grep -q "^$1$" return $? @@ -73,11 +71,39 @@ check_container_running() { create_docker_compose() { export DOMAIN=$1 - export MYSQL_ROOT_PASSWORD=$2 - export MYSQL_PASSWORD=$3 + local USE_PHPMYADMIN=$4 - 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 + + # Define phpMyAdmin service block + PHPMYADMIN_BLOCK=$( + cat <<-EOL + phpmyadmin_${DOMAIN}: + container_name: phpmyadmin_${DOMAIN} + depends_on: + - db_${DOMAIN} + image: phpmyadmin/phpmyadmin + environment: + - PMA_HOST=db_${DOMAIN} + - MYSQL_ROOT_PASSWORD=\${MYSQL_ROOT_PASSWORD} + restart: always + ports: + - "8080:80" + networks: + - ${DOMAIN}_net + profiles: + - phpmyadmin +EOL + ) + + # Process Docker Compose template + if [[ "$USE_PHPMYADMIN" == "y" ]]; then + export PHPMYADMIN_BLOCK="$PHPMYADMIN_BLOCK" + else + export PHPMYADMIN_BLOCK="" + fi + envsubst <"${SCRIPT_DIR}/templates/docker-compose.yml.template" >compose.yaml + + echo "Docker Compose file created successfully." } @@ -118,22 +144,22 @@ get_containers_status() { local compose_file="$1" local containers=() local count=0 - + while IFS= read -r container; do # Skip if empty line [ -z "$container" ] && continue containers+=("$container") ((count++)) done < <(docker compose -f "$compose_file" ps --status running --format '{{.Name}}') - + if [ $count -eq 0 ]; then echo "Status: No container is running" else echo -n "Status: $count containers are running: " printf "%s" "${containers[0]}" - for ((i=1; i<${#containers[@]}; i++)); do + for ((i = 1; i < ${#containers[@]}; i++)); do printf " and %s" "${containers[$i]}" done echo fi -} \ No newline at end of file +} diff --git a/lib/log.sh b/src/lib/log.sh similarity index 100% rename from lib/log.sh rename to src/lib/log.sh diff --git a/src/lib/menu.sh b/src/lib/menu.sh new file mode 100644 index 0000000..f73acf0 --- /dev/null +++ b/src/lib/menu.sh @@ -0,0 +1,675 @@ +#!/bin/bash + +# lib/menu.sh + +# Show the main interactive menu +show_interactive_menu() { + print_header "KazeWP - WordPress Site Management" + print_separator + print_subheader "Quick Usage:" + print_info " $0 list - List all installed WordPress sites" + print_info " $0 install - Install a new WordPress site" + print_info " $0 stop - Stop running WordPress sites" + print_info " $0 start - Start an installed WordPress sites" + print_info " $0 restart - Restart an installed WordPress sites" + print_info " $0 restart all - Restart all WordPress sites and Caddy" + print_info " $0 delete - Delete a WordPress site" + print_info " $0 delete all - Delete everything" + + # Get and display existing sites + local -a sites=() + + list_sites + + print_subheader "Available Actions" + echo -e "${BWHITE}Select an option:${NC}" + print_menu_action "n" "Install new WordPress site" + if [ -d "$WORDPRESS_DIR" ] && [ -n "$(find "$WORDPRESS_DIR" -mindepth 1 -type d 2>/dev/null)" ]; then + print_menu_action "m" "Manage multiple sites" + fi + print_menu_action "q" "Quit" + print_separator + + read -p "$(echo -e ${BCYAN}⮕${NC}) " CHOICE + + case $CHOICE in + "q" | "Q") + exit 0 + ;; + "n" | "N") + show_action_menu "new" + ;; + "m" | "M") + show_multi_site_menu "${sites[@]}" + ;; + *) + if [[ $CHOICE =~ ^[0-9]+$ ]] && [ "$CHOICE" -le "${#sites[@]}" ] && [ "$CHOICE" -gt 0 ]; then + show_single_site_menu "${sites[$CHOICE - 1]}" + else + echo -e "${RED}Invalid selection. Please try again.${NC}" + show_interactive_menu + fi + ;; + esac +} + +# Show the menu for specific actions +show_action_menu() { + local action=$1 + shift + local -a sites=("$@") + + case $action in + "brand-new") + while true; do + read -p "Do you want to install new WordPress site now? (y/N): " confirm + if [[ "$confirm" =~ ^[Yy]$ ]]; then + show_action_menu "new" + return + else + exit + fi + done + ;; + "new") + while true; do + read -p "Enter domain for new WordPress site (or 'q' to quit): " domain + if [ "$domain" = "q" ]; then + show_interactive_menu + return + fi + if validate_domain "$domain"; then + install_site "$domain" + break + fi + done + ;; + "delete") + echo -e "\n${YELLOW}Selected sites to delete:${NC}" + printf '%s\n' "${sites[@]}" + read -p "Are you sure you want to delete these sites? (y/N): " confirm + if [[ "$confirm" =~ ^[Yy]$ ]]; then + for site in "${sites[@]}"; do + delete_sites "$site" + done + else + show_interactive_menu + fi + ;; + "stop") + echo -e "\n${YELLOW}Stopping selected sites:${NC}" + printf '%s\n' "${sites[@]}" + for site in "${sites[@]}"; do + stop_sites "$site" + done + ;; + "all") + echo -e "\n${BLUE}Available actions for all sites:${NC}" + echo "1) Stop all sites" + echo "2) Delete all sites" + echo "3) Back to main menu" + read -p "Enter your choice: " choice + case $choice in + 1) stop_sites "all" ;; + 2) delete_sites "all" ;; + 3) show_interactive_menu ;; + *) echo -e "${RED}Invalid choice${NC}" ;; + esac + ;; + esac +} + +# Get the current status of a site + +get_site_status() { + local compose_file=$1 + local status="" + local running_count=0 + local total_count=0 + + # Get the total number of containers defined in the compose file + total_count=$(docker compose -f "$compose_file" ps --quiet | wc -l) + + # Get the number of running containers + running_count=$(docker compose -f "$compose_file" ps --status running --quiet | wc -l) + + if [ $total_count -eq 0 ]; then + status="${YELLOW}[Not Started]${NC}" + elif [ $running_count -eq 0 ]; then + status="${RED}[Stopped]${NC}" + elif [ $running_count -eq $total_count ]; then + status="${GREEN}[Running]${NC}" + else + status="${YELLOW}[Partial: $running_count/$total_count]${NC}" + fi + + echo -e "$status" +} + +# Function to install a new WordPress site +install_site() { + # Initialize FIRST_TIME flag to determine if Caddy is running for the first time + FIRST_TIME=false + if ! check_container_running "caddy"; then + # If Caddy is not running, set FIRST_TIME to true + FIRST_TIME=true + fi + + # Prepare necessary directories for WordPress and Caddy + setup_directories + + # Capture the domain name provided as the first argument + DOMAIN="$1" + + # Define the directory path for the WordPress project + WP_PROJECT_DIR="${WORDPRESS_DIR}/${DOMAIN}" + if [ -d "$WP_PROJECT_DIR" ]; then + # If the directory already exists, print an error message and exit + print_error "${RED}Directory ${WP_PROJECT_DIR} already exists!${NC}" + exit 1 + fi + + # Prompt for admin email and validate input + while true; do + read -p "Enter admin email: " ADMIN_EMAIL + if validate_email "$ADMIN_EMAIL"; then + break + fi + done + + # Prompt for admin username and validate input + while true; do + read -p "Enter admin username: " ADMIN_USER + if validate_username "$ADMIN_USER"; then + break + fi + done + + # Prompt for admin password and allow for random generation if not provided + read -s -p "Enter password (press Enter for random password): " ADMIN_PASSWORD + echo + + if [ -z "$ADMIN_PASSWORD" ]; then + # Generate a random password if none is provided + ADMIN_PASSWORD=$(generate_password) + echo "Generated password: $ADMIN_PASSWORD" + fi + + # Prompt for the site title + read -p "Enter site title: " SITE_TITLE + + # Generate random passwords for MySQL root and user + MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32) + MYSQL_PASSWORD=$(openssl rand -base64 32) + + # Create the WordPress project directory and navigate into it + mkdir -p "$WP_PROJECT_DIR" + cd "$WP_PROJECT_DIR" + + # Ask if the user wants to include phpMyAdmin in the setup + read -p "Do you want to include phpMyAdmin? (y/n): " USE_PHPMYADMIN + USE_PHPMYADMIN=$(echo "$USE_PHPMYADMIN" | tr '[:upper:]' '[:lower:]') + + # Create necessary configuration files for Docker Compose, Caddy, and WordPress + create_docker_compose "$DOMAIN" "$MYSQL_ROOT_PASSWORD" "$MYSQL_PASSWORD" "$USE_PHPMYADMIN" + 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" + + # Ask the user whether to start services and set up WordPress automatically or manually + 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) + # Automatically start services and run WordPress setup + if start_services "$FIRST_TIME" "$DOMAIN"; then + if run_wp_setup "$DOMAIN"; then + print_success "${GREEN}Complete setup finished successfully!${NC}" + else + print_error "${RED}WordPress setup failed. You may need to run setup manually later.${NC}" + fi + else + print_error "${RED}Service startup failed. You may need to start services manually.${NC}" + fi + break + ;; + 2) + # Provide manual setup instructions + print_info "\n${BLUE}Manual setup instructions:${NC}" + if [ "$FIRST_TIME" = true ]; then + print_info "1. Start Caddy:" + print_info " cd ${CADDY_DIR} && docker compose up -d" + fi + reload_caddy + print_info "2. Start WordPress:" + print_info " cd ${WP_PROJECT_DIR} && docker compose up -d" + print_info "3. Run the WordPress setup script:" + print_info " ./wp-setup.sh" + break + ;; + *) + # Handle invalid choices + echo -e "${RED}Invalid choice. Please enter 1 or 2.${NC}" + ;; + esac + done + + # Display WordPress site information to the user + echo -e "\n${BLUE}WordPress Site Information:${NC}" + echo "----------------------------------------" + echo "Domain: https://$DOMAIN" + echo "Admin URL: https://$DOMAIN/wp-admin" + echo "Username: $ADMIN_USER" + echo "Password: $ADMIN_PASSWORD (SAVE THIS PASSWORD!)" + echo "Email: $ADMIN_EMAIL" + echo "----------------------------------------" + + # Save credentials to a file + save_credentials "$WP_PROJECT_DIR" "$DOMAIN" "$ADMIN_USER" "$ADMIN_PASSWORD" "$ADMIN_EMAIL" + + # Notify user of saved credentials + echo -e "\nCredentials have been saved to: ${WP_PROJECT_DIR}/credentials.txt" + + # Exit the script + exit 0 +} + +# Function to list all installed WordPress sites +list_sites() { + local i=1 + print_subheader "Installed WordPress Sites" + + # First check if any sites exist + if [ ! -d "$WORDPRESS_DIR" ] || [ -z "$(ls -A "$WORDPRESS_DIR" 2>/dev/null)" ]; then + print_warning "No WordPress sites installed yet." + #show_action_menu "brand-new" + #return + else + while IFS= read -r site; do + if [ -f "$site/compose.yaml" ]; then + sites+=("$(basename "$site")") + domain=$(basename "$site") + status=$(get_site_status "$site/compose.yaml") + print_menu_item "$i" "$domain" "$status" + ((i++)) + fi + done < <(find "$WORDPRESS_DIR" -mindepth 1 -maxdepth 1 -type d | sort) + fi + +} + +# Function to stop WordPress sites +stop_sites() { + local target="$1" + + if [ ! -d "$WORDPRESS_DIR" ]; then + echo "No WordPress installations found!" + exit 1 + fi + + case "$target" in + "all") + echo "Stopping all WordPress containers..." + # Stop Caddy first + if [ -f "${CADDY_DIR}/compose.yaml" ]; then + echo "Stopping Caddy reverse proxy..." + docker compose -f "${CADDY_DIR}/compose.yaml" stop + fi + + # Stop all WordPress installations + for site in "$WORDPRESS_DIR"/*; do + if [ -d "$site" ] && [ -f "$site/compose.yaml" ]; then + domain=$(basename "$site") + echo "Stopping site: $domain" + docker compose -f "$site/compose.yaml" stop + fi + done + echo "All containers have been stopped." + ;; + + *) + # Stop specific site + WP_PROJECT_DIR="${WORDPRESS_DIR}/${target}" + if [ ! -d "$WP_PROJECT_DIR" ] || [ ! -f "$WP_PROJECT_DIR/compose.yaml" ]; then + echo "Error: Site '$target' not found" + exit 1 + fi + echo "Stopping site: $target" + docker compose -f "$WP_PROJECT_DIR/compose.yaml" stop + echo "Site containers have been stopped." + ;; + esac +} + +# Function to start WordPress sites +start_sites() { + local target="$1" + + if [ ! -d "$WORDPRESS_DIR" ]; then + echo "No WordPress installations found!" + exit 1 + fi + + case "$target" in + "all") + echo "Starting all WordPress containers..." + # Start Caddy first + if [ -f "${CADDY_DIR}/compose.yaml" ]; then + echo "Starting Caddy reverse proxy..." + docker compose -f "${CADDY_DIR}/compose.yaml" up -d + fi + + # Start all WordPress installations + for site in "$WORDPRESS_DIR"/*; do + if [ -d "$site" ] && [ -f "$site/compose.yaml" ]; then + domain=$(basename "$site") + echo "Starting site: $domain" + docker compose -f "$site/compose.yaml" up -d + fi + done + echo "All containers have been started." + ;; + + *) + # Start specific site + WP_PROJECT_DIR="${WORDPRESS_DIR}/${target}" + if [ ! -d "$WP_PROJECT_DIR" ] || [ ! -f "$WP_PROJECT_DIR/compose.yaml" ]; then + echo "Error: Site '$target' not found" + exit 1 + fi + echo "Starting site: $target" + docker compose -f "$WP_PROJECT_DIR/compose.yaml" up -d + echo "Site containers have been started." + ;; + esac +} + +# Function to delete a WordPress site +delete_sites() { + DOMAIN="$1" + WP_PROJECT_DIR="${WORDPRESS_DIR}/${DOMAIN}" + + case "$DOMAIN" in + "all") + 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..." + find . -name 'compose.yaml' -execdir docker compose down \; + + # Remove WordPress files + echo "Removing WordPress files..." + rm -rf wordpress + + # Remove Caddy files + echo "Removing Caddy files..." + rm -rf caddy + + # Remove log files + rm -rf kazewp.log + + docker system prune -af + + echo "Uninstallation complete." + else + echo "Uninstallation aborted." + fi + ;; + + *) + if [ ! -d "$WP_PROJECT_DIR" ]; then + echo "Error: Site '$DOMAIN' not found" + return 1 + else + echo "Warning: This will permanently delete the site: $DOMAIN" + read -p "Are you sure you want to continue? (y/N): " confirm + + if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then + echo "Operation cancelled" + return 0 + fi + + echo "Stopping Docker containers..." + docker compose -f "$WP_PROJECT_DIR/compose.yaml" down + + echo "Removing site directory..." + rm -rf "$WP_PROJECT_DIR" + + echo "Removing Caddy configuration..." + rm -f "${CADDY_DIR}/sites/${DOMAIN}.caddy" + + echo "Reloading Caddy..." + reload_caddy + + echo "Site '$DOMAIN' has been successfully deleted" + fi + ;; + esac + +} + +show_single_site_menu() { + local domain=$1 + local compose_file="${WORDPRESS_DIR}/${domain}/compose.yaml" + local status=$(get_site_status "$compose_file") + + while true; do + print_header "Site Management" + print_info "Managing site: ${BGREEN}${domain}${NC} ${status}" + print_separator + + print_menu_item "1" "Start site" + print_menu_item "2" "Stop site" + print_menu_item "3" "Restart site" + print_menu_item "4" "View site details" + print_menu_item "5" "Delete site" + print_menu_action "b" "Back to main menu" + print_menu_action "q" "Quit" + print_separator + + read -p "$(echo -e ${BCYAN}⮕${NC}) " action + + case $action in + 1) + start_sites "$domain" + status=$(get_site_status "$compose_file") + ;; + 2) + stop_sites "$domain" + status=$(get_site_status "$compose_file") + ;; + 3) + restart_sites "$domain" + status=$(get_site_status "$compose_file") + ;; + 4) + show_site_details "$domain" + read -p "Press Enter to continue..." + ;; + 5) + delete_sites "$domain" + show_interactive_menu + return + ;; + "b" | "B") + show_interactive_menu + return + ;; + "q" | "Q") + exit 0 + ;; + *) + echo -e "${RED}Invalid choice${NC}" + ;; + esac + done +} + +show_multi_site_menu() { + local -a sites=("$@") + + print_header "Site Management" + print_separator + echo "Select sites (e.g., 1,3,5 or 1-4 or all):" + + local i=1 + for domain in "${sites[@]}"; do + status=$(get_site_status "${WORDPRESS_DIR}/${domain}/compose.yaml") + echo -e "$i) $domain ${status}" + ((i++)) + done + + echo "----------------------------------------" + read -p "Enter site selection (or 'b' for back): " selection + + [ "$selection" = "b" ] && show_interactive_menu && return + + if [ "$selection" = "all" ]; then + selected_sites=("${sites[@]}") + else + # Convert selection to array of sites + local -a selected_sites=() + IFS=',' read -ra NUMS <<<"$selection" + for num in "${NUMS[@]}"; do + if [[ $num =~ ^[0-9]+-[0-9]+$ ]]; then + # Handle range (e.g., "1-3") + start="${num%-*}" + end="${num#*-}" + for ((i = start; i <= end; i++)); do + if [ $i -le ${#sites[@]} ]; then + selected_sites+=("${sites[$i - 1]}") + fi + done + elif [[ $num =~ ^[0-9]+$ ]] && [ "$num" -le "${#sites[@]}" ]; then + selected_sites+=("${sites[$num - 1]}") + fi + done + fi + + if [ ${#selected_sites[@]} -eq 0 ]; then + echo -e "${RED}No valid sites selected${NC}" + sleep 2 + show_multi_site_menu "${sites[@]}" + return + fi + + show_bulk_action_menu "${selected_sites[@]}" +} + +show_bulk_action_menu() { + local -a sites=("$@") + + echo -e "\n${BLUE}Bulk Actions for Selected Sites:${NC}" + echo "Selected sites: ${sites[*]}" + echo "----------------------------------------" + echo "1) Start sites" + echo "2) Stop sites" + echo "3) Restart sites" + echo "4) Delete sites" + echo "b) Back to site selection" + echo "q) Quit" + echo "----------------------------------------" + + read -p "Enter your choice: " action + + case $action in + 1) + for site in "${sites[@]}"; do + start_sites "$site" + done + ;; + 2) + for site in "${sites[@]}"; do + stop_sites "$site" + done + ;; + 3) + for site in "${sites[@]}"; do + restart_sites "$site" + done + ;; + 4) + echo -e "${YELLOW}Are you sure you want to delete these sites?${NC}" + printf '%s\n' "${sites[@]}" + read -p "Confirm deletion (y/N): " confirm + if [[ "$confirm" =~ ^[Yy]$ ]]; then + for site in "${sites[@]}"; do + delete_sites "$site" + done + fi + show_interactive_menu + return + ;; + "b" | "B") + show_multi_site_menu "${sites[@]}" + return + ;; + "q" | "Q") + exit 0 + ;; + *) + echo -e "${RED}Invalid choice${NC}" + sleep 2 + show_bulk_action_menu "${sites[@]}" + ;; + esac + + show_multi_site_menu "${sites[@]}" +} + +show_site_details() { + local domain=$1 + local compose_file="${WORDPRESS_DIR}/${domain}/compose.yaml" + + echo -e "\n${BLUE}Site Details for ${GREEN}${domain}${NC}" + echo "----------------------------------------" + echo -e "Status: $(get_site_status "$compose_file")" + echo "Installation Directory: ${WORDPRESS_DIR}/${domain}" + echo -e "\nContainer Status:" + docker compose -f "$compose_file" ps + #echo -e "\nContainer Logs (last 5 lines):" + #docker compose -f "$compose_file" logs --tail=5 +} + +# Function to restart WordPress sites +restart_sites() { + local target="$1" + + if [ ! -d "$WORDPRESS_DIR" ]; then + echo "No WordPress installations found!" + exit 1 + fi + + case "$target" in + "all") + print_subheader "Restarting all containers..." + # Restart Caddy first + if [ -f "${CADDY_DIR}/compose.yaml" ]; then + print_info "Restarting Caddy reverse proxy..." + docker compose -f "${CADDY_DIR}/compose.yaml" restart + fi + + # Restart all WordPress installations + for site in "$WORDPRESS_DIR"/*; do + if [ -d "$site" ] && [ -f "$site/compose.yaml" ]; then + domain=$(basename "$site") + echo "Starting site: $domain" + docker compose -f "$site/compose.yaml" restart + fi + done + echo "All containers have been started." + ;; + + *) + # Restart specific site + WP_PROJECT_DIR="${WORDPRESS_DIR}/${target}" + if [ ! -d "$WP_PROJECT_DIR" ] || [ ! -f "$WP_PROJECT_DIR/compose.yaml" ]; then + echo "Error: Site '$target' not found" + exit 1 + fi + echo "Starting site: $target" + docker compose -f "$WP_PROJECT_DIR/compose.yaml" restart + echo "Site containers have been started." + ;; + esac +} diff --git a/lib/utils.sh b/src/lib/utils.sh similarity index 100% rename from lib/utils.sh rename to src/lib/utils.sh diff --git a/lib/validation.sh b/src/lib/validation.sh similarity index 100% rename from lib/validation.sh rename to src/lib/validation.sh diff --git a/lib/wordpress.sh b/src/lib/wordpress.sh similarity index 100% rename from lib/wordpress.sh rename to src/lib/wordpress.sh diff --git a/templates/caddy.template b/src/templates/caddy.template similarity index 100% rename from templates/caddy.template rename to src/templates/caddy.template diff --git a/templates/docker-compose.yml.template b/src/templates/docker-compose.yml.template similarity index 98% rename from templates/docker-compose.yml.template rename to src/templates/docker-compose.yml.template index 475bdaa..3d7e05b 100644 --- a/templates/docker-compose.yml.template +++ b/src/templates/docker-compose.yml.template @@ -44,6 +44,8 @@ services: - ${DOMAIN}_net - caddy_net +$PHPMYADMIN_BLOCK + networks: ${DOMAIN}_net: driver: bridge diff --git a/templates/wp-setup.sh.template b/src/templates/wp-setup.sh.template similarity index 100% rename from templates/wp-setup.sh.template rename to src/templates/wp-setup.sh.template