586 lines
19 KiB
Bash
Executable File
586 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# One-shot OpenSIPS + OpenSIPS-CP + RTPengine installer for Ubuntu 24.04.
|
|
# Tested against OpenSIPS 3.6.x on Ubuntu Noble.
|
|
#
|
|
# Usage:
|
|
# sudo bash install-opensips-stack.sh
|
|
#
|
|
# Optional environment variables:
|
|
# SIP_IP=100.93.185.30 DB_PASS=change-me CP_VERSION=9.3.6 sudo -E bash install-opensips-stack.sh
|
|
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
export NEEDRESTART_MODE=a
|
|
|
|
OPEN_SIPS_SERIES="${OPEN_SIPS_SERIES:-3.6}"
|
|
CP_VERSION="${CP_VERSION:-9.3.6}"
|
|
SIP_IP="${SIP_IP:-$(hostname -I | awk '{print $1}')}"
|
|
SIP_DOMAIN="${SIP_DOMAIN:-$SIP_IP}"
|
|
DB_NAME="${DB_NAME:-opensips}"
|
|
DB_USER="${DB_USER:-opensips}"
|
|
DB_PASS="${DB_PASS:-opensipsrw}"
|
|
DB_RO_USER="${DB_RO_USER:-opensipsro}"
|
|
DB_RO_PASS="${DB_RO_PASS:-opensipsro}"
|
|
CP_PATH="${CP_PATH:-/var/www/html/opensips-cp}"
|
|
MI_HTTP_IP="${MI_HTTP_IP:-127.0.0.1}"
|
|
MI_HTTP_PORT="${MI_HTTP_PORT:-8888}"
|
|
RTPENGINE_NG="${RTPENGINE_NG:-127.0.0.1:2223}"
|
|
RTPENGINE_PORT_MIN="${RTPENGINE_PORT_MIN:-30000}"
|
|
RTPENGINE_PORT_MAX="${RTPENGINE_PORT_MAX:-40000}"
|
|
MONIT_IP="${MONIT_IP:-127.0.0.1}"
|
|
MONIT_PORT="${MONIT_PORT:-2812}"
|
|
MONIT_USER="${MONIT_USER:-admin}"
|
|
MONIT_PASS="${MONIT_PASS:-monit}"
|
|
|
|
log() {
|
|
printf '\n### %s\n' "$*"
|
|
}
|
|
|
|
require_root() {
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Run as root, for example: sudo -E bash $0" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
backup_once() {
|
|
local file="$1"
|
|
[ -f "$file" ] || return 0
|
|
cp -an "$file" "$file.bak.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
|
|
}
|
|
|
|
apt_install() {
|
|
apt-get install -y "$@"
|
|
}
|
|
|
|
setup_apt_repo() {
|
|
log "Configure OpenSIPS APT repository"
|
|
apt-get update
|
|
apt_install ca-certificates curl gnupg lsb-release git unzip sed gawk
|
|
curl -fsSL https://apt.opensips.org/opensips-org.gpg -o /usr/share/keyrings/opensips-org.gpg
|
|
cat > /etc/apt/sources.list.d/opensips.list <<EOF
|
|
deb [signed-by=/usr/share/keyrings/opensips-org.gpg] https://apt.opensips.org noble ${OPEN_SIPS_SERIES}-releases
|
|
EOF
|
|
cat > /etc/apt/sources.list.d/opensips-cli.list <<EOF
|
|
deb [signed-by=/usr/share/keyrings/opensips-org.gpg] https://apt.opensips.org noble cli-nightly
|
|
EOF
|
|
apt-get update
|
|
}
|
|
|
|
install_packages() {
|
|
log "Install OpenSIPS, OpenSIPS-CP dependencies and RTPengine"
|
|
apt_install \
|
|
opensips opensips-cli \
|
|
opensips-mysql-module opensips-mysql-dbschema \
|
|
opensips-auth-modules opensips-tls-module \
|
|
opensips-http-modules opensips-json-module \
|
|
rtpengine-daemon \
|
|
monit \
|
|
mariadb-server mariadb-client \
|
|
apache2 libapache2-mod-php \
|
|
php php-cli php-mysql php-gd php-pear php-apcu php-curl php-xml php-mbstring \
|
|
git unzip curl
|
|
}
|
|
|
|
setup_database() {
|
|
log "Create OpenSIPS database and users"
|
|
systemctl enable --now mariadb
|
|
mysql --protocol=socket -uroot <<SQL
|
|
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
|
|
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'localhost';
|
|
CREATE USER IF NOT EXISTS '${DB_RO_USER}'@'localhost' IDENTIFIED BY '${DB_RO_PASS}';
|
|
GRANT SELECT ON \`${DB_NAME}\`.* TO '${DB_RO_USER}'@'localhost';
|
|
FLUSH PRIVILEGES;
|
|
SQL
|
|
}
|
|
|
|
import_schema_file_if_needed() {
|
|
local sql_file="$1"
|
|
local first_table
|
|
first_table="$(awk '/CREATE TABLE/{gsub(/`/, "", $3); print $3; exit}' "$sql_file")"
|
|
if [ -z "$first_table" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if mysql --protocol=socket -uroot -D "$DB_NAME" -NBe "SHOW TABLES LIKE '$first_table'" | grep -qx "$first_table"; then
|
|
echo "schema already present: $(basename "$sql_file")"
|
|
else
|
|
echo "importing schema: $(basename "$sql_file")"
|
|
mysql --protocol=socket -uroot "$DB_NAME" < "$sql_file"
|
|
fi
|
|
}
|
|
|
|
setup_opensips_schema() {
|
|
log "Import OpenSIPS MySQL schemas"
|
|
if ! mysql --protocol=socket -uroot -D "$DB_NAME" -NBe "SHOW TABLES LIKE 'version'" | grep -qx version; then
|
|
mysql --protocol=socket -uroot "$DB_NAME" < /usr/share/opensips/mysql/standard-create.sql
|
|
else
|
|
echo "standard schema already present"
|
|
fi
|
|
|
|
local schema
|
|
for schema in \
|
|
acc alias_db auth_db permissions domain dialog dispatcher drouting dialplan \
|
|
load_balancer rtpengine rtpproxy tracer usrloc tls_mgm group speeddial
|
|
do
|
|
import_schema_file_if_needed "/usr/share/opensips/mysql/${schema}-create.sql"
|
|
done
|
|
}
|
|
|
|
configure_opensipsctlrc() {
|
|
log "Configure opensipsctlrc"
|
|
backup_once /etc/opensips/opensipsctlrc
|
|
cat > /etc/opensips/opensipsctlrc <<EOF
|
|
SIP_DOMAIN=${SIP_DOMAIN}
|
|
DBENGINE=MYSQL
|
|
DBHOST=localhost
|
|
DBNAME=${DB_NAME}
|
|
DBRWUSER=${DB_USER}
|
|
DBRWPW="${DB_PASS}"
|
|
DBROUSER=${DB_RO_USER}
|
|
DBROPW=${DB_RO_PASS}
|
|
DBROOTUSER=root
|
|
EOF
|
|
}
|
|
|
|
patch_opensips_cfg() {
|
|
log "Configure OpenSIPS listener, MI HTTP and permissions"
|
|
local cfg=/etc/opensips/opensips.cfg
|
|
backup_once "$cfg"
|
|
|
|
python3 - "$cfg" <<PY
|
|
from pathlib import Path
|
|
cfg = Path("$cfg")
|
|
s = cfg.read_text()
|
|
|
|
lines = []
|
|
socket_done = False
|
|
for line in s.splitlines():
|
|
if line.strip().startswith("socket=udp:"):
|
|
if not socket_done:
|
|
lines.append("socket=udp:${SIP_IP}:5060 /* installed by install-opensips-stack.sh */")
|
|
socket_done = True
|
|
continue
|
|
lines.append(line)
|
|
s = "\\n".join(lines) + "\\n"
|
|
if not socket_done:
|
|
marker = "####### Modules Section ########"
|
|
s = s.replace(marker, f"socket=udp:${SIP_IP}:5060\\n\\n{marker}", 1)
|
|
|
|
managed = """\
|
|
#### Managed OpenSIPS-CP runtime support
|
|
loadmodule "db_mysql.so"
|
|
|
|
loadmodule "auth.so"
|
|
loadmodule "auth_db.so"
|
|
modparam("auth_db", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("auth_db", "calculate_ha1", 1)
|
|
|
|
loadmodule "domain.so"
|
|
modparam("domain", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("domain", "db_mode", 1)
|
|
|
|
loadmodule "httpd.so"
|
|
modparam("httpd", "ip", "${MI_HTTP_IP}")
|
|
modparam("httpd", "port", ${MI_HTTP_PORT})
|
|
|
|
loadmodule "mi_http.so"
|
|
modparam("mi_http", "root", "mi")
|
|
|
|
loadmodule "permissions.so"
|
|
modparam("permissions", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("permissions", "address_table", "address")
|
|
|
|
loadmodule "dialog.so"
|
|
modparam("dialog", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("dialog", "profiles_no_value", "inbound;outbound")
|
|
modparam("dialog", "profiles_with_value", "caller;callee")
|
|
|
|
loadmodule "dispatcher.so"
|
|
modparam("dispatcher", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("dispatcher", "persistent_state", 1)
|
|
|
|
loadmodule "drouting.so"
|
|
modparam("drouting", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("drouting", "drd_table", "dr_gateways")
|
|
modparam("drouting", "drr_table", "dr_rules")
|
|
modparam("drouting", "drg_table", "dr_groups")
|
|
modparam("drouting", "drc_table", "dr_carriers")
|
|
|
|
loadmodule "rtpengine.so"
|
|
modparam("rtpengine", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("rtpengine", "db_table", "rtpengine")
|
|
|
|
loadmodule "rtpproxy.so"
|
|
modparam("rtpproxy", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")
|
|
modparam("rtpproxy", "db_table", "rtpproxy_sockets")
|
|
|
|
loadmodule "tracer.so"
|
|
modparam("tracer", "trace_on", 1)
|
|
modparam("tracer", "trace_id", "[tid]uri=mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME};table=sip_trace;")
|
|
#### End managed OpenSIPS-CP runtime support
|
|
"""
|
|
|
|
start = "#### Managed OpenSIPS-CP runtime support"
|
|
end = "#### End managed OpenSIPS-CP runtime support"
|
|
if start in s and end in s:
|
|
before, rest = s.split(start, 1)
|
|
_, after = rest.split(end, 1)
|
|
s = before + managed + after
|
|
else:
|
|
marker = '#### SIGNALING module\\n'
|
|
if marker in s:
|
|
s = s.replace(marker, managed + "\\n" + marker, 1)
|
|
else:
|
|
s = s.replace('#### Modules Section ########\\n', '#### Modules Section ########\\n' + managed + "\\n", 1)
|
|
|
|
lines = []
|
|
for line in s.splitlines():
|
|
stripped = line.strip()
|
|
if stripped.startswith('modparam("usrloc", "working_mode_preset"'):
|
|
lines.append('modparam("usrloc", "working_mode_preset", "single-instance-sql-write-through")')
|
|
continue
|
|
if stripped.startswith('modparam("usrloc", "db_url"'):
|
|
lines.append('modparam("usrloc", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")')
|
|
continue
|
|
lines.append(line)
|
|
if stripped == 'loadmodule "usrloc.so"' and 'modparam("usrloc", "db_url"' not in s:
|
|
lines.append('modparam("usrloc", "db_url", "mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}")')
|
|
s = "\n".join(lines) + "\n"
|
|
|
|
old_register = """\tif (is_method("REGISTER")) {
|
|
\t\t# store the registration and generate a SIP reply
|
|
\t\tif (!save("location"))
|
|
\t\t\txlog("failed to register AoR $tu\\n");
|
|
|
|
\t\texit;
|
|
\t}"""
|
|
new_register = """\tif (is_method("REGISTER")) {
|
|
\t\tif (!www_authorize("$fd", "subscriber")) {
|
|
\t\t\twww_challenge("$fd", "auth");
|
|
\t\t\texit;
|
|
\t\t}
|
|
|
|
\t\tif (!save("location"))
|
|
\t\t\txlog("failed to register AoR $tu\\n");
|
|
|
|
\t\texit;
|
|
\t}"""
|
|
if old_register in s:
|
|
s = s.replace(old_register, new_register, 1)
|
|
|
|
if 'create_dialog();' not in s:
|
|
out = []
|
|
inserted = False
|
|
lines = s.splitlines()
|
|
for i, line in enumerate(lines):
|
|
out.append(line)
|
|
if line.strip() == 'if (is_method("INVITE")) {' and i > 0 and 'account only INVITEs' in lines[i - 1]:
|
|
out.append('\t\tcreate_dialog();')
|
|
out.append('\t\tset_dlg_profile("outbound");')
|
|
out.append('\t\tset_dlg_profile("caller", "$fU");')
|
|
out.append('\t\tset_dlg_profile("callee", "$rU");')
|
|
inserted = True
|
|
if inserted:
|
|
s = "\n".join(out) + "\n"
|
|
|
|
if 'trace("tid", "m", "sip", "$fU");' not in s:
|
|
s = s.replace('route{\n', 'route{\n\n\ttrace("tid", "m", "sip", "$fU");\n', 1)
|
|
|
|
cfg.write_text(s)
|
|
PY
|
|
|
|
opensips -C -f "$cfg"
|
|
}
|
|
|
|
deploy_opensips_cp() {
|
|
log "Deploy OpenSIPS Control Panel ${CP_VERSION}"
|
|
if [ ! -d "$CP_PATH/.git" ]; then
|
|
rm -rf "$CP_PATH"
|
|
git clone --depth 1 --branch "$CP_VERSION" https://github.com/OpenSIPS/opensips-cp.git "$CP_PATH"
|
|
else
|
|
git -C "$CP_PATH" fetch --tags --depth 1 origin "$CP_VERSION" || true
|
|
git -C "$CP_PATH" checkout "$CP_VERSION"
|
|
fi
|
|
|
|
if ! mysql --protocol=socket -uroot -D "$DB_NAME" -NBe "SHOW TABLES LIKE 'ocp_admin_privileges'" | grep -qx ocp_admin_privileges; then
|
|
mysql --protocol=socket -uroot "$DB_NAME" < "$CP_PATH/config/db_schema.mysql"
|
|
else
|
|
echo "OpenSIPS-CP schema already present"
|
|
fi
|
|
|
|
mysql --protocol=socket -uroot "$DB_NAME" <<SQL
|
|
UPDATE ocp_boxes_config
|
|
SET monit_conn='${MONIT_IP}:${MONIT_PORT}',
|
|
monit_user='${MONIT_USER}',
|
|
monit_pass='${MONIT_PASS}',
|
|
monit_ssl=0
|
|
WHERE id=1;
|
|
|
|
INSERT INTO rtpengine (socket, set_id)
|
|
SELECT 'udp:${RTPENGINE_NG}', 0
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM rtpengine WHERE socket='udp:${RTPENGINE_NG}' AND set_id=0
|
|
);
|
|
SQL
|
|
|
|
[ -f "$CP_PATH/config/db.inc.php.sample" ] && [ ! -f "$CP_PATH/config/db.inc.php" ] && cp "$CP_PATH/config/db.inc.php.sample" "$CP_PATH/config/db.inc.php"
|
|
[ -f "$CP_PATH/config/boxes.global.inc.php.sample" ] && [ ! -f "$CP_PATH/config/boxes.global.inc.php" ] && cp "$CP_PATH/config/boxes.global.inc.php.sample" "$CP_PATH/config/boxes.global.inc.php"
|
|
|
|
python3 - "$CP_PATH/config/db.inc.php" <<PY
|
|
from pathlib import Path
|
|
p = Path("$CP_PATH/config/db.inc.php")
|
|
s = p.read_text()
|
|
repls = {
|
|
"db_host": "localhost",
|
|
"db_user": "${DB_USER}",
|
|
"db_pass": "${DB_PASS}",
|
|
"db_name": "${DB_NAME}",
|
|
}
|
|
out = []
|
|
for line in s.splitlines():
|
|
stripped = line.strip()
|
|
replaced = False
|
|
for key, value in repls.items():
|
|
needle = f"\$config->{key}"
|
|
if stripped.startswith(needle) and "=" in line:
|
|
out.append(f" \$config->{key} = '{value}';")
|
|
replaced = True
|
|
break
|
|
if not replaced:
|
|
if "db_port" in line and "db_host" in line and "port=" in line:
|
|
out.append(' if (!empty(\$config->db_port) ) \$config->db_host = \$config->db_host . ";port=" . \$config->db_port;')
|
|
else:
|
|
out.append(line)
|
|
p.write_text("\\n".join(out) + "\\n")
|
|
PY
|
|
|
|
find "$CP_PATH/config" -type f \( -name '*.inc.php' -o -name '*.php' \) -print0 | xargs -0 sed -i \
|
|
-e "s/'db_host'[[:space:]]*=>[[:space:]]*'[^']*'/'db_host' => 'localhost'/g" \
|
|
-e "s/'db_name'[[:space:]]*=>[[:space:]]*'[^']*'/'db_name' => '${DB_NAME}'/g" \
|
|
-e "s/'db_user'[[:space:]]*=>[[:space:]]*'[^']*'/'db_user' => '${DB_USER}'/g" \
|
|
-e "s/'db_pass'[[:space:]]*=>[[:space:]]*'[^']*'/'db_pass' => '${DB_PASS}'/g" || true
|
|
|
|
# OpenSIPS-CP 9.3.6 passes validation regexes to browser-side JavaScript
|
|
# through preg_quote(), which turns regexes into literal strings for
|
|
# JavaScript's RegExp(). This breaks forms such as Domains, where both IPs
|
|
# and FQDNs are intended to be valid. Keep regex semantics and only escape
|
|
# quotes for the generated JS string.
|
|
local forms_file="$CP_PATH/web/common/forms.php"
|
|
backup_once "$forms_file"
|
|
python3 - "$forms_file" <<'PY'
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
p = Path(sys.argv[1])
|
|
s = p.read_text()
|
|
s = s.replace("preg_quote($re, '/')", "addslashes($re)")
|
|
s = s.replace("preg_quote($value['validation_regex'], '/')", "addslashes($value['validation_regex'])")
|
|
p.write_text(s)
|
|
PY
|
|
|
|
# Some OpenSIPS-CP 9.3.6 Domains installs still over-escape the SIP Domain
|
|
# field regex after the common generator patch. Keep the field required, but
|
|
# let the backend accept normal SIP domains such as an IP or an FQDN.
|
|
local domains_form="$CP_PATH/web/tools/system/domains/template/domains.form.php"
|
|
if [ -f "$domains_form" ]; then
|
|
backup_once "$domains_form"
|
|
python3 - "$domains_form" <<'PY'
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
p = Path(sys.argv[1])
|
|
s = p.read_text()
|
|
lines = []
|
|
changed = False
|
|
for line in s.splitlines():
|
|
if '"domain", "n", $domain_form' in line:
|
|
indent = line[:len(line) - len(line.lstrip())]
|
|
lines.append(indent + '"domain", "n", $domain_form[\'domain\'], 128, null);')
|
|
changed = True
|
|
else:
|
|
lines.append(line)
|
|
if changed:
|
|
p.write_text("\n".join(lines) + "\n")
|
|
PY
|
|
php -l "$domains_form"
|
|
fi
|
|
|
|
php -l "$CP_PATH/config/db.inc.php"
|
|
php -l "$forms_file"
|
|
}
|
|
|
|
configure_apache() {
|
|
log "Configure Apache /cp alias"
|
|
cat > /etc/apache2/conf-available/opensips-cp.conf <<EOF
|
|
Alias /cp ${CP_PATH}/web
|
|
|
|
<Directory ${CP_PATH}/web>
|
|
Options Indexes FollowSymLinks MultiViews
|
|
AllowOverride None
|
|
Require all granted
|
|
</Directory>
|
|
|
|
<Directory ${CP_PATH}>
|
|
Options Indexes FollowSymLinks MultiViews
|
|
AllowOverride None
|
|
Require all denied
|
|
</Directory>
|
|
|
|
<DirectoryMatch "${CP_PATH}/web/tools/.*/.*/(template|custom_actions|lib)/">
|
|
Require all denied
|
|
</DirectoryMatch>
|
|
EOF
|
|
a2enconf opensips-cp >/dev/null || true
|
|
chown -R www-data:www-data "$CP_PATH"
|
|
systemctl enable --now apache2
|
|
systemctl reload apache2
|
|
}
|
|
|
|
configure_rtpengine() {
|
|
log "Configure RTPengine"
|
|
local defaults=/etc/default/rtpengine-daemon
|
|
[ -f "$defaults" ] || defaults=/etc/default/rtpengine
|
|
backup_once "$defaults"
|
|
|
|
if [ -f "$defaults" ]; then
|
|
grep -q '^RUN_RTPENGINE=' "$defaults" && sed -i 's|^#*RUN_RTPENGINE=.*|RUN_RTPENGINE=yes|' "$defaults" || echo 'RUN_RTPENGINE=yes' >> "$defaults"
|
|
local opts="OPTIONS=\"--interface=${SIP_IP} --listen-ng=${RTPENGINE_NG} --port-min=${RTPENGINE_PORT_MIN} --port-max=${RTPENGINE_PORT_MAX} --log-level=6\""
|
|
grep -q '^OPTIONS=' "$defaults" && sed -i "s|^OPTIONS=.*|${opts}|" "$defaults" || echo "$opts" >> "$defaults"
|
|
fi
|
|
|
|
systemctl enable rtpengine-daemon
|
|
systemctl restart rtpengine-daemon || true
|
|
}
|
|
|
|
configure_monit() {
|
|
log "Configure Monit HTTP interface for OpenSIPS-CP"
|
|
local monitrc=/etc/monit/monitrc
|
|
backup_once "$monitrc"
|
|
|
|
python3 - "$monitrc" <<PY
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
p = Path(sys.argv[1])
|
|
s = p.read_text()
|
|
block = """set httpd port ${MONIT_PORT} and
|
|
use address ${MONIT_IP}
|
|
allow ${MONIT_IP}
|
|
allow ${MONIT_USER}:${MONIT_PASS}
|
|
"""
|
|
lines = s.splitlines()
|
|
out = []
|
|
i = 0
|
|
inserted = False
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
stripped = line.strip()
|
|
if stripped.startswith("set httpd port") or stripped.startswith("# set httpd port"):
|
|
if not inserted:
|
|
out.extend(block.rstrip("\\n").splitlines())
|
|
inserted = True
|
|
i += 1
|
|
while i < len(lines):
|
|
nxt = lines[i]
|
|
nstr = nxt.strip()
|
|
if not nstr:
|
|
out.append(nxt)
|
|
i += 1
|
|
break
|
|
if nstr.startswith("set ") and not nstr.startswith("set httpd port"):
|
|
break
|
|
if nxt.startswith(" ") or nxt.startswith("\\t") or nstr.startswith("#"):
|
|
i += 1
|
|
continue
|
|
break
|
|
continue
|
|
out.append(line)
|
|
i += 1
|
|
if not inserted:
|
|
out.append("")
|
|
out.extend(block.rstrip("\\n").splitlines())
|
|
p.write_text("\\n".join(out) + "\\n")
|
|
PY
|
|
|
|
monit -t
|
|
systemctl enable monit
|
|
systemctl restart monit
|
|
}
|
|
|
|
enable_runtime_services() {
|
|
log "Enable runtime services at boot"
|
|
|
|
# OpenSIPS-CP is served by Apache and uses MariaDB; it is not a separate
|
|
# daemon. Enabling these services makes http://SERVER_IP/cp/ available after
|
|
# reboot, while enabling opensips starts the SIP proxy itself.
|
|
systemctl enable --now mariadb
|
|
systemctl enable --now apache2
|
|
systemctl enable opensips
|
|
systemctl restart opensips
|
|
systemctl enable rtpengine-daemon
|
|
systemctl restart rtpengine-daemon || true
|
|
systemctl enable monit
|
|
systemctl restart monit
|
|
}
|
|
|
|
restart_and_verify() {
|
|
log "Restart services and verify"
|
|
enable_runtime_services
|
|
systemctl reload apache2
|
|
|
|
systemctl is-active mariadb apache2 opensips rtpengine-daemon monit
|
|
systemctl is-enabled mariadb apache2 opensips rtpengine-daemon monit
|
|
opensips -V | head -n 4
|
|
curl -fsS --max-time 10 "http://127.0.0.1/cp/" | grep -m1 "OpenSIPS Control Panel"
|
|
curl -fsS --max-time 10 -u "${MONIT_USER}:${MONIT_PASS}" "http://${MONIT_IP}:${MONIT_PORT}/" | grep -m1 "Monit"
|
|
curl -fsS --max-time 10 -X POST "http://${MI_HTTP_IP}:${MI_HTTP_PORT}/mi" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"rtpengine_show"}'
|
|
curl -fsS --max-time 10 -X POST "http://${MI_HTTP_IP}:${MI_HTTP_PORT}/mi" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":2,"method":"rtpproxy_show"}'
|
|
curl -fsS --max-time 10 -X POST "http://${MI_HTTP_IP}:${MI_HTTP_PORT}/mi" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":3,"method":"trace"}'
|
|
curl -fsS --max-time 10 -X POST "http://${MI_HTTP_IP}:${MI_HTTP_PORT}/mi" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":4,"method":"dr_gw_status"}'
|
|
curl -fsS --max-time 10 -X POST "http://${MI_HTTP_IP}:${MI_HTTP_PORT}/mi" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"address_reload"}'
|
|
ss -ltnup | grep -E "(:80|:5060|:${MI_HTTP_PORT}|:2223)" || true
|
|
}
|
|
|
|
main() {
|
|
require_root
|
|
setup_apt_repo
|
|
install_packages
|
|
setup_database
|
|
setup_opensips_schema
|
|
configure_opensipsctlrc
|
|
patch_opensips_cfg
|
|
deploy_opensips_cp
|
|
configure_apache
|
|
configure_rtpengine
|
|
configure_monit
|
|
restart_and_verify
|
|
|
|
cat <<EOF
|
|
|
|
Install complete.
|
|
|
|
OpenSIPS-CP URL: http://${SIP_IP}/cp/
|
|
Default OpenSIPS-CP login:
|
|
username: admin
|
|
password: opensips
|
|
|
|
MI HTTP endpoint is bound to ${MI_HTTP_IP}:${MI_HTTP_PORT}/mi for local CP usage.
|
|
Do not expose MI HTTP to the public Internet.
|
|
EOF
|
|
}
|
|
|
|
main "$@"
|