diff --git a/.forgejo/workflows/build-freeswitch.yaml b/.forgejo/workflows/build-freeswitch.yaml new file mode 100644 index 0000000..711c705 --- /dev/null +++ b/.forgejo/workflows/build-freeswitch.yaml @@ -0,0 +1,22 @@ +on: + push: + paths: + - containers/freeswitch/** + - .forgejo/workflows/build-freeswitch.yaml +jobs: + build-freeswitch: + runs-on: docker + container: + image: library/docker:dind + steps: + - run: apk add --no-cache nodejs git + - name: login to container registry + run: echo "${{ secrets.DEPLOY_TOKEN }}" | docker login --username ${{ secrets.DEPLOY_USER }} --password-stdin git.janky.solutions + - name: build container image + uses: docker/build-push-action@v6 + with: + file: Containerfile + context: "{{defaultContext}}:containers/freeswitch" + tags: git.janky.solutions/jankysolutions/infra/freeswitch:latest + platforms: linux/amd64 + push: true diff --git a/containers/freeswitch/.gitignore b/containers/freeswitch/.gitignore new file mode 100644 index 0000000..3921cac --- /dev/null +++ b/containers/freeswitch/.gitignore @@ -0,0 +1 @@ +sources/ diff --git a/containers/freeswitch/Containerfile b/containers/freeswitch/Containerfile new file mode 100644 index 0000000..aa765de --- /dev/null +++ b/containers/freeswitch/Containerfile @@ -0,0 +1,84 @@ +FROM docker.io/library/debian:latest AS build +RUN apt-get update && apt-get install -y \ +# build + build-essential cmake automake autoconf 'libtool-bin|libtool' pkg-config \ +# general + libssl-dev zlib1g-dev libdb-dev unixodbc-dev libncurses5-dev libexpat1-dev libgdbm-dev bison erlang-dev libtpl-dev libtiff5-dev uuid-dev \ +# core + libpcre3-dev libedit-dev libsqlite3-dev libcurl4-openssl-dev nasm \ +# core codecs + libogg-dev libspeex-dev libspeexdsp-dev \ +# mod_enum + libldns-dev \ +# mod_python3 + python3-dev \ +# mod_av + libavformat-dev libswscale-dev \ +# mod_lua + liblua5.2-dev \ +# mod_opus + libopus-dev \ +# mod_pgsql + libpq-dev \ +# mod_sndfile + libsndfile1-dev libflac-dev libogg-dev libvorbis-dev \ +# mod_shout + libshout3-dev libmpg123-dev libmp3lame-dev \ +# mod_memcached + libmemcached-dev + +# without this git am will fail +RUN git config --global user.name nobody && git config --global user.email nobody@localhost + +ADD patches /patches + +# sofia-sip +RUN git clone https://github.com/freeswitch/sofia-sip.git /usr/src/sofia-sip && \ + cd /usr/src/sofia-sip && \ + git checkout v1.13.17 && \ + ./bootstrap.sh && \ + ./configure CFLAGS="-g -ggdb" --with-pic --with-glib=no --without-doxygen && \ + make && \ + make install + +# libks +RUN git clone https://github.com/signalwire/libks.git /usr/src/libks && \ + cd /usr/src/libks && \ + git checkout v2.0.3 && \ + cmake . -DWITH_LIBBACKTRACE=1 -DCMAKE_INSTALL_PREFIX:PATH=/usr/local && \ + make && \ + make install + +# spandsp +RUN git clone https://github.com/freeswitch/spandsp.git /usr/src/spandsp && \ + cd /usr/src/spandsp && \ + ./bootstrap.sh && \ + ./configure && \ + make && \ + make install + +# freeswitch +RUN git clone https://github.com/signalwire/freeswitch.git /usr/src/freeswitch && \ + cd /usr/src/freeswitch && \ + git checkout v1.10.11 && \ + git am /patches/freeswitch/* && \ + ./bootstrap.sh -j && \ + ./configure --enable-core-pgsql-support --disable-fhs && \ + make && \ + make install +RUN rm -rf /usr/local/freeswitch/conf + + +FROM library/golang:1.21 AS dynamic-xmlconfig +ADD dynamic-xmlconfig /go/dynamic-xmlconfig +WORKDIR /go/dynamic-xmlconfig +RUN go build -o /dynamic-xmlconfig . + +FROM debian:bookworm-slim +RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y libcurl4 libsqlite3-0 libpcre3 libspeexdsp1 libspeex1 libedit2 libtpl0 libodbc2 libtiff6 liblua5.2-0 libopus0 libavformat59 libopus0 libsndfile1 libswscale6 libldns3 libpq5 && apt-get clean && rm -rf /var/lib/apt/lists/* +COPY --from=build /usr/local/lib /usr/local/lib +COPY --from=build /usr/local/freeswitch /usr/local/freeswitch +COPY --from=dynamic-xmlconfig /dynamic-xmlconfig /usr/local/bin/dynamic-xmlconfig +ADD freeswitch.xml /usr/local/freeswitch/conf/freeswitch.xml +RUN ldconfig +CMD ["/usr/local/freeswitch/bin/freeswitch", "-nf"] diff --git a/containers/freeswitch/README.md b/containers/freeswitch/README.md new file mode 100644 index 0000000..3e45918 --- /dev/null +++ b/containers/freeswitch/README.md @@ -0,0 +1,7 @@ +# FreeSWITCH Container +This is an attempt to package a minimal FreeSWITCH container. + +* State is stored in an ODBC database. Must set environment variable `DSN` +* Configuration can be retreived from an HTTP server by setting environment variable `CONFIG_URL`. See [mod_curl_xml](https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Modules/mod_xml_curl_1049001/) for details about the HTTP response format. +* + diff --git a/containers/freeswitch/dynamic-xmlconfig/autoload_configs/modules.conf.xml b/containers/freeswitch/dynamic-xmlconfig/autoload_configs/modules.conf.xml new file mode 100644 index 0000000..7d577c4 --- /dev/null +++ b/containers/freeswitch/dynamic-xmlconfig/autoload_configs/modules.conf.xml @@ -0,0 +1,6 @@ + + + + {{ $_, $module := range .Modules }}{{ end }} + + diff --git a/containers/freeswitch/dynamic-xmlconfig/autoload_configs/xml_curl.conf.xml b/containers/freeswitch/dynamic-xmlconfig/autoload_configs/xml_curl.conf.xml new file mode 100644 index 0000000..b2fe3d6 --- /dev/null +++ b/containers/freeswitch/dynamic-xmlconfig/autoload_configs/xml_curl.conf.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/containers/freeswitch/dynamic-xmlconfig/go.mod b/containers/freeswitch/dynamic-xmlconfig/go.mod new file mode 100644 index 0000000..8cbe994 --- /dev/null +++ b/containers/freeswitch/dynamic-xmlconfig/go.mod @@ -0,0 +1,5 @@ +module codeberg.org/thefinn93/freeswitch-container + +go 1.21.5 + +require github.com/kelseyhightower/envconfig v1.4.0 diff --git a/containers/freeswitch/dynamic-xmlconfig/go.sum b/containers/freeswitch/dynamic-xmlconfig/go.sum new file mode 100644 index 0000000..8642a1a --- /dev/null +++ b/containers/freeswitch/dynamic-xmlconfig/go.sum @@ -0,0 +1,2 @@ +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= diff --git a/containers/freeswitch/dynamic-xmlconfig/main.go b/containers/freeswitch/dynamic-xmlconfig/main.go new file mode 100644 index 0000000..44efb8f --- /dev/null +++ b/containers/freeswitch/dynamic-xmlconfig/main.go @@ -0,0 +1,165 @@ +package main + +import ( + "embed" + "fmt" + "net/url" + "os" + "text/template" + + "github.com/kelseyhightower/envconfig" +) + +type Options struct { + DSN string `envconfig:"DSN"` + ConfigURL string `envconfig:"CONFIG_URL"` // URL called for configuration, see https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Modules/mod_xml_curl_1049001/ (dialplan|directory|phrases will be fetched from to this URL) + CaptureServer string `envconfig:"CAPTURE_SERVER"` // if set, this server gets a copy of all SIP messages. Example: udp:homer.domain.com:5060;hep=3;capture_id=100 + LogLevel string `envconfig:"LOG_LEVEL" default:"info"` + Gateway Gateway `envconfig:"GATEWAY"` + Modules []string `envconfig:"MODULES" default:"mod_event_socket,mod_sofia,mod_db,mod_dialplan_xml,mod_g723_1,mod_g729,mod_amr,mod_b64,mod_opus,mod_av,mod_sndfile,mod_native_file,mod_png,mod_local_stream,mod_tone_stream,mod_lua,mod_say_en"` +} + +type Gateway struct { + Name string + Username string + Password string + Extra map[string]string +} + +var ( + //go:embed autoload_configs + autoloadConfigsFS embed.FS + autoloadConfigsTemplates = template.Must(template.New("autoload_configs").ParseFS(autoloadConfigsFS, "autoload_configs/*")) + options Options + vars = map[string]string{ // this is used to fill in what is normally in vars.xml + "sound_prefix": "$${sounds_dir}/en/us/callie", + "domain": "$${local_ip_v4}", + "domain_name": "$${domain}", + "hold_music": "local_stream://moh", + "use_profile": "external", + "rtp_sdes_suites": "AEAD_AES_256_GCM_8|AEAD_AES_128_GCM_8|AES_CM_256_HMAC_SHA1_80|AES_CM_192_HMAC_SHA1_80|AES_CM_128_HMAC_SHA1_80|AES_CM_256_HMAC_SHA1_32|AES_CM_192_HMAC_SHA1_32|AES_CM_128_HMAC_SHA1_32|AES_CM_128_NULL_AUTH", + "global_codec_prefs": "OPUS,G722,PCMU,PCMA,H264,VP8", + "outbound_codec_prefs": "OPUS,G722,PCMU,PCMA,H264,VP8", + "xmpp_client_profile": "xmppc", + "xmpp_server_profile": "xmpps", + "bind_server_ip": "auto", + "unroll_loops": "true", + "outbound_caller_name": "FreeSWITCH", + "outbound_caller_id": "0000000000", + "call_debug": "false", + "console_loglevel": "info", + "default_areacode": "918", + "default_country": "US", + "presence_privacy": "false", + "au-ring": "%(400,200,383,417);%(400,2000,383,417)", + "be-ring": "%(1000,3000,425)", + "ca-ring": "%(2000,4000,440,480)", + "cn-ring": "%(1000,4000,450)", + "cy-ring": "%(1500,3000,425)", + "cz-ring": "%(1000,4000,425)", + "de-ring": "%(1000,4000,425)", + "dk-ring": "%(1000,4000,425)", + "dz-ring": "%(1500,3500,425)", + "eg-ring": "%(2000,1000,475,375)", + "es-ring": "%(1500,3000,425)", + "fi-ring": "%(1000,4000,425)", + "fr-ring": "%(1500,3500,440)", + "hk-ring": "%(400,200,440,480);%(400,3000,440,480)", + "hu-ring": "%(1250,3750,425)", + "il-ring": "%(1000,3000,400)", + "in-ring": "%(400,200,425,375);%(400,2000,425,375)", + "jp-ring": "%(1000,2000,420,380)", + "ko-ring": "%(1000,2000,440,480)", + "pk-ring": "%(1000,2000,400)", + "pl-ring": "%(1000,4000,425)", + "ro-ring": "%(1850,4150,475,425)", + "rs-ring": "%(1000,4000,425)", + "ru-ring": "%(800,3200,425)", + "sa-ring": "%(1200,4600,425)", + "tr-ring": "%(2000,4000,450)", + "uk-ring": "%(400,200,400,450);%(400,2000,400,450)", + "us-ring": "%(2000,4000,440,480)", + "bong-ring": "v", + "beep": "%(1000,0,640)", + "sit": "%(274,0,913.8);%(274,0,1370.6);%(380,0,1776.7)", + "df_us_ssn": "(?!219099999|078051120)(?!666|000|9d{2})d{3}(?!00)d{2}(?!0{4})d{4}", + "df_luhn": "?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35d{3})d{11}", + "default_provider": "example.com", + "default_provider_username": "joeuser", + "default_provider_password": "password", + "default_provider_from_domain": "example.com", + "default_provider_register": "false", + "default_provider_contact": "5000", + "sip_tls_version": "tlsv1,tlsv1.1,tlsv1.2", + "sip_tls_ciphers": "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", + "internal_auth_calls": "true", + "internal_sip_port": "5060", + "internal_tls_port": "5061", + "internal_ssl_enable": "false", + "external_auth_calls": "false", + "external_sip_port": "5080", + "external_tls_port": "5081", + "external_ssl_enable": "false", + "rtp_video_max_bandwidth_in": "3mb", + "rtp_video_max_bandwidth_out": "3mb", + "suppress_cng": "true", + "rtp_liberal_dtmf": "true", + "video_mute_png": "$${images_dir}/default-mute.png", + "video_no_avatar_png": "$${images_dir}/default-avatar.png", + } +) + +func main() { + if err := envconfig.Process("", &options); err != nil { + panic(err) + } + + if options.ConfigURL != "" { + options.Modules = append(options.Modules, "mod_xml_curl") + } + + // autoload_configs + for k, v := range vars { + fmt.Printf("\n", k, v) + } + + fmt.Println("
") + + configs, err := autoloadConfigsFS.ReadDir("autoload_configs") + if err != nil { + panic(err) + } + + for _, file := range configs { + if file.IsDir() { + fmt.Println("skipping dir: ", file.Name()) + continue + } + if err := autoloadConfigsTemplates.ExecuteTemplate(os.Stdout, file.Name(), options); err != nil { + panic(err) + } + } + + fmt.Println("
") +} + +func (g *Gateway) Decode(value string) error { + u, err := url.Parse(value) + if err != nil { + return err + } + + g.Name = u.Hostname() + g.Username = u.User.Username() + if password, ok := u.User.Password(); ok { + g.Password = password + } + + g.Extra = make(map[string]string) + for k, v := range u.Query() { + if len(v) > 0 { + g.Extra[k] = v[0] + } + } + return nil +} diff --git a/containers/freeswitch/freeswitch.xml b/containers/freeswitch/freeswitch.xml new file mode 100644 index 0000000..a743143 --- /dev/null +++ b/containers/freeswitch/freeswitch.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/containers/freeswitch/patches/freeswitch/0001-Fix-mod_spandsp-build.patch b/containers/freeswitch/patches/freeswitch/0001-Fix-mod_spandsp-build.patch new file mode 100644 index 0000000..3e8439c --- /dev/null +++ b/containers/freeswitch/patches/freeswitch/0001-Fix-mod_spandsp-build.patch @@ -0,0 +1,61 @@ +From 9d91452d5a0a4a44884ff74fa2e49c51aaaa32dd Mon Sep 17 00:00:00 2001 +From: nobody +Date: Wed, 24 Jan 2024 21:19:50 -0800 +Subject: [PATCH 1/2] Fix mod_spandsp build + +based on https://github.com/xrobau/freeswitch-ubuntubuilder/blob/master/patches/freeswitch/005-spandsp-updates.patch +--- + src/mod/applications/mod_spandsp/mod_spandsp_dsp.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c b/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c +index 836808a48d..9558f42169 100644 +--- a/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c ++++ b/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c +@@ -156,13 +156,13 @@ static int get_v18_mode(switch_core_session_t *session) + { + switch_channel_t *channel = switch_core_session_get_channel(session); + const char *var; +- int r = V18_MODE_5BIT_4545; ++ int r = V18_MODE_WEITBRECHT_5BIT_4545; + + if ((var = switch_channel_get_variable(channel, "v18_mode"))) { + if (!strcasecmp(var, "5BIT_45") || !strcasecmp(var, "baudot")) { +- r = V18_MODE_5BIT_4545; ++ r = V18_MODE_WEITBRECHT_5BIT_4545; + } else if (!strcasecmp(var, "5BIT_50")) { +- r = V18_MODE_5BIT_50; ++ r = V18_MODE_WEITBRECHT_5BIT_50; + } else if (!strcasecmp(var, "DTMF")) { + r = V18_MODE_DTMF; + } else if (!strcasecmp(var, "EDT")) { +@@ -213,7 +213,7 @@ switch_status_t spandsp_tdd_send_session(switch_core_session_t *session, const c + return SWITCH_STATUS_FALSE; + } + +- tdd_state = v18_init(NULL, TRUE, get_v18_mode(session), V18_AUTOMODING_GLOBAL, put_text_msg, NULL); ++ tdd_state = v18_init(NULL, TRUE, get_v18_mode(session), V18_AUTOMODING_GLOBAL, put_text_msg, NULL, NULL, NULL); + + + v18_put(tdd_state, text, -1); +@@ -260,7 +260,7 @@ switch_status_t spandsp_tdd_encode_session(switch_core_session_t *session, const + } + + pvt->session = session; +- pvt->tdd_state = v18_init(NULL, TRUE, get_v18_mode(session), V18_AUTOMODING_GLOBAL, put_text_msg, NULL); ++ pvt->tdd_state = v18_init(NULL, TRUE, get_v18_mode(session), V18_AUTOMODING_GLOBAL, put_text_msg, NULL, NULL, NULL); + pvt->head_lead = TDD_LEAD; + + v18_put(pvt->tdd_state, text, -1); +@@ -338,7 +338,7 @@ switch_status_t spandsp_tdd_decode_session(switch_core_session_t *session) + } + + pvt->session = session; +- pvt->tdd_state = v18_init(NULL, FALSE, get_v18_mode(session), V18_AUTOMODING_GLOBAL, put_text_msg, pvt); ++ pvt->tdd_state = v18_init(NULL, FALSE, get_v18_mode(session), V18_AUTOMODING_GLOBAL, put_text_msg, pvt, NULL, NULL); + + if ((status = switch_core_media_bug_add(session, "spandsp_tdd_decode", NULL, + tdd_decode_callback, pvt, 0, SMBF_READ_REPLACE | SMBF_NO_PAUSE, &bug)) != SWITCH_STATUS_SUCCESS) { +-- +2.39.2 + diff --git a/containers/freeswitch/patches/freeswitch/0002-disable-some-cloud-bullshit-enable-some-other-bullsh.patch b/containers/freeswitch/patches/freeswitch/0002-disable-some-cloud-bullshit-enable-some-other-bullsh.patch new file mode 100644 index 0000000..b977cfc --- /dev/null +++ b/containers/freeswitch/patches/freeswitch/0002-disable-some-cloud-bullshit-enable-some-other-bullsh.patch @@ -0,0 +1,87 @@ +From 28b7f07ae806a975074e8d0087263f6965f24ae3 Mon Sep 17 00:00:00 2001 +From: nobody +Date: Wed, 24 Jan 2024 21:20:07 -0800 +Subject: [PATCH] disable some cloud bullshit, enable some other bullshit + +--- + build/modules.conf.in | 19 +++++++++---------- + 1 file changed, 9 insertions(+), 10 deletions(-) + +diff --git a/build/modules.conf.in b/build/modules.conf.in +index 7bf59e2acc..22ddc03845 100644 +--- a/build/modules.conf.in ++++ b/build/modules.conf.in +@@ -3,12 +3,12 @@ applications/mod_av + #applications/mod_avmd + #applications/mod_bert + #applications/mod_blacklist +-#applications/mod_callcenter +-#applications/mod_cidlookup ++applications/mod_callcenter ++applications/mod_cidlookup + #applications/mod_cluechoo + applications/mod_commands + applications/mod_conference +-#applications/mod_curl ++applications/mod_curl + #applications/mod_cv + applications/mod_db + #applications/mod_directory +@@ -17,7 +17,7 @@ applications/mod_dptools + #applications/mod_easyroute + applications/mod_enum + applications/mod_esf +-#applications/mod_esl ++applications/mod_esl + applications/mod_expr + applications/mod_fifo + #applications/mod_fsk +@@ -28,7 +28,7 @@ applications/mod_httapi + #applications/mod_http_cache + #applications/mod_ladspa + #applications/mod_lcr +-#applications/mod_memcache ++applications/mod_memcache + #applications/mod_mongo + #applications/mod_mp4 + #applications/mod_mp4v2 +@@ -39,7 +39,6 @@ applications/mod_httapi + #applications/mod_rad_auth + #applications/mod_redis + #applications/mod_rss +-applications/mod_signalwire + applications/mod_sms + #applications/mod_sms_flowroute + #applications/mod_snapshot +@@ -121,7 +120,7 @@ formats/mod_native_file + formats/mod_png + #formats/mod_portaudio_stream + #formats/mod_shell_stream +-#formats/mod_shout ++formats/mod_shout + formats/mod_sndfile + #formats/mod_ssml + formats/mod_tone_stream +@@ -138,8 +137,8 @@ languages/mod_lua + #languages/mod_yaml + loggers/mod_console + #loggers/mod_graylog2 +-loggers/mod_logfile +-loggers/mod_syslog ++#loggers/mod_logfile ++#loggers/mod_syslog + #loggers/mod_raven + #say/mod_say_de + say/mod_say_en +@@ -162,7 +161,7 @@ say/mod_say_en + #timers/mod_posix_timer + #timers/mod_timerfd + xml_int/mod_xml_cdr +-#xml_int/mod_xml_curl ++xml_int/mod_xml_curl + #xml_int/mod_xml_ldap + #xml_int/mod_xml_radius + xml_int/mod_xml_rpc +-- +2.39.2 +