Adopt Rector as a require-dev tool alongside PHPStan/phpcs for safe, mechanical
refactoring:
- src/composer.json: add rector/rector ^2.0 (require-dev) + refactor/refactor:dry
composer scripts.
- build/rector.php: narrowly-scoped config over the PSR-4 class trees
(Core/Domain/Cli/Infrastructure). Skips the \TMDB lib, the streaming hot-path,
vendor, tmp/backups. Import-adding stays OFF (the check-procedural-use gate
relies on positional use imports). Behaviour-changing rules are disabled
(SafeDeclareStrictTypes, UseIdenticalOverEqualWithSameType); only the safe
deadCode + codeQuality prepared sets run (incl. the empty-if/else collapse).
- Makefile: `make rector` (dry-run, non-zero on pending changes) and
`make rector-fix` (apply). Both require dev-tools.
- docs/en/guides/refactoring.md + dev-workflow.md + mkdocs.yml nav: the
detect -> diff -> verify -> apply workflow.
No source code is changed by this commit — scaffolding only. `make rector-fix`
output will be reviewed separately.
Rename the last lowercase "vuexy" identifiers left after the de-brand:
- views {header,footer,menu,vendors,topbar}.vuexy.php -> .newui.php
- functions xc_admin_use_vuexy / xc_vuexy_vendor_* / _xc_vuexy_icon -> *_newui*
- constant XC_VUEXY_PAGES -> XC_NEWUI_PAGES
- E2E spec vuexy-tables.spec.ts -> newui-tables.spec.ts + drop the brand
name from test/Makefile prose
Also fix two identifiers the earlier brand scrub corrupted (a bare "Vuexy"
in a name became "Bootstrap 5", inserting a space): class
XcVuexyMenuBuilder -> XcNewuiMenuBuilder (was a parse error in the menu view)
and global key xmVuexyVendors -> xmNewuiVendors.
Refs: #148#178
Add a browser E2E project under tests/e2e/ that runs against a live panel
(there is no built-in server). Verified green (7/7) against a canary instance.
- playwright.config.ts: loads tests/e2e/.env (dotenv); a `setup` project logs in
once (auth.setup.ts — form #username/#password/#login_button -> ./login) and
stores the session, the chromium project reuses it via storageState. baseURL is
normalized to a trailing slash so relative gotos keep the access-code segment.
- Smoke specs: shell renders (sidebar/navbar, server-stamped data-bs-theme);
Dashboard group + Home link; six stat tiles + a live ./api?action=stats 200;
charts + server selector; customizer theme change persists across reload and
reset (opening the customizer panel) clears it.
- Config via env: XC_E2E_BASE_URL (admin base incl. access code), XC_E2E_USER,
XC_E2E_PASS (see .env.example / README). Makefile: `make e2e` / `e2e-ui` /
`e2e-install`. node_modules/.auth/results/.env gitignored; @playwright/test
pinned ~1.49 (Node 18+).
Move the .ini language files from src/resources/langs/ to
src/Core/Localization/lang/, next to the Translator subsystem that owns
them — which already defaulted its $langsDir to __DIR__ . '/lang/'. This
dissolves the now-vestigial src/resources/ bucket (its data/ tree went
with admin_constants, libs/ was empty).
- bootstrap.php calls Translator::init() with no argument, relying on the
class's own __DIR__-relative default instead of MAIN_HOME . 'resources/langs/'.
- Makefile LB removal list points at Core/Localization/lang (and drops the
gone resources/langs, resources/libs); LB still ships no UI translations.
- Drop the stale src/resources entries from phpstan scanDirectories and the
phpunit coverage excludes.
- Update the English docs (translations guide, build-system table); the ru
tree is regenerated before release.
Move the 17 global lookup arrays from resources/data/admin_constants.php
into PSR-4 classes and enums, eliminating the last big pile of `global`
reference data.
New XcVm\Core\Reference\* (const + static accessors, FfmpegBinaries-style):
GeoReference, LocaleReference, DeviceReference, UiReference,
PermissionReference, StatusBadge. New XcVm\Core\Enum\* (backed enums with
label()/options(), ModuleState-style): Theme (int), ResellerAction and
ClientFilter (string).
All consumers migrated off `global $rX` / `$GLOBALS['rX']` and the
BaseAdminController extract bridge: TableController, DashboardController,
SearchAjaxController, SettingsController, ResellerAPI and
ResellerLoginController call the classes directly, and 21 view templates
import them via `use`. The extract whitelist entries, the bootstrap
require and admin_constants.php itself are removed.
Fixes a latent bug: TableController read $rClientFilters without a
`global`, so the client-request log showed raw status codes instead of
labels; ClientFilter::labelFor() now resolves them.
The reference classes and the three new enums are excluded from the
load-balancer build (all consumers are already MAIN-only).
Adds unit tests for the enums, StatusBadge and the reference data.
The keepalive supervisor bin/xc_fanout/run.sh was deployed without its exec bit (a mode-dropping sync left it 0644), so 'service' could not launch it -> xc_fanout never started -> every stream served the not-on-air clip. Three defences: (1) Makefile set_permissions ships run.sh (and console.php) as 0755 in the archive; (2) service launches it via 'bash run.sh' so a lost exec bit no longer breaks startup; (3) StartupCommand chmods it +x on boot as a backstop.
Run tools/docs/translate.py with python -u and print per-file progress so 'make docs-translate' shows live output instead of going silent. Relocate the translation cache and venv under build/ (build/docs-cache, build/docs-venv).
PHP-CS-Fixer's `no_unused_imports` is conservative — it treats a class name that
merely appears in a PHPDoc *description* as "used", so genuinely-dead imports
(e.g. `use ...Request;` next to a "Request IP" doc description) were never
flagged. Slevomat's UnusedUses is precise: it parses annotation *types*
(@param/@return/@var), so it keeps docblock-typed imports but removes truly
unused ones — matching what Intelephense (P1003) reports.
- Swap require-dev: friendsofphp/php-cs-fixer -> squizlabs/php_codesniffer +
slevomat/coding-standard (+ phpcodesniffer-composer-installer, allow-listed).
- New narrow ruleset build/phpcs.xml.dist (import/namespace hygiene only, NOT
full PSR-12): UnusedUses (searchAnnotations=true), UseFromSameNamespace,
UseDoesNotStartWithBackslash, AlphabeticallySortedUses, UseSpacing,
NamespaceSpacing. View templates stay excluded.
- Makefile: `make cs` -> phpcs, `make cs-fix` -> phpcbf (same target names).
- CI code-style job, CLAUDE.md, CONTRIBUTING.md, docs, .gitignore updated;
build/.php-cs-fixer.dist.php removed. Committed vendor stays production-only.
Move the generated site out of the repo root: site_dir -> build/site, with the
Pages upload path and .gitignore updated to match. build/ already holds tooling
config, so the build output no longer clutters the top-level listing.
CI translation was slow, so move it out of GitHub Actions: docs/ru is now a
committed, generated tree refreshed LOCALLY before a release; CI only builds it.
- pages.yml: drop the setup-python/cache/translate steps — the workflow now just
installs the build toolchain and runs `mkdocs build --strict` on the committed
en+ru trees.
- .gitignore: stop ignoring docs/ru (now committed); keep site/ + .docs-cache/.
- Split deps: docs/requirements.txt = build only (mkdocs-material, static-i18n,
used by CI); tools/docs/requirements.txt = translators (local-only).
- Makefile: docs-venv installs both; docs-build/docs-serve no longer translate
(translation is the deliberate `make docs-translate` release step).
- updates_checklist.md: new "Regenerate translated documentation" step
(make docs-translate + docs-build, commit docs/ru with the release commit).
- Commit the generated docs/ru (37 files, translators/yandex).
Rule: edit ONLY docs/en; docs/ru is generated — never hand-edit it.
Replace the hand-maintained Docsify site (parallel docs/en + docs/ru trees
that had already drifted) with a MkDocs Material build where English is the
single source of truth and Russian is generated at build time.
Engine & structure
- mkdocs.yml: Material theme, site_url for the /XC_VM/ Pages subpath, and a
two-tab information architecture — User Guide (administration, API/Swagger,
UI translations, diagnostics, info/FAQ) vs Developer Guide (architecture,
workflow, security, integrations, build). Files are NOT moved — the split is
nav-only, so URLs and cross-links stay stable.
- mkdocs-static-i18n (folder mode): English at root, Russian under /ru/, with a
language switcher. `mkdocs build --strict` validates every link/anchor.
Translation pipeline (tools/docs/translate.py)
- Engine-agnostic via DOCS_TRANSLATE_PROVIDER: translators (free, no API key —
default), anthropic, deepl, or noop. Per-file sha256 cache so only changed
English files are re-translated. Markdown-safe: code, URLs, HTML tags and
glossary terms (XC_VM, FFmpeg, HLS, ...) are masked and never translated.
A file whose translation fails falls back to English so the build never breaks.
- docs/ru is generated and gitignored — never committed. It is produced locally
(`make docs-serve` / `docs-build`) and in CI.
CI & tooling
- pages.yml: build-then-upload (setup-python -> install -> restore .docs-cache
with restore-keys -> translate -> mkdocs build --strict -> deploy), replacing
the verbatim docs/ upload.
- Makefile: docs-venv / docs-translate / docs-build / docs-serve.
- docs/requirements.txt; .gitignore for docs/ru, site/, .docs-cache.
Migration details
- Removed Docsify control files (index.html, _navbar.md, _sidebar.md, .nojekyll)
and de-Docsify-ed body links in 6 English files (en-us/ aliases -> relative,
swagger _media paths, stripped ':ignore' link syntax).
- Preserved the two Russian-only planning docs (no English source) by moving
them into docs/adr/ as *.ru.md (repo-internal, excluded from the site).
Adds a `make phpstan-deadcode` target and build/phpstan-deadcode.neon that
layer tomasvotruba/unused-public on top of the main PHPStan setup to report
unused PUBLIC methods/properties/constants. It is an on-demand audit, NOT a
CI gate (expect false positives for dynamically-invoked code — routes,
#[ListensTo] subscribers, CLI handlers, view templates).
unused-public is a require-dev package (installed by `make dev-tools`); the
committed vendor/ stays production-only.
Ministra stops being a module — the whole Stalker portal (portal.php,
MinistraBootstrap, PortalHandler/PortalHelpers and the STB front-end) now
lives in src/Ministra/ under the XcVm\Ministra namespace, served at
/home/xc_vm/Ministra via the nginx alias.
- src/ministra/* and Modules/ministra_85a7d/{PortalHandler,PortalHelpers}
→ src/Ministra/; MinistraModule.php + module.json removed. Ministra was
the only committed module, so src/Modules/ keeps a .gitkeep.
- portal.php resolves PortalHandler as a sibling and derives MAIN_HOME from
its new location (glob crutch gone).
- nginx alias + AuthRepository $rAlias switched to /home/xc_vm/Ministra
(PascalCase); ministra entry dropped from bundled_modules.php.
- Makefile: Modules/ removed from LB_DIRS — all modules are MAIN-only, so
the ~50 MB of portal assets no longer ship to LB nodes.
- ArchitectureTest: zero committed modules is now a valid state.
- PHPStan: analyse src/Ministra, exclude the procedural portal.php entry,
repath the ministra baseline entries.
- Docs (architecture, ministra-browser-emulation, extraction plan) updated
to the new layout; the "extract to a separate repo" plan is cancelled.
Verified: php -l, make gates, make phpstan (No errors), full unit suite
(432 tests). On-server smoke: handshake + get_profile work end-to-end with
a registered MAC after deploy.
Relocate the dev-tooling config into build/ so the project root only holds
source and first-class project files:
- phpstan.dist.neon -> build/phpstan.dist.neon
- phpstan-baseline.neon -> build/phpstan-baseline.neon
- .php-cs-fixer.dist.php -> build/.php-cs-fixer.dist.php
- .php-cs-fixer.cache -> build/ (regenerated there; gitignored)
Because neon/CS-Fixer resolve relative paths against the config file's own
directory, the internal references are re-anchored one level up (src/ ->
../src/, tools/ -> ../tools/, Finder in(__DIR__.'/../src'); the baseline's
path: entries likewise). The Makefile now points PHPStan at the config with
-c build/phpstan.dist.neon (it previously relied on root auto-discovery),
generates the baseline into build/, and passes --config=build/... to CS-Fixer;
the CS-Fixer cache is pinned to build/ via setCacheFile and re-gitignored.
No behaviour change. Verified: make phpstan (No errors), make cs (0 fixable),
make gates. CI runs through these make targets, so it is covered.
gen-constants-stub.php had no Makefile/CI target, so tools/phpstan/
constants.stub.php (a bootstrapFile in phpstan.dist.neon) silently drifted:
regenerating it added DB_ACCESS_PWD and GIT_REPO_PROXY (real runtime define()s
in src/Core/Config/AppConfig.php that PHPStan could not see). Added `make
phpstan-stub` so the stub is regenerable/discoverable, and refreshed it
(129 -> 131 constants).
main_copy_files already copies the git-tracked migrations/deleted_files.txt into
the archive, so the separate delete_files_list step just re-copied and reprinted
it (looking like regeneration). Drop it from the `main` chain; keep it as an
optional manual preview. LB still needs lb_delete_files_list (migrations/ is not
in LB_DIRS).
`php -l` syntax checking is redundant with the real linters/validators (PHPStan
parses the code, PHP-CS-Fixer and the PHPUnit bootstrap also fail on parse
errors). Remove the script and every reference to it:
- Makefile: drop the `syntax_check` target and its .PHONY entry.
- CI (ci.yml): drop the dedicated `lint` (PHP Syntax Check) job.
- Release workflows (build-release, build_pre-release): drop the "Check syntax"
step from the Quality Gate (PHPUnit remains).
- CONTRIBUTING.md: replace the syntax-check pre-commit guidance with the real
checks (make dev-tools / phpstan / cs / gates / phpunit).
- updates_checklist (en/ru): replace `make syntax_check` with the quality-check
suite and drop the stale "Security scan" snippet that referenced the removed
script and a non-existent tools/run_scan.sh (Semgrep runs automatically in CI).
The committed src/vendor/ is production-only now, so PHPStan / PHP-CS-Fixer are no
longer present until installed. Update the Makefile accordingly:
- Drop the stale 'ships as a committed dev dependency' comments.
- Add a 'dev-tools' target (cd src && composer install) to install the require-dev
tooling locally; CI runs the equivalent step itself.
- phpstan / phpstan-baseline / cs / cs-fix now guard on the binary and print a
clear 'run make dev-tools' hint instead of a cryptic 'No such file' when the
dev tools have not been installed.
Switch from "commit vendor with dev deps + strip at release" to the standard
application model: the committed src/vendor/ is PRODUCTION-ONLY, and dev tooling
(PHPStan, PHP-CS-Fixer + ~37 transitive deps) is installed on demand with
"composer install".
- Regenerate the committed src/vendor/ via "composer install --no-dev"
(34 MB -> ~0.5 MB; only the Composer autoloader + gemorroj/m3u-parser +
chrisyue/php-m3u8 remain). This also stops PHPStan\PharAutoloader registering
in production.
- Commit src/composer.lock (un-ignored) — this is an application, so the lock is
committed to make "composer install" reproducible across dev/CI.
- Revert the release-time strip step (Makefile hooks + tools/build/
strip-dev-vendor.sh) — no longer needed; the archive ships the prod vendor as-is.
- CI: the phpstan and code-style jobs now run "composer install --working-dir=src"
(with tools: composer) to obtain the dev tools before running.
- New gate tools/ci/check-vendor-prod-only.sh (+ make check-vendor-prod-only,
wired into "make gates"): asserts no require-dev package from composer.lock is
committed under src/vendor/ — guards against accidentally committing a
dev-bloated vendor. Inspects git-tracked files, so it is correct even in a CI
job that already ran "composer install".
- Fix verify-lb-archive.sh: LB legitimately ships most of Cli/Commands and
Cli/CronJobs (edge commands + certbot/cache/cleanup crons), so flag only the
genuinely privileged dirs + the specific install/root files, not the whole dirs.
- .gitignore / composer.json notes updated.
Verified before pruning: PHPStan no errors, PHPUnit 303, cs + gates green. After
pruning: PHPUnit 303 (prod-only vendor), all three gates green. Local dev tools
restored afterwards with "composer install" (not committed).
The committed src/vendor/ carries require-dev packages (PHPStan, PHP-CS-Fixer +
their transitive symfony/react/psr deps, ~33 MB) so they are available for local
dev and CI. They must not ship to production — besides the weight, PHPStan's
files-autoload registers PHPStan\PharAutoloader at runtime on every request.
Add tools/build/strip-dev-vendor.sh and hook it into both `make main` and
`make lb` right after the file-copy step (before archiving). It runs against the
staged TEMP_DIR, not the repo:
- removes every dev package dir listed in vendor/composer/installed.json
(dev-package-names) and their bin shims;
- regenerates the autoloader with `composer dump-autoload --no-dev` (drops the
dev files-autoload, incl. the PharAutoloader);
- prunes the emptied vendor namespace dirs.
LB does not stage composer.json (not in LB_ROOT_FILES); the script copies it in
so dump-autoload can run (harmless metadata in the LB archive).
Result: shipped vendor/ goes 34 MB -> ~0.8 MB, contains only the Composer
autoloader + the two prod packages (gemorroj/m3u-parser, chrisyue/php-m3u8);
M3uParser/Chrisyue still autoload and no PharAutoloader is registered. The
committed src/vendor/ is left untouched (dev tooling stays for local use).
The XC_Autoloader fallback was already retired (no-op stub); this deletes it for
good. Resolution is now 100% Composer PSR-4 (+ ModuleLoader for modules), no
legacy scanner, no class-map cache.
- Move `define('MAIN_HOME', ...)` into bootstrap.php (autoload.php used to define
it); bootstrap.php now requires only vendor/autoload.php.
- Drop `\XC_Autoloader::clearCache()/warmCache()` from StartupCommand.
- tests/bootstrap.php: locate-guard + require switched to vendor/autoload.php.
- Entry points that required autoload.php directly — Public/index.php,
Public/admin/index.php, Public/stream/index.php, Public/progress/index.php,
ministra/portal.php and Admin/Reseller TableController — switched to
vendor/autoload.php (defining MAIN_HOME where they did not already). These were
not in the plan's checklist; found via grep during execution.
- phpstan.dist.neon: drop src/autoload.php from scanFiles.
- Makefile: drop autoload.php from LB_ROOT_FILES.
- deleted_files.txt: add autoload.php (client cleanup on update).
- AutoloadOrderTest: now asserts the XC_Autoloader class and file are gone.
- git rm src/autoload.php.
- PSR4_MIGRATION_PLAN.md: mark final-phase step 2 done.
Verified: grep XC_Autoloader:: = 0; php -l clean; PHPStan no errors; PHPUnit
303/303; make gates pass; bootstrap smoke — MAIN_HOME + XC_Bootstrap present,
XC_Autoloader gone, only the Composer autoloader registered.
Adds the automated gates the PSR-4 plan specified but that were verified only
manually per phase:
PHPUnit (run by the existing test job):
- AutoloadOrderTest — Composer autoloader registered; the retired
XC_Autoloader scanner is NOT in the SPL stack; init()
is a no-op; no igbinary class-map cache is written.
- BootstrapPathsTest — no live require/include points at a lowercase renamed
dir (the Фаза-1 grep-gate, as a runtime guard).
- ConsoleDiscoveryTest — console.php FQCN discovery resolves every Cli command
file and the concrete command surface stays stable.
Shell gates (new 'PSR-4 Regression Gates' CI job + 'make gates'):
- tools/ci/check_procedural_use.php — procedural/view files must import every
migrated class they use, with the `use` ABOVE the usage (PHP imports are
positional). Runs with short_open_tag=1 so short-tag templates are analysed.
- tools/ci/verify-lb-archive.sh — reproduces the Makefile LB file selection from
the real LB_* vars and asserts no privileged tree (Admin/Reseller/Player
controllers, Domain/User|Device, Cli/CronJobs|Commands) ships to an LB node
(security blocker 1).
Makefile: cs/cs-fix now force short_open_tag=1; new print-%, check-procedural-use,
verify-lb-archive and aggregate `gates` targets.
Add friendsofphp/php-cs-fixer as a committed Composer dev dependency (src/vendor/,
same model as PHPStan — no composer install on deploy).
- .php-cs-fixer.dist.php: deliberately NARROW ruleset — no_unused_imports,
ordered_imports, no_leading_import_slash, single_line_after_imports,
blank_line_after_namespace, no_extra_blank_lines[use]. NO @PSR12 / indentation
rules: the codebase is tab-indented legacy and a full reformat would be
unreviewable. Indent forced to tabs, LF endings. Excludes vendor, the bundled
Modules/tmdb/lib, tmp/, backups/.
- Makefile: 'make cs' (dry-run, fails on diff — CI) and 'make cs-fix' (apply).
- CI: new 'Code Style (PHP-CS-Fixer)' job running 'make cs' on PHP 8.3.
- .gitignore: ignore .php-cs-fixer.cache.
The downloaded phpstan.phar could not resolve namespaced third-party
packages now living in src/vendor/ (M3uParser, PhpM3u8), failing CI with
class.notFound on StreamService::parseM3U.
- add phpstan/phpstan 2.1.17 as a Composer dev dependency (committed to
src/vendor/); the vendor/bin/phpstan binary auto-loads
src/vendor/autoload.php, so vendor symbols resolve
- Makefile: drop the phpstan-install PHAR download; run src/vendor/bin/phpstan
- remove the stale Core/Parsing/M3uParser excludePath from phpstan.dist.neon
- drop the obsolete tools/phpstan/phpstan.phar gitignore entry
make phpstan: No errors (278 files).
Introduce a committed Composer PSR-4 autoloader without changing class
resolution behavior, as the foundation for the incremental PSR-4 migration.
- src/composer.json: PSR-4 (XcVm\ -> ./, M3uParser\, Chrisyue\PhpM3u8\),
platform php 8.1.33 (deploy runtime), optimize-autoloader/classmap-authoritative
false (live path resolution, no class-map cache). autoload.files left empty:
global functions are still loaded by existing require glue; moving them is
deferred until that glue is removed.
- src/vendor/ + src/composer.lock: committed (deploy path has no Composer);
generated with 'composer update' from src/. Regenerate with dump-autoload.
- src/bootstrap.php, tests/bootstrap.php: require vendor/autoload.php first,
then the legacy autoload.php.
- src/autoload.php: drop the igbinary disk cache (enableFileCache/saveCache/
shutdown handler/root-chown + bottom call); register at the END of the SPL
queue (prepend=false) so Composer wins for XcVm\* and only still-global
classes fall through to the in-memory scanner.
- Makefile: add vendor to LB_DIRS so load-balancer archives ship the loader.
- phpstan.dist.neon: exclude src/vendor/* from analysis.
- .gitignore: document that src/vendor/ is intentionally tracked.
- ci.yml: add composer-audit job (no-op until real require deps exist).
Verified: php -l clean; Composer first / XC_Autoloader last in the SPL stack;
tmp/cache/autoload_map no longer written; PHPUnit 292/292; PHPStan no errors.
- Implemented `ServerInstallCommand` for installing and configuring servers via SSH.
- Created `ProxyInstallFlow` class to handle proxy-specific installation tasks.
- Updated `ServerService` to use the new command structure for server installations.
- Modified API endpoint to initiate server installations using the new command format.
- Enhanced error handling and logging during installation processes.
Co-authored-by: Copilot <copilot@github.com>
Replace separate install/update build targets with a single archive that
serves both purposes. The update script (src/update) now extracts to a
temp directory, removes excluded dirs (binaries, config, user data), and
copies remaining files over the live installation.
Changes:
- Remove main_update, lb_update, lb_update_copy_files,
main_update_copy_files Makefile targets and UPDATE_EXCLUDE_DIRS var
- Move exclude dirs list into src/update (Python) where filtering
actually happens at runtime
- Rewrite doUpdate() to use tempdir extraction with try/finally cleanup
- Make delete_files_list/lb_delete_files_list gracefully skip when
LAST_TAG is empty (warn instead of error)
- Simplify CI workflows: one make command per variant instead of
conditional install + update steps
- Add ARCHITECTURE.md §5.5 documenting update flow
- Update en/ru docs: update-system.md, updates_checklist.md
- Update makefile-build.instructions.md with new targets