Skip to main content

Kubernetes operator that creates SIP call Jobs via a FastAPI HTTP API

Project description

mypy and pytests BuildAndPushMultiarch black-lint Cumulative Clones Docker Pulls PyPI

Gemini_Generated_Image_23m8jo23m8jo23m8_250x250.png

sipstuff-k8s-operator

A Kubernetes operator that exposes a FastAPI HTTP API for creating SIP call Jobs. It accepts call requests via POST /call, builds Kubernetes Jobs that run sipstuff.cli call, and tracks job status.

Screenshots

Operator running a SIP call with transcription

Operator startup and call execution

Operator startup with configuration overview

API Endpoints

Method Path Description
POST /call Create a K8s Job that executes a SIP call
GET /jobs List all SIP call jobs
GET /jobs/{name} Get status of a specific job
GET /health Liveness / readiness probe

Quick Start

# Install dependencies (creates a Python 3.14 venv)
make install

# Run the operator locally (starts uvicorn on :8080)
python -m sipstuff_k8s_operator

# Test K8s API connectivity only
python -m sipstuff_k8s_operator conntest

Create a call:

curl -X POST http://localhost:8080/call \
  -H "Content-Type: application/json" \
  -d '{"dest": "+4912345678", "text": "Hello from sipstuff"}'

List jobs:

curl http://localhost:8080/jobs

Check health:

curl http://localhost:8080/health

Configuration

All settings are read from environment variables. Every variable is optional with sensible defaults.

Variable Default Description
JOB_NAMESPACE Downward API namespace or "sipstuff" K8s namespace for created jobs
JOB_IMAGE "xomoxcc/sipstuff:latest" Container image for SIP call jobs
SIP_SECRET_NAME "sip-credentials" K8s Secret name for default SIP credentials
JOB_TTL_SECONDS 3600 TTL in seconds after job completion before cleanup
JOB_BACKOFF_LIMIT 0 Number of retries before marking a job as failed
JOB_HOST_NETWORK "true" Use host networking for SIP/RTP
PORT 8080 HTTP listen port
PIPER_DATA_DIR null Host path for Piper TTS model cache; mounted at /data/piper
WHISPER_DATA_DIR null Host path for Whisper STT model cache; mounted at /data/whisper
RECORDING_DIR null Host path for call recordings; mounted at /data/recordings
RUN_AS_USER null UID to run the job container as
RUN_AS_GROUP null GID to run the job container as
FS_GROUP null fsGroup for the job pod security context
NODE_SELECTOR null Default node selector for job pods (key=value,key2=value2); can be overridden or cleared per request

Note on hostPath volumes and permissions: fsGroup only takes effect on volume types that support ownership management (e.g. emptyDir, PVCs). For hostPath volumes the host directory permissions are used as-is. When RUN_AS_USER is set and volume mounts are configured, the operator automatically adds a fix-permissions initContainer (runs as root with busybox:latest) that executes chown -R <uid>:<gid> on all mounted directories before the main container starts. This ensures the SIP call container can write to the hostPath volumes regardless of the host-side permissions.

Kubernetes Deployment

Manifests are provided in the k8s/ directory:

# Create namespace
kubectl apply -f k8s/namespace.yaml

# Create RBAC (ServiceAccount, Role, RoleBinding)
kubectl apply -f k8s/rbac.yaml

# Create the SIP credentials secret (copy and edit the example first)
cp k8s/secret.yaml.example k8s/secret.yaml
# edit k8s/secret.yaml with your SIP credentials
kubectl apply -f k8s/secret.yaml

# Deploy the operator
kubectl apply -f k8s/deployment.yaml

# Expose via ClusterIP service (80 -> 8080)
kubectl apply -f k8s/service.yaml

The operator Deployment uses liveness and readiness probes against /health, runs as a single replica on port 8080, and uses a dedicated sipstuff-operator ServiceAccount with RBAC permissions for batch/jobs and pods.

Call Request Body

POST /call accepts a JSON body with the following fields. Exactly one of text or wav must be provided.

Field Type Default Description
dest string (required) Destination phone number or SIP URI
text string null Text to speak via TTS (mutually exclusive with wav)
wav string null Path to a WAV file to play (mutually exclusive with text)
sip_server string null SIP server override
sip_port integer null SIP port override (1-65535)
sip_user string null SIP username override
sip_password string null SIP password override
sip_transport string null Transport protocol: "udp", "tcp", or "tls"
sip_srtp string null SRTP mode: "disabled", "optional", or "mandatory"
sip_tls_verify boolean null TLS server certificate verification
stun_servers string null Comma-separated STUN servers
ice_enabled boolean null Enable ICE for NAT traversal
turn_server string null TURN relay server (host:port)
turn_username string null TURN auth username
turn_password string null TURN auth password
turn_transport string null TURN transport: "udp", "tcp", or "tls"
keepalive_sec integer null UDP keepalive interval in seconds (0-600)
public_address string null Public IP address for SDP/Contact headers
timeout integer 60 Call timeout in seconds (1-600)
pre_delay float 0.0 Delay before call in seconds (0-30)
inter_delay float 0.0 Delay between WAV repeats in seconds (0-30)
post_delay float 0.0 Delay after call in seconds (0-30)
wait_for_silence float null Wait for N seconds of remote silence before playback (0-30)
repeat integer 1 Number of call repetitions (1-100)
tts_model string null TTS model name
tts_sample_rate integer null TTS sample rate in Hz (0-48000)
tts_data_dir string null TTS data directory
stt_model string null Whisper model size for STT transcription (e.g. "tiny", "base", "small", "medium", "large-v3")
stt_language string null Language code for STT transcription (e.g. "de")
stt_data_dir string null Directory for Whisper STT models
record string null Record remote party audio to this WAV file path (should be below /data/recordings/ when RECORDING_DIR is configured)
transcribe boolean false Transcribe recorded audio via STT and write a JSON call report (requires record)
verbose boolean false Enable verbose logging in the call job
node_selector object null K8s node selector for the job pod (e.g. {"mayplacecalls": "true"}). Overrides the operator default from NODE_SELECTOR. Set to {} to explicitly clear the default.

SIP Credentials

SIP connection parameters can be provided per-request in the call body (sip_server, sip_port, sip_user, sip_password, sip_transport, sip_srtp, sip_tls_verify). When a field is not provided, the operator falls back to the K8s Secret specified by SIP_SECRET_NAME (default: sip-credentials).

The secret should contain these keys:

stringData:
  SIP_SERVER: "sip.example.com"
  SIP_PORT: "5060"
  SIP_USER: "sipuser"
  SIP_PASSWORD: "changeme"
  SIP_TRANSPORT: "udp"
  SIP_SRTP: "disabled"
  # SIP_TLS_VERIFY_SERVER: "false"

See k8s/secret.yaml.example for a complete example.

NAT Traversal

NAT traversal settings (STUN, ICE, TURN, keepalive, public address) can be provided per-request or configured globally via the same K8s Secret. Add any of these optional keys to the sip-credentials Secret:

stringData:
  # SIP_STUN_SERVERS: "stun.l.google.com:19302"
  # SIP_ICE_ENABLED: "false"
  # SIP_TURN_SERVER: "turn.example.com:3478"
  # SIP_TURN_USERNAME: ""
  # SIP_TURN_PASSWORD: ""
  # SIP_TURN_TRANSPORT: "udp"
  # SIP_KEEPALIVE_SEC: "0"
  # SIP_PUBLIC_ADDRESS: ""

When turn_server is provided in a request, SIP_TURN_ENABLED=true is automatically set on the job.

Development

make lint           # Black formatter (line length: 120)
make isort          # Sort imports
make tcheck         # MyPy type checking (strict mode)
make tests          # Run pytest
make prepare        # Run tests + commit-checks
make commit-checks  # Run all pre-commit hooks
make gitleaks       # Scan for secrets

Run a single test:

source .venv/bin/activate && pytest tests/test_base.py::test_version_exists -v

Initial Setup (GitHub + Docker Hub)

The repo_scripts/initial_setup_github_dockerhub.sh script automates first-time project setup:

  1. Creates the Docker Hub repository (if no OAT exists yet)
  2. Creates a scoped Organization Access Token (OAT) with push+pull for the target repo
  3. Validates that the OAT actually has push+pull access via check_dockerhub_token.py
    • If permissions are missing, offers to create a new OAT with the correct scopes
  4. Ensures the Docker Hub repo exists (handles both public and private repos)
  5. Creates a GitHub repo and a public gist for clone tracking badges
  6. Sets all required GitHub Actions secrets (DOCKERHUB_TOKEN, DOCKERHUB_USERNAME, GIST_ID, GIST_TOKEN, REPO_PRIV_TOKEN)

Configuration is read from repo_scripts/include.local.sh (not checked in). See repo_scripts/include.sh for the variable template.

# Run the setup
./repo_scripts/initial_setup_github_dockerhub.sh

Docker

# Build the image
make build
# or
docker build -t sipstuff-k8s-operator:latest .

# Run locally
docker run --rm -p 8080:8080 sipstuff-k8s-operator:latest

The image is based on python:3.14-slim and runs python3 -m sipstuff_k8s_operator as its entrypoint.

License

This project is licensed under the LGPL where applicable/possible — see LICENSE.md. Some files/parts may use other licenses: MIT | GPL | LGPL. Always check per‑file headers/comments.

Authors

  • Repo owner (primary author)
  • Additional attributions are noted inline in code comments

Acknowledgments

  • Inspirations and snippets are referenced in code comments where appropriate.

⚠️ Note

This is a development/experimental project. For production use, review security settings, customize configurations, and test thoroughly in your environment. Provided "as is" without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software. Use at your own risk.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sipstuff_k8s_operator-0.0.8.tar.gz (20.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sipstuff_k8s_operator-0.0.8-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

Details for the file sipstuff_k8s_operator-0.0.8.tar.gz.

File metadata

  • Download URL: sipstuff_k8s_operator-0.0.8.tar.gz
  • Upload date:
  • Size: 20.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.16.3 cpython/3.14.3 HTTPX/0.28.1

File hashes

Hashes for sipstuff_k8s_operator-0.0.8.tar.gz
Algorithm Hash digest
SHA256 71c5b9f84d948574d13e2442e9ae6ce0f5e8f958fc71843b5c9d17db7975eac6
MD5 b74a811f4ba03841219fe24dfc2bdda7
BLAKE2b-256 5903784427185ddc8727ed2b3768361e56009a256e1f4daa1729e97352635255

See more details on using hashes here.

File details

Details for the file sipstuff_k8s_operator-0.0.8-py3-none-any.whl.

File metadata

File hashes

Hashes for sipstuff_k8s_operator-0.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 c5549a53cbde08322452cecdd1ab197b661ec23c4c7229cd44e2ad11100440e1
MD5 66ac397035e99fe4e97f65aa707ad1a6
BLAKE2b-256 79b89908bfbd2507cfca960192774043052560a8862444e1e01d24c91a683969

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page