From 7498ea49712babd299fd076ae1a8f8c024458d13 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 24 Oct 2026 14:33:35 -0400 Subject: [PATCH] feat(macos): setup nix-darwin pour builder l'app Winnie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'app se build sur le Mac en trois commandes, le toolchain restant dans le devShell du flake winnie (jamais installé sur le Mac) : cd ~/git/nixos && just mac # déploie ce setup winnie-sync # attrape ~/git/winnie depuis tower (rsync ssh) winnie-ios # simulateur ; npm ci + tauri ios init au besoin - modules/home-mac/winnie : winnie-doctor [--fix], winnie-sync [--delete], winnie-shell, winnie-front, winnie-macos, winnie-build-macos, winnie-ios, winnie-build-ios, winnie-verify. Scripts en writeShellApplication (shellcheck au build) ; scripts.nix est séparé du module pour être constructible depuis tower avec les pkgs linux et testable sans Mac. - profiles/macos : flakes + trusted-users dans nix.conf, et system.activationScripts.winnie-xcode (xcode-select -s, licence Xcode, -runFirstLaunch) pour que seule l'installation App Store reste manuelle. - Embarque aussi le setup SSH entrant du Mac (sshd, clé hermes@tower, tunnel inverse autossh) qui était déjà dans l'arbre de travail. Vérifié depuis tower : nix eval du darwinConfigurations.macos (toplevel et home-manager-generation) + build des scripts en x86_64-linux (shellcheck) et rsync de bout en bout depuis le dépôt de tower. --- modules/home-mac/winnie/default.nix | 66 ++++++++ modules/home-mac/winnie/scripts.nix | 93 ++++++++++++ modules/home-mac/winnie/scripts/doctor.sh | 143 ++++++++++++++++++ modules/home-mac/winnie/scripts/front.sh | 12 ++ modules/home-mac/winnie/scripts/ios-build.sh | 16 ++ modules/home-mac/winnie/scripts/ios-dev.sh | 37 +++++ .../home-mac/winnie/scripts/macos-build.sh | 10 ++ modules/home-mac/winnie/scripts/macos-dev.sh | 11 ++ modules/home-mac/winnie/scripts/sync.sh | 62 ++++++++ modules/home-mac/winnie/scripts/verify.sh | 29 ++++ profiles/macos/configuration.nix | 44 ++++++ users/mac/home.nix | 37 +++++ 12 files changed, 560 insertions(+) create mode 100644 modules/home-mac/winnie/default.nix create mode 100644 modules/home-mac/winnie/scripts.nix create mode 100644 modules/home-mac/winnie/scripts/doctor.sh create mode 100644 modules/home-mac/winnie/scripts/front.sh create mode 100644 modules/home-mac/winnie/scripts/ios-build.sh create mode 100644 modules/home-mac/winnie/scripts/ios-dev.sh create mode 100644 modules/home-mac/winnie/scripts/macos-build.sh create mode 100644 modules/home-mac/winnie/scripts/macos-dev.sh create mode 100644 modules/home-mac/winnie/scripts/sync.sh create mode 100644 modules/home-mac/winnie/scripts/verify.sh diff --git a/modules/home-mac/winnie/default.nix b/modules/home-mac/winnie/default.nix new file mode 100644 index 0000000..21a5189 --- /dev/null +++ b/modules/home-mac/winnie/default.nix @@ -0,0 +1,66 @@ +# Environnement de build de l'app Winnie sur le Mac (front Svelte 5 + shell +# Tauri), pour que builder soit une commande, pas une procédure. +# +# Le toolchain (rust + triples iOS, node 22, cocoapods, tauri-cli, postgres) vit +# dans le devShell du flake `winnie` : ce module ne fait qu'exposer les +# commandes qui l'utilisent, chacune re-rentrant dans `nix develop`. +# winnie-doctor état du poste (Xcode, simulateur, dépôt, API) ; --fix +# winnie-sync rafraîchit ~/git/winnie depuis tower (LAN ou rack) +# winnie-shell devShell interactif +# winnie-front serveur Vite (HMR) +# winnie-macos app macOS en dev ; winnie-build-macos = .app/.dmg +# winnie-ios app iOS dans le simulateur ; winnie-build-ios = archive +# winnie-verify front + svelte-check + cargo check +# +# Ce qui reste manuel, forcément : Xcode (App Store, Apple en interdit la +# redistribution), le runtime du simulateur (winnie-doctor --fix le télécharge) +# et un compte Apple pour signer un build pour iPhone réel. +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.my.winnie; +in { + options.my.winnie = { + enable = lib.mkEnableOption "l'environnement de build Winnie sur macOS"; + + repoRel = lib.mkOption { + type = lib.types.str; + default = "git/winnie"; + description = "Chemin du dépôt winnie, relatif à $HOME."; + }; + + towerRepo = lib.mkOption { + type = lib.types.str; + default = "/home/marc/git/winnie"; + description = "Chemin du même dépôt sur tower, source de winnie-sync (pas de remote git)."; + }; + + towerHosts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = ["towerlocal" "tower"]; + description = '' + Alias ssh essayés dans l'ordre par winnie-sync : le LAN d'abord + (towerlocal = 192.168.1.44), puis le tunnel par le VPS + (tower = 10.100.0.3 via rack, tenu par l'agent autossh du Mac). + ''; + }; + + apiUrl = lib.mkOption { + type = lib.types.str; + default = "https://api.winnie.marcpartensky.com"; + description = "API interrogée par winnie-doctor et par le front."; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = lib.attrValues (import ./scripts.nix { + inherit pkgs lib; + cfg = { + inherit (cfg) repoRel towerRepo towerHosts apiUrl; + }; + }); + }; +} diff --git a/modules/home-mac/winnie/scripts.nix b/modules/home-mac/winnie/scripts.nix new file mode 100644 index 0000000..d898c11 --- /dev/null +++ b/modules/home-mac/winnie/scripts.nix @@ -0,0 +1,93 @@ +# Commandes de build Winnie pour macOS, en dérivations writeShellApplication : +# shellcheck tourne au build et chaque commande est versionnée avec la config. +# +# Séparé du module home-manager exprès : ce fichier s'importe tel quel depuis +# tower avec les pkgs x86_64-linux pour construire et tester les mêmes scripts +# sans Mac (le Mac les reconstruit avec ses pkgs darwin). +{ + pkgs, + lib, + cfg, # { repoRel, towerRepo, towerHosts, apiUrl } +}: +let + subst = text: + lib.replaceStrings + ["@REPO_REL@" "@TOWER_REPO@" "@TOWER_HOSTS@" "@API_URL@" "@WINNIE_SYNC@"] + [ + cfg.repoRel + cfg.towerRepo + (lib.concatMapStrings (h: "'${h}' ") cfg.towerHosts) + cfg.apiUrl + (lib.getExe sync) + ] + text; + + # Scripts exécutés DANS le devShell du flake winnie : leurs outils (npm, cargo, + # tauri, xcrun, simctl) viennent de ce shell, d'où runtimeInputs vide. + inner = name: file: pkgs.writeShellApplication { + name = "winnie-${name}-inner"; + text = subst (builtins.readFile file); + }; + + # Commandes exposées : cd dans le dépôt puis ré-exécution de l'inner dans + # `nix develop`, pour que rien ne dépende du PATH du Mac. + dev = name: innerScript: pkgs.writeShellApplication { + name = "winnie-${name}"; + runtimeInputs = [pkgs.nix]; + text = '' + repo="$HOME/${cfg.repoRel}" + if [ ! -f "$repo/flake.nix" ]; then + echo "winnie-${name} : dépôt absent ($repo). Lance winnie-sync." >&2 + exit 1 + fi + cd "$repo" + exec nix --extra-experimental-features 'nix-command flakes' develop -c ${lib.getExe innerScript} "$@" + ''; + }; + + # Rafraîchit le dépôt depuis tower : rsync du dépôt local de tower (le dépôt + # n'a pas de remote git, tower est le seul original). + sync = pkgs.writeShellApplication { + name = "winnie-sync"; + runtimeInputs = [pkgs.rsync pkgs.openssh]; + text = subst (builtins.readFile ./scripts/sync.sh); + }; + + # État du poste : Xcode, SDK iOS, runtime du simulateur, node_modules, API. + # Xcode n'est pas dans le store Nix (App Store), donc ce diagnostic est la + # seule source de vérité, et --fix automatise tout sauf l'installation d'Xcode. + doctor = pkgs.writeShellApplication { + name = "winnie-doctor"; + runtimeInputs = [pkgs.nix pkgs.curl pkgs.coreutils]; + text = subst (builtins.readFile ./scripts/doctor.sh); + }; + + # DevShell interactif du projet, sans deviner le chemin du dépôt. + shell = pkgs.writeShellApplication { + name = "winnie-shell"; + runtimeInputs = [pkgs.nix]; + text = '' + repo="$HOME/${cfg.repoRel}" + if [ ! -f "$repo/flake.nix" ]; then + echo "winnie-shell : dépôt absent ($repo). Lance winnie-sync." >&2 + exit 1 + fi + cd "$repo" + exec nix --extra-experimental-features 'nix-command flakes' develop "$@" + ''; + }; +in { + inherit doctor shell sync; + + # Serveur Vite (HMR) du front Svelte. + front = dev "front" (inner "front" ./scripts/front.sh); + + # App Tauri : macOS (CLT suffisent) et iOS (Xcode requis). + macos = dev "macos" (inner "macos" ./scripts/macos-dev.sh); + macos-build = dev "build-macos" (inner "build-macos" ./scripts/macos-build.sh); + ios = dev "ios" (inner "ios" ./scripts/ios-dev.sh); + ios-build = dev "build-ios" (inner "build-ios" ./scripts/ios-build.sh); + + # Contrôles avant commit (front + svelte-check + cargo check). + verify = dev "verify" (inner "verify" ./scripts/verify.sh); +} diff --git a/modules/home-mac/winnie/scripts/doctor.sh b/modules/home-mac/winnie/scripts/doctor.sh new file mode 100644 index 0000000..b2501e9 --- /dev/null +++ b/modules/home-mac/winnie/scripts/doctor.sh @@ -0,0 +1,143 @@ +# winnie-doctor : état du poste Mac pour builder Winnie. +# winnie-doctor diagnostic seul +# winnie-doctor --fix répare ce qui est automatisable (dépôt, node_modules, +# developer dir Xcode, licence, runtime du simulateur) +# +# Xcode, le SDK iOS et les runtimes du simulateur viennent d'Apple, jamais du +# store Nix : ce script est la seule source de vérité sur leur disponibilité. +repo="$HOME/@REPO_REL@" +api="@API_URL@" +sync_bin="@WINNIE_SYNC@" + +fix=0 +if [ "${1:-}" = "--fix" ]; then + fix=1 +fi + +todo=0 +line() { printf '%-8s %s\n' "$1" "$2"; } +ok() { line "[ok]" "$1"; } +missing() { line "[manque]" "$1"; todo=$((todo + 1)); } +info() { line "[-]" "$1"; } + +printf 'winnie-doctor\n\n' + +# --- nix --------------------------------------------------------------- +if command -v nix >/dev/null 2>&1; then + ok "nix : $(nix --version)" +else + missing "nix absent du PATH" +fi + +# --- dépôt winnie ------------------------------------------------------ +if [ -f "$repo/flake.nix" ]; then + ok "dépôt : $repo" + if nix --extra-experimental-features 'nix-command flakes' flake metadata "$repo" >/dev/null 2>&1; then + ok "flake winnie évaluable" + else + missing "flake winnie non évaluable (nix flake metadata $repo)" + fi +else + missing "dépôt : $repo" + if [ "$fix" = 1 ]; then + echo " -> winnie-sync" + "$sync_bin" || true + fi +fi + +# --- Xcode : seul le build iOS l'exige --------------------------------- +xcode_ok=0 +if [ -d /Applications/Xcode.app ]; then + ok "Xcode.app installé" + xcode_ok=1 +else + missing "Xcode.app absent (App Store, recherche « Xcode », ~8 Go)" + info "sans Xcode : le front et le build macOS marchent, seul winnie-ios l'exige" + info "fiche App Store : open 'macappstore://apps.apple.com/app/id497799835'" +fi + +if [ -x /usr/bin/xcode-select ]; then + devdir="$(/usr/bin/xcode-select -p 2>/dev/null || true)" + if [ "$devdir" = "/Applications/Xcode.app/Contents/Developer" ]; then + ok "xcode-select : Xcode" + elif [ "$xcode_ok" = 1 ]; then + missing "xcode-select : ${devdir:-vide} (attendu /Applications/Xcode.app/Contents/Developer)" + if [ "$fix" = 1 ]; then + echo " -> sudo xcode-select -s /Applications/Xcode.app (mot de passe macOS demandé)" + sudo /usr/bin/xcode-select -s /Applications/Xcode.app + sudo /usr/bin/xcodebuild -license accept || true + /usr/bin/xcodebuild -runFirstLaunch || true + fi + else + info "xcode-select : ${devdir:-vide} (Command Line Tools, suffisant hors iOS)" + fi +else + info "xcode-select absent (poste non macOS ?)" +fi + +if [ "$xcode_ok" = 1 ]; then + sdk="$(/usr/bin/xcrun --sdk iphoneos --show-sdk-version 2>/dev/null || true)" + if [ -n "$sdk" ]; then + ok "SDK iOS : $sdk" + else + missing "SDK iOS (xcrun --sdk iphoneos --show-sdk-version ; licence Xcode non acceptée ?)" + if [ "$fix" = 1 ]; then + echo " -> sudo xcodebuild -license accept" + sudo /usr/bin/xcodebuild -license accept || true + fi + fi + + runtime_ok=0 + if /usr/bin/xcrun simctl list runtimes 2>/dev/null | grep -q 'iOS '; then + runtime_ok=1 + fi + if [ "$runtime_ok" = 1 ]; then + ok "runtime du simulateur iOS" + else + missing "runtime du simulateur iOS" + if [ "$fix" = 1 ]; then + echo " -> xcodebuild -downloadPlatform iOS (~8 Go, plusieurs minutes)" + /usr/bin/xcodebuild -downloadPlatform iOS || true + fi + fi +fi + +# --- front / node ------------------------------------------------------ +if [ -f "$repo/app/package.json" ]; then + if [ -d "$repo/app/node_modules" ]; then + ok "app/node_modules (npm ci fait)" + else + missing "app/node_modules" + if [ "$fix" = 1 ]; then + echo " -> npm ci dans le devShell (~1 min)" + (cd "$repo" && nix --extra-experimental-features 'nix-command flakes' develop -c bash -c "cd app && npm ci") || true + fi + fi + if [ -d "$repo/app/src-tauri/gen/apple" ]; then + ok "projet Xcode généré (src-tauri/gen/apple)" + else + info "src-tauri/gen/apple absent : winnie-ios le génère (tauri ios init)" + fi +fi + +# --- API --------------------------------------------------------------- +code="$(curl -sS -m 8 -o /dev/null -w '%{http_code}' "$api/healthz" 2>/dev/null || true)" +if [ "$code" = "200" ]; then + ok "API : $api" +else + missing "API : $api/healthz (HTTP ${code:-aucune réponse})" +fi + +# --- verdict ----------------------------------------------------------- +printf '\n' +if [ "$todo" -eq 0 ]; then + echo "tout est prêt : winnie-ios (simulateur), winnie-front, winnie-macos." + exit 0 +fi +echo "$todo contrôle(s) en échec." +if [ "$fix" = 1 ]; then + echo "relance winnie-doctor pour vérifier ce qui reste." +else + echo "winnie-doctor --fix répare ce qui est automatisable." +fi +exit 1 diff --git a/modules/home-mac/winnie/scripts/front.sh b/modules/home-mac/winnie/scripts/front.sh new file mode 100644 index 0000000..2a407df --- /dev/null +++ b/modules/home-mac/winnie/scripts/front.sh @@ -0,0 +1,12 @@ +# Serveur Vite (HMR) de app/ : joignable sur le LAN (http://:8080) et sur +# dev.winnie.marcpartensky.com via Pangolin. Lancé par winnie-front, donc les +# outils (node 22, npm) viennent du devShell du flake winnie. +repo="$HOME/@REPO_REL@" +cd "$repo/app" + +if [ ! -d node_modules ]; then + echo "[winnie-front] npm ci" + npm ci +fi + +exec npm run dev "$@" diff --git a/modules/home-mac/winnie/scripts/ios-build.sh b/modules/home-mac/winnie/scripts/ios-build.sh new file mode 100644 index 0000000..dde7e9b --- /dev/null +++ b/modules/home-mac/winnie/scripts/ios-build.sh @@ -0,0 +1,16 @@ +# Archive iOS pour un iPhone réel (et export IPA si un compte Apple est +# configuré dans Xcode). Le simulateur n'exige aucun compte, pas ceci. +repo="$HOME/@REPO_REL@" +cd "$repo/app" + +if [ ! -d node_modules ]; then + echo "[winnie-build-ios] npm ci" + npm ci +fi + +if [ ! -d src-tauri/gen/apple ]; then + echo "[winnie-build-ios] tauri ios init (projet Xcode + pod install)" + ./node_modules/.bin/tauri ios init +fi + +exec ./node_modules/.bin/tauri ios build --target aarch64 "$@" diff --git a/modules/home-mac/winnie/scripts/ios-dev.sh b/modules/home-mac/winnie/scripts/ios-dev.sh new file mode 100644 index 0000000..46c862b --- /dev/null +++ b/modules/home-mac/winnie/scripts/ios-dev.sh @@ -0,0 +1,37 @@ +# App iOS en dev : simulateur (aucun compte Apple requis) ou iPhone branché. +# npm ci et `tauri ios init` (projet Xcode + pod install) sont faits au besoin, +# donc la toute première exécution est longue. +# winnie-ios premier iPhone du simulateur +# winnie-ios "iPhone 17 Pro" appareil précis, en argument positionnel +# WINNIE_IOS_DEVICE= winnie-ios +# winnie-ios --force-ip-prompt sur le LAN (Vite sert déjà le LAN) +# `tauri ios dev` prend l'appareil en positionnel (le champ `device` de la CLI +# n'a pas d'attribut clap) et ouvre un menu interactif quand on ne passe rien : +# d'où la recherche du premier iPhone pour que la commande marche seule. +repo="$HOME/@REPO_REL@" +cd "$repo/app" + +if [ ! -d node_modules ]; then + echo "[winnie-ios] npm ci" + npm ci +fi + +if [ ! -d src-tauri/gen/apple ]; then + echo "[winnie-ios] tauri ios init (projet Xcode + pod install)" + ./node_modules/.bin/tauri ios init +fi + +args=("$@") +if [ "${#args[@]}" -eq 0 ] && [ -n "${WINNIE_IOS_DEVICE:-}" ]; then + args=("$WINNIE_IOS_DEVICE") +fi + +if [ "${#args[@]}" -eq 0 ]; then + sim="$(xcrun simctl list devices available 2>/dev/null | awk -F ' \\(' '/^ *iPhone/ {sub(/^ */, "", $1); print $1; exit}' || true)" + if [ -n "$sim" ]; then + echo "[winnie-ios] simulateur : $sim" + args=("$sim") + fi +fi + +exec ./node_modules/.bin/tauri ios dev "${args[@]}" diff --git a/modules/home-mac/winnie/scripts/macos-build.sh b/modules/home-mac/winnie/scripts/macos-build.sh new file mode 100644 index 0000000..c5d6ffe --- /dev/null +++ b/modules/home-mac/winnie/scripts/macos-build.sh @@ -0,0 +1,10 @@ +# Bundle macOS (.app + .dmg) : app/src-tauri/target/release/bundle/. +repo="$HOME/@REPO_REL@" +cd "$repo/app" + +if [ ! -d node_modules ]; then + echo "[winnie-build-macos] npm ci" + npm ci +fi + +exec ./node_modules/.bin/tauri build "$@" diff --git a/modules/home-mac/winnie/scripts/macos-dev.sh b/modules/home-mac/winnie/scripts/macos-dev.sh new file mode 100644 index 0000000..7b08349 --- /dev/null +++ b/modules/home-mac/winnie/scripts/macos-dev.sh @@ -0,0 +1,11 @@ +# App macOS (fenêtre Tauri + Vite en HMR) : ni Xcode ni simulateur, les +# Command Line Tools suffisent. +repo="$HOME/@REPO_REL@" +cd "$repo/app" + +if [ ! -d node_modules ]; then + echo "[winnie-macos] npm ci" + npm ci +fi + +exec ./node_modules/.bin/tauri dev "$@" diff --git a/modules/home-mac/winnie/scripts/sync.sh b/modules/home-mac/winnie/scripts/sync.sh new file mode 100644 index 0000000..472e399 --- /dev/null +++ b/modules/home-mac/winnie/scripts/sync.sh @@ -0,0 +1,62 @@ +# winnie-sync : rafraîchit ~/git/winnie depuis tower, la source de vérité +# (le dépôt n'a pas de remote git : tower est le seul original). +# Le premier alias ssh qui répond gagne : LAN d'abord (towerlocal), puis le +# tunnel par le VPS (tower -> 10.100.0.3 via rack, tenu par l'agent autossh). +# winnie-sync copie incrémentale, garde les fichiers locaux en trop +# winnie-sync --delete miroir strict de tower (supprime les fichiers en trop) +repo="$HOME/@REPO_REL@" +tower_repo="@TOWER_REPO@" +hosts=(@TOWER_HOSTS@) + +delete=() +if [ "${1:-}" = "--delete" ]; then + delete=(--delete) +elif [ -n "${1:-}" ]; then + echo "usage: winnie-sync [--delete]" >&2 + exit 2 +fi + +host="" +for h in "${hosts[@]}"; do + printf 'winnie-sync : %s... ' "$h" + if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new "$h" true >/dev/null 2>&1; then + echo "ok" + host="$h" + break + fi + echo "injoignable" +done + +if [ -z "$host" ]; then + echo "winnie-sync : tower injoignable (essayé : ${hosts[*]})" >&2 + echo "winnie-sync : LAN = towerlocal (192.168.1.44) ; hors LAN = tower (via rack)" >&2 + exit 1 +fi + +mkdir -p "$repo" +echo "winnie-sync : $host:$tower_repo -> $repo" + +# La barre de progression seulement sur un terminal (sans quoi un log +# redirigé se remplit de retours chariot). +progress=() +if [ -t 1 ]; then + progress=(--info=progress2) +fi + +# Ce qui ne traverse jamais : binaires par plateforme (node_modules, tauri-cli) +# et artefacts liés à la machine (target, projet Xcode généré, clés de signature). +rsync -a --no-owner --no-group "${progress[@]}" \ + --exclude /.direnv/ \ + --exclude /backend/target/ \ + --exclude /ingest/target/ \ + --exclude /app/node_modules/ \ + --exclude /app/dist/ \ + --exclude /app/src-tauri/target/ \ + --exclude /app/src-tauri/gen/ \ + --exclude /app/.android-sdk/ \ + --exclude /app/.android-user/ \ + --exclude /app/.keys/ \ + "${delete[@]}" \ + "$host:$tower_repo/" "$repo/" + +echo "winnie-sync : terminé (winnie-doctor pour l'état du poste)." diff --git a/modules/home-mac/winnie/scripts/verify.sh b/modules/home-mac/winnie/scripts/verify.sh new file mode 100644 index 0000000..93b3673 --- /dev/null +++ b/modules/home-mac/winnie/scripts/verify.sh @@ -0,0 +1,29 @@ +# Contrôles avant commit, dans l'ordre du projet : build du front (vite), +# svelte-check, puis cargo check du backend et de l'ingest. +# `npm run check` n'est pas appelé tel quel : son `tsc -p tsconfig.node.json` +# échoue sur TS2688 (Cannot find type definition file for 'node', @types/node +# absent de app/package.json), une dette antérieure au projet. +repo="$HOME/@REPO_REL@" +cd "$repo/app" + +if [ ! -d node_modules ]; then + echo "[winnie-verify] npm ci" + npm ci +fi + +echo "[winnie-verify] front : vite build" +npm run build + +echo "[winnie-verify] front : svelte-check" +./node_modules/.bin/svelte-check --tsconfig ./tsconfig.app.json + +cd "$repo" +echo "[winnie-verify] backend : cargo check" +cargo check --manifest-path backend/Cargo.toml + +if [ -f ingest/Cargo.toml ]; then + echo "[winnie-verify] ingest : cargo check" + cargo check --manifest-path ingest/Cargo.toml +fi + +echo "[winnie-verify] ok" diff --git a/profiles/macos/configuration.nix b/profiles/macos/configuration.nix index 2463735..cfe05db 100644 --- a/profiles/macos/configuration.nix +++ b/profiles/macos/configuration.nix @@ -38,6 +38,11 @@ in { # inputs.sops.nixosModules.sops # Inclus dans sopswarden # ../../services/wg-quick + + # Tunnel SSH inverse Mac -> rack (launchd agent) : expose le sshd du Mac + # 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 ]; environment.systemPackages = [ @@ -51,6 +56,16 @@ in { nixpkgs.config.allowUnfree = true; networking.hostName = "macos"; + # --- Nix : flakes et utilisateurs de confiance ------------------------- + # Le Mac build Winnie par `nix develop` (voir modules/home-mac/winnie) : les + # flakes doivent être actives dans /etc/nix/nix.conf, pas seulement passées en + # ligne de commande, et marc doit être trusted-user pour que le client puisse + # charger des substituts ou surcharger une option locale. + nix.settings = { + experimental-features = ["nix-command" "flakes"]; + trusted-users = ["root" "@admin"]; + }; + # services.nix-daemon.enable = true; <- supprime cette ligne home-manager = { @@ -69,6 +84,16 @@ in { system.primaryUser = "marc"; + # --- SSH entrant : Remote Login + clés ----------------------------------- + # Active sshd (com.openssh.sshd) : nix-darwin écrit sshd_config.d et fait + # `launchctl enable + bootstrap` à l'activation (pas de systemsetup, qui + # exige Full Disk Access). La clé hermes@tower est posée dans + # /Users/marc/.ssh/authorized_keys par users/mac/home.nix (home.activation). + # Reachabilité : LAN direct (tower et le Mac sont sur 192.168.1.0/24) ; + # hors LAN via le tunnel inverse autossh (modules/home-mac/autossh) que + # tower joint par `ssh -J rack -p 2223 marc@localhost`. + services.openssh.enable = true; + # deactivated homebrew = { enable = false; @@ -101,5 +126,24 @@ in { cp -R ${pangolin-client}/Applications/Pangolin.app /Applications/ ''; + # --- Xcode : installation manuelle, reste automatique ------------------- + # Apple interdit la redistribution du SDK : Xcode vient de l'App Store (aucun + # paquet nix ne peut le fournir). Tout ce qui suit l'installation est en + # revanche scriptable, et c'est ici : developer dir, licence, premiers + # composants. Le runtime du simulateur (~8 Go) n'est pas téléchargé + # silencieusement : `winnie-doctor --fix` s'en charge à la demande. + system.activationScripts.winnie-xcode.text = '' + if [ -d /Applications/Xcode.app ]; then + if [ "$(/usr/bin/xcode-select -p 2>/dev/null || true)" != "/Applications/Xcode.app/Contents/Developer" ]; then + echo "winnie : xcode-select -> /Applications/Xcode.app" >&2 + /usr/bin/xcode-select -s /Applications/Xcode.app || true + /usr/bin/xcodebuild -license accept || true + /usr/bin/xcodebuild -runFirstLaunch || true + fi + else + echo "winnie : Xcode absent (App Store) ; le build macOS marche sans, seul winnie-ios l'exige" >&2 + fi + ''; + system.stateVersion = 6; } diff --git a/users/mac/home.nix b/users/mac/home.nix index 78fadd7..4edb983 100644 --- a/users/mac/home.nix +++ b/users/mac/home.nix @@ -23,6 +23,7 @@ ../../modules/home-mac/aerospace ../../modules/home-mac/hermes-skins + ../../modules/home-mac/winnie # ../../modules/home-mac/autossh ]; @@ -120,6 +121,11 @@ # Skins Hermes (pack Hermes Menagerie) -> ~/.hermes/skins/ my.hermesSkins.enable = true; + # Build de l'app Winnie sur le Mac : winnie-doctor, winnie-sync, winnie-ios, + # winnie-macos, winnie-front, winnie-verify. Le toolchain reste dans le + # devShell du flake `winnie`, ces commandes ne font que l'appeler. + my.winnie.enable = true; + # Connexions Eternal Terminal vers les hôtes NixOS : `et tower` = zellij enregistré. my.et = { enable = true; @@ -128,4 +134,35 @@ towerlocal = {}; }; }; + + # Clé SSH de l'agent hermes@tower : l'agent (qui tourne sur tower) peut se + # connecter au Mac en marc@… (sshd activé par services.openssh dans + # profiles/macos/configuration.nix). Append idempotent plutôt que + # home.file.".ssh/authorized_keys" : ne remplace JAMAIS un authorized_keys + # déjà en place (le .bak de home-manager perdrait les clés existantes). + home.activation.addHermesAuthorizedKey = lib.hm.dag.entryAfter ["writeBoundary"] '' + key='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKF3huPUXHP6P6SBXHHw9k7HGh6Cs8ntoRk2pnqrG2Hc hermes@tower' + mkdir -p "$HOME/.ssh" + chmod 700 "$HOME/.ssh" + touch "$HOME/.ssh/authorized_keys" + grep -qxF "$key" "$HOME/.ssh/authorized_keys" || echo "$key" >> "$HOME/.ssh/authorized_keys" + chmod 600 "$HOME/.ssh/authorized_keys" + ''; + + # Accès distant par rack (VPS à IP publique) : marche depuis n'importe quel + # réseau, sans VPN sur le Mac. rack joint tower par le tunnel wg1 + # (10.100.0.3) et le LAN (192.168.1.0/24) que tower route pour wg1. + # mkForce : ces deux entrées remplacent celles du module partagé. + programs.ssh.settings = { + "tower" = lib.mkForce { + HostName = "10.100.0.3"; + User = "marc"; + ProxyJump = "rack"; + }; + "deck" = lib.mkForce { + HostName = "192.168.1.67"; + User = "marc"; + ProxyJump = "rack"; + }; + }; }