Files
XC_VM/src/Core/Config/ConstantsInitializer.php
T
2026-09-23 14:22:56 +03:00

289 lines
11 KiB
PHP

<?php
namespace XcVm\Core\Config;
use RuntimeException;
// Frequently-edited release constants — kept as define()s at the top of the file
// so they are easy to find/edit and the release automation can sed them by name.
// appConfig() reads them back below; init() then skips the already-defined ones.
// Guarded so a pre-definition (e.g. the PHPStan stub) never causes a fatal.
defined('DB_ACCESS_ENABLED') || define('DB_ACCESS_ENABLED', false);
defined('DB_ACCESS_PWD') || define('DB_ACCESS_PWD', '');
defined('DEV_MODE') || define('DEV_MODE', false);
defined('XC_VM_VERSION') || define('XC_VM_VERSION', '2.5.3');
// Per-build watermark stamped into the deploy root by `make main` (see the
// Makefile stamp_release_id target). A source/dev checkout is never stamped, so
// runtime and the licence activation call report 'dev'. Unique per build, so a
// leaked copy can be traced back to the build it came from.
defined('XC_VM_BUILD_ID') || define(
'XC_VM_BUILD_ID',
is_file(__DIR__ . '/../../RELEASE_ID')
? (trim((string) file_get_contents(__DIR__ . '/../../RELEASE_ID')) ?: 'dev')
: 'dev'
);
/**
* Single source of truth for the runtime constants that used to live in the
* procedural prelude files (Paths.php, AppConfig.php, Binaries.php) and in
* XC_Bootstrap::defineStatusConstants().
*
* The value maps (paths(), appConfig(), binaries(), statuses()) are pure and
* unit-testable with different inputs in the same process — unlike the raw
* define() constants they feed, which are one-shot per process. init() is the
* place define() runs for the derived constants (the four frequently-edited
* release constants above are defined at file scope for editability); every boot
* path (ConstantsStage, WebApiBootstrap, StreamingRequestBootstrap, the progress
* endpoint) calls it directly and gets the full constant set regardless of order.
*
* @package XC_VM_Core_Config
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
class ConstantsInitializer {
/**
* Define the path, app-config and binary constants exactly once.
*
* Ordering (MAIN_HOME -> paths -> binaries) is enforced here in code rather
* than by require order: BIN_PATH is established by the path map before the
* binary map derives from it.
*
* @param string|null $mainHome Deploy root with trailing slash. Falls back to
* the MAIN_HOME constant when omitted.
*/
public static function init(?string $mainHome = null): void {
$mainHome ??= defined('MAIN_HOME') ? MAIN_HOME : null;
if ($mainHome === null) {
throw new RuntimeException(
'ConstantsInitializer::init() requires MAIN_HOME to be defined or passed explicitly.'
);
}
self::defineAll(self::paths($mainHome));
$appConfig = self::appConfig();
// OPENSSL_EXTRA is per-install key material: prefer the secret the installer
// wrote, falling back to the historical literal. Resolved only on the
// genuine first define (tests / re-init keep the already-set constant).
if (!defined('OPENSSL_EXTRA')) {
$appConfig['OPENSSL_EXTRA'] = self::resolveOpensslExtra($mainHome . 'config/openssl_extra', $appConfig['OPENSSL_EXTRA']);
}
self::defineAll($appConfig);
// Derive binaries from the now-defined BIN_PATH (an explicit test override
// of BIN_PATH wins over the computed value).
$binPath = defined('BIN_PATH') ? BIN_PATH : $mainHome . 'bin/';
self::defineAll(self::binaries($binPath));
}
/**
* Define the STATUS_* constants exactly once.
*
* Kept separate from init() to preserve the current lazy semantics: status
* codes are only needed by the admin/reseller API handlers.
*/
public static function initStatus(): void {
if (defined('STATUS_FAILURE')) {
return;
}
self::defineAll(self::statuses());
}
/**
* Filesystem path constants, derived from the deploy root.
*
* @param string $mainHome Deploy root with trailing slash.
* @return array<string,string> Constant name => value.
*/
public static function paths(string $mainHome): array {
$content = $mainHome . 'content/';
$tmp = $mainHome . 'tmp/';
$bin = $mainHome . 'bin/';
$storage = $mainHome . 'storage/';
$fanoutRun = $bin . 'xc_fanout/sockets/';
$images = $storage . 'images/';
return [
'CONTENT_PATH' => $content,
'TMP_PATH' => $tmp,
'CONFIG_PATH' => $mainHome . 'config/',
'BIN_PATH' => $bin,
'STORAGE_PATH' => $storage,
'SIGNALS_PATH' => $mainHome . 'signals/',
'FANOUT_RUN_PATH' => $fanoutRun,
'FANOUT_CTL_SOCK' => $fanoutRun . 'control.sock',
'FANOUT_HTTP_SOCK' => $fanoutRun . 'http.sock',
'STREAMS_PATH' => $content . 'streams/',
'EPG_PATH' => $content . 'epg/',
'VOD_PATH' => $content . 'vod/',
'ARCHIVE_PATH' => $content . 'archive/',
'CREATED_PATH' => $content . 'created/',
'DELAY_PATH' => $content . 'delayed/',
'VIDEO_PATH' => $content . 'video/',
'PLAYLIST_PATH' => $content . 'playlists/',
'CONS_TMP_PATH' => $tmp . 'opened_cons/',
'CRONS_TMP_PATH' => $tmp . 'crons/',
'CIDR_TMP_PATH' => $tmp . 'cidr/',
'CACHE_TMP_PATH' => $tmp . 'cache/',
'STREAMS_TMP_PATH' => $tmp . 'cache/streams/',
'SERIES_TMP_PATH' => $tmp . 'cache/series/',
'LINES_TMP_PATH' => $tmp . 'cache/lines/',
'DIVERGENCE_TMP_PATH' => $tmp . 'divergence/',
'FLOOD_TMP_PATH' => $tmp . 'flood/',
'PLAYER_TMP_PATH' => $tmp . 'player/',
'MINISTRA_TMP_PATH' => $tmp . 'ministra/',
'SIGNALS_TMP_PATH' => $tmp . 'signals/',
'LOGS_TMP_PATH' => $tmp . 'logs/',
'WATCH_TMP_PATH' => $tmp . 'watch/',
'IMAGES_PATH' => $images,
'E2_IMAGES_PATH' => $images . 'enigma2/',
];
}
/**
* Application-level constants: versioning, Git repos and feature flags.
*
* @return array<string,scalar> Constant name => value.
*/
public static function appConfig(): array {
return [
'DB_ACCESS_ENABLED' => DB_ACCESS_ENABLED,
'DB_ACCESS_PWD' => DB_ACCESS_PWD,
'DEV_MODE' => DEV_MODE,
'XC_VM_VERSION' => XC_VM_VERSION,
'GIT_OWNER' => 'Vateron-Media',
'GIT_REPO_MAIN' => 'XC_VM',
'GIT_REPO_UPDATE' => 'XC_VM_Update',
'GIT_REPO_BIN' => 'XC_VM_Binaries',
'GIT_REPO_FANOUT' => 'XC_VM_Fanout',
'GIT_REPO_PROXY' => 'XC_VM_Proxy',
'MONITOR_CALLS' => 3,
'OPENSSL_EXTRA' => 'fNiu3XD448xTDa27xoY4',
];
}
/**
* Binary-path constants, derived from the bin directory.
*
* Only the FFmpeg 4.0 anchor is pinned here; other versions are resolved
* dynamically by FfmpegPaths/FfmpegBinaries.
*
* @param string $binPath Bin directory with trailing slash (BIN_PATH).
* @return array<string,string> Constant name => value.
*/
public static function binaries(string $binPath): array {
return [
'PHP_BIN' => $binPath . 'php/bin/php',
'YOUTUBE_BIN' => $binPath . 'yt-dlp',
'FFMPEG_FONT' => $binPath . 'free-sans.ttf',
'GEOLITE2_BIN' => $binPath . 'maxmind/GeoLite2-Country.mmdb',
'GEOLITE2C_BIN' => $binPath . 'maxmind/GeoLite2-City.mmdb',
'GEOISP_BIN' => $binPath . 'maxmind/GeoIP2-ISP.mmdb',
'FFMPEG_BIN_40' => $binPath . 'ffmpeg_bin/4.0/ffmpeg',
'FFPROBE_BIN_40' => $binPath . 'ffmpeg_bin/4.0/ffprobe',
];
}
/**
* STATUS_* result codes used across admin/reseller API handlers.
*
* @return array<string,int> Constant name => value.
*/
public static function statuses(): array {
return [
'STATUS_FAILURE' => 0,
'STATUS_SUCCESS' => 1,
'STATUS_SUCCESS_MULTI' => 2,
'STATUS_CODE_LENGTH' => 3,
'STATUS_NO_SOURCES' => 4,
'STATUS_DISABLED' => 5,
'STATUS_NOT_ADMIN' => 6,
'STATUS_INVALID_EMAIL' => 7,
'STATUS_INVALID_PASSWORD' => 8,
'STATUS_INVALID_IP' => 9,
'STATUS_INVALID_PLAYLIST' => 10,
'STATUS_INVALID_NAME' => 11,
'STATUS_INVALID_CAPTCHA' => 12,
'STATUS_INVALID_CODE' => 13,
'STATUS_INVALID_DATE' => 14,
'STATUS_INVALID_FILE' => 15,
'STATUS_INVALID_GROUP' => 16,
'STATUS_INVALID_DATA' => 17,
'STATUS_INVALID_DIR' => 18,
'STATUS_INVALID_MAC' => 19,
'STATUS_EXISTS_CODE' => 20,
'STATUS_EXISTS_NAME' => 21,
'STATUS_EXISTS_USERNAME' => 22,
'STATUS_EXISTS_MAC' => 23,
'STATUS_EXISTS_SOURCE' => 24,
'STATUS_EXISTS_IP' => 25,
'STATUS_EXISTS_DIR' => 26,
'STATUS_SUCCESS_REPLACE' => 27,
'STATUS_FLUSH' => 28,
'STATUS_TOO_MANY_RESULTS' => 29,
'STATUS_SPACE_ISSUE' => 30,
'STATUS_INVALID_USER' => 31,
'STATUS_CERTBOT' => 32,
'STATUS_CERTBOT_INVALID' => 33,
'STATUS_INVALID_INPUT' => 34,
'STATUS_NOT_RESELLER' => 35,
'STATUS_NO_TRIALS' => 36,
'STATUS_INSUFFICIENT_CREDITS' => 37,
'STATUS_INVALID_PACKAGE' => 38,
'STATUS_INVALID_TYPE' => 39,
'STATUS_INVALID_USERNAME' => 40,
'STATUS_INVALID_SUBRESELLER' => 41,
'STATUS_NO_DESCRIPTION' => 42,
'STATUS_NO_KEY' => 43,
'STATUS_EXISTS_HMAC' => 44,
'STATUS_CERTBOT_RUNNING' => 45,
'STATUS_RESERVED_CODE' => 46,
'STATUS_NO_TITLE' => 47,
'STATUS_NO_SOURCE' => 48,
];
}
/**
* Resolve the per-install OPENSSL_EXTRA secret from a dedicated file the
* installer writes, falling back to the historical literal when absent.
*
* A standalone file (not config.ini) is used deliberately: it is independent
* of the config.ini -> config.enc migration and is never rewritten, so the
* value a fresh install generates is read back identically for the life of
* the install — OPENSSL_EXTRA must never change, since it feeds key/HMAC
* derivation for persisted data (hmac_keys rows, cached image filenames,
* proxy URL keys, stream tokens). The literal fallback keeps existing installs
* — whose data derives from it — decrypting unchanged.
*
* @param string $secretFile Absolute path to the per-install secret file.
* @param string $fallback Historical literal default.
*/
private static function resolveOpensslExtra(string $secretFile, string $fallback): string {
if (is_file($secretFile)) {
$value = trim((string) @file_get_contents($secretFile));
if ($value !== '') {
return $value;
}
}
return $fallback;
}
/**
* define() every entry that is not already defined.
*
* @param array<string,scalar> $constants Constant name => value.
*/
private static function defineAll(array $constants): void {
foreach ($constants as $name => $value) {
if (!defined($name)) {
define($name, $value);
}
}
}
}