├── acme └── .gitignore ├── lsws └── .gitignore ├── sites ├── .gitignore └── localhost │ ├── html │ └── .gitignore │ └── logs │ └── .gitignore ├── logs └── .gitignore ├── .gitignore ├── bin ├── dev │ ├── list-flagged-files.sh │ ├── no-skip-worktree-conf.sh │ └── skip-worktree-conf.sh ├── container │ ├── certhookctl.sh │ ├── pkginstallctl.sh │ ├── serialctl.sh │ ├── domainctl.sh │ ├── owaspctl.sh │ └── appinstallctl.sh ├── appinstall.sh ├── domain.sh ├── webadmin.sh ├── demosite.sh ├── database.sh └── acme.sh ├── .env ├── .github └── workflows │ └── docker.yml ├── .travis ├── main.sh └── verify.sh ├── LICENSE ├── .travis.yml.bk ├── docker-compose.yml └── README.md /acme/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /lsws/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /sites/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /sites/localhost/html/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /sites/localhost/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | latest.yml 3 | lsws-dockerfiles 4 | -------------------------------------------------------------------------------- /bin/dev/list-flagged-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git ls-files -v|grep '^S' 3 | -------------------------------------------------------------------------------- /bin/dev/no-skip-worktree-conf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | find conf -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd && git ls-files -z ${pwd} | xargs -0 git update-index --no-skip-worktree" \; 3 | -------------------------------------------------------------------------------- /bin/dev/skip-worktree-conf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | find conf -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd && git ls-files -z ${pwd} | xargs -0 git update-index --skip-worktree" \; 3 | 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | TimeZone=America/New_York 2 | LSWS_VERSION=6.3.4 3 | PHP_VERSION=lsphp84 4 | PHPMYADMIN_VERSION=5.2.3 5 | MYSQL_DATABASE=wordpress 6 | MYSQL_ROOT_PASSWORD=your_root_password 7 | MYSQL_USER=wordpress 8 | MYSQL_PASSWORD=your_password 9 | DOMAIN=localhost 10 | -------------------------------------------------------------------------------- /bin/container/certhookctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | BOTCRON='/var/spool/cron/crontabs/root' 3 | 4 | cert_hook(){ 5 | grep 'acme' ${BOTCRON} >/dev/null 6 | if [ ${?} = 0 ]; then 7 | grep 'lswsctrl' ${BOTCRON} >/dev/null 8 | if [ ${?} = 0 ]; then 9 | echo 'Hook already exist, skip!' 10 | else 11 | sed -i 's/--cron/--cron --renew-hook "\/usr\/local\/lsws\/bin\/lswsctrl restart"/g' ${BOTCRON} 12 | fi 13 | else 14 | echo "[X] ${BOTCRON} does not exist, please check it later!" 15 | fi 16 | } 17 | 18 | cert_hook -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: docker-build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup 18 | run: | 19 | docker compose version 20 | docker compose up -d 21 | docker image ls 22 | sleep 10 23 | - name: Verify 24 | run: bash .travis/verify.sh 25 | - name: Clean up 26 | run: | 27 | docker compose stop 28 | docker compose rm -f 29 | -------------------------------------------------------------------------------- /.travis/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | 5 | setup_dependencies() { 6 | echo "INFO: Setting up dependencies." 7 | sudo apt-get install git -y 8 | sudo rm /usr/local/bin/docker-compose 9 | curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` \ 10 | > docker-compose 11 | chmod +x docker-compose 12 | sudo mv docker-compose /usr/local/bin 13 | docker-compose --version 14 | } 15 | 16 | update_docker_configuration() { 17 | echo "INFO: Updating docker configuration." 18 | echo '{ 19 | "experimental": true, 20 | "storage-driver": "overlay2", 21 | "max-concurrent-downloads": 50, 22 | "max-concurrent-uploads": 50 23 | }' | sudo tee /etc/docker/daemon.json 24 | sudo service docker restart 25 | } 26 | 27 | main() { 28 | setup_dependencies 29 | update_docker_configuration 30 | echo "SUCCESS: Done! Finished setting up Travis machine." 31 | } 32 | 33 | main -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2022 Litespeedtech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.travis.yml.bk: -------------------------------------------------------------------------------- 1 | language: shell 2 | os: linux 3 | 4 | notifications: 5 | email: 6 | on_success: never 7 | on_failure: always 8 | slack: 9 | secure: hTQk4ZYbrD4eDnwJsrIhDyuw4WfVMkpP0w26AecHS6edQEFkS1LJS77elFQDgg4SlL8K0xdHrTwqMl5JL9LV+2TZ77xUpX0pB7H9toG1rEqxv4v7bKH7umHCvy0YLzfoz5IjcAHkVqX6/MKcp0M1DQ0Ts1TuTzk5IPacekIbCTRXDdqljKIhvGgDfXZWh0dYkSbA9xoPzeN9GXzSSa+uXqQWk7q2uCdXh4stWFOTR8ZJIHqTyUq1+tNTLAM8b3pw/F0uevCKoHIWljqpwLgg/Chu2JuGnOOV91Jwks7nXikgByiRH8ygE2J0HFjBdNCZZU1BffuYqgziZrVDpFjUJkrp5Tku+SCd76O0NmLz5PgBo6BJL0/p3Sa1N2/b4cdrUFbU0W9DVDVLMinTAOlRbhq/iuldAlBLJVTgOpKsbAQ5AqmceGsVk/JYTK8WzCw0Y0LFNiWlVWtUYSQZo887UEozSl415OEtM0kkiosOUxyd305m/HWBzS7pyHSGp6kum3SIwVLjYsHSAx5WRVA38Gg5OOmKwLHDy5RaN+1u0AN66TUGs4GSfzJNjL0+C6I66EMGCRXMerJNMf9r9dnVKsKUNwvaheCZe9IszGxubmlBrPxLLpczplkGrYTv0aRQsgMWTSGLttEek3j3p7p+ntDNWDaveDCdppipM5EjJ4M= 10 | template: 11 | - "Repo %{repository_slug} *%{result}* build (<%{build_url}|#%{build_number}>) for commit (<%{compare_url}|%{commit}>)" 12 | - "%{author}: _%{commit_message}_" 13 | - "Execution time: *%{duration}*" 14 | - "Message: *%{message}*" 15 | on_success: always 16 | 17 | services: 18 | - docker 19 | 20 | env: 21 | - DOCKER_COMPOSE_VERSION=1.25.0 22 | 23 | before_install: 24 | - ./.travis/main.sh 25 | 26 | install: 27 | - git clone https://github.com/litespeedtech/lsws-docker-env.git 28 | - docker-compose up -d 29 | 30 | before_script: 31 | - docker image ls 32 | - sleep 10 33 | 34 | script: 35 | - ./.travis/verify.sh 36 | 37 | after_script: 38 | - docker-compose stop 39 | - docker-compose rm -f 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mysql: 3 | image: mariadb:11.4 4 | logging: 5 | driver: none 6 | command: ["--max-allowed-packet=512M"] 7 | volumes: 8 | - "./data/db:/var/lib/mysql:delegated" 9 | environment: 10 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} 11 | MYSQL_DATABASE: ${MYSQL_DATABASE} 12 | MYSQL_USER: ${MYSQL_USER} 13 | MYSQL_PASSWORD: ${MYSQL_PASSWORD} 14 | restart: always 15 | networks: 16 | - default 17 | litespeed: 18 | image: litespeedtech/litespeed:${LSWS_VERSION}-${PHP_VERSION} 19 | env_file: 20 | - .env 21 | volumes: 22 | - ./lsws/conf:/usr/local/lsws/conf 23 | - ./lsws/admin/conf:/usr/local/lsws/admin/conf 24 | - ./bin/container:/usr/local/bin 25 | - ./sites:/var/www/vhosts/ 26 | - ./acme:/root/.acme.sh/ 27 | - ./logs:/usr/local/lsws/logs/ 28 | ports: 29 | - 80:80 30 | - 443:443 31 | - 443:443/udp 32 | - 7080:7080 33 | restart: always 34 | environment: 35 | TZ: ${TimeZone} 36 | networks: 37 | - default 38 | phpmyadmin: 39 | image: phpmyadmin/phpmyadmin:${PHPMYADMIN_VERSION} 40 | ports: 41 | - 8080:80 42 | environment: 43 | DATABASE_HOST: mysql 44 | restart: always 45 | networks: 46 | - default 47 | redis: 48 | image: "redis:alpine" 49 | logging: 50 | driver: none 51 | volumes: 52 | - ./redis/data:/data 53 | - ./redis/redis.conf:/usr/local/etc/redis/redis.conf 54 | environment: 55 | - REDIS_REPLICATION_MODE=master 56 | restart: always 57 | networks: 58 | - default 59 | networks: 60 | default: 61 | driver: bridge 62 | -------------------------------------------------------------------------------- /bin/appinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | APP_NAME='' 3 | DOMAIN='' 4 | EPACE=' ' 5 | 6 | echow(){ 7 | FLAG=${1} 8 | shift 9 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 10 | } 11 | 12 | help_message(){ 13 | echo -e "\033[1mOPTIONS\033[0m" 14 | echow '-A, --app [app_name] -D, --domain [DOMAIN_NAME]' 15 | echo "${EPACE}${EPACE}Example: appinstall.sh -A wordpress -D example.com" 16 | echow '-H, --help' 17 | echo "${EPACE}${EPACE}Display help and exit." 18 | exit 0 19 | } 20 | 21 | check_input(){ 22 | if [ -z "${1}" ]; then 23 | help_message 24 | exit 1 25 | fi 26 | } 27 | 28 | install_packages(){ 29 | if [ "${1}" = 'wordpress' ]; then 30 | docker compose exec litespeed /bin/bash -c "pkginstallctl.sh --package ed" 31 | docker compose exec litespeed /bin/bash -c "pkginstallctl.sh --package unzip" 32 | fi 33 | } 34 | 35 | app_download(){ 36 | install_packages ${1} 37 | docker compose exec litespeed bash -c "appinstallctl.sh --app ${1} --domain ${2}" 38 | bash bin/webadmin.sh -r 39 | exit 0 40 | } 41 | 42 | main(){ 43 | app_download "${APP_NAME}" "${DOMAIN}" 44 | } 45 | 46 | check_input ${1} 47 | while [ ! -z "${1}" ]; do 48 | case ${1} in 49 | -[hH] | -help | --help) 50 | help_message 51 | ;; 52 | -[aA] | -app | --app) shift 53 | check_input "${1}" 54 | APP_NAME="${1}" 55 | ;; 56 | -[dD] | -domain | --domain) shift 57 | check_input "${1}" 58 | DOMAIN="${1}" 59 | ;; 60 | *) 61 | help_message 62 | ;; 63 | esac 64 | shift 65 | done 66 | 67 | main -------------------------------------------------------------------------------- /bin/domain.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | CONT_NAME='litespeed' 3 | EPACE=' ' 4 | 5 | echow(){ 6 | FLAG=${1} 7 | shift 8 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 9 | } 10 | 11 | help_message(){ 12 | echo -e "\033[1mOPTIONS\033[0m" 13 | echow "-A, --add [domain_name]" 14 | echo "${EPACE}${EPACE}Example: domain.sh -A example.com, will add the domain to Listener and auto create a new virtual host." 15 | echow "-D, --del [domain_name]" 16 | echo "${EPACE}${EPACE}Example: domain.sh -D example.com, will delete the domain from Listener." 17 | echow '-H, --help' 18 | echo "${EPACE}${EPACE}Display help and exit." 19 | } 20 | 21 | check_input(){ 22 | if [ -z "${1}" ]; then 23 | help_message 24 | exit 1 25 | fi 26 | } 27 | 28 | add_domain(){ 29 | check_input ${1} 30 | docker compose exec ${CONT_NAME} su -s /bin/bash lsadm -c "cd /usr/local/lsws/conf && domainctl.sh --add ${1}" 31 | if [ ! -d "./sites/${1}" ]; then 32 | mkdir -p ./sites/${1}/{html,logs,certs} 33 | fi 34 | bash bin/webadmin.sh -r 35 | } 36 | 37 | del_domain(){ 38 | check_input ${1} 39 | docker compose exec ${CONT_NAME} su -s /bin/bash lsadm -c "cd /usr/local/lsws/conf && domainctl.sh --del ${1}" 40 | bash bin/webadmin.sh -r 41 | } 42 | 43 | check_input ${1} 44 | while [ ! -z "${1}" ]; do 45 | case ${1} in 46 | -[hH] | -help | --help) 47 | help_message 48 | ;; 49 | -[aA] | -add | --add) shift 50 | add_domain ${1} 51 | ;; 52 | -[dD] | -del | --del | --delete) shift 53 | del_domain ${1} 54 | ;; 55 | *) 56 | help_message 57 | ;; 58 | esac 59 | shift 60 | done 61 | -------------------------------------------------------------------------------- /bin/container/pkginstallctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MA_COMPOSER='/usr/local/bin/composer' 3 | EPACE=' ' 4 | 5 | echoY() { 6 | echo -e "\033[38;5;148m${1}\033[39m" 7 | } 8 | echoG() { 9 | echo -e "\033[38;5;71m${1}\033[39m" 10 | } 11 | echoR() 12 | { 13 | echo -e "\033[38;5;203m${1}\033[39m" 14 | } 15 | echow(){ 16 | FLAG=${1} 17 | shift 18 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 19 | } 20 | 21 | help_message(){ 22 | echo -e "\033[1mOPTIONS\033[0m" 23 | echow '-A, -app [wordpress|magento] -D, --domain [DOMAIN_NAME]' 24 | echo "${EPACE}${EPACE}Example: appinstallctl.sh --app wordpress --domain example.com" 25 | echow '-H, --help' 26 | echo "${EPACE}${EPACE}Display help and exit." 27 | exit 0 28 | } 29 | 30 | install_ed(){ 31 | if [ ! -f /bin/ed ]; then 32 | echo "Install ed package.." 33 | apt-get install ed -y > /dev/null 2>&1 34 | ed -V > /dev/null 2>&1 35 | if [ ${?} != 0 ]; then 36 | echoR 'Issue with ed, Please check!' 37 | fi 38 | fi 39 | } 40 | 41 | install_unzip(){ 42 | if [ ! -f /usr/bin/unzip ]; then 43 | echoG "Install unzip package" 44 | apt-get install unzip -y > /dev/null 2>&1 45 | unzip -v > /dev/null 2>&1 46 | if [ ${?} != 0 ]; then 47 | echoR 'Issue with unzip, Please check!' 48 | fi 49 | fi 50 | } 51 | 52 | 53 | case ${1} in 54 | -[pP] | -package | --package) shift 55 | if [ -z "${1}" ]; then 56 | help_message 57 | fi 58 | case ${1} in 59 | ed) 60 | install_ed 61 | ;; 62 | unzip) 63 | install_unzip 64 | ;; 65 | esac 66 | ;; 67 | -[hH] | -help | --help) 68 | help_message 69 | ;; 70 | *) 71 | help_message 72 | ;; 73 | esac -------------------------------------------------------------------------------- /bin/container/serialctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LSDIR='/usr/local/lsws' 3 | EPACE=' ' 4 | 5 | echow(){ 6 | FLAG=${1} 7 | shift 8 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 9 | } 10 | 11 | help_message(){ 12 | echo -e "\033[1mOPTIONS\033[0m" 13 | echow '-S, --serial [YOUR_SERIAL|TRIAL]' 14 | echo "${EPACE}${EPACE}Will apply and register the serial to LSWS." 15 | echow '-H, --help' 16 | echo "${EPACE}${EPACE}Display help and exit." 17 | exit 0 18 | } 19 | 20 | check_input(){ 21 | if [ -z "${1}" ]; then 22 | help_message 23 | exit 1 24 | fi 25 | } 26 | 27 | backup_old(){ 28 | if [ -f ${1} ] && [ ! -f ${1}_old ]; then 29 | mv ${1} ${1}_old 30 | fi 31 | } 32 | 33 | detect_ols(){ 34 | if [ -e ${LSDIR}/bin/openlitespeed ]; then 35 | echo '[X] Detect OpenLiteSpeed, abort!' 36 | exit 1 37 | fi 38 | } 39 | 40 | apply_serial(){ 41 | detect_ols 42 | check_input ${1} 43 | echo ${1} | grep -i 'trial' >/dev/null 44 | if [ ${?} = 0 ]; then 45 | echo 'Apply Trial License' 46 | if [ ! -e ${LSDIR}/conf/serial.no ] && [ ! -e ${LSDIR}/conf/license.key ]; then 47 | rm -f ${LSDIR}/conf/trial.key* 48 | wget -P ${LSDIR}/conf -q http://license.litespeedtech.com/reseller/trial.key 49 | echo 'Apply trial finished' 50 | else 51 | echo "Please backup and remove your existing license, apply abort!" 52 | exit 1 53 | fi 54 | else 55 | echo "Apply Serial number: ${1}" 56 | backup_old ${LSDIR}/conf/serial.no 57 | backup_old ${LSDIR}/conf/license.key 58 | backup_old ${LSDIR}/conf/trial.key 59 | echo "${1}" > ${LSDIR}/conf/serial.no 60 | ${LSDIR}/bin/lshttpd -r 61 | if [ -f ${LSDIR}/conf/license.key ]; then 62 | echo '[O] Apply success' 63 | else 64 | echo '[X] Apply failed, please check!' 65 | exit 1 66 | fi 67 | fi 68 | } 69 | 70 | check_input ${1} 71 | while [ ! -z "${1}" ]; do 72 | case ${1} in 73 | -[hH] | -help | --help) 74 | help_message 75 | ;; 76 | -[sS] | -serial | --serial) shift 77 | apply_serial "${1}" 78 | ;; 79 | *) 80 | help_message 81 | ;; 82 | esac 83 | shift 84 | done -------------------------------------------------------------------------------- /bin/webadmin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | CONT_NAME='litespeed' 3 | EPACE=' ' 4 | 5 | echow(){ 6 | FLAG=${1} 7 | shift 8 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 9 | } 10 | 11 | help_message(){ 12 | echo -e "\033[1mOPTIONS\033[0m" 13 | echow '[Enter Your PASSWORD]' 14 | echo "${EPACE}${EPACE}Example: webadmin.sh MY_SECURE_PASS, to update web admin password immediatly." 15 | echow '-R, --restart' 16 | echo "${EPACE}${EPACE}Will gracefully restart LiteSpeed Web Server." 17 | echow '-M, --mod-secure [enable|disable]' 18 | echo "${EPACE}${EPACE}Example: webadmin.sh -M enable, will enable and apply Mod_Secure OWASP rules on server" 19 | echow '-U, --upgrade' 20 | echo "${EPACE}${EPACE}Will upgrade web server to latest stable version" 21 | echow '-S, --serial [YOUR_SERIAL|TRIAL]' 22 | echo "${EPACE}${EPACE}Will apply your serial number to LiteSpeed Web Server." 23 | echow '-H, --help' 24 | echo "${EPACE}${EPACE}Display help and exit." 25 | exit 0 26 | } 27 | 28 | check_input(){ 29 | if [ -z "${1}" ]; then 30 | help_message 31 | exit 1 32 | fi 33 | } 34 | 35 | lsws_restart(){ 36 | docker compose exec -T ${CONT_NAME} su -c '/usr/local/lsws/bin/lswsctrl restart >/dev/null' 37 | } 38 | 39 | apply_serial(){ 40 | docker compose exec ${CONT_NAME} su -c "serialctl.sh --serial ${1}" 41 | lsws_restart 42 | } 43 | 44 | mod_secure(){ 45 | if [ "${1}" = 'enable' ] || [ "${1}" = 'Enable' ]; then 46 | docker compose exec ${CONT_NAME} su -s /bin/bash root -c "owaspctl.sh --enable" 47 | lsws_restart 48 | elif [ "${1}" = 'disable' ] || [ "${1}" = 'Disable' ]; then 49 | docker compose exec ${CONT_NAME} su -s /bin/bash root -c "owaspctl.sh --disable" 50 | lsws_restart 51 | else 52 | help_message 53 | fi 54 | } 55 | 56 | ls_upgrade(){ 57 | echo 'Upgrade web server to latest stable version.' 58 | docker compose exec ${CONT_NAME} su -c '/usr/local/lsws/admin/misc/lsup.sh 2>/dev/null' 59 | } 60 | 61 | set_web_admin(){ 62 | echo 'Update web admin password.' 63 | local LSADPATH='/usr/local/lsws/admin' 64 | docker compose exec ${CONT_NAME} su -s /bin/bash lsadm -c \ 65 | 'if [ -e /usr/local/lsws/admin/fcgi-bin/admin_php ]; then \ 66 | echo "admin:$('${LSADPATH}'/fcgi-bin/admin_php -q '${LSADPATH}'/misc/htpasswd.php '${1}')" > '${LSADPATH}'/conf/htpasswd; \ 67 | else echo "admin:$('${LSADPATH}'/fcgi-bin/admin_php5 -q '${LSADPATH}'/misc/htpasswd.php '${1}')" > '${LSADPATH}'/conf/htpasswd; \ 68 | fi'; 69 | } 70 | 71 | main(){ 72 | set_web_admin ${1} 73 | } 74 | 75 | check_input ${1} 76 | while [ ! -z "${1}" ]; do 77 | case ${1} in 78 | -[hH] | -help | --help) 79 | help_message 80 | ;; 81 | -[rR] | -restart | --restart) 82 | lsws_restart 83 | ;; 84 | -M | -mode-secure | --mod-secure) shift 85 | mod_secure ${1} 86 | ;; 87 | -lsup | --lsup | --upgrade | -U) shift 88 | ls_upgrade 89 | ;; 90 | -[sS] | -serial | --serial) shift 91 | apply_serial ${1} 92 | ;; 93 | *) 94 | main ${1} 95 | ;; 96 | esac 97 | shift 98 | done -------------------------------------------------------------------------------- /.travis/verify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | EX_DM='example.com' 5 | 6 | install_demo(){ 7 | ./bin/demosite.sh 8 | } 9 | 10 | verify_lsws(){ 11 | curl -sIk http://localhost:7080/ | grep -i LiteSpeed 12 | if [ ${?} = 0 ]; then 13 | echo '[O] https://localhost:7080/' 14 | else 15 | echo '[X] https://localhost:7080/' 16 | exit 1 17 | fi 18 | } 19 | 20 | verify_page(){ 21 | curl -sIk http://localhost:80/ | grep -i WordPress 22 | if [ ${?} = 0 ]; then 23 | echo '[O] http://localhost:80/' 24 | else 25 | echo '[X] http://localhost:80/' 26 | curl -sIk http://localhost:80/ 27 | exit 1 28 | fi 29 | curl -sIk https://localhost:443/ | grep -i WordPress 30 | if [ ${?} = 0 ]; then 31 | echo '[O] https://localhost:443/' 32 | else 33 | echo '[X] https://localhost:443/' 34 | curl -sIk https://localhost:443/ 35 | exit 1 36 | fi 37 | } 38 | 39 | verify_phpadmin(){ 40 | curl -sIk http://localhost:8080/ | grep -i phpMyAdmin 41 | if [ ${?} = 0 ]; then 42 | echo '[O] http://localhost:8080/' 43 | else 44 | echo '[X] http://localhost:8080/' 45 | exit 1 46 | fi 47 | } 48 | 49 | verify_add_vh_wp(){ 50 | echo "Setup a WordPress site with ${EX_DM} domain" 51 | bash bin/domain.sh --add "${EX_DM}" 52 | bash bin/database.sh --domain "${EX_DM}" 53 | bash bin/appinstall.sh --app wordpress --domain "${EX_DM}" 54 | curl -sIk http://${EX_DM}:80/ --resolve ${EX_DM}:80:127.0.0.1 | grep -i WordPress 55 | if [ ${?} = 0 ]; then 56 | echo "[O] http://${EX_DM}:80/" 57 | else 58 | echo "[X] http://${EX_DM}:80/" 59 | curl -sIk http://${EX_DM}:80/ 60 | exit 1 61 | fi 62 | } 63 | verify_del_vh_wp(){ 64 | echo "Remove ${EX_DM} domain" 65 | bash bin/domain.sh --del ${EX_DM} 66 | if [ ${?} = 0 ]; then 67 | echo "[O] ${EX_DM} VH is removed" 68 | else 69 | echo "[X] ${EX_DM} VH is not removed" 70 | exit 1 71 | fi 72 | echo "Remove examplecom DataBase" 73 | bash bin/database.sh --delete -DB examplecom 74 | } 75 | 76 | verify_owasp(){ 77 | echo 'Updating LSWS' 78 | bash bin/webadmin.sh --upgrade 2>&1 /dev/null 79 | echo 'Enabling OWASP' 80 | bash bin/webadmin.sh --mod-secure enable 81 | curl -sIk http://localhost:80/phpinfo.php | awk '/HTTP/ && /403/' 82 | if [ ${?} = 0 ]; then 83 | echo '[O] OWASP enable' 84 | else 85 | echo '[X] OWASP enable' 86 | curl -sIk http://localhost:80/phpinfo.php | awk '/HTTP/ && /403/' 87 | exit 1 88 | fi 89 | bash bin/webadmin.sh --mod-secure disable 90 | curl -sIk http://localhost:80/phpinfo.php | grep -i WordPress 91 | if [ ${?} = 0 ]; then 92 | echo '[O] OWASP disable' 93 | else 94 | echo '[X] OWASP disable' 95 | curl -sIk http://localhost:80/phpinfo.php 96 | exit 1 97 | fi 98 | } 99 | 100 | 101 | main(){ 102 | verify_lsws 103 | verify_phpadmin 104 | install_demo 105 | verify_page 106 | verify_owasp 107 | verify_add_vh_wp 108 | verify_del_vh_wp 109 | } 110 | main -------------------------------------------------------------------------------- /bin/demosite.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source .env 3 | APP='wordpress' 4 | CONT_NAME='litespeed' 5 | DOC_FD='' 6 | EPACE=' ' 7 | 8 | echow(){ 9 | FLAG=${1} 10 | shift 11 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 12 | } 13 | 14 | help_message(){ 15 | case ${1} in 16 | "1") 17 | echow "Script will get 'DOMAIN' and 'database' info from .env file, then auto setup virtual host and the wordpress site for you." 18 | echo -e "\033[1mOPTIONS\033[0m" 19 | echow '-W, --wordpress' 20 | echo "${EPACE}${EPACE}Example: lsws1clk.sh -W. If no input, script will still install wordpress by default" 21 | echow '-H, --help' 22 | echo "${EPACE}${EPACE}Display help and exit." 23 | exit 0 24 | ;; 25 | "2") 26 | echow 'Service finished, enjoy your accelarated LiteSpeed server!' 27 | ;; 28 | esac 29 | } 30 | 31 | domain_filter(){ 32 | if [ ! -n "${DOMAIN}" ]; then 33 | echo "Parameters not supplied, please check!" 34 | exit 1 35 | fi 36 | DOMAIN="${1}" 37 | DOMAIN="${DOMAIN#http://}" 38 | DOMAIN="${DOMAIN#https://}" 39 | DOMAIN="${DOMAIN#ftp://}" 40 | DOMAIN="${DOMAIN#scp://}" 41 | DOMAIN="${DOMAIN#scp://}" 42 | DOMAIN="${DOMAIN#sftp://}" 43 | DOMAIN=${DOMAIN%%/*} 44 | } 45 | 46 | gen_root_fd(){ 47 | DOC_FD="./sites/${1}/" 48 | if [ -d "./sites/${1}" ]; then 49 | echo -e "[O] The root folder \033[32m${DOC_FD}\033[0m exist." 50 | else 51 | echo "Creating - document root." 52 | bash bin/domain.sh -add ${1} 53 | echo "Finished - document root." 54 | fi 55 | } 56 | 57 | create_db(){ 58 | if [ ! -n "${MYSQL_DATABASE}" ] || [ ! -n "${MYSQL_USER}" ] || [ ! -n "${MYSQL_PASSWORD}" ]; then 59 | echo "Parameters not supplied, please check!" 60 | exit 1 61 | else 62 | bash bin/database.sh -D ${1} -U ${MYSQL_USER} -P ${MYSQL_PASSWORD} -DB ${MYSQL_DATABASE} 63 | fi 64 | } 65 | 66 | store_credential(){ 67 | if [ -f ${DOC_FD}/.db_pass ]; then 68 | echo '[O] db file exist!' 69 | else 70 | echo 'Storing database parameter' 71 | cat > "${DOC_FD}/.db_pass" << EOT 72 | "Database":"${MYSQL_DATABASE}" 73 | "Username":"${MYSQL_USER}" 74 | "Password":"$(echo ${MYSQL_PASSWORD} | tr -d "'")" 75 | EOT 76 | fi 77 | } 78 | 79 | install_packages(){ 80 | if [ "${1}" = 'wordpress' ]; then 81 | docker compose exec litespeed /bin/bash -c "pkginstallctl.sh --package ed" 82 | docker compose exec litespeed /bin/bash -c "pkginstallctl.sh --package unzip" 83 | fi 84 | } 85 | 86 | app_download(){ 87 | install_packages ${1} 88 | docker compose exec -T ${CONT_NAME} bash -c "appinstallctl.sh --app ${1} --domain ${2}" 89 | } 90 | 91 | lsws_restart(){ 92 | bash bin/webadmin.sh -r 93 | } 94 | 95 | main(){ 96 | domain_filter ${DOMAIN} 97 | gen_root_fd ${DOMAIN} 98 | create_db ${DOMAIN} 99 | store_credential 100 | app_download ${APP} ${DOMAIN} 101 | lsws_restart 102 | } 103 | 104 | while [ ! -z "${1}" ]; do 105 | case ${1} in 106 | -[hH] | -help | --help) 107 | help_message 1 108 | ;; 109 | -[wW] | --wordpress) 110 | APP='wordpress' 111 | ;; 112 | *) 113 | help_message 1 114 | ;; 115 | esac 116 | shift 117 | done 118 | main 119 | -------------------------------------------------------------------------------- /bin/container/domainctl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | CK_RESULT='' 3 | LSDIR='/usr/local/lsws' 4 | LS_HTTPD_CONF="${LSDIR}/conf/httpd_config.xml" 5 | OLS_HTTPD_CONF="${LSDIR}/conf/httpd_config.conf" 6 | EPACE=' ' 7 | 8 | echow(){ 9 | FLAG=${1} 10 | shift 11 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 12 | } 13 | 14 | help_message(){ 15 | echo -e "\033[1mOPTIONS\033[0m" 16 | echow '-A, --add [DOMAIN_NAME]' 17 | echo "${EPACE}${EPACE}Will add domain to listener and creat a virtual host from template" 18 | echow '-D, --del [DOMAIN_NAME]' 19 | echo "${EPACE}${EPACE}Will delete domain from listener" 20 | echow '-H, --help' 21 | echo "${EPACE}${EPACE}Display help." 22 | } 23 | 24 | check_lsv(){ 25 | if [ -f ${LSDIR}/bin/openlitespeed ]; then 26 | LSV='openlitespeed' 27 | elif [ -f ${LSDIR}/bin/litespeed ]; then 28 | LSV='lsws' 29 | else 30 | echo 'Version not exist, abort!' 31 | exit 1 32 | fi 33 | } 34 | 35 | dot_escape(){ 36 | ESCAPE=$(echo ${1} | sed 's/\./\\./g') 37 | } 38 | 39 | check_duplicate(){ 40 | CK_RESULT=$(grep -E "${1}" ${2}) 41 | } 42 | 43 | fst_match_line(){ 44 | FIRST_LINE_NUM=$(grep -n -m 1 ${1} ${2} | awk -F ':' '{print $1}') 45 | } 46 | fst_match_after(){ 47 | FIRST_NUM_AFTER=$(tail -n +${1} ${2} | grep -n -m 1 ${3} | awk -F ':' '{print $1}') 48 | } 49 | lst_match_line(){ 50 | fst_match_after ${1} ${2} ${3} 51 | LAST_LINE_NUM=$((${FIRST_LINE_NUM}+${FIRST_NUM_AFTER}-1)) 52 | } 53 | 54 | check_input(){ 55 | if [ -z "${1}" ]; then 56 | help_message 57 | exit 1 58 | fi 59 | } 60 | 61 | check_www(){ 62 | CHECK_WWW=$(echo ${1} | cut -c1-4) 63 | if [[ ${CHECK_WWW} == www. ]] ; then 64 | echo 'www domain shoudnt be passed!' 65 | exit 1 66 | fi 67 | } 68 | 69 | www_domain(){ 70 | check_www ${1} 71 | WWW_DOMAIN=$(echo www.${1}) 72 | } 73 | 74 | add_ls_domain(){ 75 | fst_match_line 'docker.xml' ${LS_HTTPD_CONF} 76 | NEWNUM=$((FIRST_LINE_NUM+2)) 77 | sed -i "${NEWNUM}i \ \ \ \ \ \ \n \ \ \ \ \ \ \ ${DOMAIN}\n \ \ \ \ \ \ \ ${DOMAIN},${WWW_DOMAIN}\n \ \ \ \ \ \ " ${LS_HTTPD_CONF} 78 | } 79 | 80 | add_ols_domain(){ 81 | perl -0777 -p -i -e 's/(vhTemplate docker \{[^}]+)\}*(^.*listeners.*$)/\1$2 82 | member '${DOMAIN}' { 83 | vhDomain '${DOMAIN},${WWW_DOMAIN}' 84 | }/gmi' ${OLS_HTTPD_CONF} 85 | } 86 | 87 | add_domain(){ 88 | check_lsv 89 | dot_escape ${1} 90 | DOMAIN=${ESCAPE} 91 | www_domain ${1} 92 | if [ "${LSV}" = 'lsws' ]; then 93 | check_duplicate "vhDomain.*${DOMAIN}" ${LS_HTTPD_CONF} 94 | if [ "${CK_RESULT}" != '' ]; then 95 | echo "# It appears the domain already exist! Check the ${LS_HTTPD_CONF} if you believe this is a mistake!" 96 | exit 1 97 | fi 98 | add_ls_domain 99 | elif [ "${LSV}" = 'openlitespeed' ]; then 100 | check_duplicate "member.*${DOMAIN}" ${OLS_HTTPD_CONF} 101 | if [ "${CK_RESULT}" != '' ]; then 102 | echo "# It appears the domain already exist! Check the ${OLS_HTTPD_CONF} if you believe this is a mistake!" 103 | exit 1 104 | fi 105 | add_ols_domain 106 | fi 107 | } 108 | 109 | del_ls_domain(){ 110 | fst_match_line "*${1}" ${LS_HTTPD_CONF} 111 | FIRST_LINE_NUM=$((FIRST_LINE_NUM-1)) 112 | lst_match_line ${FIRST_LINE_NUM} ${LS_HTTPD_CONF} '' 113 | sed -i "${FIRST_LINE_NUM},${LAST_LINE_NUM}d" ${LS_HTTPD_CONF} 114 | } 115 | 116 | del_ols_domain(){ 117 | fst_match_line ${1} ${OLS_HTTPD_CONF} 118 | lst_match_line ${FIRST_LINE_NUM} ${OLS_HTTPD_CONF} '}' 119 | sed -i "${FIRST_LINE_NUM},${LAST_LINE_NUM}d" ${OLS_HTTPD_CONF} 120 | } 121 | 122 | del_domain(){ 123 | check_lsv 124 | dot_escape ${1} 125 | DOMAIN=${ESCAPE} 126 | if [ "${LSV}" = 'lsws' ]; then 127 | check_duplicate "vhDomain.*${DOMAIN}" ${LS_HTTPD_CONF} 128 | if [ "${CK_RESULT}" = '' ]; then 129 | echo "# Domain non-exist! Check the ${LS_HTTPD_CONF} if you believe this is a mistake!" 130 | exit 1 131 | fi 132 | del_ls_domain ${1} 133 | elif [ "${LSV}" = 'openlitespeed' ]; then 134 | check_duplicate "member.*${DOMAIN}" ${OLS_HTTPD_CONF} 135 | if [ "${CK_RESULT}" = '' ]; then 136 | echo "# Domain non-exist! Check the ${OLS_HTTPD_CONF} if you believe this is a mistake!" 137 | exit 1 138 | fi 139 | del_ols_domain ${1} 140 | fi 141 | } 142 | 143 | check_input ${1} 144 | while [ ! -z "${1}" ]; do 145 | case ${1} in 146 | -[hH] | -help | --help) 147 | help_message 148 | ;; 149 | -[aA] | -add | --add) shift 150 | add_domain ${1} 151 | ;; 152 | -[dD] | -del | --del | --delete) shift 153 | del_domain ${1} 154 | ;; 155 | *) 156 | help_message 157 | ;; 158 | esac 159 | shift 160 | done -------------------------------------------------------------------------------- /bin/database.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source .env 3 | 4 | DOMAIN='' 5 | SQL_DB='' 6 | SQL_USER='' 7 | SQL_PASS='' 8 | ANY="'%'" 9 | SET_OK=0 10 | EPACE=' ' 11 | METHOD=0 12 | 13 | echow(){ 14 | FLAG=${1} 15 | shift 16 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 17 | } 18 | 19 | help_message(){ 20 | echo -e "\033[1mOPTIONS\033[0m" 21 | echow '-D, --domain [DOMAIN_NAME]' 22 | echo "${EPACE}${EPACE}Example: database.sh -D example.com" 23 | echo "${EPACE}${EPACE}Will auto-generate Database/username/password for the domain" 24 | echow '-D, --domain [DOMAIN_NAME] -U, --user [xxx] -P, --password [xxx] -DB, --database [xxx]' 25 | echo "${EPACE}${EPACE}Example: database.sh -D example.com -U USERNAME -P PASSWORD -DB DATABASENAME" 26 | echo "${EPACE}${EPACE}Will create Database/username/password by given" 27 | echow '-R, --delete -DB, --database [xxx] -U, --user [xxx]' 28 | echo "${EPACE}${EPACE}Example: database.sh -r -DB DATABASENAME -U USERNAME" 29 | echo "${EPACE}${EPACE}Will delete the database (require) and username (optional) by given" 30 | echow '-H, --help' 31 | echo "${EPACE}${EPACE}Display help and exit." 32 | exit 0 33 | } 34 | 35 | check_input(){ 36 | if [ -z "${1}" ]; then 37 | help_message 38 | exit 1 39 | fi 40 | } 41 | 42 | specify_name(){ 43 | check_input ${SQL_USER} 44 | check_input ${SQL_PASS} 45 | check_input ${SQL_DB} 46 | } 47 | 48 | auto_name(){ 49 | SQL_DB="${TRANSNAME}" 50 | SQL_USER="${TRANSNAME}" 51 | SQL_PASS="'${RANDOM_PASS}'" 52 | } 53 | 54 | gen_pass(){ 55 | RANDOM_PASS="$(openssl rand -base64 12)" 56 | } 57 | 58 | trans_name(){ 59 | TRANSNAME=$(echo ${1} | tr -d '.&&-') 60 | } 61 | 62 | display_credential(){ 63 | if [ ${SET_OK} = 0 ]; then 64 | echo "Database: ${SQL_DB}" 65 | echo "Username: ${SQL_USER}" 66 | echo "Password: $(echo ${SQL_PASS} | tr -d "'")" 67 | fi 68 | } 69 | 70 | store_credential(){ 71 | if [ -d "./sites/${1}" ]; then 72 | if [ -f ./sites/${1}/.db_pass ]; then 73 | mv ./sites/${1}/.db_pass ./sites/${1}/.db_pass.bk 74 | fi 75 | cat > "./sites/${1}/.db_pass" << EOT 76 | "Database":"${SQL_DB}" 77 | "Username":"${SQL_USER}" 78 | "Password":"$(echo ${SQL_PASS} | tr -d "'")" 79 | EOT 80 | else 81 | echo "./sites/${1} not found, abort credential store!" 82 | fi 83 | } 84 | 85 | check_db_access(){ 86 | docker compose exec -T mysql su -c "mariadb -uroot --password=${MYSQL_ROOT_PASSWORD} -e 'status'" >/dev/null 2>&1 87 | if [ ${?} != 0 ]; then 88 | echo '[X] DB access failed, please check!' 89 | exit 1 90 | fi 91 | } 92 | 93 | check_db_exist(){ 94 | docker compose exec -T mysql su -c "test -e /var/lib/mysql/${1}" 95 | if [ ${?} = 0 ]; then 96 | echo "Database ${1} already exist, skip DB creation!" 97 | exit 0 98 | fi 99 | } 100 | 101 | check_db_not_exist(){ 102 | docker compose exec -T mysql su -c "test -e /var/lib/mysql/${1}" 103 | if [ ${?} != 0 ]; then 104 | echo "Database ${1} doesn't exist, skip DB deletion!" 105 | exit 0 106 | fi 107 | } 108 | 109 | db_setup(){ 110 | docker compose exec -T mysql su -c 'mariadb -uroot --password=${MYSQL_ROOT_PASSWORD} \ 111 | -e "CREATE DATABASE '${SQL_DB}';" \ 112 | -e "GRANT ALL PRIVILEGES ON '${SQL_DB}'.* TO '${SQL_USER}'@'${ANY}' IDENTIFIED BY '${SQL_PASS}';" \ 113 | -e "FLUSH PRIVILEGES;"' 114 | SET_OK=${?} 115 | } 116 | 117 | db_delete(){ 118 | if [ "${SQL_DB}" == '' ]; then 119 | echo "Database parameter is required!" 120 | exit 0 121 | fi 122 | if [ "${SQL_USER}" == '' ]; then 123 | SQL_USER="${SQL_DB}" 124 | fi 125 | check_db_not_exist ${SQL_DB} 126 | docker compose exec -T mysql su -c 'mariadb -uroot --password=${MYSQL_ROOT_PASSWORD} \ 127 | -e "DROP DATABASE IF EXISTS '${SQL_DB}';" \ 128 | -e "DROP USER IF EXISTS '${SQL_USER}'@'${ANY}';" \ 129 | -e "FLUSH PRIVILEGES;"' 130 | echo "Database ${SQL_DB} and User ${SQL_USER} are deleted!" 131 | } 132 | 133 | auto_setup_main(){ 134 | check_input ${DOMAIN} 135 | gen_pass 136 | trans_name ${DOMAIN} 137 | auto_name 138 | check_db_exist ${SQL_DB} 139 | check_db_access 140 | db_setup 141 | display_credential 142 | store_credential ${DOMAIN} 143 | } 144 | 145 | specify_setup_main(){ 146 | specify_name 147 | check_db_exist ${SQL_DB} 148 | check_db_access 149 | db_setup 150 | display_credential 151 | store_credential ${DOMAIN} 152 | } 153 | 154 | main(){ 155 | if [ ${METHOD} == 1 ]; then 156 | db_delete 157 | exit 0 158 | fi 159 | if [ "${SQL_USER}" != '' ] && [ "${SQL_PASS}" != '' ] && [ "${SQL_DB}" != '' ]; then 160 | specify_setup_main 161 | else 162 | auto_setup_main 163 | fi 164 | } 165 | 166 | check_input ${1} 167 | while [ ! -z "${1}" ]; do 168 | case ${1} in 169 | -[hH] | -help | --help) 170 | help_message 171 | ;; 172 | -[dD] | -domain| --domain) shift 173 | DOMAIN="${1}" 174 | ;; 175 | -[uU] | -user | --user) shift 176 | SQL_USER="${1}" 177 | ;; 178 | -[pP] | -password| --password) shift 179 | SQL_PASS="'${1}'" 180 | ;; 181 | -db | -DB | -database| --database) shift 182 | SQL_DB="${1}" 183 | ;; 184 | -[rR] | -del | --del | --delete) 185 | METHOD=1 186 | ;; 187 | *) 188 | help_message 189 | ;; 190 | esac 191 | shift 192 | done 193 | main -------------------------------------------------------------------------------- /bin/container/owaspctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LSDIR='/usr/local/lsws' 3 | OWASP_DIR="${LSDIR}/conf/owasp" 4 | CRS_DIR='owasp-modsecurity-crs' 5 | RULE_FILE='modsec_includes.conf' 6 | LS_HTTPD_CONF="${LSDIR}/conf/httpd_config.xml" 7 | OLS_HTTPD_CONF="${LSDIR}/conf/httpd_config.conf" 8 | EPACE=' ' 9 | OWASP_V='4.3.0' 10 | 11 | echow(){ 12 | FLAG=${1} 13 | shift 14 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 15 | } 16 | 17 | help_message(){ 18 | echo -e "\033[1mOPTIONS\033[0m" 19 | echow '-E, --enable' 20 | echo "${EPACE}${EPACE}Will Enable mod_secure module with latest OWASP version of rules" 21 | echow '-D, --disable' 22 | echo "${EPACE}${EPACE}Will Disable mod_secure module with latest OWASP version of rules" 23 | echow '-H, --help' 24 | echo "${EPACE}${EPACE}Display help and exit." 25 | exit 0 26 | } 27 | 28 | check_lsv(){ 29 | if [ -f ${LSDIR}/bin/openlitespeed ]; then 30 | LSV='openlitespeed' 31 | elif [ -f ${LSDIR}/bin/litespeed ]; then 32 | LSV='lsws' 33 | else 34 | echo 'Version not exist, abort!' 35 | exit 1 36 | fi 37 | } 38 | 39 | check_input(){ 40 | if [ -z "${1}" ]; then 41 | help_message 42 | exit 1 43 | fi 44 | } 45 | 46 | mk_owasp_dir(){ 47 | if [ -d ${OWASP_DIR} ] ; then 48 | rm -rf ${OWASP_DIR} 49 | fi 50 | mkdir -p ${OWASP_DIR} 51 | if [ ${?} -ne 0 ] ; then 52 | echo "Unable to create directory: ${OWASP_DIR}, exit!" 53 | exit 1 54 | fi 55 | } 56 | 57 | fst_match_line(){ 58 | FIRST_LINE_NUM=$(grep -n -m 1 "${1}" ${2} | awk -F ':' '{print $1}') 59 | } 60 | fst_match_after(){ 61 | FIRST_NUM_AFTER=$(tail -n +${1} ${2} | grep -n -m 1 ${3} | awk -F ':' '{print $1}') 62 | } 63 | lst_match_line(){ 64 | fst_match_after ${1} ${2} ${3} 65 | LAST_LINE_NUM=$((${FIRST_LINE_NUM}+${FIRST_NUM_AFTER}-1)) 66 | } 67 | 68 | enable_ols_modsec(){ 69 | grep 'module mod_security {' ${OLS_HTTPD_CONF} >/dev/null 2>&1 70 | if [ ${?} -eq 0 ] ; then 71 | echo "Already configured for modsecurity." 72 | else 73 | echo 'Enable modsecurity' 74 | sed -i "s=module cache=module mod_security {\nmodsecurity on\ 75 | \nmodsecurity_rules \`\nSecRuleEngine On\n\`\nmodsecurity_rules_file \ 76 | ${OWASP_DIR}/${RULE_FILE}\n ls_enabled 1\n}\ 77 | \n\nmodule cache=" ${OLS_HTTPD_CONF} 78 | fi 79 | } 80 | 81 | enable_ls_modsec(){ 82 | grep '1' ${LS_HTTPD_CONF} >/dev/null 2>&1 83 | if [ ${?} -eq 0 ] ; then 84 | echo "LSWS already configured for modsecurity" 85 | else 86 | echo 'Enable modsecurity' 87 | sed -i \ 88 | "s=0=1=" ${LS_HTTPD_CONF} 89 | sed -i \ 90 | "s==\n\ 91 | \n\ 92 | ModSec\n\ 93 | 1\n\ 94 | include ${OWASP_DIR}/${RULE_FILE}\n\ 95 | =" ${LS_HTTPD_CONF} 96 | fi 97 | } 98 | 99 | enable_modsec(){ 100 | if [ "${LSV}" = 'lsws' ]; then 101 | enable_ls_modsec 102 | elif [ "${LSV}" = 'openlitespeed' ]; then 103 | enable_ols_modsec 104 | fi 105 | } 106 | 107 | disable_ols_modesec(){ 108 | grep 'module mod_security {' ${OLS_HTTPD_CONF} >/dev/null 2>&1 109 | if [ ${?} -eq 0 ] ; then 110 | echo 'Disable modsecurity' 111 | fst_match_line 'module mod_security' ${OLS_HTTPD_CONF} 112 | lst_match_line ${FIRST_LINE_NUM} ${OLS_HTTPD_CONF} '}' 113 | sed -i "${FIRST_LINE_NUM},${LAST_LINE_NUM}d" ${OLS_HTTPD_CONF} 114 | else 115 | echo 'Already disabled for modsecurity' 116 | fi 117 | } 118 | 119 | disable_ls_modesec(){ 120 | grep '0' ${LS_HTTPD_CONF} 121 | if [ ${?} -eq 0 ] ; then 122 | echo 'Already disabled for modsecurity' 123 | else 124 | echo 'Disable modsecurity' 125 | sed -i \ 126 | "s=1=0=" ${LS_HTTPD_CONF} 127 | fst_match_line 'censorshipRuleSet' ${LS_HTTPD_CONF} 128 | lst_match_line ${FIRST_LINE_NUM} ${LS_HTTPD_CONF} '/censorshipRuleSet' 129 | sed -i "${FIRST_LINE_NUM},${LAST_LINE_NUM}d" ${LS_HTTPD_CONF} 130 | fi 131 | } 132 | 133 | disable_modsec(){ 134 | check_lsv 135 | if [ "${LSV}" = 'lsws' ]; then 136 | disable_ls_modesec 137 | elif [ "${LSV}" = 'openlitespeed' ]; then 138 | disable_ols_modesec 139 | fi 140 | } 141 | 142 | install_unzip(){ 143 | if [ ! -f /usr/bin/unzip ]; then 144 | echo 'Install Unzip' 145 | apt update >/dev/null 2>&1 146 | apt-get install unzip -y >/dev/null 2>&1 147 | fi 148 | } 149 | 150 | backup_owasp(){ 151 | if [ -d ${OWASP_DIR} ]; then 152 | echo "Detect ${OWASP_DIR} folder exist, move to ${OWASP_DIR}.$(date +%F).bk" 153 | if [ -d ${OWASP_DIR}.$(date +%F).bk ]; then 154 | rm -rf ${OWASP_DIR}.$(date +%F).bk 155 | fi 156 | mv ${OWASP_DIR} ${OWASP_DIR}.$(date +%F).bk 157 | fi 158 | } 159 | 160 | install_owasp(){ 161 | cd ${OWASP_DIR} 162 | echo 'Download OWASP rules' 163 | wget -q https://github.com/coreruleset/coreruleset/archive/refs/tags/v${OWASP_V}.zip 164 | unzip -qq v${OWASP_V}.zip 165 | rm -f v${OWASP_V}.zip 166 | mv coreruleset-* ${CRS_DIR} 167 | } 168 | 169 | configure_owasp(){ 170 | echo 'Config OWASP rules.' 171 | cd ${OWASP_DIR} 172 | if [ -f ${CRS_DIR}/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example ]; then 173 | mv ${CRS_DIR}/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example ${CRS_DIR}/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf 174 | fi 175 | if [ -f ${CRS_DIR}/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example ]; then 176 | mv ${CRS_DIR}/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example ${CRS_DIR}/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf 177 | fi 178 | if [ -f ${RULE_FILE} ]; then 179 | mv ${RULE_FILE} ${RULE_FILE}.bk 180 | fi 181 | echo 'include modsecurity.conf' >> ${RULE_FILE} 182 | if [ -f ${CRS_DIR}/crs-setup.conf.example ]; then 183 | mv ${CRS_DIR}/crs-setup.conf.example ${CRS_DIR}/crs-setup.conf 184 | echo "include ${CRS_DIR}/crs-setup.conf" >> ${RULE_FILE} 185 | fi 186 | ALL_RULES="$(ls ${CRS_DIR}/rules/ | grep 'REQUEST-\|RESPONSE-')" 187 | echo "${ALL_RULES}" | while read LINE; do echo "include ${CRS_DIR}/rules/${LINE}" >> ${RULE_FILE}; done 188 | echo 'SecRuleEngine On' > modsecurity.conf 189 | chown -R lsadm ${OWASP_DIR} 190 | } 191 | 192 | main_owasp(){ 193 | backup_owasp 194 | mk_owasp_dir 195 | install_unzip 196 | install_owasp 197 | configure_owasp 198 | check_lsv 199 | enable_modsec 200 | } 201 | 202 | check_input ${1} 203 | while [ ! -z "${1}" ]; do 204 | case ${1} in 205 | -[hH] | -help | --help) 206 | help_message 207 | ;; 208 | -[eE] | -enable | --enable) 209 | main_owasp 210 | ;; 211 | -[dD] | -disable | --disable) 212 | disable_modsec 213 | ;; 214 | *) 215 | help_message 216 | ;; 217 | esac 218 | shift 219 | done -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiteSpeed WordPress Docker Container 2 | [![Build Status](https://github.com/litespeedtech/lsws-docker-env/workflows/docker-build/badge.svg)](https://github.com/litespeedtech/lsws-docker-env/actions/) 3 | [![docker pulls](https://img.shields.io/docker/pulls/litespeedtech/litespeed?style=flat&color=blue)](https://hub.docker.com/r/litespeedtech/litespeed) 4 | [](litespeedtech.com/slack) 5 | [](https://twitter.com/litespeedtech) 6 | 7 | Install a Lightweight WordPress container with LiteSpeed stable version based on Ubuntu 24.04 Linux. 8 | 9 | ### Prerequisites 10 | 1. [Install Docker](https://www.docker.com/) 11 | 2. [Install Docker Compose](https://docs.docker.com/compose/) 12 | 13 | ## Configuration 14 | Edit the `.env` file to update the demo site domain, default MySQL user, and password. 15 | Feel free to check [Docker hub Tag page](https://hub.docker.com/repository/docker/litespeedtech/litespeed/tags) if you want to update default litespeed and php versions. 16 | 17 | ## Installation 18 | Clone this repository or copy the files from this repository into a new folder: 19 | ``` 20 | git clone https://github.com/litespeedtech/lsws-docker-env.git 21 | ``` 22 | Open a terminal, `cd` to the folder in which `docker-compose.yml` is saved, and run: 23 | ``` 24 | docker compose up 25 | ``` 26 | 27 | Note: If you wish to run a single web server container, please see the [usage method here](https://github.com/litespeedtech/lsws-dockerfiles#usage). 28 | 29 | 30 | ## Components 31 | The docker image installs the following packages on your system: 32 | 33 | |Component|Version| 34 | | :-------------: | :-------------: | 35 | |Linux|Ubuntu 24.04| 36 | |LiteSpeed|[Latest version](https://www.litespeedtech.com/products/litespeed-web-server/download)| 37 | |MariaDB|[Stable version: 11.4](https://hub.docker.com/_/mariadb)| 38 | |PHP|[Latest version](http://rpms.litespeedtech.com/debian/)| 39 | |LiteSpeed Cache|[Latest from WordPress.org](https://wordpress.org/plugins/litespeed-cache/)| 40 | |ACME|[Latest from ACME official](https://github.com/acmesh-official/get.acme.sh)| 41 | |WordPress|[Latest from WordPress](https://wordpress.org/download/)| 42 | |phpMyAdmin|[Latest from dockerhub](https://hub.docker.com/r/bitnami/phpmyadmin/)| 43 | |Redis|[Latest from dockerhub](https://hub.docker.com/_/redis/)| 44 | 45 | ## Data Structure 46 | Cloned project 47 | ```bash 48 | ├── acme 49 | ├── bin 50 | │   └── container 51 | ├── data 52 | │   └── db 53 | ├── logs 54 | │   ├── access.log 55 | │   ├── error.log 56 | │   ├── lsrestart.log 57 | │   └── stderr.log 58 | ├── lsws 59 | │   ├── admin-conf 60 | │   └── conf 61 | ├── sites 62 | │ └── localhost 63 | ├── LICENSE 64 | ├── README.md 65 | └── docker-compose.yml 66 | ``` 67 | 68 | * `acme` contains all applied certificates from Lets Encrypt 69 | 70 | * `bin` contains multiple CLI scripts to allow you add or delete virtual hosts, install applications, upgrade, etc 71 | 72 | * `data` stores the MySQL database 73 | 74 | * `logs` contains all of the web server logs and virtual host access logs 75 | 76 | * `lsws` contains all web server configuration files 77 | 78 | * `sites` contains the document roots (the WordPress application will install here) 79 | 80 | ## Usage 81 | ### Starting a Container 82 | Start the container with the `up` or `start` methods: 83 | ``` 84 | docker compose up 85 | ``` 86 | You can run with daemon mode, like so: 87 | ``` 88 | docker compose up -d 89 | ``` 90 | The container is now built and running. 91 | 92 | Note: The container will auto-apply a 15-day trial license. Please contact LiteSpeed to extend the trial, or apply your own license, [starting from $0](https://www.litespeedtech.com/pricing). 93 | 94 | ### Stopping a Container 95 | ``` 96 | docker compose stop 97 | ``` 98 | ### Removing Containers 99 | To stop and remove all containers, use the `down` command: 100 | ``` 101 | docker compose down 102 | ``` 103 | ### Setting the WebAdmin Password 104 | We strongly recommend you set your personal password right away. 105 | ``` 106 | bash bin/webadmin.sh my_password 107 | ``` 108 | ### Starting a Demo Site 109 | After running the following command, you should be able to access the WordPress installation with the configured domain. By default the domain is http://localhost. 110 | ``` 111 | bash bin/demosite.sh 112 | ``` 113 | ### Creating a Domain and Virtual Host 114 | ``` 115 | bash bin/domain.sh [-A, --add] example.com 116 | ``` 117 | > Please ignore SSL certificate warnings from the server. They happen if you haven't applied the certificate. 118 | ### Deleting a Domain and Virtual Host 119 | ``` 120 | bash bin/domain.sh [-D, --del] example.com 121 | ``` 122 | ### Creating a Database 123 | You can either automatically generate the user, password, and database names, or specify them. Use the following to auto generate: 124 | ``` 125 | bash bin/database.sh [-D, --domain] example.com 126 | ``` 127 | Use this command to specify your own names, substituting `user_name`, `my_password`, and `database_name` with your preferred values: 128 | ``` 129 | bash bin/database.sh [-D, --domain] example.com [-U, --user] USER_NAME [-P, --password] MY_PASS [-DB, --database] DATABASE_NAME 130 | ``` 131 | ### Installing a WordPress Site 132 | To preconfigure the `wp-config` file, run the `database.sh` script for your domain, before you use the following command to install WordPress: 133 | ``` 134 | ./bin/appinstall.sh [-A, --app] wordpress [-D, --domain] example.com 135 | ``` 136 | 137 | ### Connecting to Redis 138 | Go to [WordPress > LSCache Plugin > Cache > Object](https://docs.litespeedtech.com/lscache/lscwp/cache/#object-tab), select **Redis** method and input `redis` to the Host field. 139 | 140 | ### Installing ACME 141 | We need to run the ACME installation command the **first time only**. 142 | With email notification: 143 | ``` 144 | ./bin/acme.sh [-I, --install] [-E, --email] EMAIL_ADDR 145 | ``` 146 | ### Applying a Let's Encrypt Certificate 147 | Use the root domain in this command, and it will check for a certificate and automatically apply one with and without `www`: 148 | ``` 149 | ./bin/acme.sh [-D, --domain] example.com 150 | ``` 151 | 152 | Other parameters: 153 | 154 | * [`-r`, `--renew`]: Renew a specific domain with -D or --domain parameter if posibile. To force renew, use -f parameter. 155 | 156 | * [`-R`, `--renew-all`]: Renew all domains if possible. To force renew, use -f parameter. 157 | 158 | * [`-f`, `-F`, `--force`]: Force renew for a specific domain or all domains. 159 | 160 | * [`-v`, `--revoke`]: Revoke a domain. 161 | 162 | * [`-V`, `--remove`]: Remove a domain. 163 | 164 | ### Updating Web Server 165 | To upgrade the web server to latest stable version, run the following: 166 | ``` 167 | bash bin/webadmin.sh [-U, --upgrade] 168 | ``` 169 | ### Applying OWASP ModSecurity 170 | Enable OWASP `mod_secure` on the web server: 171 | ``` 172 | bash bin/webadmin.sh [-M, --mod-secure] enable 173 | ``` 174 | Disable OWASP `mod_secure` on the web server: 175 | ``` 176 | bash bin/webadmin.sh [-M, --mod-secure] disable 177 | ``` 178 | >Please ignore ModSecurity warnings from the server. They happen if some of the rules are not supported by the server. 179 | ### Applying license to LSWS 180 | Apply your license with command: 181 | ``` 182 | bash bin/webadmin.sh [-S, --serial] YOUR_SERIAL 183 | ``` 184 | Apply trial license to server with command: 185 | ``` 186 | bash bin/webadmin.sh [-S, --serial] TRIAL 187 | ``` 188 | 189 | ### Accessing the Database 190 | After installation, you can use phpMinAdmin to access the database by visiting http://127.0.0.1:8080 or https://127.0.0.1:8443. The default username is `root`, and the password is the same as the one you supplied in the `.env` file. 191 | 192 | ## Customization 193 | If you want to customize the image by adding some packages, e.g. `lsphp83-pspell`, just extend it with a Dockerfile. 194 | 1. We can create a `custom` folder and a `custom/Dockerfile` file under the main project. 195 | 2. Add the following example code to `Dockerfile` under the custom folder 196 | ``` 197 | FROM litespeedtech/litespeed:latest 198 | RUN apt-get update && apt-get install lsphp83-pspell 199 | ``` 200 | 3. Add `build: ./custom` line under the "image: litespeedtech" of docker-compose file. So it will looks like this 201 | ``` 202 | litespeed: 203 | image: litespeedtech/litespeed:${LSWS_VERSION}-${PHP_VERSION} 204 | build: ./custom 205 | ``` 206 | 4. Build and start it with command: 207 | ``` 208 | docker compose up --build 209 | ``` 210 | 211 | ## Support & Feedback 212 | If you still have a question after using LiteSpeed Docker, you have a few options. 213 | * Join [the GoLiteSpeed Slack community](https://litespeedtech.com/slack/) for real-time discussion 214 | * Post to [the LiteSpeed Forums](https://www.litespeedtech.com/support/forum/) for community support 215 | * Reporting any issue on [Github lsws-docker-env](https://github.com/litespeedtech/lsws-docker-env/issues) project 216 | 217 | **Pull requests are always welcome** -------------------------------------------------------------------------------- /bin/container/appinstallctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DEFAULT_VH_ROOT='/var/www/vhosts' 4 | VH_DOC_ROOT='' 5 | APP='' 6 | DOMAIN='' 7 | WWW_UID='' 8 | WWW_GID='' 9 | USER='1000' 10 | WPCONSTCONF='' 11 | DB_HOST='mysql' 12 | PLUGINLIST="litespeed-cache.zip" 13 | THEME='twentytwenty' 14 | LSDIR='/usr/local/lsws' 15 | EMAIL='test@example.com' 16 | APP_ACCT='' 17 | APP_PASS='' 18 | SKIP_WP=0 19 | app_skip=0 20 | EPACE=' ' 21 | 22 | echoY() { 23 | echo -e "\033[38;5;148m${1}\033[39m" 24 | } 25 | echoG() { 26 | echo -e "\033[38;5;71m${1}\033[39m" 27 | } 28 | echoR() 29 | { 30 | echo -e "\033[38;5;203m${1}\033[39m" 31 | } 32 | echow(){ 33 | FLAG=${1} 34 | shift 35 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 36 | } 37 | 38 | help_message(){ 39 | case ${1} in 40 | "1") 41 | echo -e "\033[1mOPTIONS\033[0m" 42 | echow '-A, -app [wordpress|magento] -D, --domain [DOMAIN_NAME]' 43 | echo "${EPACE}${EPACE}Example: appinstallctl.sh --app wordpress --domain example.com" 44 | echow '-H, --help' 45 | echo "${EPACE}${EPACE}Display help and exit." 46 | exit 0 47 | ;; 48 | "2") 49 | echow 'Service finished, enjoy your accelarated LiteSpeed server!' 50 | ;; 51 | esac 52 | } 53 | 54 | check_input(){ 55 | if [ -z "${1}" ]; then 56 | help_message 1 57 | exit 1 58 | fi 59 | } 60 | 61 | linechange(){ 62 | LINENUM=$(grep -n "${1}" ${2} | cut -d: -f 1) 63 | if [ -n "${LINENUM}" ] && [ "${LINENUM}" -eq "${LINENUM}" ] 2>/dev/null; then 64 | sed -i "${LINENUM}d" ${2} 65 | sed -i "${LINENUM}i${3}" ${2} 66 | fi 67 | } 68 | 69 | ck_ed(){ 70 | if [ ! -f /bin/ed ]; then 71 | echo 'ed package not exist, please check!' 72 | exit 1 73 | fi 74 | } 75 | 76 | ck_unzip(){ 77 | if [ ! -f /usr/bin/unzip ]; then 78 | echo 'unzip package not exist, please check!' 79 | exit 1 80 | fi 81 | } 82 | 83 | gen_pass(){ 84 | APP_STR=$(shuf -i 100-999 -n1) 85 | APP_PASS=$(openssl rand -hex 16) 86 | APP_ACCT="admin${APP_STR}" 87 | MA_BACK_URL="admin_${APP_STR}" 88 | } 89 | 90 | get_owner(){ 91 | WWW_UID=$(stat -c "%u" ${DEFAULT_VH_ROOT}) 92 | WWW_GID=$(stat -c "%g" ${DEFAULT_VH_ROOT}) 93 | if [ ${WWW_UID} -eq 0 ] || [ ${WWW_GID} -eq 0 ]; then 94 | WWW_UID=1000 95 | WWW_GID=1000 96 | echo "Set owner to ${WWW_UID}" 97 | fi 98 | } 99 | 100 | get_db_pass(){ 101 | if [ -f ${DEFAULT_VH_ROOT}/${1}/.db_pass ]; then 102 | SQL_DB=$(grep -i Database ${VH_ROOT}/.db_pass | awk -F ':' '{print $2}' | tr -d '"') 103 | SQL_USER=$(grep -i Username ${VH_ROOT}/.db_pass | awk -F ':' '{print $2}' | tr -d '"') 104 | SQL_PASS=$(grep -i Password ${VH_ROOT}/.db_pass | awk -F ':' '{print $2}' | tr -d '"') 105 | else 106 | echo 'db pass file can not locate, skip wp-config pre-config.' 107 | fi 108 | } 109 | 110 | set_vh_docroot(){ 111 | if [ -d ${DEFAULT_VH_ROOT}/${1}/html ]; then 112 | VH_ROOT="${DEFAULT_VH_ROOT}/${1}" 113 | VH_DOC_ROOT="${DEFAULT_VH_ROOT}/${1}/html" 114 | WPCONSTCONF="${VH_DOC_ROOT}/wp-content/plugins/litespeed-cache/data/const.default.json" 115 | else 116 | echo "${DEFAULT_VH_ROOT}/${1}/html does not exist, please add domain first! Abort!" 117 | exit 1 118 | fi 119 | } 120 | 121 | check_sql_native(){ 122 | local COUNTER=0 123 | local LIMIT_NUM=100 124 | until [ "$(curl -v mysql:3306 2>&1 | grep -i 'native\|Connected')" ]; do 125 | echo "Counter: ${COUNTER}/${LIMIT_NUM}" 126 | COUNTER=$((COUNTER+1)) 127 | if [ ${COUNTER} = 10 ]; then 128 | echo '--- MySQL is starting, please wait... ---' 129 | elif [ ${COUNTER} = ${LIMIT_NUM} ]; then 130 | echo '--- MySQL is timeout, exit! ---' 131 | exit 1 132 | fi 133 | sleep 1 134 | done 135 | } 136 | 137 | install_wp_plugin(){ 138 | for PLUGIN in ${PLUGINLIST}; do 139 | wget -q -P ${VH_DOC_ROOT}/wp-content/plugins/ https://downloads.wordpress.org/plugin/${PLUGIN} 140 | if [ ${?} = 0 ]; then 141 | ck_unzip 142 | unzip -qq -o ${VH_DOC_ROOT}/wp-content/plugins/${PLUGIN} -d ${VH_DOC_ROOT}/wp-content/plugins/ 143 | else 144 | echo "${PLUGINLIST} FAILED to download" 145 | fi 146 | done 147 | rm -f ${VH_DOC_ROOT}/wp-content/plugins/*.zip 148 | } 149 | 150 | config_wp_htaccess(){ 151 | if [ ! -f ${VH_DOC_ROOT}/.htaccess ]; then 152 | touch ${VH_DOC_ROOT}/.htaccess 153 | fi 154 | cat << EOM > ${VH_DOC_ROOT}/.htaccess 155 | # BEGIN WordPress 156 | 157 | RewriteEngine On 158 | RewriteBase / 159 | RewriteRule ^index\.php$ - [L] 160 | RewriteCond %{REQUEST_FILENAME} !-f 161 | RewriteCond %{REQUEST_FILENAME} !-d 162 | RewriteRule . /index.php [L] 163 | 164 | # END WordPress 165 | EOM 166 | } 167 | 168 | 169 | get_theme_name(){ 170 | THEME_NAME=$(grep WP_DEFAULT_THEME ${VH_DOC_ROOT}/wp-includes/default-constants.php | grep -v '!' | awk -F "'" '{print $4}') 171 | echo "${THEME_NAME}" | grep 'twenty' >/dev/null 2>&1 172 | if [ ${?} = 0 ]; then 173 | THEME="${THEME_NAME}" 174 | fi 175 | } 176 | 177 | set_lscache(){ 178 | wget -q -O ${WPCONSTCONF} https://raw.githubusercontent.com/litespeedtech/lscache_wp/refs/heads/master/data/const.default.json 179 | if [ -f ${WPCONSTCONF} ]; then 180 | sed -ie 's/"object": .*"/"object": '\"true\"'/g' ${WPCONSTCONF} 181 | sed -ie 's/"object-kind": .*"/"object-kind": '\"true\"'/g' ${WPCONSTCONF} 182 | sed -ie 's/"object-host": .*"/"object-host": '\"redis\"'/g' ${WPCONSTCONF} 183 | sed -ie 's/"object-port": .*"/"object-port": '\"6379\"'/g' ${WPCONSTCONF} 184 | fi 185 | 186 | THEME_PATH="${VH_DOC_ROOT}/wp-content/themes/${THEME}" 187 | if [ ! -f ${THEME_PATH}/functions.php ]; then 188 | cat >> "${THEME_PATH}/functions.php" <>/dev/null 2>&1 201 | 2i 202 | require_once( WP_CONTENT_DIR.'/../wp-admin/includes/plugin.php' ); 203 | \$path = 'litespeed-cache/litespeed-cache.php' ; 204 | if (!is_plugin_active( \$path )) { 205 | activate_plugin( \$path ) ; 206 | rename( __FILE__ . '.bk', __FILE__ ); 207 | } 208 | . 209 | w 210 | q 211 | END 212 | fi 213 | } 214 | 215 | preinstall_wordpress(){ 216 | get_db_pass ${DOMAIN} 217 | if [ ! -f ${VH_DOC_ROOT}/wp-config.php ] && [ -f ${VH_DOC_ROOT}/wp-config-sample.php ]; then 218 | cp ${VH_DOC_ROOT}/wp-config-sample.php ${VH_DOC_ROOT}/wp-config.php 219 | NEWDBPWD="define('DB_PASSWORD', '${SQL_PASS}');" 220 | linechange 'DB_PASSWORD' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 221 | NEWDBPWD="define('DB_USER', '${SQL_USER}');" 222 | linechange 'DB_USER' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 223 | NEWDBPWD="define('DB_NAME', '${SQL_DB}');" 224 | linechange 'DB_NAME' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 225 | NEWDBPWD="define('DB_HOST', '${DB_HOST}');" 226 | linechange 'DB_HOST' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 227 | elif [ -f ${VH_DOC_ROOT}/wp-config.php ]; then 228 | echo "${VH_DOC_ROOT}/wp-config.php already exist, exit !" 229 | exit 1 230 | else 231 | echo 'Skip!' 232 | exit 2 233 | fi 234 | } 235 | 236 | app_wordpress_dl(){ 237 | if [ ! -f "${VH_DOC_ROOT}/wp-config.php" ] && [ ! -f "${VH_DOC_ROOT}/index.php" ]; then 238 | wp core download \ 239 | --allow-root \ 240 | --quiet 241 | else 242 | echoR 'wordpress or other file already exist, please manually clean up the document root folder, abort!' 243 | exit 1 244 | fi 245 | } 246 | 247 | change_owner(){ 248 | chown -R ${WWW_UID}:${WWW_GID} ${DEFAULT_VH_ROOT}/${DOMAIN} 249 | } 250 | 251 | store_access(){ 252 | cat << EOM > ${VH_ROOT}/.app_access 253 | Account: ${APP_ACCT} 254 | Password: ${APP_PASS} 255 | Admin_URL: ${MA_BACK_URL} 256 | EOM 257 | } 258 | 259 | show_access(){ 260 | echoG '----------------------------------------' 261 | echoY "Account: ${APP_ACCT}" 262 | echoY "Password: ${APP_PASS}" 263 | echoY "Admin_URL: ${MA_BACK_URL}" 264 | echoG '----------------------------------------' 265 | } 266 | 267 | main(){ 268 | set_vh_docroot ${DOMAIN} 269 | get_owner 270 | gen_pass 271 | cd ${VH_DOC_ROOT} 272 | if [ "${APP}" = 'wordpress' ] || [ "${APP}" = 'W' ]; then 273 | check_sql_native 274 | app_wordpress_dl 275 | preinstall_wordpress 276 | install_wp_plugin 277 | config_wp_htaccess 278 | get_theme_name 279 | set_lscache 280 | change_owner 281 | exit 0 282 | else 283 | echo "APP: ${APP} not support, exit!" 284 | exit 1 285 | fi 286 | help_message 2 287 | } 288 | 289 | check_input ${1} 290 | while [ ! -z "${1}" ]; do 291 | case ${1} in 292 | -[hH] | -help | --help) 293 | help_message 1 294 | ;; 295 | -[aA] | -app | --app) shift 296 | check_input "${1}" 297 | APP="${1}" 298 | ;; 299 | -[dD] | -domain | --domain) shift 300 | check_input "${1}" 301 | DOMAIN="${1}" 302 | ;; 303 | *) 304 | help_message 1 305 | ;; 306 | esac 307 | shift 308 | done 309 | main 310 | -------------------------------------------------------------------------------- /bin/acme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | EMAIL='' 3 | NO_EMAIL='' 4 | DOMAIN='' 5 | INSTALL='' 6 | UNINSTALL='' 7 | TYPE=0 8 | CONT_NAME='litespeed' 9 | ACME_SRC='https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh' 10 | EPACE=' ' 11 | RENEW='' 12 | RENEW_ALL='' 13 | FORCE='' 14 | REVOKE='' 15 | REMOVE='' 16 | 17 | echow(){ 18 | FLAG=${1} 19 | shift 20 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 21 | } 22 | 23 | help_message(){ 24 | case ${1} in 25 | "1") 26 | echo 'You will need to install acme script at the first time.' 27 | echo 'Please run acme.sh --install --email example@example.com' 28 | ;; 29 | "2") 30 | echo -e "\033[1mOPTIONS\033[0m" 31 | echow '-D, --domain [DOMAIN_NAME]' 32 | echo "${EPACE}${EPACE}Example: acme.sh --domain example.com" 33 | echo "${EPACE}${EPACE}will auto detect and apply for both example.com and www.example.com domains." 34 | echow '-H, --help' 35 | echo "${EPACE}${EPACE}Display help and exit." 36 | echo -e "\033[1m Only for the First time\033[0m" 37 | echow '--install --email [EMAIL_ADDR]' 38 | echo "${EPACE}${EPACE}Will install ACME with the Email provided" 39 | echow '-r, --renew' 40 | echo "${EPACE}${EPACE}Renew a specific domain with -D or --domain parameter if posibile. To force renew, use -f parameter." 41 | echow '-R, --renew-all' 42 | echo "${EPACE}${EPACE}Renew all domains if possible. To force renew, use -f parameter." 43 | echow '-f, -F, --force' 44 | echo "${EPACE}${EPACE}Force renew for a specific domain or all domains." 45 | echow '-v, --revoke' 46 | echo "${EPACE}${EPACE}Revoke a domain." 47 | echow '-V, --remove' 48 | echo "${EPACE}${EPACE}Remove a domain." 49 | exit 0 50 | ;; 51 | "3") 52 | echo 'Please run acme.sh --domain [DOMAIN_NAME] to apply certificate' 53 | exit 0 54 | ;; 55 | esac 56 | } 57 | 58 | check_input(){ 59 | if [ -z "${1}" ]; then 60 | help_message 2 61 | fi 62 | } 63 | 64 | domain_filter(){ 65 | if [ -z "${1}" ]; then 66 | help_message 3 67 | fi 68 | DOMAIN="${1}" 69 | DOMAIN="${DOMAIN#http://}" 70 | DOMAIN="${DOMAIN#https://}" 71 | DOMAIN="${DOMAIN#ftp://}" 72 | DOMAIN="${DOMAIN#scp://}" 73 | DOMAIN="${DOMAIN#scp://}" 74 | DOMAIN="${DOMAIN#sftp://}" 75 | DOMAIN=${DOMAIN%%/*} 76 | } 77 | 78 | email_filter(){ 79 | local EMAIL_CLEAN="${1%\"}" 80 | EMAIL_CLEAN="${EMAIL_CLEAN#\"}" 81 | 82 | CKREG="^[a-z0-9!#\$%&'*+/=?^_\`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\$" 83 | 84 | if [[ "${EMAIL_CLEAN}" =~ ${CKREG} ]]; then 85 | echo -e "[O] The E-mail \033[32m${EMAIL_CLEAN}\033[0m is valid." 86 | else 87 | echo -e "[X] The E-mail \e[31m${EMAIL_CLEAN}\e[39m is invalid" 88 | exit 1 89 | fi 90 | } 91 | 92 | cert_hook(){ 93 | echo '[Start] Adding ACME hook' 94 | docker compose exec ${CONT_NAME} su -s /bin/bash -c "certhookctl.sh" 95 | echo '[End] Adding ACME hook' 96 | } 97 | 98 | www_domain(){ 99 | CHECK_WWW=$(echo ${1} | cut -c1-4) 100 | if [[ ${CHECK_WWW} == www. ]] ; then 101 | DOMAIN=$(echo ${1} | cut -c 5-) 102 | else 103 | DOMAIN=${1} 104 | fi 105 | WWW_DOMAIN="www.${DOMAIN}" 106 | } 107 | 108 | domain_verify(){ 109 | curl -Is http://${DOMAIN}/ | grep -i LiteSpeed > /dev/null 2>&1 110 | if [ ${?} = 0 ]; then 111 | echo -e "[O] The domain name \033[32m${DOMAIN}\033[0m is accessible." 112 | TYPE=1 113 | curl -Is http://${WWW_DOMAIN}/ | grep -i LiteSpeed > /dev/null 2>&1 114 | if [ ${?} = 0 ]; then 115 | echo -e "[O] The domain name \033[32m${WWW_DOMAIN}\033[0m is accessible." 116 | TYPE=2 117 | else 118 | echo -e "[!] The domain name ${WWW_DOMAIN} is inaccessible." 119 | fi 120 | else 121 | echo -e "[X] The domain name \e[31m${DOMAIN}\e[39m is inaccessible, please verify." 122 | exit 1 123 | fi 124 | } 125 | 126 | install_acme(){ 127 | echo '[Start] Install ACME' 128 | if [ "${1}" = 'true' ]; then 129 | docker compose exec litespeed su -c " 130 | cd && 131 | wget ${ACME_SRC} && 132 | chmod 755 acme.sh && 133 | ./acme.sh --install --cert-home ~/.acme.sh/certs && 134 | /root/.acme.sh/acme.sh --set-default-ca --server letsencrypt && 135 | rm ~/acme.sh 136 | " 137 | elif [ "${2}" != '' ]; then 138 | email_filter \"${2}\" 139 | docker compose exec litespeed su -c " 140 | cd && 141 | wget ${ACME_SRC} && 142 | chmod 755 acme.sh && 143 | ./acme.sh --install --cert-home ~/.acme.sh/certs --accountemail ${2} && 144 | /root/.acme.sh/acme.sh --set-default-ca --server letsencrypt && 145 | rm ~/acme.sh 146 | " 147 | else 148 | help_message 1 149 | exit 1 150 | fi 151 | echo '[End] Install ACME' 152 | } 153 | 154 | uninstall_acme(){ 155 | echo '[Start] Uninstall ACME' 156 | docker compose exec ${CONT_NAME} su -c "~/.acme.sh/acme.sh --uninstall" 157 | echo '[End] Uninstall ACME' 158 | exit 0 159 | } 160 | 161 | check_acme(){ 162 | echo '[Start] Checking ACME' 163 | docker compose exec ${CONT_NAME} su -c "test -f /root/.acme.sh/acme.sh" 164 | if [ ${?} != 0 ]; then 165 | install_acme "${NO_EMAIL}" "${EMAIL}" 166 | cert_hook 167 | help_message 3 168 | fi 169 | echo '[End] Checking ACME' 170 | } 171 | 172 | lsws_restart(){ 173 | docker compose exec ${CONT_NAME} su -c '/usr/local/lsws/bin/lswsctrl restart >/dev/null' 174 | } 175 | 176 | doc_root_verify(){ 177 | if [ "${DOC_ROOT}" = '' ]; then 178 | DOC_PATH="/var/www/vhosts/${1}/html" 179 | else 180 | DOC_PATH="${DOC_ROOT}" 181 | fi 182 | docker compose exec ${CONT_NAME} su -c "[ -e ${DOC_PATH} ]" 183 | if [ ${?} -eq 0 ]; then 184 | echo -e "[O] The document root folder \033[32m${DOC_PATH}\033[0m does exist." 185 | else 186 | echo -e "[X] The document root folder \e[31m${DOC_PATH}\e[39m does not exist!" 187 | exit 1 188 | fi 189 | } 190 | 191 | install_cert(){ 192 | echo '[Start] Apply Lets Encrypt Certificate' 193 | if [ ${TYPE} = 1 ]; then 194 | docker compose exec ${CONT_NAME} su -c "/root/.acme.sh/acme.sh --issue -d ${1} -w ${DOC_PATH}" 195 | elif [ ${TYPE} = 2 ]; then 196 | docker compose exec ${CONT_NAME} su -c "/root/.acme.sh/acme.sh --issue -d ${1} -d www.${1} -w ${DOC_PATH}" 197 | else 198 | echo 'unknown Type!' 199 | exit 2 200 | fi 201 | echo '[End] Apply Lets Encrypt Certificate' 202 | } 203 | 204 | renew_acme(){ 205 | echo '[Start] Renew ACME' 206 | if [ "${FORCE}" = 'true' ]; then 207 | docker compose exec ${CONT_NAME} su -c "~/.acme.sh/acme.sh --renew --domain ${1} --force" 208 | else 209 | docker compose exec ${CONT_NAME} su -c "~/.acme.sh/acme.sh --renew --domain ${1}" 210 | fi 211 | echo '[End] Renew ACME' 212 | lsws_restart 213 | } 214 | 215 | renew_all_acme(){ 216 | echo '[Start] Renew all ACME' 217 | if [ "${FORCE}" = 'true' ]; then 218 | docker compose exec ${CONT_NAME} su -c "~/.acme.sh/acme.sh --renew-all --force" 219 | else 220 | docker compose exec ${CONT_NAME} su -c "~/.acme.sh/acme.sh --renew-all" 221 | fi 222 | echo '[End] Renew all ACME' 223 | lsws_restart 224 | } 225 | 226 | revoke(){ 227 | echo '[Start] Revoke a domain' 228 | docker compose exec ${CONT_NAME} su -c "~/.acme.sh/acme.sh --revoke --domain ${1}" 229 | echo '[End] Revoke a domain' 230 | lsws_restart 231 | } 232 | 233 | remove(){ 234 | echo '[Start] Remove a domain' 235 | docker compose exec ${CONT_NAME} su -c "~/.acme.sh/acme.sh --remove --domain ${1}" 236 | echo '[End] Remove a domain' 237 | lsws_restart 238 | } 239 | 240 | main(){ 241 | if [ "${RENEW_ALL}" = 'true' ]; then 242 | renew_all_acme 243 | exit 0 244 | elif [ "${RENEW}" = 'true' ]; then 245 | renew_acme ${DOMAIN} 246 | exit 0 247 | elif [ "${REVOKE}" = 'true' ]; then 248 | revoke ${DOMAIN} 249 | exit 0 250 | elif [ "${REMOVE}" = 'true' ]; then 251 | remove ${DOMAIN} 252 | exit 0 253 | fi 254 | 255 | check_acme 256 | domain_filter ${DOMAIN} 257 | www_domain ${DOMAIN} 258 | domain_verify 259 | doc_root_verify ${DOMAIN} 260 | install_cert ${DOMAIN} 261 | lsws_restart 262 | } 263 | 264 | check_input ${1} 265 | while [ ! -z "${1}" ]; do 266 | case ${1} in 267 | -[hH] | -help | --help) 268 | help_message 2 269 | ;; 270 | -[dD] | -domain | --domain) shift 271 | check_input "${1}" 272 | DOMAIN="${1}" 273 | ;; 274 | -[iI] | --install ) 275 | INSTALL=true 276 | ;; 277 | -[uU] | --uninstall ) 278 | UNINSTALL=true 279 | uninstall_acme 280 | ;; 281 | -[fF] | --force ) 282 | FORCE=true 283 | ;; 284 | -[r] | --renew ) 285 | RENEW=true 286 | ;; 287 | -[R] | --renew-all ) 288 | RENEW_ALL=true 289 | ;; 290 | -[v] | --revoke ) 291 | REVOKE=true 292 | ;; 293 | -[V] | --remove ) 294 | REMOVE=true 295 | ;; 296 | -[eE] | --email ) shift 297 | check_input "${1}" 298 | EMAIL="${1}" 299 | ;; 300 | *) 301 | help_message 2 302 | ;; 303 | esac 304 | shift 305 | done 306 | 307 | main --------------------------------------------------------------------------------