From dc2f11542565dddeae5377ce4dc478d793c9d680 Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Wed, 27 Nov 2024 00:08:07 -0500 Subject: [PATCH] [asterisk] Enable APIs --- roles/asterisk/files/voicemail_api.py | 52 +++++++ roles/asterisk/files/voicemail_api.service | 12 ++ roles/asterisk/handlers/main.yml | 6 + .../asterisk/tasks/install_voicemail_api.yml | 23 +++ roles/asterisk/tasks/main.yml | 12 ++ roles/asterisk/templates/ari.conf.j2 | 42 ++++++ roles/asterisk/templates/http.conf.j2 | 132 ++++++++++++++++++ 7 files changed, 279 insertions(+) create mode 100644 roles/asterisk/files/voicemail_api.py create mode 100644 roles/asterisk/files/voicemail_api.service create mode 100644 roles/asterisk/tasks/install_voicemail_api.yml create mode 100644 roles/asterisk/templates/ari.conf.j2 create mode 100644 roles/asterisk/templates/http.conf.j2 diff --git a/roles/asterisk/files/voicemail_api.py b/roles/asterisk/files/voicemail_api.py new file mode 100644 index 0000000..3e23ffd --- /dev/null +++ b/roles/asterisk/files/voicemail_api.py @@ -0,0 +1,52 @@ +"""Simple Asterisk voicemail HTTP API""" + +import configparser +import os +import pathlib + +import fastapi + +VM_ROOT = pathlib.Path("/var/spool/asterisk/voicemail") + +app = fastapi.FastAPI() + + +@app.get("/contexts") +def list_contexts(): + return os.listdir(VM_ROOT) + + +@app.get("/mailboxes/{context}") +def list_context_mailboxes(context: str): + context = VM_ROOT / context + if not (context.exists() and context.is_dir()): + raise fastapi.HTTPException(404) + return os.listdir(context) + + +@app.get("/mailboxes/{context}/{mailbox}") +def list_mailbox_folders(context: str, mailbox: str): + mailbox = VM_ROOT / context / mailbox + if not (mailbox.exists() and mailbox.is_dir()): + raise fastapi.HTTPException(404) + folders = sorted(node.name for node in mailbox.iterdir() if node.is_dir()) + return folders + + +@app.get("/mailboxes/{context}/{mailbox}/{folder}") +def list_messages(context: str, mailbox: str, folder: str): + folder = VM_ROOT / context / mailbox / folder + if not (folder.exists() and folder.is_dir()): + raise fastapi.HTTPException(404) + messages = [] + for info_path in sorted(folder.glob("*.txt")): + files = [ + filename.name + for filename in folder.glob(f"{info_path.stem}.*") + if filename != info_path + ] + with info_path.open("r") as info: + parser = configparser.ConfigParser() + parser.read_file(info) + messages.append({"info": parser["message"], "files": files}) + return messages diff --git a/roles/asterisk/files/voicemail_api.service b/roles/asterisk/files/voicemail_api.service new file mode 100644 index 0000000..142bf45 --- /dev/null +++ b/roles/asterisk/files/voicemail_api.service @@ -0,0 +1,12 @@ +[Unit] +Description=Simple Asterisk voicemail HTTP API +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=root +WorkingDirectory=/var/lib/asterisk/voicemail_api +ExecStart=/usr/bin/env python3 -m uvicorn voicemail_api:app --host 0.0.0.0 --port 8000 diff --git a/roles/asterisk/handlers/main.yml b/roles/asterisk/handlers/main.yml index 1ae190a..77cbe9e 100644 --- a/roles/asterisk/handlers/main.yml +++ b/roles/asterisk/handlers/main.yml @@ -4,3 +4,9 @@ command: asterisk -rx 'pjsip reload' - name: reload voicemail command: asterisk -rx 'voicemail reload' +- name: reload core + command: asterisk -rx 'core reload' +- name: restart voicemail api + ansible.builtin.service: + name: voicemail_api + state: restarted diff --git a/roles/asterisk/tasks/install_voicemail_api.yml b/roles/asterisk/tasks/install_voicemail_api.yml new file mode 100644 index 0000000..1caac9f --- /dev/null +++ b/roles/asterisk/tasks/install_voicemail_api.yml @@ -0,0 +1,23 @@ +- name: Install FastAPI + ansible.builtin.apt: + name: python3-fastapi + update_cache: true + state: latest +- name: Create Voicemail API directory + ansible.builtin.file: + path: /var/lib/asterisk/voicemail_api + state: directory +- name: Install Voicemail API + ansible.builtin.copy: + src: voicemail_api.py + dest: /var/lib/asterisk/voicemail_api/voicemail_api.py + notify: restart voicemail api +- name: Create Voicemail API Service + ansible.builtin.copy: + src: voicemail_api.service + dest: /etc/systemd/system/voicemail_api.service +- name: Start Voicemail API Service + ansible.builtin.service: + name: voicemail_api + state: started + enabled: true diff --git a/roles/asterisk/tasks/main.yml b/roles/asterisk/tasks/main.yml index 9db2b24..67865cd 100644 --- a/roles/asterisk/tasks/main.yml +++ b/roles/asterisk/tasks/main.yml @@ -13,3 +13,15 @@ src: voicemail.conf.j2 dest: /etc/asterisk/voicemail.conf notify: reload voicemail +- name: Configure HTTP interface + template: + src: http.conf.j2 + dest: /etc/asterisk/http.conf + notify: reload core +- name: Configure ARI interface + template: + src: ari.conf.j2 + dest: /etc/asterisk/ari.conf + notify: reload core +- ansible.builtin.include_tasks: + file: install_voicemail_api.yml diff --git a/roles/asterisk/templates/ari.conf.j2 b/roles/asterisk/templates/ari.conf.j2 new file mode 100644 index 0000000..252cacc --- /dev/null +++ b/roles/asterisk/templates/ari.conf.j2 @@ -0,0 +1,42 @@ +[general] +enabled = yes ; When set to no, ARI support is disabled. +;pretty = no ; When set to yes, responses from ARI are +; ; formatted to be human readable. +;allowed_origins = ; Comma separated list of allowed origins, for +; ; Cross-Origin Resource Sharing. May be set to * to +; ; allow all origins. +;auth_realm = ; Realm to use for authentication. Defaults to Asterisk +; ; REST Interface. +; +; Default write timeout to set on websockets. This value may need to be adjusted +; for connections where Asterisk must write a substantial amount of data and the +; receiving clients are slow to process the received information. Value is in +; milliseconds; default is 100 ms. +;websocket_write_timeout = 100 +; +; Display certain channel variables every time a channel-oriented +; event is emitted: +; +; Note that this does incur a performance penalty and should be avoided if possible. +; +;channelvars = var1,var2,var3 + +;[username] +;type = user ; Specifies user configuration +;read_only = no ; When set to yes, user is only authorized for +; ; read-only requests. +; +;password = ; Crypted or plaintext password (see password_format). +; +; password_format may be set to plain (the default) or crypt. When set to crypt, +; crypt(3) is used to validate the password. A crypted password can be generated +; using mkpasswd -m sha-512. +; +; When set to plain, the password is in plaintext. +; +;password_format = plain + +[correl] +type = user +password = hylian2426 +read_only = yes diff --git a/roles/asterisk/templates/http.conf.j2 b/roles/asterisk/templates/http.conf.j2 new file mode 100644 index 0000000..15a5046 --- /dev/null +++ b/roles/asterisk/templates/http.conf.j2 @@ -0,0 +1,132 @@ +; +; Asterisk Built-in mini-HTTP server +; +; +; Note about Asterisk documentation: +; If Asterisk was installed from a tarball, then the HTML documentation should +; be installed in the static-http/docs directory which is +; (/var/lib/asterisk/static-http/docs) on linux by default. If the Asterisk +; HTTP server is enabled in this file by setting the "enabled", "bindaddr", +; and "bindport" options, then you should be able to view the documentation +; remotely by browsing to: +; http://:/static/docs/index.html +; +[general] +; +; The name of the server, advertised in both the Server field in HTTP +; response message headers, as well as the
element in certain HTTP +; response message bodies. If not furnished here, "Asterisk/{version}" will be +; used as a default value for the Server header field and the
+; element. Setting this property to a blank value will result in the omission +; of the Server header field from HTTP response message headers and the +;
element from HTTP response message bodies. +; +servername=Asterisk +; +; Whether HTTP/HTTPS interface is enabled or not. Default is no. +; This also affects manager/rawman/mxml access (see manager.conf) +; +enabled=yes +; +; Address to bind to, both for HTTP and HTTPS. You MUST specify +; a bindaddr in order for the HTTP server to run. There is no +; default value. +; +bindaddr=0.0.0.0 +; +; Port to bind to for HTTP sessions (default is 8088) +; +;bindport=8088 +; +; Prefix allows you to specify a prefix for all requests +; to the server. The default is blank. If uncommented +; all requests must begin with /asterisk +; +;prefix=asterisk +; +; sessionlimit specifies the maximum number of httpsessions that will be +; allowed to exist at any given time. (default: 100) +; +;sessionlimit=100 +; +; session_inactivity specifies the number of milliseconds to wait for +; more data over the HTTP connection before closing it. +; +; Default: 30000 +;session_inactivity=30000 +; +; session_keep_alive specifies the number of milliseconds to wait for +; the next HTTP request over a persistent connection. +; +; Set to 0 to disable persistent HTTP connections. +; Default: 15000 +;session_keep_alive=15000 +; +; Whether Asterisk should serve static content from static-http +; Default is no. +; +;enable_static=yes +; +; Whether Asterisk should serve a status page showing the running +; configuration of this built-in HTTP server. +; Default is yes. +; +;enable_status=no +; +; Redirect one URI to another. This is how you would set a +; default page. +; Syntax: redirect= +; For example, if you are using the Asterisk-gui, +; it is convenient to enable the following redirect: +; +;redirect = / /static/config/index.html +; +; HTTPS support. In addition to enabled=yes, you need to +; explicitly enable tls, define the port to use, +; and have a certificate somewhere. +;tlsenable=yes ; enable tls - default no. +;tlsbindaddr=0.0.0.0:8089 ; address and port to bind to - default is bindaddr and port 8089. +; +;tlscertfile= ; path to the certificate file (*.pem) only. +;tlsprivatekey= ; path to private key file (*.pem) only. +; If no path is given for tlscertfile or tlsprivatekey, default is to look in current +; directory. If no tlsprivatekey is given, default is to search tlscertfile for private key. +; +; To produce a certificate you can e.g. use openssl. This places both the cert and +; private in same .pem file. +; openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem +; +; tlscipher= ; The list of allowed ciphers +; ; if none are specified the following cipher +; ; list will be used instead: +; ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384: +; ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256: +; kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA: +; ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384: +; ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA: +; DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA: +; AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA: +; AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH: +; !EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +; +; tlsdisablev1=yes ; Disable TLSv1 support - if not set this defaults to "yes" +; tlsdisablev11=yes ; Disable TLSv1.1 support - if not set this defaults to "no" +; tlsdisablev12=yes ; Disable TLSv1.2 support - if not set this defaults to "no" +; +; tlsservercipherorder=yes ; Use the server preference order instead of the client order +; ; Defaults to "yes" +; +; The post_mappings section maps URLs to real paths on the filesystem. If a +; POST is done from within an authenticated manager session to one of the +; configured POST mappings, then any files in the POST will be placed in the +; configured directory. +; +;[post_mappings] +; +; NOTE: You need a valid HTTP AMI mansession_id cookie with the manager +; config permission to POST files. +; +; In this example, if the prefix option is set to "asterisk", then using the +; POST URL: /asterisk/uploads will put files in /var/lib/asterisk/uploads/. +;uploads = /var/lib/asterisk/uploads/ +;