#!/usr/bin/env bash set -euo pipefail # --- Config --- APP_DIR="/var/www/html/alliance_sms" WEB_ROOT="/var/www/html" BUILD_ROOT="/tmp/alliance_sms_release_$(date +%Y%m%d_%H%M%S)" OUT_ZIP="$BUILD_ROOT/../alliance_sms_package_$(date +%Y%m%d_%H%M%S).zip" DB_NAME="mms" DB_USER="root" DB_PASS="cachicamo" VHOST_FILE="apache/alliance_sms.conf" # --- Prep build tree --- mkdir -p "$BUILD_ROOT"/{app,apache,sql,scripts} echo "[*] Build dir: $BUILD_ROOT" # --- Copy application (exclude volatile) --- rsync -a --delete \ --exclude 'logs/*' \ --exclude '*.log' \ --exclude '.git' \ --exclude '.DS_Store' \ "$APP_DIR"/ "$BUILD_ROOT/app/" # --- Generate Apache vhost --- cat > "$BUILD_ROOT/$VHOST_FILE" <<'EOF' # /etc/apache2/sites-available/alliance_sms.conf # Redirect all HTTP to HTTPS ServerName alliance.csc20.com ServerAlias sms.csc20.com mms.csc20.com RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # alliance.csc20.com — app UI/API under /var/www/html/alliance_sms ServerName alliance.csc20.com DocumentRoot /var/www/html/alliance_sms DirectoryIndex index.php AllowOverride All Options -Indexes +FollowSymLinks Require all granted SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/" ErrorLog ${APACHE_LOG_DIR}/alliance_error.log CustomLog ${APACHE_LOG_DIR}/alliance_access.log combined SSLEngine On SSLCertificateFile /etc/letsencrypt/live/alliance.csc20.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/alliance.csc20.com/privkey.pem # Security and perf Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set Referrer-Policy "strict-origin-when-cross-origin" # sms.csc20.com — webhooks (receive_sms.php, receive_mms.php) ServerName sms.csc20.com DocumentRoot /var/www/html/alliance_sms DirectoryIndex index.php AllowOverride All Options -Indexes +FollowSymLinks Require all granted SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/" ErrorLog ${APACHE_LOG_DIR}/sms_error.log CustomLog ${APACHE_LOG_DIR}/sms_access.log combined SSLEngine On SSLCertificateFile /etc/letsencrypt/live/sms.csc20.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/sms.csc20.com/privkey.pem Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set Referrer-Policy "strict-origin-when-cross-origin" # mms.csc20.com — serves processed assets from /var/www/html ServerName mms.csc20.com DocumentRoot /var/www/html DirectoryIndex index.php index.html AllowOverride None Options -Indexes +FollowSymLinks Require all granted SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/" ErrorLog ${APACHE_LOG_DIR}/mms_error.log CustomLog ${APACHE_LOG_DIR}/mms_access.log combined SSLEngine On SSLCertificateFile /etc/letsencrypt/live/mms.csc20.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/mms.csc20.com/privkey.pem Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set Referrer-Policy "strict-origin-when-cross-origin" EOF # --- Generate DB schema (DDL only) --- cat > "$BUILD_ROOT/sql/schema.sql" <<'EOF' -- Create database & root password (optional; adjust for your env) CREATE DATABASE IF NOT EXISTS `mms` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE `mms`; -- (Optional) set root password; comment out if using unix_socket auth -- ALTER USER 'root'@'localhost' IDENTIFIED BY 'cachicamo'; -- campaigns (index/list of campaigns) CREATE TABLE IF NOT EXISTS `campaigns` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `campaign_name` VARCHAR(255) NOT NULL, `campaign_ext_id` VARCHAR(64) NOT NULL, `originating_number` VARCHAR(20) NOT NULL, `phone_numbers_file` VARCHAR(255) NULL, `image_file` VARCHAR(255) NULL, `text_file` VARCHAR(255) NULL, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `user_id` INT UNSIGNED NULL, `user_name` VARCHAR(128) NULL, `archived` TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `ux_campaigns_ext` (`campaign_ext_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- template recipients table (per-campaign tables are created LIKE this) CREATE TABLE IF NOT EXISTS `campaign` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `srcnum` VARCHAR(20) NOT NULL, `campaignID` VARCHAR(64) NOT NULL, `destnum` VARCHAR(40) NOT NULL, `sent` TINYINT(1) NOT NULL DEFAULT 0, `operator_msg_id` VARCHAR(40) NULL, `dlr_id` VARCHAR(40) NULL, `dlr_done_at` DATETIME NULL, `dlr_delivery_status` VARCHAR(40) NULL, `dlr_delivery_error_code` VARCHAR(5) NULL, PRIMARY KEY (`id`), KEY `idx_campaign_sent` (`sent`), KEY `idx_campaign_destnum` (`destnum`), KEY `idx_campaign_operator_msg_id` (`operator_msg_id`), KEY `idx_campaign_dlr_id` (`dlr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- inbound log (legacy) + flags we added CREATE TABLE IF NOT EXISTS `smsrec` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `from_num` VARCHAR(20) NULL, `to_num` VARCHAR(20) NULL, `message` VARCHAR(1024) NULL, `message_norm` VARCHAR(128) NULL, `msg_id` VARCHAR(64) NULL, `insert_date` DATETIME NULL, `replied` SMALLINT(5) UNSIGNED NULL, `keyword_code` VARCHAR(32) NULL, `has_conversation` TINYINT(1) NOT NULL DEFAULT 0, `pending_from_customer` TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_smsrec_from_to` (`from_num`,`to_num`), KEY `idx_smsrec_msgid` (`msg_id`), KEY `idx_smsrec_insert` (`insert_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- keyword engine CREATE TABLE IF NOT EXISTS `sms_keywords` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `keyword_code` VARCHAR(32) NOT NULL, -- canonical: OPTOUT, HELP, etc. `variant_norm` VARCHAR(128) NOT NULL, -- normalized variant: "stop", "parar", etc. PRIMARY KEY (`id`), UNIQUE KEY `ux_kw_variant` (`variant_norm`), KEY `idx_kw_code` (`keyword_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `sms_responses` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `keyword_code` VARCHAR(32) NOT NULL, -- matches sms_keywords.keyword_code `response_text` VARCHAR(1600) NOT NULL, `auto_reply` TINYINT(1) NOT NULL DEFAULT 1, `is_optout` TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_resp_code` (`keyword_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- opt-outs per pair CREATE TABLE IF NOT EXISTS `sms_optouts` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `from_num` VARCHAR(20) NOT NULL, `to_num` VARCHAR(20) NOT NULL, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `ux_pair` (`from_num`,`to_num`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- conversations CREATE TABLE IF NOT EXISTS `sms_conversations` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `from_num` VARCHAR(20) NOT NULL, `to_num` VARCHAR(20) NOT NULL, `status` ENUM('open','closed') NOT NULL DEFAULT 'open', `last_customer_msg_at` DATETIME NULL, `last_operator_msg_at` DATETIME NULL, `unread_customer_count` INT NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_conv_pair` (`from_num`,`to_num`), KEY `idx_conv_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `sms_conversation_msgs` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `conversation_id` INT UNSIGNED NOT NULL, `direction` ENUM('IN','OUT') NOT NULL, `body` VARCHAR(1600) NOT NULL, `provider_msg_id` VARCHAR(64) NULL, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_msg_conv` (`conversation_id`), KEY `idx_msg_provider` (`provider_msg_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- DLR storage CREATE TABLE IF NOT EXISTS `mms_dlr` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `dlr_id` VARCHAR(64) NULL, `message_id` VARCHAR(64) NULL, `transaction_id` VARCHAR(64) NULL, `from_num` VARCHAR(32) NULL, `to_nums` VARCHAR(255) NULL, `mm_status_code` VARCHAR(64) NULL, `provider_type` VARCHAR(64) NULL, `raw_json` MEDIUMTEXT NULL, `received_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_mms_msg` (`message_id`), KEY `idx_mms_tx` (`transaction_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `sms_dlr` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `dlr_id` VARCHAR(64) NULL, `object` VARCHAR(32) NULL, `type` VARCHAR(32) NULL, `from_num` VARCHAR(32) NULL, `to_num` VARCHAR(32) NULL, `sms_id` VARCHAR(64) NULL, `inserted_at` DATETIME NULL, `updated_at` DATETIME NULL, `submitted_at` DATETIME NULL, `done_at` DATETIME NULL, `delivery_status` VARCHAR(32) NULL, `delivery_error_code` VARCHAR(32) NULL, `raw_json` MEDIUMTEXT NULL, `received_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_sms_id` (`sms_id`), KEY `idx_sms_status` (`delivery_status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- audit table CREATE TABLE IF NOT EXISTS `msg_sent_audit` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `campaigns_id` INT UNSIGNED NOT NULL, `campaign_ex_id` VARCHAR(20) NOT NULL, `datesent` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `destnum` VARCHAR(40) NOT NULL, `srcnum` VARCHAR(20) NOT NULL, `operator_msg_id` VARCHAR(40) NULL, `dlr_id` VARCHAR(40) NULL, `dlr_done_at` DATETIME NULL, `dlr_delivery_status` VARCHAR(40) NULL, `dlr_delivery_error_code` VARCHAR(5) NULL, PRIMARY KEY (`id`), KEY `idx_msa_campaigns_id` (`campaigns_id`), KEY `idx_msa_campaign_ex_id` (`campaign_ex_id`), KEY `idx_msa_destnum` (`destnum`), KEY `idx_msa_datesent` (`datesent`), KEY `idx_msa_operator_msg_id` (`operator_msg_id`), KEY `idx_msa_dlr_id` (`dlr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- seed OPTOUT keyword (optional) INSERT IGNORE INTO sms_keywords(keyword_code, variant_norm) VALUES ('OPTOUT','stop'),('OPTOUT','cancel'),('OPTOUT','unsubscribe'); INSERT IGNORE INTO sms_responses(keyword_code, response_text, auto_reply, is_optout) VALUES ('OPTOUT','You have been removed from our list. Thank you',1,1); EOF # --- Optional full DB dump (data) if mysqldump is available --- if command -v mysqldump >/dev/null 2>&1; then echo "[*] Creating optional data dump (data_dump.sql)" mysqldump --routines --triggers --events --single-transaction \ -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BUILD_ROOT/sql/data_dump.sql" || true else echo "[*] mysqldump not found; skipping data dump" fi # --- Bootstrap script for Debian 12 --- cat > "$BUILD_ROOT/scripts/bootstrap_debian12.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail APP_SRC="$(pwd)/app" VHOST_SRC="$(pwd)/apache/alliance_sms.conf" SCHEMA_SQL="$(pwd)/sql/schema.sql" DATA_SQL="$(pwd)/sql/data_dump.sql" # 1) System packages export DEBIAN_FRONTEND=noninteractive apt-get update apt-get install -y apache2 mariadb-server \ php php-fpm php-mysql php-curl php-mbstring php-xml php-zip php-gd php-imagick \ imagemagick unzip zip curl certbot python3-certbot-apache # 2) PHP-FPM + Apache modules a2enmod proxy proxy_fcgi setenvif rewrite headers ssl a2enconf php*-fpm || true # 3) Deploy app mkdir -p /var/www/html/alliance_sms rsync -a "$APP_SRC"/ /var/www/html/alliance_sms/ mkdir -p /var/www/html/alliance_sms/logs chown -R www-data:www-data /var/www/html/alliance_sms chmod -R 775 /var/www/html/alliance_sms/logs # 4) Apache vhost cp "$VHOST_SRC" /etc/apache2/sites-available/alliance_sms.conf a2ensite alliance_sms.conf systemctl reload apache2 # 5) MariaDB — create DB & schema # NOTE: If root uses unix_socket auth, the following works without password. mariadb -e "CREATE DATABASE IF NOT EXISTS mms;" mariadb mms < "$SCHEMA_SQL" || true # Optionally import existing data (if present) if [ -f "$DATA_SQL" ]; then mariadb mms < "$DATA_SQL" || true fi echo echo "=== IMPORTANT ===" echo "Run certbot to get real certificates:" echo " sudo certbot --apache -d alliance.csc20.com -d sms.csc20.com -d mms.csc20.com" echo "Then: sudo systemctl reload apache2" EOF chmod +x "$BUILD_ROOT/scripts/bootstrap_debian12.sh" # --- README --- cat > "$BUILD_ROOT/README_DEPLOY.md" <<'EOF' # Alliance SMS — Release Package ## Contents - `app/` — application code (place at `/var/www/html/alliance_sms`) - `apache/alliance_sms.conf` — Apache vhost for `alliance.csc20.com`, `sms.csc20.com`, `mms.csc20.com` - `sql/schema.sql` — full schema (DDL). Creates DB `mms` and all required tables. - `sql/data_dump.sql` — optional data dump (if available at build time). - `scripts/bootstrap_debian12.sh` — one-shot installer for Debian 12. ## Quick install (new server, Debian 12) ```bash unzip alliance_sms_package_*.zip cd alliance_sms_release_*/ sudo bash scripts/bootstrap_debian12.sh sudo certbot --apache -d alliance.csc20.com -d sms.csc20.com -d mms.csc20.com sudo systemctl reload apache2 ##****************ZIPIT****************** ( cd "$BUILD_ROOT/.." && zip -r9 "$OUT_ZIP" "$(basename "$BUILD_ROOT")" >/dev/null ) echo "[✓] Package created: $OUT_ZIP" echo echo "Next steps:" echo " scp $OUT_ZIP @:/tmp/" echo " ssh @" echo " cd /tmp && unzip $(basename "$OUT_ZIP") && cd $(basename "$BUILD_ROOT")" echo " sudo bash scripts/bootstrap_debian12.sh" ##***************************************** --- ### How to use ```bash sudo bash /var/www/html/alliance_sms/make_release.sh # → prints the ZIP path, e.g. /tmp/alliance_sms_package_20250821_074501.zip # copy to new server scp /tmp/alliance_sms_package_*.zip user@new.box:/tmp/ # on new server ssh user@new.box cd /tmp unzip alliance_sms_package_*.zip cd alliance_sms_release_*/ sudo bash scripts/bootstrap_debian12.sh sudo certbot --apache -d alliance.csc20.com -d sms.csc20.com -d mms.csc20.com sudo systemctl reload apache2