feat(macos): Firefox quotidien pilotable par Hermes (launchd + MCP connectExisting)
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
# Firefox du Mac pilotable par Hermes (module nix-darwin, importé par
|
||||||
|
# profiles/macos/configuration.nix).
|
||||||
|
#
|
||||||
|
# Firefox (profil quotidien de marc, session et cookies conservés) est lancé
|
||||||
|
# par launchd avec Marionette (127.0.0.1:2828) et WebDriver BiDi
|
||||||
|
# (127.0.0.1:9222). Le serveur MCP « firefox-mac » de tower
|
||||||
|
# (services/firefox-mac-mcp) s'y attache en --connectExisting à travers ssh :
|
||||||
|
# l'instance survit aux sessions Hermes, la musique continue après la fin
|
||||||
|
# d'une conversation. LibreWolf reste le navigateur des sites sensibles.
|
||||||
|
#
|
||||||
|
# - Firefox reste l'app impérative de /Applications (auto-update Mozilla,
|
||||||
|
# binaire universel arm64) ; seul le lancement est déclaratif.
|
||||||
|
# - Si Firefox tourne déjà SANS ces flags (lancé depuis le Dock, réouverture
|
||||||
|
# des fenêtres à la connexion), le wrapper attend qu'il soit quitté puis
|
||||||
|
# prend le relais : jamais deux instances sur le même profil.
|
||||||
|
# - KeepAlive : un Cmd+Q relance Firefox (avec les flags) après ~30 s.
|
||||||
|
# Arrêt réel : launchctl bootout gui/$(id -u)/org.nixos.firefox-hermes
|
||||||
|
# - Effets visibles du mode piloté : icône robot et barre d'adresse rayée,
|
||||||
|
# navigator.webdriver=true (certains anti-bots sont plus méfiants).
|
||||||
|
# - Sécurité : ports en loopback seulement, mais sans authentification : tout
|
||||||
|
# process local du Mac peut piloter ce Firefox. Accepté par marc.
|
||||||
|
{pkgs, ...}: let
|
||||||
|
firefox = "/Applications/Firefox.app/Contents/MacOS/firefox";
|
||||||
|
wrapper = pkgs.writeShellScript "firefox-hermes" ''
|
||||||
|
while /usr/bin/pgrep -f '^${firefox}( |$)' >/dev/null; do
|
||||||
|
sleep 15
|
||||||
|
done
|
||||||
|
exec ${firefox} --marionette --remote-debugging-port 9222
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
launchd.user.agents.firefox-hermes = {
|
||||||
|
serviceConfig = {
|
||||||
|
ProgramArguments = ["${wrapper}"];
|
||||||
|
RunAtLoad = true;
|
||||||
|
KeepAlive = true;
|
||||||
|
ThrottleInterval = 30;
|
||||||
|
ProcessType = "Interactive";
|
||||||
|
StandardOutPath = "/tmp/firefox-hermes.log";
|
||||||
|
StandardErrorPath = "/tmp/firefox-hermes.err";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -43,6 +43,10 @@ in {
|
|||||||
# sur 127.0.0.1:2223 du VPS, pour que tower (ou marc) joigne le Mac hors
|
# sur 127.0.0.1:2223 du VPS, pour que tower (ou marc) joigne le Mac hors
|
||||||
# LAN. La clé du Mac est déjà autorisée pour root@rack.
|
# LAN. La clé du Mac est déjà autorisée pour root@rack.
|
||||||
../../modules/home-mac/autossh
|
../../modules/home-mac/autossh
|
||||||
|
|
||||||
|
# Firefox quotidien lancé avec Marionette/BiDi en loopback : piloté par
|
||||||
|
# le MCP firefox-mac de tower (services/firefox-mac-mcp).
|
||||||
|
../../modules/home-mac/firefox-hermes
|
||||||
];
|
];
|
||||||
|
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# services/firefox-mac-mcp/default.nix
|
||||||
|
# Serveur MCP "firefox-mac" : le navigateur du MAC de marc, piloté par Hermes
|
||||||
|
# (qui tourne sur tower). Même serveur officiel Mozilla que services/firefox-mcp
|
||||||
|
# (mozilla/firefox-devtools-mcp 0.10.4), mais exécuté LÀ-BAS via ssh : le MCP
|
||||||
|
# parle stdio, le wrapper exec simplement `ssh mac ...`, et geckodriver +
|
||||||
|
# Selenium WebDriver BiDi lancent le LibreWolf du Mac localement (le driver ne
|
||||||
|
# peut pas lancer un navigateur d'une autre machine, le process vit donc côté
|
||||||
|
# Mac).
|
||||||
|
#
|
||||||
|
# == Depuis le 02/11/2026 : Firefox quotidien en --connectExisting ==
|
||||||
|
# Le serveur ne lance PLUS de navigateur : il s'attache au Firefox de marc,
|
||||||
|
# démarré par launchd avec --marionette (2828) --remote-debugging-port 9222
|
||||||
|
# (module nix-darwin modules/home-mac/firefox-hermes). Raisons : session et
|
||||||
|
# cookies conservés (LibreWolf les purge à la fermeture), instance qui
|
||||||
|
# survit aux sessions Hermes (musique YouTube), une seule instance partagée.
|
||||||
|
# Vérifié sur un Firefox jetable : navigate/list_pages OK, deux connexions
|
||||||
|
# successives OK, Firefox toujours vivant après la sortie du serveur.
|
||||||
|
# Marionette n'accepte qu'UN client à la fois : deux sessions Hermes
|
||||||
|
# simultanées se gênent. Les notes LibreWolf ci-dessous sont historiques.
|
||||||
|
#
|
||||||
|
# == Côté Mac (impératif, PAS géré par ce flake) ==
|
||||||
|
# hosts/macos a le module homebrew désactivé, ces deux outils vivent hors nix :
|
||||||
|
# brew install geckodriver # 0.37.1 (25/10/2026)
|
||||||
|
# npm install -g @mozilla/firefox-devtools-mcp@0.10.4 # node brew v25.2.1
|
||||||
|
# Mise à jour : `brew upgrade geckodriver` + `npm install -g
|
||||||
|
# @mozilla/firefox-devtools-mcp@latest` (mettre à jour la version ci-dessus).
|
||||||
|
#
|
||||||
|
# == Décisions vérifiées le 25/10/2026 ==
|
||||||
|
# - geckodriver accepte /Applications/LibreWolf.app/Contents/MacOS/librewolf :
|
||||||
|
# version 145.0.1 lue depuis le bundle, pas de piège application.ini comme
|
||||||
|
# sur le binaire unwrapped nix de tower. Session W3C créée puis fermée OK.
|
||||||
|
# - --firefoxArg -no-remote : le LibreWolf PERSO de marc tourne en permanence
|
||||||
|
# sur le Mac (profil ~/Library/Application Support/librewolf). Sans
|
||||||
|
# -no-remote, une 2e invocation du même binaire risque d'être absorbée par
|
||||||
|
# l'instance existante. Avec, l'instance d'automation démarre à côté sur son
|
||||||
|
# profil dédié et le perso n'est jamais touché (vérifié : navigate_page +
|
||||||
|
# take_snapshot, LibreWolf perso intact, zéro process résiduel après sortie).
|
||||||
|
# - PAS de --headless : le but est que marc VOIE la fenêtre sur son écran.
|
||||||
|
# Pour la discrétion, ajouter --headless à remoteCmd.
|
||||||
|
# - Profil d'automation dédié et persistant :
|
||||||
|
# /Users/marc/.firefox-mcp-mac/profile/firefox_devtools_mcp_profile
|
||||||
|
# (sous-dossier créé par le serveur au 1er lancement). Login manuel une fois
|
||||||
|
# par site : ouvrir CE profil dans un LibreWolf visible (fermer l'instance
|
||||||
|
# d'automation d'abord, un seul process par profil), se connecter, fermer :
|
||||||
|
# /Applications/LibreWolf.app/Contents/MacOS/librewolf --no-remote \
|
||||||
|
# --profile /Users/marc/.firefox-mcp-mac/profile/firefox_devtools_mcp_profile
|
||||||
|
# Les cookies restent ensuite disponibles pour l'automation.
|
||||||
|
#
|
||||||
|
# == Transport ==
|
||||||
|
# stdio à travers ssh, alias de l'utilisateur hermes (cf ~/.ssh/config) :
|
||||||
|
# maclan d'abord (LAN direct, rapide), repli mac (tunnel inverse via rack,
|
||||||
|
# marche depuis n'importe quel réseau). BatchMode strict : clé id_ed25519
|
||||||
|
# seulement, aucune interaction possible. Le repli ne se déclenche que si la
|
||||||
|
# connexion à maclan échoue, aucune lecture de stdin n'a lieu avant connexion.
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
remoteCmd = lib.concatStringsSep " " [
|
||||||
|
"export PATH=/opt/homebrew/bin:$PATH;"
|
||||||
|
"exec /opt/homebrew/bin/firefox-devtools-mcp"
|
||||||
|
"--connectExisting"
|
||||||
|
"--marionettePort 2828"
|
||||||
|
];
|
||||||
|
|
||||||
|
ssh = "${pkgs.openssh}/bin/ssh";
|
||||||
|
|
||||||
|
wrapper = pkgs.writeShellScript "firefox-mac-mcp" ''
|
||||||
|
HOST_CMD=${lib.escapeShellArg remoteCmd}
|
||||||
|
SSH=${ssh}
|
||||||
|
if "$SSH" -T -o BatchMode=yes -o ConnectTimeout=8 -o LogLevel=ERROR -o ServerAliveInterval=30 -o ServerAliveCountMax=6 maclan "$HOST_CMD"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exec "$SSH" -T -o BatchMode=yes -o ConnectTimeout=15 -o LogLevel=ERROR -o ServerAliveInterval=30 -o ServerAliveCountMax=6 mac "$HOST_CMD"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
services.hermes-agent.mcpServers.firefox-mac = {
|
||||||
|
command = "${wrapper}";
|
||||||
|
# 1er appel d'outil = démarrage du navigateur (profil + handshake BiDi) via
|
||||||
|
# ssh ; le défaut de 120s est trop court pour le pire cas.
|
||||||
|
timeout = 300;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user