From bb81739f16513998725ba4c992251103a8153150 Mon Sep 17 00:00:00 2001 From: lfoxdev Date: Thu, 14 Nov 2024 22:03:56 +0400 Subject: [PATCH 1/6] Fix some bugs that occur when running in Docker 1. When running in Docker container with built-in Redis, LibreSBC starts Redis, but doesn't wait for it to be ready and tries to retrive Kamailio layers from database immediatly. If Redis is not started yet, basestartup() will simply exit with no Kamailio instances started. 2. By default, Redis saves database to disk after a very long time, so many changes in configuration are lost if server failed/rebooted soon after changes. # Unless specified otherwise, by default Redis will save the DB: # * After 3600 seconds (an hour) if at least 1 change was performed # * After 300 seconds (5 minutes) if at least 100 changes were performed # * After 60 seconds if at least 10000 changes were performed One of solutions is to use "--appendonly yes" which make Redis to immediatly write changes to AOF file (https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/). Note: AOF file will become bigger on every change, but is reduced when it reaches 64mb by default. This can be managed with '--auto-aof-rewrite-min-size 64mb'. 3. If server or container with running Kamailio is not shut down properly, the /run/kamailio/{layer}.pid file persists and blocks Kamailio from starting next time, so i suppose to delete it manually. --- liberator/basemgr.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/liberator/basemgr.py b/liberator/basemgr.py index dd26c7ed..ccf2bb5d 100644 --- a/liberator/basemgr.py +++ b/liberator/basemgr.py @@ -243,7 +243,7 @@ def fsinstance(data): fsrun = Popen(['/usr/local/bin/freeswitch', '-nc', '-reincarnate'], stdout=PIPE, stderr=PIPE) _, stderr = bdecode(fsrun.communicate()) - if stderr and not stderr.endswith('Backgrounding.'): + if stderr and not stderr.endswith('Backgrounding.\n'): result = False stderr = stderr.replace('\n', '') logger.error(f"module=liberator, space=basemgr, action=fsinstance.fsrun, error={stderr}") @@ -325,7 +325,8 @@ def kaminstance(data): cfgdel = osdelete(_cfgfile) luadel = osdelete(_luafile) - logger.info(f"module=liberator, space=basemgr, action=kaminstance.filedel, requestid={requestid}, cfgdel={cfgdel}, luadel={luadel}") + piddel = osdelete(_pidfile) + logger.info(f"module=liberator, space=basemgr, action=kaminstance.filedel, requestid={requestid}, cfgdel={cfgdel}, luadel={luadel}, piddel={piddel}") # ------------------------------------------------------------ # LAUNCH THE NEW INSTANCE # ------------------------------------------------------------ @@ -395,7 +396,7 @@ def rdbinstance(): try: logger.info(f"module=liberator, space=basemgr, node={NODEID}, action=rdbinstance, state=initiating") rdbrun = Popen(['/usr/bin/redis-server', '--bind', '127.0.0.1', '--port', '6379', '--pidfile', '/run/redis/redis.pid', '--unixsocket', - '/run/redis/redis.sock', '--unixsocketperm', '755', '--dbfilename', 'libresbc.rdb', '--dir', '/run/redis', '--loglevel', 'warning']) + '/run/redis/redis.sock', '--unixsocketperm', '755', '--appendfilename', 'libresbc.aof', '--dir', '/var/redis', '--appendonly', 'yes', '--loglevel', 'warning']) _, stderr = bdecode(rdbrun.communicate()) if stderr: logger.error(f"module=liberator, space=basemgr, action=rdbinstance.rdbrun, error={stderr}") @@ -409,6 +410,7 @@ def rdbinstance(): # BASE RESOURCE STARTUP #----------------------------------------------------------------------------------------------------------------------------------------------------------------------- _NGLUA = Environment(loader=FileSystemLoader('nglua')) +_REDIS_TIMEOUT = 50 #seconds @threaded def basestartup(): result = False @@ -416,6 +418,16 @@ def basestartup(): logger.info(f"module=liberator, space=basemgr, node={NODEID}, action=basestartup, state=initiating") data = {'portion': 'liberator:startup', 'requestid': '00000000-0000-0000-0000-000000000000'} rdbinstance() + for t in range(1, _REDIS_TIMEOUT//5): + try: + if rdbconn.ping(): + break + except redis.ConnectionError: + logger.info(f"module=liberator, space=basemgr, action=rdbinstance, result=Waiting for Redis (attempt {t})...") + time.sleep(5) + if not rdbconn.ping(): + logger.error(f'module=liberator, space=basemgr, action=exception, result="Redis has not started in {_REDIS_TIMEOUT} seconds. Other modules can not be loaded."') + return fsinstance(data) nftupdate(data) layers = rdbconn.smembers('nameset:access:service') @@ -423,8 +435,6 @@ def basestartup(): data.update({'layer': layer, '_layer': layer}) kaminstance(data) result = True - except redis.RedisError as e: - time.sleep(10) except Exception as e: logger.critical(f'module=liberator, space=basemgr, action=exception, exception={e}, tracings={traceback.format_exc()}') time.sleep(5) From 4797a2ad6b17e3f85a8229904343e862fe49572b Mon Sep 17 00:00:00 2001 From: lfoxdev Date: Sun, 17 Nov 2024 18:06:06 +0400 Subject: [PATCH 2/6] Fix a bug with Kamailio TLS configuration If TLS is included in transports of AccessService, Kamailio module tls.so is added to 'layer.cfg' with config file "{{layer}}.tls.cfg". Howewer, this TLS config file is not created anywhere, so Kamailio simply fails to start. This fix adds template for this file and makes it be created when user enables TLS transport. A new TLS class is also added to AccessService class to make Kamailio TLS configurable by user. The most common settings are included: - TLS version - path to certificate - path to private key - custom Server Name Indication string --- liberator/basemgr.py | 10 +++++++++- liberator/kamcfg/layer.j2.tls.cfg | 16 +++++++++++++++ liberator/libreapi.py | 33 ++++++++++++++++++++++++++++++- webui/assets/js/site.js | 8 +++++++- 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 liberator/kamcfg/layer.j2.tls.cfg diff --git a/liberator/basemgr.py b/liberator/basemgr.py index ccf2bb5d..a307e54b 100644 --- a/liberator/basemgr.py +++ b/liberator/basemgr.py @@ -315,6 +315,7 @@ def kaminstance(data): _pidfile = f'{PIDDIR}/{_layer}.pid' _cfgfile = f'{CFGDIR}/{_layer}.cfg' _luafile = f'{CFGDIR}/{_layer}.lua' + _tlsfile = f'{CFGDIR}/{layer}.tls.cfg' kamend = Popen([pidkill, '-F', _pidfile], stdout=PIPE, stderr=PIPE) _, stderr = bdecode(kamend.communicate()) @@ -325,8 +326,9 @@ def kaminstance(data): cfgdel = osdelete(_cfgfile) luadel = osdelete(_luafile) + tlsdel = osdelete(_tlsfile) piddel = osdelete(_pidfile) - logger.info(f"module=liberator, space=basemgr, action=kaminstance.filedel, requestid={requestid}, cfgdel={cfgdel}, luadel={luadel}, piddel={piddel}") + logger.info(f"module=liberator, space=basemgr, action=kaminstance.filedel, requestid={requestid}, cfgdel={cfgdel}, luadel={luadel}, tlsdel={tlsdel}, piddel={piddel}") # ------------------------------------------------------------ # LAUNCH THE NEW INSTANCE # ------------------------------------------------------------ @@ -336,6 +338,7 @@ def kaminstance(data): pidfile = f'{PIDDIR}/{layer}.pid' cfgfile = f'{CFGDIR}/{layer}.cfg' luafile = f'{CFGDIR}/{layer}.lua' + tlsfile = f'{CFGDIR}/{layer}.tls.cfg' kamcfgs = jsonhash(rdbconn.hgetall(f'access:service:{layer}')) netaliases = fieldjsonify(rdbconn.hget(f'base:netalias:{kamcfgs.get("sip_address")}', 'addresses')) @@ -369,6 +372,11 @@ def kaminstance(data): luatemplate = _KAM.get_template("layer.j2.lua") luastream = luatemplate.render(_KAMCONST=_KAMCONST, kamcfgs=kamcfgs, layer=layer, swipaddrs=swipaddrs, jsonpolicies=json.dumps(policies), dftdomain=dftdomain) with open(luafile, 'w') as lf: lf.write(luastream) + # TLS configuration + if 'tls' in kamcfgs.get('transports'): + tlstemplate = _KAM.get_template("layer.j2.tls.cfg") + tlsstream = tlstemplate.render(_KAMCONST=_KAMCONST, kamcfgs=kamcfgs, layer=layer) + with open(tlsfile, 'w') as tf: tf.write(tlsstream) kamrun = Popen([kambin, '-S', '-M', '16', '-P', pidfile, '-f', cfgfile], stdout=PIPE, stderr=PIPE) _, stderr = bdecode(kamrun.communicate()) diff --git a/liberator/kamcfg/layer.j2.tls.cfg b/liberator/kamcfg/layer.j2.tls.cfg new file mode 100644 index 00000000..e496b36e --- /dev/null +++ b/liberator/kamcfg/layer.j2.tls.cfg @@ -0,0 +1,16 @@ +[server:default] +method = {{kamcfgs.tls.method}} +verify_certificate = no +require_certificate = no +private_key = {{kamcfgs.tls.cert}} +certificate = {{kamcfgs.tls.key}} +{%- if kamcfgs.tlssni %} +server_name = {{kamcfgs.tls.sni}} +{%- endif %} +#ca_list = /var/tls/cacert.pem +#crl = /var/tls/crl.pem + +[client:default] +method = {{kamcfgs.tls.method}} +verify_certificate = no +require_certificate = no \ No newline at end of file diff --git a/liberator/libreapi.py b/liberator/libreapi.py index 4c86dd71..e36c6636 100644 --- a/liberator/libreapi.py +++ b/liberator/libreapi.py @@ -13,6 +13,7 @@ import hashlib import redis import validators +import os.path from pydantic import BaseModel, Field, validator, root_validator, schema, constr from pydantic.fields import ModelField from typing import Optional, List, Dict, Union, Any @@ -3086,7 +3087,20 @@ def list_access_domain_policy(response: Response): finally: return result -#----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +class TLSMethodsEnum(str, Enum): + TLS13plus = 'TLSv1.3+' + TLS13 = 'TLSv1.3' + TLS12plus = 'TLSv1.2+' + TLS12 = 'TLSv1.2' + TLS11plus = 'TLSv1.1+' + TLS11 = 'TLSv1.1' + TLS1plus = 'TLSv1+' + TLS1 = 'TLSv1' + SSL3 = 'SSLv3' + SSL2 = 'SSLv2' + SSL23 = 'SSLv23' class AntiFlooding(BaseModel): sampling: int = Field(default=2, ge=1, le=300, description='sampling time unit (in second)') @@ -3105,6 +3119,12 @@ class AttackAvoid(BaseModel): threshold: int = Field(default=5, ge=1, le=3600, description='number of request threshold that will be banned') bantime: int = Field(default=86400, ge=600, le=864000, description='firewall ban time in second') +class TLS(BaseModel): + method: TLSMethodsEnum = Field(default='TLSv1+', description='Allowed TLS version') + cert: str = Field(default='/var/tls/cert.pem', description='Path to TLS certificate in PEM format') + key: str = Field(default='/var/tls/cert.key', description='Path to TLS key in PEM format') + sni: Optional[str] = Field(description='Server Name Indication (SNI) for TLS connections') + class AccessService(BaseModel): name: str = Field(regex=_NAME_, max_length=32, description='name of access service') desc: Optional[str] = Field(default='access service', max_length=64, description='description') @@ -3124,6 +3144,7 @@ class AccessService(BaseModel): blackipv6s: List[IPv6Network] = Field(default=[], max_items=1024, description='denied ipv6 list') whiteipv6s: List[IPv4Network] = Field(default=[], max_items=1024 ,description='allowed ipv6 list') domains: List[str] = Field(min_items=1, max_items=8, description='list of policy domain') + tls: Optional[TLS] = Field(description='TLS settings') @root_validator def access_service_validation(cls, kvs): kvs = jsonable_encoder(kvs) @@ -3146,6 +3167,16 @@ def access_service_validation(cls, kvs): whiteipv6s = kvs.get('whiteipv6s') if blackipv6s and whiteipv6s: raise ValueError('only one of blackipv6s/whiteipv6s can be set') + if 'tls' in kvs.get('transports'): + if not kvs.get('tls'): + raise ValueError("TLS parameters must be specified when using TLS transport") + tls = jsonable_encoder(kvs.get('tls')) + cert = tls.get('cert') + if not os.path.exists(cert): + raise ValueError(f"TLS certificate file {cert} does not exist") + key = tls.get('key') + if not os.path.exists(key): + raise ValueError(f"TLS key file {key} does not exist") return kvs diff --git a/webui/assets/js/site.js b/webui/assets/js/site.js index acfadbb8..23bd9e59 100644 --- a/webui/assets/js/site.js +++ b/webui/assets/js/site.js @@ -250,7 +250,13 @@ const APIGuide = { "sip_address": "netalias_name", "domains": [ "libre.sbc" - ] + ], + "tls": { + "method": "TLSv1+", + "cert": "/var/tls/cert.pem", + "key": "/var/tls/key.pem", + "sni": "libre.sbc" + } } }, "AccessDomainPolicy": { From b55e66f8d589b8226ee65ec729d5c6fa1c3ecf16 Mon Sep 17 00:00:00 2001 From: lfoxdev Date: Sun, 17 Nov 2024 18:46:35 +0400 Subject: [PATCH 3/6] Allow creation of Access Domains with IP adrreses in addidition to RFC domain mames Many SIP clients have only one 'domain' field, which implies both the hostname of SIP registrar and the 'domain' field in AOR. A field to set SIP registrar address separately may be missing or hidden deep in the settings. On the other hand, server with LibreSBC may not have a real domain name. It seems to be good to allow administrator of such server create Access Domains with IP address instead of RFC domain name to simplify setup of various SIP clients. --- liberator/libreapi.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/liberator/libreapi.py b/liberator/libreapi.py index e36c6636..f5a84b57 100644 --- a/liberator/libreapi.py +++ b/liberator/libreapi.py @@ -53,7 +53,7 @@ def field_schema(field: ModelField, **kwargs: Any) -> Any: _NAME_ = r'^[a-zA-Z][a-zA-Z0-9_-]+$' _ID_ = r'^[a-zA-Z0-9][a-zA-Z0-9_]+$' _SRNAME_ = r'^_[a-zA-Z0-9_]+$' -_REALM_ = r'^[a-z][a-z0-9_\-\.]+$' +_REALM_ = r'^[a-z0-9_\-\.]+$' _DIAL_ = r'^[a-zA-Z0-9_\-+#*@\.]*$' # ROUTING _QUERY = 'query' @@ -2957,8 +2957,8 @@ class DomainPolicy(BaseModel): def policy(cls, kvs): kvs = jsonable_encoder(kvs) domain = kvs.get('domain') - if not validators.domain(domain): - raise ValueError('Invalid domain name, please refer rfc1035') + if not validators.domain(domain) and not validators.ipv4(domain): + raise ValueError('Domain must be rfc1035 domain name or an IP address') src_socket = kvs.get('srcsocket') srcsocket = f'{src_socket["transport"]}:{src_socket["ip"]}:{src_socket["port"]}' dst_socket = kvs.get('dstsocket') @@ -3353,8 +3353,8 @@ class UserDirectory(BaseModel): @root_validator def user_directory_validation(cls, kvs): domain = kvs.get('domain') - if not validators.domain(domain): - raise ValueError('Invalid domain name, please refer rfc1035') + if not validators.domain(domain) and not validators.ipv4(domain): + raise ValueError('Domain must be rfc1035 domain name or an IP address') if not rdbconn.exists(f'access:policy:{domain}'): raise ValueError('Undefined domain') return kvs @@ -3429,7 +3429,7 @@ def detail_access_directory_user(response: Response, domain: str=Path(..., regex return result @librerouter.get("/libreapi/access/directory/user/{domain}", status_code=200) -def list_access_directory_user(response: Response, domain: str=Path(..., regex=r'^[a-z][a-z0-9_\-\.]+$|^\*$')): +def list_access_directory_user(response: Response, domain: str=Path(..., regex=r'^[a-z0-9_\-\.]+$|^\*$')): result = None try: pipe = rdbconn.pipeline() From 267fd6b832f315bc0999e164d84d1cd9345967d6 Mon Sep 17 00:00:00 2001 From: lfoxdev Date: Sun, 17 Nov 2024 19:44:52 +0400 Subject: [PATCH 4/6] WebUI automatic start with LibreSBC First, the Go must be installed: curl -L https://go.dev/dl/go1.23.2.linux-amd64.tar.gz -o /usr/local/go1.23.2.linux-amd64.tar.gz && tar -xzf /usr/local/go1.23.2.linux-amd64.tar.gz -C /usr/local And then WebUI compiled: cd /opt/libresbc/webui && /usr/local/go/bin/go build -o /opt/libresbc/webui/webuisrv (These steps will be automated when installing in Docker. Dockerfile coming soon.) If LIBRE_WEBUI environmental variable is set to TRUE, 1 or YES, WebUI will be started with LibreSBC on port 8088. --- liberator/basemgr.py | 27 ++++++++++++++++++++++++--- liberator/configuration.py | 5 +++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/liberator/basemgr.py b/liberator/basemgr.py index a307e54b..415f5ab2 100644 --- a/liberator/basemgr.py +++ b/liberator/basemgr.py @@ -18,9 +18,9 @@ from jinja2 import Environment, FileSystemLoader from ipaddress import ip_address as IPvAddress, ip_network as IPvNetwork from configuration import (NODEID, CHANGE_CFG_CHANNEL, NODEID_CHANNEL, SECURITY_CHANNEL, ESL_HOST, ESL_PORT, - REDIS_HOST, REDIS_PORT, REDIS_DB, REDIS_PASSWORD, REDIS_TIMEOUT, LOGLEVEL, LOGSTACKS, - CONTAINERIZED, BUILTIN_FIREWALL, LIBRE_REDIS, -) + REDIS_HOST, REDIS_PORT, REDIS_DB, REDIS_PASSWORD, REDIS_TIMEOUT, LOGLEVEL, LOGSTACKS, + CONTAINERIZED, BUILTIN_FIREWALL, LIBRE_REDIS, LIBRE_WEBUI, + ) from utilities import logger, threaded, listify, fieldjsonify, stringify, bdecode, jsonhash, randomstr @@ -413,6 +413,26 @@ def rdbinstance(): except Exception as e: logger.critical(f'module=liberator, space=basemgr, action=exception, exception={e}, tracings={traceback.format_exc()}') +#----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# WEB USER INTERFACE +#----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +@threaded +def webui(): + if not LIBRE_WEBUI: + logger.info(f"module=liberator, space=basemgr, action=webui, message=[skip action since Web UI is disabled]") + return + + try: + logger.info(f"module=liberator, space=basemgr, node={NODEID}, action=webui, state=initiating") + webuirun = Popen(['/opt/libresbc/webui/webuisrv', '-libresbc', 'http://127.0.0.1:8080']) + _, stderr = bdecode(webuirun.communicate()) + if stderr: + logger.error(f"module=liberator, space=basemgr, action=webui, error={stderr}") + else: + logger.info(f"module=liberator, space=basemgr, action=webui, result=success") + except Exception as e: + logger.critical(f'module=liberator, space=basemgr, action=exception, exception={e}, tracings={traceback.format_exc()}') #----------------------------------------------------------------------------------------------------------------------------------------------------------------------- # BASE RESOURCE STARTUP @@ -436,6 +456,7 @@ def basestartup(): if not rdbconn.ping(): logger.error(f'module=liberator, space=basemgr, action=exception, result="Redis has not started in {_REDIS_TIMEOUT} seconds. Other modules can not be loaded."') return + webui() fsinstance(data) nftupdate(data) layers = rdbconn.smembers('nameset:access:service') diff --git a/liberator/configuration.py b/liberator/configuration.py index 4b9a96cb..c0b83d12 100644 --- a/liberator/configuration.py +++ b/liberator/configuration.py @@ -48,6 +48,11 @@ if _LIBRE_REDIS and _LIBRE_REDIS.upper() in ['TRUE', '1', 'YES']: LIBRE_REDIS = True +_LIBRE_WEBUI = os.getenv('LIBRE_WEBUI') +LIBRE_WEBUI = False +if _LIBRE_WEBUI and _LIBRE_WEBUI.upper() in ['TRUE', '1', 'YES']: + LIBRE_WEBUI = True + _BUILTIN_FIREWALL = os.getenv('LIBRE_BUILTIN_FIREWALL') BUILTIN_FIREWALL = True if _BUILTIN_FIREWALL and _BUILTIN_FIREWALL.upper() in ['FALSE', '0', 'NO']: From ea216b4a38196b1f7da6483a9fa123912a45f822 Mon Sep 17 00:00:00 2001 From: lfoxdev Date: Tue, 19 Nov 2024 21:45:44 +0400 Subject: [PATCH 5/6] Fix two bugs in previous commits --- liberator/kamcfg/layer.j2.tls.cfg | 6 +++--- liberator/libreapi.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/liberator/kamcfg/layer.j2.tls.cfg b/liberator/kamcfg/layer.j2.tls.cfg index e496b36e..ecf22cf9 100644 --- a/liberator/kamcfg/layer.j2.tls.cfg +++ b/liberator/kamcfg/layer.j2.tls.cfg @@ -2,8 +2,8 @@ method = {{kamcfgs.tls.method}} verify_certificate = no require_certificate = no -private_key = {{kamcfgs.tls.cert}} -certificate = {{kamcfgs.tls.key}} +private_key = {{kamcfgs.tls.key}} +certificate = {{kamcfgs.tls.cert}} {%- if kamcfgs.tlssni %} server_name = {{kamcfgs.tls.sni}} {%- endif %} @@ -13,4 +13,4 @@ server_name = {{kamcfgs.tls.sni}} [client:default] method = {{kamcfgs.tls.method}} verify_certificate = no -require_certificate = no \ No newline at end of file +require_certificate = no diff --git a/liberator/libreapi.py b/liberator/libreapi.py index f5a84b57..1eece412 100644 --- a/liberator/libreapi.py +++ b/liberator/libreapi.py @@ -3150,8 +3150,8 @@ def access_service_validation(cls, kvs): kvs = jsonable_encoder(kvs) domains = kvs.get('domains') for domain in domains: - if not validators.domain(domain): - raise ValueError('Invalid domain name, please refer rfc1035') + if not validators.domain(domain) and not validators.ipv4(domain): + raise ValueError('Domain must be rfc1035 domain name or an IP address') if not rdbconn.exists(f'access:policy:{domain}'): raise ValueError('Undefined domain') sip_address = kvs.get('sip_address') From 9e9f77aa10caa5a1d0c813d5edc3acaa7269baf4 Mon Sep 17 00:00:00 2001 From: lfoxdev Date: Wed, 20 Nov 2024 21:21:57 +0400 Subject: [PATCH 6/6] A large rewrite of Dockerfile 1. With Docker multi-stage builds, entire process is divided for two stages: build and runtime. In build stage all build tools and packages are installed and sources are downloaded and compiled. In runtime stage, a smaller debian image is used, only runtime necessary packages are installed, and only binaries are copied from build stage. This made it possible to reduce the size of container from 2.64 Gb to less than 1 Gb (!) 2. All heavy steps such as downloading tons of sources and building freeswitch and kamailio are moved to the beginning of the Dockerfile. Now, when the changes are made only to LibreSBC code, docker uses build cache for these heavy steps, so subsequent bulds are made MUCH faster. 3. A docker volume 'libresbc' for storing Redis database and other data created by user. Now the libresbc container can be safely deleted and replaced (e.g. for update), and all user settings are persisted in real host file system (currently /var/lib/docker/volumes/libresbc) 4. WebUI is included into image 5. Built-in Redis and WebUI are enabled by default for smoother user experience. Docker best practices recommend to build ready-to-use container with all components inside it. --- build/docker/Dockerfile | 92 ++++++++++++++++++++++++--------- build/docker/docker-compose.yml | 4 +- build/docker/libre.env | 5 +- 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile index 9217b4b6..bef749d3 100644 --- a/build/docker/Dockerfile +++ b/build/docker/Dockerfile @@ -1,14 +1,12 @@ -FROM debian:bullseye +FROM debian:bullseye AS build LABEL maintainer="Minh Minh " -ENV LIBRE_CONTAINERIZED 1 -ENV LIBRE_BUILTIN_FIREWALL 0 -ENV LIBRE_REDIS 1 + +# ==================== BUILD ==================== # BASE SOFTWARE RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -yq install \ # base - git curl wget lsof vim redis procps\ - sngrep tcpdump net-tools rsyslog logrotate rsync nftables chrony \ + git curl wget \ # build build-essential make cmake gnupg2 automake autoconf g++ gcc 'libtool-bin|libtool' pkg-config \ # general @@ -26,12 +24,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -yq install \ # kams flex gdb libxml2-dev libunistring-dev libhiredis-dev -RUN mkdir -p /run/redis /opt/libresbc /var/log/libresbc/cdr -COPY callng /opt/libresbc/callng -COPY liberator /opt/libresbc/liberator -COPY build/ansible/roles/platform/files/modules.conf /opt/libresbc/modules.conf - -# FREESWITCH +# Download FreeSWITCH & add modules RUN git clone https://github.com/signalwire/libks /usr/src/libs/libks && \ git clone --branch v1.13.17 https://github.com/freeswitch/sofia-sip.git /usr/src/libs/sofia-sip && \ git clone https://github.com/freeswitch/spandsp /usr/src/libs/spandsp && \ @@ -42,33 +35,82 @@ RUN git clone https://github.com/signalwire/libks /usr/src/libs/libks && \ cp /usr/include/opencore-amrnb/interf_enc.h /usr/src/freeswitch/src/mod/codecs/mod_amr/interf_enc.h && \ cp /usr/include/opencore-amrnb/interf_dec.h /usr/src/freeswitch/src/mod/codecs/mod_amr/interf_dec.h -RUN cd /usr/src/libs/libks && cmake . -DCMAKE_INSTALL_PREFIX=/usr -DWITH_LIBBACKTRACE=1 && make install && \ - cd /usr/src/libs/sofia-sip && ./bootstrap.sh && ./configure CFLAGS="-g -ggdb" --with-pic --with-glib=no --without-doxygen --disable-stun --prefix=/usr && make -j`nproc --all` && make install && \ - cd /usr/src/libs/spandsp && git checkout 0d2e6ac && ./bootstrap.sh && ./configure CFLAGS="-g -ggdb" --with-pic --prefix=/usr && make -j`nproc --all` && make install && \ - cd /usr/src/libs/signalwire-c && PKG_CONFIG_PATH=/usr/lib/pkgconfig cmake . -DCMAKE_INSTALL_PREFIX=/usr && make install && \ - cd /usr/src/freeswitch && cp /opt/libresbc/modules.conf /usr/src/freeswitch/modules.conf && \ - ./bootstrap.sh -j && ./configure -C --prefix=/usr/local --with-rundir=/run/freeswitch --with-logfiledir=/var/log/freeswitch/ --enable-64 --with-openssl && make -j`nproc` && make install +# Build FreeSWITCH +RUN cd /usr/src/libs/libks && cmake . -DCMAKE_INSTALL_PREFIX=/usr -DWITH_LIBBACKTRACE=1 && make install +RUN cd /usr/src/libs/sofia-sip && ./bootstrap.sh && ./configure CFLAGS="-g -ggdb" --with-pic --with-glib=no --without-doxygen --disable-stun --prefix=/usr && make -j`nproc --all` && make install +RUN cd /usr/src/libs/spandsp && git checkout 0d2e6ac && ./bootstrap.sh && ./configure CFLAGS="-g -ggdb" --with-pic --prefix=/usr && make -j`nproc --all` && make install +RUN cd /usr/src/libs/signalwire-c && PKG_CONFIG_PATH=/usr/lib/pkgconfig cmake . -DCMAKE_INSTALL_PREFIX=/usr && make install +RUN --mount=type=bind,source=build/ansible/roles/platform/files/modules.conf,target=/usr/src/freeswitch/modules.conf \ + cd /usr/src/freeswitch && ./bootstrap.sh -j && ./configure -C --prefix=/usr/local --with-rundir=/run/freeswitch --with-logfiledir=/var/log/freeswitch/ --enable-64 --with-openssl && make -j`nproc` && make install +# Download and build G729 codec module RUN git clone https://github.com/hnimminh/mod_bcg729.git /usr/local/src/mod_bcg729 && cd /usr/local/src/mod_bcg729 && make && make install -# KAMAILIO +# Download and build Kamailio RUN curl https://www.kamailio.org/pub/kamailio/5.7.1/src/kamailio-5.7.1_src.tar.gz -o /usr/local/src/kamailio-5.7.1_src.tar.gz && \ - tar -xzvf /usr/local/src/kamailio-5.7.1_src.tar.gz -C /usr/local/src && cd /usr/local/src/kamailio-5.7.1 && \ + tar -xzvf /usr/local/src/kamailio-5.7.1_src.tar.gz -C /usr/local/src +RUN cd /usr/local/src/kamailio-5.7.1 && \ make cfg && make include_modules="jsonrpcs ctl kex corex tm tmx outbound sl rr pv maxfwd topoh dialog usrloc registrar textops textopsx siputils sanity uac kemix auth nathelper tls debugger htable pike xlog app_lua regex utils" cfg && \ make all && make install -RUN chmod +x /opt/libresbc/callng/requirement.sh && /opt/libresbc/callng/requirement.sh &&\ +# Install LUA & Python requirements +RUN --mount=type=bind,rw,source=callng/requirement.sh,target=/opt/libresbc/callng/requirement.sh \ + chmod +x /opt/libresbc/callng/requirement.sh && /opt/libresbc/callng/requirement.sh +RUN --mount=type=bind,source=liberator/requirements.txt,target=/opt/libresbc/liberator/requirements.txt \ pip3 install -r /opt/libresbc/liberator/requirements.txt -# LAYOUT & CLEANUP -RUN rm -rf /usr/src/freeswitch-1.10.9.tar.gz /usr/local/freeswitch/conf/* /usr/local/src/kamailio* && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ +# Install Go +RUN curl -L https://go.dev/dl/go1.23.2.linux-amd64.tar.gz -o /usr/local/go1.23.2.linux-amd64.tar.gz && tar -xzf /usr/local/go1.23.2.linux-amd64.tar.gz -C /usr/local + +# Build LibreSBC WebUI +COPY ./webui /opt/libresbc/webui +RUN cd /opt/libresbc/webui && /usr/local/go/bin/go build -o /opt/libresbc/webui/webuisrv + + +# ==================== RUNTIME ==================== + +FROM debian:bullseye-slim +LABEL maintainer="Minh Minh " +ENV LIBRE_CONTAINERIZED 1 +ENV LIBRE_BUILTIN_FIREWALL 0 +ENV LIBRE_REDIS 1 + +# Install runtime packages +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -yq --no-install-recommends --no-install-suggests install \ + lsof vim redis procps sngrep tcpdump net-tools iproute2 curl \ + python3 lua5.2 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Copy files from build stage +COPY --from=build /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu +COPY --from=build /usr/lib/lib* /usr/lib/ +COPY --from=build /usr/local/bin /usr/local/bin +COPY --from=build /usr/local/lib/freeswitch /usr/local/lib/freeswitch +COPY --from=build /usr/local/var/lib/freeswitch /usr/local/var/lib/freeswitch +COPY --from=build /usr/local/share/freeswitch /usr/local/share/freeswitch +COPY --from=build /usr/local/etc/freeswitch /usr/local/etc/freeswitch +COPY --from=build /usr/local/lib/lib* /usr/local/lib/ +COPY --from=build /usr/local/sbin/kam* /usr/local/sbin/ +COPY --from=build /usr/local/share/kamailio /usr/local/share/kamailio +COPY --from=build /usr/local/etc/kamailio /usr/local/etc/kamailio +COPY --from=build /usr/local/lib64/kamailio /usr/local/lib64/kamailio +COPY --from=build /usr/local/lib/lua /usr/local/lib/lua +COPY --from=build /usr/local/share/lua /usr/local/share/lua +COPY --from=build /usr/local/lib/python3.9 /usr/local/lib/python3.9 +COPY --from=build /opt/libresbc/webui /opt/libresbc/webui + +COPY ./callng /opt/libresbc/callng +COPY ./liberator /opt/libresbc/liberator + +# LAYOUT +RUN mkdir -p /run/redis /var/log/libresbc/cdr && \ ln -nfs /opt/libresbc/callng /usr/local/share/lua/5.2/callng && \ ln -nfs /opt/libresbc/callng /usr/local/share/freeswitch/scripts/callng +VOLUME ["/var/redis", "/var/tls"] WORKDIR /opt/libresbc/liberator CMD ["/usr/bin/python3", "/opt/libresbc/liberator/main.py"] # docker build --platform linux/amd64 -t hnimminh/libresbc:latest -f build/docker/Dockerfile . # docker tag hnimminh/libresbc:latest hnimminh/libresbc:0.7.1.c -# docker run --env-file ../libre.env --cap-add NET_ADMIN --cap-add SYS_NICE --network host --name libresbc hnimminh/libresbc:latest +# docker run --env-file build/docker/libre.env --cap-add NET_ADMIN --cap-add SYS_NICE --network host --name libresbc -volume libresbc:/var/redis --volume libresbc:/var/tls hnimminh/libresbc:latest diff --git a/build/docker/docker-compose.yml b/build/docker/docker-compose.yml index 0c8f3e18..edd3bd45 100644 --- a/build/docker/docker-compose.yml +++ b/build/docker/docker-compose.yml @@ -14,8 +14,8 @@ services: restart: always image: hnimminh/libresbc:latest volumes: - - ./liberator:/opt/libresbc/liberator - - ./callng:/opt/libresbc/callng + - libresbc:/var/redis + - libresbc:/var/tls network_mode: host cap_add: - NET_ADMIN diff --git a/build/docker/libre.env b/build/docker/libre.env index 3d4fc052..e5c5de0f 100644 --- a/build/docker/libre.env +++ b/build/docker/libre.env @@ -1,8 +1,7 @@ REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_DB=0 -SCAN_COUNT=1000 -REDIS_TIMEOUT=5 NODEID=devsbc LOGSTACKS=CONSOLE -LIBRE_REDIS=NO +LIBRE_REDIS=YES +LIBRE_WEBUI=YES