@@ -206,6 +206,78 @@ def notify(cfw: str, message: str) -> bool:
206206 log ("WARN" , f"unsupported CFW '{ cfw } '; would have sent: { message } " )
207207 return False
208208
209+ # If there's a modules dir (used for Tools menu in ES),
210+ # add a Pharos launch script there if the daemon is in use.
211+ MODULES_DIR = Path ("/storage/.config/modules" )
212+ MODULE_SCRIPT = MODULES_DIR / "Start Pharos.sh"
213+ MODULE_GAMELIST = MODULES_DIR / "gamelist.xml"
214+ MODULE_IMAGE = MODULES_DIR / "images" / "pharos.png"
215+ MODULE_COVER_SOURCE = INSTALL_DIR / "cover.png"
216+ _MODULE_SCRIPT_TEMPLATE = """#!/bin/bash
217+ # Created by pharos-daemon: launches Pharos from the Tools menu.
218+ source /etc/profile
219+
220+ for launcher in "{ports_dir}/Pharos"*.sh; do
221+ if [ -f "$launcher" ]; then
222+ exec /bin/bash "$launcher"
223+ fi
224+ done
225+ echo "Pharos launcher not found in {ports_dir}" >&2
226+ exit 1
227+ """
228+
229+ _GAMELIST_ENTRY_TEMPLATE = """ <game>
230+ <path>./Start Pharos.sh</path>
231+ <name>Pharos</name>
232+ <desc>Pharos is a tool for downloading ports and wine bottles hosted on independent GitHub repositories.</desc>
233+ <developer>Jeod</developer>
234+ <publisher>Jeod</publisher>
235+ <rating>5.0</rating>
236+ <releasedate>2025</releasedate>
237+ <genre>Tool</genre>
238+ <players>1</players>
239+ {image_line} </game>
240+ """
241+
242+
243+ def ensure_es_module () -> None :
244+ """Idempotently install 'Start Pharos.sh' + its gamelist.xml entry into
245+ the ES modules dir. No-op on CFWs without /storage/.config/modules."""
246+ if not MODULES_DIR .is_dir ():
247+ return
248+ try :
249+ if not MODULE_SCRIPT .exists ():
250+ ports_dir = INSTALL_DIR .parent
251+ MODULE_SCRIPT .write_text (
252+ _MODULE_SCRIPT_TEMPLATE .format (ports_dir = ports_dir ), encoding = "utf-8"
253+ )
254+ MODULE_SCRIPT .chmod (0o755 )
255+ log ("INFO" , f"installed ES module { MODULE_SCRIPT } " )
256+
257+ if not MODULE_IMAGE .exists () and MODULE_COVER_SOURCE .exists ():
258+ MODULE_IMAGE .parent .mkdir (parents = True , exist_ok = True )
259+ shutil .copyfile (MODULE_COVER_SOURCE , MODULE_IMAGE )
260+ log ("INFO" , f"copied module image -> { MODULE_IMAGE } " )
261+
262+ if MODULE_GAMELIST .exists ():
263+ xml = MODULE_GAMELIST .read_text ("utf-8" )
264+ else :
265+ xml = '<?xml version="1.0"?>\n <gameList>\n </gameList>\n '
266+ if "Start Pharos.sh" not in xml and "</gameList>" in xml :
267+ image_line = (
268+ " <image>./images/pharos.png</image>\n "
269+ if MODULE_IMAGE .exists ()
270+ else ""
271+ )
272+ entry = _GAMELIST_ENTRY_TEMPLATE .format (image_line = image_line )
273+ xml = xml .replace ("</gameList>" , entry + "</gameList>" , 1 )
274+ tmp = MODULE_GAMELIST .with_suffix (".tmp" )
275+ tmp .write_text (xml , encoding = "utf-8" )
276+ os .replace (tmp , MODULE_GAMELIST )
277+ log ("INFO" , f"added Pharos entry to { MODULE_GAMELIST } " )
278+ except OSError as e :
279+ log ("WARN" , f"ES module injection failed: { e } " )
280+
209281# ----------------------------------------------------------------------
210282# Update check
211283# ----------------------------------------------------------------------
@@ -433,6 +505,8 @@ def run_check() -> tuple[bool, bool]:
433505 global _network_errors_this_pass
434506 _network_errors_this_pass = 0
435507
508+ ensure_es_module ()
509+
436510 local_md5s , local_titles , local_repos , muted = load_local_manifest ()
437511 if not local_md5s :
438512 log ("INFO" , "manifest empty; nothing tracked" )
0 commit comments