diff --git a/etc/skel/www/userMediaStackPanel.php b/etc/skel/www/userMediaStackPanel.php index 2953deb3..d52c9a54 100644 --- a/etc/skel/www/userMediaStackPanel.php +++ b/etc/skel/www/userMediaStackPanel.php @@ -572,9 +572,12 @@ function pmssMediaStackPanelStartCommandBuild(string $home, string $username): s $scriptPath = pmssCustomerHomePath($home, 'install-media-stack.sh'); $pidPath = pmssCustomerHomePath($home, '.install-media-stack-web.pid'); + // Background only the installer: a trailing `&` over the whole && list would also + // background `rm` (racing away the pid file) and keep shell_exec's pipe open until + // the install ends, so the request dies with the installer's lighttpd restart. $innerCommand = 'cd '.escapeshellarg($home) .' && rm -f -- '.escapeshellarg($pidPath) - .' && nohup /bin/bash '.escapeshellarg($scriptPath).' >/dev/null 2>&1 & echo $! > '.escapeshellarg($pidPath); + .' && { nohup /bin/bash '.escapeshellarg($scriptPath).' /dev/null 2>&1 & echo $! > '.escapeshellarg($pidPath).'; }'; return 'HOME='.escapeshellarg($home) .' USER='.escapeshellarg($username) diff --git a/scripts/lib/tests/development/mediaStackPanelTest.php b/scripts/lib/tests/development/mediaStackPanelTest.php index 35e34701..54520147 100644 --- a/scripts/lib/tests/development/mediaStackPanelTest.php +++ b/scripts/lib/tests/development/mediaStackPanelTest.php @@ -359,6 +359,23 @@ public function testStartCommandIncludesPidFileAndScriptPath(): void $this->assertStringContainsAllStrings(['.install-media-stack-web.pid', 'install-media-stack.sh', "USER='alice'"], $command); } + public function testStartCommandReturnsAtOnceAndKeepsPidFile(): void + { + $home = $this->pmssMakeTempDir('pmss-media-start-launch-'); + $this->pmssWriteExecutableFile($home.'/install-media-stack.sh', "#!/bin/bash\nsleep 3\n"); + + $started = microtime(true); + shell_exec(\pmssMediaStackPanelStartCommandBuild($home, 'alice')); + $elapsed = microtime(true) - $started; + $pid = (int) trim((string) @file_get_contents($home.'/.install-media-stack-web.pid')); + + $this->assertTrue($elapsed < 2.0, 'launch must not wait for the installer ('.round($elapsed, 2).'s)'); + $this->assertTrue($pid > 0 && is_dir('/proc/'.$pid), 'pid file must name the running installer'); + if ($pid > 0 && function_exists('posix_kill')) { + posix_kill($pid, 15); + } + } + public function testRecoveryCommandUsesFixedInstallerMode(): void { $command = \pmssMediaStackPanelRecoveryCommandBuild('/home/alice', 'alice');