1. touch creator.sh 2. save below data inside it. 3. after saving run command sed -i 's/\r$//' /root/scripts/creator.sh chmod +x /root/scripts/creator.sh /root/scripts/creator.sh #!/usr/bin/env bash set -euo pipefail # === cPanel/WHM tool paths === UAPI="/usr/local/cpanel/bin/uapi" CPAPI2="/usr/local/cpanel/bin/cpapi2" WHMAPI1="/usr/local/cpanel/bin/whmapi1" WHOOWNS="/usr/local/cpanel/scripts/whoowns" # Save summary here OUTPUT_DIR="/root/scripts/output" # ---------- Helpers ---------- require_root() { if [[ $EUID -ne 0 ]]; then echo "[ERR] Please run as root."; exit 1; fi; } cpuser_exists() { [[ -f "/var/cpanel/users/$1" ]]; } who_owns() { local domain="$1" owner="" if [[ -x "$WHOOWNS" ]]; then owner=$("$WHOOWNS" "$domain" 2>/dev/null | awk '{print $1}') elif [[ -r /etc/userdomains ]]; then owner=$(awk -F: -v d="$domain" '$1==d{gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2}' /etc/userdomains 2>/dev/null) fi echo "$owner" } domain_owned_by_user() { [[ "$(who_owns "$1")" == "$CPUSER" ]]; } find_owned_rootdomain() { local domain="$1"; IFS='.' read -r -a parts <<< "$domain"; local n=${#parts[@]} for ((i=0; i<=n-2; i++)); do local candidate; candidate=$(IFS='.'; echo "${parts[@]:i}") if [[ "$candidate" != "$domain" ]] && domain_owned_by_user "$candidate"; then echo "$candidate"; return 0 fi done echo ""; return 1 } sanitize_label() { echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9-]+/-/g;s/^-+//;s/-+$//'; } sanitize_mysql() { echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_]+/_/g;s/^_+//;s/_+$//'; } first_label() { echo "${1%%.*}" | tr '[:upper:]' '[:lower:]'; } now_str() { date +"%Y%m%d-%H%M%S"; } gen_password() { if command -v openssl >/dev/null 2>&1; then openssl rand -base64 32 | tr -d '\n' | tr '+/' '-_' | cut -c1-24 else tr -dc 'A-Za-z0-9-_=+@#%^*' /dev/null 2>&1; then md=$("$WHMAPI1" accountsummary user="$CPUSER" 2>/dev/null | awk '$1=="main_domain:"{print $2; exit}') fi if [[ -z "$md" && -f "/var/cpanel/users/$CPUSER" ]]; then md=$(awk -F= '$1=="DNS"{print $2; exit}' "/var/cpanel/users/$CPUSER") fi if [[ -z "$md" ]]; then md=$("$UAPI" --user="$CPUSER" --output=json DomainInfo list_domains 2>/dev/null \ | tr -d '\n' | sed -n 's/.*"main_domain":"\([^"]\+\)".*/\1/p' | head -n1) fi echo "$md" } # Accept Y/n or a domain in the same prompt ask_domain_choice() { local input read -rp "Also create a domain/subdomain? [Y/n or type domain]: " input input="${input,,}" if [[ -z "$input" ]]; then WANT_DOMAIN="y"; return 0 fi if [[ "$input" =~ ^y(es)?$ ]]; then WANT_DOMAIN="y"; return 0 fi if [[ "$input" =~ ^n(o)?$ ]]; then WANT_DOMAIN="n"; return 0 fi # Domain pattern (must contain at least one dot; letters/digits/hyphens) if [[ "$input" =~ ^[a-z0-9][a-z0-9-]*(\.[a-z0-9-]+)+$ ]]; then NEWDOMAIN="$input" WANT_DOMAIN="y"; return 0 fi # If unrecognized, treat as 'n' WANT_DOMAIN="n" } # ---------- Prompts ---------- prompt_inputs() { # cPanel user (default kikde) read -rp "cPanel account [kikde]: " CPUSER; CPUSER="${CPUSER:-kikde}" cpuser_exists "$CPUSER" || { echo "[ERR] cPanel user '$CPUSER' not found."; exit 1; } # Domain flow toggle (now supports entering a domain directly) if [[ "${SKIP_DOMAIN:-0}" == "1" ]]; then WANT_DOMAIN="n" else ask_domain_choice fi DEFAULT_DB_SUFFIX="live" # will be replaced if domain provided if [[ "${WANT_DOMAIN,,}" =~ ^y(es)?$ ]]; then if [[ -z "${NEWDOMAIN:-}" ]]; then read -rp "Domain to create (e.g., example.com or sub.example.com): " NEWDOMAIN NEWDOMAIN="$(echo "$NEWDOMAIN" | tr '[:upper:]' '[:lower:]')" fi [[ "$NEWDOMAIN" =~ ^[a-z0-9.-]+$ ]] || { echo "[ERR] Invalid domain."; exit 1; } # Laravel default = YES (Enter => Yes) read -rp "Is this a Laravel app (docroot = /public)? [Y/n]: " IS_LARAVEL IS_LARAVEL="${IS_LARAVEL:-Y}" HOMEDIR="/home/$CPUSER" DOCROOT_REL_BASE="public_html/$NEWDOMAIN" DOCROOT_ABS_BASE="$HOMEDIR/$DOCROOT_REL_BASE" if [[ "${IS_LARAVEL,,}" =~ ^y(es)?$ ]]; then DOCROOT_REL="$DOCROOT_REL_BASE/public" DOCROOT_ABS="$DOCROOT_ABS_BASE/public" else DOCROOT_REL="$DOCROOT_REL_BASE" DOCROOT_ABS="$DOCROOT_ABS_BASE" fi # derive DB default suffix from first label of NEWDOMAIN DEFAULT_DB_SUFFIX="$(first_label "$NEWDOMAIN")" DEFAULT_DB_SUFFIX="$(sanitize_mysql "$DEFAULT_DB_SUFFIX")" [[ -z "$DEFAULT_DB_SUFFIX" ]] && DEFAULT_DB_SUFFIX="live" else NEWDOMAIN=""; IS_LARAVEL="Y"; DOCROOT_REL=""; DOCROOT_ABS=""; DOCROOT_REL_BASE=""; DOCROOT_ABS_BASE=""; HOMEDIR="/home/$CPUSER" fi # DB flow toggle (default Yes) if [[ "${SKIP_DB:-0}" == "1" ]]; then WANT_DB="n" else read -rp "Create database + user with ALL PRIVILEGES? [Y/n]: " WANT_DB; WANT_DB="${WANT_DB:-Y}" fi if [[ "${WANT_DB,,}" =~ ^y(es)?$ ]]; then # DB name default from domain first label (or 'live' if no domain) read -rp "Database name (suffix only) [${DEFAULT_DB_SUFFIX}]: " DBNAME DBNAME="${DBNAME:-$DEFAULT_DB_SUFFIX}" DBNAME="$(sanitize_mysql "$DBNAME")" [[ -z "$DBNAME" ]] && { echo "[ERR] Database name cannot be empty."; exit 1; } # DB user default = DB name read -rp "MySQL username (suffix only, default = DB) [${DBNAME}]: " DBUSER DBUSER="${DBUSER:-$DBNAME}" DBUSER="$(sanitize_mysql "$DBUSER")" [[ -z "$DBUSER" ]] && { echo "[ERR] MySQL username cannot be empty."; exit 1; } # Password: one prompt — leave blank to auto-generate read -srp "MySQL password (leave blank to auto-generate): " DBPASS; echo if [[ -z "$DBPASS" ]]; then DBPASS="$(gen_password)" echo "[INFO] Generated password: $DBPASS" else read -srp "Confirm password: " DBPASS2; echo [[ "$DBPASS" == "$DBPASS2" ]] || { echo "[ERR] Passwords do not match."; exit 1; } (( ${#DBPASS} >= 8 )) || { echo "[ERR] Password must be at least 8 chars."; exit 1; } fi FULL_DB="${CPUSER}_${DBNAME}" FULL_USER="${CPUSER}_${DBUSER}" (( ${#FULL_DB} <= 64 )) || { echo "[ERR] DB name too long (>64)."; exit 1; } (( ${#FULL_USER} <= 32 )) || { echo "[ERR] MySQL user too long (>32)."; exit 1; } fi echo echo "Summary:" echo " cPanel user : $CPUSER" if [[ -n "${NEWDOMAIN:-}" ]]; then echo " Domain : $NEWDOMAIN" echo " Laravel : $([[ "${IS_LARAVEL,,}" =~ ^y ]] && echo Yes || echo No)" echo " Docroot : ~$CPUSER/$DOCROOT_REL" else echo " Domain : " fi if [[ "${WANT_DB,,}" =~ ^y(es)?$ ]]; then echo " DB (suffix) : ${DBNAME} -> actual may be '${CPUSER}_${DBNAME}'" echo " User (suf) : ${DBUSER} -> actual may be '${CPUSER}_${DBUSER}'" else echo " Database : " fi read -rp "Proceed? [Y/n]: " OK OK="${OK:-Y}" [[ "${OK,,}" =~ ^y(es)?$ ]] || { echo "Aborted."; exit 1; } } # ---------- Filesystem ---------- ensure_docroot() { [[ -z "${DOCROOT_ABS:-}" ]] && return 0 if [[ "${IS_LARAVEL,,}" =~ ^y(es)?$ ]]; then echo "[..] Creating directories: $DOCROOT_ABS_BASE and $DOCROOT_ABS" mkdir -p "$DOCROOT_ABS" chown -R "$CPUSER:$CPUSER" "$DOCROOT_ABS_BASE" chmod 750 "$DOCROOT_ABS_BASE" || true else echo "[..] Creating directory: $DOCROOT_ABS" mkdir -p "$DOCROOT_ABS" chown -R "$CPUSER:$CPUSER" "$DOCROOT_ABS" chmod 750 "$DOCROOT_ABS" || true fi } # ---------- Domain creation ---------- create_subdomain() { local rootdomain="$1" local sublabel="${NEWDOMAIN%.$rootdomain}"; sublabel="$(sanitize_label "$sublabel")" echo "[..] Creating SUBDOMAIN '$NEWDOMAIN' under '$rootdomain' (docroot: $DOCROOT_REL)" "$UAPI" --user="$CPUSER" SubDomain addsubdomain domain="$sublabel" rootdomain="$rootdomain" dir="$DOCROOT_REL" >/dev/null } create_addondomain() { local label main_root; main_root="$(main_domain_for_user)" [[ -n "$main_root" ]] || { echo "[ERR] Could not determine main_domain for user '$CPUSER'."; exit 1; } label="$(sanitize_label "${NEWDOMAIN%%.*}")"; [[ -n "$label" ]] || label="addondomain" echo "[..] Creating ADDON domain '$NEWDOMAIN' (cpapi2) with subdomain label '$label' on '$main_root' (docroot: $DOCROOT_REL)" "$CPAPI2" --user="$CPUSER" AddonDomain addaddondomain dir="$DOCROOT_REL" newdomain="$NEWDOMAIN" subdomain="$label" >/dev/null } # ---------- MySQL (auto-prefix detection + fallback) ---------- list_has_db() { "$UAPI" --user="$CPUSER" --output=json Mysql list_databases | tr -d '\n' | grep -q "\"${1}\""; } list_has_user() { "$UAPI" --user="$CPUSER" --output=json Mysql list_users | tr -d '\n' | grep -q "\"${1}\""; } create_db_autoprefix() { echo "[..] Creating database (auto-prefix): try '$DBNAME'" if "$UAPI" --user="$CPUSER" Mysql create_database name="$DBNAME" >/dev/null 2>&1; then if list_has_db "$FULL_DB" || list_has_db "$DBNAME"; then return 0; fi fi echo " -> retry with prefixed name '$FULL_DB'" "$UAPI" --user="$CPUSER" Mysql create_database name="$FULL_DB" >/dev/null } create_user_autoprefix() { echo "[..] Creating MySQL user (auto-prefix): try '$DBUSER'" if "$UAPI" --user="$CPUSER" Mysql create_user name="$DBUSER" password="$DBPASS" >/dev/null 2>&1; then if list_has_user "$FULL_USER" || list_has_user "$DBUSER"; then return 0; fi fi echo " -> retry with prefixed name '$FULL_USER'" "$UAPI" --user="$CPUSER" Mysql create_user name="$FULL_USER" password="$DBPASS" >/dev/null } grant_privs_autoprefix() { echo "[..] Granting ALL PRIVILEGES to ${FULL_USER} on ${FULL_DB}" if "$UAPI" --user="$CPUSER" Mysql set_privileges_on_database \ user="$FULL_USER" database="$FULL_DB" privileges="ALL PRIVILEGES" >/dev/null 2>&1; then : else echo " -> retry with explicit privilege list" local ALL_LIST="ALTER,ALTER ROUTINE,CREATE,CREATE ROUTINE,CREATE TEMPORARY TABLES,CREATE VIEW,DELETE,DROP,EVENT,EXECUTE,INDEX,INSERT,LOCK TABLES,REFERENCES,SELECT,SHOW VIEW,TRIGGER,UPDATE" "$UAPI" --user="$CPUSER" Mysql set_privileges_on_database \ user="$FULL_USER" database="$FULL_DB" privileges="$ALL_LIST" >/dev/null fi # Verify privileges on the prefixed names if ! "$UAPI" --user="$CPUSER" Mysql get_privileges_on_database \ user="$FULL_USER" database="$FULL_DB" | grep -qE 'ALL PRIVILEGES|SELECT'; then echo "[ERR] Could not verify privileges; please recheck in cPanel ? MySQL Databases." exit 1 fi } rollback_db_user() { echo "[RB] Deleting MySQL user (if exists)" "$UAPI" --user="$CPUSER" Mysql delete_user name="$DBUSER" >/dev/null 2>&1 || true "$UAPI" --user="$CPUSER" Mysql delete_user name="$FULL_USER" >/dev/null 2>&1 || true } rollback_db() { echo "[RB] Deleting database (if exists)" "$UAPI" --user="$CPUSER" Mysql delete_database name="$DBNAME" >/dev/null 2>&1 || true "$UAPI" --user="$CPUSER" Mysql delete_database name="$FULL_DB" >/dev/null 2>&1 || true } # ---------- Summary ---------- print_summary() { local ts out_file actual_db actual_user ts="$(now_str)"; mkdir -p "$OUTPUT_DIR"; umask 077 out_file="$OUTPUT_DIR/${CPUSER}_$([[ -n "${NEWDOMAIN:-}" ]] && echo "${NEWDOMAIN//\//_}" || echo "dbonly")_${ts}.txt" if [[ "${WANT_DB,,}" =~ ^y(es)?$ ]]; then if list_has_db "$FULL_DB"; then actual_db="$FULL_DB"; else actual_db="$DBNAME"; fi if list_has_user "$FULL_USER"; then actual_user="$FULL_USER"; else actual_user="$DBUSER"; fi fi { echo "===== BEGIN SITE DETAILS =====" echo "Timestamp: $ts" echo echo "cPanel User: $CPUSER" echo if [[ -n "${NEWDOMAIN:-}" ]]; then echo "Domain:" echo " Name: $NEWDOMAIN" echo " Laravel: $([[ "${IS_LARAVEL,,}" =~ ^y ]] && echo Yes || echo No)" echo " Docroot: /home/$CPUSER/$DOCROOT_REL" echo else echo "Domain: " echo fi if [[ "${WANT_DB,,}" =~ ^y(es)?$ ]]; then echo "MySQL:" echo " Host: localhost" echo " DB: $actual_db" echo " User: $actual_user" echo " Pass: $DBPASS" echo echo ".env sample:" echo " DB_HOST=localhost" echo " DB_DATABASE=$actual_db" echo " DB_USERNAME=$actual_user" echo " DB_PASSWORD=$DBPASS" echo echo "MySQL CLI:" echo " mysql -u \"$actual_user\" -p'$DBPASS' \"$actual_db\"" else echo "MySQL: " fi echo "===== END SITE DETAILS =====" } >"$out_file" echo; echo "[OK] All set!"; echo cat "$out_file" echo; echo "[INFO] Saved to: $out_file (permissions 600)" } # ---------- Main ---------- main() { require_root command -v "$UAPI" >/dev/null || { echo "[ERR] UAPI not found at $UAPI"; exit 1; } command -v "$CPAPI2" >/dev/null || { echo "[ERR] cpapi2 not found at $CPAPI2"; exit 1; } command -v "$WHMAPI1" >/dev/null || echo "[INFO] whmapi1 not found; using file/UAPI fallbacks." [[ -x "$WHOOWNS" ]] || echo "[INFO] whoowns not found; will fall back to /etc/userdomains." prompt_inputs # Domain creation (optional) if [[ -n "${NEWDOMAIN:-}" ]]; then if domain_owned_by_user "$NEWDOMAIN"; then echo "[INFO] Domain already exists for this user: $NEWDOMAIN (skipping)" else MAIN_ROOT="$(main_domain_for_user)" if [[ -n "$MAIN_ROOT" && "$NEWDOMAIN" == *".${MAIN_ROOT}" ]]; then ensure_docroot; create_subdomain "$MAIN_ROOT" else ROOT_OWNED="$(find_owned_rootdomain "$NEWDOMAIN" || true)" if [[ -n "$ROOT_OWNED" ]]; then ensure_docroot; create_subdomain "$ROOT_OWNED" else ensure_docroot; create_addondomain fi fi fi fi # DB flow (optional) with rollback if [[ "${WANT_DB,,}" =~ ^y(es)?$ ]]; then CREATED_DB=0; CREATED_USER=0 trap 'echo "[ERR] Error encountered. Starting rollback..."; \ if (( CREATED_USER )); then rollback_db_user; fi; \ if (( CREATED_DB )); then rollback_db; fi' ERR if list_has_db "${CPUSER}_${DBNAME}" || list_has_db "$DBNAME"; then echo "[ERR] Database already exists (either '${CPUSER}_${DBNAME}' or '$DBNAME'). Choose a different DB name."; exit 1; fi if list_has_user "${CPUSER}_${DBUSER}" || list_has_user "$DBUSER"; then echo "[ERR] MySQL user already exists (either '${CPUSER}_${DBUSER}' or '$DBUSER'). Choose a different username."; exit 1; fi create_db_autoprefix; CREATED_DB=1 create_user_autoprefix; CREATED_USER=1 grant_privs_autoprefix trap - ERR fi print_summary } main "$@"