codego/php-ksip-telnet

FreePBX extension manager via SSH

Maintainers

Package info

github.com/kitenebie/FreePBX-Console

Language:HTML

pkg:composer/codego/php-ksip-telnet

Statistics

Installs: 27

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.0.0 2026-04-29 06:36 UTC

README

A PHP library for managing FreePBX/Asterisk PJSIP extensions remotely via SSH.

FreePBX-Safe Extension Creation

Direct SQL inserts into users, devices, sip, or the generic pjsip table can produce half-created extensions that appear in dialplan but are not fully managed FreePBX endpoints.

This library now expects extension creation to happen through a PBX-side PHP script that bootstraps FreePBX and uses its Core module methods. A starter script is included at stubs/create_freepbx_extension.php.

Deployment steps

  1. Copy the stub to your FreePBX host:
scp stubs/create_freepbx_extension.php root@your-pbx:/var/lib/asterisk/bin/create_freepbx_extension.php
ssh root@your-pbx "chmod 755 /var/lib/asterisk/bin/create_freepbx_extension.php"
  1. Verify that the script can load /etc/freepbx.conf on the PBX host.
  2. Call SSHClient::createExtensionKsip() as usual. It now invokes the PBX-side script instead of raw SQL inserts.
  3. Reload FreePBX after successful creation.

Version note

The stub uses FreePBX::Create()->Core->addDevice() and addUser() as the intended integration path, but exact method signatures can vary by FreePBX version. Confirm them on your PBX before production rollout.

WEB DOCUMENTATION

Visit Documentation: php-ksip-telnet & juv-ksip-softphone Documentation

Requirements

  • PHP >= 7.4
  • Composer

Installation

composer require codego/php-ksip-telnet

Usage

1. Connect to SSH

<?php

require 'vendor/autoload.php';

use KsipTelnet\SSHClient;

$client = new SSHClient();

// Default port 22
$client->connect('your-server-ip', 'root', 'your-password');

// Custom port
$client->connect('your-server-ip', 'root', 'your-password', 2222);

2. Create a PJSIP Extension

$result = $client->createExtensionKsip(
    '1001',          // extension number
    'secret123',     // extension password
    'freepbxuser',   // MySQL username
    'dbpassword'     // MySQL password
);

echo $result['sql_output'];
echo $result['reload_output'];

3. Get All Extensions

$extensions = $client->getExtKsipList('freepbxuser', 'dbpassword');
// returns: ['1001', '1002', '1003', ...]

4. Generate Extension if Not Exists

$result = $client->genExtKsip(
    [
        'extName'  => '1005',
        'password' => 'secret123'  // optional, defaults to extName
    ],
    'freepbxuser',
    'dbpassword'
);

// If extension already exists:
// ['status' => 'exists', 'extension' => '1005', 'message' => 'Extension 1005 already exists']

// If extension was created:
// ['status' => 'created', 'extension' => '1005', 'result' => [...]]

5. Run a Custom SSH Command

$output = $client->exec('asterisk -rx "pjsip show endpoints"');
echo $output;

Environment Variables (Recommended)

Instead of hardcoding credentials, use environment variables:

export SSH_HOST=your-server-ip
export SSH_USER=root
export SSH_PASS=your-password
export SSH_PORT=22
$client->connect(
    getenv('SSH_HOST'),
    getenv('SSH_USER'),
    getenv('SSH_PASS'),
    getenv('SSH_PORT') ?: 22
);

License

MIT

Laravel Integration

1. Install the package

composer require codego/php-ksip-telnet

2. Add credentials to .env

SSH_HOST=your-server-ip
SSH_USER=root
SSH_PASS=your-password
SSH_PORT=22
SSH_DB_USER=freepbxuser
SSH_DB_PASS=dbpassword

3. Create a Service class

php artisan make:service FreePBXService
<?php

namespace App\Services;

use KsipTelnet\SSHClient;

class FreePBXService
{
    protected SSHClient $client;

    public function __construct()
    {
        $this->client = new SSHClient();
        $this->client->connect(
            config('services.freepbx.host'),
            config('services.freepbx.user'),
            config('services.freepbx.pass'),
            config('services.freepbx.port', 22)
        );
    }

    public function createExtensionKsip(string $ext, string $password): array
    {
        return $this->client->createExtensionKsip(
            $ext,
            $password,
            config('services.freepbx.db_user'),
            config('services.freepbx.db_pass')
        );
    }

    public function getExtKsipList(): array
    {
        return $this->client->getExtKsipList(
            config('services.freepbx.db_user'),
            config('services.freepbx.db_pass')
        );
    }

    public function genExtKsip(array $data): array
    {
        return $this->client->genExtKsip(
            $data,
            config('services.freepbx.db_user'),
            config('services.freepbx.db_pass')
        );
    }

    public function exec(string $command): string
    {
        return $this->client->exec($command);
    }
}

4. Register in config/services.php

'freepbx' => [
    'host'    => env('SSH_HOST'),
    'user'    => env('SSH_USER'),
    'pass'    => env('SSH_PASS'),
    'port'    => env('SSH_PORT', 22),
    'db_user' => env('SSH_DB_USER'),
    'db_pass' => env('SSH_DB_PASS'),
],

5. Bind in AppServiceProvider

use App\Services\FreePBXService;

public function register(): void
{
    $this->app->singleton(FreePBXService::class);
}

6. Use in a Controller

php artisan make:controller ExtensionController
<?php

namespace App\Http\Controllers;

use App\Services\FreePBXService;
use Illuminate\Http\Request;

class ExtensionController extends Controller
{
    public function __construct(protected FreePBXService $freepbx) {}

    public function store(Request $request)
    {
        $request->validate([
            'extension' => 'required|numeric',
            'password'  => 'required|string',
        ]);

        $result = $this->freepbx->createExtensionKsip(
            $request->extension,
            $request->password
        );

        return response()->json($result);
    }

    public function index()
    {
        return response()->json($this->freepbx->getExtKsipList());
    }

    public function generate(Request $request)
    {
        $request->validate([
            'extName'  => 'required|numeric',
            'password' => 'nullable|string',
        ]);

        $result = $this->freepbx->genExtKsip($request->only('extName', 'password'));

        return response()->json($result);
    }
}

7. Add route in routes/api.php

use App\Http\Controllers\ExtensionController;

Route::get('/extensions', [ExtensionController::class, 'index']);
Route::post('/extensions', [ExtensionController::class, 'store']);
Route::post('/extensions/generate', [ExtensionController::class, 'generate']);

8. Test via API

curl -X POST http://your-app.com/api/extensions \
  -H "Content-Type: application/json" \
  -d '{"extension": "1001", "password": "secret123"}'

Auto Extension Registration (ksipRegisterUser)

Automatically generates a 12-digit extension number from a user's name acronym + birthdate, then registers it in FreePBX.

Extension Number Formula

Part Example Result
Last name initial (L = 12th letter) Luna 12
First name initial (J = 10th letter) Juan 10
Middle name initial (M = 13th letter) Mercado 13
Birthdate (mmddyy) 12/16/1996 121696
Final Extension 121013121696

If the result is less than 12 digits, random digits are appended to complete it.

1. Scaffold the cron job

php artisan make:ksip-register-user

This generates:

  • app/Console/Commands/AssignExtensionToUsers.php
  • Appends schedule entry to routes/console.php (Laravel 11+)

2. Add to .env

SSH_HOST=your-server-ip
SSH_USER=root
SSH_PASS=your-password
SSH_PORT=22
SSH_DB_USER=freepbxuser
SSH_DB_PASS=dbpassword

3. Run the scheduler

# Run once manually
php artisan ksip:assign-extensions

# Or let the scheduler run it every minute
php artisan schedule:run

Production: Auto-run via Supervisor

In production, use Supervisor to keep the scheduler running automatically — no need to manually trigger it.

Install Supervisor:

sudo apt install supervisor

Create config file:

sudo nano /etc/supervisor/conf.d/laravel-scheduler.conf
[program:laravel-scheduler]
process_name=%(program_name)s
command=bash -c "while true; do php /var/www/your-project/artisan schedule:run; sleep 60; done"
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/your-project/storage/logs/scheduler.log

Start Supervisor:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-scheduler

Check status:

sudo supervisorctl status laravel-scheduler

Replace /var/www/your-project with your actual project path and www-data with your server user (e.g. ubuntu, forge, deployer).

4. Call directly from a Registration Controller

use KsipTelnet\SSHClient;

// After saving the user in your register() method:
$ssh = new SSHClient();
$ssh->connect(
    config('services.freepbx.host'),
    config('services.freepbx.user'),
    config('services.freepbx.pass'),
    config('services.freepbx.port', 22)
);

$result = SSHClient::ksipRegisterUser(
    $user,
    $ssh,
    config('services.freepbx.db_user'),
    config('services.freepbx.db_pass')
);

// $result['status']    → 'assigned' | 'skipped' | 'error'
// $result['extension'] → '121013121696'

Full Registration Controller Example

<?php

namespace App\Http\Controllers\Auth;

use App\Models\User;
use Illuminate\Http\Request;
use KsipTelnet\SSHClient;

class RegisterController extends Controller
{
    public function register(Request $request)
    {
        $request->validate([
            'first_name'  => 'required|string',
            'last_name'   => 'required|string',
            'middle_name' => 'nullable|string',
            'birth_date'  => 'required|date',
            'email'       => 'required|email|unique:users',
            'password'    => 'required|min:8|confirmed',
        ]);

        $user = User::create([
            'first_name'  => $request->first_name,
            'last_name'   => $request->last_name,
            'middle_name' => $request->middle_name,
            'birth_date'  => $request->birth_date,
            'email'       => $request->email,
            'password'    => bcrypt($request->password),
        ]);

        // Assign FreePBX extension immediately on registration
        $ssh = new SSHClient();
        $ssh->connect(
            config('services.freepbx.host'),
            config('services.freepbx.user'),
            config('services.freepbx.pass'),
            config('services.freepbx.port', 22)
        );

        $result = SSHClient::ksipRegisterUser(
            $user,
            $ssh,
            config('services.freepbx.db_user'),
            config('services.freepbx.db_pass')
        );

        return response()->json([
            'user'      => $user,
            'extension' => $result['extension'],
            'status'    => $result['status'],
        ], 201);
    }
}

Generate Extension Number Only (no SSH)

use KsipTelnet\SSHClient;

$ext = SSHClient::generateExtensionFromUser(
    'Luna',     // last_name
    'Juan',     // first_name
    'Mercado',  // middle_name
    '12/16/1996' // birth_date
);

// $ext → '121013121696'

Return Values of ksipRegisterUser

status Meaning
assigned Extension generated and saved to FreePBX
skipped User already has an extensionName
error Missing name or birthdate fields

Artisan Scaffold Generator

The package includes a make:ksipgen command that auto-generates call recording scaffold files in your Laravel project.

What it generates

  • app/Http/Controllers/Api/CallRecordingController.php
  • database/migrations/{timestamp}_create_call_recordings_table.php
  • Appends routes to routes/api.php

Usage

php artisan make:ksipgen
php artisan migrate

Generated Routes

Route::prefix('recordings')->group(function () {
    Route::post('/upload', [CallRecordingController::class, 'upload']);
    Route::get('/', [CallRecordingController::class, 'index']);
    Route::get('/{id}', [CallRecordingController::class, 'show']);
    Route::get('/{id}/download', [CallRecordingController::class, 'download']);
    Route::delete('/{id}', [CallRecordingController::class, 'delete']);
});

Available API Endpoints

Method Endpoint Description
GET /api/recordings List all recordings
POST /api/recordings/upload Upload a recording (mp3/wav/ogg)
GET /api/recordings/{id} Get a single recording
GET /api/recordings/{id}/download Download a recording
DELETE /api/recordings/{id} Delete a recording

Upload Example

curl -X POST http://your-app.com/api/recordings/upload \
  -F "file=@/path/to/recording.wav" \
  -F "caller=1001" \
  -F "callee=1002" \
  -F "duration=60"

Migration Schema

Schema::create('call_recordings', function (Blueprint $table) {
    $table->id();
    $table->string('filename');
    $table->string('path');
    $table->string('caller')->nullable();
    $table->string('callee')->nullable();
    $table->integer('duration')->nullable();
    $table->timestamps();
});