- TypeScript 87.9%
- Go 5.5%
- Shell 4.1%
- CSS 0.9%
- HTML 0.8%
- Other 0.8%
| .github/workflows | ||
| baileys_whatsapp_patch | ||
| bin | ||
| cmd/server | ||
| docs | ||
| freeradius-config | ||
| internal | ||
| prisma | ||
| production | ||
| public | ||
| scripts | ||
| src | ||
| tests | ||
| vps-install | ||
| .air.toml | ||
| .env.example | ||
| .env.production.example | ||
| .gitattributes | ||
| .gitignore | ||
| audit_routing.sh | ||
| CHANGELOG.md | ||
| check_admin.py | ||
| check_config.py | ||
| components.json | ||
| cron-service.js | ||
| deploy-restart.sh | ||
| docker-compose.genieacs.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| next.config.ts | ||
| nginx-frontend.conf | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| test_login.sh | ||
| test_otp.py | ||
| tsconfig.json | ||
| verify_session.py | ||
| vitest.config.ts | ||
| vpn-watchdog.sh | ||
| vps_audit.py | ||
| wa-service.js | ||
| ZTE_OID_TABLE.md | ||
SALFANET RADIUS - Billing System for ISP/RTRW.NET
Modern, full-stack billing & RADIUS management system for ISP/RTRW.NET with FreeRADIUS integration supporting PPPoE and Hotspot authentication.
Latest: v2.25.2 — Native Baileys WhatsApp gateway built-in di VPS, QR modal auto-retry, auto-reconnect setelah device disconnect (Apr 26, 2026)
🤖 AI Development Assistant
READ FIRST: docs/AI_PROJECT_MEMORY.md — contains full architecture, VPS details, DB schema, known issues, and proven solutions.
🎯 Features
| Category | Key Capabilities |
|---|---|
| RADIUS / Auth | FreeRADIUS 3.0.26, PAP/CHAP/MS-CHAP, VPN L2TP/IPSec, PPPoE & Hotspot, CoA real-time speed/disconnect |
| VPN Management | MikroTik CHR via API, VPS built-in WireGuard & L2TP/IPsec peer management, configurable IP pool & gateway per protocol, auto-generated RouterOS scripts |
| PPPoE Management | Customer accounts, profile-based bandwidth, isolation, IP assignment, MikroTik auto-sync, foto KTP+instalasi via kamera HP, GPS otomatis |
| Hotspot Voucher | 8 code types, batch up to 25,000, agent distribution, auto-sync with RADIUS, print templates |
| Billing | Postpaid/prepaid invoices, auto-generation, payment reminders, balance/deposit, auto-renewal |
| Payment | Manual upload (bukti transfer), Midtrans/Xendit/Duitku gateway, approval workflow, 0–5 bank accounts |
| Notifications | WhatsApp (Fonnte/WAHA/GOWA/MPWA/Wablas/WABlast/Kirimi.id/Baileys native), Email SMTP, broadcast (outage/invoice/payment), webhook pesan masuk |
| Agent/Reseller | Balance-based voucher generation, commission tracking, sales stats |
| Financial | Income/expense tracking with categories, keuangan reconciliation |
| Network (FTTH) | OLT/ODC/ODP management, customer port assignment, network map, distance calculation |
| GenieACS TR-069 | CPE/ONT management, WiFi config (SSID/password), device status & uptime |
| Isolation | Auto-isolate expired customers, customizable WhatsApp/Email/HTML landing page templates |
| Cron Jobs | 16 automated background jobs (tsx runner via PM2 fork), history, distributed locking, manual trigger |
| Roles & Permissions | 53 permissions, 5 portals (Admin/Customer/Agent/Technician + SuperAdmin) |
| Activity Log | Audit trail with auto-cleanup (30 days) |
| Security | Session timeout 30 min, idle warning, RBAC, HTTPS/SSL |
| Bahasa | Bahasa Indonesia (full) |
| PWA | Installable di semua portal (admin, customer, agent, technician), offline fallback, service worker cache |
| Web Push | VAPID-based browser push notifications, subscribe/unsubscribe toggle, admin broadcast |
| System Update | Update via SSH menggunakan updater.sh, tidak ada web-based update |
| Mobile App | Flutter customer portal (WiFi control, invoice, payment) |
| WhatsApp Baileys | Native WhatsApp gateway built-in VPS via @whiskeysockets/baileys, PM2 proses terpisah, scan QR langsung di admin panel, auto-reconnect |
📱 WhatsApp Baileys (Native Gateway)
Provider WhatsApp bawaan tanpa layanan pihak ketiga. Berjalan sebagai proses PM2 terpisah (salfanet-wa) di VPS.
Setup
Provider Baileys otomatis di-setup saat menjalankan updater.sh. Tidak ada konfigurasi tambahan.
# Cek status wa-service
pm2 status
pm2 logs salfanet-wa --lines 20
Cara Pakai
- Buka Admin → Pengaturan → WhatsApp → Penyedia
- Klik + Tambah Provider, pilih tipe Baileys
- Klik QR Code → scan dengan HP (WhatsApp → Linked Devices)
- Setelah scan berhasil, modal menampilkan centang hijau konfirmasi
- Provider siap digunakan untuk kirim notifikasi
PM2 Processes
| Process | Mode | Port | Purpose |
|---|---|---|---|
salfanet-radius |
cluster | 3000 | Next.js app |
salfanet-wa |
fork | 4000 (internal) | Baileys WA service |
salfanet-cron |
fork | — | Background jobs |
Auth Session
Session WhatsApp tersimpan di /var/data/salfanet/baileys_auth/ dan persist meski PM2 restart. Untuk logout/scan ulang, klik Restart Session di admin panel.
🚀 Tech Stack
| Component | Technology |
|---|---|
| Framework | Next.js 16 (App Router, standalone output) |
| Language | TypeScript |
| Styling | Tailwind CSS |
| Database | MySQL 8.0 + Prisma ORM |
| RADIUS | FreeRADIUS 3.0.26 |
| Process Manager | PM2 (cluster × 2) |
| Session Tracking | FreeRADIUS radacct (real-time) |
| Maps | Leaflet / OpenStreetMap |
📁 Project Structure
salfanet-radius/
├── src/
│ ├── app/
│ │ ├── admin/ # Admin panel
│ │ ├── agent/ # Agent/reseller portal
│ │ ├── api/ # API route handlers
│ │ ├── customer/ # Customer self-service portal
│ │ └── technician/ # Technician portal
│ ├── server/ # DB, services, jobs, cache, auth
│ ├── features/ # Vertical slices (queries, schemas, types)
│ ├── components/ # Shared React components
│ ├── locales/ # i18n translations (id, en)
│ └── types/ # Shared TypeScript types
├── prisma/
│ ├── schema.prisma # Database schema (~45 models)
│ └── seeds/ # Seed scripts
├── freeradius-config/ # FreeRADIUS config (deployed by installer)
├── vps-install/ # One-command VPS installer scripts
├── production/ # PM2 & Nginx config templates
├── mobile-app/ # Flutter customer app
├── scripts/ # Utility & tuning scripts
└── docs/ # Documentation & AI memory
⚙️ Installation
Metode 1 — Git Clone (Recommended)
ssh root@YOUR_VPS_IP
git clone https://github.com/s4lfanet/salfanet-radius.git /root/salfanet-radius
cd /root/salfanet-radius
bash vps-install/vps-installer.sh
Installer akan berjalan interaktif — mendeteksi environment otomatis, memandu konfigurasi, lalu menjalankan semua step.
Metode 2 — Upload Manual via SCP (Tanpa Akses Internet di Server)
# Jalankan di terminal LOKAL (bukan di server)
scp -r ./salfanet-radius root@YOUR_VPS_IP:/root/salfanet-radius
# SSH ke server, lalu jalankan installer
ssh root@YOUR_VPS_IP
cd /root/salfanet-radius
bash vps-install/vps-installer.sh
Environment yang Didukung
| Environment | Flag | Akses |
|---|---|---|
| Public VPS (DigitalOcean, Vultr, Hetzner, AWS) | --env vps |
Internet |
| Proxmox LXC | --env lxc |
LAN/VLAN |
| Proxmox VM / VirtualBox | --env vm |
LAN |
| Bare Metal / Server Fisik | --env bare |
LAN |
# Contoh: paksa environment + IP
bash vps-install/vps-installer.sh --env lxc --ip 192.168.1.50
Updating Existing Installation
Cara paling aman. Semua data upload (logo, foto KTP pelanggan, bukti bayar) otomatis dipreservasi.
bash /var/www/salfanet-radius/vps-install/updater.sh
Atau update dari branch terbaru secara manual:
cd /var/www/salfanet-radius
git pull origin master
npm install --legacy-peer-deps
npx prisma db push
npm run build
pm2 reload all
Lihat detail lengkap di vps-install/README.md.
Data yang Aman Saat Update
| Data | Status |
|---|---|
Logo perusahaan (public/uploads/logos/) |
✅ Dipreservasi |
| Foto KTP & dokumen pelanggan | ✅ Dipreservasi |
| Bukti pembayaran | ✅ Dipreservasi |
File .env (database, secrets) |
✅ Tidak disentuh |
| Database MySQL (semua data pelanggan) | ✅ Tidak disentuh |
Default Credentials
| Admin URL | http://YOUR_VPS_IP/admin/login |
| Username | superadmin |
| Password | admin123 |
⚠️ Ganti password segera setelah login pertama!
🔌 FreeRADIUS
Key config files at /etc/freeradius/3.0/:
| File | Purpose |
|---|---|
mods-enabled/sql |
MySQL connection for user auth |
mods-enabled/rest |
REST API for voucher management |
sites-enabled/default |
Main auth logic (PPPoE realm support) |
clients.conf |
NAS/router clients (+ $INCLUDE clients.d/) |
sites-enabled/coa |
CoA/Disconnect-Request virtual server |
Config backup in freeradius-config/ is auto-deployed by the installer.
Auth Flow
PPPoE: MikroTik → FreeRADIUS → MySQL (radcheck/radusergroup/radgroupreply) → Access-Accept with Mikrotik-Rate-Limit
Hotspot Voucher: Same RADIUS path + REST /api/radius/post-auth → sets firstLoginAt, expiresAt, syncs keuangan
RADIUS Tables
| Table | Purpose |
|---|---|
radcheck |
User credentials |
radreply |
User-specific reply attrs |
radusergroup |
User → Group mapping |
radgroupreply |
Group reply (bandwidth, session timeout) |
radacct |
Session accounting |
nas |
NAS/Router clients (dynamic) |
⏰ Cron Jobs (16 automated)
| Job | Schedule | Function |
|---|---|---|
| Voucher Sync | Every 5 min | Sync voucher status with RADIUS |
| Disconnect Sessions | Every 5 min | CoA disconnect expired vouchers |
| Auto Isolir (PPPoE) | Every hour | Suspend overdue customers |
| FreeRADIUS Health | Every 5 min | Auto-restart if down |
| PPPoE Session Sync | Every 10 min | Sync radacct sessions |
| Agent Sales | Daily 1 AM | Update sales statistics |
| Invoice Generate | Daily 2 AM | Generate monthly invoices |
| Activity Log Cleanup | Daily 2 AM | Delete logs >30 days |
| Invoice Reminder | Daily 8 AM | Send payment reminders |
| Invoice Status | Daily 9 AM | Mark overdue invoices |
| Notification Check | Every 10 min | Process notification queue |
| Auto Renewal | Daily 8 AM | Prepaid auto-renew from balance |
| Webhook Log Cleanup | Daily 3 AM | Delete webhook logs >30 days |
| Session Monitor | Every 5 min | Security session monitoring |
| Cron History Cleanup | Daily 4 AM | Keep last 50 per job type |
| Suspend Check | Every hour | Activate/restore suspend requests |
All jobs can be triggered manually from Settings → Cron in the admin panel.
<EFBFBD> Android APK Builder
Buat APK Android (WebView wrapper) untuk 4 portal langsung di server VPS — tanpa GitHub Actions, tanpa Android Studio.
1) Setup Android SDK (satu kali via SSH)
apt-get update && apt-get install -y openjdk-17-jdk wget unzip && \
mkdir -p /opt/android/cmdline-tools && \
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdtools.zip && \
unzip -q /tmp/cmdtools.zip -d /opt/android/cmdline-tools && \
mv /opt/android/cmdline-tools/cmdline-tools /opt/android/cmdline-tools/latest && \
yes | /opt/android/cmdline-tools/latest/bin/sdkmanager --licenses && \
/opt/android/cmdline-tools/latest/bin/sdkmanager "platforms;android-34" "build-tools;34.0.0" && \
echo 'export ANDROID_HOME=/opt/android' >> /etc/environment && \
echo 'Selesai!'
Perkiraan waktu: ~5–10 menit (download ~500MB). Disk yang dibutuhkan: ~2GB.
2) Build APK via Admin Panel
Buka Admin → Download Aplikasi Android → klik Build APK pada role yang diinginkan.
- Build berjalan di background (tidak timeout meski butuh beberapa menit)
- Status diperbarui otomatis setiap 3 detik
- Setelah selesai, tombol Download APK muncul
3) Build via API (opsional)
# Cek environment
curl http://YOUR_VPS/api/admin/apk/trigger
# Mulai build (role: admin | customer | technician | agent)
curl -X POST http://YOUR_VPS/api/admin/apk/trigger?role=customer \
-H "Cookie: next-auth.session-token=..."
# Cek status
curl http://YOUR_VPS/api/admin/apk/status?role=customer
# Download APK
curl -OJ http://YOUR_VPS/api/admin/apk/file?role=customer \
-H "Cookie: next-auth.session-token=..."
Storage APK
| Path | Keterangan |
|---|---|
/var/data/salfanet/apk/{role}/app.apk |
File APK hasil build |
/var/data/salfanet/apk/{role}/status.json |
Status & metadata build |
/var/data/salfanet/apk/{role}/build.log |
Log Gradle |
/var/data/salfanet/gradle-cache |
Cache Gradle (mempercepat build berikutnya) |
Paket Aplikasi
| Role | Package ID | Warna |
|---|---|---|
| Admin | net.salfanet.admin |
Biru |
| Customer | net.salfanet.customer |
Cyan |
| Technician | net.salfanet.technician |
Hijau |
| Agent | net.salfanet.agent |
Ungu |
<EFBFBD>🛠️ Common Commands
# PM2
pm2 status ; pm2 logs salfanet-radius
pm2 restart ecosystem.config.js --update-env
# FreeRADIUS
systemctl restart freeradius
freeradius -XC # Test config
radtest 'user@realm' password 127.0.0.1 0 testing123
# Database
mysql -u salfanet_user -psalfanetradius123 salfanet_radius
mysqldump -u salfanet_user -psalfanetradius123 salfanet_radius > backup.sql
🧯 Troubleshooting Cepat
1) Website tidak bisa diakses dari IP VPS
Jika Nginx dan app sudah jalan di server tapi dari internet tetap tidak bisa akses, biasanya masalah ada di layer jaringan (NAT/forwarding/firewall external), bukan di aplikasi.
# Di VM/VPS guest
ss -tulpn | grep -E ':80|:443|:3000'
curl -I http://127.0.0.1:3000
curl -I http://127.0.0.1
systemctl status nginx --no-pager
pm2 status
Jika semua check local di atas OK, cek mapping di host Proxmox/router/cloud firewall:
Public:2020 -> VM:22(SSH)Public:80 -> VM:80(HTTP)Public:443 -> VM:443(HTTPS)
Catatan: IP:2020 adalah port SSH, bukan URL web aplikasi.
2) PM2 jalan tapi web tetap blank/error
pm2 status
pm2 logs salfanet-radius --lines 100
cd /var/www/salfanet-radius
npm run build
pm2 restart ecosystem.config.js --update-env
4) Jalankan diagnosa Nginx otomatis dari installer
Installer Nginx terbaru menambahkan self-check internal (127.0.0.1:3000, 127.0.0.1) dan best-effort check publik (HTTP/HTTPS).
cd /var/www/salfanet-radius
bash vps-install/install-nginx.sh
Jika warning menunjukkan HTTP publik tidak reachable, fokus perbaikan di NAT/port-forward/security-group, bukan di Next.js.
🔐 Security
# Firewall
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp
ufw allow 1812/udp && ufw allow 1813/udp && ufw allow 3799/udp
- Change default admin password on first login
- Change MySQL passwords in
.env - Configure SSL (Let's Encrypt or Cloudflare)
- Enable UFW
📡 CoA (Change of Authorization)
Sends real-time speed/disconnect commands to MikroTik without dropping PPPoE connections.
MikroTik requirement: /radius incoming set accept=yes port=3799
API: POST /api/radius/coa — actions: disconnect, update, sync-profile, test
Auto-triggered when: PPPoE profile speed is edited (syncs all active sessions).
📲 WhatsApp Providers
| Provider | Base URL | Auth |
|---|---|---|
| Fonnte | https://api.fonnte.com/send |
Token |
| WAHA | http://IP:PORT |
API Key |
| GOWA | http://IP:PORT |
user:pass |
| MPWA | http://IP:PORT |
API Key |
| Wablas | https://pati.wablas.com |
Token |
⏱️ Timezone
| Layer | Timezone | Note |
|---|---|---|
| Database (Prisma) | UTC | Prisma default |
| FreeRADIUS | WIB (UTC+7) | Server local time |
| PM2 env | WIB | TZ: 'Asia/Jakarta' in ecosystem.config.js |
| API / Frontend | WIB | Auto-converts UTC ↔ WIB |
For WITA (UTC+8) or WIT (UTC+9): change TZ in .env, ecosystem.config.js, and src/lib/timezone.ts.
📋 Admin Modules
Dashboard · PPPoE · Hotspot · Agent · Invoice · Payment · Keuangan · Sessions · WhatsApp · Network (OLT/ODC/ODP) · GenieACS · Settings
Roles: SUPER_ADMIN · FINANCE · CUSTOMER_SERVICE · TECHNICIAN · MARKETING · VIEWER
📝 Changelog
Bagian ini otomatis sinkron dari CHANGELOG.md saat file changelog berubah di GitHub.
v2.34.4 — 2026-05-13
Added
- Sidebar: Permintaan Top-Up & Suspend — tambah
nav.topupRequests(/admin/topup-requests) dannav.suspendRequests(/admin/suspend-requests) sebagai child PPPoE - Sidebar: ODC, ODP, Peta Jaringan — tambah 3 item ke Topology: Network Map, ODC, ODP
- Sidebar: Fiber ODC & Fiber ODP — tambah ke seksi Manajemen Fiber
- Sidebar: GenieACS Files — tambah child
nav.fileske seksi GenieACS - Sidebar: Kelola Teknisi — tambah item standalone di catManagement
- Sidebar: Log Aktivitas — tambah item standalone di catManagement
- Sidebar: Pengaturan Keamanan — tambah child
/admin/settings/securityke settingsMenu - Sidebar: WhatsApp jadi submenu — ubah dari single link ke children (Settings, Riwayat, Template, Kirim, Notifikasi, Providers)
- i18n: tambah nav keys —
topupRequests,suspendRequests,activityLogs,security,fiberOdcs,fiberOdps
Files
src/app/admin/AdminClientLayout.tsx— tambah menu items, WhatsApp jadi submenu, import UserCogsrc/locales/id.json— tambah 6 nav translation keys
v2.32.2 — 2026-05-13
Fixed
- System Info API: silent git errors — Semua
execSyncgit di/api/admin/system/infokini pakaistdio: 'pipe'sehingga stderr tidak bocor ke PM2 log;getAppDir()kini mencari/var/www/salfanet-frontendlebih dulu (direktori dengan.git) sebelum fallback ke path lain
Files
src/app/api/admin/system/info/route.ts— tambahstdio: 'pipe'padaexecSync/execFileSync, perbarui urutan kandidatgetAppDir()
v2.32.1 — 2026-05-11
Fixed
- PPPoE Session Sync error 1264 —
acctsessiontimedi-clamp ke range INT MariaDB (GREATEST(0, LEAST(..., 2147483647))) pada semua 4 UPDATE query; sesi denganacctstarttimetidak valid (0000-00-00atau sangat lama) tidak lagi menyebabkan cron gagal
Files
src/server/jobs/pppoe-session-sync.ts— clamp TIMESTAMPDIFF ke INT range, tambah filteracctstarttime > '2000-01-01'pada update aktif
v2.32.0 — 2026-05-11
Added
- Centralized Cron Schedule Management — jadwal semua cron job kini bisa diatur dari satu halaman Admin → Settings → Cron tab "Jadwal Cron"; perubahan disimpan ke DB
cron_schedule_config, aktif setelahpm2 restart salfanet-cron - Schedule Editor modal — 17 preset waktu (Every minute, Every 5 min, dll.) + custom cron expression; menampilkan default schedule sebagai referensi
- 3-tab layout cron page — Tab: Status & Trigger, Jadwal Cron, Riwayat Eksekusi
- API
/api/cron/schedules— GET/PUT/DELETE untuk manajemen schedule override per job (SUPERADMIN only) - DB table
cron_schedule_config— menyimpan override schedule per jobType
Changed
runner.ts— load schedule overrides dari DB saat startup; fallback ke default jika tidak ada override atau tabel belum ada; supportpreload.cjsmock untukserver-onlyjobs.config.ts— hapusimport 'server-only'guard (redundant; diganti comment penjelasan)
Fixed
- Duplicate
CronSettingsPagedeclaration — page.tsx memiliki duaexport default function CronSettingsPage()yang menyebabkan build error Turbopack; baris duplikat dihapus server-onlymodule block tsx cron runner —src/cron/preload.cjsmocking moduleserver-onlysebelum tsx load file apapun agar standalone cron runner bisa berjalan
Files
src/app/admin/settings/cron/page.tsx— rewrite lengkap dengan 3-tab layout + ScheduleEditor modalsrc/app/api/cron/schedules/route.ts— NEW: CRUD API untuk schedule overridesrc/cron/runner.ts— load schedule overrides dari DB viainitSchedules()src/cron/preload.cjs— NEW: mockserver-onlyagar tsx bisa load server filessrc/cron/runner-wrapper.cjs— NEW: CJS wrapper entry point (opsional)src/server/jobs/jobs.config.ts— hapusimport 'server-only'prisma/schema.prisma— tambah modelcronScheduleConfig
v2.31.12 — 2026-05-11
Fixed
- MikroTik timeout empty error message —
node-routerosmelempar empty string""saat timeout (bukanErrorobject); sekarang ada fallback message yang jelas jika error kosong atau{} - Library timeout conflict —
node-routerosinternal timeout diset ke 9999s agar tidak interferensi denganPromise.racetimeout kita yang memberikan pesan error yang lebih informatif
Files
src/server/services/mikrotik/client.ts— set library timeout ke 9999s, tambah fallback untuk empty error message
See full changelog: docs/getting-started/CHANGELOG.md
📚 Documentation
| File | Description |
|---|---|
| docs/INSTALLATION-GUIDE.md | Complete VPS installation |
| docs/GENIEACS-GUIDE.md | GenieACS TR-069 setup & WiFi management |
| docs/AGENT_DEPOSIT_SYSTEM.md | Agent balance & deposit |
| docs/RADIUS-CONNECTIVITY.md | RADIUS architecture |
| docs/FREERADIUS-SETUP.md | FreeRADIUS configuration guide |
📝 License
MIT License - Free for commercial and personal use
👨💻 Development
Built with ❤️ for Indonesian ISPs
Important: Always use formatWIB() and toWIB() functions when displaying dates to users.