XC_VM Build System (MAIN vs LB)¶
How XC_VM produces two build variants from a single codebase: a full MAIN server and a lightweight Load Balancer (LB) server.
Build Variants¶
XC_VM supports two deployment roles from a single source tree:
| Variant | Archive | Purpose |
|---|---|---|
| MAIN | xc_vm.tar.gz |
Full application — admin panel, streaming, all modules, cron jobs |
| LB (Load Balancer) | loadbalancer.tar.gz |
Streaming-only server — no admin panel, no user management |
MAIN is the primary server that manages everything: admin UI, database writes, user/device management, EPG processing, backups, etc.
LB is a lightweight streaming node that receives streams from MAIN (or other sources) and delivers them to clients. It connects to the master database in read-only mode and has no admin panel or management capabilities.
Makefile Targets¶
| Target | Output | Description |
|---|---|---|
make main |
dist/xc_vm.tar.gz |
Full MAIN build |
make lb |
dist/loadbalancer.tar.gz |
LB build (streaming-only subset) |
make new |
(resets dist/) |
Remove and recreate the empty dist/ output dir — run before a build; builds nothing itself |
make generate_deleted_files |
src/migrations/deleted_files.txt |
List files removed since the last tag (see below) |
Updates reuse the full archive. There is no separate incremental-update target — the same
xc_vm.tar.gz/loadbalancer.tar.gzis used for both install and update; filtering happens on the server at update time (see Update Mechanism). To delete files that were removed between releases,make generate_deleted_files [LAST_TAG=vX.Y.Z]diffs git and writesdeleted_files.txt, which the updater applies.
Additional outputs:
XC_VM.zip— installer package (install/+xc_vm.tar.gz)hashes.md5— MD5 checksums for integrity verification
Composer Dependencies¶
src/vendor/ (the Composer PSR-4 autoloader plus the production dependencies) is
committed and shipped as-is — the deploy path has no Composer and never runs
composer install. It is kept production-only via composer install --no-dev, so
both build variants ship a lean vendor with no dev tooling.
src/composer.lockis committed socomposer installis reproducible.- Dev tools (PHPStan, phpcs) are
require-devand are not in the committed vendor or the archives. Developers and CI add them withmake dev-tools(composer install); thecheck-vendor-prod-onlygate fails if a dev package is ever committed undersrc/vendor/. - There is no build-time vendor step —
make main/make lbcopy the committedvendor/directly into the archive.
What Goes Into Each Build¶
MAIN Build¶
The MAIN build contains the entire src/ directory.
LB Build — Included Directories¶
Only these directories are copied into the LB archive:
bin/ Cli/ config/ content/ Core/
Domain/ Infrastructure/ Public/ resources/
signals/ Streaming/ tmp/ vendor/ www/
Plus root files: bootstrap.php, console.php, service, update.
LB Build — Excluded Content¶
After copying, admin-specific content is removed from the LB build:
Directories removed:
| Path | Reason |
|---|---|
bin/install/ |
Installer scripts (not needed on LB) |
bin/redis/ |
Redis binary (LB doesn't run its own Redis) |
bin/nginx/conf/codes/ |
Error code pages (admin UI) |
Public/Controllers/Admin/ |
Admin panel controllers |
Public/Controllers/Player/ |
Player panel controllers |
Public/Controllers/Reseller/ |
Reseller panel controllers |
Public/Views/ |
Panel templates |
Public/assets/ |
Panel static assets |
Public/routes/ |
Panel route maps |
Domain/User/ |
User management |
Domain/Device/ |
Device registration |
Domain/Auth/ |
Auth management (panel auth) |
resources/langs/ |
Language resource files |
resources/libs/ |
Admin library resources |
Files removed (these mirror LB_FILES_TO_REMOVE in the Makefile):
⚠️ Several entries here use the legacy
www/…prefix (e.g.www/stream/auth.php,www/xplugin.php).src/www/no longer exists — the streaming/API endpoints moved undersrc/Public/stream/andsrc/Public/…. Thesewww/…removal entries are therefore no-ops today and are worth auditing in the Makefile (a file that should be stripped from LB may in fact still ship under itsPublic/path).
| File | Reason |
|---|---|
Public/Controllers/Api/AdminApiController.php |
Full admin API removed from LB |
Public/Controllers/Api/ResellerRestApiController.php |
Reseller API removed from LB |
www/xplugin.php, www/probe.php, www/playlist.php |
Admin endpoints |
www/player_api.php, www/epg.php, www/enigma2.php |
Client API endpoints (served by MAIN) |
www/stream/auth.php |
Auth endpoint (legacy path — see note) |
www/admin/api.php, www/admin/proxy_api.php |
Admin API |
bin/maxmind/GeoLite2-City.mmdb |
GeoIP DB shipped separately |
config/rclone.conf |
Backup config |
Domain/Epg/EPG.php |
EPG processing class |
bin/nginx/conf/gzip.conf |
Gzip config (LB uses own) |
CLI commands removed:
| File | Reason |
|---|---|
Cli/Commands/MigrateCommand.php |
Migration is MAIN-only |
Cli/Commands/CacheHandlerCommand.php |
Cache handler is MAIN-only |
Cli/Commands/ServerInstallCommand.php |
Server installer (not needed on LB itself) |
Cli/Commands/LbInstallFlow.php |
LB install helper (not needed on LB itself) |
Cli/Commands/ProxyInstallFlow.php |
Proxy install helper (not needed on LB itself) |
Cron jobs removed:
| File | Reason |
|---|---|
Cli/CronJobs/RootMysqlCronJob.php |
DB maintenance (MAIN-only) |
Cli/CronJobs/BackupsCronJob.php |
Backups (MAIN-only) |
Cli/CronJobs/CacheEngineCronJob.php |
Full cache rebuild (MAIN-only) |
Cli/CronJobs/EpgCronJob.php |
EPG processing (MAIN-only) |
Cli/CronJobs/UpdateCronJob.php |
Update check (MAIN-only) |
Cli/CronJobs/ProvidersCronJob.php |
Provider sync (MAIN-only) |
Cli/CronJobs/SeriesCronJob.php |
Series metadata (MAIN-only) |
Note: Module-related crons (TMDB, Plex, Watch) live inside
src/Modules/<name>/and are excluded from LB builds automatically —Modules/is not inLB_DIRS.Ministra (
src/Ministra/, the Stalker portal — ~50 MB of assets) is likewise excluded by omission: it isn't listed inLB_DIRS, so it's never copied into the LB archive (there is no explicit removal rule for it — hence theministraabsence check in Build Verification below).
LB Build — Replaced Configs¶
These files from lb_configs/ replace the MAIN versions:
| Source | Target | Purpose |
|---|---|---|
lb_configs/nginx.conf |
bin/nginx/conf/nginx.conf |
Performance-tuned nginx for streaming |
lb_configs/live.conf |
bin/nginx_rtmp/conf/live.conf |
RTMP callback hooks |
MAIN vs LB — Key Differences¶
| Aspect | MAIN | LB |
|---|---|---|
| Admin panel | ✅ Full UI | ❌ Not included |
| Database role | Read + Write | Read-only consumer |
| User/device management | ✅ | ❌ |
| EPG processing | ✅ | ❌ |
| Backups | ✅ | ❌ |
| Migration tool | ✅ | ❌ |
| Stream delivery | ✅ | ✅ |
| RTMP ingestion | ✅ | ✅ |
| Transcoding (FFmpeg) | ✅ | ✅ |
| CLI commands | 26 | ~15 (admin-only removed) |
| Cron jobs | 25 | ~16 (admin-only removed) |
| Module system | ✅ | ❌ |
LB Nginx Configuration¶
The LB build uses a specialized nginx config optimized for high-throughput streaming:
| Setting | Value | Purpose |
|---|---|---|
| Worker processes | auto |
Scale to CPU cores |
| Worker connections | 16,000 | High concurrency per worker |
| Max file descriptors | 300,000 | System resource limit |
| Thread pool | pool_xc_vm (32 threads) |
Async I/O for streaming |
| Gzip | OFF | Streaming data is already compressed |
| Access logs | OFF | Reduce I/O overhead |
| Rate limiting | 20 req/s per IP | DDoS mitigation |
| Send timeout | 20 min | Support long-running streams |
RTMP hooks (lb_configs/live.conf) route authentication through local HTTP callbacks instead of the admin panel:
on_play http://127.0.0.1:8080/stream/rtmp;
on_publish http://127.0.0.1:8080/stream/rtmp;
on_play_done http://127.0.0.1:8080/stream/rtmp;
Runtime Behavior on LB¶
Conditional Command Loading¶
console.php uses file_exists() guards for commands that may not exist on LB servers:
if (file_exists(__DIR__ . '/Cli/Commands/CacheHandlerCommand.php')) {
$rRegistry->register(new CacheHandlerCommand());
}
This prevents crashes when LB attempts to register a command whose file was removed during the build.
Streaming Dependency Chain¶
LB servers retain the full streaming pipeline:
www/stream/*.php
├── www/stream/init.php
├── vendor/autoload.php (Composer PSR-4 autoloader)
├── bootstrap.php (lightweight stream/bootstrap path)
├── Core/* (Config, Database, Cache, Auth, Http, Logging, Util)
├── Domain/Stream, Domain/Server, Domain/Vod, Domain/Bouquet
├── Streaming/* (Auth, Delivery, Codec, Protection)
├── Infrastructure/Redis, Infrastructure/Database
└── resources/data
Adding New Code to Builds¶
New streaming-relevant directory under src/¶
Add it to LB_DIRS in the Makefile:
LB_DIRS := bin Cli config content Core Domain \
Infrastructure Public resources signals Streaming tmp vendor www your_dir
New admin-only directory¶
Add it to LB_DIRS_TO_REMOVE:
New admin-only file¶
Add it to LB_FILES_TO_REMOVE:
New CLI command (admin-only)¶
- Add
file_exists()guard inconsole.php - Add the file to
LB_FILES_TO_REMOVE
Build Verification¶
After modifying the build, verify both variants:
# Build both
make new
# Check LB contains streaming code
tar -tzf dist/loadbalancer.tar.gz | grep -cE "Core/|Domain/Stream|Streaming/"
# Expected: > 0
# Check LB does NOT contain admin code
tar -tzf dist/loadbalancer.tar.gz | grep -cE "admin/|player/|ministra|reseller"
# Expected: 0
# Compare sizes (LB should be significantly smaller)
ls -lh dist/xc_vm.tar.gz dist/loadbalancer.tar.gz