The committed PHPUnit runner lived at tools/.bin/phpunit.phar, away from the suite it runs. Move it next to the tests it drives — tests/phpunit.phar — and update every invocation to `php tests/phpunit.phar -c tests/phpunit.xml.dist`: - CI workflows (ci, build-release, build_pre-release) + the ci.yml header, - CLAUDE.md, CONTRIBUTING.md, tools/README.md, the qa-lead-reviewer agent, - docs/en (dev-workflow, updates_checklist, phpunit-phar, refactoring). docs/ru is generated from docs/en (make docs-translate) and is left for the next regeneration, per the docs workflow.
14 KiB
Contributing to XC_VM
Thank you for considering contributing! XC_VM is an open-source, Xtream-Codes-style IPTV management panel (PHP 8.1+, AGPL-3.0) — a modular monolith migrated to Composer PSR-4. This guide covers the layout, the local dev loop, the checks CI enforces, and how to open a good pull request.
📌 General Guidelines
- Minimally use AI; you own every line you submit and must understand it.
- Follow the project's coding style and best practices.
- Keep pull requests focused on a single change.
- Write meaningful commit messages (Conventional Commits, English).
- Document behaviour that isn't obvious from the code.
- If you refactor and aren't sure code is unused elsewhere, comment it out with a note instead of deleting — it can be removed after the next release.
🧭 Repository Layout (read this first)
The trap for new contributors: the application lives under src/, not the
repo root.
src/is the application root and the deploy root.composer.json,vendor/,bootstrap.php,console.phpall live insrc/. It maps 1:1 to the install root on a server (src/Core/X.php→/home/xc_vm/Core/X.php).- PSR-4:
XcVm\→src/(e.g.XcVm\Core\Database\DatabaseHandler=src/Core/Database/DatabaseHandler.php). - The repo root holds build & CI tooling only:
Makefile,tools/,tests/,install/,lb_configs/,build/,docs/.
Two build flavours are produced from the same tree: the full MAIN panel and
a stripped Load Balancer (LB) archive (restreaming only — admin/reseller/
player code, installers, and privileged commands removed). Modules are
MAIN-only. Deeper architecture notes live in docs/en/development/ and the
repo CLAUDE.md.
⚠️ Hard Constraints
src/vendor/is committed and PRODUCTION-ONLY. Never runcomposer installon a deploy path. To change the autoload map, runcomposer dump-autoloadfromsrc/. After changing dependencies, re-commit acomposer install --no-devvendor tree plus the updatedcomposer.lock.- Modules own their DB schema via file migrations
(
src/Modules/<name>/migrations/<semver>.up.sql/.down.sql). Do not add module tables tosrc/bin/install/database.sql, and core must not touch module-owned tables directly — dispatch an event and let the module clean up. - Bundled binaries are regular Git objects (not Git LFS). The
ffmpeg/ffprobe,redis-server,yt-dlp, MaxMind DBs,login-bg.mp4, etc. are committed as plain binary blobs (marked-text -diffin.gitattributes) — a normalgit clonefetches them whole, nogit lfs pullneeded. Averify_no_lfs_pointersbuild gate still guards against stray legacy LFS pointer stubs. - Outbound HTTPS from PHP must use cURL —
file_get_contents()over https does not work in this environment.
🛠️ Local Setup
You need PHP 8.1+, git, make, and Composer.
git clone https://github.com/Vateron-Media/XC_VM.git
cd XC_VM
make dev-tools # composer install in src/ — adds PHPStan +
# phpcs (Slevomat) + unused-public to src/vendor
make dev-tools installs the require-dev toolchain (PHPStan 2.2.9, PHP_
CodeSniffer + Slevomat, Tomas Votruba's unused-public) into src/vendor. These
are not in the committed vendor tree, so run it before the checks below. When
you're done, make dev-clean prunes src/vendor back to production-only.
🔁 The Dev Loop: Deploying to a Live Box
Prerequisite — you need a working XC_VM install to sync onto. sync-dev.sh
does not provision a server; it pushes changed code onto an existing install root
(/home/xc_vm/ by default). So first stand up a throwaway/test box the normal
way — install the panel from a release (see Quick Install
in the README: download XC_VM.zip, unzip, sudo python3 install). Only once
the panel is installed and running does sync-dev.sh become useful for pushing
your local edits onto it.
Once you have that box, use tools/sync-dev.sh to iterate against it without
building a release. It works out which files under src/ your commits changed
and copies their full current content to the server's install root. Files
your commits deleted are removed on the server; renames delete the old path
and copy the new one.
It is a developer convenience — it never runs DB migrations, ships binaries, or edits per-server config. For a real upgrade use the release archive + panel updater.
# Preview the copy/delete plan — never contacts the server; always safe:
DEV_SERVER=<ip> tools/sync-dev.sh <RANGE> --dry-run
# Push uncommitted edits to tracked files (fastest inner loop):
DEV_SERVER=<ip> tools/sync-dev.sh --working --restart
# Push committed work since the last sync (watermark in .dev-sync-state):
DEV_SERVER=<ip> tools/sync-dev.sh --restart
# Push everything since a tag / branch / sha:
DEV_SERVER=<ip> tools/sync-dev.sh 2.4.1 --restart
Here <RANGE> is usually the version tag of the panel currently installed on
the box (e.g. 2.4.1). The box was installed from that release, so syncing
<that version>..HEAD pushes exactly the changes your branch adds on top of it,
nothing more.
--working— also include uncommitted changes to tracked files (diff vs HEAD).--restart— restart the panel so OPcache reloads new PHP code. Slow (>1 min); run in the background under a short timeout. Pure CSS/JS/template changes are served statically and usually need only a hard refresh, not this.--cache— rebuild the settings cache (for settings-shaped changes).
RANGE is a git range A..B, or a single ref REF (treated as REF..HEAD).
Omitted, it continues from .dev-sync-state (or HEAD~1..HEAD on first run).
SSH auth uses your keys by default; set DEV_SSH_USER / DEV_SSH_PASS (needs
sshpass) to override. All SSH multiplexes over one ControlMaster socket so
repeated syncs don't trip fail2ban. See the header of tools/sync-dev.sh (or
--help) for the full reference.
✅ Pre-Commit Checks
Always run make cs-fix first. It auto-formats your changes to the coding
standard (K&R braces, tab indentation, spacing, import order) via phpcbf, so your
diff matches what CI expects. Then run the checks below — CI runs the exact same
set and will reject a PR that fails any of them:
make cs-fix # run FIRST — auto-format to the coding standard (phpcbf)
make cs # verify code style: PSR-12 base + K&R braces + tab indentation
make phpstan # static analysis, level 5 (also catches syntax errors)
make gates # PSR-4 regression gates (see below)
php tests/phpunit.phar -c tests/phpunit.xml.dist # unit tests
make cs-fix applies everything auto-fixable; whatever make cs still reports
afterwards (e.g. a missing parameter type hint) you fix by hand.
make gates runs three CI blockers:
check-procedural-use— every procedural/view file mustuse-import theXcVm\classes it references at the top (PHPuseis positional; a bare short name faults at runtime).verify-lb-archive— the load-balancer build must contain no privileged code (admin/reseller/player UI, user/device domain, install/root crons).check-vendor-prod-only— the committedsrc/vendor/must stay production-only (no dev package tracked in the git index).
Quick single-file syntax check while editing (no DB needed):
php -l path/to/File.php
🔬 Static Analysis (PHPStan)
The project is analysed at level 5 with the config in
build/phpstan.dist.neon. PHPStan is a require-dev Composer package installed
by make dev-tools (into src/vendor/bin/phpstan); a bootstrap in
tools/phpstan/ defines the ~131 runtime define() constants static scanning
can't see.
make phpstan # must report [OK] No errors
How the gate works:
- A committed baseline (
build/phpstan-baseline.neon) freezes pre-existing findings, so CI fails only on new issues your change introduces — fix those. - The baseline is not a list of accepted bugs (most entries are false positives from dynamic DB-row shapes or templates). Do not grow it to hide a real problem in new code.
- If you clear a batch of existing findings, regenerate the smaller baseline:
make phpstan-baseline. - After runtime constants change, regenerate the stub with
make phpstan-stub; after editing PHPDoc types, clear the cache first:php src/vendor/bin/phpstan clear-result-cache.
✨ Code Style
The PHP standard (build/phpcs.xml.dist) is PSR-12 with project overrides —
run make cs-fix to auto-format to it before committing (make cs only
reports). Key points:
- K&R brace style (opening brace on the same line), not PSR-12's Allman.
- Tabs for indentation (one tab per level), not spaces.
- Give every function parameter a type hint (
make csflags untyped ones; this one is not auto-fixable — add the type yourself). - The ruleset excludes view templates (
Public/Views/,Modules/*/views/), so short-tag templates (<?,<?=) need no special handling there. - All code comments and docblocks in English.
- Prefer inverting empty-else guards:
if (!$c) { body }overif ($c) {} else { body }. - Avoid unused functions and redundant code.
- Follow best practices for Python and Bash tooling under
tools/.
🧪 Tests
Unit tests (PHPUnit 10.5, config tests/phpunit.xml.dist, suite "Unit"):
php tests/phpunit.phar -c tests/phpunit.xml.dist # all
php tests/phpunit.phar -c tests/phpunit.xml.dist --filter SomeTest # one
On an installed server, use the bundled interpreter instead of system PHP:
/home/xc_vm/bin/php/bin/php tests/phpunit.phar ...(seedocs/en/guides/phpunit-phar.md). On a dev machine, plainphpis fine.
Guidelines:
- Add PHP tests under
tests/Unit/, named after the class under test (e.g.GitHubReleasesTest.php). - Prefer focused tests for the file you changed over broad project-wide mocks.
- Cover valid inputs, invalid inputs, edge cases, and side effects.
- If code writes to stdout, capture it in the test so PHPUnit output stays clean.
End-to-end tests (Playwright, in tests/e2e/) exercise the running panel UI:
make e2e-install # one-time: npm ci + playwright install chromium
make e2e # run the suite
make e2e-ui # interactive UI mode
🧰 Developer Tooling (tools/)
Nothing in tools/ ships to production. Highlights useful while developing —
see tools/README.md for the full list:
| Tool | Purpose |
|---|---|
sync-dev.sh |
Incremental code deploy to a live box (see the dev loop above). |
test_player_api.sh <url> <user> <pass> |
HTTP smoke-test of every Player API endpoint (status / content-type / JSON shape). |
stream-check/ |
Dependency-free MPEG-TS + HLS stream integrity checker (stream_queue_check.py) with an SVG grapher (stream_graph.py). |
test-stream-generator/ |
Generates a synthetic moving test pattern (stopwatch + wall-clock) as an HTTP "live" source; no input file. |
test-install/ |
Docker end-to-end install test of the built release archive. |
📦 Building a Release (maintainers)
make main # full panel archive: dist/xc_vm.tar.gz + XC_VM.zip installer
make lb # load-balancer archive (privileged code stripped; MAIN-only modules excluded)
make new # wipe dist/
Builds copy only git-tracked files and run verify_no_lfs_pointers.
📚 Documentation
Docs are a MkDocs Material site under docs/.
- Edit only
docs/en/— English is the single source of truth. - Never hand-edit
docs/ru/(or any other language tree): it is generated fromdocs/enbytools/docs/translate.pyand overwritten on the next run. - After editing English docs, verify with
make docs-build(strict — fails on broken links/anchors). Preview locally withmake docs-serve. docs/ruis regenerated locally before a release (make docs-translate), not in CI.
🔥 Submitting a Pull Request
-
Fork and branch (see naming conventions below):
git checkout -b feature/your-feature -
Make your changes; run the pre-commit checks; commit with a Conventional Commits message:
git commit -m "feat: short description of the change" -
Push and open a pull request on GitHub:
git push origin feature/your-feature
Keep the PR focused on a single change and describe what and why.
Branch Naming Conventions
| Type | Template | Example |
|---|---|---|
| Features | feature/<short-description> |
feature/user-authentication |
| Bug Fixes | fix/<short-description> |
fix/login-bug |
| Hotfixes | hotfix/<short-description> |
hotfix/critical-error |
| Refactoring | refactor/<short-description> |
refactor/code-cleanup |
| Testing | test/<short-description> |
test/api-endpoints |
| Documentation | docs/<short-description> |
docs/documentation-api |
👀 Code Reviews
- All PRs must be reviewed by at least 2 maintainers.
- Address review comments before merging.
🚀 Reporting Issues
- Use GitHub Issues to report bugs and suggest features.
- Provide clear steps to reproduce, plus relevant logs or error messages.
🌟 Recognition
Your GitHub profile will be added to CONTRIBUTORS.md.
Thank you for contributing! 🎉