Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ it take a look at the
- Conferences
- Federation between µURU instances
- WebSIP to initiate calls directly from the browser
- Originate calls via physical phones from the browser
- LDAP Phonebook
- Rendering of Markdown for (Help-)Pages
- Managing of images, audio files and other media
Expand Down
40 changes: 40 additions & 0 deletions app/api/telephoning.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
Licensed under the MIT license. See LICENSE file in the project root for details.
"""

from asterisk.ami.response import FutureResponse
from logging import getLogger
from asterisk.ami.client import AMIClientAdapter
from app.models.crud.asterisk import get_contact
from app.core.db import SessionDep
from app.models.crud.extension import get_extension_by_id
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, HTTPException
Expand All @@ -22,6 +28,7 @@

router = APIRouter(prefix="/telephoning", tags=["telephoning"])

logger = getLogger(__name__)

class PhoneType(BaseModel):
schema: Optional[dict] = None
Expand Down Expand Up @@ -97,3 +104,36 @@ def put_websip(extension: str):
ext.last_seen = datetime.now()

return {}


@router.get("/originate", status_code=status.HTTP_204_NO_CONTENT)
def originate_call(session: SessionDep, session_asterisk: SessionAsteriskDep, user: CurrentUser, source: str, dest: str):
source_extension = get_extension_by_id(session, source, public=False)
if source_extension is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Source extension unknown")
if source_extension.user_id != user.id and not user.role == UserRole.ADMIN:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You may not originate calls from this extension")

contact = get_contact(session_asterisk, source_extension, user)
if contact is None:
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail="The source extension is offline")

if not dest.isnumeric():
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail="Destination must be a number!")

client = Telephoning.instance().get_ami_client()
adapter = AMIClientAdapter(client)
response: FutureResponse = adapter.Originate(
Channel=f"PJSIP/{source}",
Exten=dest,
Priority=1,
Context="pjsip_internal"
)

logger.info(f"Originated call from {source} to {dest}")
logger.debug(f"Received AMI response:\n{response.response}")

if response.response is not None:
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="unable to originate call due to error from AMI")

return {}
6 changes: 6 additions & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,5 +244,11 @@ def logging_loglevel(self) -> int:
FEDERATION_IAX2_HOST: str = ASTERISK_HOST
FEDERATION_UURU_HOST: str = WEB_HOST

## AMI
ASTERISK_AMI_USER: str = "uuru_ami_user"
ASTERISK_AMI_PASS: str = "uuru_ami_secret"
ASTERISK_AMI_ADDR: str = "172.17.0.1"
ASTERSIK_AMI_PORT: int = 5038


settings = Settings()
18 changes: 16 additions & 2 deletions app/telephoning/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
Licensed under the MIT license. See LICENSE file in the project root for details.
"""

from typing import Self
import importlib
import pkgutil
from fastapi import APIRouter, FastAPI
from apscheduler.schedulers.background import BackgroundScheduler
import json
from logging import getLogger

from asterisk.ami import AMIClient

from app.core.config import settings
from app.telephoning.flavor import PhoneFlavor

Expand Down Expand Up @@ -47,7 +50,7 @@ def load_phone_flavors() -> list[PhoneFlavor]:


class Telephoning(object):
_instance = None
_instance: Self | None = None

@staticmethod
def instance():
Expand All @@ -65,8 +68,12 @@ def __init__(self):
self.flavor_by_type = {}
self.all_types = []

self.ami_client = AMIClient(settings.ASTERISK_AMI_ADDR, settings.ASTERSIK_AMI_PORT)

def start(self, app: FastAPI, scheduler: BackgroundScheduler):

self.setup_ami()

for cls in self.flavor_classes:
# 1st: create instance
flavor_name = cls.__name__.lower()
Expand Down Expand Up @@ -109,7 +116,14 @@ def start(self, app: FastAPI, scheduler: BackgroundScheduler):
# include router
app.include_router(self.router)

def stop(self): ...
def stop(self):
self.ami_client.logoff()

def setup_ami(self):
self.ami_client.login(settings.ASTERISK_AMI_USER, settings.ASTERISK_AMI_PASS)

def get_ami_client(self):
return self.ami_client

@staticmethod
def get_flavor_by_type(phone_type: str) -> PhoneFlavor | None:
Expand Down
4 changes: 2 additions & 2 deletions asterisk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM ubuntu:24.04
FROM ubuntu:noble-20251013

ARG ASTERISK_SOURCE_VERSION=20.16.0
ARG ASTERISK_SOURCE_VERSION=20.17.0

# download needed packages
RUN apt-get update && apt-get install -y --no-install-recommends \
Expand Down
7 changes: 7 additions & 0 deletions asterisk/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

set -e

# set default values for AMI handling
export UURU_ASTERISK_AMI_USER="${UURU_ASTERISK_AMI_USER:-uuru_ami_user}"
export UURU_ASTERISK_AMI_PASS="${UURU_ASTERISK_AMI_PASS:-uuru_ami_secret}"
export UURU_ASTERISK_AMI_ADDR="${UURU_ASTERISK_AMI_ADDR:-172.17.0.1}"
export UURU_ASTERISK_AMI_PORT="${UURU_ASTERISK_AMI_PORT:-5038}"

# execute alembic database migrations
cd /etc/alembic
envsubst < config.ini.tmpl > config.ini
Expand All @@ -11,4 +17,5 @@ alembic -c config.ini upgrade head
cd /
envsubst < /etc/odbc.ini.tmpl > /etc/odbc.ini
envsubst < /etc/asterisk/res_odbc.conf.tmpl > /etc/asterisk/res_odbc.conf
envsubst < /etc/asterisk/manager.conf.tmpl > /etc/asterisk/manager.conf
exec asterisk -f
9 changes: 9 additions & 0 deletions asterisk/etc/asterisk/manager.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[general]
enabled=yes
port=${UURU_ASTERISK_AMI_PORT}
bindaddr=${UURU_ASTERISK_AMI_ADDR}

[${UURU_ASTERISK_AMI_USER}]
secret=${UURU_ASTERISK_AMI_PASS}
read=all
write=all
25 changes: 22 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ The credentials for the first created account are configured by
### HTTP routing

!!! warning
Do not change those values, currently uURU contains some hardcoded links
which will break otherwise!
Do not change those values, currently uURU contains some hardcoded links
which will break otherwise!

| Key | Description | Default |
| ----------------------- | --------------------------------------- | ------------ |
Expand Down Expand Up @@ -122,7 +122,7 @@ The database must be a mariadb / mysql db
### WebSIP

!!! note
The `UURU_WEBSIP_EXTENSION_RANGE` should be reserved via the `UURU_RESERVED_EXTENSIONS` key.
The `UURU_WEBSIP_EXTENSION_RANGE` should be reserved via the `UURU_RESERVED_EXTENSIONS` key.

| Key | Description | Default |
| --------------------------- | ------------------------------------------------------ | ---------------------- |
Expand All @@ -141,3 +141,22 @@ The database must be a mariadb / mysql db
| UURU_MEDIA_ALLOW_RAW | Allow upload of raw files | 0 |
| UURU_MEDIA_IMAGE_STORAGE_FORMAT | Format in which images should be stored on disk | png |
| UURU_MEDIA_AUDIO_STORAGE_FORMAT | Format in which audio files should be stored on disk | mp3 |

### Asterisk Manager Interface

!!! info
Because the `asterisk` container is exposed via `--network=host` we have to bind the AMI interface to a specific (non 127.0.0.0/8) ip.
We assume that your docker installation ships with `docker0` on `172.17.0.1` - change to your setup if needed.

!!! danger
By default all other containers/processes on your machine can talk via `AMI` to the `asterisk`.
We assume a trusted environment.
Please generate a secure `UURU_ASTERISK_AMI_PASS` for production setups.


| Key | Description | Default |
| ---------------------- | ------------------------------------------- | -------------------------- |
| UURU_ASTERISK_AMI_USER | Username for the asterisk manager interface | uuru_ami_user |
| UURU_ASTERISK_AMI_PASS | Secret for the asterisk manager interface | uuru_ami_secret |
| UURU_ASTERISK_AMI_ADDR | Host of the asterisk manager interface | 172.17.0.1 (Docker bridge) |
| UURU_ASTERSIK_AMI_PORT | Port of the asterisk manager interface | 5038 |
11 changes: 11 additions & 0 deletions docs/features/originate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Call Origination

Analagous to `WebSIP` this feature allows the user to comfortably dial an extension from the frontend.
But in this case the call is originated from a physical telephone selectable from a drop-down menu.

## Process
1. Click on an extension in the phonebook
2. Select a phone to send the call to
3. The selected phone will ring
4. After pickup, the target phone will ring
5. Profit!
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ docker-compose.
- Conferences
- Federation between µURU instances
- WebSIP to initiate calls directly from the browser
- Originate calls via physical phones from the browser
- LDAP Phonebook
- Rendering of Markdown for (Help-)Pages
- Managing of images, audio files and other media
Expand Down
Loading
Loading