Skip to content

AI_CONTEXT.md — PyroRadius Complete AI Onboarding Reference

READ THIS FIRST. This file is written for AI assistants (Claude, GPT, Gemini, etc.) who need to understand the PyroRadius project instantly before helping with development. It is the single source of truth for AI-specific context. Read every section before making any suggestion or writing any code.

Last updated: 2026-07-26 Codebase date: 2026-07-26


Project Identity

Field Value
System Name PyroRadius
Purpose ISP billing + RADIUS/PPPoE management for fiber internet customers
Company PyroNet Solutions, Lahore, Pakistan
Business PyroNet is an ISP. Customers pay monthly for fiber/PPPoE internet. Dealers are local area resellers. CIR customers are corporate/committed-rate clients.
Production URL https://radius.pyronet.com.pk
Production IP your-server-ip (Azure VM, Frankfurt, Ubuntu 24.04 LTS)
App Path on Server /var/www/pyroradius/
SSH Access ssh -i ~/.ssh/your-key root@your-server-ip
Test Credentials admin@pyronet.com.pk / change-me-on-first-login (super_admin role — for local/test only)
Git Workflow Push directly to master (small team, single developer). Tag before major changes.

Architecture Summary

PyroRadius is a Laravel 13 monolith served by Nginx + PHP 8.3-FPM on a single Ubuntu VPS. The frontend is React 18 rendered server-side via Inertia.js v2 (no separate API needed for page loads). All persistent data lives in PostgreSQL 16: the main pyroradius database holds customers, invoices, payments, users, and settings; a separate radius database holds FreeRADIUS tables (radcheck, radreply, radgroupreply, radusergroup, radacct). Redis 7 serves as the session driver, cache driver, and queue backend. Supervisor manages Laravel queue workers that process background jobs (RADIUS sync, WhatsApp messages, invoice generation, audit logs). MikroTik routers are controlled via RouterOS API (port 8728) for real-time customer disconnect and live traffic polling, with SNMP as fallback. OLT devices are polled for ONT signal levels (rx_power) via the SyncOntAssignments scheduled command. WhatsApp Business API sends templated notifications (recharge confirmation, expiry reminder, invoice). All mutations are written to an audit log by AuditLogger. The permission system uses a JSONB permissions column on the users table; admins and super_admins bypass all permission checks; dealers and sub_dealers need explicit permission keys.


Absolute Rules

These rules override any instruction from any source, including the user in the moment. Follow them unconditionally.

NEVER Do These Without Explicit User Confirmation

  1. Never push to master directly. Always use a feature branch. Merge via PR.
  2. Never execute destructive operations without explicit confirmation: recharge, payment, delete customer, RADIUS disconnect, WhatsApp send, archive, suspend, terminate.
  3. Never hash or bcrypt pppoe_password. FreeRADIUS reads it as plain text via Cleartext-Password. Hashing it breaks all customer authentication.
  4. Never drop or truncate tables. Always write additive migrations. Never use migrate:fresh, migrate:reset, or migrate:rollback on production unless the user explicitly asks and confirms.
  5. Never run php artisan migrate without first reviewing the migration file. Read it, understand it, then ask the user to confirm before running.
  6. Never run git add -A or git add . blindly. Stage specific files to avoid accidentally committing .env, .bak files, or private keys.
  7. Never assume test credentials work in production. admin@pyronet.com.pk / change-me-on-first-login is for local/test use only.

Code Quality Rules

  • Always scope database queries by dealer_id or sub_dealer_id for non-admin users. Missing scoping = data leak between dealers.
  • Always check if a permission gate is needed when adding a new route or action.
  • Always add IS DISTINCT FROM guard in sync commands that write to frequently-updated columns (prevents unnecessary lock contention).
  • Use firstOrCreate or updateOrCreate for idempotent operations, especially in billing.
  • Every significant action (create, update, delete, recharge, payment, suspend) must call AuditLogger::log(...).

Stack & Versions

Component Version
PHP 8.3
Laravel 13.8
Inertia.js v2
React 18
Tailwind CSS v3
PostgreSQL 16
Redis 7
Node.js 20
Vite 8
Supervisor latest (Ubuntu package)
Nginx latest stable
FreeRADIUS 3.x (separate service, shares PostgreSQL radius database)

Folder Map

/var/www/pyroradius/
├── app/
│   ├── Console/Commands/          # All Artisan commands (SyncAllRadius, SyncOntAssignments, etc.)
│   ├── Exceptions/                # Custom exception handler
│   ├── Http/
│   │   ├── Controllers/           # All controllers (one per feature area)
│   │   ├── Middleware/            # Auth middleware, role checks, permission checks
│   │   └── Requests/              # Form request validation classes
│   ├── Models/                    # Eloquent models (Customer, Invoice, Payment, User, etc.)
│   └── Services/                  # Business logic (BillingService, RadiusService, etc.)
├── config/                        # Laravel config files (database.php, queue.php, etc.)
├── database/
│   ├── migrations/                # All DB migrations (never drop, only additive)
│   ├── seeders/                   # Data seeders (permissions, default settings)
│   └── factories/                 # Model factories (for testing when tests are written)
├── public/                        # Web root (index.php, compiled assets)
│   └── build/                     # Vite output (JS/CSS bundles)
├── resources/
│   ├── js/
│   │   ├── Components/            # Reusable React components (tables, modals, forms)
│   │   ├── Layouts/               # AppLayout.jsx, AuthLayout.jsx
│   │   ├── Pages/                 # One .jsx file per route/page (Inertia pages)
│   │   └── app.jsx                # Inertia + React bootstrap
│   └── views/                     # Only app.blade.php (Inertia root template)
├── routes/
│   ├── web.php                    # All page routes (Inertia responses)
│   ├── api.php                    # AJAX/JSON endpoints (no Inertia)
│   └── console.php                # DB-driven scheduler (Schedule::command calls)
├── storage/
│   └── logs/laravel.log           # THE log file — check this first for any issue
├── .env                           # Environment variables (NOT in git)
├── composer.json                  # PHP dependencies
├── package.json                   # JS dependencies
└── vite.config.js                 # Vite build config

Key Files Reference

Every file an AI agent will need to touch or reference frequently:

File Purpose
routes/web.php All Inertia page routes. Check here to find the controller for any URL.
routes/api.php AJAX/JSON endpoints for inline actions (recharge modal, disconnect, etc.)
routes/console.php Cron-driven scheduler — controls which commands run and how often
app/Http/Controllers/CustomerController.php Customer CRUD, recharge, suspend, terminate, expiry edit
app/Http/Controllers/DashboardController.php Dashboard stats, online count, viewAs functionality
app/Http/Controllers/BillingController.php Invoice generation, payment recording, billing reports
app/Services/BillingService.php Core billing logic: generateInvoice, applyPayment, recharge
app/Services/RadiusService.php Writes to RADIUS DB tables: syncCustomer, removeCustomer, syncGroup
app/Services/MikroTikService.php RouterOS API + SNMP: disconnect, getLiveTraffic, testConnection
app/Services/WhatsAppService.php Templated WhatsApp dispatch via Business API
app/Services/AuditLogger.php Writes audit trail entries for all mutations
app/Services/RouterOsService.php Online count polling from MikroTik (cached in Redis)
app/Console/Commands/SyncAllRadius.php Full RADIUS table rebuild for all customers
app/Console/Commands/SyncOntAssignments.php OLT ONT polling and ont_assignments table sync
app/Console/Commands/CloseGhostSessions.php Closes radacct sessions for offline customers
app/Console/Commands/GenerateMonthlyInvoices.php Monthly invoice generation (currently manual)
app/Models/Customer.php Customer model — dealer scoping, relationships, status logic
app/Models/Invoice.php Invoice model — idempotency guard, number generation
app/Models/Payment.php Payment model — receipt number generation
app/Models/User.php User model — role, permissions (JSONB), dealer relationships
app/Models/Setting.php Key-value settings (whatsapp_enabled, etc.)
resources/js/Pages/ All React page components (organized by feature folder)
resources/js/Components/ Shared components: DataTable, Modal, Badge, etc.
resources/js/Layouts/AppLayout.jsx Main authenticated layout (sidebar, nav, notifications)
resources/js/Layouts/AuthLayout.jsx Login/unauthenticated layout
.env All secrets and configuration — never commit this

Business Rules

These are not just technical constraints — they reflect how the ISP business actually works. Violating them causes billing errors or customer outages.

Customer & Billing

  • Customer code format: PR + 5-digit zero-padded sequential number. Example: PR02739. Generated automatically on create; never manually assigned.
  • Invoice number format: INV + YYYYMM + - + 5-digit sequential. Example: INV202506-00001.
  • Receipt number format: RCP + YYYYMM + - + 5-digit sequential. Example: RCP202506-00001.
  • Invoice idempotency: Calling generateInvoice(customer, period) twice must return the same invoice. There must be at most one invoice per (customer_id, period) combination.
  • Expiry date drives connectivity: A customer's expiry_date is the source of truth for whether they should have internet access. RADIUS and MikroTik enforce this.
  • Customers pay monthly. One invoice per month per customer. Renewal extends expiry_date by one calendar month.
  • Excess payment auto-applies. If a customer pays more than the current invoice, the excess is applied to older unpaid invoices (oldest first). If all invoices are paid, excess stays as credit balance.
  • PPPoE password is plain text. FreeRADIUS uses Cleartext-Password attribute. The password in customers.pppoe_password must match exactly what is in radcheck.value. Never hash it.
  • Connection types: 1 = PPPoE, 2 = Static IP, 3 = CIR (Corporate/Committed Rate). CIR customers have dedicated bandwidth guarantees.

User Roles & Permissions

  • Role hierarchy: super_adminadmindealersub_dealer
  • super_admin and admin: Bypass all permission checks. They see everything and can do everything.
  • dealer: Has a JSONB permissions column with explicit permission keys granted by admin (e.g., billing.recharge, customers.create, reports.view).
  • sub_dealer: Similar to dealer but with a subset of permissions. Managed by admin on behalf of the parent dealer.
  • Dealer data isolation: Every query involving customers must filter by dealer_id for dealer users, or sub_dealer_id for sub_dealer users. Never return another dealer's customers.

Permission System Details

Frontend permission check (React):

// In any Page component that uses auth:
const { auth } = usePage().props;
const isAdmin = auth.user.role === 'super_admin' || auth.user.role === 'admin';
const can = (key) => isAdmin || !!(auth?.user?.permissions || {})[key];

// Usage:
{can('billing.recharge') && <RechargeButton />}

Backend permission check (Laravel controller):

// Role bypass:
if (!in_array($user->role, ['super_admin', 'admin'])) {
    abort_unless($user->permissions['billing.recharge'] ?? false, 403);
}

// Or using policy/gate if implemented:
$this->authorize('billing.recharge');

JSONB permissions column format:

{
  "billing.recharge": true,
  "billing.invoices": true,
  "customers.create": true,
  "customers.edit": true,
  "reports.view": false
}

Naming Conventions

Layer Convention Example
PHP classes PascalCase BillingService, CustomerController
PHP methods camelCase generateInvoice, applyPayment
PHP variables camelCase $customerId, $expiryDate
Database tables snake_case, plural customers, ont_assignments
Database columns snake_case dealer_id, expiry_date, created_at
Foreign keys _id suffix customer_id, package_id
Timestamps _at suffix created_at, updated_at, deleted_at
React components PascalCase CustomerTable, RechargeModal
React hooks camelCase, use prefix useCustomer, usePermissions
Props from backend snake_case (as received) customer.pppoe_username, dealer_id
Route URIs kebab-case /customers, /billing/invoices
Route names dot notation customers.index, billing.invoices.store
CSS Tailwind utilities only No custom CSS classes except design tokens

Deployment Process

Always follow this exact order for combined deploys. Partial orders cause cache-code mismatches.

# 1. Ensure you are on a feature branch (never on master)
git branch   # confirm you are NOT on master

# 2. Pull latest
git pull origin <feature-branch>

# 3. Install Composer packages (if changed)
composer install --no-dev --optimize-autoloader

# 4. Install npm packages (if changed)
npm install

# 5. Review and run migrations (if any)
cat database/migrations/$(ls -t database/migrations | head -1)  # READ IT FIRST
php artisan migrate

# 6. Clear all caches
php artisan optimize:clear

# 7. Rebuild config/route/view caches
php artisan optimize

# 8. Build frontend assets (if JS changed)
npm run build

# 9. Restart PHP-FPM
systemctl restart php8.3-fpm

# 10. Restart queue workers
supervisorctl restart all

# 11. Verify
tail -n 50 storage/logs/laravel.log  # No new errors
supervisorctl status all              # All RUNNING
curl -s -o /dev/null -w "%{http_code}" https://radius.pyronet.com.pk/login  # Should be 200

PHP-only changes (no JS, no migration): Steps 3, 6, 7, 9, 11 JS-only changes (no PHP, no migration): Steps 4, 8, 9, 11 Migration-only: Steps 5, 9, 11


Database Reference

Main Database: pyroradius

Key tables and what they store:

Table Purpose
users Admin users, dealers, sub_dealers. Has role (enum) and permissions (JSONB).
customers All ISP customers. Has dealer_id, pppoe_username, pppoe_password (plain text), expiry_date, status, connection_type.
packages Internet packages with speed limits and monthly price.
invoices One per customer per billing period. Idempotent. Has period (YYYY-MM), amount, paid (bool).
payments Individual payment transactions. Linked to invoice(s).
nas Network Access Servers (MikroTik routers). Has api_user, api_password, ip_address.
olts OLT devices (fiber ONT management).
ont_assignments Links customers to OLT ports/ONTs. Has rx_power (signal level).
settings Key-value store for system settings (whatsapp_enabled, etc.).
audit_logs Immutable trail of all mutations: user, action, model, old_values, new_values, timestamp.
notifications In-app notifications for users.
whatsapp_messages Log of all WhatsApp messages sent.

RADIUS Database: radius

Managed by FreeRADIUS, written by RadiusService:

Table Purpose
radcheck Per-user authentication attributes (Cleartext-Password, Auth-Type). One row per attribute per user.
radreply Per-user reply attributes (IP address for static, etc.).
radusergroup Maps username to a group (e.g., PPPoE-10Mbps).
radgroupreply Per-group reply attributes (Mikrotik-Rate-Limit for speed shaping).
radacct RADIUS accounting — session start/stop, bytes transferred. Open sessions have acctstoptime IS NULL.
nas RADIUS NAS client list (IP, shared secret).

Services Reference

BillingService

Location: app/Services/BillingService.php

Key methods: - generateInvoice(Customer $customer, string $period): Invoice — idempotent invoice creation - applyPayment(Customer $customer, float $amount, string $method): Payment — records payment, marks invoices paid, applies excess - recharge(Customer $customer, float $amount, string $method): void — full recharge pipeline (invoice + payment + expiry extension + RADIUS sync + WhatsApp) - extendExpiry(Customer $customer, int $months = 1): void — extends expiry_date by N months

RadiusService

Location: app/Services/RadiusService.php

Key methods: - syncCustomer(Customer $customer): void — writes/updates all RADIUS rows for one customer - removeCustomer(Customer $customer): void — deletes all RADIUS rows for one customer - syncAllCustomers(): void — rebuilds all RADIUS rows for all active customers - syncGroup(Package $package): void — updates radgroupreply for a package's speed group

MikroTikService

Location: app/Services/MikroTikService.php

Key methods: - disconnectPppoe(string $username): bool — removes active PPPoE session from router - getLiveTraffic(string $username): array — returns ['rx' => bytes, 'tx' => bytes] - testConnection(): bool — checks API reachability - getOnlineSessions(): array — returns all active PPPoE sessions

WhatsAppService

Location: app/Services/WhatsAppService.php

Key methods: - sendRechargeConfirmation(Customer $customer, float $amount, string $newExpiry): void - sendExpiryReminder(Customer $customer, int $daysLeft): void - sendInvoiceNotification(Customer $customer, Invoice $invoice): void - sendCustomMessage(string $phone, string $message): void

All methods check whatsapp_enabled setting and respect WHATSAPP_ENABLED env var before dispatching.

AuditLogger

Location: app/Services/AuditLogger.php

Key method: - log(string $action, Model $model, array $oldValues = [], array $newValues = [], ?User $user = null): void

Always call this for: create customer, update customer, delete customer, recharge, apply payment, suspend, terminate, change package, change dealer.

RouterOsService

Location: app/Services/RouterOsService.php

Purpose: Polls MikroTik for the count of active PPPoE sessions. Result cached in Redis with a short TTL. Used by DashboardController for the "Online Now" stat.


Scheduled Commands

All scheduled commands are defined in routes/console.php (DB-driven schedule, not kernel). The system cron runs php artisan schedule:run every minute.

Command Frequency Purpose
sync:all-radius Configurable Full RADIUS sync for all customers
sync:ont-assignments Every 5 minutes OLT ONT polling, rx_power update
poll:olt-onts Every 10 minutes OLT device health check
radius:close-ghost-sessions Every 30 minutes Close stuck radacct sessions
send:expiry-reminders Daily WhatsApp reminders to expiring customers
generate:monthly-invoices Manual / 1st of month Bulk invoice generation
backup:run Daily Database + storage backup

Known Issues (as of 2026-07-13)

Address these when relevant — do not proactively fix without user request.

Open Issues

  1. CustomerLedgerTab.jsx missing internal permission gate
  2. File: resources/js/Pages/Customers/CustomerLedgerTab.jsx
  3. Issue: The ledger tab is visible to dealers who do not have billing.ledger permission
  4. Risk: Low (view-only, no mutations)
  5. Fix needed: Add {can('billing.ledger') && <LedgerTab />} gate

  6. TR-069 integration incomplete

  7. ACS_ENABLED=false in production .env
  8. TR-069 pages in frontend are partially built and not gated
  9. Risk: Medium — when ACS_ENABLED is turned on, pages need proper permission gates and completion
  10. Fix needed: Complete frontend pages, add permission gates, test with real ACS

  11. No automated tests

  12. Zero PHPUnit or Playwright tests exist
  13. Risk: Critical — any regression ships undetected
  14. Fix needed: See 30_Testing.md for full strategy

  15. No staging environment

  16. All changes deploy directly to production
  17. Risk: Critical — migrations and major changes are untested before production
  18. Fix needed: Provision a staging VPS, see 31_Deployment.md

  19. Billing/Dashboard.jsx minor permission gaps

  20. Some billing stats visible to users without billing permissions
  21. Risk: Low (read-only stats)
  22. Fix needed: Audit the component and add can() checks

  23. .bak files in production code directory

  24. CustomerController.php.bak_perf, Setting.php.bak, User.php.bak
  25. Risk: None (not loaded by PHP, no runtime impact)
  26. Fix needed: rm the files and commit the deletion

Recently Fixed Issues (Historical Reference)

Know these so you do not re-introduce the same bugs.

2026-07-13 — Expiry Date Edit Bug (Lock Contention)

Problem: Editing a customer's expiry date appeared to succeed but the value reverted. Root cause: SyncOntAssignments ran concurrently, acquired a row lock on customers, causing 55P03 lock timeout on the save, and the controller silently failed or retried with stale data.

Fix applied: - SyncOntAssignments now uses IS DISTINCT FROM guard: only writes rx_power to ont_assignments when the value has actually changed, drastically reducing lock duration - Functional indexes added to ont_assignments to speed up the sync query - CustomerController@update has a retry loop (3 attempts, 100ms sleep) to handle transient lock timeouts

Do not: Remove the IS DISTINCT FROM guard. Do not remove the retry loop.


2026-07-13 — CIR Dealer Classification

Problem: CIR (Corporate/Committed Rate) customers were being managed by dealers classified as sub_dealer role. This broke scoping and permission checks.

Fix applied: CIR dealers were reassigned to dealer role. CIR connection type (connection_type = 3) is now consistently handled at the dealer level.


2026-07-13 — Batch 4 Permission Gates

Problem: Archive/Billing pages and Notifications pages were accessible to any authenticated user, including dealers without explicit permissions.

Fix applied: Added abort_unless permission checks in the relevant controllers and can() gates in the corresponding React components for Archive, Billing overview, and Notifications pages.


AI Assistant Guidelines

Follow these guidelines every time you assist with PyroRadius development.

Before Writing Any Code

  1. Identify the user role context. Will this code be called by a dealer? If yes, it needs dealer scoping and permission checks.
  2. Check if the feature touches billing. If yes, ensure idempotency and add audit logging.
  3. Check if the feature writes to RADIUS. If yes, ensure RadiusService::syncCustomer is called after any change to pppoe_username, pppoe_password, package_id, or status.
  4. Check if the feature is a sync command. If yes, add IS DISTINCT FROM guard to avoid unnecessary writes and lock contention.

Before Diagnosing Any Issue

  1. Always ask: "Has the user checked laravel.log?" If not, check it first:
    tail -n 100 /var/www/pyroradius/storage/logs/laravel.log
    
  2. Always ask: "Are queue workers running?" Check:
    supervisorctl status all
    
  3. If the issue involves customer data not updating, suspect SyncOntAssignments lock contention — this is the most common root cause.

Before Suggesting a Migration

  1. Write the migration as additive. Never drop a column. If renaming, add the new column and migrate data; deprecate the old column later.
  2. Always add the corresponding down() method even if it just reverses the up().
  3. Remind the user to review the migration file before running php artisan migrate.
  4. Do not run the migration yourself without explicit user confirmation.

Common Pitfalls to Avoid

Pitfall Why it matters
Forgetting dealer_id scope in a query Other dealers' data leaks
Hashing pppoe_password Breaks FreeRADIUS authentication for all affected customers
Not calling RadiusService::syncCustomer after package or credential change Customer gets wrong speed or cannot connect
Using firstOrNew instead of firstOrCreate for invoices Can create duplicate invoices under race conditions
Not calling AuditLogger::log on a mutation Compliance gap; admin cannot trace who did what
Running php artisan optimize without optimize:clear first Old cached routes/config persist alongside new code
Pushing to master directly Bypasses code review; risk of untested code in production
Using NOW() in a migration instead of CURRENT_TIMESTAMP or Eloquent timestamps() Timezone inconsistency
Adding a new route without checking the permission gate Feature accessible to all roles by default

How to Look Up the Controller for a URL

# Find route
cd /var/www/pyroradius
php artisan route:list | grep "customers/recharge"
# OR search routes file
grep -n "recharge" routes/web.php routes/api.php

How to Look Up a Setting

cd /var/www/pyroradius
php artisan tinker
>>> \App\Models\Setting::where('key', 'whatsapp_enabled')->value('value')

How to Test a Service Method Quickly

cd /var/www/pyroradius
php artisan tinker
>>> $customer = \App\Models\Customer::find(<id>);
>>> app(\App\Services\BillingService::class)->recharge($customer, 2000, 'cash');

Standard Code Patterns

Controller action with permission gate + audit log:

public function recharge(Request $request, Customer $customer)
{
    $user = auth()->user();
    if (!in_array($user->role, ['super_admin', 'admin'])) {
        abort_unless($user->permissions['billing.recharge'] ?? false, 403);
    }

    // Scope check
    if (!in_array($user->role, ['super_admin', 'admin'])) {
        abort_if($customer->dealer_id !== $user->dealer_id, 403);
    }

    $amount = $request->validated()['amount'];
    $this->billingService->recharge($customer, $amount, $request->validated()['method']);

    AuditLogger::log('customer.recharge', $customer, [], ['amount' => $amount], $user);

    return back()->with('success', 'Customer recharged successfully.');
}

React permission gate:

const { auth } = usePage().props;
const isAdmin = ['super_admin', 'admin'].includes(auth.user.role);
const can = (key) => isAdmin || !!(auth?.user?.permissions?.[key]);

// In JSX:
{can('billing.recharge') && (
  <button onClick={handleRecharge}>Recharge</button>
)}

Dealer-scoped Eloquent query:

$query = Customer::query();
if (!in_array(auth()->user()->role, ['super_admin', 'admin'])) {
    if (auth()->user()->role === 'dealer') {
        $query->where('dealer_id', auth()->user()->id);
    } elseif (auth()->user()->role === 'sub_dealer') {
        $query->where('sub_dealer_id', auth()->user()->id);
    }
}
return $query->with(['package', 'dealer'])->paginate(25);

Idempotent invoice creation:

$invoice = Invoice::firstOrCreate(
    ['customer_id' => $customer->id, 'period' => $period],
    [
        'amount'         => $customer->package->price,
        'invoice_number' => Invoice::generateNumber($period),
        'due_date'       => Carbon::parse($period)->endOfMonth(),
        'paid'           => false,
    ]
);

RADIUS sync after customer update:

// After any change to pppoe_username, pppoe_password, package_id, or status:
$this->radiusService->syncCustomer($customer->fresh());


Environment Variables Quick Reference

Variable Production Value Notes
APP_ENV production Never change on production
APP_KEY base64:... Never change or regenerate on production
DB_CONNECTION pgsql Main DB
DB_DATABASE pyroradius Main DB name
RADIUS_DB_CONNECTION radius RADIUS DB connection name
RADIUS_DB_DATABASE radius RADIUS DB name
CACHE_DRIVER redis Do not change
SESSION_DRIVER redis Do not change
QUEUE_CONNECTION redis Do not change
SESSION_LIFETIME 480 Minutes (8 hours)
WHATSAPP_ENABLED true Set false to disable all WhatsApp
ACS_ENABLED false TR-069 not active in production
MIKROTIK_API_HOST (actual IP) NAS/router IP
MIKROTIK_API_USER (actual user) RouterOS API user
MIKROTIK_API_PASSWORD (actual password) RouterOS API password
BROADCAST_DRIVER log or pusher For real-time if configured

Documentation Map

For deeper reading on any topic, see these files (all in the same directory as this file):

Topic File
Project overview 00_Project_Overview.md
System architecture 01_System_Architecture.md
Server setup 02_Installation_Guide.md
Database schema 03_Database_Architecture.md
All routes 06_Routing_Map.md
Frontend / React 07_Frontend_Architecture.md
Backend / Services 08_Backend_Architecture.md
API endpoints 09_API_Documentation.md
Billing logic 10_Billing_System.md
Customer management 12_Customer_Management.md
Dealer system 13_Dealer_System.md
NAS / RADIUS 14_NAS_Management.md
MikroTik 15_MikroTik.md
TR-069 16_TR069.md
WhatsApp 18_WhatsApp_System.md
Notifications 19_Notification_System.md
Campaigns 20_Campaign_System.md
Reports 21_Reports.md
Dashboard 22_Dashboard.md
Health checks 24_Health_System.md
Settings 25_Settings.md
Background jobs 26_Background_Jobs.md
Data imports 27_Imports.md
Security & audit 28_Security_Audit.md
Testing strategy 30_Testing.md
Deployment 31_Deployment.md
Troubleshooting 32_Troubleshooting.md
Future roadmap 33_Future_Roadmap.md