Deployment currently has two documented paths. For the dev environment, code is deployed in cPanel using a Bitbucket webhook plus deploy scripts. For UAT and live preparation, the release flow first promotes code across the long-lived Git branches in order: devtestuatlive using a server-side Python merge helper.

i
Server-side merge helper The merge script is maintained on the deployment servers inside a repo_merge folder under the server user's home directory. The examples below use dummy paths only, so update them with the real values in that environment.

Overview

The deployment preparation flow has three main parts:

  1. Dev environment auto deployment through cPanel

    A Bitbucket push triggers a webhook, which is received by a custom PHP script and forwarded to a deployment shell script. The same shell script also sends a Cliq channel notification after deployment.

  2. Promote branches using the merge helper

    This ensures test, uat, and live receive the expected upstream code in order.

  3. Run the environment deployment scripts

    Once branch promotion completes successfully, call the UAT or live deployment shell scripts from the deployment server.

Dev cPanel flow

In the dev environment, deployment is handled through cPanel rather than the branch-promotion helper. The current flow is:

flowchart LR A[Bitbucket push] --> B[Bitbucket webhook] B --> C[deploy.php] C --> D[deploy.sh] D --> E[Application deployed] D --> F[Cliq channel notification]

The webhook request is received by a custom PHP entry point, which triggers a deployment shell script. That same shell script also posts deployment status into Cliq using the Cliq channel message API.

i
Dev-only deployment path This cPanel webhook flow is specifically used for the dev environment. It is separate from the branch promotion process documented below for UAT and live preparation.

cPanel deploy scripts

The cPanel deployment scripts are maintained under the cPanel web root in the following locations:

PathPurpose
/public_html/cicd/bashscripts/deploy.php Receives the Bitbucket webhook request and triggers the deployment shell script.
/public_html/cicd/bashscripts/deploy.sh Executes the deployment steps and sends the final Cliq notification from the same script.

Operationally, the dev environment follows this chain:

  1. Push code to Bitbucket

    The push event becomes the trigger for the deployment automation.

  2. Bitbucket sends the webhook request

    The webhook hits the custom PHP receiver hosted in cPanel.

  3. deploy.php validates and forwards the action

    This script acts as the receiver and handoff point into the shell deployment layer.

  4. deploy.sh performs deployment

    The shell script runs the actual deployment commands for the dev environment.

  5. Cliq notification is sent

    The same shell script sends a channel update through the Cliq message API after deployment completes.

Branch promotion flow

The merge script performs a sequential promotion chain:

flowchart LR A[dev] --> B[test] B --> C[uat] C --> D[live]

The script first refreshes dev, then merges:

StepAction
1git checkout dev and git pull origin dev
2Merge dev into test
3Merge test into uat
4Merge uat into live
!
Important deployment gate This branch promotion step should happen before live release execution. If the script fails at any merge or push step, stop and resolve that issue before continuing.

Auto merge script

The deployment servers keep a Python helper called auto_merge.py. Below is the current script for documentation and future reference.

import subprocess
import sys
import datetime
import os

# Function to execute shell commands in a specific directory
def run_command(command, log_file, repo_path):
    try:
        result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True, cwd=repo_path)
        log_message = f"[SUCCESS] {command}\n{result.stdout.strip()}"
    except subprocess.CalledProcessError as e:
        log_message = f"[ERROR] {command}\n{e.stderr.strip() if e.stderr else e.stdout.strip()}"
        print(log_message, file=sys.stderr)
        log_file.write(log_message + "\n")
        sys.exit(1)

    print(log_message)
    log_file.write(log_message + "\n")

# Main function to automate merging
def auto_merge(repo_path, commit_message):
    if not os.path.isdir(repo_path):
        print(f"[ERROR] Invalid repository path: {repo_path}")
        sys.exit(1)

    script_dir = os.getcwd()
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    log_filename = os.path.join(script_dir, f"merge_log_{timestamp}.txt")

    with open(log_filename, "w") as log_file:
        log_file.write(f"=== Auto Merge Script Started at {timestamp} ===\n")

        log_file.write("\n--- Pulling latest changes from dev ---\n")
        run_command("git checkout dev", log_file, repo_path)
        run_command("git pull origin dev", log_file, repo_path)

        branches = [("dev", "test"), ("test", "uat"), ("uat", "live")]

        for source, target in branches:
            merge_msg = f"MERGE_{target.upper()}_{commit_message}"

            log_file.write(f"\n--- Merging {source} -> {target} ---\n")

            run_command(f"git checkout {target}", log_file, repo_path)
            run_command(f"git pull origin {target}", log_file, repo_path)
            run_command(f"git merge --no-ff {source} -m \"{merge_msg}\"", log_file, repo_path)
            run_command(f"git push origin {target}", log_file, repo_path)

        log_file.write("\n=== Auto Merge Completed Successfully ===\n")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python auto_merge.py <REPO_PATH> '<COMMIT_MESSAGE_SUFFIX>'")
        print("Usage Example: python auto_merge.py './nhance' '<COMMIT_MESSAGE_SUFFIX>'")
        print("Usage Example: python auto_merge.py './nhance-enrollment' '<COMMIT_MESSAGE_SUFFIX>'")
        sys.exit(1)

    repo_path = sys.argv[1].strip()
    commit_suffix = sys.argv[2].strip().upper()

    auto_merge(repo_path, commit_suffix)

Script usage

The script is typically run from the server-side repo_merge directory. Use a dummy home path like the example below and replace it with the real deployment user home directory.

Dummy pathDescription
/home/deploy-user/repo_merge Example folder where auto_merge.py is stored on UAT or live.
/home/deploy-user/nhance Example repository checkout path passed as the first script argument.
RELEASE_2026_05_12 Example commit message suffix appended into merge commit messages.
cd /home/deploy-user/repo_merge
python3 auto_merge.py "/home/deploy-user/nhance" "RELEASE_2026_05_12"

Usage format:

python3 auto_merge.py "<REPO_PATH>" "<COMMIT_MESSAGE_SUFFIX>"

The commit message suffix is converted to uppercase by the script. For each merge step, the script generates messages like:

MERGE_TEST_RELEASE_2026_05_12
MERGE_UAT_RELEASE_2026_05_12
MERGE_LIVE_RELEASE_2026_05_12

Log output

Every run creates a timestamped log file in the directory where the script is executed. The log captures both successful commands and the exact step that failed.

BehaviorDetails
Log filename merge_log_YYYY-MM-DD_HH-MM-SS.txt
Success logging Writes [SUCCESS] plus command output.
Error logging Writes [ERROR], prints to stderr, and exits immediately.

Operational notes

  1. Run the script from the deployment helper folder

    This keeps the generated merge log files in one predictable location.

  2. Make sure the repo path is correct before starting

    The script exits immediately if the provided repository directory does not exist.

  3. Do not continue deployment on merge failure

    If checkout, pull, merge, or push fails for any branch, stop and resolve the issue first.

  4. Use a meaningful commit suffix

    Choose a release identifier that makes merge history easy to trace later.

+
Recommended practice Treat this script as the first gate in the deployment flow for UAT and live releases. Once branch promotion completes successfully, continue with the environment-specific deployment steps.

Code move scripts

!
Strict warning before running these steps Before calling the UAT or live deployment shell scripts, any required database changes and environment variable changes must be done manually. Do not assume these scripts handle DB updates, migrations, secrets, or environment-specific configuration automatically.

After the previous Python merge script completes successfully, the next step is to run the environment-specific deployment shell scripts from the server. These scripts are available only on the deployment servers and are not stored in this repository.

ScriptEnvironment / Purpose
crm_deployment.sh Live CRM code move script.
enrolment_depoloyment.sh Live enrolment code move script.
uat_crm_deployment.sh UAT CRM code move script.
uat_enrolment_depoloyment.sh UAT enrolment code move script.

The execution rule is simple:

  1. Run auto_merge.py first

    This completes the required branch promotion chain before any server-side code move starts.

  2. Choose the scripts based on target environment

    For UAT, call the uat_* scripts. For live, call the non-UAT deployment scripts.

  3. Execute the relevant application scripts

    Run the CRM and enrolment deployment scripts that match the environment being released.

# UAT example
python3 auto_merge.py "/home/deploy-user/nhance" "RELEASE_2026_05_12"
./uat_crm_deployment.sh
./uat_enrolment_depoloyment.sh

# LIVE example
python3 auto_merge.py "/home/deploy-user/nhance" "RELEASE_2026_05_12"
./crm_deployment.sh
./enrolment_depoloyment.sh
!
Server-only scripts These deployment scripts exist only on the deployment servers. Keep this documentation as the operational reference, but do not expect to find the actual shell files inside this repository.

Manual fallback

If the dev auto-deploy flow fails, manual deployment is still available through cPanel Git Version Control. That manual apply option should be used as the fallback path when the webhook receiver or deploy script does not complete successfully.

!
Fallback path Keep cPanel Git Version Control enabled for the repository so the team can manually apply the latest revision whenever the automatic cPanel deployment path fails.