Skip to content

Instantly share code, notes, and snippets.

@cPFence
Last active January 29, 2026 14:19
Show Gist options
  • Select an option

  • Save cPFence/041ef1ba10a41d62bd2a4d6cee2dae41 to your computer and use it in GitHub Desktop.

Select an option

Save cPFence/041ef1ba10a41d62bd2a4d6cee2dae41 to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# cPFence OpenLiteSpeed Freeze - v2 (Enhance CP / Ubuntu 24)
# Version adapted for Enhance v12
# Copyright (C) 2023 - 2026 Linkers Gate LLC.
#
# Usage:
# ./cPFence_ols_freeze.sh freeze -> store current top-of-config and freeze + auto-install systemd manager
# ./cPFence_ols_freeze.sh unfreeze -> remove freeze marker and stored files + disable systemd manager
# ./cPFence_ols_freeze.sh -> run enforcement (intended for systemd path/timer or manual)
# ./cPFence_ols_freeze.sh install-systemd -> install/enable cpf_ols_freeze.service + cpf_ols_freeze.path + cpf_ols_freeze.timer
# ./cPFence_ols_freeze.sh uninstall-systemd -> disable/remove cpf_ols_freeze units
#
# [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.
#
#
# 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.
#
#
################################################################################
set -euo pipefail
IFS=$'\n\t'
# -----------------------------
# Timer controls (defaults)
# -----------------------------
# systemd timer frequency (default 1 minute)
TIMER_INTERVAL="1min"
# delay after boot before first run
TIMER_BOOT_DELAY="2min"
# -----------------------------
# Paths / settings (Enhance CP)
# -----------------------------
CONFIG_PATH="/usr/local/lsws/conf/httpd_config.conf"
BACKUP_DIR="/usr/local/lsws/conf/"
FREEZE_DIR="/usr/local/src"
FROZEN_TOP_FILE="$FREEZE_DIR/ols_top_config_frozen.conf"
FROZEN_MD5_FILE="$FREEZE_DIR/ols_config_frozen.md5"
FREEZE_MARKER="$FREEZE_DIR/.ols_frozen"
RETENTION_DAYS=7
# OpenLiteSpeed systemd service name (Enhance CP)
SERVICE_NAME="lshttpd"
# systemd manager unit base name
UNIT_NAME="cpf_ols_freeze"
# lock file to prevent concurrent runs (timer/path/manual)
LOCK_FILE="/run/${UNIT_NAME}.lock"
# temp vars (avoid "unbound variable" with set -u)
TMP_VHOSTS=""
TMP_NEW_CONF=""
# ---------------------------------------------------------------------------
log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"; }
display_welcome() {
echo "**********************************************************************************************"
echo "* cPFence Web Security *"
echo "* cPFence OpenLiteSpeed Freeze v2 *"
echo "* Copyright (C) 2023 - 2026 Linkers Gate LLC. *"
echo "**********************************************************************************************"
}
require_root() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
echo "ERROR: Must run as root."
exit 1
fi
}
ensure_dirs() {
mkdir -p "$BACKUP_DIR" "$FREEZE_DIR"
chmod 0750 "$FREEZE_DIR" 2>/dev/null || true
}
check_config_exists() {
if [ ! -f "$CONFIG_PATH" ]; then
echo "ERROR: OLS config file not found at: $CONFIG_PATH"
exit 1
fi
}
cleanup_old_backups() {
log "Cleaning up backup files older than $RETENTION_DAYS days in $BACKUP_DIR..."
find "$BACKUP_DIR" -name "httpd_config_backup-*.conf" -type f -mtime +"$RETENTION_DAYS" -print -exec rm -f {} \; || true
}
backup_current_config() {
local TIMESTAMP BACKUP_FILE
TIMESTAMP="$(date +'%d%m%y-%H%M%S')"
BACKUP_FILE="${BACKUP_DIR}httpd_config_backup-${TIMESTAMP}.conf"
cp -a "$CONFIG_PATH" "$BACKUP_FILE"
log "Backed up current config to: $BACKUP_FILE"
}
# Freeze: backup full config, store top portion, store md5, create marker, install systemd manager
freeze_config() {
require_root
ensure_dirs
check_config_exists
cleanup_old_backups
backup_current_config
log "Extracting top portion (everything before 'virtualhost Example {')..."
awk '/virtualhost[[:space:]]+Example[[:space:]]*{/{exit}{print}' "$CONFIG_PATH" > "$FROZEN_TOP_FILE"
if [ ! -s "$FROZEN_TOP_FILE" ]; then
echo "ERROR: Frozen top file ended up empty. Pattern 'virtualhost Example {' not found?"
exit 1
fi
log "Computing MD5 of the full config..."
md5sum "$CONFIG_PATH" | awk '{print $1}' > "$FROZEN_MD5_FILE"
touch "$FREEZE_MARKER"
log "CONFIG FROZEN. Future runs will enforce the stored top-of-config settings."
log "WARNING: Unfreeze before making changes to OLS settings or cPFence WAF settings."
install_systemd_units
}
# Unfreeze: remove marker and stored files + disable systemd manager
unfreeze_config() {
require_root
ensure_dirs
rm -f "$FREEZE_MARKER" "$FROZEN_TOP_FILE" "$FROZEN_MD5_FILE" 2>/dev/null || true
log "Unfrozen. Future runs will not revert configuration changes."
disable_systemd_units
}
# Enforce freeze: if frozen and MD5 differs, restore frozen top portion + vhosts, restart service
enforce_freeze_if_needed() {
require_root
ensure_dirs
# Safe cleanup (no "unbound variable")
trap 'rm -f "${TMP_VHOSTS:-}" "${TMP_NEW_CONF:-}" 2>/dev/null || true' EXIT
# prevent concurrent runs (flock optional)
exec 9>"$LOCK_FILE"
if command -v flock >/dev/null 2>&1; then
if ! flock -n 9; then
log "Another ${UNIT_NAME} run is in progress. Exiting."
exit 0
fi
else
log "WARNING: flock not found; running without lock."
fi
if [ ! -f "$FREEZE_MARKER" ]; then
log "Not frozen. No action taken."
exit 0
fi
check_config_exists
if [ ! -f "$FROZEN_TOP_FILE" ] || [ ! -f "$FROZEN_MD5_FILE" ]; then
echo "ERROR: Freeze marker present but frozen files are missing!"
exit 1
fi
local CURRENT_MD5 STORED_MD5 NEW_MD5
CURRENT_MD5="$(md5sum "$CONFIG_PATH" | awk '{print $1}')"
STORED_MD5="$(cat "$FROZEN_MD5_FILE" 2>/dev/null || echo "")"
if [ "$CURRENT_MD5" = "$STORED_MD5" ]; then
log "No changes detected (MD5 match). Exiting."
exit 0
fi
# Small debounce helps when Enhance writes config multiple times quickly
sleep 2
CURRENT_MD5="$(md5sum "$CONFIG_PATH" | awk '{print $1}')"
if [ "$CURRENT_MD5" = "$STORED_MD5" ]; then
log "Change disappeared after debounce (MD5 match). Exiting."
exit 0
fi
log "Changes detected. Reverting to frozen top-of-config..."
cleanup_old_backups
backup_current_config
local MODE
TMP_VHOSTS="$(mktemp /tmp/ols_vhosts.XXXXXX)"
TMP_NEW_CONF="$(mktemp /tmp/ols_new_conf.XXXXXX)"
log "Extracting virtualhost section (from 'virtualhost Example {' to EOF)..."
awk '/virtualhost[[:space:]]+Example[[:space:]]*{/{found=1}found{print}' "$CONFIG_PATH" > "$TMP_VHOSTS"
if [ ! -s "$TMP_VHOSTS" ]; then
echo "ERROR: Could not extract vhost section. Pattern 'virtualhost Example {' not found?"
exit 1
fi
log "Combining frozen top-of-config with current virtualhosts..."
cat "$FROZEN_TOP_FILE" "$TMP_VHOSTS" > "$TMP_NEW_CONF"
MODE="$(stat -c '%a' "$CONFIG_PATH")"
log "Installing new config and preserving permissions (mode $MODE)..."
install -m "$MODE" -o lsadm -g www-data "$TMP_NEW_CONF" "$CONFIG_PATH"
log "Restarting OpenLiteSpeed service ($SERVICE_NAME)..."
systemctl restart "$SERVICE_NAME"
NEW_MD5="$(md5sum "$CONFIG_PATH" | awk '{print $1}')"
echo "$NEW_MD5" > "$FROZEN_MD5_FILE"
log "Reversion complete. MD5 updated."
}
# ---------------- systemd install (service + path + timer) ----------------
install_systemd_units() {
require_root
ensure_dirs
local SRC DST
SRC="$(readlink -f "$0")"
DST="/usr/local/sbin/${UNIT_NAME}"
install -m 0755 "$SRC" "$DST"
local SERVICE_FILE TIMER_FILE PATH_FILE
SERVICE_FILE="/etc/systemd/system/${UNIT_NAME}.service"
TIMER_FILE="/etc/systemd/system/${UNIT_NAME}.timer"
PATH_FILE="/etc/systemd/system/${UNIT_NAME}.path"
cat > "$SERVICE_FILE" <<EOF
[Unit]
Description=cPFence OLS Freeze enforcement
After=${SERVICE_NAME}.service
Wants=${SERVICE_NAME}.service
[Service]
Type=oneshot
ExecStart=${DST}
StandardOutput=journal
StandardError=journal
EOF
# Run immediately when config changes
cat > "$PATH_FILE" <<EOF
[Unit]
Description=Watch OLS config and enforce cPFence freeze
[Path]
PathChanged=${CONFIG_PATH}
Unit=${UNIT_NAME}.service
[Install]
WantedBy=multi-user.target
EOF
# Timer fallback
cat > "$TIMER_FILE" <<EOF
[Unit]
Description=Run cPFence OLS Freeze enforcement periodically
[Timer]
OnBootSec=${TIMER_BOOT_DELAY}
OnUnitActiveSec=${TIMER_INTERVAL}
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now "${UNIT_NAME}.path" "${UNIT_NAME}.timer" >/dev/null
# restart to apply any interval changes immediately
systemctl restart "${UNIT_NAME}.path" "${UNIT_NAME}.timer" >/dev/null 2>&1 || true
log "systemd manager installed/enabled: ${UNIT_NAME}.service + ${UNIT_NAME}.path + ${UNIT_NAME}.timer"
}
disable_systemd_units() {
systemctl disable --now "${UNIT_NAME}.timer" "${UNIT_NAME}.path" 2>/dev/null || true
systemctl daemon-reload 2>/dev/null || true
}
uninstall_systemd_units() {
require_root
disable_systemd_units
rm -f "/etc/systemd/system/${UNIT_NAME}.service" \
"/etc/systemd/system/${UNIT_NAME}.timer" \
"/etc/systemd/system/${UNIT_NAME}.path" 2>/dev/null || true
rm -f "/usr/local/sbin/${UNIT_NAME}" 2>/dev/null || true
systemctl daemon-reload
log "systemd manager removed: ${UNIT_NAME}"
}
# MAIN
display_welcome
MODE="${1:-}"
case "$MODE" in
freeze)
freeze_config
;;
unfreeze)
unfreeze_config
;;
install-systemd)
install_systemd_units
;;
uninstall-systemd)
uninstall_systemd_units
;;
*)
enforce_freeze_if_needed
;;
esac
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment