@@ -104,24 +104,20 @@ public function getCachedLatest(): ?string
104104 /**
105105 * Fetch the latest release tag from GitHub, cache the result, and return it.
106106 * Returns null on any failure.
107+ *
108+ * Uses the system curl binary when available — required for standalone
109+ * micro.sfx builds, which lack the openssl extension and cannot make HTTPS
110+ * requests via PHP stream wrappers. Falls back to file_get_contents for
111+ * environments where curl is not on PATH (e.g. Composer-global installs
112+ * running under a full PHP with openssl).
107113 */
108114 public function fetchLatest (): ?string
109115 {
110- $ context = stream_context_create ([
111- 'http ' => [
112- 'method ' => 'GET ' ,
113- 'header ' => implode ("\r\n" , [
114- 'User-Agent: cnkill-updater ' ,
115- 'Accept: application/vnd.github+json ' ,
116- ]),
117- 'timeout ' => self ::FETCH_TIMEOUT ,
118- 'ignore_errors ' => true ,
119- ],
120- ]);
116+ $ raw = trim ((string ) shell_exec ('command -v curl 2>/dev/null ' )) !== ''
117+ ? $ this ->fetchViaSystemCurl ()
118+ : $ this ->fetchViaFileGetContents ();
121119
122- $ raw = @file_get_contents (self ::API_URL , false , $ context );
123-
124- if ($ raw === false ) {
120+ if ($ raw === null ) {
125121 return null ;
126122 }
127123
@@ -138,6 +134,65 @@ public function fetchLatest(): ?string
138134 return $ tag ;
139135 }
140136
137+ /**
138+ * Perform the GitHub API request using the system curl binary.
139+ * Returns the raw response body, or null on failure.
140+ */
141+ private function fetchViaSystemCurl (): ?string
142+ {
143+ $ proc = proc_open (
144+ [
145+ 'curl ' ,
146+ '--fail ' , '--silent ' , '--show-error ' , '--location ' ,
147+ '--max-time ' , (string ) self ::FETCH_TIMEOUT ,
148+ '-H ' , 'User-Agent: cnkill-updater ' ,
149+ '-H ' , 'Accept: application/vnd.github+json ' ,
150+ self ::API_URL ,
151+ ],
152+ [['pipe ' , 'r ' ], ['pipe ' , 'w ' ], ['pipe ' , 'w ' ]],
153+ $ pipes
154+ );
155+
156+ if (! is_resource ($ proc )) {
157+ return null ;
158+ }
159+
160+ fclose ($ pipes [0 ]);
161+
162+ $ raw = stream_get_contents ($ pipes [1 ]);
163+
164+ fclose ($ pipes [1 ]);
165+ fclose ($ pipes [2 ]);
166+
167+ $ exitCode = proc_close ($ proc );
168+
169+ return ($ exitCode === 0 && $ raw !== false && $ raw !== '' ) ? $ raw : null ;
170+ }
171+
172+ /**
173+ * Perform the GitHub API request using PHP's file_get_contents stream wrapper.
174+ * Requires allow_url_fopen = 1 and the openssl extension (HTTPS support).
175+ * Returns the raw response body, or null on failure.
176+ */
177+ private function fetchViaFileGetContents (): ?string
178+ {
179+ $ context = stream_context_create ([
180+ 'http ' => [
181+ 'method ' => 'GET ' ,
182+ 'header ' => implode ("\r\n" , [
183+ 'User-Agent: cnkill-updater ' ,
184+ 'Accept: application/vnd.github+json ' ,
185+ ]),
186+ 'timeout ' => self ::FETCH_TIMEOUT ,
187+ 'ignore_errors ' => true ,
188+ ],
189+ ]);
190+
191+ $ raw = @file_get_contents (self ::API_URL , false , $ context );
192+
193+ return ($ raw !== false && $ raw !== '' ) ? $ raw : null ;
194+ }
195+
141196 /**
142197 * Return the latest release tag, preferring the on-disk cache when fresh.
143198 * Falls back to a live GitHub fetch when stale or missing.
0 commit comments