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
|
||||
# LAN. La clé du Mac est déjà autorisée pour root@rack.
|
||||
../../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 = [
|
||||
|
||||
@@ -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