├── acme └── .gitignore ├── lsws └── .gitignore ├── logs └── .gitignore ├── sites ├── .gitignore └── localhost │ ├── html │ └── .gitignore │ └── logs │ └── .gitignore ├── .gitignore ├── bin ├── dev │ ├── list-flagged-files.sh │ ├── no-skip-worktree-conf.sh │ └── skip-worktree-conf.sh ├── container │ ├── certhookctl.sh │ ├── serialctl.sh │ ├── domainctl.sh │ ├── owaspctl.sh │ └── appinstallctl.sh ├── appinstall.sh ├── domain.sh ├── demosite.sh ├── webadmin.sh ├── database.sh ├── acme.sh └── mkcert.sh ├── .env ├── .github └── workflows │ └── docker.yml ├── .gitattributes ├── .travis ├── main.sh └── verify.sh ├── LICENSE ├── .travis.yml.bk ├── docker-compose.yml └── README.md /acme/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /lsws/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /sites/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /sites/localhost/html/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /sites/localhost/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | latest.yml 3 | config 4 | lsws/conf 5 | certs -------------------------------------------------------------------------------- /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 | OLS_VERSION=1.8.4 3 | PHP_VERSION=lsphp84 4 | PHPMYADMIN_VERSION=5.2.3 5 | MYSQL_ROOT_PASSWORD=your_root_password 6 | MYSQL_DATABASE=wordpress 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 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # Configure line ending normalisation for this repository. 3 | # See http://schacon.github.io/git/gitattributes.html for more information. 4 | # 5 | # Also each developer should configure the old style normalisation on her workstation 6 | # (see http://timclem.wordpress.com/2012/03/01/mind-the-end-of-your-line/): 7 | # 8 | # Windows user should use: git config --global core.autocrlf = true 9 | # Unix/Linux users should use: git config --global core.autocrlf = input 10 | # 11 | 12 | # Auto detect text files and perform LF normalization 13 | * text=auto 14 | 15 | *.txt text 16 | *.xml text diff=xml 17 | # Shell scripts require LF 18 | *.sh text eol=lf 19 | # Batch scripts require CRLF 20 | *.bat text eol=crlf 21 | -------------------------------------------------------------------------------- /.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. -------------------------------------------------------------------------------- /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 | echo "${EPACE}${EPACE}Will install WordPress CMS under the example.com domain" 17 | echow '-H, --help' 18 | echo "${EPACE}${EPACE}Display help and exit." 19 | exit 0 20 | } 21 | 22 | check_input(){ 23 | if [ -z "${1}" ]; then 24 | help_message 25 | exit 1 26 | fi 27 | } 28 | 29 | app_download(){ 30 | docker compose exec litespeed su -c "appinstallctl.sh --app ${1} --domain ${2}" 31 | bash bin/webadmin.sh -r 32 | exit 0 33 | } 34 | 35 | main(){ 36 | app_download ${APP_NAME} ${DOMAIN} 37 | } 38 | 39 | check_input ${1} 40 | while [ ! -z "${1}" ]; do 41 | case ${1} in 42 | -[hH] | -help | --help) 43 | help_message 44 | ;; 45 | -[aA] | -app | --app) shift 46 | check_input "${1}" 47 | APP_NAME="${1}" 48 | ;; 49 | -[dD] | -domain | --domain) shift 50 | check_input "${1}" 51 | DOMAIN="${1}" 52 | ;; 53 | *) 54 | help_message 55 | ;; 56 | esac 57 | shift 58 | done 59 | 60 | main -------------------------------------------------------------------------------- /.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: mSj4SYM4weApU3Ct+nqdaHkLw0J/q5+VH1q0LYnviZ06UpRU/N6lricfu9ihgND2VJ+cwfuQpAegdI1cDFzxDRpZpnzU9Db4N7OW5cDkb8eHpy6XhjQYi5KqWfgamh2UwiqYGgoQBc4gXhGDlChjJQopM+qPesHO9y/ucFAjxdlkEHVdZKNYDiVfiOKCGBzDuP+PbOPiZiqQFBgmFs1YLLrrQ7y5dgdoiai2I72MAN0kngoNB9ZsUgtQ63WTdgPKJOiX+oQMMXgYoP0+9iIhS6/cKHs64Z7jPreYYuWWMTnQPdvaIgh4ASIhUE6FVI5SdFxmajVik8SMlRK1rQApQLJ9wOJammUJHCSI4jfEVQ5H2og9R3+BA0qspBQVZXMTCYfX10Up1tmL+Kev1Za335v2z046gzX4aTiWBxi1I9mYmnYKQiGuaIG5crkPodIAeS9HX/DulMUhPRpa0Djwi7ZJlCAzfuEGDgAlWt/oWfIw66unTY/G6cEaxeEbZ3Ho+bPy48dRxhYW5kRHR1OuHqqfNULYeAGm6AIF3ng+2GjvXh6rhqmstBh/myROqM3X7ofUzEJRo9ow+hDroZLE4mfavn4UAnQybN7FfzuJiOoYTmcws7JzYD8b/G+Aynjw04m0ojDDJN0fVgQ+qltMoCfFMhx8FscF/QjGo/T/zc4= 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 | - bash .travis/main.sh 25 | 26 | install: 27 | - git clone https://github.com/litespeedtech/ols-docker-env.git 28 | - docker-compose up -d 29 | 30 | before_script: 31 | - docker image ls 32 | - sleep 10 33 | 34 | script: 35 | - bash .travis/verify.sh 36 | 37 | after_script: 38 | - docker-compose stop 39 | - docker-compose rm -f 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/openlitespeed:${OLS_VERSION}-${PHP_VERSION} 19 | container_name: litespeed 20 | env_file: 21 | - .env 22 | volumes: 23 | - ./lsws/conf:/usr/local/lsws/conf 24 | - ./lsws/admin-conf:/usr/local/lsws/admin/conf 25 | - ./bin/container:/usr/local/bin 26 | - ./sites:/var/www/vhosts/ 27 | - ./acme:/root/.acme.sh/ 28 | - ./logs:/usr/local/lsws/logs/ 29 | ports: 30 | - 80:80 31 | - 443:443 32 | - 443:443/udp 33 | - 7080:7080 34 | restart: always 35 | environment: 36 | TZ: ${TimeZone} 37 | networks: 38 | - default 39 | phpmyadmin: 40 | image: phpmyadmin/phpmyadmin:${PHPMYADMIN_VERSION} 41 | env_file: 42 | - .env 43 | ports: 44 | - 8080:80 45 | environment: 46 | PMA_HOST: mysql 47 | restart: always 48 | networks: 49 | - default 50 | redis: 51 | image: "redis:alpine" 52 | logging: 53 | driver: none 54 | # command: redis-server --requirepass 8b405f60665e48f795752e534d93b722 55 | volumes: 56 | - ./redis/data:/data 57 | - ./redis/redis.conf:/usr/local/etc/redis/redis.conf 58 | environment: 59 | - REDIS_REPLICATION_MODE=master 60 | restart: always 61 | networks: 62 | - default 63 | networks: 64 | default: 65 | driver: bridge 66 | -------------------------------------------------------------------------------- /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/demosite.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source .env 3 | APP_NAME='wordpress' 4 | CONT_NAME='litespeed' 5 | DOC_FD='' 6 | 7 | echow(){ 8 | FLAG=${1} 9 | shift 10 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 11 | } 12 | 13 | help_message(){ 14 | case ${1} in 15 | "1") 16 | echow "Script will get 'DOMAIN' and 'database' info from .env file, then auto setup virtual host and the wordpress site for you." 17 | exit 0 18 | ;; 19 | "2") 20 | echow 'Service finished, enjoy your accelarated LiteSpeed server!' 21 | ;; 22 | esac 23 | } 24 | 25 | domain_filter(){ 26 | if [ ! -n "${DOMAIN}" ]; then 27 | echo "Parameters not supplied, please check!" 28 | exit 1 29 | fi 30 | DOMAIN="${1}" 31 | DOMAIN="${DOMAIN#http://}" 32 | DOMAIN="${DOMAIN#https://}" 33 | DOMAIN="${DOMAIN#ftp://}" 34 | DOMAIN="${DOMAIN#scp://}" 35 | DOMAIN="${DOMAIN#scp://}" 36 | DOMAIN="${DOMAIN#sftp://}" 37 | DOMAIN=${DOMAIN%%/*} 38 | } 39 | 40 | gen_root_fd(){ 41 | DOC_FD="./sites/${1}/" 42 | if [ -d "./sites/${1}" ]; then 43 | echo -e "[O] The root folder \033[32m${DOC_FD}\033[0m exist." 44 | else 45 | echo "Creating - document root." 46 | bash bin/domain.sh -add ${1} 47 | echo "Finished - document root." 48 | fi 49 | } 50 | 51 | create_db(){ 52 | if [ ! -n "${MYSQL_DATABASE}" ] || [ ! -n "${MYSQL_USER}" ] || [ ! -n "${MYSQL_PASSWORD}" ]; then 53 | echo "Parameters not supplied, please check!" 54 | exit 1 55 | else 56 | bash bin/database.sh -D ${1} -U ${MYSQL_USER} -P ${MYSQL_PASSWORD} -DB ${MYSQL_DATABASE} 57 | fi 58 | } 59 | 60 | store_credential(){ 61 | if [ -f ${DOC_FD}/.db_pass ]; then 62 | echo '[O] db file exist!' 63 | else 64 | echo 'Storing database parameter' 65 | cat > "${DOC_FD}/.db_pass" << EOT 66 | "Database":"${MYSQL_DATABASE}" 67 | "Username":"${MYSQL_USER}" 68 | "Password":"$(echo ${MYSQL_PASSWORD} | tr -d "'")" 69 | EOT 70 | fi 71 | } 72 | 73 | app_download(){ 74 | docker compose exec -T ${CONT_NAME} su -c "appinstallctl.sh --app ${1} --domain ${2}" 75 | } 76 | 77 | lsws_restart(){ 78 | bash bin/webadmin.sh -r 79 | } 80 | 81 | main(){ 82 | domain_filter ${DOMAIN} 83 | gen_root_fd ${DOMAIN} 84 | create_db ${DOMAIN} 85 | store_credential 86 | app_download ${APP_NAME} ${DOMAIN} 87 | lsws_restart 88 | help_message 2 89 | } 90 | 91 | while [ ! -z "${1}" ]; do 92 | case ${1} in 93 | -[hH] | -help | --help) 94 | help_message 1 95 | ;; 96 | *) 97 | help_message 1 98 | ;; 99 | esac 100 | shift 101 | done 102 | main -------------------------------------------------------------------------------- /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/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 220 | -------------------------------------------------------------------------------- /bin/container/appinstallctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DEFAULT_VH_ROOT='/var/www/vhosts' 3 | VH_DOC_ROOT='' 4 | VHNAME='' 5 | APP_NAME='' 6 | DOMAIN='' 7 | WWW_UID='' 8 | WWW_GID='' 9 | WPCONSTCONF='' 10 | PUB_IP=$(curl -s http://checkip.amazonaws.com) 11 | DB_HOST='mysql' 12 | PLUGINLIST="litespeed-cache.zip" 13 | THEME='twentytwenty' 14 | EPACE=' ' 15 | 16 | echow(){ 17 | FLAG=${1} 18 | shift 19 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 20 | } 21 | 22 | help_message(){ 23 | echo -e "\033[1mOPTIONS\033[0m" 24 | echow '-A, -app [wordpress] -D, --domain [DOMAIN_NAME]' 25 | echo "${EPACE}${EPACE}Example: appinstallctl.sh --app wordpress --domain example.com" 26 | echow '-H, --help' 27 | echo "${EPACE}${EPACE}Display help and exit." 28 | exit 0 29 | } 30 | 31 | check_input(){ 32 | if [ -z "${1}" ]; then 33 | help_message 34 | exit 1 35 | fi 36 | } 37 | 38 | linechange(){ 39 | LINENUM=$(grep -n "${1}" ${2} | cut -d: -f 1) 40 | if [ -n "${LINENUM}" ] && [ "${LINENUM}" -eq "${LINENUM}" ] 2>/dev/null; then 41 | sed -i "${LINENUM}d" ${2} 42 | sed -i "${LINENUM}i${3}" ${2} 43 | fi 44 | } 45 | 46 | ck_ed(){ 47 | if [ ! -f /bin/ed ]; then 48 | echo "Install ed package.." 49 | apt-get install ed -y > /dev/null 2>&1 50 | fi 51 | } 52 | 53 | ck_unzip(){ 54 | if [ ! -f /usr/bin/unzip ]; then 55 | echo "Install unzip package.." 56 | apt-get install unzip -y > /dev/null 2>&1 57 | fi 58 | } 59 | 60 | get_owner(){ 61 | WWW_UID=$(stat -c "%u" ${DEFAULT_VH_ROOT}) 62 | WWW_GID=$(stat -c "%g" ${DEFAULT_VH_ROOT}) 63 | if [ ${WWW_UID} -eq 0 ] || [ ${WWW_GID} -eq 0 ]; then 64 | WWW_UID=1000 65 | WWW_GID=1000 66 | echo "Set owner to ${WWW_UID}" 67 | fi 68 | } 69 | 70 | get_db_pass(){ 71 | if [ -f ${DEFAULT_VH_ROOT}/${1}/.db_pass ]; then 72 | SQL_DB=$(grep -i Database ${VH_ROOT}/.db_pass | awk -F ':' '{print $2}' | tr -d '"') 73 | SQL_USER=$(grep -i Username ${VH_ROOT}/.db_pass | awk -F ':' '{print $2}' | tr -d '"') 74 | SQL_PASS=$(grep -i Password ${VH_ROOT}/.db_pass | awk -F ':' '{print $2}' | tr -d '"') 75 | else 76 | echo 'db pass file can not locate, skip wp-config pre-config.' 77 | fi 78 | } 79 | 80 | set_vh_docroot(){ 81 | if [ "${VHNAME}" != '' ]; then 82 | VH_ROOT="${DEFAULT_VH_ROOT}/${VHNAME}" 83 | VH_DOC_ROOT="${DEFAULT_VH_ROOT}/${VHNAME}/html" 84 | WPCONSTCONF="${VH_DOC_ROOT}/wp-content/plugins/litespeed-cache/data/const.default.json" 85 | elif [ -d ${DEFAULT_VH_ROOT}/${1}/html ]; then 86 | VH_ROOT="${DEFAULT_VH_ROOT}/${1}" 87 | VH_DOC_ROOT="${DEFAULT_VH_ROOT}/${1}/html" 88 | WPCONSTCONF="${VH_DOC_ROOT}/wp-content/plugins/litespeed-cache/data/const.default.json" 89 | else 90 | echo "${DEFAULT_VH_ROOT}/${1}/html does not exist, please add domain first! Abort!" 91 | exit 1 92 | fi 93 | } 94 | 95 | check_sql_native(){ 96 | local COUNTER=0 97 | local LIMIT_NUM=100 98 | until [ "$(curl -v mysql:3306 2>&1 | grep -i 'native\|Connected')" ]; do 99 | echo "Counter: ${COUNTER}/${LIMIT_NUM}" 100 | COUNTER=$((COUNTER+1)) 101 | if [ ${COUNTER} = 10 ]; then 102 | echo '--- MySQL is starting, please wait... ---' 103 | elif [ ${COUNTER} = ${LIMIT_NUM} ]; then 104 | echo '--- MySQL is timeout, exit! ---' 105 | exit 1 106 | fi 107 | sleep 1 108 | done 109 | } 110 | 111 | install_wp_plugin(){ 112 | for PLUGIN in ${PLUGINLIST}; do 113 | wget -q -P ${VH_DOC_ROOT}/wp-content/plugins/ https://downloads.wordpress.org/plugin/${PLUGIN} 114 | if [ ${?} = 0 ]; then 115 | ck_unzip 116 | unzip -qq -o ${VH_DOC_ROOT}/wp-content/plugins/${PLUGIN} -d ${VH_DOC_ROOT}/wp-content/plugins/ 117 | else 118 | echo "${PLUGINLIST} FAILED to download" 119 | fi 120 | done 121 | rm -f ${VH_DOC_ROOT}/wp-content/plugins/*.zip 122 | } 123 | 124 | set_htaccess(){ 125 | if [ ! -f ${VH_DOC_ROOT}/.htaccess ]; then 126 | touch ${VH_DOC_ROOT}/.htaccess 127 | fi 128 | cat << EOM > ${VH_DOC_ROOT}/.htaccess 129 | # BEGIN WordPress 130 | 131 | RewriteEngine On 132 | RewriteBase / 133 | RewriteRule ^index\.php$ - [L] 134 | RewriteCond %{REQUEST_FILENAME} !-f 135 | RewriteCond %{REQUEST_FILENAME} !-d 136 | RewriteRule . /index.php [L] 137 | 138 | # END WordPress 139 | EOM 140 | } 141 | 142 | get_theme_name(){ 143 | THEME_NAME=$(grep WP_DEFAULT_THEME ${VH_DOC_ROOT}/wp-includes/default-constants.php | grep -v '!' | awk -F "'" '{print $4}') 144 | echo "${THEME_NAME}" | grep 'twenty' >/dev/null 2>&1 145 | if [ ${?} = 0 ]; then 146 | THEME="${THEME_NAME}" 147 | fi 148 | } 149 | 150 | set_lscache(){ 151 | wget -q -O ${WPCONSTCONF} https://raw.githubusercontent.com/litespeedtech/lscache_wp/refs/heads/master/data/const.default.json 152 | if [ -f ${WPCONSTCONF} ]; then 153 | sed -ie 's/"object": .*"/"object": '\"true\"'/g' ${WPCONSTCONF} 154 | sed -ie 's/"object-kind": .*"/"object-kind": '\"true\"'/g' ${WPCONSTCONF} 155 | sed -ie 's/"object-host": .*"/"object-host": '\"redis\"'/g' ${WPCONSTCONF} 156 | sed -ie 's/"object-port": .*"/"object-port": '\"6379\"'/g' ${WPCONSTCONF} 157 | fi 158 | THEME_PATH="${VH_DOC_ROOT}/wp-content/themes/${THEME}" 159 | if [ ! -f ${THEME_PATH}/functions.php ]; then 160 | cat >> "${THEME_PATH}/functions.php" <>/dev/null 2>&1 173 | 2i 174 | require_once( WP_CONTENT_DIR.'/../wp-admin/includes/plugin.php' ); 175 | \$path = 'litespeed-cache/litespeed-cache.php' ; 176 | if (!is_plugin_active( \$path )) { 177 | activate_plugin( \$path ) ; 178 | rename( __FILE__ . '.bk', __FILE__ ); 179 | } 180 | . 181 | w 182 | q 183 | END 184 | fi 185 | } 186 | 187 | preinstall_wordpress(){ 188 | if [ "${VHNAME}" != '' ]; then 189 | get_db_pass ${VHNAME} 190 | else 191 | get_db_pass ${DOMAIN} 192 | fi 193 | if [ ! -f ${VH_DOC_ROOT}/wp-config.php ] && [ -f ${VH_DOC_ROOT}/wp-config-sample.php ]; then 194 | cp ${VH_DOC_ROOT}/wp-config-sample.php ${VH_DOC_ROOT}/wp-config.php 195 | NEWDBPWD="define('DB_PASSWORD', '${SQL_PASS}');" 196 | linechange 'DB_PASSWORD' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 197 | NEWDBPWD="define('DB_USER', '${SQL_USER}');" 198 | linechange 'DB_USER' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 199 | NEWDBPWD="define('DB_NAME', '${SQL_DB}');" 200 | linechange 'DB_NAME' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 201 | #NEWDBPWD="define('DB_HOST', '${PUB_IP}');" 202 | NEWDBPWD="define('DB_HOST', '${DB_HOST}');" 203 | linechange 'DB_HOST' ${VH_DOC_ROOT}/wp-config.php "${NEWDBPWD}" 204 | elif [ -f ${VH_DOC_ROOT}/wp-config.php ]; then 205 | echo "${VH_DOC_ROOT}/wp-config.php already exist, exit !" 206 | exit 1 207 | else 208 | echo 'Skip!' 209 | exit 2 210 | fi 211 | } 212 | 213 | app_wordpress_dl(){ 214 | if [ ! -f "${VH_DOC_ROOT}/wp-config.php" ] && [ ! -f "${VH_DOC_ROOT}/wp-config-sample.php" ]; then 215 | wp core download \ 216 | --allow-root \ 217 | --quiet 218 | else 219 | echo 'wordpress already exist, abort!' 220 | exit 1 221 | fi 222 | } 223 | 224 | change_owner(){ 225 | if [ "${VHNAME}" != '' ]; then 226 | chown -R ${WWW_UID}:${WWW_GID} ${DEFAULT_VH_ROOT}/${VHNAME} 227 | else 228 | chown -R ${WWW_UID}:${WWW_GID} ${DEFAULT_VH_ROOT}/${DOMAIN} 229 | fi 230 | } 231 | 232 | main(){ 233 | set_vh_docroot ${DOMAIN} 234 | get_owner 235 | cd ${VH_DOC_ROOT} 236 | if [ "${APP_NAME}" = 'wordpress' ] || [ "${APP_NAME}" = 'wp' ]; then 237 | check_sql_native 238 | app_wordpress_dl 239 | preinstall_wordpress 240 | install_wp_plugin 241 | set_htaccess 242 | get_theme_name 243 | set_lscache 244 | change_owner 245 | exit 0 246 | else 247 | echo "APP: ${APP_NAME} not support, exit!" 248 | exit 1 249 | fi 250 | } 251 | 252 | check_input ${1} 253 | while [ ! -z "${1}" ]; do 254 | case ${1} in 255 | -[hH] | -help | --help) 256 | help_message 257 | ;; 258 | -[aA] | -app | --app) shift 259 | check_input "${1}" 260 | APP_NAME="${1}" 261 | ;; 262 | -[dD] | -domain | --domain) shift 263 | check_input "${1}" 264 | DOMAIN="${1}" 265 | ;; 266 | -vhname | --vhname) shift 267 | VHNAME="${1}" 268 | ;; 269 | *) 270 | help_message 271 | ;; 272 | esac 273 | shift 274 | done 275 | main 276 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenLiteSpeed WordPress Docker Container 2 | 3 | ![ols-docker-env](https://socialify.git.ci/litespeedtech/ols-docker-env/image?custom_language=Shell&description=1&font=Inter&forks=1&issues=1&language=1&logo=https%3A%2F%2Fwww.litespeedtech.com%2Fimages%2Flogos%2Flitespeed%2Flitespeed-logo-square.svg&name=1&owner=1&pattern=Plus&pulls=1&stargazers=1&theme=Auto) 4 | 5 | [![Build Status](https://github.com/litespeedtech/ols-docker-env/workflows/docker-build/badge.svg)](https://github.com/litespeedtech/ols-docker-env/actions/) 6 | [![docker pulls](https://img.shields.io/docker/pulls/litespeedtech/openlitespeed?style=flat&color=blue)](https://hub.docker.com/r/litespeedtech/openlitespeed) 7 | [![LiteSpeed on Slack](https://img.shields.io/badge/slack-LiteSpeed-blue.svg?logo=slack)](https://litespeedtech.com/slack) 8 | [![Follow on Twitter](https://img.shields.io/twitter/follow/litespeedtech.svg?label=Follow&style=social)](https://twitter.com/litespeedtech) 9 | 10 | Install a lightweight WordPress container with OpenLiteSpeed Edge or Stable version based on Ubuntu 24.04 Linux. 11 | 12 | ## Prerequisites 13 | 14 | 1. [Install Docker](https://www.docker.com/) 15 | 2. [Install Docker Compose](https://docs.docker.com/compose/) 16 | 17 | ## Configuration 18 | 19 | Edit the `.env` file to update the demo site domain, default MySQL user, and password. 20 | Feel free to check [Docker hub Tag page](https://hub.docker.com/repository/docker/litespeedtech/openlitespeed/tags) if you want to update default openlitespeed and php versions. 21 | 22 | ## Installation 23 | 24 | Clone this repository or copy the files from this repository into a new folder: 25 | 26 | ```bash 27 | git clone https://github.com/litespeedtech/ols-docker-env.git 28 | ``` 29 | 30 | Open a terminal, `cd` to the folder in which `docker compose.yml` is saved, and run: 31 | 32 | ```bash 33 | docker compose up 34 | ``` 35 | 36 | Note: If you wish to run a single web server container, please see the [usage method here](https://github.com/litespeedtech/ols-dockerfiles#usage). 37 | 38 | ## Components 39 | 40 | The docker image installs the following packages on your system: 41 | 42 | |Component|Version| 43 | | :-------------: | :-------------: | 44 | |Linux|Ubuntu 24.04| 45 | |OpenLiteSpeed|[Latest version](https://hub.docker.com/r/litespeedtech/openlitespeed)| 46 | |MariaDB|[Stable version: 11.4](https://hub.docker.com/_/mariadb)| 47 | |PHP|[Latest version](http://rpms.litespeedtech.com/debian/)| 48 | |LiteSpeed Cache|[Latest from WordPress.org](https://wordpress.org/plugins/litespeed-cache/)| 49 | |ACME|[Latest from ACME official](https://github.com/acmesh-official/get.acme.sh)| 50 | |WordPress|[Latest from WordPress](https://wordpress.org/download/)| 51 | |phpMyAdmin|[Latest from dockerhub](https://hub.docker.com/r/phpmyadmin/phpmyadmin/)| 52 | |Redis|[Latest from dockerhub](https://hub.docker.com/_/redis/)| 53 | 54 | ## Data Structure 55 | 56 | Cloned project 57 | 58 | ```bash 59 | ├── acme 60 | ├── bin 61 | │   └── container 62 | ├── data 63 | │   └── db 64 | ├── logs 65 | │   ├── access.log 66 | │   ├── error.log 67 | │   ├── lsrestart.log 68 | │   └── stderr.log 69 | ├── lsws 70 | │   ├── admin-conf 71 | │   └── conf 72 | ├── sites 73 | │ └── localhost 74 | ├── LICENSE 75 | ├── README.md 76 | └── docker-compose.yml 77 | ``` 78 | 79 | * `acme` contains all applied certificates from Lets Encrypt 80 | 81 | * `bin` contains multiple CLI scripts to allow you add or delete virtual hosts, install applications, upgrade, etc 82 | 83 | * `data` stores the MySQL database 84 | 85 | * `logs` contains all of the web server logs and virtual host access logs 86 | 87 | * `lsws` contains all web server configuration files 88 | 89 | * `sites` contains the document roots (the WordPress application will install here) 90 | 91 | ## Usage 92 | 93 | ### Starting a Container 94 | 95 | Start the container with the `up` or `start` methods: 96 | 97 | ```bash 98 | docker compose up 99 | ``` 100 | 101 | You can run with daemon mode, like so: 102 | 103 | ```bash 104 | docker compose up -d 105 | ``` 106 | 107 | The container is now built and running. 108 | 109 | ### Stopping a Container 110 | 111 | ```bash 112 | docker compose stop 113 | ``` 114 | 115 | ### Removing Containers 116 | 117 | To stop and remove all containers, use the `down` command: 118 | 119 | ```bash 120 | docker compose down 121 | ``` 122 | 123 | ### Setting the WebAdmin Password 124 | 125 | We strongly recommend you set your personal password right away. 126 | 127 | ```bash 128 | bash bin/webadmin.sh my_password 129 | ``` 130 | 131 | ### Starting a Demo Site 132 | 133 | After running the following command, you should be able to access the WordPress installation with the configured domain. By default the domain is . 134 | 135 | ```bash 136 | bash bin/demosite.sh 137 | ``` 138 | 139 | ### Creating a Domain and Virtual Host 140 | 141 | ```bash 142 | bash bin/domain.sh [-A, --add] example.com 143 | ``` 144 | 145 | > Please ignore SSL certificate warnings from the server. They happen if you haven't applied the certificate. 146 | > 147 | ### Deleting a Domain and Virtual Host 148 | 149 | ```bash 150 | bash bin/domain.sh [-D, --del] example.com 151 | ``` 152 | 153 | ### Creating a Database 154 | 155 | You can either automatically generate the user, password, and database names, or specify them. Use the following to auto generate: 156 | 157 | ```bash 158 | bash bin/database.sh [-D, --domain] example.com 159 | ``` 160 | 161 | Use this command to specify your own names, substituting `user_name`, `my_password`, and `database_name` with your preferred values: 162 | 163 | ```bash 164 | bash bin/database.sh [-D, --domain] example.com [-U, --user] USER_NAME [-P, --password] MY_PASS [-DB, --database] DATABASE_NAME 165 | ``` 166 | 167 | ### Installing a WordPress Site 168 | 169 | To preconfigure the `wp-config` file, run the `database.sh` script for your domain, before you use the following command to install WordPress: 170 | 171 | ```bash 172 | bash bin/appinstall.sh [-A, --app] wordpress [-D, --domain] example.com 173 | ``` 174 | 175 | ### Connecting to Redis 176 | 177 | 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. 178 | 179 | ### Install ACME 180 | 181 | We need to run the ACME installation command the **first time only**. 182 | With email notification: 183 | 184 | ```bash 185 | bash bin/acme.sh [-I, --install] [-E, --email] EMAIL_ADDR 186 | ``` 187 | 188 | ### Applying a Let's Encrypt Certificate 189 | 190 | Use the root domain in this command, and it will check for a certificate and automatically apply one with and without `www`: 191 | 192 | ```bash 193 | bash bin/acme.sh [-D, --domain] example.com 194 | ``` 195 | 196 | Other parameters: 197 | 198 | * [`-r`, `--renew`]: Renew a specific domain with -D or --domain parameter if posibile. To force renew, use -f parameter. 199 | 200 | * [`-R`, `--renew-all`]: Renew all domains if possible. To force renew, use -f parameter. 201 | 202 | * [`-f`, `-F`, `--force`]: Force renew for a specific domain or all domains. 203 | 204 | * [`-v`, `--revoke`]: Revoke a domain. 205 | 206 | * [`-V`, `--remove`]: Remove a domain. 207 | 208 | ### Using mkcert for Local Development SSL 209 | 210 | For local development domains (`.test`, `.local`, `.dev`, etc.), you can use `mkcert` to generate trusted SSL certificates without warnings. 211 | 212 | #### Installing mkcert 213 | 214 | First-time installation (Windows with Chocolatey): 215 | 216 | ```bash 217 | bash bin/mkcert.sh --install 218 | ``` 219 | 220 | This will: 221 | 222 | * Install `mkcert` via Chocolatey 223 | * Create and install a local Certificate Authority (CA) in your system trust store 224 | 225 | #### Generating Local SSL Certificate 226 | 227 | After adding a domain to your environment, generate an SSL certificate: 228 | 229 | ```bash 230 | bash bin/mkcert.sh [-D, --domain] example.test 231 | ``` 232 | 233 | This will: 234 | 235 | 1. Check if the domain exists in your configuration 236 | 2. Generate certificates for `example.test` and `www.example.test` 237 | 3. Create a `dockerLocal` template with SSL configuration 238 | 4. Copy certificates to the container 239 | 5. Move the domain from the standard template to the SSL-enabled template 240 | 6. Restart OpenLiteSpeed 241 | 242 | Your domain will now be accessible via HTTPS with a trusted certificate at `https://example.test` 243 | 244 | #### Removing Local SSL Certificate 245 | 246 | To remove the SSL certificate and revert to HTTP: 247 | 248 | ```bash 249 | bash bin/mkcert.sh [-R, --remove] [-D, --domain] example.test 250 | ``` 251 | 252 | This will: 253 | 254 | 1. Remove the domain from the `dockerLocal` template 255 | 2. Move it back to the standard `docker` template 256 | 3. Delete certificate files from both host and container 257 | 4. Clean up empty templates if no other domains use SSL 258 | 5. Restart OpenLiteSpeed 259 | 260 | > **Important**: You must add the domain to your environment first using `bash bin/domain.sh --add example.test` before generating certificates. 261 | 262 | ### Update Web Server 263 | 264 | To upgrade the web server to latest stable version, run the following: 265 | 266 | ```bash 267 | bash bin/webadmin.sh [-U, --upgrade] 268 | ``` 269 | 270 | ### Apply OWASP ModSecurity 271 | 272 | Enable OWASP `mod_secure` on the web server: 273 | 274 | ```bash 275 | bash bin/webadmin.sh [-M, --mod-secure] enable 276 | ``` 277 | 278 | Disable OWASP `mod_secure` on the web server: 279 | 280 | ```bash 281 | bash bin/webadmin.sh [-M, --mod-secure] disable 282 | ``` 283 | 284 | >Please ignore ModSecurity warnings from the server. They happen if some of the rules are not supported by the server. 285 | > 286 | ### Accessing the Database 287 | 288 | After installation, you can use phpMyAdmin 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. 289 | 290 | ## Customization 291 | 292 | If you want to customize the image by adding some packages, e.g. `lsphp83-pspell`, just extend it with a Dockerfile. 293 | 294 | 1. We can create a `custom` folder and a `custom/Dockerfile` file under the main project. 295 | 2. Add the following example code to `Dockerfile` under the custom folder 296 | 297 | ```bash 298 | FROM litespeedtech/openlitespeed:latest 299 | RUN apt-get update && apt-get install lsphp83-pspell -y 300 | ``` 301 | 302 | 3. Add `build: ./custom` line under the "image: litespeedtech" of docker-composefile. So it will looks like this 303 | 304 | ```bash 305 | litespeed: 306 | image: litespeedtech/openlitespeed:${OLS_VERSION}-${PHP_VERSION} 307 | build: ./custom 308 | ``` 309 | 310 | 4. Build and start it with command: 311 | 312 | ```bash 313 | docker compose up --build 314 | ``` 315 | 316 | ## Support & Feedback 317 | 318 | If you still have a question after using OpenLiteSpeed Docker, you have a few options. 319 | 320 | * Join [the GoLiteSpeed Slack community](https://litespeedtech.com/slack) for real-time discussion 321 | * Post to [the OpenLiteSpeed Forums](https://forum.openlitespeed.org/) for community support 322 | * Reporting any issue on [Github ols-docker-env](https://github.com/litespeedtech/ols-docker-env/issues) project 323 | 324 | **_Pull requests are always welcome!_** 325 | -------------------------------------------------------------------------------- /bin/mkcert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DOMAIN='' 3 | INSTALL='' 4 | REMOVE='' 5 | CONT_NAME='litespeed' 6 | CERT_DIR='./certs' 7 | EPACE=' ' 8 | 9 | echow(){ 10 | FLAG=${1} 11 | shift 12 | echo -e "\033[1m${EPACE}${FLAG}\033[0m${@}" 13 | } 14 | 15 | help_message(){ 16 | echo -e "\033[1mUSAGE\033[0m" 17 | echo "${EPACE}mkcert.sh [OPTIONS]" 18 | echo "" 19 | echo -e "\033[1mOPTIONS\033[0m" 20 | echow '-D, --domain [DOMAIN_NAME]' 21 | echo "${EPACE}${EPACE}Example: mkcert.sh --domain example.test" 22 | echo "${EPACE}${EPACE}Will create certificate for example.test and www.example.test" 23 | echow '-I, --install' 24 | echo "${EPACE}${EPACE}Install mkcert on Windows (requires Chocolatey)" 25 | echow '-R, --remove' 26 | echo "${EPACE}${EPACE}Remove certificate for a specific domain. Must be used with --domain." 27 | echo "${EPACE}${EPACE}Example: mkcert.sh --remove --domain example.test" 28 | echow '-H, --help' 29 | echo "${EPACE}${EPACE}Display help and exit" 30 | exit 0 31 | } 32 | 33 | check_input(){ 34 | if [ -z "${1}" ]; then 35 | help_message 36 | fi 37 | } 38 | 39 | domain_filter(){ 40 | if [ -z "${1}" ]; then 41 | echo "[X] Domain name is required!" 42 | exit 1 43 | fi 44 | DOMAIN="${1}" 45 | DOMAIN="${DOMAIN#http://}" 46 | DOMAIN="${DOMAIN#https://}" 47 | DOMAIN="${DOMAIN#ftp://}" 48 | DOMAIN="${DOMAIN%%/*}" 49 | } 50 | 51 | www_domain(){ 52 | CHECK_WWW=$(echo ${1} | cut -c1-4) 53 | if [[ ${CHECK_WWW} == www. ]] ; then 54 | DOMAIN=$(echo ${1} | cut -c 5-) 55 | else 56 | DOMAIN=${1} 57 | fi 58 | WWW_DOMAIN="www.${DOMAIN}" 59 | } 60 | 61 | check_mkcert() { 62 | echo "[Start] Checking mkcert installation..." 63 | 64 | if MKCERT_CMD=$(command -v mkcert.exe 2>/dev/null || command -v mkcert 2>/dev/null); then 65 | echo "[✔] mkcert found at: ${MKCERT_CMD}" 66 | else 67 | echo "[✖] mkcert not found!" 68 | echo "→ Please run 'bash bin/mkcert.sh --install' or install it manually." 69 | echo " Windows: choco install mkcert" 70 | echo " (Linux/macOS support can be added here later)" 71 | exit 1 72 | fi 73 | 74 | echo "[End] mkcert check completed." 75 | } 76 | 77 | install_mkcert() { 78 | echo "[Start] Installing mkcert..." 79 | case "$(uname -s)" in 80 | Linux*) OS="linux" ;; 81 | Darwin*) OS="mac" ;; 82 | MINGW*|MSYS*|CYGWIN*|Windows*) OS="windows" ;; 83 | *) echo "[X] Unsupported OS: $(uname -s)"; exit 1 ;; 84 | esac 85 | echo "[*] Detected OS: $OS" 86 | if command -v mkcert >/dev/null 2>&1 || command -v mkcert.exe >/dev/null 2>&1; then 87 | echo "[O] mkcert is already installed." 88 | echo "[!] Ensuring local CA is installed..." 89 | (command -v mkcert.exe >/dev/null 2>&1 && mkcert.exe -install || mkcert -install) 90 | echo "[O] Local CA configured." 91 | return 0 92 | fi 93 | case "$OS" in 94 | windows) 95 | if ! command -v choco >/dev/null 2>&1 && ! command -v choco.exe >/dev/null 2>&1; then 96 | echo "[X] Chocolatey not found!" 97 | echo "Install it first: https://chocolatey.org/install" 98 | exit 1 99 | fi 100 | choco install mkcert -y 101 | ;; 102 | mac) 103 | if ! command -v brew >/dev/null 2>&1; then 104 | echo "[X] Homebrew not found!" 105 | echo "Install it from https://brew.sh/" 106 | exit 1 107 | fi 108 | brew install mkcert nss 109 | ;; 110 | linux) 111 | if command -v apt >/dev/null 2>&1; then 112 | sudo apt update -y && sudo apt install -y mkcert libnss3-tools 113 | elif command -v dnf >/dev/null 2>&1; then 114 | sudo dnf install -y mkcert nss-tools 115 | elif command -v yum >/dev/null 2>&1; then 116 | sudo yum install -y mkcert nss-tools 117 | elif command -v zypper >/dev/null 2>&1; then 118 | sudo zypper install -y mkcert mozilla-nss-tools 119 | else 120 | echo "[X] Unsupported Linux distro. Install manually:" 121 | echo "→ https://github.com/FiloSottile/mkcert" 122 | exit 1 123 | fi 124 | ;; 125 | esac 126 | if command -v mkcert >/dev/null 2>&1 || command -v mkcert.exe >/dev/null 2>&1; then 127 | echo "[O] mkcert installed successfully." 128 | echo "[!] Creating local CA..." 129 | (command -v mkcert.exe >/dev/null 2>&1 && mkcert.exe -install || mkcert -install) 130 | echo "[O] Local CA configured." 131 | echo "[End] mkcert installation complete." 132 | else 133 | echo "[X] mkcert installation failed!" 134 | exit 1 135 | fi 136 | } 137 | 138 | create_cert_dir(){ 139 | if [ ! -d "${CERT_DIR}" ]; then 140 | echo "[!] Creating certificate directory: ${CERT_DIR}" 141 | mkdir -p "${CERT_DIR}" 142 | fi 143 | } 144 | 145 | domain_verify(){ 146 | local domain="${1}" 147 | local doc_path="/var/www/vhosts/${domain}/html" 148 | 149 | echo "[!] Checking if domain '${domain}' has been added..." 150 | 151 | if docker compose exec -T ${CONT_NAME} bash -c "[ -d ${doc_path} ]" 2>/dev/null; then 152 | echo -e "[O] Domain \033[32m${domain}\033[0m exists (document root found)" 153 | return 0 154 | else 155 | echo -e "[X] Domain \033[31m${domain}\033[0m has NOT been added yet!" 156 | echo "[!] Document root not found: ${doc_path}" 157 | echo "[!] Please add this domain first using: bash bin/domain.sh -a ${domain}" 158 | exit 1 159 | fi 160 | } 161 | 162 | generate_cert(){ 163 | echo '[Start] Generating SSL certificate' 164 | www_domain "${DOMAIN}" 165 | create_cert_dir 166 | mkdir -p "${CERT_DIR}/${DOMAIN}" 167 | cd "${CERT_DIR}/${DOMAIN}" 168 | echo -e "[!] Generating certificate for: \033[32m${DOMAIN}\033[0m and \033[32m${WWW_DOMAIN}\033[0m" 169 | 170 | ${MKCERT_CMD} -key-file key.pem -cert-file cert.pem "${DOMAIN}" "${WWW_DOMAIN}" >/dev/null 2>&1 171 | if [ ${?} = 0 ]; then 172 | echo -e "[O] Certificate generated successfully" 173 | echo "[!] Certificate files:" 174 | echo "${EPACE}Cert: ${CERT_DIR}/${DOMAIN}/cert.pem" 175 | echo "${EPACE}Key: ${CERT_DIR}/${DOMAIN}/key.pem" 176 | else 177 | echo "[X] Failed to generate certificate" 178 | cd ../.. 179 | rm -rf "${CERT_DIR}/${DOMAIN}" 180 | exit 1 181 | fi 182 | cd - > /dev/null 183 | echo '[End] Generating SSL certificate' 184 | } 185 | 186 | create_local_template(){ 187 | echo '[Start] Creating docker-local.conf template' 188 | local source_file="/usr/local/lsws/conf/templates/docker.conf" 189 | local dest_file="/usr/local/lsws/conf/templates/docker-local.conf" 190 | if docker compose exec -T ${CONT_NAME} bash -c "[ -f ${dest_file} ]" 2>/dev/null; then 191 | echo "[i] Template file already exists: ${dest_file}" 192 | echo '[End] Creating docker-local.conf template' 193 | return 0 194 | fi 195 | 196 | docker compose exec -T ${CONT_NAME} bash -c " 197 | # Copy template file 198 | cp ${source_file} ${dest_file} 199 | 200 | # Remove old vhssl block and last closing brace 201 | sed -i '/^ vhssl {/,/^ }/d; \$d' ${dest_file} 202 | 203 | # Append new vhssl configuration 204 | cat >> ${dest_file} <<'VHSSL_EOF' 205 | vhssl { 206 | keyFile /usr/local/lsws/conf/cert/\$VH_NAME/key.pem 207 | certFile /usr/local/lsws/conf/cert/\$VH_NAME/cert.pem 208 | certChain 1 209 | } 210 | } 211 | VHSSL_EOF 212 | 213 | # Fix ownership and permissions 214 | chown nobody:nogroup ${dest_file} 2>/dev/null || chown lsadm:lsadm ${dest_file} 215 | chmod 644 ${dest_file} 216 | " 217 | 218 | echo -e "[O] Template \033[32mdocker-local.conf\033[0m created successfully!" 219 | echo -e " SSL certificates path: /usr/local/lsws/conf/cert/\$VH_NAME/" 220 | echo '[End] Creating docker-local.conf template' 221 | } 222 | 223 | register_local_template() { 224 | echo '[Start] Registering vhTemplate: dockerLocal' 225 | local config_file="/usr/local/lsws/conf/httpd_config.conf" 226 | local template_name="dockerLocal" 227 | local template_path="conf/templates/docker-local.conf" 228 | 229 | docker compose exec -T ${CONT_NAME} bash -c " 230 | if ! grep -q 'vhTemplate ${template_name} {' ${config_file}; then 231 | cat >> ${config_file} </dev/null' 415 | 416 | if [ ${?} = 0 ]; then 417 | echo -e "[O] OpenLiteSpeed restarted successfully" 418 | else 419 | echo "[X] Failed to restart OpenLiteSpeed" 420 | fi 421 | } 422 | 423 | main(){ 424 | if [ "${INSTALL}" = 'true' ]; then 425 | install_mkcert 426 | exit 0 427 | fi 428 | domain_filter "${DOMAIN}" 429 | if [ "${REMOVE}" = 'true' ]; then 430 | remove_cert 431 | exit 0 432 | fi 433 | check_mkcert 434 | domain_verify "${DOMAIN}" 435 | generate_cert 436 | configure_litespeed 437 | } 438 | 439 | check_input ${1} 440 | while [ ! -z "${1}" ]; do 441 | case ${1} in 442 | -[hH] | -help | --help) 443 | help_message 444 | ;; 445 | -[dD] | -domain | --domain) 446 | shift 447 | check_input "${1}" 448 | DOMAIN="${1}" 449 | ;; 450 | -[iI] | --install) 451 | INSTALL=true 452 | ;; 453 | -[rR] | --remove) 454 | REMOVE=true 455 | ;; 456 | *) 457 | help_message 458 | ;; 459 | esac 460 | shift 461 | done 462 | 463 | main --------------------------------------------------------------------------------