How to Disable Sitejet Globally Without Breaking Existing Websites

Taking Back Control: Granular Sitejet Management on cPanel Servers

The hosting landscape is constantly evolving. Recently, WebPros has heavily integrated Sitejet into the core cPanel experience. While Sitejet is a powerful builder, it’s not the right fit for every hosting provider. Many companies have already invested in competing site builders, prefer to keep their interface lightweight, or are looking forward to WebPros’ own upcoming “Nova” builder and don’t want to invest in Sitejet in the interim.

The challenge for sysadmins is that simply disabling Sitejet globally via the WHM Feature Manager does not affect already deployed websites, but it does prevent customers who are still developing and continuously editing their published Sitejet sites from using the editor. To avoid back-and-forth support tickets to re-enable the editor for those users, we need a way to opt out new users while keeping active users’ editors functional.

Today I’m sharing a script I designed to address this scenario by automating the complex tasks of segmenting users, cloning packages, and reassigning features—so you can manage your site builder offering on your own terms.

To disable Sitejet safely, you must perform a multi-step migration:

  1. Identify users actively publishing with Sitejet.
  2. Backup existing configurations.
  3. Create a new Feature List with Sitejet enabled.
  4. Clone existing hosting Packages and tie them to this new Feature List.
  5. Move the active Sitejet users to these new packages.
  6. Finally, disable Sitejet in the default feature list for everyone else.

Doing this manually via the WHM GUI for hundreds or thousands of accounts is practically impossible and highly prone to error.

Introducing the cPanel Sitejet Management Toolkit I have created two scripts to automate this lifecycle, now available on GitHub.

1. detects_users.sh (The Audit Tool) This script is non-invasive. It scans all accounts (or filters by OWNER=root) and generates a detailed CSV classification of Sitejet usage. It determines if a site is:

  • sitejet_standard_active: Published, deployed, and currently visible.
  • sitejet_present_overridden: Metadata exists, but the user has manually uploaded an index.php, effectively hiding the Sitejet site.
  • sitejet_meta_only: The user opened the builder but never published.

This is your first step to understand your actual exposure before making changes.

2. operate.sh (The Migration Tool) This is an interactive script that performs the heavy lifting. It can operate in two modes: scanning all users with Sitejet data, or using the --active-only flag (recommended) to target strictly active, non-overridden sites.

It handles the entire workflow:

  • Creates timestamped backups of /var/cpanel/packages and /var/cpanel/features.
  • Creates a default_sitejet feature list based on your current default, with Sitejet enabled.
  • Identifies the packages used by target users and clones them (e.g., Starter becomes Starter_with-sitejet).
  • Crucially: It replicates CloudLinux LVE limits to the new cloned packages if present.
  • Moves eligible users to the new _with-sitejet packages via whmapi1 changepackage.
  • Provides the option to disable Sitejet in the standard default feature list.

The Result: An “Opt-In” Workflow After running these scripts, Sitejet is disabled for new signups and existing users on standard packages. If a client demands Sitejet, you simply move them to one of the _with-sitejet packages, and it appears in their cPanel interface. Active users never see a service interruption.

Conclusion Whether you are waiting for Nova, promoting a different builder, or just disagree with the default WebPros orientation, these scripts give you the logic needed to assert control over your cPanel environment without risking existing customer data.

readme.md

# cPanel Sitejet Migration & Management Tools

## Overview

As WebPros pushes Sitejet deeper into the cPanel ecosystem, many hosting providers require granular control over its availability. Some providers prefer not to invest in Sitejet due to existing partnerships with other site builders, or because they are awaiting WebPros' upcoming "Nova" builder and wish to disable Sitejet in the interim.

Simply disabling Sitejet globally can break existing customer sites.

This repository provides robust, defensive Bash tools to automate the migration of active Sitejet users to a dedicated, parallel packaging structure. This allows administrators to disable Sitejet in the `default` WHM feature list while ensuring uninterrupted service for clients actively using the builder.

## Features

* **`detects_users.sh`**: Audits the server and generates a CSV classifying usage states (active, overridden, metadata only). Supports filtering by `OWNER=root`.
* **Parallel Packaging Model**: Leaves standard packages untouched. Sitejet becomes an "opt-in" by assigning users to parallel packages (e.g., `packageName` -> `packageName_with-sitejet`).
* **CloudLinux Integration**: Automates the cloning of LVE limits from original packages to cloned packages using `lvectl` or `lve-manager`.
* **Defensive Design**:
    * Requires root privileges.
    * Syntactically strict Bash (errexit, nounset, pipefail).
    * Interactive operation with explicit prompts.
    * Full Dry-Run support (`SITEJET_DRY=1`).
    * Automated backups of package and feature directories before modification.

## Prerequisites

* A cPanel & WHM server.
* Root access.
* `jq` installed (standard in recent cPanel installs, otherwise `dnf install jq`).

## Installation

```bash
mkdir -p /root/workspace/sitejet
cd /root/workspace/sitejet
# Copy the scripts operate.sh and detects_users.sh into this directory
chmod +x operate.sh detects_users.sh
```
All logs, backups, and artifacts are stored within /root/workspace/sitejet/.
Usage
1. Audit Current Usage (detects_users.sh)

Generate a full report of all accounts utilizing Sitejet, classified by their actual publication status.
Bash

sudo ./detects_users.sh

To limit the audit strictly to accounts owned by root:
Bash

sudo SITEJET_OWNER_FILTER=root ./detects_users.sh

Artifact: /root/workspace/sitejet/logs/sitejet_pkg_verify_full.csv
2. Execute Migration (operate.sh)

This script is interactive. It is highly recommended to run a dry-run first.

Dry-Run (Simulate changes):
Bash

sudo SITEJET_DRY=1 ./operate.sh --active-only

Live Execution (Recommended mode: Active Users Only):

The --active-only flag ensures only users with published, standard (not overridden) Sitejet sites are migrated to the parallel packages.
Bash

sudo ./operate.sh --active-only

The Migration Lifecycle Explained

When running operate.sh, the following workflow is executed based on your prompt responses:

    Backup: /var/cpanel/packages and /var/cpanel/features are backed up to /root/workspace/sitejet/backups/[TIMESTAMP].

    Feature List Creation: A new feature list named default_sitejet is created as a clone of default, with the Sitejet key enabled.

    Package Cloning: Packages used by active Sitejet users are cloned. New package name format: [OriginalName]_with-sitejet.

    LVE Replication: If CloudLinux is present, LVE limits from original packages are replicated to the _with-sitejet packages.

    Disable Default: An option is provided to set the Sitejet feature key to 0 in the standard /var/cpanel/features/default file.

    User Migration: Target users are moved to the new _with-sitejet packages via whmapi1 changepackage.

## Rollback

The scripts provide detailed rollback hints upon completion.

    To rollback feature/package configurations:
    Replace the contents of /var/cpanel/packages/ and /var/cpanel/features/ with the contents of the latest backup directory created by the script.

    To rollback users:
    Consult /root/workspace/sitejet/logs/sitejet_pkg_moves.log for the "before and after" state, and use whmapi1 changepackage to return them to their original package.

Disclaimer

These scripts modify core cPanel configurations and user packages. While designed defensively, they are provided "as-is". The user assumes all responsibility for execution. Always review dry-run output and ensure backups are functional before running live on a production environment.


---

### Part 3: Sanitized Execution Example

Here is the example execution, sanitized as requested. Usernames, specific package names (replaced with generic 'Bronze/Silver'), IDs, and timestamps have been changed.

```text
# /root/workspace/sitejet/operate.sh --active-only

Sitejet migration final script (OWNER=root only)
Workdir: /root/workspace/sitejet
Backups dir: /root/workspace/sitejet/backups
Logs: /root/workspace/sitejet/logs
DRY_RUN: 0

NOTICE: Running in ACTIVE_ONLY mode. Only users meeting strict publishing criteria will be included.
Generate users CSV (only OWNER=root) at /root/workspace/sitejet/sitejet_users.csv now? [y/n] (default: y): y
--- DEBUG: Starting generate_users_csv User Scan (find | while read) ---
--- FILTER: ENFORCING STRICT ACTIVE USER CRITERIA (Published, Deployed, No index.php override) ---
...
DEBUG: Checking file: /var/cpanel/users/alpha
DEBUG: Checking file: /var/cpanel/users/betauser
DEBUG: Checking file: /var/cpanel/users/gammasite
DEBUG: Checking file: /var/cpanel/users/deltacorp
--- DEBUG: Finishing generate_users_csv (Final Count: 28) ---
Detected 28 OWNER=root users with Sitejet metadata.
Run detailed detection and produce /root/workspace/sitejet/logs/sitejet_detection_details.csv? [y/n] (default: y): y
Detection CSV: /root/workspace/sitejet/logs/sitejet_detection_details.csv
Create full backup of /var/cpanel/packages and /var/cpanel/features to workspace backups now? [y/n] (default: y): y
Backups created at: /root/workspace/sitejet/backups/20261015T090001
Detected feature key: sitejet
Create /var/cpanel/features/default_sitejet (Sitejet enabled) based on default? [y/n] (default: y): y
Sitejet-enabled feature list created: /var/cpanel/features/default_sitejet
<not present>
Disable Sitejet in default feature file (set sitejet=0) now? This is reversible via backup. [y/n] (default: n): y
Default feature sitejet set to 0. Backup: /root/workspace/sitejet/backups/20261015T090001/features-default.bak

Packages to clone (3):
 - Bronze Plan
 - Silver Plan
 - Corporate Dedicated
Proceed to clone packages and bind them to the /var/cpanel/features/default_sitejet feature list? [y/n] (default: y): y

Processing package: Bronze Plan

Processing package: Silver Plan

Processing package: Corporate Dedicated

Preview of users CSV (first 20 lines):
     1 user,pkg
...
    17 user1example,Bronze Plan
    18 user2test,Silver Plan
    19 user3hosting,Bronze Plan
    20 user4site,Corporate Dedicated
Proceed with changepackage for all OWNER=root users in /root/workspace/sitejet/sitejet_users.csv? (Dry-run will simulate) [y/n] (default: n): y
Run strict verification across all OWNER=root users and append to /root/workspace/sitejet/logs/sitejet_pkg_verify.csv? [y/n] (default: y): y
Verification results appended to /root/workspace/sitejet/logs/sitejet_pkg_verify.csv

Migration completed. Artifacts and logs:
 - Workspace: /root/workspace/sitejet
 - Backups: /root/workspace/sitejet/backups/20261015T090001
 - Actions log: /root/workspace/sitejet/logs/sitejet_actions.log
 - Moves log: /root/workspace/sitejet/logs/sitejet_pkg_moves.log
 - Detection details: /root/workspace/sitejet/logs/sitejet_detection_details.csv
 - Verification CSV: /root/workspace/sitejet/logs/sitejet_pkg_verify.csv

Rollback hints:
 - Review /root/workspace/sitejet/logs/sitejet_actions.log and /root/workspace/sitejet/logs/sitejet_pkg_moves.log for MOVED entries.
 - To rollback a user:
     whmapi1 changepackage user=<user> pkg=<original_pkg>
 - To restore feature files from backup:
     cp -a <BACKUP_DIR>/features-default.bak /var/cpanel/features/default
     # If default_sitejet existed before the run, restore it too
     [ -f "<BACKUP_DIR>/features-default_sitejet.bak" ] && cp -a <BACKUP_DIR>/features-default_sitejet.bak /var/cpanel/features/default_sitejet
 - To restore all backed-up files:
     rm -rf /var/cpanel/features /var/cpanel/packages
     cp -a <BACKUP_DIR>/packages_features.<ts>.tgz /var

/root/workspace/sitejet/operate.sh


#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail

# operate.sh
# Interactive Sitejet migration script restricted to accounts owned by OWNER=root
#
# Usage:
#   SITEJET_DRY=1 sudo /root/workspace/sitejet/operate.sh               # dry-run preview (broad scope)
#   sudo /root/workspace/sitejet/operate.sh --active-only               # interactive commit (strict scope)
#   sudo /root/workspace/sitejet/operate.sh --gen-csv                   # generate CSV only (broad scope)
#
# Requirements: run as root; jq and whmapi1 must be available.

# ---- Configuration ----
FEATURE_BASE="/var/cpanel/features/default"
# New configuration for the Sitejet-enabled feature list
SITEJET_FEATURE_BASE="/var/cpanel/features/default_sitejet" 
PACKAGES_DIR="/var/cpanel/packages"
WORKDIR="/root/workspace/sitejet"
BACKUPS_DIR="${WORKDIR}/backups"
LOG_DIR="${WORKDIR}/logs"
USERS_CSV="${WORKDIR}/sitejet_users.csv"
DETECTION_CSV="${LOG_DIR}/sitejet_detection_details.csv"
VERIFICATION_CSV="${LOG_DIR}/sitejet_pkg_verify.csv"
ACTIONS_LOG="${LOG_DIR}/sitejet_actions.log"
DETAILED_DETECT_LOG="${LOG_DIR}/sitejet_detection.log"
DRY_RUN="${SITEJET_DRY:-0}"
TMPDIR="${WORKDIR}/tmp_$$"
# NEW: Flag to enforce strict active user criteria (1 = enforced, 0 = broad scan)
ACTIVE_ONLY=0 

# ---- Helpers ----
timestamp(){ date -Iseconds; }

die(){ echo "$*" >&2; exit 1; }

ensure_root(){ [ "$(id -u)" -eq 0 ] || die "Run as root"; }

# Ensure dirs
prepare_dirs(){
  mkdir -p "$WORKDIR" "$BACKUPS_DIR" "$LOG_DIR" "$TMPDIR"
  touch "$ACTIONS_LOG" "$DETAILED_DETECT_LOG"
  if ! grep -q '^user,' "$VERIFICATION_CSV" 2>/dev/null; then
    echo "user,domain,status,index.html,index.php" > "$VERIFICATION_CSV"
  fi
}

log_action(){
  local action="$1" target="$2" result="$3" backup="$4"
  echo "$(timestamp) | ${action} | ${target} | ${result} | ${backup:-}" >> "$ACTIONS_LOG"
}

prompt_yesno(){
  local prompt="${1:-Continue?}" default="${2:-y}" answer
  while :; do
    read -r -p "$prompt [y/n] (default: $default): " answer
    answer="${answer:-$default}"
    case "${answer,,}" in
      y|yes) return 0 ;;
      n|no) return 1 ;;
      *) echo "Please answer y or n." ;;
    esac
  done
}

# ---- Prereqs ----
check_prereqs(){
  command -v jq >/dev/null 2>&1 || die "jq not found. Install jq and retry."
  command -v whmapi1 >/dev/null 2>&1 || die "whmapi1 not found in PATH. Ensure whmapi1 available."
  prepare_dirs
}

# ---- Backup utilities ----
create_timestamped_backup(){
  local ts backup_dir
  ts="$(date +%Y%m%dT%H%M%S)"
  backup_dir="${BACKUPS_DIR}/${ts}"
  mkdir -p "$backup_dir"
  if [ "$DRY_RUN" -eq 1 ]; then
    echo "DRY_RUN: would create backup at ${backup_dir}"
    echo "$backup_dir"
    return 0
  fi
  tar -C /var -czf "${backup_dir}/packages_features.${ts}.tgz" cpanel/packages cpanel/features || true
  cp -a "$FEATURE_BASE" "${backup_dir}/features-default.bak" || true
  # Backup the new feature base if it exists
  [ -f "$SITEJET_FEATURE_BASE" ] && cp -a "$SITEJET_FEATURE_BASE" "${backup_dir}/features-default_sitejet.bak" || true
  echo "${backup_dir}"
}

# ---- Detection & users CSV generation (only OWNER=root) ----
# OWNER=root check: read OWNER= value from /var/cpanel/users/<user>
is_owner_root(){
  # Defensive initialization for nounset
  local user=""
  user="${1:-}"

  local ufile="/var/cpanel/users/${user}"
  if [ ! -f "$ufile" ]; then
    return 1
  fi
  # Extract OWNER value if present
  local owner
  owner="$(awk -F= '/^OWNER=/{print $2; exit}' "$ufile" 2>/dev/null || echo)"
  # Treat missing OWNER as root-owned
  if [ -z "$owner" ] || [ "$owner" = "root" ]; then
    return 0
  fi
  return 1
}	

# NEW: Checks if a domain for a user meets the strict "active" criteria
# CRITERIA: Published (pub_status=1, date>0), Deployed (has webcard in files list), 
#           and NOT Overridden (no index.php in webcard folder)
check_active_user(){
  local user="$1" domain="$2"
  local meta="/home/${user}/.cpanel/sitejet/${domain}"
  
  if [ ! -f "$meta" ]; then
    return 1 # Metadata missing
  fi

  local dr_pub dr_lp files_list document_root webcard_dir
  
  # CRITERIA 1: Published Status 
  dr_pub="$(jq -r '.publish_status // empty' "$meta" 2>/dev/null || echo)"
  dr_lp="$(jq -r '.latest_publish_date // 0' "$meta" 2>/dev/null || echo 0)"
  
  if [ "$dr_pub" != "1" ] || [ "${dr_lp:-0}" -eq 0 ]; then
    return 1 # Not published
  fi

  # CRITERIA 2: Active Deployment 
  files_list="${meta}-files"
  if [ ! -f "$files_list" ] || ! grep -q 'webcard' "$files_list"; then
    return 1 # No webcard deployment found
  fi
  
  # CRITERIA 3: Not Overridden 
  document_root="$(jq -r '.document_root // empty' "$meta" 2>/dev/null || echo)"
  document_root="${document_root:-/home/${user}/public_html}"
  document_root="${document_root%/}"
  local webcard_dir="${document_root}/webcard"
  
  if [ -d "$webcard_dir" ] && [ -f "${webcard_dir}/index.php" ]; then
    return 1 # index.php override detected (not standard active site)
  fi
  
  # All strict criteria met: standard active Sitejet user.
  return 0
}


generate_users_csv(){
  mkdir -p "$(dirname "$USERS_CSV")"
  : > "$USERS_CSV"
  
  # Ensure the CSV starts with a header for clean counting
  echo "user,pkg" > "$USERS_CSV" 
  
  echo "--- DEBUG: Starting generate_users_csv User Scan (find | while read) ---" >&2
  # Output the criteria being applied
  if [ "$ACTIVE_ONLY" -eq 1 ]; then
    echo "--- FILTER: ENFORCING STRICT ACTIVE USER CRITERIA (Published, Deployed, No index.php override) ---" >&2
  fi
  
  # FIX: Use find -print0 | while read -d $'\0' for safe iteration
  find /var/cpanel/users/ -maxdepth 1 -type f -print0 | while IFS= read -r -d $'\0' ufile; do
    
    # CRITICAL DEBUG STEP: This is now inside the robust loop structure
    echo "DEBUG: Checking file: $ufile" >&2
    
    user="$(basename "$ufile")"
    local sjdir="/home/${user}/.cpanel/sitejet"
    local has_eligible_domain="false"

    # 1. Check if owned by root
    if ! is_owner_root "$user"; then
        echo "$(timestamp) SKIP_NON_ROOT_OWNER ${user}" >> "$DETAILED_DETECT_LOG"
        continue
    fi

    # 2. Check for Sitejet metadata
    if [ -d "$sjdir" ]; then
      
      # Iterate over all domains/metadata files for this user
      for meta in "$sjdir"/*; do
        [ -f "$meta" ] || continue
        [[ "$meta" =~ -files$ ]] && continue
        
        local domain="$(basename "$meta")"

        # FILTERING LOGIC
        if [ "$ACTIVE_ONLY" -eq 1 ]; then
          if check_active_user "$user" "$domain"; then
            has_eligible_domain="true"
            break # Found one active domain, this user is eligible
          fi
        else
          # If not ACTIVE_ONLY, any Sitejet metadata makes them eligible
          has_eligible_domain="true"
          break
        fi
      done
      
      # If user passed filtering (or no filtering was applied), add to CSV
      if [ "$has_eligible_domain" = "true" ]; then
        # Defensive read for package
        pkg="$( { awk -F= '/^PLAN/ {print $2; exit}' "$ufile" 2>/dev/null || echo ""; } )"
        pkg="${pkg:-unknown}"
        
        echo "${user},${pkg}" >> "$USERS_CSV"
        echo "$(timestamp) Detected Sitejet metadata for user ${user} (pkg=${pkg}, ACTIVE_ONLY=${ACTIVE_ONLY})" >> "$DETAILED_DETECT_LOG"
      fi

    fi
  done
  
  # Since the loop runs in a subshell, we count the lines written to the CSV file
  # Note: 'wc -l' includes the header, so we subtract 1.
  local final_count
  final_count="$(wc -l < "$USERS_CSV")"
  final_count=$((final_count > 0 ? final_count - 1 : 0))

  echo "$final_count" > "${TMPDIR}/gen_count" # Passes count back to parent shell
  log_action "GENERATE_USERS_CSV" "$USERS_CSV" "OK" ""
  
  echo "--- DEBUG: Finishing generate_users_csv (Final Count: $final_count) ---" >&2
  return 0
}

detailed_detection_report(){
  : > "${TMPDIR}/detection.tmp"
  echo "user,domain,document_root,publish_status,latest_publish_date,files_has_webcard" > "${TMPDIR}/detection.tmp"
  while IFS=, read -r user pkg; do
    [ -z "$user" ] && continue
    # ensure root-owned still (defensive)
    if ! is_owner_root "$user"; then
      echo "${user},-,-,-,-,0" >> "${TMPDIR}/detection.tmp"
      continue
    fi
    sitejet_dir="/home/${user}/.cpanel/sitejet"
    if [ ! -d "$sitejet_dir" ]; then
      echo "${user},-,-,-,-,0" >> "${TMPDIR}/detection.tmp"
      continue
    fi
    for meta in "$sitejet_dir"/*; do
      [ -f "$meta" ] || continue
      [[ "$meta" =~ -files$ ]] && continue
      domain="$(basename "$meta")"
      dr="$(jq -r '.document_root // empty' "$meta" 2>/dev/null || echo)"
      pub="$(jq -r '.publish_status // empty' "$meta" 2>/dev/null || echo)"
      lp="$(jq -r '.latest_publish_date // 0' "$meta" 2>/dev/null || echo 0)"
      files_list="${meta}-files"
      fhw=0
      if [ -f "$files_list" ] && grep -q 'webcard' "$files_list"; then
        fhw=1
      fi
      echo "${user},${domain},${dr},${pub},${lp},${fhw}" >> "${TMPDIR}/detection.tmp"
    done
  done < "$USERS_CSV"
  cp "${TMPDIR}/detection.tmp" "$DETECTION_CSV"
  log_action "DETECTION_REPORT" "$DETECTION_CSV" "OK" ""
}

# ---- Feature key detection and feature list management ----
detect_feature_key(){
  if [ -f "$FEATURE_BASE" ]; then
    if grep -qi '^cpanel_sitejet_website_builder=' "$FEATURE_BASE"; then
      echo "cpanel_sitejet_website_builder"
      return 0
    fi
    if grep -qi '^sitejet=' "$FEATURE_BASE"; then
      echo "sitejet"
      return 0
    fi
  fi
  echo "sitejet"
}

# Creates the sitejet-enabled feature list: default_sitejet
create_sitejet_feature_base(){
  local backup_dir="$1" feature_key="$2"
  if [ "$DRY_RUN" -eq 1 ]; then
    echo "DRY_RUN: would create ${SITEJET_FEATURE_BASE} from ${FEATURE_BASE} and set ${feature_key}=1"
    log_action "CREATE_SITEJET_FEATURE_BASE" "$SITEJET_FEATURE_BASE" "DRY_RUN" "$backup_dir"
    return 0
  fi
  
  # Copy default feature file
  cp -a "$FEATURE_BASE" "$SITEJET_FEATURE_BASE"
  
  # Set the feature key to 1
  if grep -qi "^${feature_key}=" "$SITEJET_FEATURE_BASE"; then
    sed -i "s/^${feature_key}=.*/${feature_key}=1/I" "$SITEJET_FEATURE_BASE"
  else
    echo "${feature_key}=1" >> "$SITEJET_FEATURE_BASE"
  fi
  
  chown root:root "$SITEJET_FEATURE_BASE"
  chmod 644 "$SITEJET_FEATURE_BASE"
  log_action "CREATE_SITEJET_FEATURE_BASE" "$SITEJET_FEATURE_BASE" "OK" "$backup_dir"
}

# Disables Sitejet in the original default feature file
disable_sitejet_in_default(){
  local backup_dir="$1" feature_key="$2"
  if [ "$DRY_RUN" -eq 1 ]; then
    echo "DRY_RUN: would backup and set ${feature_key}=0 in ${FEATURE_BASE}"
    log_action "DISABLE_DEFAULT_FEATURE" "$FEATURE_BASE" "DRY_RUN" "$backup_dir"
    return 0
  fi
  
  # Backup the original default feature file
  cp -a "$FEATURE_BASE" "${backup_dir}/features-default.bak"
  
  # Set the feature key to 0
  if grep -qi "^${feature_key}=" "$FEATURE_BASE"; then
    sed -i "s/^${feature_key}=.*/${feature_key}=0/I" "$FEATURE_BASE"
  else
    echo "${feature_key}=0" >> "$FEATURE_BASE"
  fi
  log_action "DISABLE_DEFAULT_FEATURE" "$FEATURE_BASE" "OK" "$backup_dir"
}

# ---- Per-package featurelist binding (updated to use SITEJET_FEATURE_BASE) ----
bind_package_featurelist_whmapi(){
  local newpkg="$1" featurelist_name="$2"
  if [ "$DRY_RUN" -eq 1 ]; then
    echo "DRY_RUN: whmapi1 editpkg name=${newpkg} featurelist=${featurelist_name}"
    log_action "BIND_PACKAGE_FEATURELIST" "${newpkg} to ${featurelist_name}" "DRY_RUN" ""
    return 0
  fi
  # Use the single SITEJET_FEATURE_BASE name
  if whmapi1 editpkg name="${newpkg}" featurelist="${featurelist_name}" >/dev/null 2>&1; then
    log_action "BIND_PACKAGE_FEATURELIST" "${newpkg} to ${featurelist_name}" "OK" ""
  else
    log_action "BIND_PACKAGE_FEATURELIST" "${newpkg} to ${featurelist_name}" "FAIL" ""
  fi
}

# ---- Clone package file and optional LVE replication ----
clone_package_file(){
  # FINAL FIX: Use parameter expansion to safely assign the argument.
  local oldpkg="${1:-}" 

  # The local declaration above should prevent the need for this check,
  # but we keep it for logical safety (to skip empty package names).
  if [ -z "$oldpkg" ]; then
    log_action "CLONE_PACKAGE" "NULL_INPUT" "ERROR" ""
    return 1
  fi
  
  local newpkg="${oldpkg}_with-sitejet"
  local oldfile="${PACKAGES_DIR}/${oldpkg}"
  local newfile="${PACKAGES_DIR}/${newpkg}"

  if [ ! -f "$oldfile" ]; then
    log_action "CLONE_PACKAGE" "${oldpkg}" "MISSING_SOURCE" ""
    return 1
  fi
  if [ -f "$newfile" ]; then
    log_action "CLONE_PACKAGE" "${newpkg}" "ALREADY_EXISTS" ""
    return 0
  fi
  if [ "$DRY_RUN" -eq 1 ]; then
    echo "DRY_RUN: would cp ${oldfile} ${newfile}"
    log_action "CLONE_PACKAGE" "${newpkg}" "DRY_RUN" ""
    return 0
  fi
  cp -a "$oldfile" "$newfile"
  log_action "CLONE_PACKAGE" "${newpkg}" "OK" ""
  return 0
}


clone_lve_limits(){
  # FIX: Two-step initialization to bypass aggressive 'nounset' behavior.
  # 1. Declare 'oldpkg' locally and set to empty string.
  local oldpkg="" 
  # 2. Assign the actual argument value from $1.
  oldpkg="${1:-}"

  # Now that oldpkg is guaranteed to be set, we can safely use it.
  local newpkg="${oldpkg}_with-sitejet" 
  
  if [ -z "$oldpkg" ]; then
    log_action "LVE_CLONE" "NULL_INPUT" "ERROR" ""
    return 1
  fi
  
  if command -v lvectl >/dev/null 2>&1; then
    if [ "$DRY_RUN" -eq 1 ]; then
      log_action "LVE_CLONE" "${oldpkg}->${newpkg}" "DRY_RUN" ""
      return 0
    fi
    if lvectl package-clone --from="${oldpkg}" --to="${newpkg}" >/dev/null 2>&1; then
      log_action "LVE_CLONE" "${oldpkg}->${newpkg}" "OK" ""
    else
      log_action "LVE_CLONE" "${oldpkg}->${newpkg}" "FAIL" ""
    fi
    return 0
  fi
  if command -v lve-manager >/dev/null 2>&1; then
    if [ "$DRY_RUN" -eq 1 ]; then
      log_action "LVE_CLONE" "${oldpkg}->${newpkg}" "DRY_RUN" ""
      return 0
    fi
    if lve-manager clone-package "${oldpkg}" "${newpkg}" >/dev/null 2>&1; then
      log_action "LVE_CLONE" "${oldpkg}->${newpkg}" "FAIL" ""
    else
      log_action "LVE_CLONE" "${oldpkg}->${newpkg}" "FAIL" ""
    fi
    return 0
  fi
  log_action "LVE_CLONE" "${oldpkg}->${newpkg}" "NO_TOOL" ""
  echo "NOTICE: No CloudLinux tool found; manual LVE cloning required for ${oldpkg} -> ${newpkg}"
}

# ---- Strict verification for a user (appends to verification CSV) ----
verify_user_strict(){
  local user="$1"
  local domain_field domain status has_index_html has_index_php dr pub lp files_list
  domain_field="$(awk -F= '/^DNS/ {print $2; exit}' "/var/cpanel/users/$user" 2>/dev/null || true)"
  domain="${domain_field:-unknown}"
  status="no_sitejet_dir"; has_index_html=0; has_index_php=0
  local sitejet_dir="/home/${user}/.cpanel/sitejet"
  [ -d "$sitejet_dir" ] || { echo "${user},${domain},no_sitejet_dir,0,0" >> "$VERIFICATION_CSV"; return; }
  for meta in "$sitejet_dir"/*; do
    [ -f "$meta" ] || continue
    [[ "$meta" =~ -files$ ]] && continue
    dr="$(jq -r '.document_root // empty' "$meta" 2>/dev/null || echo)"
    pub="$(jq -r '.publish_status // empty' "$meta" 2>/dev/null || echo)"
    lp="$(jq -r '.latest_publish_date // 0' "$meta" 2>/dev/null || echo 0)"
    files_list="${meta}-files"
    if [ "$pub" = "1" ] && [ "${lp:-0}" -gt 0 ] && [ -f "$files_list" ] && grep -q 'webcard' "$files_list"; then
      dr="${dr:-/home/${user}/public_html}"
      dr="${dr%/}"
      local webcard="${dr}/webcard"
      if [ -d "$webcard" ]; then
        [ -f "${webcard}/index.html" ] && has_index_html=1 || true
        [ -f "${webcard}/index.php" ] && has_index_php=1 || true
        if [ "$has_index_html" -eq 1 ] && [ "$has_index_php" -eq 0 ]; then
          status="sitejet_standard_active"
          break
        else
          status="sitejet_present_overridden"
        fi
      else
        status="sitejet_meta_only"
      fi
    fi
  done
  echo "${user},${domain},${status},index.html=${has_index_html},index.php=${has_index_php}" >> "$VERIFICATION_CSV"
}

# ---- Change package for user (exit codes, structured logging) ----
change_package_for_user(){
  local user="$1" oldpkg="$2" newpkg="${oldpkg}_with-sitejet"
  if ! id "$user" &>/dev/null; then
    echo "SKIP_USER_NOT_FOUND ${user}"
    return 1
  fi
  if [ ! -f "${PACKAGES_DIR}/${newpkg}" ]; then
    echo "SKIP_TARGET_PKG_MISSING ${user} ${newpkg}"
    return 2
  fi
  local current_plan
  current_plan="$(whmapi1 accountsummary user="$user" 2>/dev/null | awk -F': ' '/plan:/{print $2}' || true)"
  current_plan="${current_plan:-$(awk -F= '/^PLAN/ {print $2; exit}' /var/cpanel/users/$user 2>/dev/null || true)}"
  if [ "$current_plan" = "$newpkg" ]; then
    echo "NOOP_ALREADY_ON_TARGET ${user} ${newpkg}"
    return 0
  fi
  if [ "$DRY_RUN" -eq 1 ]; then
    echo "DRY_RUN Would move ${user} from ${current_plan} to ${newpkg}"
    return 0
  fi
  if whmapi1 changepackage user="$user" pkg="$newpkg" >/dev/null 2>&1; then
    echo "MOVED ${user} ${current_plan} -> ${newpkg}"
    return 0
  else
    echo "FAIL_CHANGEPACKAGE ${user} -> ${newpkg}"
    return 3
  fi
}

# ---- Rollback hints ----
rollback_instructions(){
  cat <<EOF

Rollback hints:
 - Review ${ACTIONS_LOG} and ${LOG_DIR}/sitejet_pkg_moves.log for MOVED entries.
 - To rollback a user:
     whmapi1 changepackage user=<user> pkg=<original_pkg>
 - To restore feature files from backup:
     cp -a <BACKUP_DIR>/features-default.bak /var/cpanel/features/default
     # If default_sitejet existed before the run, restore it too
     [ -f "<BACKUP_DIR>/features-default_sitejet.bak" ] && cp -a <BACKUP_DIR>/features-default_sitejet.bak /var/cpanel/features/default_sitejet
 - To restore all backed-up files:
     rm -rf /var/cpanel/features /var/cpanel/packages
     cp -a <BACKUP_DIR>/packages_features.<ts>.tgz /var
EOF
}

# ---- Main flow ----
ensure_root
check_prereqs

echo
echo "Sitejet migration final script (OWNER=root only)"
echo "Workdir: $WORKDIR"
echo "Backups dir: $BACKUPS_DIR"
echo "Logs: $LOG_DIR"
echo "DRY_RUN: $DRY_RUN"
echo

# Argument parsing for ACTIVE_ONLY
if [ "${1:-}" = "--active-only" ]; then
    ACTIVE_ONLY=1
    shift # Consume the argument
    echo "NOTICE: Running in ACTIVE_ONLY mode. Only users meeting strict publishing criteria will be included."
fi

# CLI --gen-csv mode
if [ "${1:-}" = "--gen-csv" ]; then
  OUTFILE="${2:-$USERS_CSV}"
  generate_users_csv
  echo "Generated users CSV at ${OUTFILE}"
  exit 0
fi

# Step 1: generate users.csv interactively
if prompt_yesno "Generate users CSV (only OWNER=root) at ${USERS_CSV} now?" "y"; then
  generate_users_csv
else
  [ -f "$USERS_CSV" ] || die "Users CSV not found; aborting."
fi
# Read the count from the temporary file
gen_count="$(cat ${TMPDIR}/gen_count 2>/dev/null || echo 0)"
echo "Detected ${gen_count} OWNER=root users with Sitejet metadata."
log_action "GENERATE_USERS_SUMMARY" "$USERS_CSV" "COUNT=${gen_count}" ""

# Step 2: detailed detection report
if prompt_yesno "Run detailed detection and produce ${DETECTION_CSV}?" "y"; then
  detailed_detection_report
  echo "Detection CSV: ${DETECTION_CSV}"
fi

# Step 3: backup
if prompt_yesno "Create full backup of /var/cpanel/packages and /var/cpanel/features to workspace backups now?" "y"; then
  BACKUP_PATH="$(create_timestamped_backup)"
  echo "Backups created at: ${BACKUP_PATH}"
  log_action "CREATE_BACKUP" "${BACKUP_PATH}" "OK" "${BACKUP_PATH}"
else
  die "Backup is required before proceeding. Aborting."
fi

# Step 4: feature key detection and feature list setup
FEATURE_KEY="$(detect_feature_key)"
echo "Detected feature key: ${FEATURE_KEY}"

# 4a: Create default_sitejet feature list
if prompt_yesno "Create ${SITEJET_FEATURE_BASE} (Sitejet enabled) based on default?" "y"; then
  create_sitejet_feature_base "$BACKUP_PATH" "$FEATURE_KEY"
  echo "Sitejet-enabled feature list created: ${SITEJET_FEATURE_BASE}"
else
  die "Creating the sitejet feature list is required. Aborting."
fi

# 4b: Disable Sitejet in default feature list
grep -n -i "^${FEATURE_KEY}=" "$FEATURE_BASE" 2>/dev/null || echo "<not present>"
if prompt_yesno "Disable Sitejet in default feature file (set ${FEATURE_KEY}=0) now? This is reversible via backup." "n"; then
  disable_sitejet_in_default "$BACKUP_PATH" "$FEATURE_KEY"
  echo "Default feature ${FEATURE_KEY} set to 0. Backup: ${BACKUP_PATH}/features-default.bak"
else
  echo "Skipping disable of default feature as requested."
  log_action "DISABLE_DEFAULT_FEATURE" "$FEATURE_BASE" "SKIP" "$BACKUP_PATH"
fi

# Step 5: build list of packages to clone (unique)
packages_to_clone=()
while IFS=, read -r u p; do
  [ -z "$u" ] && continue
  # ensure owner root for safety (defensive)
  if ! is_owner_root "$u"; then
    echo "$(timestamp) SKIP_NON_ROOT_OWNER_IN_CSV ${u}" >> "$DETAILED_DETECT_LOG"
    continue
  fi
  packages_to_clone+=("$p")
done < "$USERS_CSV"
mapfile -t packages_to_clone < <(printf '%s\n' "${packages_to_clone[@]}" | awk '!seen[$0]++' | sed '/^$/d')

echo
echo "Packages to clone (${#packages_to_clone[@]}):"
for p in "${packages_to_clone[@]}"; do echo " - $p"; done

if [ "${#packages_to_clone[@]}" -eq 0 ]; then
  die "No packages found to clone. Aborting."
fi

if ! prompt_yesno "Proceed to clone packages and bind them to the ${SITEJET_FEATURE_BASE} feature list?" "y"; then
  echo "Aborting before package operations."
  exit 0
fi

# Step 6: clone packages, bind to default_sitejet, LVE clone
SITEJET_FEATURE_LIST="$(basename "$SITEJET_FEATURE_BASE")"
for p in "${packages_to_clone[@]}"; do
  # Defensive check for empty package name (from previous fix)
  if [ -z "$p" ]; then
    echo "Skipping empty package name in loop." >&2
    continue
  fi
  # -------------------------------------------------------
  echo
  echo "Processing package: $p"
  clone_package_file "$p" || echo "Warning: clone_package_file returned non-zero for $p"
  newpkg="${p}_with-sitejet"
  # Bind the new package to the single SITEJET_FEATURE_LIST
  bind_package_featurelist_whmapi "$newpkg" "$SITEJET_FEATURE_LIST"
  clone_lve_limits "$p"
done

# Save artifacts to backup folder
cp -a "$USERS_CSV" "${BACKUP_PATH}/sitejet_users.csv" || true
cp -a "$DETECTION_CSV" "${BACKUP_PATH}/sitejet_detection_details.csv" || true
log_action "SAVE_ARTIFACTS_TO_BACKUP" "${BACKUP_PATH}" "OK" "${BACKUP_PATH}"

# Step 7: preview and confirm user migrations
echo
echo "Preview of users CSV (first 20 lines):"
nl -ba "$USERS_CSV" | sed -n '1,20p'

if ! prompt_yesno "Proceed with changepackage for all OWNER=root users in ${USERS_CSV}? (Dry-run will simulate)" "n"; then
  echo "Operator chose not to proceed with user reassignment."
  exit 0
fi

# Step 8: reassign users and log
: > "${LOG_DIR}/sitejet_pkg_moves.log"
while IFS=, read -r user oldpkg; do
  [ -z "$user" ] && continue
  # enforce owner=root check again defensively
  if ! is_owner_root "$user"; then
    echo "$(timestamp) SKIP_NON_ROOT_OWNER ${user}" >> "${LOG_DIR}/sitejet_pkg_moves.log"
    log_action "CHANGEPACKAGE" "user=${user}" "SKIP_NON_ROOT_OWNER" "${BACKUP_PATH}"
    continue
  fi
  result="$(change_package_for_user "$user" "$oldpkg" 2>&1 || true)"
  rc=$?
  echo "$(timestamp) | ${result}" >> "${LOG_DIR}/sitejet_pkg_moves.log"
  case "$rc" in
    0) log_action "CHANGEPACKAGE" "user=${user}" "OK" "${BACKUP_PATH}" ;;
    1) log_action "CHANGEPACKAGE" "user=${user}" "SKIP_USER_NOT_FOUND" "${BACKUP_PATH}" ;;
    2) log_action "CHANGEPACKAGE" "user=${user}" "SKIP_TARGET_PKG_MISSING" "${BACKUP_PATH}" ;;
    3) log_action "CHANGEPACKAGE" "user=${user}" "FAIL_CHANGEPACKAGE" "${BACKUP_PATH}" ;;
    *) log_action "CHANGEPACKAGE" "user=${user}" "UNKNOWN_RC_${rc}" "${BACKUP_PATH}" ;;
  esac
done < "$USERS_CSV"

# Step 9: strict verification post-migration
if prompt_yesno "Run strict verification across all OWNER=root users and append to ${VERIFICATION_CSV}?" "y"; then
  while IFS=, read -r user oldpkg; do
    [ -z "$user" ] && continue
    if ! is_owner_root "$user"; then
      echo "$(timestamp) SKIP_NON_ROOT_OWNER_IN_VERIFY ${user}" >> "$DETAILED_DETECT_LOG"
      continue
    fi
    verify_user_strict "$user"
  done < "$USERS_CSV"
  log_action "POST_MIGRATION_VERIFICATION" "$VERIFICATION_CSV" "OK" "${BACKUP_PATH}"
  echo "Verification results appended to ${VERIFICATION_CSV}"
fi

# Final summary and rollback hints
echo
echo "Migration completed. Artifacts and logs:"
echo " - Workspace: $WORKDIR"
echo " - Backups: $BACKUP_PATH"
echo " - Actions log: $ACTIONS_LOG"
echo " - Moves log: ${LOG_DIR}/sitejet_pkg_moves.log"
echo " - Detection details: ${DETECTION_CSV}"
echo " - Verification CSV: ${VERIFICATION_CSV}"
rollback_instructions
log_action "RUN_COMPLETE" "$WORKDIR" "OK" "${BACKUP_PATH}"

# Cleanup
rm -rf "$TMPDIR"

exit 0

/root/workspace/sitejet/detects_users.sh

#!/usr/bin/env bash
# detects_users.sh
# Strict Sitejet verification across all accounts that have /home/<user>/.cpanel/sitejet
# Writes CSV to /root/workspace/sitejet/logs/sitejet_pkg_verify_full.csv
# Requires: run as root; jq available in PATH.
#
# Usage:
#   sudo /root/workspace/sitejet/detects_users.sh
#   sudo SITEJET_OWNER_FILTER=root /root/workspace/sitejet/detects_users.sh   # optional OWNER filter (see below)

set -o errexit
set -o nounset
set -o pipefail

OUT_DIR="/root/workspace/sitejet/logs"
OUT_FILE="${OUT_DIR}/sitejet_pkg_verify_full.csv"
TMPDIR="/root/workspace/sitejet/tmp_verify_$$"
OWNER_FILTER="${SITEJET_OWNER_FILTER:-}"   # optional: if set (e.g., root), only include users whose OWNER is empty or equals this value; if empty include all users

timestamp() { date -Iseconds; }
die() { echo "$*" >&2; exit 1; }

# Prereqs and environment
[ "$(id -u)" -eq 0 ] || die "Run as root"
command -v jq >/dev/null 2>&1 || die "jq not found in PATH"

mkdir -p "$OUT_DIR" "$TMPDIR"
: > "$OUT_FILE"
echo "user,domain,status,index.html,index.php,document_root,publish_status,latest_publish_date,files_has_webcard" > "$OUT_FILE"

# Helper: check owner if OWNER_FILTER set
owner_allowed() {
  local user="$1"
  local ufile="/var/cpanel/users/${user}"
  [ -f "$ufile" ] || return 1
  if [ -z "$OWNER_FILTER" ]; then
    return 0
  fi
  local owner
  owner="$(awk -F= '/^OWNER=/{print $2; exit}' "$ufile" 2>/dev/null || echo)"
  # treat missing owner as allowed when filtering for "root"
  if [ -z "$owner" ] && [ "$OWNER_FILTER" = "root" ]; then
    return 0
  fi
  if [ "$owner" = "$OWNER_FILTER" ]; then
    return 0
  fi
  return 1
}

# Main loop: scan users
for ufile in /var/cpanel/users/*; do
  [ -f "$ufile" ] || continue
  user="$(basename "$ufile")"

  # optional owner filter
  if ! owner_allowed "$user"; then
    continue
  fi

  sjdir="/home/${user}/.cpanel/sitejet"
  [ -d "$sjdir" ] || continue

  for meta in "$sjdir"/*; do
    [ -f "$meta" ] || continue
    # skip manifest files when parsing JSON (we handle manifest separately)
    [[ "$meta" =~ -files$ ]] && continue

    domain="$(basename "$meta")"
    # extract metadata fields (jq required)
    document_root="$(jq -r '.document_root // empty' "$meta" 2>/dev/null || echo)"
    publish_status="$(jq -r '.publish_status // empty' "$meta" 2>/dev/null || echo)"
    latest_publish_date="$(jq -r '.latest_publish_date // 0' "$meta" 2>/dev/null || echo 0)"

    files_list="${meta}-files"
    files_has_webcard=0
    if [ -f "$files_list" ] && grep -q "webcard" "$files_list"; then
      files_has_webcard=1
    fi

    status="no_sitejet_dir"
    idx_html=0
    idx_php=0

    # strict publish checks
    if [ "$publish_status" = "1" ] && [ "${latest_publish_date:-0}" -gt 0 ] && [ "$files_has_webcard" -eq 1 ]; then
      # determine document_root fallback
      if [ -z "$document_root" ]; then
        document_root="/home/${user}/public_html"
      fi
      document_root="${document_root%/}"
      webcard_dir="${document_root}/webcard"

      if [ -d "$webcard_dir" ]; then
        [ -f "${webcard_dir}/index.html" ] && idx_html=1 || true
        [ -f "${webcard_dir}/index.php" ] && idx_php=1 || true

        if [ "$idx_html" -eq 1 ] && [ "$idx_php" -eq 0 ]; then
          status="sitejet_standard_active"
        else
          status="sitejet_present_overridden"
        fi
      else
        status="sitejet_meta_only"
      fi
    else
      status="sitejet_meta_only"
    fi

    # escape commas in document_root for CSV
    safe_dr="$(printf '%s' "$document_root" | sed 's/,/\\,/g')"

    printf '%s,%s,%s,%d,%d,%s,%s,%s,%d\n' \
      "$user" "$domain" "$status" "$idx_html" "$idx_php" "$safe_dr" "$publish_status" "$latest_publish_date" "$files_has_webcard" \
      >> "$OUT_FILE"
  done
done

# Summary counts by status
echo "$(timestamp) Wrote verification CSV: $OUT_FILE"
awk -F, 'NR>1{cnt[$3]++}END{for (k in cnt) printf "%-30s %d\n", k, cnt[k]}' "$OUT_FILE" | sort -k2 -n -r

# finish
rm -rf "$TMPDIR"
exit 0

Comments

6 comments

  1. A possible bug when there are no accounts detected:

    Disable Sitejet in default feature file (set sitejet=0) now? This is reversible via backup. [y/n] (default: n): y
    Default feature sitejet set to 0. Backup: /root/workspace/sitejet/backups/20260105T102000/features-default.bak
    /root/workspace/sitejet/operate.sh: line 594: packages_to_clone[@]: unbound variable

    Packages to clone (0):
    /root/workspace/sitejet/operate.sh: line 598: packages_to_clone[@]: unbound variable

    —

    It may also be goo to generate the csv first to have an idea :

    /root/workspace/sitejet/operate.sh –gen-csv /root/workspace/sitejet/audit_results.csv
    cat /root/workspace/sitejet/audit_results.csv

    /root/workspace/sitejet/operate.sh –active-only –gen-csv /root/workspace/sitejet/active_audit.csv
    cat /root/workspace/sitejet/audit_results.csv

  2. TODO (non-dry mode should have default Y) for:

    Disable Sitejet in default feature file (set sitejet=0) now? This is reversible via backup. [y/n] (default: n):
    // indicating that it can also be modified via WHM > features manager
    Proceed with changepackage for all OWNER=root users in /root/workspace/sitejet/sitejet_users.csv? (Dry-run will simulate) [y/n] (default: n):

    1. # Changed 4b to default ‘y’
      if prompt_yesno “Disable Sitejet in default feature file (set ${FEATURE_KEY}=0) now? (Also manageable via WHM Feature Manager)” “y”; then
      # … logic …
      fi

      # Changed Step 7 to default ‘y’
      if ! prompt_yesno “Proceed with changepackage for all OWNER=root users in ${USERS_CSV}?” “y”; then
      # … logic …
      fi

  3. lve is not being updated after cloning, lve are stored in /etc/container/ve.cfg, so to clone it, either copy the values from lvectl and reply them using lvectl

    lvectl package-list | grep -i ‘ProPlan’
    ProPlan 200 20480M 0K 80 250 20480 1024
    ProPlan 100 1024M 0K 20 100 1024 1024

    1. unless lve was set using the lve package extension on whm > editing package (which then is defined at the level of the package file), e.g.:

      QUOTA=5120
      lve_inodes_hard=110000
      lve_inodes_soft=100000
      lve_mysql_cpu=DEFAULT
      lve_mysql_io=DEFAULT
      _PACKAGE_EXTENSIONS=lve
      lve_ncpu=DEFAULT
      lve_cpu=100%
      lve_io=1024
      lve_mem=
      lve_pmem=1G
      lve_nproc=100
      lve_iops=1024
      lve_ep=20

Leave a Reply

Your email address will not be published. Required fields are marked *