PyroRadius — Installation Guide¶
Version: 1.0 (Documentation) Last Updated: 2026-07-13
Table of Contents¶
- Server Requirements
- Pre-Installation Checklist
- Step-by-Step Installation
- Environment Variables Reference
- FreeRADIUS Integration
- Nginx Configuration
- Supervisor Configuration
- Post-Installation Checklist
- Upgrade Procedure
- Common Installation Errors and Fixes
Server Requirements¶
Minimum Hardware¶
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU |
| RAM | 4 GB | 8 GB |
| Disk | 40 GB SSD | 100 GB SSD |
| Network | 100 Mbps | 1 Gbps |
Operating System¶
- Ubuntu 22.04 LTS (tested and supported)
- Ubuntu 24.04 LTS (compatible, test before production use)
- Debian 12 (compatible with minor adjustments)
Required Software Versions¶
| Software | Required Version | Installation Method |
|---|---|---|
| PHP | 8.3 | Ondrej PPA / Ubuntu package |
| Nginx | 1.18+ | Ubuntu package |
| PostgreSQL | 16 | PostgreSQL PGDG repo |
| Redis | 7.x | Ubuntu package |
| Node.js | 20 LTS | NodeSource or nvm |
| npm | 10+ | Bundled with Node 20 |
| Composer | 2.x | Direct install |
| Supervisor | 4.x | Ubuntu package |
| FreeRADIUS | 3.2+ | Ubuntu package (for RADIUS integration) |
| Git | 2.x | Ubuntu package |
Required PHP Extensions¶
php8.3-fpm php8.3-cli php8.3-pgsql php8.3-redis php8.3-mbstring
php8.3-xml php8.3-curl php8.3-zip php8.3-gd php8.3-intl
php8.3-bcmath php8.3-pcntl php8.3-snmp php8.3-opcache
Pre-Installation Checklist¶
Before starting the installation, confirm:
- [ ] Ubuntu server is freshly provisioned with root or sudo access
- [ ] Domain or IP is pointed correctly (or will use IP directly)
- [ ] SSH key-based access configured for the deployment user
- [ ] PostgreSQL 16 is available (can be on same server or remote)
- [ ] FreeRADIUS is installed and its configuration can be modified
- [ ] Git repository access is available (credentials or deploy key)
- [ ] Redis is accessible (local or remote)
- [ ] All required ports are open in firewall: 80, 443 (web); 8728 (RouterOS API outbound); 1812/1813 UDP (RADIUS inbound if co-located)
- [ ] SSL certificate is ready (Let's Encrypt recommended) or will use self-signed for testing
- [ ]
.envvalues are collected (DB credentials, app key source, mail settings, etc.)
Step-by-Step Installation¶
Step 1: Update System and Install Base Packages¶
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git unzip supervisor nginx software-properties-common gnupg2
Step 2: Install PHP 8.3¶
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y php8.3-fpm php8.3-cli php8.3-pgsql php8.3-redis \
php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-gd \
php8.3-intl php8.3-bcmath php8.3-pcntl php8.3-snmp php8.3-opcache
# Verify
php -v
# Should output: PHP 8.3.x ...
Step 3: Install PostgreSQL 16¶
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list'
wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc >/dev/null
sudo apt update
sudo apt install -y postgresql-16 postgresql-client-16 postgresql-contrib-16
# Start and enable
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Verify
psql --version
# Should output: psql (PostgreSQL) 16.x
Step 4: Install Redis 7¶
sudo apt install -y redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server
# Configure Redis to bind to localhost only (security)
sudo sed -i 's/^bind 127.0.0.1 ::1/bind 127.0.0.1/' /etc/redis/redis.conf
sudo systemctl restart redis-server
# Verify
redis-cli ping
# Should output: PONG
Step 5: Install Node.js 20¶
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node -v # Should output: v20.x.x
npm -v # Should output: 10.x.x
Step 6: Install Composer 2¶
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
# Verify
composer --version
# Should output: Composer version 2.x.x
Step 7: Create Database and User¶
sudo -u postgres psql <<EOF
CREATE USER pyroradius WITH PASSWORD 'YOUR_STRONG_DB_PASSWORD';
CREATE DATABASE pyroradius OWNER pyroradius;
GRANT ALL PRIVILEGES ON DATABASE pyroradius TO pyroradius;
-- FreeRADIUS will also use this database
-- Either use the same pyroradius user or create a separate radius user
EOF
Step 8: Clone the Repository¶
sudo mkdir -p /var/www/pyroradius
sudo chown www-data:www-data /var/www/pyroradius
# If deploying as www-data:
sudo -u www-data git clone git@github.com:pyronet/pyroradius.git /var/www/pyroradius
# Or as current user then fix permissions:
git clone git@github.com:pyronet/pyroradius.git /var/www/pyroradius
sudo chown -R www-data:www-data /var/www/pyroradius
Step 9: Install PHP Dependencies (Composer)¶
cd /var/www/pyroradius
sudo -u www-data composer install --no-dev --optimize-autoloader --no-interaction
# Key packages installed:
# - inertiajs/inertia-laravel ^2.0
# - laravel/sanctum ^4.0
# - dompdf/dompdf ^3.1
# - evilfreelancer/routeros-api-php ^1.7
# - phpoffice/phpspreadsheet ^5.7
# - tightenco/ziggy ^2.0
Step 10: Configure Environment File¶
cd /var/www/pyroradius
sudo -u www-data cp .env.example .env
# Edit the .env file (see Environment Variables Reference section below)
sudo -u www-data nano .env
Step 11: Generate Application Key¶
cd /var/www/pyroradius
sudo -u www-data php artisan key:generate
# This sets APP_KEY in .env. Must be kept secret and consistent across deployments.
Step 12: Run Database Migrations¶
cd /var/www/pyroradius
sudo -u www-data php artisan migrate --force
# The --force flag is required for production environments
# This creates all tables: users, customers, invoices, radcheck, etc.
Step 13: Seed Initial Data¶
cd /var/www/pyroradius
sudo -u www-data php artisan db:seed --force
# Seeds: default super_admin user, default settings, connection types,
# payment methods, default company profile, etc.
Step 14: Create Storage Symlink¶
cd /var/www/pyroradius
sudo -u www-data php artisan storage:link
# Creates: public/storage → storage/app/public
Step 15: Install Node.js Dependencies¶
cd /var/www/pyroradius
sudo -u www-data npm install
# Installs: react, @inertiajs/react, tailwindcss, recharts, lucide-react, vite, etc.
Step 16: Build Frontend Assets¶
cd /var/www/pyroradius
sudo -u www-data npm run build
# Runs Vite build, outputs to public/build/
# Generates public/build/manifest.json (required by @vite Blade directive)
Step 17: Optimize Laravel for Production¶
cd /var/www/pyroradius
sudo -u www-data php artisan optimize
# Caches: config, routes, events, views
# Speeds up application significantly in production
Step 18: Set Correct Permissions¶
sudo chown -R www-data:www-data /var/www/pyroradius
sudo chmod -R 755 /var/www/pyroradius
sudo chmod -R 775 /var/www/pyroradius/storage
sudo chmod -R 775 /var/www/pyroradius/bootstrap/cache
Environment Variables Reference¶
The .env file at /var/www/pyroradius/.env controls all runtime configuration.
Application Settings¶
| Key | Description | Example |
|---|---|---|
APP_NAME |
Application name shown in UI | PyroRadius |
APP_ENV |
Environment: local or production |
production |
APP_KEY |
32-byte encryption key (generated by artisan) | base64:... |
APP_DEBUG |
Show detailed errors. Must be false in production |
false |
APP_URL |
Full application URL including scheme | https://your-server-ip |
APP_TIMEZONE |
PHP timezone for all date operations | Asia/Karachi |
Database Settings¶
| Key | Description | Example |
|---|---|---|
DB_CONNECTION |
Must be pgsql |
pgsql |
DB_HOST |
PostgreSQL host | 127.0.0.1 |
DB_PORT |
PostgreSQL port | 5432 |
DB_DATABASE |
Database name | pyroradius |
DB_USERNAME |
PostgreSQL user | pyroradius |
DB_PASSWORD |
PostgreSQL password | your_strong_password |
Cache and Session Settings¶
| Key | Description | Example |
|---|---|---|
CACHE_DRIVER |
Cache backend | redis |
SESSION_DRIVER |
Session backend | redis |
SESSION_LIFETIME |
Session lifetime in minutes | 120 |
REDIS_HOST |
Redis host | 127.0.0.1 |
REDIS_PORT |
Redis port | 6379 |
REDIS_PASSWORD |
Redis password (null if none) | null |
REDIS_DB |
Redis database index for cache | 0 |
REDIS_CACHE_DB |
Redis database index for sessions | 1 |
Queue Settings¶
| Key | Description | Example |
|---|---|---|
QUEUE_CONNECTION |
Queue backend | redis |
QUEUE_FAILED_DRIVER |
Failed job storage | database |
Mail Settings (Secondary notification channel)¶
| Key | Description | Example |
|---|---|---|
MAIL_MAILER |
Mail driver | smtp |
MAIL_HOST |
SMTP host | mail.pyronet.com.pk |
MAIL_PORT |
SMTP port | 587 |
MAIL_USERNAME |
SMTP username | noreply@pyronet.com.pk |
MAIL_PASSWORD |
SMTP password | your_smtp_password |
MAIL_ENCRYPTION |
TLS/SSL | tls |
MAIL_FROM_ADDRESS |
From address | noreply@pyronet.com.pk |
MAIL_FROM_NAME |
From display name | PyroRadius |
WhatsApp / OpenWA Settings¶
| Key | Description | Example |
|---|---|---|
WHATSAPP_DRIVER |
openwa or log |
openwa |
OPENWA_BASE_URL |
OpenWA session HTTP endpoint | http://localhost:8002 |
OPENWA_API_KEY |
OpenWA API key (if configured) | your_openwa_key |
MikroTik / RADIUS Settings¶
| Key | Description | Example |
|---|---|---|
MIKROTIK_DEFAULT_PORT |
RouterOS API port | 8728 |
MIKROTIK_TIMEOUT |
API connection timeout (seconds) | 10 |
GenieACS / TR-069 Settings¶
| Key | Description | Example |
|---|---|---|
GENIEACS_URL |
GenieACS NBI URL | http://localhost:7557 |
GENIEACS_USERNAME |
GenieACS auth username | admin |
GENIEACS_PASSWORD |
GenieACS auth password | your_genieacs_password |
Logging Settings¶
| Key | Description | Example |
|---|---|---|
LOG_CHANNEL |
Log channel | daily |
LOG_LEVEL |
Minimum log level | error |
LOG_DEPRECATIONS_CHANNEL |
Deprecation log channel | null |
Sanctum Settings¶
| Key | Description | Example |
|---|---|---|
SANCTUM_STATEFUL_DOMAINS |
Domains for session-based auth | your-server-ip,localhost |
Vite / Asset Settings¶
| Key | Description | Example |
|---|---|---|
VITE_APP_NAME |
App name accessible in React | PyroRadius |
FreeRADIUS Integration¶
FreeRADIUS must be configured to use the PostgreSQL database (same database as PyroRadius).
Install FreeRADIUS¶
Configure PostgreSQL Module¶
Edit /etc/freeradius/3.0/mods-available/sql:
sql {
driver = "rlm_sql_postgresql"
dialect = "postgresql"
server = "127.0.0.1"
port = 5432
login = "pyroradius"
password = "YOUR_DB_PASSWORD"
radius_db = "pyroradius"
# Table configuration (must match PyroRadius migrations)
authcheck_table = "radcheck"
authreply_table = "radreply"
groupcheck_table = "radgroupcheck"
groupreply_table = "radgroupreply"
usergroup_table = "radusergroup"
read_groups = yes
acct_table1 = "radacct"
acct_table2 = "radacct"
postauth_table = "radpostauth"
}
Enable SQL Module¶
sudo ln -s /etc/freeradius/3.0/mods-available/sql /etc/freeradius/3.0/mods-enabled/sql
sudo systemctl restart freeradius
Configure NAS Clients¶
Add MikroTik NAS devices to /etc/freeradius/3.0/clients.conf:
client mikrotik_nas_1 {
ipaddr = 192.168.1.1 # MikroTik IP
secret = your_radius_secret
shortname = NAS-1
nastype = other
}
The RADIUS secret here must match the
secretcolumn in thenastable in PyroRadius.
Nginx Configuration¶
Create /etc/nginx/sites-available/pyroradius:
server {
listen 80;
server_name your-server-ip;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-server-ip;
root /var/www/pyroradius/public;
index index.php;
# SSL Certificate (replace with your actual certificate paths)
ssl_certificate /etc/ssl/certs/pyroradius.crt;
ssl_certificate_key /etc/ssl/private/pyroradius.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# Vite-built assets: cache aggressively (content-hash filenames)
location /build/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 120;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
location ~ /\.(?!well-known).* {
deny all;
}
# Upload size (for Excel import files)
client_max_body_size 50M;
access_log /var/log/nginx/pyroradius-access.log;
error_log /var/log/nginx/pyroradius-error.log;
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/pyroradius /etc/nginx/sites-enabled/
sudo nginx -t # Test configuration
sudo systemctl reload nginx
Supervisor Configuration¶
Create /etc/supervisor/conf.d/pyroradius-worker.conf:
[program:pyroradius-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/pyroradius/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/supervisor/pyroradius-worker.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10
stopwaitsecs=3600
[program:pyroradius-scheduler]
command=php /var/www/pyroradius/artisan schedule:work
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/supervisor/pyroradius-scheduler.log
Apply Supervisor configuration:
sudo mkdir -p /var/log/supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start all
sudo supervisorctl status
Post-Installation Checklist¶
After completing all installation steps, verify the following:
- [ ] Application loads — open browser to
https://your-server-ip, login page appears - [ ] Login works — log in with
admin@pyronet.com.pk/change-me-on-first-login - [ ] Dashboard renders — no blank page, no console errors
- [ ] Change default password — go to Profile → Change Password immediately
- [ ] Queue workers running —
sudo supervisorctl statusshows all workersRUNNING - [ ] Redis connected —
php artisan tinker→Cache::set('test', 1)→Cache::get('test')returns 1 - [ ] Database connected — can create a test customer without errors
- [ ] RADIUS tables populated — check that
radchecktable is accessible and FreeRADIUS auth works for a test PPPoE user - [ ] WhatsApp driver configured — set
WHATSAPP_DRIVER=loginitially, then switch toopenwawhen session is ready - [ ] Storage symlink —
ls -la /var/www/pyroradius/public/storageshows symlink - [ ] Nginx error log clean —
sudo tail -50 /var/log/nginx/pyroradius-error.logshows no errors - [ ] Laravel log clean —
tail -50 /var/www/pyroradius/storage/logs/laravel.logshows no critical errors - [ ] Cron jobs table — set up initial cron jobs from the Health page in the admin UI
- [ ] Company profile — go to Settings → Company Profile and enter PyroNet/Indus Broadband details
- [ ] Packages created — add at least one internet package before creating customers
- [ ] NAS devices added — add MikroTik NAS devices (Network → NAS) with correct API credentials
- [ ] Test RADIUS auth —
radtest testuser testpass 127.0.0.1 0 your_radius_secretreturns Access-Accept for a synced customer
Upgrade Procedure¶
When a new version is deployed from the git repository, follow this exact sequence to avoid downtime and data loss:
# 1. Pull latest code
cd /var/www/pyroradius
sudo -u www-data git pull origin main
# 2. Install/update PHP dependencies
sudo -u www-data composer install --no-dev --optimize-autoloader --no-interaction
# 3. Install/update Node.js dependencies
sudo -u www-data npm install
# 4. Build frontend assets
sudo -u www-data npm run build
# 5. Run any new database migrations
sudo -u www-data php artisan migrate --force
# 6. Clear all caches
sudo -u www-data php artisan optimize:clear
# 7. Re-cache for production
sudo -u www-data php artisan optimize
# 8. Restart PHP-FPM (picks up new PHP files)
sudo systemctl restart php8.3-fpm
# 9. Restart queue workers (picks up new Job classes)
sudo supervisorctl restart pyroradius-worker:*
# 10. Verify application is working
sudo tail -20 /var/www/pyroradius/storage/logs/laravel.log
Always test upgrades on a staging environment before applying to production. Never run
migrate:freshon production — it drops all data.
Pre-Upgrade Safety Steps¶
# Take a database backup before any upgrade
sudo -u postgres pg_dump pyroradius > /var/backups/pyroradius_pre_upgrade_$(date +%Y%m%d).sql
Common Installation Errors and Fixes¶
Error: SQLSTATE[08006] could not connect to server¶
Cause: PostgreSQL not running or wrong credentials.
Fix:
sudo systemctl status postgresql
sudo -u postgres psql -c "\l" # List databases
# Verify DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD in .env
Error: php artisan migrate fails with relation already exists¶
Cause: Partial previous migration run.
Fix:
php artisan migrate:status # Check which migrations ran
# For a fresh install, if the DB is truly empty:
php artisan migrate:fresh --force # WARNING: destroys all data
# For production with existing data, identify and skip the conflicting migration
Error: Blank white page after deploy¶
Cause: Usually a missing APP_KEY, failed optimize, or JavaScript build not present.
Fix:
# Check APP_KEY is set
grep APP_KEY /var/www/pyroradius/.env
# Check public/build/ exists
ls /var/www/pyroradius/public/build/
# Clear caches and re-optimize
php artisan optimize:clear && php artisan optimize
# Check Laravel log
tail -50 /var/www/pyroradius/storage/logs/laravel.log
Error: npm run build fails with Vite error¶
Cause: Node.js version mismatch or missing node_modules.
Fix:
Error: Queue jobs not processing¶
Cause: Supervisor workers not running.
Fix:
sudo supervisorctl status
sudo supervisorctl start pyroradius-worker:*
# Check Redis connection
redis-cli ping
# Check queue
php artisan queue:monitor redis:default
Error: Permission denied on storage or cache¶
Cause: Wrong file ownership after git pull.
Fix:
sudo chown -R www-data:www-data /var/www/pyroradius/storage
sudo chown -R www-data:www-data /var/www/pyroradius/bootstrap/cache
sudo chmod -R 775 /var/www/pyroradius/storage
sudo chmod -R 775 /var/www/pyroradius/bootstrap/cache
Error: FreeRADIUS authentication failing¶
Cause: SQL module not enabled, wrong table names, or credential mismatch.
Fix:
# Test FreeRADIUS SQL connection
sudo freeradius -X 2>&1 | grep -i sql
# Test RADIUS auth manually
radtest pppoeuser password 127.0.0.1 0 radius_secret
# Verify radcheck has the user
sudo -u postgres psql pyroradius -c "SELECT * FROM radcheck WHERE username='pppoeuser';"
Error: Target class [CheckPermission] does not exist¶
Cause: Config cache is stale after adding new middleware.
Fix:
Error: Inertia page shows JSON instead of rendered UI¶
Cause: X-Inertia header is present on a request that returns a redirect or plain response.
Fix: This is typically a server configuration issue. Ensure Nginx is passing the full PHP response including all headers. Check that mod_headers equivalent is not stripping Inertia response headers.
For architecture context, see 01_System_Architecture.md. For database schema, see 03_Database_Architecture.md.