Installer: configurable videos folder

install.php now takes the videos path (form field / --videos= CLI flag),
persists it into config.php, and creates the directory if missing.
config.example.php default changed from a dev path to /srv/courses.
This commit is contained in:
Harwinder Sekhon
2026-07-11 11:06:45 -04:00
parent ceb7aa6780
commit 4bd3d63a76
2 changed files with 58 additions and 14 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
// /srv/media/courses/ // /srv/media/courses/
// Course 1/Intro/Video1.mp4 // Course 1/Intro/Video1.mp4
// Course 2/Module 3/Advanced/lesson.mp4 // Course 2/Module 3/Advanced/lesson.mp4
define('VIDEOS_ROOT', '/home/harwinder/Development/coursehub/videos'); define('VIDEOS_ROOT', '/srv/courses');
define('DB_HOST', '127.0.0.1'); define('DB_HOST', '127.0.0.1');
define('DB_PORT', 3306); define('DB_PORT', 3306);
+52 -8
View File
@@ -2,7 +2,7 @@
// CourseHub installer — sets up the database and schema. // CourseHub installer — sets up the database and schema.
// //
// Browser: open install.php and fill in the form. // Browser: open install.php and fill in the form.
// CLI: php install.php [db_admin_user] [db_admin_pass] // CLI: php install.php [db_admin_user] [db_admin_pass] [--videos=/path/to/courses]
// //
// Steps performed: // Steps performed:
// 1. Creates config.php from config.example.php if missing (edit it, re-run). // 1. Creates config.php from config.example.php if missing (edit it, re-run).
@@ -51,8 +51,17 @@ require_once __DIR__ . '/config.php';
// -- Collect input ----------------------------------------------------------- // -- Collect input -----------------------------------------------------------
if ($isCli) { if ($isCli) {
$adminUser = $_SERVER['argv'][1] ?? ''; $args = array_slice($_SERVER['argv'], 1);
$adminPass = $_SERVER['argv'][2] ?? ''; $videosRoot = '';
foreach ($args as $i => $a) {
if (str_starts_with($a, '--videos=')) {
$videosRoot = substr($a, strlen('--videos='));
unset($args[$i]);
}
}
$args = array_values($args);
$adminUser = $args[0] ?? '';
$adminPass = $args[1] ?? '';
$appUser = ''; $appUser = '';
$appPass = ''; $appPass = '';
$run = true; $run = true;
@@ -61,6 +70,7 @@ if ($isCli) {
$adminPass = $_POST['admin_pass'] ?? ''; $adminPass = $_POST['admin_pass'] ?? '';
$appUser = trim($_POST['app_user'] ?? ''); $appUser = trim($_POST['app_user'] ?? '');
$appPass = $_POST['app_pass'] ?? ''; $appPass = $_POST['app_pass'] ?? '';
$videosRoot = trim($_POST['videos_root'] ?? '');
$run = $_SERVER['REQUEST_METHOD'] === 'POST'; $run = $_SERVER['REQUEST_METHOD'] === 'POST';
} }
@@ -70,7 +80,7 @@ if (!$run) {
} }
// -- Run the installation ---------------------------------------------------- // -- Run the installation ----------------------------------------------------
[$done, $steps, $notice] = performInstall($adminUser, $adminPass, $appUser, $appPass); [$done, $steps, $notice] = performInstall($adminUser, $adminPass, $appUser, $appPass, $videosRoot);
if ($done && @file_put_contents($lockFile, date('c') . "\n") === false) { if ($done && @file_put_contents($lockFile, date('c') . "\n") === false) {
$steps[] = [false, 'Could not write .install.lock — the installer will stay re-runnable. ' $steps[] = [false, 'Could not write .install.lock — the installer will stay re-runnable. '
. 'Delete or protect install.php on a production server.']; . 'Delete or protect install.php on a production server.'];
@@ -80,7 +90,7 @@ respond($steps, $done, $notice);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function performInstall(string $adminUser, string $adminPass, function performInstall(string $adminUser, string $adminPass,
string $appUser, string $appPass): array string $appUser, string $appPass, string $videosRoot): array
{ {
$steps = []; $steps = [];
$notice = ''; $notice = '';
@@ -162,9 +172,33 @@ function performInstall(string $adminUser, string $adminPass,
} }
} }
if (!is_dir(VIDEOS_ROOT)) { // Videos folder: optionally persist a new path into config.php, then
$steps[] = [false, 'Warning: VIDEOS_ROOT (' . VIDEOS_ROOT . ') does not exist yet — ' // make sure the effective path exists.
. 'set it in config.php before scanning.']; $effectiveRoot = VIDEOS_ROOT;
if ($videosRoot !== '' && $videosRoot !== VIDEOS_ROOT) {
$cfgFile = __DIR__ . '/config.php';
$cfg = file_get_contents($cfgFile);
$new = preg_replace(
"/define\('VIDEOS_ROOT',\s*'[^']*'\);/",
"define('VIDEOS_ROOT', '" . addslashes($videosRoot) . "');",
$cfg, 1, $replaced
);
if ($replaced === 1 && @file_put_contents($cfgFile, $new) !== false) {
$effectiveRoot = $videosRoot;
$steps[] = [true, 'Set VIDEOS_ROOT in config.php to ' . $videosRoot . '.'];
} else {
$steps[] = [false, 'Could not update config.php — set VIDEOS_ROOT to '
. $videosRoot . ' manually.'];
}
}
if (!is_dir($effectiveRoot)) {
if (@mkdir($effectiveRoot, 0755, true)) {
$steps[] = [true, 'Created videos folder ' . $effectiveRoot . '.'];
} else {
$steps[] = [false, 'Warning: videos folder ' . $effectiveRoot . ' does not exist '
. 'and could not be created — create it (or fix VIDEOS_ROOT in config.php) '
. 'before scanning.'];
}
} }
$notice = 'Installation complete. Open the app, log in, and use "Rescan library" ' $notice = 'Installation complete. Open the app, log in, and use "Rescan library" '
@@ -237,6 +271,16 @@ function renderPage(?array $steps, bool $done, string $notice): void
<div class="col"><input class="form-control" type="password" name="admin_pass" placeholder="password"></div> <div class="col"><input class="form-control" type="password" name="admin_pass" placeholder="password"></div>
</div> </div>
</div> </div>
<div>
<div class="fw-semibold mb-1">Videos folder</div>
<div class="form-text mb-2">
Absolute path on this server containing your course folders.
Saved to config.php; created if it doesn't exist.
</div>
<input class="form-control" name="videos_root"
value="<?= $e($_POST['videos_root'] ?? (defined('VIDEOS_ROOT') ? VIDEOS_ROOT : '')) ?>"
placeholder="/srv/courses">
</div>
<div> <div>
<div class="fw-semibold mb-1">First login account <span class="text-secondary fw-normal">(optional)</span></div> <div class="fw-semibold mb-1">First login account <span class="text-secondary fw-normal">(optional)</span></div>
<div class="row g-2"> <div class="row g-2">