Platform Stack
Jabali Panel is a Laravel 12 + Filament v5 web hosting control panel with privileged operations delegated to a PHP agent daemon.
System Overview
User Browser
↓
nginx (port 80, 443)
↓
FrankenPHP Panel (port 8443, PANEL_PORT env configurable)
├── Filament Admin UI (/jabali-admin/)
├── Filament User UI (/jabali-panel/)
└── REST API endpoints
↓
AgentClient → Unix Socket (/var/run/jabali-agent.sock)
↓
jabali-agent (PHP binary, bin/jabali-agent)
├── File system operations (owned by root)
├── Service management
├── SSL certificate handling
├── User/domain operations
└── System commands (escapeshellarg, proc_open for safety)
Core Components
1. Panel (FrankenPHP)
Process: Laravel app running on FrankenPHP, listening on port 8443 (configurable via PANEL_PORT environment variable). This is independent of nginx, which proxies requests from ports 80/443.
Guards: - admin guard at /jabali-admin/ for administrative access - web guard at /jabali-panel/ for user access - Both guards share the same users provider
Entry Points: - bootstrap/app.php — middleware, exception handling, routing configuration (Laravel 12 streamlined structure) - routes/web.php — Filament route registration - routes/api.php — API endpoints for agent communication
Key Directories: - app/Filament/Admin/Pages/ — admin panel pages (Services, SSL, Users, System, etc.) - app/Filament/Jabali/Pages/ — user panel pages (Dashboard, Domains, Backups, Emails, etc.) - app/Services/Agent/AgentClient — all privileged operations route through this service
2. Agent (Privileged Daemon)
Binary: bin/jabali-agent (PHP standalone executable, ~21k lines)
Communication: Unix socket at /var/run/jabali-agent.sock. Panel communicates via AgentClient::call(), passing JSON-encoded command and arguments. Returns JSON response.
Responsibility: All operations requiring root privileges: - File ownership/permissions - Service start/stop/restart - SSL certificate issuance and installation - User home directory creation/deletion - Cron job management - Shell command execution with proper escaping (escapeshellarg()) - Password management via proc_open() to chpasswd
Safety: - All shell arguments escaped with escapeshellarg() - Passwords piped via proc_open() (not via command line) - File paths validated with PathSanitizer::clean() to prevent traversal - Cron commands validated against allowlist via CronCommandValidator::validate() - Service names checked against $allowedServices global array
Response Format: JSON with success boolean, result (data), and optional error string.
3. Services
Stalwart Mail Server
Role: Only mail backend. Supports SMTP, IMAP, JMAP, ManageSieve.
Configuration: MySQL-backed storage for accounts, domains, settings.
Management: - jabali mail:* commands (create, delete, password, quota, list, log, queue, queue-retry, queue-delete) - Mailbox quotas stored in database - Log viewing via mail:log (filters by domain, type like smtp or delivery)
Integration: Panel syncs mailbox operations to Stalwart, reads statistics and logs from database and log files.
PowerDNS
Role: Authoritative DNS server with REST API and MySQL backend. Supports DNSSEC.
Configuration: MySQL stores zones and records. REST API at http://localhost:8081 (Poweradmin credentials).
Management: - jabali dns:* commands (list, records, add, delete-record, sync) - Zone files synced from PowerDNS API - Records created/updated/deleted via API - dns:sync refreshes zone from API (useful after bulk imports)
Integration: Panel manages DNS records for domains and subdomains, including mail.$domain (MX records).
Restic Backup
Role: Incremental, deduplicated backup system with encryption. Stores snapshots (not continuous archives).
Configuration: Local or remote destinations (SFTP, S3, B2). Password-encrypted repository.
Management: - jabali backup:* commands (create, list, info, restore, delete, password) - Snapshots browsable/restorable from panel - User-level backups (home directory and domains) - Encryption password set via backup:password
Integration: Panel initiates backups, lists snapshots, displays restoration wizard, verifies Restic binary availability.
Certbot / Let’s Encrypt
Role: SSL/TLS certificate issuance and renewal using webroot mode.
Configuration: Webroot at domain document root, certificates stored in /etc/letsencrypt/live/{domain}/.
Management: - jabali ssl:* commands (issue, renew, check, list, status, panel, panel-issue) - Automatic renewal via systemd timer - Panel certificate separate (for FrankenPHP itself) - Includes mail.$domain as additional SAN
Integration: Panel issues certs, checks expiration, renews on demand, auto-deploys to web serving.
GoAccess Statistics
Role: Real-time web traffic analytics. Daemon mode with WebSocket updates.
Configuration: Reads nginx access logs, generates HTML report. WebSocket server for live updates.
Integration: Panel displays bandwidth and visitor statistics on user domains page.
4. Webmail (Bulwark)
Binary: Next.js JMAP client at /opt/bulwark, served via nginx proxy at /webmail/.
Port: 3000 (internal), proxied by nginx on user-facing port 443.
Authentication: SSO via token file at /var/lib/jabali/sso-tokens/{token}. Custom endpoint PUT /webmail/api/auth/session accepts token + username, returns session cookie.
Patches: Applied during install.sh via patch_bulwark(): - basePath configuration (running under /webmail/) - SSO integration (token file reading) - auth-store modifications (session handling) - proxy.ts configuration (upstream headers)
Update: upgrade_bulwark() rebuilds from source, applies patches, validates version marker.
5. Security Integration
Daemon: jabali-security (separate repo, PHP service).
Features: - Brute-force protection (IP-based rate limiting on login) - WAF (Web Application Firewall) - Malware scanning (on-demand via wp:scan for WordPress) - CrowdSec integration (optional, for threat intelligence)
Integration: Filament plugin provides admin dashboard, WordPress plugin hooks into scan workflow.
6. Shell Isolation (jabali-isolator)
Technology: systemd-nspawn containers (lightweight namespaced environments).
Purpose: SSH shell access sandboxed per user, preventing lateral movement.
Architecture: - One container per user with user-owned filesystem - SSH login via /opt/jabali-shell.sh wrapper (nsenter into container) - Container idle timeout via systemd timer (jabali-container-idle-check.sh) - Web serving (PHP-FPM) runs on host, not in container
Integration: ssh_shell_enabled toggle on hosting packages, auto-enabled on user creation.
7. Job Queue
Technology: Laravel queue system with database driver (default), or Redis for high-volume.
Jobs: - Backup creation (CreateBackupJob) - SSL certificate issuance (IssueCertificateJob) - User deletion (cleanup of domains, emails, databases) - WordPress operations (update, scan, import)
Processing: Supervisor or systemd service runs queue:work daemon.
8. Directory Layout
/home/shuki/projects/jabali/
├── app/
│ ├── Console/Commands/Cli/ # 86 CLI command classes
│ ├── Filament/Admin/Pages/ # Admin panel pages
│ ├── Filament/Admin/Resources/ # Admin resources (Users, Domains, etc.)
│ ├── Filament/Jabali/Pages/ # User panel pages
│ ├── Filament/Jabali/Resources/ # User resources
│ ├── Http/Controllers/ # API controllers
│ ├── Http/Requests/ # Form request validation
│ ├── Jobs/ # Queue jobs
│ ├── Models/ # Eloquent models (User, Domain, Backup, etc.)
│ ├── Services/Agent/AgentClient.php # Agent communication
│ ├── Services/Agent/AgentRequest.php # Request builder
│ ├── Services/Auth/ # Authentication services
│ └── Providers/ # Service providers
├── bin/
│ ├── jabali # CLI entry point
│ └── jabali-agent # Privileged agent binary
├── bootstrap/
│ └── app.php # Application configuration (Laravel 12)
├── config/
│ ├── app.php, cache.php, database.php, etc. # Configuration files
│ └── services.php # Third-party service configs
├── database/
│ ├── migrations/ # Database schema
│ └── factories/ # Model factories
├── docs/
│ ├── cli-reference.md
│ ├── architecture.md # This file
│ ├── mail.md
│ ├── ssl.md
│ ├── security.md
│ ├── backups.md
│ ├── dns.md
│ ├── one-time-login.md
│ └── diagnostic-logs.md
├── resources/
│ ├── views/ # Blade templates
│ ├── wordpress/jabali-cache/ # WordPress cache plugin
│ └── css/, js/ # Frontend assets
├── routes/
│ ├── web.php # Filament routes
│ └── api.php # API routes
├── storage/
│ ├── logs/ # Log files
│ └── uploads/ # User uploads (symlinked to public/storage)
├── stubs/
│ ├── jabali-shell.sh # SSH login wrapper
│ ├── jabali-container-idle-check.sh # Container idle timeout
│ └── [config templates] # nginx, PHP-FPM, service templates
├── tests/
│ ├── Feature/ # Feature tests (Livewire, API)
│ ├── Unit/ # Unit tests
│ └── Pest.php (or phpunit.xml) # Test configuration
├── .env.example # Environment template
├── composer.json # PHP dependencies
├── package.json # NPM dependencies
├── artisan # Laravel CLI
├── install.sh # Server installation script
└── README.md
Data Flow Examples
User Creation
- Admin clicks “Create User” in
/jabali-admin/ - Filament form posts to
CreateUserAction - Action validates input, creates User model record
- Agent command dispatched:
createUser(username, password, email, is_admin) - Agent creates Unix user, home directory, SSH key, database user
- Panel stores user record with agent response data
- If
ssh_shell_enabled, container created automatically
Domain Creation
- User clicks “Add Domain” in
/jabali-panel/ - Filament form posts to domain creation action
- Panel validates domain ownership (DNS TXT record or email verification)
- Agent creates vhost directory, symlinks to user home
- Agent adds DNS zone to PowerDNS
- Panel creates Domain model, associates with User and SSL certificate request
- Certbot automatically issues certificate via webroot
SSL Certificate Issuance
- Panel detects new domain or expiring certificate
- Dispatches
IssueCertificateJob - Job calls agent
sslInstallCertificate(domain) - Agent calls Certbot with webroot, stores cert in
/etc/letsencrypt/live/{domain}/ - Agent updates nginx vhost config with cert paths
- Panel stores certificate metadata (expiration, issuer)
- Auto-renewal via systemd timer
Backup Restoration
- User selects snapshot from backup list
- Filament opens restoration wizard (select files, restore location)
- User submits
- Panel dispatches
RestoreBackupJob - Job calls agent
restoreBackup(snapshot_id, target_path) - Agent mounts Restic snapshot (readonly), copies files to user home
- Panel marks restoration complete, displays result