Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
73de319
chore(deps): update 3rdparty pointer
vitormattos Apr 8, 2026
59d4101
feat(process): add process manager service
vitormattos Apr 8, 2026
2859d1f
test(process): add process manager unit tests
vitormattos Apr 8, 2026
cba9430
refactor(cfssl): manage process lifecycle via process manager
vitormattos Apr 8, 2026
8c565fc
test(cfssl): cover binary checks and process integration
vitormattos Apr 8, 2026
e4b6cbc
refactor(install): use process api for async install
vitormattos Apr 8, 2026
6a6ae37
test(install): cover async process pid handling
vitormattos Apr 8, 2026
d8abd81
refactor(migration): stop workers via process manager
vitormattos Apr 8, 2026
a540968
refactor(worker): remove worker counter service
vitormattos Apr 8, 2026
b746f68
test(worker): remove worker counter tests
vitormattos Apr 8, 2026
598dc46
refactor(worker): remove worker stopper service
vitormattos Apr 8, 2026
1282766
refactor(worker): calculate workers from process manager
vitormattos Apr 8, 2026
1600b18
refactor(worker): start workers via symfony process
vitormattos Apr 8, 2026
210ffbd
test(worker): add worker starter unit tests
vitormattos Apr 8, 2026
6946973
test(worker): improve worker health scenarios
vitormattos Apr 8, 2026
cc30543
test(worker): improve worker job counter scenarios
vitormattos Apr 8, 2026
3bbd4e4
chore(psalm): update baseline
vitormattos Apr 8, 2026
ca0641e
chore(deps): update 3rdparty pointer after ci fix
vitormattos Apr 8, 2026
7709632
chore(deps): update 3rdparty submodule to main
vitormattos Apr 8, 2026
99b73a4
refactor(process): orchestrate manager with collaborators
vitormattos Apr 8, 2026
d663015
feat(process): add listening pid resolver
vitormattos Apr 8, 2026
d53ff8a
feat(process): add process signaler service
vitormattos Apr 8, 2026
5db20e4
refactor(cfssl): use process manager fallback orchestration
vitormattos Apr 8, 2026
04f3347
test(process): cover manager fallback business rules
vitormattos Apr 8, 2026
06e10a6
test(process): add listening resolver unit coverage
vitormattos Apr 8, 2026
979f0d9
test(process): add process signaler unit coverage
vitormattos Apr 8, 2026
37610cb
test(cfssl): improve handler unit scenarios
vitormattos Apr 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty
Submodule 3rdparty updated 30 files
+1 −0 composer.json
+67 −2 composer.lock
+1 −1 composer/composer/autoload_classmap.php
+1 −1 composer/composer/autoload_psr4.php
+3 −3 composer/composer/autoload_static.php
+68 −0 composer/composer/installed.json
+21 −0 composer/symfony/process/Exception/ExceptionInterface.php
+21 −0 composer/symfony/process/Exception/InvalidArgumentException.php
+21 −0 composer/symfony/process/Exception/LogicException.php
+42 −0 composer/symfony/process/Exception/ProcessFailedException.php
+36 −0 composer/symfony/process/Exception/ProcessSignaledException.php
+61 −0 composer/symfony/process/Exception/ProcessTimedOutException.php
+24 −0 composer/symfony/process/Exception/RunProcessFailedException.php
+21 −0 composer/symfony/process/Exception/RuntimeException.php
+85 −0 composer/symfony/process/ExecutableFinder.php
+91 −0 composer/symfony/process/InputStream.php
+19 −0 composer/symfony/process/LICENSE
+29 −0 composer/symfony/process/Messenger/RunProcessContext.php
+26 −0 composer/symfony/process/Messenger/RunProcessMessage.php
+31 −0 composer/symfony/process/Messenger/RunProcessMessageHandler.php
+78 −0 composer/symfony/process/PhpExecutableFinder.php
+64 −0 composer/symfony/process/PhpProcess.php
+137 −0 composer/symfony/process/PhpSubprocess.php
+180 −0 composer/symfony/process/Pipes/AbstractPipes.php
+54 −0 composer/symfony/process/Pipes/PipesInterface.php
+113 −0 composer/symfony/process/Pipes/UnixPipes.php
+153 −0 composer/symfony/process/Pipes/WindowsPipes.php
+1,388 −0 composer/symfony/process/Process.php
+60 −0 composer/symfony/process/ProcessUtils.php
+30 −0 composer/symfony/process/composer.json
106 changes: 67 additions & 39 deletions lib/Handler/CertificateEngine/CfsslHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
use OCA\Libresign\Service\CertificatePolicyService;
use OCA\Libresign\Service\Crl\CrlRevocationChecker;
use OCA\Libresign\Service\Install\InstallService;
use OCA\Libresign\Service\Process\ProcessManager;
use OCA\Libresign\Vendor\Symfony\Component\Process\Process;
use OCP\Files\AppData\IAppDataFactory;
use OCP\IAppConfig;
use OCP\IConfig;
Expand All @@ -39,6 +41,7 @@
*/
class CfsslHandler extends AEngineHandler implements IEngineHandler {
public const CFSSL_URI = 'http://127.0.0.1:8888/api/v1/cfssl/';
private const PROCESS_SOURCE = 'cfssl';

/** @var Client */
protected $client;
Expand All @@ -58,6 +61,7 @@ public function __construct(
protected CrlMapper $crlMapper,
protected LoggerInterface $logger,
CrlRevocationChecker $crlRevocationChecker,
private ProcessManager $processManager,
) {
parent::__construct(
$config,
Expand Down Expand Up @@ -245,10 +249,11 @@ private function gencert(): void {
$configPath = $this->getCurrentConfigPath();
$csrFile = $configPath . '/csr_server.json';

$cmd = escapeshellcmd($binary) . ' gencert -initca ' . escapeshellarg($csrFile);
$output = shell_exec($cmd);
$process = $this->createProcess([$binary, 'gencert', '-initca', $csrFile]);
$process->run();
$output = $process->getOutput();

if (!$output) {
if (!$process->isSuccessful() || $output === '') {
throw new \RuntimeException('cfssl without output.');
}

Expand Down Expand Up @@ -320,11 +325,26 @@ private function wakeUp(): void {
throw new LibresignException('CFSSL not configured.');
}
$this->cfsslServerHandler->updateExpirity($this->getCaExpiryInDays());
$cmd = 'nohup ' . $binary . ' serve -address=127.0.0.1 '
. '-ca-key ' . $configPath . DIRECTORY_SEPARATOR . 'ca-key.pem '
. '-ca ' . $configPath . DIRECTORY_SEPARATOR . 'ca.pem '
. '-config ' . $configPath . DIRECTORY_SEPARATOR . 'config_server.json > /dev/null 2>&1 & echo $!';
shell_exec($cmd);
$process = $this->createProcess([
$binary,
'serve',
'-address=127.0.0.1',
'-ca-key', $configPath . DIRECTORY_SEPARATOR . 'ca-key.pem',
'-ca', $configPath . DIRECTORY_SEPARATOR . 'ca.pem',
'-config', $configPath . DIRECTORY_SEPARATOR . 'config_server.json',
]);
$process->setOptions(['create_new_console' => true]);
$process->setTimeout(null);
$process->disableOutput();
$process->start();

$pid = (int)($process->getPid() ?? 0);
if ($pid > 0) {
$this->processManager->register(self::PROCESS_SOURCE, $pid, [
'uri' => $this->getCfsslUri(),
]);
}

$loops = 0;
while (!$this->portOpen() && $loops <= 4) {
sleep(1);
Expand All @@ -349,38 +369,37 @@ private function portOpen(): bool {
}

private function getServerPid(): int {
$cmd = 'ps -eo pid,command|';
$cmd .= 'grep "cfssl.*serve.*-address"|'
. 'grep -v grep|'
. 'grep -v defunct|'
. 'sed -e "s/^[[:space:]]*//"|cut -d" " -f1';
$output = shell_exec($cmd);
if (!is_string($output)) {
return 0;
}
$pid = trim($output);
return (int)$pid;
}

/**
* Parse command
*
* Have commands that need to be executed as sudo otherwise don't will work,
* by example the command runuser or kill. To prevent error when run in a
* GitHub Actions, these commands are executed prefixed by sudo when exists
* an environment called GITHUB_ACTIONS.
*/
private function parseCommand(string $command): string {
if (getenv('GITHUB_ACTIONS') !== false) {
$command = 'sudo ' . $command;
}
return $command;
$uri = $this->getCfsslUri();
return $this->processManager->findRunningPid(
self::PROCESS_SOURCE,
fn (array $entry): bool => ($entry['context']['uri'] ?? '') === $uri,
);
}

private function stopIfRunning(): void {
$pid = $this->getServerPid();
if ($pid > 0) {
exec($this->parseCommand('kill -9 ' . $pid));
$uri = $this->getCfsslUri();
$port = (int)(parse_url($uri, PHP_URL_PORT) ?? 0);
$this->processManager->setSourceHint(self::PROCESS_SOURCE, [
'uri' => $uri,
'port' => $port,
]);

$this->processManager->findRunningPid(
self::PROCESS_SOURCE,
fn (array $entry): bool => ($entry['context']['uri'] ?? '') === $uri,
);

foreach ($this->processManager->listRunning(self::PROCESS_SOURCE) as $entry) {
if (($entry['context']['uri'] ?? '') !== $uri) {
continue;
}

$pid = (int)($entry['pid'] ?? 0);
if ($pid <= 0) {
continue;
}
$this->processManager->stopPid($pid, SIGKILL);
$this->processManager->unregister(self::PROCESS_SOURCE, $pid);
}
}

Expand Down Expand Up @@ -452,8 +471,10 @@ private function checkBinaries(): array {
->setTip('Run occ libresign:install --cfssl'),
];
}
$version = shell_exec("$binary version");
if (!is_string($version) || empty($version)) {
$process = $this->createProcess([$binary, 'version']);
$process->run();
$version = $process->getOutput();
if (!$process->isSuccessful() || empty($version)) {
return [
(new ConfigureCheckHelper())
->setErrorMessage(sprintf(
Expand Down Expand Up @@ -502,6 +523,13 @@ private function checkBinaries(): array {
return $return;
}

/**
* @param string[] $command
*/
protected function createProcess(array $command): Process {
return new Process($command);
}

/**
* Get Authority Key Identifier from certificate (needed for CFSSL revocation)
*
Expand Down
8 changes: 5 additions & 3 deletions lib/Migration/StopRunningWorkers.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@

namespace OCA\Libresign\Migration;

use OCA\Libresign\Service\Worker\WorkerStopper;
use OCA\Libresign\Service\Process\ProcessManager;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use Psr\Log\LoggerInterface;

class StopRunningWorkers implements IRepairStep {
private const PROCESS_SOURCE = 'worker';

public function __construct(
private WorkerStopper $stopper,
private ProcessManager $processManager,
private LoggerInterface $logger,
) {
}
Expand All @@ -28,7 +30,7 @@ public function getName(): string {
#[\Override]
public function run(IOutput $output): void {
try {
$stopped = $this->stopper->stopAll();
$stopped = $this->processManager->stopAll(self::PROCESS_SOURCE, SIGTERM);
if ($stopped > 0) {
$output->info('Stopped ' . $stopped . ' LibreSign worker(s).');
}
Expand Down
51 changes: 32 additions & 19 deletions lib/Service/Install/InstallService.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
use OCA\Libresign\Handler\CertificateEngine\CfsslHandler;
use OCA\Libresign\Handler\CertificateEngine\IEngineHandler;
use OCA\Libresign\Service\CaIdentifierService;
use OCA\Libresign\Service\Process\ProcessManager;
use OCA\Libresign\Vendor\LibreSign\WhatOSAmI\OperatingSystem;
use OCA\Libresign\Vendor\Symfony\Component\Process\Process;
use OCP\Files\AppData\IAppDataFactory;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
Expand All @@ -37,7 +39,6 @@
use RuntimeException;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;

class InstallService {
use TSimpleFile {
Expand All @@ -52,6 +53,7 @@ class InstallService {
public const JSIGNPDF_VERSION = '2.3.0'; /** @todo When update, verify the hash **/
private const JSIGNPDF_HASH = 'd239658ea50a39eb35169d8392feaffb';
public const CFSSL_VERSION = '1.6.5';
private const PROCESS_SOURCE = 'install';

private ICache $cache;
private ?OutputInterface $output = null;
Expand All @@ -77,6 +79,7 @@ public function __construct(
private SignSetupService $signSetupService,
protected IAppDataFactory $appDataFactory,
private CaIdentifierService $caIdentifierService,
private ProcessManager $processManager,
) {
$this->cache = $cacheFactory->createDistributed('libresign-setup');
$this->appData = $appDataFactory->get('libresign');
Expand Down Expand Up @@ -145,18 +148,28 @@ private function getDataDir(): string {

private function runAsync(): void {
$resource = $this->resource;
$process = new Process([OC::$SERVERROOT . '/occ', 'libresign:install', '--' . $resource]);
$process = $this->createProcess([OC::$SERVERROOT . '/occ', 'libresign:install', '--' . $resource]);
$process->setOptions(['create_new_console' => true]);
$process->setTimeout(null);
$process->start();
$data['pid'] = $process->getPid();
if ($data['pid']) {
$this->processManager->register(self::PROCESS_SOURCE, (int)$data['pid'], [
'resource' => $resource,
]);
$this->setCache($resource, $data);
} else {
$this->logger->error('Error to get PID of background install proccess. Command: ' . OC::$SERVERROOT . '/occ libresign:install --' . $resource);
}
}

/**
* @param string[] $command
*/
protected function createProcess(array $command): Process {
return new Process($command);
}

private function progressToDatabase(int $downloadSize, int $downloaded): void {
$data = $this->getProressData();
$data['download_size'] = $downloadSize;
Expand Down Expand Up @@ -295,27 +308,27 @@ public function isDownloadWip(): bool {
}

private function getInstallPid(int $pid = 0): int {
$resource = $this->resource;
if ($pid > 0) {
if (shell_exec('which ps') === null) {
if (is_dir('/proc/' . $pid)) {
return $pid;
}
return 0;
$registeredPid = $this->processManager->findRunningPid(
self::PROCESS_SOURCE,
fn (array $entry): bool
=> $entry['pid'] === $pid
&& ($entry['context']['resource'] ?? '') === $resource,
);

if ($registeredPid > 0) {
return $registeredPid;
}
$cmd = 'ps -p ' . $pid . ' -o pid,command|';
} else {
$cmd = 'ps -eo pid,command|';
}
$cmd .= 'grep "libresign:install --' . $this->resource . '"|'
. 'grep -v grep|'
. 'grep -v defunct|'
. 'sed -e "s/^[[:space:]]*//"|cut -d" " -f1';
$output = shell_exec($cmd);
if (!is_string($output)) {

$this->processManager->unregister(self::PROCESS_SOURCE, $pid);
return 0;
}
$pid = trim($output);
return (int)$pid;

return $this->processManager->findRunningPid(
self::PROCESS_SOURCE,
fn (array $entry): bool => ($entry['context']['resource'] ?? '') === $resource,
);
}

public function setResource(string $resource): self {
Expand Down
Loading
Loading