352 lines
12 KiB
Bash
352 lines
12 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
APP_DIR="${APP_DIR:-/opt/cmpp-platform}"
|
|
REPO_URL="${REPO_URL:-http://175.27.255.91:3000/hectorzhao/lislgosms.git}"
|
|
BRANCH="${BRANCH:-main}"
|
|
PUBLIC_HTTP_PORT="${PUBLIC_HTTP_PORT:-12026}"
|
|
API_PORT="${API_PORT:-3000}"
|
|
API_ENABLE_SEND_WORKER="${API_ENABLE_SEND_WORKER:-true}"
|
|
API_SEND_WORKER_CONCURRENCY="${API_SEND_WORKER_CONCURRENCY:-50}"
|
|
GATEWAY_CONTROL_ADDR="${GATEWAY_CONTROL_ADDR:-127.0.0.1:8090}"
|
|
GATEWAY_CMPP_ADDR="${GATEWAY_CMPP_ADDR:-0.0.0.0:17890}"
|
|
CMPP_PUBLIC_HOST="${CMPP_PUBLIC_HOST:-8.160.169.106}"
|
|
CMPP_PUBLIC_PORT="${CMPP_PUBLIC_PORT:-${GATEWAY_CMPP_ADDR##*:}}"
|
|
DB_NAME="${DB_NAME:-cmpp_platform}"
|
|
DB_USER="${DB_USER:-cmpp}"
|
|
DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -hex 24 | tr -d '\n')}"
|
|
MINIO_ROOT_USER="${MINIO_ROOT_USER:-cmpp_minio}"
|
|
MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD:-$(openssl rand -base64 32 | tr -d '\n')}"
|
|
MINIO_BUCKET="${MINIO_BUCKET:-cmpp-platform}"
|
|
OBJECT_STORAGE_DRIVER="${OBJECT_STORAGE_DRIVER:-minio}"
|
|
OBJECT_STORAGE_LOCAL_ROOT="${OBJECT_STORAGE_LOCAL_ROOT:-/var/lib/cmpp-platform/object-storage}"
|
|
PROD_ADMIN_EMAIL="${PROD_ADMIN_EMAIL:-admin@example.com}"
|
|
PROD_ADMIN_USERNAME="${PROD_ADMIN_USERNAME:-prod_admin}"
|
|
PROD_ADMIN_PASSWORD="${PROD_ADMIN_PASSWORD:-$(openssl rand -base64 18 | tr -d '\n')}"
|
|
OPERATION_LOG_ARCHIVE_ENABLED="${OPERATION_LOG_ARCHIVE_ENABLED:-true}"
|
|
OPERATION_LOG_RETENTION_DAYS="${OPERATION_LOG_RETENTION_DAYS:-180}"
|
|
OPERATION_LOG_ARCHIVE_BATCH_SIZE="${OPERATION_LOG_ARCHIVE_BATCH_SIZE:-1000}"
|
|
OPERATION_LOG_ARCHIVE_MAX_BATCHES="${OPERATION_LOG_ARCHIVE_MAX_BATCHES:-20}"
|
|
OPERATION_LOG_ARCHIVE_INTERVAL_MS="${OPERATION_LOG_ARCHIVE_INTERVAL_MS:-86400000}"
|
|
SMS_RECEIPT_TIMEOUT_SCAN_ENABLED="${SMS_RECEIPT_TIMEOUT_SCAN_ENABLED:-true}"
|
|
SMS_RECEIPT_TIMEOUT_HOURS="${SMS_RECEIPT_TIMEOUT_HOURS:-72}"
|
|
SMS_RECEIPT_TIMEOUT_SCAN_INTERVAL_MS="${SMS_RECEIPT_TIMEOUT_SCAN_INTERVAL_MS:-300000}"
|
|
|
|
if [[ "$(id -u)" -ne 0 ]]; then
|
|
echo "Run as root." >&2
|
|
exit 1
|
|
fi
|
|
|
|
log() { printf '\n[%s] %s\n' "$(date '+%F %T')" "$*"; }
|
|
|
|
install_packages() {
|
|
log "Installing OS packages"
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
apt-get update
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl gnupg git nginx redis-server postgresql postgresql-contrib build-essential tar gzip xz-utils openssl
|
|
elif command -v dnf >/dev/null 2>&1; then
|
|
dnf install -y ca-certificates curl git nginx redis postgresql-server postgresql-contrib gcc gcc-c++ make tar gzip xz openssl
|
|
if [[ ! -d /var/lib/pgsql/data/base ]]; then
|
|
postgresql-setup --initdb
|
|
fi
|
|
elif command -v yum >/dev/null 2>&1; then
|
|
yum install -y ca-certificates curl git nginx redis postgresql-server postgresql-contrib gcc gcc-c++ make tar gzip xz openssl
|
|
if [[ ! -d /var/lib/pgsql/data/base ]]; then
|
|
postgresql-setup initdb
|
|
fi
|
|
else
|
|
echo "Unsupported Linux distribution: apt-get/dnf/yum not found." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
install_node() {
|
|
local version="${NODE_VERSION:-22.21.1}"
|
|
local arch
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) arch="x64" ;;
|
|
aarch64|arm64) arch="arm64" ;;
|
|
*) echo "Unsupported Node.js architecture: $(uname -m)" >&2; exit 1 ;;
|
|
esac
|
|
if command -v node >/dev/null 2>&1 && [[ "$(node -v | sed 's/^v//' | cut -d. -f1)" -ge 22 ]]; then
|
|
return
|
|
fi
|
|
log "Installing Node.js ${version} for linux-${arch}"
|
|
curl -fL "https://nodejs.org/dist/v${version}/node-v${version}-linux-${arch}.tar.xz" -o /tmp/node.tar.xz
|
|
rm -rf /usr/local/lib/nodejs
|
|
mkdir -p /usr/local/lib/nodejs
|
|
tar -C /usr/local/lib/nodejs -xJf /tmp/node.tar.xz --strip-components=1
|
|
ln -sf /usr/local/lib/nodejs/bin/node /usr/local/bin/node
|
|
ln -sf /usr/local/lib/nodejs/bin/npm /usr/local/bin/npm
|
|
ln -sf /usr/local/lib/nodejs/bin/npx /usr/local/bin/npx
|
|
ln -sf /usr/local/lib/nodejs/bin/corepack /usr/local/bin/corepack
|
|
}
|
|
|
|
install_go() {
|
|
local version="${GO_VERSION:-1.26.0}"
|
|
local arch
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) arch="amd64" ;;
|
|
aarch64|arm64) arch="arm64" ;;
|
|
*) echo "Unsupported Go architecture: $(uname -m)" >&2; exit 1 ;;
|
|
esac
|
|
if command -v go >/dev/null 2>&1 && [[ "$(go version | awk '{print $3}' | sed 's/go//')" == "$version" ]]; then
|
|
return
|
|
fi
|
|
log "Installing Go ${version} for linux-${arch}"
|
|
local downloaded=""
|
|
for url in \
|
|
"https://dl.google.com/go/go${version}.linux-${arch}.tar.gz" \
|
|
"https://golang.google.cn/dl/go${version}.linux-${arch}.tar.gz" \
|
|
"https://go.dev/dl/go${version}.linux-${arch}.tar.gz"; do
|
|
if curl -fL --connect-timeout 20 --max-time 300 "$url" -o /tmp/go.tar.gz; then
|
|
downloaded="yes"
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$downloaded" != "yes" ]]; then
|
|
echo "Failed to download Go ${version} for linux-${arch}." >&2
|
|
exit 1
|
|
fi
|
|
rm -rf /usr/local/go
|
|
tar -C /usr/local -xzf /tmp/go.tar.gz
|
|
ln -sf /usr/local/go/bin/go /usr/local/bin/go
|
|
}
|
|
|
|
install_minio() {
|
|
if [[ "$OBJECT_STORAGE_DRIVER" == "local" ]]; then
|
|
log "Skipping MinIO install because OBJECT_STORAGE_DRIVER=local"
|
|
mkdir -p "$OBJECT_STORAGE_LOCAL_ROOT"
|
|
return
|
|
fi
|
|
local arch
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) arch="amd64" ;;
|
|
aarch64|arm64) arch="arm64" ;;
|
|
*) echo "Unsupported MinIO architecture: $(uname -m)" >&2; exit 1 ;;
|
|
esac
|
|
if [[ -x /usr/local/bin/minio ]]; then
|
|
log "MinIO already installed"
|
|
else
|
|
log "Installing MinIO for linux-${arch}"
|
|
curl -fL "https://dl.min.io/server/minio/release/linux-${arch}/minio" -o /usr/local/bin/minio
|
|
chmod +x /usr/local/bin/minio
|
|
fi
|
|
useradd --system --home /var/lib/minio --shell /usr/sbin/nologin minio 2>/dev/null || true
|
|
mkdir -p /var/lib/minio
|
|
chown -R minio:minio /var/lib/minio
|
|
}
|
|
|
|
start_infra() {
|
|
log "Starting PostgreSQL and Redis"
|
|
systemctl enable --now postgresql || systemctl enable --now postgresql.service
|
|
systemctl enable --now redis-server 2>/dev/null || systemctl enable --now redis
|
|
runuser -u postgres -- psql <<SQL
|
|
DO \$\$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${DB_USER}') THEN
|
|
CREATE ROLE ${DB_USER} LOGIN PASSWORD '${DB_PASSWORD}';
|
|
ELSE
|
|
ALTER ROLE ${DB_USER} WITH LOGIN PASSWORD '${DB_PASSWORD}';
|
|
END IF;
|
|
END
|
|
\$\$;
|
|
SELECT 'CREATE DATABASE ${DB_NAME} OWNER ${DB_USER}'
|
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_NAME}')\\gexec
|
|
ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER};
|
|
SQL
|
|
local hba_file
|
|
hba_file="$(runuser -u postgres -- psql -Atc 'SHOW hba_file;')"
|
|
if [[ -n "$hba_file" ]] && ! grep -q "CMPP platform local access" "$hba_file"; then
|
|
log "Configuring PostgreSQL local password authentication"
|
|
cp "$hba_file" "${hba_file}.bak.$(date +%Y%m%d%H%M%S)"
|
|
{
|
|
echo "# CMPP platform local access"
|
|
echo "host ${DB_NAME} ${DB_USER} 127.0.0.1/32 scram-sha-256"
|
|
echo "host ${DB_NAME} ${DB_USER} ::1/128 scram-sha-256"
|
|
cat "$hba_file"
|
|
} >"${hba_file}.tmp"
|
|
mv "${hba_file}.tmp" "$hba_file"
|
|
chown postgres:postgres "$hba_file"
|
|
chmod 600 "$hba_file"
|
|
fi
|
|
systemctl reload postgresql || systemctl reload postgresql.service
|
|
}
|
|
|
|
write_env() {
|
|
log "Writing production environment"
|
|
mkdir -p /etc/cmpp-platform "$APP_DIR" "$APP_DIR/logs/api" "$APP_DIR/logs/gateway" "$APP_DIR/backups" "$OBJECT_STORAGE_LOCAL_ROOT"
|
|
cat >/etc/cmpp-platform/cmpp-platform.env <<EOF
|
|
NODE_ENV=production
|
|
API_PORT=${API_PORT}
|
|
API_ENABLE_SEND_WORKER=${API_ENABLE_SEND_WORKER}
|
|
API_SEND_WORKER_CONCURRENCY=${API_SEND_WORKER_CONCURRENCY}
|
|
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@127.0.0.1:5432/${DB_NAME}?schema=public
|
|
REDIS_HOST=127.0.0.1
|
|
REDIS_PORT=6379
|
|
OPERATION_LOG_ARCHIVE_ENABLED=${OPERATION_LOG_ARCHIVE_ENABLED}
|
|
OPERATION_LOG_RETENTION_DAYS=${OPERATION_LOG_RETENTION_DAYS}
|
|
OPERATION_LOG_ARCHIVE_BATCH_SIZE=${OPERATION_LOG_ARCHIVE_BATCH_SIZE}
|
|
OPERATION_LOG_ARCHIVE_MAX_BATCHES=${OPERATION_LOG_ARCHIVE_MAX_BATCHES}
|
|
OPERATION_LOG_ARCHIVE_INTERVAL_MS=${OPERATION_LOG_ARCHIVE_INTERVAL_MS}
|
|
SMS_RECEIPT_TIMEOUT_SCAN_ENABLED=${SMS_RECEIPT_TIMEOUT_SCAN_ENABLED}
|
|
SMS_RECEIPT_TIMEOUT_HOURS=${SMS_RECEIPT_TIMEOUT_HOURS}
|
|
SMS_RECEIPT_TIMEOUT_SCAN_INTERVAL_MS=${SMS_RECEIPT_TIMEOUT_SCAN_INTERVAL_MS}
|
|
MINIO_ENDPOINT=127.0.0.1:9000
|
|
MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
|
|
MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
|
|
MINIO_BUCKET=${MINIO_BUCKET}
|
|
OBJECT_STORAGE_DRIVER=${OBJECT_STORAGE_DRIVER}
|
|
OBJECT_STORAGE_LOCAL_ROOT=${OBJECT_STORAGE_LOCAL_ROOT}
|
|
GATEWAY_CONTROL_URL=http://127.0.0.1:8090
|
|
GATEWAY_HEALTH_ADDR=${GATEWAY_CONTROL_ADDR}
|
|
GATEWAY_CMPP_ADDR=${GATEWAY_CMPP_ADDR}
|
|
CMPP_PUBLIC_HOST=${CMPP_PUBLIC_HOST}
|
|
CMPP_PUBLIC_PORT=${CMPP_PUBLIC_PORT}
|
|
API_BASE_URL=http://127.0.0.1:${API_PORT}/api
|
|
EOF
|
|
chmod 600 /etc/cmpp-platform/cmpp-platform.env
|
|
cat >/etc/cmpp-platform/minio.env <<EOF
|
|
MINIO_ROOT_USER=${MINIO_ROOT_USER}
|
|
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
|
EOF
|
|
chmod 600 /etc/cmpp-platform/minio.env
|
|
}
|
|
|
|
write_services() {
|
|
log "Writing systemd and nginx configuration"
|
|
local node_bin
|
|
node_bin="$(command -v node)"
|
|
cat >/etc/systemd/system/cmpp-minio.service <<'EOF'
|
|
[Unit]
|
|
Description=CMPP MinIO object storage
|
|
After=network.target
|
|
|
|
[Service]
|
|
User=minio
|
|
Group=minio
|
|
EnvironmentFile=/etc/cmpp-platform/minio.env
|
|
ExecStart=/usr/local/bin/minio server /var/lib/minio --address 127.0.0.1:9000 --console-address 127.0.0.1:9001
|
|
Restart=always
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
cat >/etc/systemd/system/cmpp-api.service <<EOF
|
|
[Unit]
|
|
Description=CMPP Platform API
|
|
After=network.target postgresql.service redis.service cmpp-minio.service
|
|
|
|
[Service]
|
|
WorkingDirectory=${APP_DIR}/api
|
|
EnvironmentFile=/etc/cmpp-platform/cmpp-platform.env
|
|
ExecStart=${node_bin} dist/main.js
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=append:${APP_DIR}/logs/api/stdout.log
|
|
StandardError=append:${APP_DIR}/logs/api/stderr.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
cat >/etc/systemd/system/cmpp-gateway.service <<EOF
|
|
[Unit]
|
|
Description=CMPP Gateway control service
|
|
After=network.target cmpp-api.service
|
|
|
|
[Service]
|
|
WorkingDirectory=${APP_DIR}
|
|
EnvironmentFile=/etc/cmpp-platform/cmpp-platform.env
|
|
ExecStart=${APP_DIR}/dist/cmpp-gateway
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=append:${APP_DIR}/logs/gateway/stdout.log
|
|
StandardError=append:${APP_DIR}/logs/gateway/stderr.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
cat >/etc/nginx/conf.d/cmpp-platform.conf <<EOF
|
|
server {
|
|
listen ${PUBLIC_HTTP_PORT};
|
|
server_name _;
|
|
root ${APP_DIR}/dist;
|
|
index index.html;
|
|
client_max_body_size 50m;
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 1024;
|
|
gzip_comp_level 5;
|
|
gzip_types application/json application/javascript text/javascript text/css text/plain text/csv image/svg+xml;
|
|
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:${API_PORT}/api/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
}
|
|
|
|
location / {
|
|
try_files \$uri \$uri/ /index.html;
|
|
}
|
|
}
|
|
EOF
|
|
nginx -t
|
|
systemctl enable nginx
|
|
}
|
|
|
|
checkout_code() {
|
|
log "Checking out application code"
|
|
if [[ -d "$APP_DIR/.git" ]]; then
|
|
git -C "$APP_DIR" fetch origin "$BRANCH"
|
|
git -C "$APP_DIR" checkout "$BRANCH"
|
|
git -C "$APP_DIR" reset --hard "origin/$BRANCH"
|
|
else
|
|
rm -rf "$APP_DIR"
|
|
git clone --branch "$BRANCH" "$REPO_URL" "$APP_DIR"
|
|
fi
|
|
}
|
|
|
|
run_deploy() {
|
|
log "Building and deploying application"
|
|
PROD_ADMIN_EMAIL="$PROD_ADMIN_EMAIL" \
|
|
PROD_ADMIN_USERNAME="$PROD_ADMIN_USERNAME" \
|
|
PROD_ADMIN_PASSWORD="$PROD_ADMIN_PASSWORD" \
|
|
bash "$APP_DIR/tools/deploy/production-deploy.sh"
|
|
}
|
|
|
|
install_packages
|
|
install_node
|
|
install_go
|
|
install_minio
|
|
start_infra
|
|
write_env
|
|
write_services
|
|
checkout_code
|
|
run_deploy
|
|
|
|
cat >/root/cmpp-platform-credentials.txt <<EOF
|
|
CMPP production credentials
|
|
admin_url=http://$(hostname -I | awk '{print $1}'):${PUBLIC_HTTP_PORT}/admin/login
|
|
admin_username=${PROD_ADMIN_USERNAME}
|
|
admin_email=${PROD_ADMIN_EMAIL}
|
|
admin_password=${PROD_ADMIN_PASSWORD}
|
|
database=postgresql://${DB_USER}:***@127.0.0.1:5432/${DB_NAME}
|
|
minio_user=${MINIO_ROOT_USER}
|
|
minio_password=${MINIO_ROOT_PASSWORD}
|
|
object_storage_driver=${OBJECT_STORAGE_DRIVER}
|
|
EOF
|
|
chmod 600 /root/cmpp-platform-credentials.txt
|
|
|
|
log "Production bootstrap finished"
|
|
echo "Credentials saved to /root/cmpp-platform-credentials.txt"
|
|
echo "Frontend: http://<server-ip>:${PUBLIC_HTTP_PORT}"
|
|
echo "Gateway control health: http://127.0.0.1:8090/health"
|
|
echo "CMPP inbound port variable reserved: ${GATEWAY_CMPP_ADDR}"
|