è un enorme contenitore che unisce CRM, gestione documentale, contabilità e molte altre funzionalità che piacciono molto ai manager ma non sempre al personale IT. Il portale è utilizzato da molte piccole e medie imprese, comprese piccole cliniche, produttori e persino centri estetici. La funzione principale che i "manager" adorano è l'integrazione della telefonia con il CRM, dove ogni chiamata viene registrata immediatamente nel CRM, vengono create schede cliente e appare l'informazione sul cliente in arrivo, così è subito chiaro chi sia, cosa gli si possa vendere e quanto debba. Tuttavia, la telefonia di Bitrix24 e la sua integrazione con il CRM richiedono un investimento, talvolta anche consistente. In questo articolo condividerò l'esperienza di integrazione con strumenti open source e una popolare centralina telefonica IP , e discuterò anche la logica di funzionamento delle varie parti
Lavoro in outsourcing per un'azienda che si occupa di vendita, configurazione e integrazione della telefonia IP. Quando mi è stato chiesto se potessimo proporre a quell'azienda e a quell'altra qualcosa per integrare Bitrix24 con le centraline telefoniche già utilizzate dai clienti e con le centraline virtuali su diversi VDS delle aziende, sono andato su Google. E ovviamente mi ha restituito il link a , dove ci sono sia descrizione sia il link a GitHub, e sembra che tutto funzioni. Ma quando ho provato a utilizzare questa soluzione ho scoperto che Bitrix24 non è più quello di una volta e c'è molto da rifare. Inoltre, FreePBX non è un semplice Asterisk, qui è necessario pensare a come conciliare la facilità d'uso con un dialplan hardcore nei file di configurazione.
Studiamo la logica di funzionamento
Innanzitutto, come dovrebbe funzionare tutto questo. Quando arriva una chiamata dall'esterno alla centralina (evento SIP INVITE dal fornitore), inizia l'elaborazione del dialplan (piano di composizione, dialplan) — le regole su cosa e in quale ordine fare con la chiamata. Dal primo pacchetto si possono ottenere molte informazioni che poi possono essere utilizzate nelle regole. Un ottimo strumento per studiare il funzionamento interno del SIP è l'analizzatore sngrep () che si installa facilmente nelle distribuzioni popolari tramite apt install/yum install e simili, ma si può anche compilare dai sorgenti. Vediamo il log della chiamata in sngrep

In forma semplificata, il dialplan si occupa solo del primo pacchetto, a volte durante la conversazione viene anche effettuata la traduzione delle chiamate, la pressione dei tasti (DTMF) e altre curiosità come FollowMe, RingGroup, IVR e altro ancora.
Cosa c'è dentro il pacchetto Invite

In sostanza, la maggior parte dei dialplan semplici funziona con i primi due campi e tutta la logica ruota attorno a DID e CallerID. DID è dove chiamiamo, CallerID è chi sta chiamando.
Ma noi abbiamo un'azienda e non un solo telefono, quindi è probabile che nel centralino ci siano gruppi di chiamata (chiamate simultanee/riservate di più dispositivi) su numeri urbani (Ring Group), IVR (Buongiorno, hai chiamato... Premi uno per…), segreterie telefoniche (Phrases), condizioni temporali (Time Conditions), inoltraggi a numeri diversi o a cellulari (FollowMe, Forward). Questo significa che è molto difficile determinare con certezza a chi arriverà realmente la chiamata e con chi ci sarà la conversazione quando arriva una chiamata. Ecco un esempio dell'inizio di una chiamata tipica nel centralino dei nostri clienti.

Dopo l'ingresso riuscito della chiamata nel centralino, essa intraprende un viaggio attraverso il dialplan in diversi "contesti". Un contesto, dal punto di vista di Asterisk, è un insieme numerato di comandi, ognuno dei quali contiene un filtro per il numero composto (si chiama exten, per una chiamata esterna all'inizio è exten=DID). I comandi nel dialplan possono essere di qualsiasi tipo: funzioni interne (ad esempio, chiamare un abbonato interno — Dial(), riattaccare — Hangup()), operatori condizionali (IF, ELSE, ExecIF e simili), passaggi a altre regole di questo contesto (Goto, GotoIF), passaggi ad altri contesti sotto forma di chiamata a funzioni (Gosub, Macro). A parte si trova la direttiva include nome_contesto, che aggiunge comandi di un altro contesto alla fine del contesto attuale. I comandi inclusi tramite include vengono sempre eseguiti la data rientri nel PITR. ai comandi del contesto attuale.
L'intera logica di funzionamento di FreePBX è costruita sull'inclusione reciproca di diversi contesti attraverso include e chiamate tramite Gosub, Macro e gestori Handler. Esaminiamo il contesto delle chiamate in arrivo di FreePBX.

La chiamata passa attraverso tutti i contesti dall'alto verso il basso a turno; all'interno di ogni contesto possono esserci chiamate ad altri contesti come macro (Macro), funzioni (Gosub) o semplicemente passaggi (Goto), quindi il reale albero di ciò che viene chiamato può essere tracciato solo nei log.
Lo schema tipico di configurazione di un tipico centralino telefonico è mostrato di seguito. Durante le chiamate in entrata, viene cercato il DID, che verifica le condizioni temporali; se tutto è a posto, viene avviato il menu vocale. Da questo si può uscire con il tasto 1 o attraverso un timeout per collegarsi al gruppo di operatori. Al termine della chiamata viene attivato il macro hangupcall, dopo il quale non sarà più possibile eseguire nulla nel piano di chiamata, a parte i gestori speciali (hangup handler).

Dove in questo algoritmo di chiamata dobbiamo fornire informazioni sull'inizio della chiamata al CRM, dove iniziare la registrazione, dove terminare la registrazione e inviarla insieme alle informazioni della chiamata al CRM?
Integrazione con sistemi esterni
Cos'è l'integrazione tra centralini telefonici e CRM? Sono configurazioni e programmi che convertono dati ed eventi tra queste due piattaforme e si scambiano informazioni. Il modo più comune di interazione tra sistemi indipendenti è l'API, mentre il metodo più popolare per accedere all'API è HTTP REST. Ma non per Asterisk.
All'interno di Asterisk ci sono:
AGI — chiamata sincrona di programmi/componenti esterni, utilizzata principalmente nel piano di chiamata, ci sono librerie come ,
AMI — socket TCP testuale, che funziona secondo il principio di abbonamento agli eventi e invio di comandi testuali, ricorda SMTP dall'interno, può monitorare eventi e gestire le chiamate, c'è la libreria — la più popolare per stabilire una connessione con Asterisk
Esempio di output AMI
Event: Newchannel
Privilege: call,all
Channel: PJSIP/VMS_pjsip-0000078b
ChannelState: 4
ChannelStateDesc: Ring
CallerIDNum: 111222
CallerIDName: 111222
ConnectedLineNum:
ConnectedLineName:
Language: en
AccountCode:
Context: from-pstn
Exten: s
Priority: 1
Uniqueid: 1599589046.5244
Linkedid: 1599589046.5244
ARI — una combinazione di entrambe, tutto attraverso REST, WebSocket, in formato JSON — ma le librerie e i wrapper recenti non sono molto sviluppati, a occhio si trovano (, ) che si sono fermate nel loro sviluppo circa 3 anni fa.
Esempio di output ARI durante l'inizio di una chiamata
{ «variable»:»CallMeCallerIDName», «value»:»111222″, «type»:»ChannelVarset», «timestamp»:»2020-09-09T09:38:36.269+0000″, «channel»:{ «id»:»1599644315.5334″, «name»:»PJSIP/VMSpjsip-000007b6″, «state»:»Ring», «caller»:{ «name»:»111222″, «number»:»111222″ }, «connected»:{ «name»:»», «number»:»» }, «accountcode»:»», «dialplan»:{ «context»:»from-pstn», «exten»:»s», «priority»:2, «appname»:»Stasis», «appdata»:»hello-world» }, «creationtime»:»2020-09-09T09:38:35.926+0000″, «language»:»ru» }, «asteriskid»:»48:5b:aa:aa:aa:aa», «application»:»hello-world» }
La comodità o l'inconveniente, la possibilità o l'impossibilità di lavorare con un determinato API dipendono dai compiti che devono essere risolti. I compiti per l'integrazione con il CRM sono i seguenti:
Tracciare l'inizio della chiamata, dove è stata trasferita, estrarre CallerID, DID, orari di inizio e fine, e possibilmente i dati dalla directory (per trovare la relazione tra il telefono e l'utente CRM)
Iniziare e terminare la registrazione della chiamata, salvarla nel formato necessario, comunicare al termine della registrazione dove si trova il file
Iniziare una chiamata tramite un evento esterno (da un programma), chiamare un numero interno, un numero esterno e collegarli
Opzionale: integrare con il CRM, i gruppi di chiamata e FollowME per il trasferimento automatico delle chiamate in assenza (secondo le informazioni del CRM)
Tutti questi compiti possono essere risolti tramite AMI o ARI, ma ARI fornisce molte meno informazioni, molteplici eventi non vengono monitorati e molte variabili, che in AMI sono presenti (come le chiamate a macro, la definizione di variabili all'interno delle macro, inclusa la registrazione delle chiamate). Pertanto, per un monitoraggio corretto e preciso, sceglieremo per ora AMI (ma non in modo definitivo). Inoltre (e come si può fare senza, siamo persone pigre) - nel lavoro iniziale () utilizzano PAMI. *Poi dobbiamo provare a riconvertire in ARI, ma non è garantito che ci riusciremo.
Inventiamo nuovamente l'integrazione
In modo che il nostro FreePBX possa comunicare in AMI in modi semplici sull'inizio della chiamata, sui tempi di fine, sui numeri, sui nomi dei file registrati, calcolando la durata della chiamata è più semplice utilizzare lo stesso trucco degli autori originali: introdurre le proprie variabili e analizzare l'output per la loro presenza. PAMI offre di farlo semplicemente attraverso la funzione di filtro.
Ecco un esempio di definizione di una propria variabile per il tempo di inizio della chiamata (s è un numero speciale nel piano di chiamata, che viene eseguito PRIMA di iniziare la ricerca per DID)
[ext-did-custom]
exten => s,1,Set(CallStart=${STRFTIME(epoch,,%s)})Esempio di evento AMI per questa riga
Event: Newchannel
Privilege: call,all
Channel: PJSIP/VMS_pjsip-0000078b
ChannelState: 4
ChannelStateDesc: Ring
CallerIDNum: 111222
CallerIDName: 111222
ConnectedLineNum:
ConnectedLineName:
Language: en
AccountCode:
Context: from-pstn
Exten: s
Priority: 1
Uniqueid: 1599589046.5244
Linkedid: 1599589046.5244
Applicazione: Imposta AppData:
CallStart=1599571046
Poiché FreePBX sovrascrive i file extention.conf e extention_additional.conf, useremo il file extention_custom.conf
Codice completo extention_custom.conf
[globals]
;; Controlla i percorsi e i permessi delle cartelle - l'utente asterisk deve avere diritti di scrittura
;; Qui verranno registrate le conversazioni
WAV=\/var\/www\/html\/callme\/records\/wav
MP3=\/var\/www\/html\/callme\/records\/mp3
;; Le registrazioni saranno riprodotte e scaricate da questi percorsi
URLRECORDS=https:\/\/www.host.ru\/callmeplus\/records\/mp3
;; Indirizzo per il callback in caso di chiamata in uscita
URLPHP=https:\/\/www.host.ru\/callmeplus
;; Sì, registriamo le conversazioni
RECORDING=1
;; Questo è un macro per registrare le conversazioni nella nostra cartella.
;; È possibile utilizzare anche la registrazione di sistema, ma per ora rimaniamo con questa -
;; funziona
[recording]
exten => ~~s~~,1,Set(LOCAL(calling)=${ARG1})
exten => ~~s~~,2,Set(LOCAL(called)=${ARG2})
exten => ~~s~~,3,GotoIf($["${RECORDING}" = "1"]?4:14)
exten => ~~s~~,4,Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called})
exten => ~~s~~,5,Set(datedir=${STRFTIME(${EPOCH},,%Y\/ %m\/ %d)})
exten => ~~s~~,6,System(mkdir -p ${MP3}\/ ${datedir})
exten => ~~s~~,7,System(mkdir -p ${WAV}\/ ${datedir})
exten => ~~s~~,8,Set(monopt=nice -n 19 \/usr\/bin\/lame -b 32 --silent "${WAV}\/ ${datedir}\/ ${fname}.wav" "${MP3}\/ ${datedir}\/ ${fname}.mp3" && rm -f "${WAV}\/ ${fname}.wav" && chmod o+r "${MP3}\/ ${datedir}\/ ${fname}.mp3")
exten => ~~s~~,9,Set(FullFname=${URLRECORDS}\/ ${datedir}\/ ${fname}.mp3)
exten => ~~s~~,10,Set(CDR(filename)=${fname}.mp3)
exten => ~~s~~,11,Set(CDR(recordingfile)=${fname}.wav)
exten => ~~s~~,12,Set(CDR(realdst)=${called})
exten => ~~s~~,13,MixMonitor(${WAV}\/ ${datedir}\/ ${fname}.wav,b,${monopt})
exten => ~~s~~,14,NoOp(Finish if_recording_1)
exten => ~~s~~,15,Return()
;; Questo è il contesto principale per iniziare una chiamata
[ext-did-custom]
;; È un po' scorretta fare così e qui, ma funziona - aggiungiamo '8' al numero
nexten => s,1,Set(CALLERID(num)=8${CALLERID(num)})
;; Qui ci sono varie variabili per lo script
exten => s,n,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
exten => s,n,ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp())
exten => s,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
;; La cosa più importante! Gestore della fine della chiamata.
;; I normali percorsi per gestire la fine attraverso (exten=>h,1,qualcosaqui) in FreePBX non funzionano - Macro(hangupcall,) rovina tutto.
;; Quindi colleghiamo Hangup_Handler alla fine della chiamata
exten => s,n,Set(CHANNEL(hangup_handler_push)=sub-call-from-cid-ended,s,1(${CALLERID(num)},${EXTEN}))
;; Gestore della fine della chiamata in arrivo
[sub-call-from-cid-ended]
;; Comunichiamo i valori alla fine della chiamata
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})
;; Stato della chiamata - Risposta, non risposta...
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
exten => s,n,Return
;; Gestore delle chiamate in uscita - tutto simile
[outbound-allroutes-custom]
;; Registrazione
exten => _.,1,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
;; Variabili
exten => _.,n,Set(__CallIntNum=${CALLERID(num)})
exten => _.,n,Set(CallExtNum=${EXTEN})
exten => _.,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten => _.,n,Set(CallmeCALLID=${SIPCALLID})
;; Colleghiamo Hangup_Handler alla fine della chiamata
exten => _.,n,Set(CHANNEL(hangup_handler_push)=sub-call-internal-ended,s,1(${CALLERID(num)},${EXTEN}))
;; Gestore della fine della chiamata in uscita
[sub-call-internal-ended]
;; variabili
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
;; Chiamata allo script che avviserà della chiamata nel CRM - si tratta di una chiamata in uscita,
;; quindi al termine della stessa
exten => s,n,System(curl -s ${URLPHP}\/CallMeOut.php --data action=sendcall2b24 --data ExtNum=${CallExtNum} --data call_id=${SIPCALLID} --data-urlencode FullFname='${FullFname}' --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition='${CallMeDISPOSITION}')
exten => s,n,ReturnLa caratteristica e la differenza rispetto al piano dialettale originale degli autori dell'articolo sorgente sono
Il piano dialettale nel formato .conf, come desidera FreePBX (sì, supporta .ael, ma non tutte le versioni e non sempre è comodo)
Invece di elaborare la conclusione tramite exten=>h, è stata introdotta l'elaborazione tramite hangup_handler, poiché il piano dialettale di FreePBX funziona solo con esso
Corretto il comando di chiamata dello script, aggiunte virgolette e numero esterno di chiamata ExtNum
Le elaborazioni sono state spostate nei contesti _custom e permettono di non modificare i file di configurazione di FreePBX — entranti tramite [ext-did-custom], uscenti tramite [outbound-allroutes-custom]
Nessun vincolo ai numeri — il file è universale e richiede solo la configurazione del percorso e un link al server
Per iniziare a lavorare, è necessario anche consentire lo script in AMI tramite login e password — per questo in FreePBX c'è anche un file _custom
Il file manager_custom.conf
;; questo è il login
[callmeplus]
;; questo è la password
secret = trampampamturlala
deny = 0.0.0.0/0.0.0.0
;; lavoro con la macchina locale - ma se necessario, possono essere aggiunti anche altri
permit = 127.0.0.1/255.255.255.255
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,agent,log,verbose,user,config,command,reporting,originateEntrambi i file devono essere posizionati in /etc/asterisk, quindi leggere nuovamente i file di configurazione (o riavviare Asterisk)
# astrisk -rv
Connected to Asterisk 16.6.2 currently running on freepbx (pid = 31629)
#freepbx*CLI> dialplan reload
Dialplan reloaded.
#freepbx*CLI> exitOra passiamo a PHP
Inizializzazione degli script e creazione del servizio
Poiché lo schema di lavoro con Bitrix 24, il servizio per AMI non è del tutto semplice e trasparente, va trattato separatamente. Asterisk, all'attivazione di AMI, apre semplicemente una porta e basta. Quando un client si connette, richiede l'autenticazione e poi il client si iscrive agli eventi desiderati. Gli eventi arrivano in testo semplice, che PAMI trasforma in oggetti strutturati e offre la possibilità di impostare funzioni di filtraggio solo per eventi, campi, numeri, ecc.
Non appena arriva la chiamata, si verifica l'evento NewExten a partire dal contesto genitore [from-pstn], quindi seguono tutti gli eventi nell'ordine delle righe nei contesti. Quando si ricevono informazioni dalle variabili _custom CallMeCallerIDName e CallStart definite nel piano dialettale, viene invocato
La funzione di richiesta UserID, corrispondente al numero interno a cui è arrivata la chiamata. E se si tratta di un gruppo di chiamate? È una questione politica, bisogna decidere se effettuare una chiamata a tutti contemporaneamente (quando tutti chiamano insieme) o se creare chiamate man mano che si compiono le chiamate sequenziali? La maggior parte dei clienti ha una strategia First Available, quindi non ci sono problemi, chiama solo uno. Ma bisogna affrontare la questione.
La funzione di registrazione della chiamata in Bitrix24, che restituisce il CallID, necessario successivamente per comunicare i parametri della chiamata e il link alla registrazione. Richiede o un numero interno o un UserID.

Al termine della chiamata, viene chiamata la funzione di caricamento della registrazione, che comunica contemporaneamente lo stato di fine chiamata (Occupato, Nessuna risposta, Successo), e carica anche il link al file mp3 con la registrazione (se disponibile).
Poiché il modulo CallMeIn.php deve funzionare continuamente, è stato creato un file di avvio SystemD. callme.service, che deve essere posizionato in /etc/systemd/system/callme.service
[Unit]
Description=CallMe
[Service]
WorkingDirectory=/var/www/html/callmeplus
ExecStart=/usr/bin/php /var/www/html/callmeplus/CallMeIn.php 2>&1 >>/var/log/callmeplus.log
ExecStop=/bin/kill -WINCH ${MAINPID}
KillSignal=SIGKILL
Restart=on-failure
RestartSec=10s
# Qui bisogna verificare quali sono i permessi sulle cartelle
#User=www-data #Ubuntu - debian
#User=nginx #Centos
[Install]
WantedBy=multi-user.targetL'inizializzazione e l'avvio dello script avvengono tramite systemctl o service.
# systemctl enable callme
# systemctl start callmeIl servizio si riavvierà automaticamente se necessario (in caso di arresto). Il servizio di monitoraggio delle chiamate in arrivo non richiede l'installazione di un server web, è necessario solo php (che è sicuramente presente sul server FeePBX). Tuttavia, in assenza di accesso alle registrazioni delle chiamate tramite il server web (anche con https), non sarà possibile ascoltare le registrazioni delle conversazioni.
Ora parliamo delle chiamate in uscita. Lo script CallMeOut.php ha due funzioni:
Iniziare una chiamata all'arrivo della richiesta allo script php (incluso tramite il pulsante "Chiama" in Bitrix stesso). Senza server web non funziona, la richiesta arriva tramite HTTP POST, nella richiesta è contenuto un token.
Inviare un messaggio sulla chiamata, i suoi parametri e le registrazioni in Bitrix. Avviene su iniziativa di Asterisk nel piano di chiamata [sub-call-internal-ended] al termine della chiamata.

Il server web è necessario solo per due cose: il caricamento dei file di registrazione in Bitrix (tramite HTTPS) e la chiamata dello script CallMeOut.php. Si può utilizzare il server integrato FreePBX, i cui file si trovano in /var/www/html, è possibile installare un altro server o specificare un altro percorso.
Server web
Lasciamo la configurazione del server web per l'autoapprendimento (, , ). Se non hai un dominio, puoi provare FreeDomain( ), che ti daranno gratuitamente un nome per il tuo IP pubblico (non dimenticare di inoltrare le porte 80, 443 tramite il router, se l'indirizzo esterno è solo su di esso). Se hai appena creato un dominio DNS, dovrai aspettare (da 15 minuti a 48 ore) affinché tutti i server si carichino. Dall'esperienza con i provider domestici - da 1 ora a un giorno.
Automazione dell'installazione
Su GitHub è iniziato lo sviluppo di un installer, in modo da poter installare ancora più facilmente. Ma andava tutto bene sulla carta — finché installiamo tutto manualmente, per fortuna dopo aver frugato in tutto ciò è diventato cristallino, chi è amico di chi, dove va ciascuno e come fare il debugging. L'installer non è ancora disponibile (
Docker
Se vuoi provare rapidamente una soluzione, c'è un'opzione con Docker — creare rapidamente un contenitore, aprire porte esterne, inserire i file di configurazione e provare (questo è un'opzione con il contenitore LetsEncrypt, se il certificato è già disponibile, basta reindirizzare il reverse proxy al server web FreePBX (a cui abbiamo assegnato un'altra porta — 88), LetsEncrypt in Docker basato su
Devi eseguire il file nella cartella del progetto scaricata (dopo git clone), ma precedentemente è necessario entrare nelle configurazioni di Asterisk (cartella asterisk) e scrivere lì i percorsi ai registri e URL del tuo sito web
version: '3.3'
services:
nginx:
image: nginx:1.15-alpine
ports:
- "80:80"
- "443:443"
volumes:
- .\/nginx\/ssl_docker.conf:\/etc\/nginx\/conf.d\/ssl_docker.conf
certbot:
image: certbot\/certbot
freepbx:
image: flaviostutz\/freepbx
ports:
- 88:80 # per la configurazione
- 5060:5060\/udp
- 5160:5160\/udp
- 127.0.0.1:5038:5038 # per CallMeOut.php
# - 3306:3306
- 18000-18100:18000-18100\/udp
restart: always
environment:
- ADMIN_PASSWORD=admin123
volumes:
- backup:\/backup
- recordings:\/var\/spool\/asterisk\/monitor
- .\/callme:\/var\/www\/html\/callme
- .\/systemd\/callme.service:\/etc\/systemd\/system\/callme.conf
- .\/asterisk\/manager_custom.conf:\/etc\/asterisk\/manager_custom.conf
- .\/asterisk\/extensions_custom.conf:\/etc\/asterisk\/extensions_custom.conf
# - .\/conf\/startup.sh:\/startup.sh
volumes:
backup:
recordings:
Questo file docker-compose.yaml viene avviato tramite
docker-compose up -d
Se nginx non si è avviato, significa che c'è qualcosa che non va nella configurazione nella cartella nginx\/ssl_docker.conf
Altre integrazioni
E perché non inserire anche diversi CRM negli script, ci siamo detti. Abbiamo esaminato diverse API di altri CRM, in particolare quelli gratuiti integrati in alcune PABX — ShugarCRM e Vtiger, e sì! Si può fare, il principio è lo stesso. Ma questa è un'altra storia che poi caricheremo su GitHub separatamente.
Link
Il codice stesso su GitHub —
Articolo iniziale per l'ispirazione
Dichiarazione di non responsabilità: eventuali coincidenze con la realtà sono puramente casuali e non sono responsabile io,
Fonte: habr.com
