From 40edce0d1f6205310167c079723699503ff1c05b Mon Sep 17 00:00:00 2001 From: Anh Duc Le Date: Sat, 25 Oct 2025 11:29:00 +0700 Subject: [PATCH 1/6] Add mkcert.sh script for SSL certificate generation and management --- .gitignore | 3 +- bin/mkcert.sh | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 bin/mkcert.sh diff --git a/.gitignore b/.gitignore index 6e1823d..2fa1a32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ data latest.yml config -lsws/conf \ No newline at end of file +lsws/conf +certs \ No newline at end of file diff --git a/bin/mkcert.sh b/bin/mkcert.sh new file mode 100644 index 0000000..0906c1b --- /dev/null +++ b/bin/mkcert.sh @@ -0,0 +1,352 @@ +#!/usr/bin/env bash +DOMAIN='' +INSTALL='' +REMOVE='' +CONT_NAME='litespeed' +CERT_DIR='./certs' +EPACE=' ' + +echow(){ + FLAG=${1} + shift + echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" +} + +help_message(){ + echo -e "\033[1mUSAGE\033[0m" + echo "${EPACE}mkcert.sh [OPTIONS]" + echo "" + echo -e "\033[1mOPTIONS\033[0m" + echow '-D, --domain [DOMAIN_NAME]' + echo "${EPACE}${EPACE}Example: mkcert.sh --domain example.test" + echo "${EPACE}${EPACE}Will create certificate for example.test and www.example.test" + echow '-I, --install' + echo "${EPACE}${EPACE}Install mkcert on Windows (requires Chocolatey)" + echow '-R, --remove' + echo "${EPACE}${EPACE}Remove certificate for a specific domain" + echow '-H, --help' + echo "${EPACE}${EPACE}Display help and exit" + exit 0 +} + +check_input(){ + if [ -z "${1}" ]; then + help_message + fi +} + +domain_filter(){ + if [ -z "${1}" ]; then + echo "[X] Domain name is required!" + exit 1 + fi + DOMAIN="${1}" + DOMAIN="${DOMAIN#http://}" + DOMAIN="${DOMAIN#https://}" + DOMAIN="${DOMAIN#ftp://}" + DOMAIN="${DOMAIN%%/*}" +} + +www_domain(){ + CHECK_WWW=$(echo ${1} | cut -c1-4) + if [[ ${CHECK_WWW} == www. ]] ; then + DOMAIN=$(echo ${1} | cut -c 5-) + else + DOMAIN=${1} + fi + WWW_DOMAIN="www.${DOMAIN}" +} + +check_mkcert(){ + echo '[Start] Checking mkcert installation' + + # Try .exe first (for WSL/Windows) + if command -v mkcert.exe >/dev/null 2>&1; then + MKCERT_CMD="mkcert.exe" + echo -e "[O] mkcert is installed (using: mkcert.exe)" + elif command -v mkcert >/dev/null 2>&1; then + MKCERT_CMD="mkcert" + echo -e "[O] mkcert is installed (using: mkcert)" + else + echo "[X] mkcert is not installed!" + echo "[!] Please run: ./bin/mkcert.sh --install" + echo "[!] Or install manually: choco install mkcert" + exit 1 + fi + + echo '[End] Checking mkcert' +} + +install_mkcert(){ + echo '[Start] Installing mkcert' + + # Try Windows executable first (for WSL/Git Bash) + choco.exe --version > /dev/null 2>&1 + CHOCO_CHECK=$? + + # If .exe doesn't work, try without extension + if [ ${CHOCO_CHECK} != 0 ]; then + choco --version > /dev/null 2>&1 + CHOCO_CHECK=$? + fi + + if [ ${CHOCO_CHECK} != 0 ]; then + echo "[X] Chocolatey is not installed or not in PATH!" + echo "[!] Please install Chocolatey first: https://chocolatey.org/install" + echo "[!] After installation, restart your terminal" + exit 1 + fi + + echo "[O] Chocolatey is installed" + + # Check if mkcert already installed (try .exe first for WSL) + mkcert.exe -version > /dev/null 2>&1 + MKCERT_CHECK=$? + + if [ ${MKCERT_CHECK} != 0 ]; then + mkcert -version > /dev/null 2>&1 + MKCERT_CHECK=$? + fi + + if [ ${MKCERT_CHECK} = 0 ]; then + echo "[!] mkcert is already installed" + MKCERT_VERSION=$(mkcert.exe -version 2>&1 || mkcert -version 2>&1 | head -n 1) + echo "[!] Version: ${MKCERT_VERSION}" + echo "[!] Running mkcert -install to ensure local CA is configured..." + mkcert.exe -install || mkcert -install + echo '[End] Installing mkcert' + return 0 + fi + + echo "[!] Installing mkcert via Chocolatey..." + choco.exe install mkcert -y || choco install mkcert -y + + if [ ${?} = 0 ]; then + echo -e "[O] mkcert installed successfully" + echo "[!] Running mkcert -install to create local CA..." + mkcert.exe -install || mkcert -install + echo '[End] Installing mkcert' + else + echo "[X] Failed to install mkcert" + exit 1 + fi +} + +create_cert_dir(){ + if [ ! -d "${CERT_DIR}" ]; then + echo "[!] Creating certificate directory: ${CERT_DIR}" + mkdir -p "${CERT_DIR}" + fi +} + +generate_cert(){ + echo '[Start] Generating SSL certificate' + domain_filter "${DOMAIN}" + www_domain "${DOMAIN}" + + create_cert_dir + + cd "${CERT_DIR}" + + echo -e "[!] Generating certificate for: \033[32m${DOMAIN}\033[0m and \033[32m${WWW_DOMAIN}\033[0m" + + # Use the detected mkcert command + ${MKCERT_CMD} "${DOMAIN}" "${WWW_DOMAIN}" + + if [ ${?} = 0 ]; then + echo -e "[O] Certificate generated successfully" + + # Rename files to standard format + CERT_FILE="${DOMAIN}+1.pem" + KEY_FILE="${DOMAIN}+1-key.pem" + + if [ -f "${CERT_FILE}" ] && [ -f "${KEY_FILE}" ]; then + echo "[!] Certificate files:" + echo "${EPACE}Cert: ${CERT_DIR}/${CERT_FILE}" + echo "${EPACE}Key: ${CERT_DIR}/${KEY_FILE}" + fi + else + echo "[X] Failed to generate certificate" + exit 1 + fi + + cd - > /dev/null + echo '[End] Generating SSL certificate' +} + +configure_litespeed(){ + echo '[Start] Configuring OpenLiteSpeed' + + CERT_FILE="${DOMAIN}+1.pem" + KEY_FILE="${DOMAIN}+1-key.pem" + + # Check if certificate files exist + if [ ! -f "${CERT_DIR}/${CERT_FILE}" ] || [ ! -f "${CERT_DIR}/${KEY_FILE}" ]; then + echo "[X] Certificate files not found!" + exit 1 + fi + + echo "[!] Configuring SSL for domain: ${DOMAIN}" + + LSWS_CONF_DIR="/usr/local/lsws/conf" + HTTPD_CONF="${LSWS_CONF_DIR}/httpd_config.conf" + + # Copy certificates to container + docker compose cp "${CERT_DIR}/${CERT_FILE}" ${CONT_NAME}:${LSWS_CONF_DIR}/cert/ + docker compose cp "${CERT_DIR}/${KEY_FILE}" ${CONT_NAME}:${LSWS_CONF_DIR}/cert/ + + echo "[O] Certificates copied to container" + + # Backup config + docker compose exec -T ${CONT_NAME} bash -c "cp ${HTTPD_CONF} ${HTTPD_CONF}.backup.\$(date +%Y%m%d_%H%M%S)" + echo "[O] Config backed up" + + # Kiểm tra xem đã có SSL Listener chưa + HAS_SSL=$(docker compose exec -T ${CONT_NAME} bash -c "grep -c 'listener Default HTTPS' ${HTTPD_CONF}" | tr -d '\r') + + if [ "${HAS_SSL}" = "0" ]; then + echo '[!] Creating new SSL Listener...' + + # Tạo SSL listener mới + docker compose exec -T ${CONT_NAME} bash -c "cat >> ${HTTPD_CONF} <<'LISTENER_EOF' + +listener Default HTTPS { + address *:443 + secure 1 + keyFile ${LSWS_CONF_DIR}/cert/${KEY_FILE} + certFile ${LSWS_CONF_DIR}/cert/${CERT_FILE} + certChain 1 + sslProtocol 24 + enableSpdy 15 + map ${DOMAIN} ${DOMAIN} +} +LISTENER_EOF +" + echo '[O] SSL Listener created' + else + echo '[!] SSL Listener exists, updating...' + + # Cập nhật cert paths + docker compose exec -T ${CONT_NAME} bash -c " + sed -i '/listener Default HTTPS/,/^}/s|keyFile.*| keyFile ${LSWS_CONF_DIR}/cert/${KEY_FILE}|' ${HTTPD_CONF} + sed -i '/listener Default HTTPS/,/^}/s|certFile.*| certFile ${LSWS_CONF_DIR}/cert/${CERT_FILE}|' ${HTTPD_CONF} + " + echo '[O] Certificate paths updated' + + # Kiểm tra xem domain đã được map chưa + HAS_MAPPING=$(docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF} | grep -c 'map.*${DOMAIN}'" | tr -d '\r') + + if [ "${HAS_MAPPING}" = "0" ]; then + # Thêm mapping + docker compose exec -T ${CONT_NAME} bash -c " + sed -i '/listener Default HTTPS/,/^}/ { + /^}/i\ map ${DOMAIN} ${DOMAIN} + }' ${HTTPD_CONF} + " + echo '[O] Domain mapping added to SSL Listener' + else + echo '[!] Domain mapping already exists' + fi + fi + + echo "" + echo "[!] Current SSL Listener configuration:" + docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF}" + echo "" + + if [ ${?} = 0 ]; then + echo -e "[O] SSL configured for: \033[32m${DOMAIN}\033[0m" + echo "[!] Restarting OpenLiteSpeed..." + lsws_restart + else + echo "[X] Failed to configure SSL" + exit 1 + fi + + echo '[End] Configuring OpenLiteSpeed' +} + +lsws_restart(){ + docker compose exec ${CONT_NAME} su -c '/usr/local/lsws/bin/lswsctrl restart >/dev/null' + if [ ${?} = 0 ]; then + echo -e "[O] OpenLiteSpeed restarted successfully" + else + echo "[X] Failed to restart OpenLiteSpeed" + fi +} + +remove_cert(){ + echo '[Start] Removing SSL certificate' + domain_filter "${DOMAIN}" + + CERT_FILE="${DOMAIN}+1.pem" + KEY_FILE="${DOMAIN}+1-key.pem" + + if [ -f "${CERT_DIR}/${CERT_FILE}" ]; then + rm "${CERT_DIR}/${CERT_FILE}" + echo -e "[O] Removed: ${CERT_DIR}/${CERT_FILE}" + fi + + if [ -f "${CERT_DIR}/${KEY_FILE}" ]; then + rm "${CERT_DIR}/${KEY_FILE}" + echo -e "[O] Removed: ${CERT_DIR}/${KEY_FILE}" + fi + + # Remove SSL listener config + SSL_LISTENER="/usr/local/lsws/conf/cert/${DOMAIN}.xml" + docker compose exec ${CONT_NAME} bash -c "[ -f ${SSL_LISTENER} ] && rm ${SSL_LISTENER}" + + echo '[End] Removing SSL certificate' + lsws_restart +} + +main(){ + if [ "${INSTALL}" = 'true' ]; then + install_mkcert + exit 0 + fi + + if [ "${REMOVE}" = 'true' ]; then + remove_cert + exit 0 + fi + + check_mkcert + generate_cert + configure_litespeed + + echo "" + echo -e "\033[1m[SUCCESS] SSL certificate setup completed!\033[0m" + echo "" + echo "Next steps:" + echo "1. Add '${DOMAIN}' to your Windows hosts file (C:\Windows\System32\drivers\etc\hosts)" + echo " Example: 127.0.0.1 ${DOMAIN} ${WWW_DOMAIN}" + echo "2. Configure your virtual host to use SSL-${DOMAIN} listener" + echo "3. Access https://${DOMAIN} in your browser" +} + +check_input ${1} +while [ ! -z "${1}" ]; do + case ${1} in + -[hH] | -help | --help) + help_message + ;; + -[dD] | -domain | --domain) + shift + check_input "${1}" + DOMAIN="${1}" + ;; + -[iI] | --install) + INSTALL=true + ;; + -[rR] | --remove) + REMOVE=true + ;; + *) + help_message + ;; + esac + shift +done + +main \ No newline at end of file From 8e480f37684fc3eedda61eca95aca2486dd0a9b4 Mon Sep 17 00:00:00 2001 From: Anh Duc Le Date: Tue, 4 Nov 2025 17:19:39 +0700 Subject: [PATCH 2/6] Enhance mkcert.sh script with improved error handling, added test option, and refined SSL configuration steps --- bin/mkcert.sh | 370 ++++++++++++++++++++++++++++---------------------- 1 file changed, 209 insertions(+), 161 deletions(-) diff --git a/bin/mkcert.sh b/bin/mkcert.sh index 0906c1b..706f37f 100644 --- a/bin/mkcert.sh +++ b/bin/mkcert.sh @@ -2,16 +2,19 @@ DOMAIN='' INSTALL='' REMOVE='' +TEST='' CONT_NAME='litespeed' CERT_DIR='./certs' EPACE=' ' +# Function to print messages with a specific format echow(){ FLAG=${1} shift echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" } +# Function to display help message help_message(){ echo -e "\033[1mUSAGE\033[0m" echo "${EPACE}mkcert.sh [OPTIONS]" @@ -29,12 +32,14 @@ help_message(){ exit 0 } +# Function to check input parameters check_input(){ if [ -z "${1}" ]; then help_message fi } +# Function to filter and extract domain name domain_filter(){ if [ -z "${1}" ]; then echo "[X] Domain name is required!" @@ -47,6 +52,7 @@ domain_filter(){ DOMAIN="${DOMAIN%%/*}" } +# Function to get www version of the domain www_domain(){ CHECK_WWW=$(echo ${1} | cut -c1-4) if [[ ${CHECK_WWW} == www. ]] ; then @@ -57,81 +63,73 @@ www_domain(){ WWW_DOMAIN="www.${DOMAIN}" } -check_mkcert(){ - echo '[Start] Checking mkcert installation' - - # Try .exe first (for WSL/Windows) - if command -v mkcert.exe >/dev/null 2>&1; then - MKCERT_CMD="mkcert.exe" - echo -e "[O] mkcert is installed (using: mkcert.exe)" - elif command -v mkcert >/dev/null 2>&1; then - MKCERT_CMD="mkcert" - echo -e "[O] mkcert is installed (using: mkcert)" +# Function to check if mkcert is installed +check_mkcert() { + echo "[Start] Checking mkcert installation..." + + # Detect mkcert command (Windows supported, other OS can be added later) + if MKCERT_CMD=$(command -v mkcert.exe 2>/dev/null || command -v mkcert 2>/dev/null); then + echo "[✔] mkcert found at: ${MKCERT_CMD}" else - echo "[X] mkcert is not installed!" - echo "[!] Please run: ./bin/mkcert.sh --install" - echo "[!] Or install manually: choco install mkcert" + echo "[✖] mkcert not found!" + echo "→ Please run 'bash bin/mkcert.sh --install' or install it manually." + echo " Windows: choco install mkcert" + echo " (Linux/macOS support can be added here later)" exit 1 fi - - echo '[End] Checking mkcert' + + echo "[End] mkcert check completed." } -install_mkcert(){ - echo '[Start] Installing mkcert' - - # Try Windows executable first (for WSL/Git Bash) - choco.exe --version > /dev/null 2>&1 - CHOCO_CHECK=$? - - # If .exe doesn't work, try without extension - if [ ${CHOCO_CHECK} != 0 ]; then - choco --version > /dev/null 2>&1 - CHOCO_CHECK=$? - fi - - if [ ${CHOCO_CHECK} != 0 ]; then - echo "[X] Chocolatey is not installed or not in PATH!" - echo "[!] Please install Chocolatey first: https://chocolatey.org/install" - echo "[!] After installation, restart your terminal" - exit 1 - fi - - echo "[O] Chocolatey is installed" - - # Check if mkcert already installed (try .exe first for WSL) - mkcert.exe -version > /dev/null 2>&1 - MKCERT_CHECK=$? - - if [ ${MKCERT_CHECK} != 0 ]; then - mkcert -version > /dev/null 2>&1 - MKCERT_CHECK=$? - fi - - if [ ${MKCERT_CHECK} = 0 ]; then - echo "[!] mkcert is already installed" - MKCERT_VERSION=$(mkcert.exe -version 2>&1 || mkcert -version 2>&1 | head -n 1) - echo "[!] Version: ${MKCERT_VERSION}" - echo "[!] Running mkcert -install to ensure local CA is configured..." - mkcert.exe -install || mkcert -install - echo '[End] Installing mkcert' +# Function to install mkcert on Windows using Chocolatey +# ------------------------------------------------------------------------------ +# 💡 Notes for contributors: +# - This script currently supports Windows / WSL / Git Bash only. +# - To extend for macOS or Linux, add logic below: +# macOS: brew install mkcert nss +# Ubuntu: sudo apt install mkcert libnss3-tools +# Fedora: sudo dnf install mkcert nss-tools +# ------------------------------------------------------------------------------ +install_mkcert() { + echo "[Start] Installing mkcert..." + + # 1️⃣ Check if mkcert is already installed + if command -v mkcert.exe >/dev/null 2>&1 || command -v mkcert >/dev/null 2>&1; then + echo "[O] mkcert is already installed." + echo "[!] Ensuring local CA is installed..." + # Ensure local CA is installed + (mkcert.exe -install || mkcert -install) + echo "[O] Local CA configured." + echo "[End] mkcert installation check complete." return 0 fi - - echo "[!] Installing mkcert via Chocolatey..." - choco.exe install mkcert -y || choco install mkcert -y - - if [ ${?} = 0 ]; then - echo -e "[O] mkcert installed successfully" - echo "[!] Running mkcert -install to create local CA..." - mkcert.exe -install || mkcert -install - echo '[End] Installing mkcert' + + # 2️⃣ Check if Chocolatey is available + if ! command -v choco.exe >/dev/null 2>&1 && ! command -v choco >/dev/null 2>&1; then + echo "[X] Chocolatey not found!" + echo "→ Please install Chocolatey from: https://chocolatey.org/install" + echo "→ After installation, restart your terminal and re-run this script." + exit 1 + fi + + # 3️⃣ Install mkcert using Chocolatey + echo "[*] Installing mkcert via Chocolatey..." + (choco.exe install mkcert -y || choco install mkcert -y) + + # 4️⃣ Verify installation result + if command -v mkcert.exe >/dev/null 2>&1 || command -v mkcert >/dev/null 2>&1; then + echo "[O] mkcert installed successfully." + echo "[!] Creating local CA..." + (mkcert.exe -install || mkcert -install) + echo "[O] Local CA configured." + echo "[End] mkcert installation complete." else - echo "[X] Failed to install mkcert" + echo "[X] mkcert installation failed!" exit 1 fi } +# Function to create certificate directory if it doesn't exist create_cert_dir(){ if [ ! -d "${CERT_DIR}" ]; then echo "[!] Creating certificate directory: ${CERT_DIR}" @@ -139,6 +137,7 @@ create_cert_dir(){ fi } +# Function to generate SSL certificate using mkcert generate_cert(){ echo '[Start] Generating SSL certificate' domain_filter "${DOMAIN}" @@ -146,27 +145,24 @@ generate_cert(){ create_cert_dir - cd "${CERT_DIR}" + mkdir -p "${CERT_DIR}/${DOMAIN}" + + cd "${CERT_DIR}/${DOMAIN}" echo -e "[!] Generating certificate for: \033[32m${DOMAIN}\033[0m and \033[32m${WWW_DOMAIN}\033[0m" # Use the detected mkcert command - ${MKCERT_CMD} "${DOMAIN}" "${WWW_DOMAIN}" + ${MKCERT_CMD} -key-file key.pem -cert-file cert.pem "${DOMAIN}" "${WWW_DOMAIN}" >/dev/null 2>&1 if [ ${?} = 0 ]; then echo -e "[O] Certificate generated successfully" - - # Rename files to standard format - CERT_FILE="${DOMAIN}+1.pem" - KEY_FILE="${DOMAIN}+1-key.pem" - - if [ -f "${CERT_FILE}" ] && [ -f "${KEY_FILE}" ]; then - echo "[!] Certificate files:" - echo "${EPACE}Cert: ${CERT_DIR}/${CERT_FILE}" - echo "${EPACE}Key: ${CERT_DIR}/${KEY_FILE}" - fi + echo "[!] Certificate files:" + echo "${EPACE}Cert: ${CERT_DIR}/${DOMAIN}/cert.pem" + echo "${EPACE}Key: ${CERT_DIR}/${DOMAIN}/key.pem" else echo "[X] Failed to generate certificate" + cd ../.. + rm -rf "${CERT_DIR}/${DOMAIN}" exit 1 fi @@ -175,99 +171,87 @@ generate_cert(){ } configure_litespeed(){ - echo '[Start] Configuring OpenLiteSpeed' + echo '[Start] Configuring OpenLiteSpeed for domain' - CERT_FILE="${DOMAIN}+1.pem" - KEY_FILE="${DOMAIN}+1-key.pem" + local cert_host_path="${CERT_DIR}/${DOMAIN}" # Check if certificate files exist - if [ ! -f "${CERT_DIR}/${CERT_FILE}" ] || [ ! -f "${CERT_DIR}/${KEY_FILE}" ]; then - echo "[X] Certificate files not found!" + if [ ! -f "${cert_host_path}/cert.pem" ] || [ ! -f "${cert_host_path}/key.pem" ]; then + echo "[X] Certificate files not found on host at: ${cert_host_path}" exit 1 fi echo "[!] Configuring SSL for domain: ${DOMAIN}" - LSWS_CONF_DIR="/usr/local/lsws/conf" - HTTPD_CONF="${LSWS_CONF_DIR}/httpd_config.conf" - - # Copy certificates to container - docker compose cp "${CERT_DIR}/${CERT_FILE}" ${CONT_NAME}:${LSWS_CONF_DIR}/cert/ - docker compose cp "${CERT_DIR}/${KEY_FILE}" ${CONT_NAME}:${LSWS_CONF_DIR}/cert/ - - echo "[O] Certificates copied to container" - - # Backup config - docker compose exec -T ${CONT_NAME} bash -c "cp ${HTTPD_CONF} ${HTTPD_CONF}.backup.\$(date +%Y%m%d_%H%M%S)" - echo "[O] Config backed up" - - # Kiểm tra xem đã có SSL Listener chưa - HAS_SSL=$(docker compose exec -T ${CONT_NAME} bash -c "grep -c 'listener Default HTTPS' ${HTTPD_CONF}" | tr -d '\r') - - if [ "${HAS_SSL}" = "0" ]; then - echo '[!] Creating new SSL Listener...' - - # Tạo SSL listener mới - docker compose exec -T ${CONT_NAME} bash -c "cat >> ${HTTPD_CONF} <<'LISTENER_EOF' + # Define paths inside the container + local lsws_conf_dir="/usr/local/lsws/conf" + local httpd_conf="${lsws_conf_dir}/httpd_config.conf" + local vhosts_dir="${lsws_conf_dir}/vhosts" + local cert_container_path="${lsws_conf_dir}/cert/${DOMAIN}" -listener Default HTTPS { - address *:443 - secure 1 - keyFile ${LSWS_CONF_DIR}/cert/${KEY_FILE} - certFile ${LSWS_CONF_DIR}/cert/${CERT_FILE} - certChain 1 - sslProtocol 24 - enableSpdy 15 - map ${DOMAIN} ${DOMAIN} -} -LISTENER_EOF -" - echo '[O] SSL Listener created' - else - echo '[!] SSL Listener exists, updating...' - - # Cập nhật cert paths - docker compose exec -T ${CONT_NAME} bash -c " - sed -i '/listener Default HTTPS/,/^}/s|keyFile.*| keyFile ${LSWS_CONF_DIR}/cert/${KEY_FILE}|' ${HTTPD_CONF} - sed -i '/listener Default HTTPS/,/^}/s|certFile.*| certFile ${LSWS_CONF_DIR}/cert/${CERT_FILE}|' ${HTTPD_CONF} - " - echo '[O] Certificate paths updated' - - # Kiểm tra xem domain đã được map chưa - HAS_MAPPING=$(docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF} | grep -c 'map.*${DOMAIN}'" | tr -d '\r') - - if [ "${HAS_MAPPING}" = "0" ]; then - # Thêm mapping - docker compose exec -T ${CONT_NAME} bash -c " - sed -i '/listener Default HTTPS/,/^}/ { - /^}/i\ map ${DOMAIN} ${DOMAIN} - }' ${HTTPD_CONF} - " - echo '[O] Domain mapping added to SSL Listener' - else - echo '[!] Domain mapping already exists' - fi + # Find the Virtual Host name mapped to the domain + echo "[!] Searching for Virtual Host mapped to '${DOMAIN}'..." + local vhost_name=$(docker compose exec -T ${CONT_NAME} bash -c "grep -B 2 'vhDomain.*${DOMAIN}' ${httpd_conf} | grep 'member' | awk '{print \$2}'" | tr -d '\r') + + if [ -z "${vhost_name}" ]; then + echo "[X] No Virtual Host found for domain '${DOMAIN}' in ${httpd_conf}." + echo "[!] Please add this domain to your environment first (e.g., using the 'domain' script)." + exit 1 fi - - echo "" - echo "[!] Current SSL Listener configuration:" - docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF}" - echo "" + echo "[O] Found Virtual Host member name: '${vhost_name}'" + + local vhconf_path="${vhosts_dir}/${vhost_name}/vhconf.conf" + + # Copy certificate files into the container + echo "[!] Copying certificates to container..." + docker compose exec -T ${CONT_NAME} bash -c "mkdir -p ${cert_container_path}" + docker compose cp "${cert_host_path}/cert.pem" "${CONT_NAME}:${cert_container_path}/cert.pem" + docker compose cp "${cert_host_path}/key.pem" "${CONT_NAME}:${cert_container_path}/key.pem" + echo "[O] Certificates copied to container at: ${cert_container_path}" + + # Modify the vhost configuration to enable SSL + echo "[!] Modifying vhost config: ${vhconf_path}" + docker compose exec -T ${CONT_NAME} bash -c " + # Create vhconf.conf if it doesn't exist + if [ ! -f ${vhconf_path} ]; then + mkdir -p \$(dirname ${vhconf_path}) + touch ${vhconf_path} + echo '[O] Created missing vhconf.conf file.' + fi + + # Backup vhconf.conf + cp ${vhconf_path} ${vhconf_path}.backup.\$(date +%Y%m%d_%H%M%S) + + # Remove existing vhssl block if present to avoid duplicates + sed -i '/vhssl[[:space:]]*{/,/}/d' ${vhconf_path} + sed -i '/^virtualHostConfig[[:space:]]*{/,/}/d' ${vhconf_path} + + # Add new SSL configuration inside a virtualHostConfig block + cat >> ${vhconf_path} </dev/null' + if [ ${?} = 0 ]; then echo -e "[O] OpenLiteSpeed restarted successfully" else @@ -281,23 +265,87 @@ remove_cert(){ CERT_FILE="${DOMAIN}+1.pem" KEY_FILE="${DOMAIN}+1-key.pem" + LSWS_CONF_DIR="/usr/local/lsws/conf" + HTTPD_CONF="${LSWS_CONF_DIR}/httpd_config.conf" + # 1. Xóa chứng chỉ trên host if [ -f "${CERT_DIR}/${CERT_FILE}" ]; then rm "${CERT_DIR}/${CERT_FILE}" echo -e "[O] Removed: ${CERT_DIR}/${CERT_FILE}" + else + echo "[!] Certificate file not found: ${CERT_DIR}/${CERT_FILE}" fi if [ -f "${CERT_DIR}/${KEY_FILE}" ]; then rm "${CERT_DIR}/${KEY_FILE}" echo -e "[O] Removed: ${CERT_DIR}/${KEY_FILE}" + else + echo "[!] Key file not found: ${CERT_DIR}/${KEY_FILE}" fi - # Remove SSL listener config - SSL_LISTENER="/usr/local/lsws/conf/cert/${DOMAIN}.xml" - docker compose exec ${CONT_NAME} bash -c "[ -f ${SSL_LISTENER} ] && rm ${SSL_LISTENER}" + # 2. Xóa chứng chỉ trong container + docker compose exec -T ${CONT_NAME} bash -c " + if [ -f ${LSWS_CONF_DIR}/cert/${CERT_FILE} ]; then + rm ${LSWS_CONF_DIR}/cert/${CERT_FILE} + echo '[O] Removed certificate from container' + fi + + if [ -f ${LSWS_CONF_DIR}/cert/${KEY_FILE} ]; then + rm ${LSWS_CONF_DIR}/cert/${KEY_FILE} + echo '[O] Removed key from container' + fi + " - echo '[End] Removing SSL certificate' + # 3. Xóa domain mapping khỏi SSL Listener + echo "[!] Removing domain mapping from SSL Listener..." + + HAS_MAPPING=$(docker compose exec -T ${CONT_NAME} bash -c "grep -c 'map.*${DOMAIN}' ${HTTPD_CONF}" | tr -d '\r') + + if [ "${HAS_MAPPING}" != "0" ]; then + # Backup trước khi xóa + docker compose exec -T ${CONT_NAME} bash -c "cp ${HTTPD_CONF} ${HTTPD_CONF}.backup.\$(date +%Y%m%d_%H%M%S)" + + # Xóa dòng map của domain + docker compose exec -T ${CONT_NAME} bash -c " + sed -i '/listener Default HTTPS/,/^}/ { + /map.*${DOMAIN}/d + }' ${HTTPD_CONF} + " + echo -e "[O] Removed domain mapping for: \033[32m${DOMAIN}\033[0m" + + # Kiểm tra xem còn domain nào được map không + REMAINING_MAPS=$(docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF} | grep -c 'map'" | tr -d '\r') + + if [ "${REMAINING_MAPS}" = "0" ]; then + echo "[!] No more domains mapped to SSL Listener" + echo "[?] Do you want to remove the entire SSL Listener? (y/N)" + read -r REMOVE_LISTENER + + if [[ "${REMOVE_LISTENER}" =~ ^[Yy]$ ]]; then + docker compose exec -T ${CONT_NAME} bash -c " + sed -i '/listener Default HTTPS {/,/^}/d' ${HTTPD_CONF} + " + echo "[O] SSL Listener removed" + fi + fi + else + echo "[!] Domain mapping not found in SSL Listener" + fi + + # 4. Hiển thị cấu hình hiện tại + echo "" + echo "[!] Current SSL Listener configuration:" + docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF}" || echo "[!] No SSL Listener found" + echo "" + + # 5. Restart LiteSpeed + echo "[!] Restarting OpenLiteSpeed..." lsws_restart + + echo "" + echo -e "\033[1m[SUCCESS] Certificate removed for domain: ${DOMAIN}\033[0m" + echo "" + echo '[End] Removing SSL certificate' } main(){ @@ -310,21 +358,18 @@ main(){ remove_cert exit 0 fi - + + if [ "${TEST}" = 'true' ]; then + check_mkcert + exit 0 + fi + check_mkcert generate_cert configure_litespeed - - echo "" - echo -e "\033[1m[SUCCESS] SSL certificate setup completed!\033[0m" - echo "" - echo "Next steps:" - echo "1. Add '${DOMAIN}' to your Windows hosts file (C:\Windows\System32\drivers\etc\hosts)" - echo " Example: 127.0.0.1 ${DOMAIN} ${WWW_DOMAIN}" - echo "2. Configure your virtual host to use SSL-${DOMAIN} listener" - echo "3. Access https://${DOMAIN} in your browser" } +# Parse command-line arguments check_input ${1} while [ ! -z "${1}" ]; do case ${1} in @@ -342,6 +387,9 @@ while [ ! -z "${1}" ]; do -[rR] | --remove) REMOVE=true ;; + -[tT] | --test) + TEST=true + ;; *) help_message ;; From 4a4ced1a28a0d487d606c43141132a10b5d563a9 Mon Sep 17 00:00:00 2001 From: Anh Duc Le Date: Wed, 5 Nov 2025 10:39:08 +0700 Subject: [PATCH 3/6] Add domain verification and local SSL configuration to mkcert.sh --- bin/mkcert.sh | 354 +++++++++++++++++++++++++++++++------------------- 1 file changed, 219 insertions(+), 135 deletions(-) diff --git a/bin/mkcert.sh b/bin/mkcert.sh index 706f37f..45f072b 100644 --- a/bin/mkcert.sh +++ b/bin/mkcert.sh @@ -2,7 +2,6 @@ DOMAIN='' INSTALL='' REMOVE='' -TEST='' CONT_NAME='litespeed' CERT_DIR='./certs' EPACE=' ' @@ -137,10 +136,27 @@ create_cert_dir(){ fi } +# Function to verify if domain has been added (by checking document root existence) +domain_verify(){ + local domain="${1}" + local doc_path="/var/www/vhosts/${domain}/html" + + echo "[!] Checking if domain '${domain}' has been added..." + + if docker compose exec -T ${CONT_NAME} bash -c "[ -d ${doc_path} ]" 2>/dev/null; then + echo -e "[O] Domain \033[32m${domain}\033[0m exists (document root found)" + return 0 + else + echo -e "[X] Domain \033[31m${domain}\033[0m has NOT been added yet!" + echo "[!] Document root not found: ${doc_path}" + echo "[!] Please add this domain first using: bash bin/domain.sh -a ${domain}" + exit 1 + fi +} + # Function to generate SSL certificate using mkcert generate_cert(){ echo '[Start] Generating SSL certificate' - domain_filter "${DOMAIN}" www_domain "${DOMAIN}" create_cert_dir @@ -170,8 +186,78 @@ generate_cert(){ echo '[End] Generating SSL certificate' } +# Function to create docker-local.conf template for local development +create_local_template(){ + echo '[Start] Creating docker-local.conf template' + + local source_file="/usr/local/lsws/conf/templates/docker.conf" + local dest_file="/usr/local/lsws/conf/templates/docker-local.conf" + + # Check if template file already exists + if docker compose exec -T ${CONT_NAME} bash -c "[ -f ${dest_file} ]" 2>/dev/null; then + echo "[i] Template file already exists: ${dest_file}" + echo '[End] Creating docker-local.conf template' + return 0 + fi + + # Copy and modify template file in a single command + docker compose exec -T ${CONT_NAME} bash -c " + # Copy template file + cp ${source_file} ${dest_file} + + # Remove old vhssl block and last closing brace + sed -i '/^ vhssl {/,/^ }/d; \$d' ${dest_file} + + # Append new vhssl configuration + cat >> ${dest_file} <<'VHSSL_EOF' + vhssl { + keyFile /usr/local/lsws/conf/cert/\$VH_NAME/key.pem + certFile /usr/local/lsws/conf/cert/\$VH_NAME/cert.pem + certChain 1 + } +} +VHSSL_EOF + + # Fix ownership and permissions + chown nobody:nogroup ${dest_file} 2>/dev/null || chown lsadm:lsadm ${dest_file} + chmod 644 ${dest_file} + " + + echo -e "[O] Template \033[32mdocker-local.conf\033[0m created successfully!" + echo -e " SSL certificates path: /usr/local/lsws/conf/cert/\$VH_NAME/" + echo '[End] Creating docker-local.conf template' +} + +# Function to register dockerLocal vhTemplate in httpd_config.conf +register_local_template() { + echo '[Start] Registering vhTemplate: dockerLocal' + + local config_file="/usr/local/lsws/conf/httpd_config.conf" + local template_name="dockerLocal" + local template_path="conf/templates/docker-local.conf" + + docker compose exec -T ${CONT_NAME} bash -c " + if ! grep -q 'vhTemplate ${template_name} {' ${config_file}; then + cat >> ${config_file} <> ${vhconf_path} </dev/null' @@ -259,112 +436,22 @@ lsws_restart() { fi } -remove_cert(){ - echo '[Start] Removing SSL certificate' - domain_filter "${DOMAIN}" - - CERT_FILE="${DOMAIN}+1.pem" - KEY_FILE="${DOMAIN}+1-key.pem" - LSWS_CONF_DIR="/usr/local/lsws/conf" - HTTPD_CONF="${LSWS_CONF_DIR}/httpd_config.conf" - - # 1. Xóa chứng chỉ trên host - if [ -f "${CERT_DIR}/${CERT_FILE}" ]; then - rm "${CERT_DIR}/${CERT_FILE}" - echo -e "[O] Removed: ${CERT_DIR}/${CERT_FILE}" - else - echo "[!] Certificate file not found: ${CERT_DIR}/${CERT_FILE}" - fi - - if [ -f "${CERT_DIR}/${KEY_FILE}" ]; then - rm "${CERT_DIR}/${KEY_FILE}" - echo -e "[O] Removed: ${CERT_DIR}/${KEY_FILE}" - else - echo "[!] Key file not found: ${CERT_DIR}/${KEY_FILE}" - fi - - # 2. Xóa chứng chỉ trong container - docker compose exec -T ${CONT_NAME} bash -c " - if [ -f ${LSWS_CONF_DIR}/cert/${CERT_FILE} ]; then - rm ${LSWS_CONF_DIR}/cert/${CERT_FILE} - echo '[O] Removed certificate from container' - fi - - if [ -f ${LSWS_CONF_DIR}/cert/${KEY_FILE} ]; then - rm ${LSWS_CONF_DIR}/cert/${KEY_FILE} - echo '[O] Removed key from container' - fi - " - - # 3. Xóa domain mapping khỏi SSL Listener - echo "[!] Removing domain mapping from SSL Listener..." - - HAS_MAPPING=$(docker compose exec -T ${CONT_NAME} bash -c "grep -c 'map.*${DOMAIN}' ${HTTPD_CONF}" | tr -d '\r') - - if [ "${HAS_MAPPING}" != "0" ]; then - # Backup trước khi xóa - docker compose exec -T ${CONT_NAME} bash -c "cp ${HTTPD_CONF} ${HTTPD_CONF}.backup.\$(date +%Y%m%d_%H%M%S)" - - # Xóa dòng map của domain - docker compose exec -T ${CONT_NAME} bash -c " - sed -i '/listener Default HTTPS/,/^}/ { - /map.*${DOMAIN}/d - }' ${HTTPD_CONF} - " - echo -e "[O] Removed domain mapping for: \033[32m${DOMAIN}\033[0m" - - # Kiểm tra xem còn domain nào được map không - REMAINING_MAPS=$(docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF} | grep -c 'map'" | tr -d '\r') - - if [ "${REMAINING_MAPS}" = "0" ]; then - echo "[!] No more domains mapped to SSL Listener" - echo "[?] Do you want to remove the entire SSL Listener? (y/N)" - read -r REMOVE_LISTENER - - if [[ "${REMOVE_LISTENER}" =~ ^[Yy]$ ]]; then - docker compose exec -T ${CONT_NAME} bash -c " - sed -i '/listener Default HTTPS {/,/^}/d' ${HTTPD_CONF} - " - echo "[O] SSL Listener removed" - fi - fi - else - echo "[!] Domain mapping not found in SSL Listener" - fi - - # 4. Hiển thị cấu hình hiện tại - echo "" - echo "[!] Current SSL Listener configuration:" - docker compose exec -T ${CONT_NAME} bash -c "grep -A 15 'listener Default HTTPS' ${HTTPD_CONF}" || echo "[!] No SSL Listener found" - echo "" - - # 5. Restart LiteSpeed - echo "[!] Restarting OpenLiteSpeed..." - lsws_restart - - echo "" - echo -e "\033[1m[SUCCESS] Certificate removed for domain: ${DOMAIN}\033[0m" - echo "" - echo '[End] Removing SSL certificate' -} - +# Main function to orchestrate the script operations main(){ if [ "${INSTALL}" = 'true' ]; then install_mkcert exit 0 fi - + + domain_filter "${DOMAIN}" + if [ "${REMOVE}" = 'true' ]; then remove_cert exit 0 fi - if [ "${TEST}" = 'true' ]; then - check_mkcert - exit 0 - fi - check_mkcert + domain_verify "${DOMAIN}" generate_cert configure_litespeed } @@ -387,9 +474,6 @@ while [ ! -z "${1}" ]; do -[rR] | --remove) REMOVE=true ;; - -[tT] | --test) - TEST=true - ;; *) help_message ;; From 08f2b45f5a8b64f27150e04cc3e736ef314d5ccf Mon Sep 17 00:00:00 2001 From: Anh Duc Le Date: Wed, 5 Nov 2025 10:48:38 +0700 Subject: [PATCH 4/6] Refactor installation steps in mkcert.sh for clarity and consistency --- bin/mkcert.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/mkcert.sh b/bin/mkcert.sh index 45f072b..99669f8 100644 --- a/bin/mkcert.sh +++ b/bin/mkcert.sh @@ -92,7 +92,7 @@ check_mkcert() { install_mkcert() { echo "[Start] Installing mkcert..." - # 1️⃣ Check if mkcert is already installed + # Step 1 Check if mkcert is already installed if command -v mkcert.exe >/dev/null 2>&1 || command -v mkcert >/dev/null 2>&1; then echo "[O] mkcert is already installed." echo "[!] Ensuring local CA is installed..." @@ -103,7 +103,7 @@ install_mkcert() { return 0 fi - # 2️⃣ Check if Chocolatey is available + # Step 2 Check if Chocolatey is available if ! command -v choco.exe >/dev/null 2>&1 && ! command -v choco >/dev/null 2>&1; then echo "[X] Chocolatey not found!" echo "→ Please install Chocolatey from: https://chocolatey.org/install" @@ -111,11 +111,11 @@ install_mkcert() { exit 1 fi - # 3️⃣ Install mkcert using Chocolatey + # Step 3 Install mkcert using Chocolatey echo "[*] Installing mkcert via Chocolatey..." (choco.exe install mkcert -y || choco install mkcert -y) - # 4️⃣ Verify installation result + # Step 4 Verify installation result if command -v mkcert.exe >/dev/null 2>&1 || command -v mkcert >/dev/null 2>&1; then echo "[O] mkcert installed successfully." echo "[!] Creating local CA..." From a11d61c5a28ab2ca39bf612cf0a985bfa8d319c2 Mon Sep 17 00:00:00 2001 From: Anh Duc Le Date: Wed, 5 Nov 2025 11:13:42 +0700 Subject: [PATCH 5/6] Add mkcert usage instructions for local development SSL in README.md --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/README.md b/README.md index f4f783d..5daa42f 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,66 @@ Other parameters: * [`-V`, `--remove`]: Remove a domain. +### Using mkcert for Local Development SSL + +For local development domains (`.test`, `.local`, `.dev`, etc.), you can use `mkcert` to generate trusted SSL certificates without warnings. + +#### Installing mkcert + +First-time installation (Windows with Chocolatey): + +```bash +bash bin/mkcert.sh --install +``` + +This will: + +* Install `mkcert` via Chocolatey +* Create and install a local Certificate Authority (CA) in your system trust store + +> **Note**: For macOS or Linux users, please install mkcert manually: +> +> * macOS: `brew install mkcert nss` +> * Ubuntu: `sudo apt install mkcert libnss3-tools` +> * Fedora: `sudo dnf install mkcert nss-tools` + +#### Generating Local SSL Certificate + +After adding a domain to your environment, generate an SSL certificate: + +```bash +bash bin/mkcert.sh [-D, --domain] example.test +``` + +This will: + +1. Check if the domain exists in your configuration +2. Generate certificates for `example.test` and `www.example.test` +3. Create a `dockerLocal` template with SSL configuration +4. Copy certificates to the container +5. Move the domain from the standard template to the SSL-enabled template +6. Restart OpenLiteSpeed + +Your domain will now be accessible via HTTPS with a trusted certificate at `https://example.test` + +#### Removing Local SSL Certificate + +To remove the SSL certificate and revert to HTTP: + +```bash +bash bin/mkcert.sh [-R, --remove] [-D, --domain] example.test +``` + +This will: + +1. Remove the domain from the `dockerLocal` template +2. Move it back to the standard `docker` template +3. Delete certificate files from both host and container +4. Clean up empty templates if no other domains use SSL +5. Restart OpenLiteSpeed + +> **Important**: You must add the domain to your environment first using `bash bin/domain.sh --add example.test` before generating certificates. + ### Update Web Server To upgrade the web server to latest stable version, run the following: From 4e41befe7be9d0a55f0214eb3691238e8bcacbcf Mon Sep 17 00:00:00 2001 From: Anh Duc Le Date: Thu, 6 Nov 2025 10:17:29 +0700 Subject: [PATCH 6/6] Update help message in mkcert.sh to clarify usage of --remove option --- bin/mkcert.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/mkcert.sh b/bin/mkcert.sh index 99669f8..3b5124c 100644 --- a/bin/mkcert.sh +++ b/bin/mkcert.sh @@ -25,7 +25,8 @@ help_message(){ echow '-I, --install' echo "${EPACE}${EPACE}Install mkcert on Windows (requires Chocolatey)" echow '-R, --remove' - echo "${EPACE}${EPACE}Remove certificate for a specific domain" + echo "${EPACE}${EPACE}Remove certificate for a specific domain. Must be used with --domain." + echo "${EPACE}${EPACE}Example: mkcert.sh --remove --domain example.test" echow '-H, --help' echo "${EPACE}${EPACE}Display help and exit" exit 0