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: dev → test →
uat → live using a server-side Python merge helper.
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.
The deployment preparation flow has three main parts:
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.
This ensures test, uat, and live receive the expected upstream code in order.
Once branch promotion completes successfully, call the UAT or live deployment shell scripts from the deployment server.
In the dev environment, deployment is handled through cPanel rather than the branch-promotion helper. The current flow is:
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.
The cPanel deployment scripts are maintained under the cPanel web root in the following locations:
| Path | Purpose |
|---|---|
/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:
The push event becomes the trigger for the deployment automation.
The webhook hits the custom PHP receiver hosted in cPanel.
deploy.php validates and forwards the action
This script acts as the receiver and handoff point into the shell deployment layer.
deploy.sh performs deployment
The shell script runs the actual deployment commands for the dev environment.
The same shell script sends a channel update through the Cliq message API after deployment completes.
The merge script performs a sequential promotion chain:
The script first refreshes dev, then merges:
| Step | Action |
|---|---|
| 1 | git checkout dev and git pull origin dev |
| 2 | Merge dev into test |
| 3 | Merge test into uat |
| 4 | Merge uat into live |
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)
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 path | Description |
|---|---|
/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
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.
| Behavior | Details |
|---|---|
| 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. |
This keeps the generated merge log files in one predictable location.
The script exits immediately if the provided repository directory does not exist.
If checkout, pull, merge, or push fails for any branch, stop and resolve the issue first.
Choose a release identifier that makes merge history easy to trace later.
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.
| Script | Environment / 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:
auto_merge.py first
This completes the required branch promotion chain before any server-side code move starts.
For UAT, call the uat_* scripts. For live, call the non-UAT deployment 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
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.