bootDir = '/boot/config/plugins/podman'; $this->storagePath = '/mnt/cache/system/podman'; $this->storageImageSizeGb = 20; $this->enabled = true; $this->stopTimeoutSeconds = 10; $this->socketPath = '/var/run/podman/podman.sock'; $this->autostartFile = $this->bootDir . '/autostart'; $this->autostartDelayFile = $this->bootDir . '/autostart-delay'; } public static function load(): self { $cfg = new self(); $cfgFile = $cfg->bootDir . '/podman.cfg'; if (is_readable($cfgFile)) { $values = self::parseShellStyleFile($cfgFile); if (isset($values['STORAGE_PATH'])) { $cfg->storagePath = $values['STORAGE_PATH']; } if (isset($values['STORAGE_IMAGE_SIZE_GB'])) { $cfg->storageImageSizeGb = (int) $values['STORAGE_IMAGE_SIZE_GB']; } if (isset($values['PODMAN_ENABLED'])) { $cfg->enabled = strtolower($values['PODMAN_ENABLED']) === 'yes'; } if (isset($values['STOP_TIMEOUT'])) { $cfg->stopTimeoutSeconds = (int) $values['STOP_TIMEOUT']; } } return $cfg; } /** * Parses the simple `KEY="value"` / `KEY=value` shell-sourceable format * used by podman.cfg (see config/podman.cfg.example) WITHOUT executing * it as shell — this file is read by an unprivileged PHP-FPM worker, * so treating it as data rather than sourcing it is a deliberate * safety boundary, not just a convenience. * * @return array */ private static function parseShellStyleFile(string $path): array { $result = []; $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if ($lines === false) { return $result; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || str_starts_with($line, '#')) { continue; } if (!preg_match('/^([A-Z_][A-Z0-9_]*)=(.*)$/', $line, $m)) { continue; } [$_, $key, $value] = $m; $value = trim($value); // Strip one layer of matching quotes, if present. if (strlen($value) >= 2 && ( ($value[0] === '"' && str_ends_with($value, '"')) || ($value[0] === "'" && str_ends_with($value, "'")) )) { $value = substr($value, 1, -1); } $result[$key] = $value; } return $result; } public function newClient(): PodmanClient { return new PodmanClient($this->socketPath); } }