From db08126d1827d1c4f5c08a5571721645b0b3e860 Mon Sep 17 00:00:00 2001 From: K4r3s-git Date: Fri, 25 Sep 2026 22:57:58 +0200 Subject: [PATCH] fix(media-stack panel): background only the installer in the start command The start command ended in `... && nohup bash install-media-stack.sh >/dev/null 2>&1 & echo $! > PIDFILE`. The trailing `&` applies to the whole && list, so: - `rm -f PIDFILE` runs in the background subshell and usually deletes the pid file right after `echo $!` wrote it, so the panel never reports the install as running. - The background subshell keeps shell_exec's stdout pipe open until the installer exits, so the Start request blocks for the whole install and dies with a 502 when the installer restarts lighttpd/php-cgi at the end. Group the backgrounded installer with the pid write and detach its stdin. Test: a fake installer sleeping 3s; the launch must return at once and leave a pid file naming the running process (fails on the old command after 3.01s). Co-Authored-By: Claude Opus 5.5 --- etc/skel/www/userMediaStackPanel.php | 5 ++++- .../tests/development/mediaStackPanelTest.php | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) 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');