Commit Graph
16 Commits
Author SHA1 Message Date
Divarion_D 758a9cab0b style: apply K&R + tab formatting across the codebase (make cs-fix)
Mechanical, behaviour-preserving reformat produced by 'make cs-fix' under
the new build/phpcs.xml.dist ruleset: K&R braces, tab indentation, and the
other whitespace normalisations. No logic changes.
2026-09-13 14:14:23 +03:00
Divarion_D a242662fb6 fix(update): guard update/rollback against non-root invocation
The update/rollback flow shells out to `sudo` (the Python updater), which
only works when the process is already root — in XC_VM's model the xc_vm
user has no sudoers entry. Run as a non-root user, the nested sudo prompts
for a password it can never read (no tty) and the updater silently never
launches, leaving the server stranded at status=5 (updating) with nothing
left to reset it.

Add a private assertRunAsRoot() check at the top of the update and
rollback cases, before any status change, plus a launcher pre-flight
(updater script + interpreter present) so status=5 is only set once a
launch is actually possible.
2026-09-10 19:33:25 +03:00
Divarion_D 7caff1513a feat(updates): per-repository update channels (stable/beta)
Replace the single global `update_channel` setting with independent
per-repository channels for MAIN (panel), BIN (compiled binaries) and
FANOUT (xc_fanout daemon). The GeoLite/ASN (UPDATE) and proxy (PROXY)
repos have no channel of their own and follow the MAIN channel.

- Add XcVm\Core\Updates\UpdateChannels resolver (main/bin/fanout/forRepo).
- Migration 016: add update_channel_main/bin/fanout, copy the previous
  selection into all three (unstable -> beta), drop update_channel.
- Settings UI: one "Updates" section with three channel selectors.
- Route every GitHubReleases call site through the resolver.
- LbInstallFlow: resolve the BIN tag channel-aware (was always /latest),
  so a BIN beta channel provisions LB nodes with pre-release binaries.
- XcvmCoreCommand: map the BIN channel to a git branch (stable -> main,
  beta -> beta) with a fallback to the stable branch when beta is absent.
2026-09-09 20:09:34 +03:00
Divarion_D 55f6fcf206 feat(update): add per-server version rollback from the panel
Adds a "safety net" rollback so a server can be downgraded to an earlier
release without a manual redeploy, mirroring the existing update flow.

- GitHubReleases: getVersionFile() fetches a specific version's asset (not
  just latest); getPreviousVersions() lists the newest releases older than a
  given version, each flagged beta (GitHub prerelease). getReleases() now
  delegates to a shared getFilteredReleases() that keeps the prerelease flag.
- UpdateCommand: new `rollback <version>` case — validates the target is a
  real, strictly-older release; on MAIN takes an automatic DB backup first
  (aborting if it fails); downloads the exact version (main/lb_update asset),
  verifies MD5, and hands off to the same python `update` applier.
- RootSignalsCronJob: dispatch the `rollback` signal to `console.php update
  rollback <version>` (version regex-validated, escapeshellarg'd).
- api.php: `rollback_versions` (channel-aware list, optional per-server base
  version) and server `sub=rollback` (validates version, writes the signal).
- servers.php: per-server "Rollback Version" action (dropdown item + button)
  opening a modal to pick a version; beta builds show "(beta)". Works for
  MAIN and each LB independently.
- docs: rollback sections in server-update.md and update-system.md.

The downgrade reuses the version-agnostic python applier, so binaries,
config and user data are preserved. Migrations are forward-only and are not
rolled back; the automatic MAIN backup is the recovery path.
2026-08-26 19:27:29 +03:00
Divarion_D b1e78aa35f refactor(cli): use safe settings accessors, hoist cron reads, clear baseline debt
Migrate SettingsManager::getAll()['key'] to SettingsManager::get('key') across
CLI commands and cron jobs. get() is used here (rather than the typed
accessors) because it returns the raw value unchanged, preserving the diverse
call sites verbatim: `=== null` checks, `?? ''`/`?? false` fallbacks, string
concatenation, numeric comparisons and ternaries. It still adds a default,
silencing PHP 8 "Undefined array key" warnings. Whole-array `$rSettings =
getAll()` grabs are left as-is.

Hoist the request-invariant redis_handler flag to method entry in
UsersCronJob/StreamsCronJob (loadCron/processDeletions), where it was read deep
inside nested per-connection loops.

Also fix two long-standing PHPStan baseline entries by initialising
$rRedisDelete and $rLiveKeys unconditionally in UsersCronJob (they were only
assigned inside `if ($rRedis)` but read under correlated guards) and drop the
now-stale baseline entries.
2026-08-24 21:52:56 +03:00
Divarion_D 9aaf6315ee feat(binaries): xcvm_core extension self-update (unblocks LB Redis)
The xcvm_core PHP extension is mirrored into the binaries repo tree — decoupled
from the heavy per-distro runtime bundle — under bin/xcvm_core/ as per-OpenSSL-ABI
archives (openssl1.1 / openssl3) + version.json + SHA256SUMS. Nothing panel-side
kept it current, so nodes drifted onto whatever build their install archive shipped
— and critically the LB's build lacked XC_VM::config_set_redis, so an LB could not
point its Redis at the main server and every redis_handler connection failed with
LINE_CREATE_FAIL.

Add XcvmCoreCommand (console.php xcvm_core), modelled on FanoutBinaryCommand:
- resolves the latest version from version.json (raw, main branch);
- detects this host's OpenSSL ABI (libcrypto.so.3 -> openssl3, else openssl1.1;
  both present -> prefer openssl3 with a load-test fallback to the other);
- downloads the matched archive, verifies SHA-256;
- installs the .so ATOMICALLY with a backup + fresh-php load-test + rollback, so a
  wrong-ABI/broken extension never takes php-fpm down, then USR2-reloads php-fpm;
- version-compared via a sidecar marker next to the .so (idempotent).

Wired into UpdateCommand (post-update) and RootSignalsCronJob (hourly self-heal),
not LB-stripped, so every node converges. Proven on the canary LB: none -> 2.1.0
(openssl3), config_set_redis appears, StatusCommand::configureRedisLb points Redis
at main, RedisManager connects, and a viewer redirected to the LB is served
video/mp2t by the daemon (/connections + /rates populated, php-fpm flat at 1).
2026-08-20 16:56:07 +03:00
Divarion_D 1d0f0d6674 feat(streaming): auto-install/update the xc_fanout daemon (ADR 0003, Phase G)
Nothing pulled or bootstrapped the daemon: the panel archive doesn't ship the
binary (it's a GitHub-release asset via `fanout_binary`), and `fanout_binary`
was invoked by no installer, updater or cron. So a fresh node/LB never got the
daemon, and an updated panel kept an old one. Wire it up:

- UpdateCommand: after a panel update, run `console.php fanout_binary` (root,
  background, best-effort) so the daemon is refreshed to match the new version.
- RootSignalsCronJob (root, every node incl. LBs): hourly self-heal — throttled
  `fanout_binary` via a stamp, first pass immediate, so a fresh install/LB gets
  the daemon within a minute and version drift is corrected.
- service: the keepalive loop now runs UNCONDITIONALLY and re-checks for the
  binary each 2s pass instead of gating the whole block on `-x` at boot. This
  closes the bootstrap gap — fanout_binary only pkills and relies on a running
  keepalive to respawn, which didn't exist on a node whose binary arrived after
  boot; now the loop launches the daemon within ~2s of the binary appearing.

fanout_binary is idempotent (downloads only on a version mismatch, only when
GitHub is reachable), so polling is safe. php -l + make gates green.
2026-08-17 22:08:18 +03:00
Divarion-D fc0fdb2f29 Fix documentation links in Reseller controllers and related classes to point to the correct GitHub repository URL. Remove unused directory registration code in autoload.php. 2026-06-25 20:32:18 +03:00
Divarion-D 9eec63937e style: import/namespace hygiene across src (PHP-CS-Fixer)
Apply 'make cs-fix' — 493 files. Mechanical, import-block only:
- sort use statements alphabetically (class/function/const grouped);
- drop imports left unused by the PSR-4 migration (e.g. classes referenced by
  leading-backslash FQCN whose redundant 'use' the automated insertion had added);
- one blank line after namespace and after the import block; collapse stray
  blank lines around use.

No logic changes. Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; and a
temporary PHPStan pass over src/Public/Controllers confirms no still-referenced
import was removed (0 unresolved classes). 'use' after inline HTML in view
templates is valid and aliases correctly (verified) — those imports are sorted too.
2026-06-25 20:24:36 +03:00
Divarion-D 3377236d77 chore(psr4): phase 3 (Cli) — namespace Cli + switch console.php to FQCN discovery
Namespace all 55 Cli classes into XcVm\Cli (CommandInterface, CommandRegistry,
CronTrait, DaemonTrait), XcVm\Cli\Commands (28) and XcVm\Cli\CronJobs (23);
migration_logic.php stays procedural/global.

- console.php: switch command discovery from basename==classname + manual require
  to FQCN resolution — each scan dir maps to its PSR-4 namespace, classes load via
  Composer, implementsInterface(CommandInterface::class). Drop the manual
  CommandInterface/CommandRegistry requires. This is the atomic switch the plan
  required to land with the Cli namespacing.
- Commands/CronJobs get 'use XcVm\Cli\CommandInterface;' (implements) and the
  trait imports 'use XcVm\Cli\CronTrait;'/'DaemonTrait;'; rewrite the modules'
  'use CommandRegistry;' → FQCN; qualify built-ins/global (\ReflectionClass,
  \PDO, \Exception, \RuntimeException, \XC_Autoloader, \XC_VM).
- Fixtures: 'use CommandRegistry;' → FQCN; InterfaceContractTest registerCommands
  param-type → FQCN. phpstan-baseline.neon regenerated.

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; FQCN discovery resolves
51 classes (49 implement CommandInterface); no mangled FQCNs; re-sweep clean.
2026-06-25 19:03:39 +03:00
Divarion-D 42d9ca8ea0 chore(psr4): phase 3 — namespace Domain layer
Namespace all of Domain into XcVm\Domain\<Subdir> (33 classes across Bouquet,
Device, Epg, Line, Security, Server, Stream, User, Vod).

- Add namespace to every Domain class; add use to referrers across src/ and
  tests/; convert the leading-backslash refs qualified in earlier Core commits
  (\UserRepository, \ServerRepository, \BouquetService, \CategoryService,
  \ConnectionTracker, \BlocklistService, ...) to their FQCNs.
- Qualify still-global / built-in deps inside Domain with leading backslash
  (\RedisManager, \TMDB, \WatchService, \FFprobeRunner, \ProcessChecker,
  \Exception, \DateTime, \PDO, ...) — Infrastructure/Streaming/module classes
  migrate later.
- BruteforceGuard: string guards class_exists('ServerRepository'/'BlocklistService')
  → ::class FQCN; drop the now-always-true method_exists check.
- phpstan-baseline.neon regenerated (292→291).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; leading-backslash
re-sweep across Domain classes is clean.
2026-06-25 18:39:28 +03:00
Divarion-D da291e20f8 chore(psr4): phase 3 (Core) — namespace remaining Core subdirs
Namespace the rest of Core, completing the Core layer:
- Backup, Boundary, Cache (CacheInterface/FileCache/RedisCache), Diagnostics,
  GeoIP (GeoIPService/MaxMindUpdater), Init (LegacyInitializer), Localization
  (Translator), Process (Thread/Multithread/ProcessManager), Updates
  (GitHubReleases), Util (NetworkUtils, AdminHelpers, Encryption, GeoIP,
  ImageUtils/ImageResizeService, Mobile_Detect, StreamUtils, SystemInfo,
  TimeUtils), Validation (InputValidator) → XcVm\Core\<Subdir>.
- Rename GithubReleases.php → GitHubReleases.php so the file matches its class
  name (PSR-4 is case-sensitive); fix the WebApiBootstrap require accordingly.
- Qualify built-ins (\Exception, \DateTime, \DateTimeZone, \Redis,
  \RuntimeException, \BadMethodCallException, ...) and still-global deps
  (\ServerRepository, \CacheReader, \DatabaseFactory, \BouquetService,
  \CategoryService, \FfmpegPaths, \StreamSorter, \XC_VM). LegacyInitializer's
  Domain calls stay \-qualified (the known Core→Domain exception).
- Add use to referrers; fix leading-backslash refs from earlier commits; fix
  string class refs class_exists('Translator'/'NetworkUtils'/...) → ::class.
  Remove the duplicate global 'use BoundaryInterface;' in MinistraModule.
- phpstan-baseline.neon regenerated.

Core is now fully namespaced (procedural constant/error/RequestGuard files stay
global by design; M3uParser/PhpM3u8 remain vendored). Verified: php -l clean;
PHPStan no errors; PHPUnit 295/295; leading-backslash re-sweep clean.
2026-06-24 22:35:33 +03:00
Divarion-D 42db4be509 chore(psr4): phase 3 (Core hubs) — namespace Core/Database
Move Core/Database into XcVm\Core\Database (DatabaseHandler, Database,
MigrationRunner, QueryHelper). First of the Core hub sub-layers.

- Namespace the 4 classes; DatabaseHandler extends Database (same namespace);
  built-ins/ioncube qualified (\PDO, \PDOException, \Exception, \Throwable,
  \XC_VM); Database keeps its 'use XcVm\Core\Logging\FileLogger;'.
- Add 'use XcVm\Core\Database\...;' to referencing files (DatabaseHandler 51,
  Database 64, QueryHelper 29, MigrationRunner 3) + 2 test files (PHPStan does not
  analyse tests/, so PHPUnit is the gate there).
- Rewrite pre-existing leading-backslash global refs (\DatabaseHandler etc., e.g.
  in @param docblocks of ResellerApiDispatcher/ResellerTableRenderer) to the full
  FQCN \XcVm\Core\Database\... — a 'use' import does not cover a leading-\
  reference. Done with a lookbehind so FQCN continuations and use-lines are intact.
- phpstan-baseline.neon regenerated (292→292; pre-existing Database/migration_logic
  findings re-anchored after class names in messages gained the namespace).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295.
2026-06-24 21:15:06 +03:00
Divarion-D 8ac1908554 chore(psr4): phase 3 (Core leaf) — namespace SettingsManager + SettingsRepository
Move the last two Core/Config classes into XcVm\Core\Config. SettingsManager
has the largest fan-out of the whole migration (referenced by ~224 files).

- Namespace SettingsManager (self-contained singleton, no class deps) and
  SettingsRepository (\FileCache:: qualified).
- Add 'use XcVm\Core\Config\SettingsManager;' to 224 referencing files and
  'use ...\SettingsRepository;' to 12 — call sites (SettingsManager::get(), etc.)
  unchanged. Done with a token-based inserter (after namespace/declare/<?php,
  idempotent, same-namespace files skipped).
- ToolsCommand::processRecaptcha: drop dead class_exists('SettingsManager') +
  method_exists guard (always autoloadable now) → call SettingsManager::clearCache()
  directly. This was the only string-literal class reference.

Core/Config is now fully namespaced (ConfigReader, DomainResolver, SettingsManager,
SettingsRepository); the procedural constant files (AppConfig/Binaries/Paths)
remain global by design.

Verified: php -l clean (226 files); PHPStan no errors (baseline unchanged — it is
line-independent so the added use-lines don't disturb it); PHPUnit 295/295; all
Config FQCNs resolve via Composer; sample Public referrers lint-clean.
2026-06-24 20:52:00 +03:00
Divarion-D 15750f314c chore(psr4): phase 3 (Core leaf) — namespace Core/Logging
Move the Core/Logging layer into XcVm\Core\Logging (Logger, LoggerInterface,
FileLogger, DatabaseLogger, UpdateLogger). Composer PSR-4 now resolves them.

- Add namespace to the 5 logging classes; built-in \Throwable qualified in
  Logger; same-namespace LoggerInterface references unqualified.
- Add 'use XcVm\Core\Logging\...;' to the 16 referencing files (bootstrap,
  web/stream bootstraps, stream entry points, Database, EPG, update command/cron,
  auth, ministra) — call sites unchanged.
- Authenticator::logRecaptcha: drop the now-dead class_exists/method_exists
  guard (Logger is always autoloadable post-Composer) and call Logger::log
  directly.

Procedural Core/Error + Core/Config constants files (ErrorCodes/ErrorHandler/
AppConfig/Binaries/Paths — no classes) intentionally left global per plan.

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295.
2026-06-24 20:33:55 +03:00
Divarion-D 1a296d0985 chore(psr4): phase 1 — rename 7 top-level dirs to PascalCase
Atomic case-rename of the seven class-holding source directories to match the
PSR-4 namespace casing, plus every consequent path reference. No code logic
changes — pure structural rename. (config/content/resources/signals/migrations/
ministra/storage/tmp/vendor/www/bin stay lowercase.)

- git mv: core→Core, domain→Domain, infrastructure→Infrastructure,
  streaming→Streaming, modules→Modules, cli→Cli, public→Public (module subdirs
  like Modules/plex stay lowercase; loaded by ModuleLoader, not Composer).
- PHP filesystem paths updated across src/ + tests/: require/include, glob/scandir
  bases, view includes, asset/nginx-alias paths, autoload.php registerDirectories(),
  ModuleLoader/ModuleManager module roots, console.php discovery dirs.
- src/composer.json PSR-4 vendored-lib paths → Core/Parsing/...; vendor/ regenerated.
- nginx.conf: docroot + SCRIPT_FILENAME → Public/. SCRIPT_NAME and the public-facing
  /streaming/*.php compat routes are URLs, left unchanged.
- Build/CI: Makefile LB_DIRS / LB_DIRS_TO_REMOVE / LB_FILES_TO_REMOVE PascalCased
  (closes the LB-archive privileged-leak blocker); phpstan.dist.neon paths/
  scanDirectories/excludePaths; phpunit.xml.dist coverage; phpstan-baseline.neon
  regenerated (294→294 errors, no new resolution failures).
- migrations/deleted_files.txt: old lowercase trees listed for client cleanup
  (assumes case-sensitive FS — the supported Linux target).

Verified: grep-gate 0 live lowercase-dir require/include in src+tests; php -l clean;
PHPUnit 292/292; PHPStan no errors; Composer first / XC_Autoloader last in SPL stack;
global classes resolve from the renamed dirs.
2026-06-24 20:10:34 +03:00