@@ -24,6 +24,7 @@ class PluginInstaller
2424 private $ pluginsManifest ;
2525 private $ repoPublic ;
2626 private $ helperScriptPath ;
27+ private $ pluginVarsPath ;
2728
2829 public function __construct ()
2930 {
@@ -36,6 +37,7 @@ public function __construct()
3637 $ this ->pluginsManifest = '/plugins/manifest.json ' ;
3738 $ this ->repoPublic = $ this ->getRepository ();
3839 $ this ->helperScriptPath = RASPI_CONFIG .'/plugins/plugin_helper.sh ' ;
40+ $ this ->pluginVarsPath = $ this ->rootPath . '/config/plugin_vars.json ' ;
3941 }
4042
4143 // Returns a single instance of PluginInstaller
@@ -152,6 +154,7 @@ public function installPlugin(string $pluginUri, string $pluginVersion, string $
152154 $ tempFile = null ;
153155 $ extractDir = null ;
154156 $ pluginDir = null ;
157+ $ tempRepoFiles = [];
155158
156159 try {
157160 if ($ installPath === 'plugins-available ' ) {
@@ -181,6 +184,8 @@ public function installPlugin(string $pluginUri, string $pluginVersion, string $
181184 }
182185
183186 $ manifest = $ this ->parseManifest ($ pluginDir );
187+ $ varMap = $ this ->loadVariableMap ();
188+ $ manifest = $ this ->resolveManifestVariables ($ manifest , $ varMap );
184189 $ this ->pluginName = preg_replace ('/\s+/ ' , '' , $ manifest ['name ' ]);
185190 $ rollbackStack = []; // Store actions to rollback on failure
186191
@@ -190,6 +195,33 @@ public function installPlugin(string $pluginUri, string $pluginVersion, string $
190195 $ rollbackStack [] = 'removeSudoers ' ;
191196 }
192197 if (!empty ($ manifest ['keys ' ])) {
198+ foreach ($ manifest ['keys ' ] as &$ keyEntry ) {
199+ $ repo = $ keyEntry ['repo ' ];
200+ if (strncmp ($ repo , 'http:// ' , 7 ) !== 0 && strncmp ($ repo , 'https:// ' , 8 ) !== 0 ) {
201+ if (strncmp ($ repo , '/ ' , 1 ) !== 0 ) {
202+ $ repo = $ pluginDir . DIRECTORY_SEPARATOR . $ repo ;
203+ }
204+ $ content = file_get_contents ($ repo );
205+ if ($ content === false ) {
206+ throw new \Exception ("Failed to read repo file: $ repo " );
207+ }
208+ if (!empty ($ varMap )) {
209+ $ content = str_replace (array_keys ($ varMap ), array_values ($ varMap ), $ content );
210+ }
211+ $ tmpBase = tempnam (sys_get_temp_dir (), 'plugin_repo_ ' );
212+ if ($ tmpBase === false ) {
213+ throw new \Exception ("Failed to create temp file for repo " );
214+ }
215+ unlink ($ tmpBase );
216+ $ tmpRepo = $ tmpBase . '.list ' ;
217+ if (file_put_contents ($ tmpRepo , $ content ) === false ) {
218+ throw new \Exception ("Failed to write temp repo file: $ tmpRepo " );
219+ }
220+ $ tempRepoFiles [] = $ tmpRepo ;
221+ $ keyEntry ['repo ' ] = $ tmpRepo ;
222+ }
223+ }
224+ unset($ keyEntry );
193225 $ this ->installRepositoryKeys ($ manifest ['keys ' ]);
194226 $ rollbackStack [] = 'uninstallRepositoryKeys ' ;
195227 }
@@ -235,6 +267,11 @@ public function installPlugin(string $pluginUri, string $pluginVersion, string $
235267 if (isset ($ extractDir ) && is_dir ($ extractDir )) {
236268 $ this ->deleteDir ($ extractDir );
237269 }
270+ foreach ($ tempRepoFiles as $ tmpRepo ) {
271+ if (file_exists ($ tmpRepo )) {
272+ unlink ($ tmpRepo );
273+ }
274+ }
238275 }
239276 }
240277
@@ -500,6 +537,66 @@ private function parseManifest($pluginDir): array
500537 return $ manifest ;
501538 }
502539
540+ /**
541+ * Loads the token-to-system-value map from plugin_vars.json
542+ *
543+ * @return array flat map of token => resolved value
544+ * @throws \Exception if a required key cannot be resolved
545+ */
546+ private function loadVariableMap (): array
547+ {
548+ if (!file_exists ($ this ->pluginVarsPath )) {
549+ return [];
550+ }
551+
552+ $ json = file_get_contents ($ this ->pluginVarsPath );
553+ $ entries = json_decode ($ json , true );
554+
555+ if (json_last_error () !== JSON_ERROR_NONE || !is_array ($ entries )) {
556+ return [];
557+ }
558+
559+ $ map = [];
560+ foreach ($ entries as $ token => $ entry ) {
561+ $ parsed = parse_ini_file ($ entry ['file ' ], false , INI_SCANNER_RAW );
562+ $ key = $ entry ['key ' ];
563+ if ($ parsed === false || !isset ($ parsed [$ key ]) || $ parsed [$ key ] === '' ) {
564+ throw new \Exception (
565+ "Cannot resolve $ token: $ key not found in {$ entry ['file ' ]}"
566+ );
567+ }
568+ $ map [$ token ] = $ parsed [$ key ];
569+ }
570+ return $ map ;
571+ }
572+
573+ /**
574+ * Substitutes tokens in all string values of a manifest array
575+ *
576+ * @param array $manifest
577+ * @param array $varMap pre-loaded variable map
578+ * @return array manifest with tokens replaced
579+ * @throws \Exception
580+ */
581+ private function resolveManifestVariables (array $ manifest , array $ varMap ): array
582+ {
583+ if (empty ($ varMap )) {
584+ return $ manifest ;
585+ }
586+
587+ $ walk = function ($ value ) use (&$ walk , $ varMap ) {
588+ if (is_string ($ value )) {
589+ return str_replace (array_keys ($ varMap ), array_values ($ varMap ), $ value );
590+ }
591+ if (is_array ($ value )) {
592+ return array_map ($ walk , $ value );
593+ }
594+ return $ value ;
595+ };
596+
597+ return array_map ($ walk , $ manifest );
598+ }
599+
503600 /**
504601 * Retrieves a plugin archive and extracts it to /tmp
505602 *
0 commit comments