Skip to content

Instantly share code, notes, and snippets.

@cPFence
Last active January 28, 2025 18:52
Show Gist options
  • Save cPFence/041ef1ba10a41d62bd2a4d6cee2dae41 to your computer and use it in GitHub Desktop.
Save cPFence/041ef1ba10a41d62bd2a4d6cee2dae41 to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# Written by: cPFence Team / https://cpfence.app/
#
#
# Description:
# This script is designed to work with the Enhance Control Panel to ensure
# that custom OpenLiteSpeed (OLS) configurations persist across vhost rebuilds.
# Using a simple freeze/unfreeze mechanism, it allows administrators to lock
# their custom configurations and prevent them from being overwritten by the
# default settings applied during vhost creation or updates.
# MD5 checksum validation is added to detect changes and prevent redundant updates.
#
# [WARNING] !!!
# If the configuration is frozen, you **must unfreeze** before making changes
# to cPFence WAF settings (e.g., turning WAF on or off). Failure to unfreeze
# will cause the cron job to enter a loop and continuously restart OpenLiteSpeed.
# Always unfreeze before making changes, and freeze again afterward.
#
# Usage:
# 1) Make your edits in the OpenLiteSpeed admin panel. https://your.domain.com:7080/
# 2) Run: `./cPFence_ols_freeze.sh freeze` to freeze and save your changes.
# 3) If you need to make changes through the admin panel again:
# - Run: `./cPFence_ols_freeze.sh unfreeze`
# - Stop the cron job to avoid overwriting configurations during edits.
# 4) After completing your edits, run: `./cPFence_ols_freeze.sh freeze` again.
# 5) Set up a cron job to run the script with no arguments:
# - Cron Command: `* * * * * /path/to/cPFence_ols_freeze.sh`
#
# Example Workflow:
# - Make edits in the OLS admin panel → Freeze the configuration.
# - Unfreeze before making further changes → Stop the cron temporarily.
# - Freeze again after updates → Resume the cron job.
#
# Disclaimer:
# This script is provided "as is" without any warranties of any kind, express or implied.
# It is recommended to thoroughly test this script in a non-production environment prior to
# deployment on any live or critical systems. cPFence and Linkers Gate LLC are not liable for
# any damage or data loss resulting from the use of this script.
#
################################################################################
# USER CONFIGURABLE SECTION #
################################################################################
# Using Enhance v12 option (set to 'on' when Enhance v12 is released)
Using_Enhance_v12="off" # "on" or "off"
CONTAINER_NAME="openlitespeed"
# Path to the main OLS config
CONFIG_PATH="/usr/local/lsws/conf/httpd_config.conf"
# Backup directory for storing the full config before changes
BACKUP_DIR="/usr/local/lsws/conf/"
# Directory where we store freeze-related files
FREEZE_DIR="/usr/local/src"
# The file storing the ols settings that we freeze
FROZEN_TOP_FILE="$FREEZE_DIR/ols_top_config_frozen.conf"
# The MD5 we store for the entire config at freeze time
FROZEN_MD5_FILE="$FREEZE_DIR/ols_config_frozen.md5"
# The "marker" file indicating the config is currently frozen
FREEZE_MARKER="$FREEZE_DIR/.ols_frozen"
# Retention period for old backups (in days)
RETENTION_DAYS=7
################################################################################
# DO NOT EDIT BELOW THIS LINE #
################################################################################
# Decide Docker vs local
if [ "$Using_Enhance_v12" = "on" ]; then
docker_cmd=""
else
docker_cmd="docker exec $CONTAINER_NAME "
fi
MODE="$1" # freeze, unfreeze, or blank
# welcome message
display_welcome()
{
echo "**********************************************************************************************"
echo "* cPFence Web Security *"
echo "* cPFence OpenLiteSpeed Freeze *"
echo "* Copyright (C) 2023 - 2025 Linkers Gate LLC. *"
echo "**********************************************************************************************"
}
display_welcome
###############################################################################
# Helper: Check if config file exists
###############################################################################
check_config_exists() {
if ! $docker_cmd test -f "$CONFIG_PATH" >/dev/null 2>&1; then
echo "ERROR: OLS config file not found at: $CONFIG_PATH"
exit 1
fi
}
###############################################################################
# Cleanup: Remove old backup files
###############################################################################
cleanup_old_backups() {
echo "Cleaning up backup files older than $RETENTION_DAYS days in $BACKUP_DIR..."
local deleted_files
deleted_files=$($docker_cmd find "$BACKUP_DIR" -name "httpd_config_backup-*.conf" -type f -mtime +$RETENTION_DAYS -print -exec rm -f {} \;)
if [ -n "$deleted_files" ]; then
echo "Cleanup complete. The following old backup files were removed:"
echo "$deleted_files"
else
echo "No old backup files found for cleanup."
fi
}
###############################################################################
# 1) freeze_config
# - Backup entire config
# - Store current OLS settings in a file
# - Compute MD5 of entire config, store it
# - Touch marker
###############################################################################
freeze_config() {
check_config_exists
# (1) Cleanup old backups
cleanup_old_backups
# (2) Backup entire config
local TIMESTAMP
TIMESTAMP="$(date +'%d%m%y-%H%M%S')"
local BACKUP_FILE="httpd_config_backup-${TIMESTAMP}.conf"
echo "Backing up current config to: ${BACKUP_DIR}${BACKUP_FILE}"
$docker_cmd cp -a "$CONFIG_PATH" "${BACKUP_DIR}${BACKUP_FILE}"
# (3) Extract current OLS settings
local TMP_TOP="/tmp/ols_top_portion.$$"
# We'll stop printing as soon as we see "virtualhost Example {"
$docker_cmd sh -c "awk '
/virtualhost[[:space:]]+Example[[:space:]]*{/ {
exit
}
{ print \$0 }
' \"$CONFIG_PATH\" > \"$TMP_TOP\""
# Copy current OLS settings from container to host if needed
if [ "$Using_Enhance_v12" = "on" ]; then
cp "$TMP_TOP" "$FROZEN_TOP_FILE"
else
docker cp "$CONTAINER_NAME:$TMP_TOP" "$FROZEN_TOP_FILE"
$docker_cmd rm -f "$TMP_TOP" >/dev/null 2>&1
fi
echo "Stored current OLS settings in: $FROZEN_TOP_FILE"
# (4) Compute MD5 of entire config
local CURRENT_MD5
if [ "$Using_Enhance_v12" = "on" ]; then
CURRENT_MD5="$(md5sum "$CONFIG_PATH" | awk '{print $1}')"
else
CURRENT_MD5="$($docker_cmd md5sum "$CONFIG_PATH" | awk '{print $1}')"
fi
echo "$CURRENT_MD5" > "$FROZEN_MD5_FILE"
echo "Stored config MD5 in: $FROZEN_MD5_FILE"
# (5) Touch marker
touch "$FREEZE_MARKER"
echo "CONFIG FROZEN. Future runs will enforce your current OLS settings."
# (6) Display warning after successful freeze
echo "[WARNING] You must unfreeze the configuration before making changes to OLS settings or cPFence WAF settings (e.g., turning WAF on or off)."
echo "[WARNING] Failure to do so will cause the cron job to undo your changes and enter a loop, causing cPFence to keep restarting OLS every 5 minutes!"
exit 0
}
###############################################################################
# 2) unfreeze_config
# - Remove freeze marker & stored files so changes won't be reverted
###############################################################################
unfreeze_config() {
if [ -f "$FREEZE_MARKER" ]; then
rm -f "$FREEZE_MARKER" 2>/dev/null
rm -f "$FROZEN_TOP_FILE" 2>/dev/null
rm -f "$FROZEN_MD5_FILE" 2>/dev/null
echo "Unfrozen. Future runs won't revert any changes."
else
echo "Already unfrozen (no freeze marker found)."
fi
exit 0
}
###############################################################################
# 3) enforce_freeze_if_needed
# - If not frozen, do nothing.
# - If frozen:
# * Compare entire config MD5 to stored MD5.
# * If any difference is found, it backs up the file, then replaces only the settings from your frozen copy
# preserving the rest (the vhost portion). Finally, it restarts OLS.
###############################################################################
enforce_freeze_if_needed() {
# If not frozen, exit
if [ ! -f "$FREEZE_MARKER" ]; then
echo "Not frozen. No action taken."
exit 0
fi
check_config_exists
# Ensure we have a frozen settings and MD5
if [ ! -f "$FROZEN_TOP_FILE" ] || [ ! -f "$FROZEN_MD5_FILE" ]; then
echo "ERROR: Freeze marker exists, but missing frozen settings or MD5 file!"
exit 1
fi
# Compare entire config's MD5
local CURRENT_MD5
if [ "$Using_Enhance_v12" = "on" ]; then
CURRENT_MD5="$(md5sum "$CONFIG_PATH" | awk '{print $1}')"
else
CURRENT_MD5="$($docker_cmd md5sum "$CONFIG_PATH" | awk '{print $1}')"
fi
local STORED_MD5
STORED_MD5="$(cat "$FROZEN_MD5_FILE" 2>/dev/null || echo "")"
if [ "$CURRENT_MD5" = "$STORED_MD5" ]; then
echo "No changes detected (MD5 match). Exiting."
exit 0
fi
echo "Changes detected! Reverting stored OLS settings..."
# Cleanup old backups
cleanup_old_backups
# (1) Backup entire config
local TIMESTAMP
TIMESTAMP="$(date +'%d%m%y-%H%M%S')"
local BACKUP_FILE="httpd_config_backup-${TIMESTAMP}.conf"
echo "Backing up changed config to: ${BACKUP_DIR}${BACKUP_FILE}"
$docker_cmd cp -a "$CONFIG_PATH" "${BACKUP_DIR}${BACKUP_FILE}"
# (2) Extract vhost from the current config
local TMP_SECOND_HALF="/tmp/ols_second_half.$$"
$docker_cmd sh -c "awk '
/virtualhost[[:space:]]+Example[[:space:]]*{/ {
found=1
}
found==1 {
print \$0
}
' \"$CONFIG_PATH\" > \"$TMP_SECOND_HALF\""
# Copy the vhost portion to host if needed
local SECOND_HALF_HOST="/tmp/ols_second_half_host.$$"
if [ "$Using_Enhance_v12" = "on" ]; then
cp "$TMP_SECOND_HALF" "$SECOND_HALF_HOST"
else
docker cp "$CONTAINER_NAME:$TMP_SECOND_HALF" "$SECOND_HALF_HOST"
$docker_cmd rm -f "$TMP_SECOND_HALF" >/dev/null 2>&1
fi
# (3) Combine the stored ols settings + the vhost portion
local TMP_NEW_CONF="/tmp/ols_new_conf.$$"
cat "$FROZEN_TOP_FILE" "$SECOND_HALF_HOST" > "$TMP_NEW_CONF"
# (4) Overwrite the config in the container or host
if [ "$Using_Enhance_v12" = "on" ]; then
# Copy and preserve permissions and ownership in one step
install -m 0750 -o lsadm -g webserver "$TMP_NEW_CONF" "$CONFIG_PATH"
else
docker cp "$TMP_NEW_CONF" "$CONTAINER_NAME:$CONFIG_PATH"
# Ensure permissions and ownership are correct inside the container
docker exec "$CONTAINER_NAME" chown lsadm:webserver "$CONFIG_PATH"
docker exec "$CONTAINER_NAME" chmod 0750 "$CONFIG_PATH"
fi
# Cleanup
rm -f "$SECOND_HALF_HOST" "$TMP_NEW_CONF" 2>/dev/null
# (5) Restart OLS
${docker_cmd}/usr/local/lsws/bin/lswsctrl restart
echo "OpenLiteSpeed restarted with the reverted (frozen) settings."
# (6) Compute new MD5 of entire config and update the value
local CURRENT_MD5
if [ "$Using_Enhance_v12" = "on" ]; then
CURRENT_MD5="$(md5sum "$CONFIG_PATH" | awk '{print $1}')"
else
CURRENT_MD5="$($docker_cmd md5sum "$CONFIG_PATH" | awk '{print $1}')"
fi
echo "$CURRENT_MD5" > "$FROZEN_MD5_FILE"
echo "Updated the new MD5 in: $FROZEN_MD5_FILE"
}
###############################################################################
# MAIN
###############################################################################
case "$MODE" in
freeze)
freeze_config
;;
unfreeze)
unfreeze_config
;;
*)
enforce_freeze_if_needed
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment