From 1938b7c743201a62653949277615772e6d0bbbff Mon Sep 17 00:00:00 2001 From: ahauimix Date: Mon, 1 Jun 2026 08:12:19 +0300 Subject: [PATCH] Expand local_machine docs and automation for connectivity, games, and ops. Add proxy/VPN/telegram launchd and emergency runbooks; reorganize apps docs; document JA3 CrossOver runbook and Wine troubleshooting; add GOG/HoMM game scripts, disk cleanup guides, and gitea push-via-proxy helper. Ignore temp/. Co-authored-by: Cursor --- .gitignore | 3 + docs/apps/anytype/README.md | 164 +++ docs/{ => apps}/cursor/README.md | 51 +- .../cursor/scripts/cursor-socks-tunnel.ps1 | 107 ++ .../cursor/scripts/cursor-socks-tunnel.sh | 0 docs/{ => apps}/launch/ALLOWED.md | 0 docs/{ => apps}/launch/DISALLOWED.md | 0 .../launch/disable_login_items_notion_mega.sh | 0 .../launch/disable_system_launch_items.sh | 0 .../connectivity/HOTSPOT_NETWORK_EMERGENCY.md | 122 +++ docs/connectivity/PROXY_EMERGENCY.md | 172 ++++ docs/connectivity/PROXY_GUIDE.md | 216 +++- docs/connectivity/README.md | 22 +- docs/connectivity/TELEGRAM_GUIDE.md | 299 +++--- docs/connectivity/WINDOWS_CURSOR_TUNNEL.md | 269 +++++ docs/connectivity/WINDOWS_SSH_KEY_INSTALL.md | 166 +++ docs/connectivity/launchd/README.md | 34 +- .../com.hunab.telegram-socks-via-do.plist | 35 + docs/connectivity/launchd/install-via-do.sh | 29 + .../launchd/telegram-socks-launch.sh | 13 + docs/connectivity/launchd/uninstall-via-do.sh | 12 + docs/connectivity/matrix/Caddyfile.example | 27 + docs/connectivity/matrix/docker-compose.yml | 45 + docs/connectivity/matrix/env.example | 2 + .../scripts/create-windows-proxy-key.sh | 51 + .../scripts/macos-hotspot-network-recover.sh | 105 ++ docs/connectivity/scripts/macos-pac-off.sh | 2 + docs/connectivity/scripts/macos-pac-on.sh | 2 + docs/connectivity/scripts/macos-system-pac.sh | 76 ++ .../scripts/setup-ssh-key-for-proxy.ps1 | 97 ++ docs/connectivity/scripts/ssh-hunab-prod.sh | 19 + .../scripts/telegram-socks-tunnel.sh | 78 +- docs/connectivity/vpn/README.md | 11 + docs/connectivity/vpn/SSH_EXIT_MODES.md | 95 ++ docs/disk_cleanup/DISK_CLEANUP.md | 20 +- docs/disk_cleanup/DO_NOT_DELETE.md | 204 ++++ docs/disk_cleanup/cleanup-disk-now.sh | 38 +- .../scripts/scan-cleanup-items.sh | 32 +- .../LOSSLESS_VIDEO_COMPRESSION_GUIDE.md | 128 +++ docs/games/GOG_GALAXY_7H_RCA.md | 117 +++ docs/games/JA3_MAC_WITHOUT_GALAXY.md | 216 ++++ docs/games/JA3_RUNBOOK.md | 297 ++++++ docs/games/README.md | 0 docs/games/TROUBLESHOOTING.md | 962 ++++++++++++++++++ docs/games/UNITY_POINTER_MOUSE_FIX.md | 48 + docs/games/WINE_APPS_LAUNCH_RUNBOOK.md | 225 ++++ docs/games/WINE_MAC_RUNBOOK.md | 205 ++++ docs/git/GITEA_COMPLETE_GUIDE.md | 331 +++--- docs/git/LOCAL_MACHINE_GITEA_SETUP.md | 16 +- .../gitea/git-push-gitea-via-proxy.sh | 211 ++++ scripts/games/README.md | 0 scripts/games/cache/.gitignore | 2 + scripts/games/config.sh | 214 ++++ scripts/games/diagnose-gog-auth.sh | 46 + scripts/games/diagnose-gog-ja3-session.sh | 4 + scripts/games/diagnose-homm-mouse.sh | 41 + scripts/games/diagnose-homm-saves.sh | 88 ++ scripts/games/diagnose-homm-steam-ui.sh | 74 ++ scripts/games/fix-gog-auth.sh | 75 ++ scripts/games/fix-homm-steam-ui.sh | 45 + scripts/games/fix-homm-wine-input.sh | 28 + scripts/games/gog/.gitignore | 1 + scripts/games/gog/README.md | 64 ++ scripts/games/gog/bottle.env.example | 6 + scripts/games/gog/collect-gog-logs-7h.sh | 70 ++ scripts/games/gog/collect-setup-rca.sh | 38 + scripts/games/gog/config.sh | 238 +++++ scripts/games/gog/configure-ja3-d3dmetal.sh | 37 + scripts/games/gog/create-bottle.sh | 102 ++ scripts/games/gog/diagnose-galaxy-window.sh | 44 + scripts/games/gog/diagnose-session.sh | 28 + scripts/games/gog/emergency-stop.sh | 26 + scripts/games/gog/fetch-ja3-setup-exe.sh | 42 + scripts/games/gog/finish-ja3-install.sh | 7 + scripts/games/gog/finish-ja3-layout.sh | 48 + scripts/games/gog/fix-ja3-crossover-mouse.sh | 77 ++ scripts/games/gog/force-ja3-d3d11.sh | 35 + scripts/games/gog/gog-process.sh | 72 ++ scripts/games/gog/install-all.sh | 37 + scripts/games/gog/install-corefonts.sh | 48 + scripts/games/gog/install-dxvk.sh | 35 + scripts/games/gog/install-galaxy-extract.sh | 100 ++ scripts/games/gog/install-gdiplus.sh | 20 + scripts/games/gog/install-gptk-whisky.sh | 51 + scripts/games/gog/install-ja3-dxmt.sh | 58 ++ scripts/games/gog/install-ja3-innoextract.sh | 203 ++++ scripts/games/gog/install-ja3-multipart.sh | 152 +++ scripts/games/gog/install-ja3-offline.sh | 64 ++ scripts/games/gog/install-ja3-redist.sh | 35 + scripts/games/gog/install-ja3-wine-finish.sh | 53 + scripts/games/gog/install-ja3.sh | 66 ++ scripts/games/gog/ja3-dxvk.conf | 6 + scripts/games/gog/launch-galaxy-ui.sh | 9 + scripts/games/gog/launch-galaxy-whisky.sh | 22 + scripts/games/gog/launch-galaxy.sh | 133 +++ scripts/games/gog/list-bottles.sh | 11 + scripts/games/gog/pin-galaxy-whisky.sh | 45 + scripts/games/gog/prepare-ja3-d3dmetal.sh | 84 ++ scripts/games/gog/purge-steam-from-bottle.sh | 52 + scripts/games/gog/register-galaxy-registry.sh | 20 + scripts/games/gog/register-ja3-registry.sh | 27 + scripts/games/gog/reset-ja3-crossover.sh | 17 + scripts/games/gog/reset.sh | 9 + scripts/games/gog/run-galaxy-setup.sh | 117 +++ scripts/games/gog/run-ja3-crossover.sh | 35 + scripts/games/gog/run-ja3-gptk-wine.sh | 57 ++ scripts/games/gog/run-ja3.sh | 103 ++ scripts/games/gog/run-web-installer.sh | 96 ++ scripts/games/gog/setup-bottle.sh | 53 + scripts/games/gog/setup-ja3-bottle.sh | 24 + scripts/games/gog/setup-ja3-crossover.sh | 72 ++ scripts/games/gog/setup-ja3-gptk-prefix.sh | 64 ++ .../games/gog/upgrade-whisky-wine-gcenx.sh | 45 + scripts/games/gog/use-bottle.sh | 20 + scripts/games/gog/wine-fonts.sh | 85 ++ scripts/games/install-dxvk-macos.sh | 53 + scripts/games/install-gog-galaxy.sh | 109 ++ scripts/games/install-steam-extract.sh | 31 + scripts/games/install-steam-in-bottle.sh | 25 + scripts/games/install-whiskywine-manual.sh | 24 + scripts/games/install-wine-corefonts.sh | 3 + .../games/migrate-bottle-to-franke-whisky.sh | 50 + scripts/games/pin-gog-whisky.sh | 66 ++ scripts/games/purge-steam-wine.sh | 83 ++ scripts/games/run-gog-web-installer.sh | 3 + scripts/games/run-homm-bottle.sh | 3 + scripts/games/run-wine-exe.sh | 186 ++++ scripts/games/setup-gog-bottle.sh | 2 + scripts/games/setup-wine-bottle.sh | 96 ++ scripts/games/start.sh | 281 +++++ scripts/games/wine-fonts.sh | 104 ++ scripts/vpn/exit-via-local.sh | 3 + scripts/vpn/exit-via-ru-do.sh | 3 + scripts/vpn/exit-via-ru.sh | 3 + scripts/vpn/ssh-exit-mode.sh | 139 +++ 135 files changed, 9933 insertions(+), 388 deletions(-) create mode 100644 docs/apps/anytype/README.md rename docs/{ => apps}/cursor/README.md (71%) create mode 100644 docs/apps/cursor/scripts/cursor-socks-tunnel.ps1 rename docs/{ => apps}/cursor/scripts/cursor-socks-tunnel.sh (100%) rename docs/{ => apps}/launch/ALLOWED.md (100%) rename docs/{ => apps}/launch/DISALLOWED.md (100%) rename docs/{ => apps}/launch/disable_login_items_notion_mega.sh (100%) rename docs/{ => apps}/launch/disable_system_launch_items.sh (100%) create mode 100644 docs/connectivity/HOTSPOT_NETWORK_EMERGENCY.md create mode 100644 docs/connectivity/PROXY_EMERGENCY.md create mode 100644 docs/connectivity/WINDOWS_CURSOR_TUNNEL.md create mode 100644 docs/connectivity/WINDOWS_SSH_KEY_INSTALL.md create mode 100644 docs/connectivity/launchd/com.hunab.telegram-socks-via-do.plist create mode 100755 docs/connectivity/launchd/install-via-do.sh create mode 100755 docs/connectivity/launchd/telegram-socks-launch.sh create mode 100755 docs/connectivity/launchd/uninstall-via-do.sh create mode 100644 docs/connectivity/matrix/Caddyfile.example create mode 100644 docs/connectivity/matrix/docker-compose.yml create mode 100644 docs/connectivity/matrix/env.example create mode 100755 docs/connectivity/scripts/create-windows-proxy-key.sh create mode 100755 docs/connectivity/scripts/macos-hotspot-network-recover.sh create mode 100755 docs/connectivity/scripts/macos-pac-off.sh create mode 100755 docs/connectivity/scripts/macos-pac-on.sh create mode 100755 docs/connectivity/scripts/macos-system-pac.sh create mode 100644 docs/connectivity/scripts/setup-ssh-key-for-proxy.ps1 create mode 100755 docs/connectivity/scripts/ssh-hunab-prod.sh create mode 100644 docs/connectivity/vpn/SSH_EXIT_MODES.md create mode 100644 docs/disk_cleanup/DO_NOT_DELETE.md create mode 100644 docs/disk_cleanup/videos/LOSSLESS_VIDEO_COMPRESSION_GUIDE.md create mode 100644 docs/games/GOG_GALAXY_7H_RCA.md create mode 100644 docs/games/JA3_MAC_WITHOUT_GALAXY.md create mode 100644 docs/games/JA3_RUNBOOK.md create mode 100644 docs/games/README.md create mode 100644 docs/games/TROUBLESHOOTING.md create mode 100644 docs/games/UNITY_POINTER_MOUSE_FIX.md create mode 100644 docs/games/WINE_APPS_LAUNCH_RUNBOOK.md create mode 100644 docs/games/WINE_MAC_RUNBOOK.md create mode 100755 scripts/deployment/gitea/git-push-gitea-via-proxy.sh create mode 100644 scripts/games/README.md create mode 100644 scripts/games/cache/.gitignore create mode 100755 scripts/games/config.sh create mode 100755 scripts/games/diagnose-gog-auth.sh create mode 100755 scripts/games/diagnose-gog-ja3-session.sh create mode 100755 scripts/games/diagnose-homm-mouse.sh create mode 100755 scripts/games/diagnose-homm-saves.sh create mode 100755 scripts/games/diagnose-homm-steam-ui.sh create mode 100755 scripts/games/fix-gog-auth.sh create mode 100755 scripts/games/fix-homm-steam-ui.sh create mode 100755 scripts/games/fix-homm-wine-input.sh create mode 100644 scripts/games/gog/.gitignore create mode 100644 scripts/games/gog/README.md create mode 100644 scripts/games/gog/bottle.env.example create mode 100755 scripts/games/gog/collect-gog-logs-7h.sh create mode 100755 scripts/games/gog/collect-setup-rca.sh create mode 100755 scripts/games/gog/config.sh create mode 100755 scripts/games/gog/configure-ja3-d3dmetal.sh create mode 100755 scripts/games/gog/create-bottle.sh create mode 100755 scripts/games/gog/diagnose-galaxy-window.sh create mode 100755 scripts/games/gog/diagnose-session.sh create mode 100755 scripts/games/gog/emergency-stop.sh create mode 100755 scripts/games/gog/fetch-ja3-setup-exe.sh create mode 100755 scripts/games/gog/finish-ja3-install.sh create mode 100755 scripts/games/gog/finish-ja3-layout.sh create mode 100755 scripts/games/gog/fix-ja3-crossover-mouse.sh create mode 100755 scripts/games/gog/force-ja3-d3d11.sh create mode 100755 scripts/games/gog/gog-process.sh create mode 100755 scripts/games/gog/install-all.sh create mode 100755 scripts/games/gog/install-corefonts.sh create mode 100755 scripts/games/gog/install-dxvk.sh create mode 100755 scripts/games/gog/install-galaxy-extract.sh create mode 100755 scripts/games/gog/install-gdiplus.sh create mode 100755 scripts/games/gog/install-gptk-whisky.sh create mode 100755 scripts/games/gog/install-ja3-dxmt.sh create mode 100755 scripts/games/gog/install-ja3-innoextract.sh create mode 100755 scripts/games/gog/install-ja3-multipart.sh create mode 100755 scripts/games/gog/install-ja3-offline.sh create mode 100755 scripts/games/gog/install-ja3-redist.sh create mode 100755 scripts/games/gog/install-ja3-wine-finish.sh create mode 100755 scripts/games/gog/install-ja3.sh create mode 100644 scripts/games/gog/ja3-dxvk.conf create mode 100755 scripts/games/gog/launch-galaxy-ui.sh create mode 100755 scripts/games/gog/launch-galaxy-whisky.sh create mode 100755 scripts/games/gog/launch-galaxy.sh create mode 100755 scripts/games/gog/list-bottles.sh create mode 100755 scripts/games/gog/pin-galaxy-whisky.sh create mode 100755 scripts/games/gog/prepare-ja3-d3dmetal.sh create mode 100755 scripts/games/gog/purge-steam-from-bottle.sh create mode 100755 scripts/games/gog/register-galaxy-registry.sh create mode 100755 scripts/games/gog/register-ja3-registry.sh create mode 100755 scripts/games/gog/reset-ja3-crossover.sh create mode 100755 scripts/games/gog/reset.sh create mode 100755 scripts/games/gog/run-galaxy-setup.sh create mode 100755 scripts/games/gog/run-ja3-crossover.sh create mode 100755 scripts/games/gog/run-ja3-gptk-wine.sh create mode 100755 scripts/games/gog/run-ja3.sh create mode 100755 scripts/games/gog/run-web-installer.sh create mode 100755 scripts/games/gog/setup-bottle.sh create mode 100755 scripts/games/gog/setup-ja3-bottle.sh create mode 100755 scripts/games/gog/setup-ja3-crossover.sh create mode 100755 scripts/games/gog/setup-ja3-gptk-prefix.sh create mode 100755 scripts/games/gog/upgrade-whisky-wine-gcenx.sh create mode 100755 scripts/games/gog/use-bottle.sh create mode 100755 scripts/games/gog/wine-fonts.sh create mode 100755 scripts/games/install-dxvk-macos.sh create mode 100755 scripts/games/install-gog-galaxy.sh create mode 100755 scripts/games/install-steam-extract.sh create mode 100755 scripts/games/install-steam-in-bottle.sh create mode 100755 scripts/games/install-whiskywine-manual.sh create mode 100644 scripts/games/install-wine-corefonts.sh create mode 100755 scripts/games/migrate-bottle-to-franke-whisky.sh create mode 100755 scripts/games/pin-gog-whisky.sh create mode 100755 scripts/games/purge-steam-wine.sh create mode 100755 scripts/games/run-gog-web-installer.sh create mode 100755 scripts/games/run-homm-bottle.sh create mode 100755 scripts/games/run-wine-exe.sh create mode 100755 scripts/games/setup-gog-bottle.sh create mode 100755 scripts/games/setup-wine-bottle.sh create mode 100755 scripts/games/start.sh create mode 100644 scripts/games/wine-fonts.sh create mode 100755 scripts/vpn/exit-via-local.sh create mode 100755 scripts/vpn/exit-via-ru-do.sh create mode 100755 scripts/vpn/exit-via-ru.sh create mode 100755 scripts/vpn/ssh-exit-mode.sh diff --git a/.gitignore b/.gitignore index abf4d0c..7c06943 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ # VPN/WireGuard configs with private keys (only under connectivity/vpn) docs/connectivity/vpn/*.conf **/vpn/*.conf +# Key pair for Windows proxy tunnel (created by create-windows-proxy-key.sh) +docs/connectivity/scripts/windows-proxy-key/ # --- Python --- __pycache__/ @@ -67,6 +69,7 @@ yarn-error.log* *.backup *.cache .cache/ +temp/ # --- OS junk --- Thumbs.db diff --git a/docs/apps/anytype/README.md b/docs/apps/anytype/README.md new file mode 100644 index 0000000..a216fc0 --- /dev/null +++ b/docs/apps/anytype/README.md @@ -0,0 +1,164 @@ +# Anytype (macOS) — чёрный экран после загрузки, пустое окно + +> **Проверенный кейс (2026-04):** интерфейс успевает показать контент, затем окно уходит в чёрный/пустой экран. +> **Что сработало:** обновление клиента до актуальной стабильной версии через Homebrew Cask; данные в `~/Library/Application Support/anytype/data/` не трогались. + +**Связано:** безопасная очистка кэшей и структура каталогов — [ANYTYPE_WORKSPACE_FIX.md](../../disk_cleanup/ANYTYPE_WORKSPACE_FIX.md). Официально про графику — [Troubleshooting (Anytype Docs)](https://doc.anytype.io/anytype-docs/advanced/troubleshooting). + +--- + +## Содержание + +1. [Симптомы](#1-симптомы) +2. [Диагностика](#2-диагностика) +3. [Что нельзя удалять (данные)](#3-что-нельзя-удалять-данные) +4. [Шаг 1 (опционально): только кэши UI](#4-шаг-1-опционально-только-кэши-ui) +5. [Шаг 2 (основной): обновить приложение](#5-шаг-2-основной-обновить-приложение) +6. [Если после обновления снова IncompatibleVersion](#6-если-после-обновления-снова-incompatibleversion) +7. [Сброс профиля Electron с сохранением vault](#7-сброс-профиля-electron-с-сохранением-vault) +8. [Ссылки](#8-ссылки) + +--- + +## 1. Симптомы + +- Окно Anytype **сначала отрисовывается**, затем **чёрный или пустой** экран. +- Несколько окон / фон / сон Mac — иногда усугубляют (Electron), но **не всегда** являются первопричиной. +- В логах ядра при работе с сетью возможны ошибки **`IncompatibleVersion`** (несовпадение версии клиента с ожиданиями координации Anytype). + +--- + +## 2. Диагностика + +### Версия установленного приложения + +```bash +/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' /Applications/Anytype.app/Contents/Info.plist +``` + +### Логи клиента (Electron) + +```bash +tail -n 200 "$HOME/Library/Application Support/anytype/logs/log.log" +``` + +### Логи middleware («сердце» / синхронизация) + +```bash +grep -n 'IncompatibleVersion' "$HOME/Library/Application Support/anytype/common/logs/anytype.log" | tail -20 +``` + +Если **`IncompatibleVersion`** повторяется на свежем запуске — в первую очередь **обновите клиент** до версии не старше рекомендуемой сетью (стабильный cask Homebrew обычно достаточен). + +### Место на диске + +Переполненный том ломает **автообновление** (обрыв загрузки, checksum mismatch, `ENOSPC` в логах). Перед установкой DMG/ZIP оставьте запас свободного места на томе с `/Users` (ориентир: не держать системный том постоянно **>~95%** занятым). + +--- + +## 3. Что нельзя удалять (данные) + +Локальный vault и ключи лежат под: + +```text +~/Library/Application Support/anytype/data/ +``` + +Внутри каталога аккаунта — как минимум `localstore`, `spaceStoreNew`, `objectstore`, `config.json`, `device.key` и т.д. Подробная схема — в [ANYTYPE_WORKSPACE_FIX.md](../../disk_cleanup/ANYTYPE_WORKSPACE_FIX.md). + +**Страховка перед любыми действиями:** + +```bash +mkdir -p "$HOME/Desktop/anytype-backups" +cp -a "$HOME/Library/Application Support/anytype/data" \ + "$HOME/Desktop/anytype-backups/data-$(date +%Y%m%d_%H%M%S)" +``` + +--- + +## 4. Шаг 1 (опционально): только кэши UI + +Пока Anytype **полностью закрыт** (`Anytype` → Quit или `killall Anytype`), можно удалить **только** кэши Chromium/Electron — **не** трогая `data/`. Готовый набор команд — в [ANYTYPE_WORKSPACE_FIX.md § Безопасная очистка](../../disk_cleanup/ANYTYPE_WORKSPACE_FIX.md#-безопасная-очистка-кэша-anytype). + +Официальная подсказка для графики (Linux-путь в доке; на macOS аналог — те же папки под `Application Support/anytype`): удалить **`GPUCache`** — см. [Troubleshooting](https://doc.anytype.io/anytype-docs/advanced/troubleshooting). + +--- + +## 5. Шаг 2 (основной): обновить приложение + +Рекомендуемый способ на macOS — **Homebrew Cask** (версия совпадает с тем, что проверяет сообщество; URL — официальный CDN Anytype в формуле cask). + +### 5.1. Закрыть приложение + +```bash +killall Anytype 2>/dev/null || true +sleep 1 +``` + +### 5.2. Установка / обновление + +```bash +HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask anytype +``` + +Если Brew сообщает, что **`Anytype.app` уже есть в `/Applications`** и установка **прерывается**, старое приложение нужно **убрать из пути** (переименовать — безопасно, это не данные): + +```bash +killall Anytype 2>/dev/null || true +sleep 1 +ts=$(date +%Y%m%d_%H%M%S) +mv /Applications/Anytype.app "/Applications/Anytype.app.backup-$ts" +HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask anytype +``` + +Проверка версии: + +```bash +/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' /Applications/Anytype.app/Contents/Info.plist +``` + +Запуск: + +```bash +open -a Anytype +``` + +После проверки резервную копию `Anytype.app.backup-*` можно удалить. + +**Важно:** установка/замена **`.app`** не удаляет `~/Library/Application Support/anytype/data/` — локальные пространства и объекты сохраняются. + +--- + +## 6. Если после обновления снова IncompatibleVersion + +1. Убедиться, что действительно запускается **новый** бинарь (версия в Info.plist, один экземпляр в `/Applications`). +2. Освободить место на диске и повторить обновление. +3. При необходимости поставить более свежую ветку (**alpha**), если она доступна отдельным cask и не конфликтует со стабильным (см. `brew search anytype` и описание cask’ов). + +--- + +## 7. Сброс профиля Electron с сохранением vault + +Если обновление **помогло по версии**, но UI всё ещё «ломается», можно пересоздать кэш/Local Storage **всего каталога поддержки**, оставив только `data/` (как и раньше — **сначала полная копия** `data/` в безопасное место). + +Пока Anytype **закрыт**: + +```bash +killall Anytype 2>/dev/null || true +BASE="$HOME/Library/Application Support/anytype" +cp -a "$BASE/data" "$HOME/Desktop/anytype-backups/data-manual-$(date +%Y%m%d_%H%M%S)" +mv "$BASE" "${BASE}_profile_backup_$(date +%Y%m%d_%H%M%S)" +mkdir -p "$BASE" +mv "${BASE}_profile_backup_"*/data "$BASE/" +open -a Anytype +``` + +Не удаляйте каталог `*_profile_backup_*`, пока не убедитесь, что приложение снова стабильно открывается. + +--- + +## 8. Ссылки + +- [Anytype — Troubleshooting](https://doc.anytype.io/anytype-docs/advanced/troubleshooting) +- [anytype-ts releases (GitHub)](https://github.com/anyproto/anytype-ts/releases) +- [Homebrew Cask `anytype`](https://formulae.brew.sh/cask/anytype) diff --git a/docs/cursor/README.md b/docs/apps/cursor/README.md similarity index 71% rename from docs/cursor/README.md rename to docs/apps/cursor/README.md index 43da18c..7b6f193 100644 --- a/docs/cursor/README.md +++ b/docs/apps/cursor/README.md @@ -4,7 +4,7 @@ > **Временный обход:** Disable Http2 в настройках — помогает, но всё сильно замедляется. > **Рекомендуемое решение:** SOCKS-прокси через SSH-туннель на сервер с нормальным доступом (DO / RU VPS), без отключения HTTP/2. -**Связанные гайды:** [PROXY_GUIDE.md](../../connectivity/PROXY_GUIDE.md), [BUILD_AAB_FROM_RUSSIA_GUIDE.md](../../connectivity/BUILD_AAB_FROM_RUSSIA_GUIDE.md) — тот же паттерн «туннель через прокси-сервер». +**Связанные гайды:** [PROXY_GUIDE.md](../connectivity/PROXY_GUIDE.md), [BUILD_AAB_FROM_RUSSIA_GUIDE.md](../connectivity/BUILD_AAB_FROM_RUSSIA_GUIDE.md) — тот же паттерн «туннель через прокси-сервер». **Выход через DO (как VPN на 209.38.32.21):** [docs/connectivity/vpn/README.md](../connectivity/vpn/README.md). **Windows:** [WINDOWS_CURSOR_TUNNEL.md](../connectivity/WINDOWS_CURSOR_TUNNEL.md) и скрипт `scripts/cursor-socks-tunnel.ps1`. --- @@ -58,35 +58,45 @@ ### Схема ``` -Cursor (macOS) → SOCKS 127.0.0.1:10809 → SSH-туннель → VPS (DO/RU) - ↓ - HTTPS/HTTP2 → *.cursor.sh +Cursor → SOCKS 127.0.0.1:10809 → SSH → RU hsites-ahau → DO 209.38.32.21 + ↓ + HTTPS/HTTP2 → *.cursor.sh ``` -На VPS не нужны отдельные прокси-сервисы — достаточно SSH (как в [PROXY_GUIDE](../../connectivity/PROXY_GUIDE.md) и [BUILD_AAB_FROM_RUSSIA_GUIDE](../../connectivity/BUILD_AAB_FROM_RUSSIA_GUIDE.md)). +При **`CURSOR_SOCKS_VIA_DO=1`** (по умолчанию) — **только** цепочка **Mac → RU → DO** (`telegram-socks-via-do`). Если **`hsites-ahau`** недоступен, скрипт **завершается с ошибкой** (прямой Mac→DO для SOCKS не поднимается). Публичный IP снаружи — **DO** (шаблон `Host telegram-socks-via-do` в [PROXY_GUIDE.md](../connectivity/PROXY_GUIDE.md)). + +На стороне DO не нужен отдельный прокси-сервис — только SSH и те же `Host`, что для Telegram. ### Предположения -- Есть VPS с нормальным доступом к интернету (например, тот же, что для ru.hunab.app: **149.154.64.19**, доступ по `ssh hsites-ahau` или аналог). -- Локально свободен порт для SOCKS (ниже — **10809**, чтобы не пересекаться с другими прокси). +- В **`~/.ssh/config`** задан **`telegram-socks-via-do`** с **`ProxyJump hsites-ahau`** (шаблон в [PROXY_GUIDE.md](../connectivity/PROXY_GUIDE.md)). +- Локально свободен порт **10809** (не путать с **1081** для Telegram). ### Шаг 1: Поднять SOCKS-туннель +Рекомендуется скрипт (выход через DO по умолчанию): + +```bash +./docs/cursor/scripts/cursor-socks-tunnel.sh start +``` + +Переменные: **`CURSOR_SOCKS_PORT`** (по умолчанию 10809), **`CURSOR_SOCKS_VIA_DO=0`** — только RU (`CURSOR_TUNNEL_HOST`, по умолчанию `hsites-ahau`). + +Вручную (выход с DO, как у скрипта): + ```bash -# Убить старый туннель на порту 10809 (если был) pkill -f "ssh.*-D 10809" 2>/dev/null - -# Запуск SOCKS5 на 127.0.0.1:10809 через ваш VPS -ssh -D 10809 -f -N hsites-ahau +ssh -D 10809 -f -N -o ServerAliveInterval=60 -o ServerAliveCountMax=4 telegram-socks-via-do ``` -Если используете другой хост (например, hunab-prod): +Только RU (без DO в цепочке выхода): ```bash -ssh -D 10809 -f -N hunab-prod +CURSOR_SOCKS_VIA_DO=0 ./docs/cursor/scripts/cursor-socks-tunnel.sh start +# или: ssh -D 10809 -f -N hsites-ahau ``` -Проверка: туннель держится, пока сессия не разорвана; можно проверить доступ через SOCKS (см. [Проверка и диагностика](#-проверка-и-диагностика)). +Проверка: туннель держится, пока сессия не разорвана; см. [Проверка и диагностика](#-проверка-и-диагностика). ### Шаг 2: Настроить Cursor на использование SOCKS @@ -106,13 +116,13 @@ ssh -D 10809 -f -N hunab-prod ### Шаг 3: Не отключать HTTP/2 -**Cursor > General: Disable Http2** — выключить (оставить HTTP/2 включённым). Весь трафик к Cursor API пойдёт с VPS, где HTTP/2 работает нормально. +**Cursor > General: Disable Http2** — выключить (оставить HTTP/2 включённым). Трафик к Cursor API идёт с DO (или с RU, если включён только RU-туннель). ### Шаг 4: Запуск Cursor при уже поднятом туннеле Порядок каждый раз: -1. В терминале: `ssh -D 10809 -f -N hsites-ahau` (или ваш хост). +1. В терминале: `./docs/cursor/scripts/cursor-socks-tunnel.sh start` (или ручная команда из шага 1). 2. Запуск Cursor как обычно. Чтобы не забывать туннель, можно завести скрипт запуска (см. [Скрипт запуска с туннелем](#скрипт-запуска-с-туннелем)). @@ -194,7 +204,7 @@ ssh -D 10809 -f -N -o ServerAliveInterval=30 -o ServerAliveCountMax=6 hsites-aha ./docs/cursor/scripts/cursor-socks-tunnel.sh stop # остановить ``` -Переменные окружения: `CURSOR_SOCKS_PORT` (по умолчанию 10809), `CURSOR_TUNNEL_HOST` (по умолчанию `hsites-ahau`). +Переменные окружения: `CURSOR_SOCKS_PORT` (10809), **`CURSOR_SOCKS_VIA_DO`** (по умолчанию **1** — выход через DO), при **`CURSOR_SOCKS_VIA_DO=0`** — `CURSOR_TUNNEL_HOST` (по умолчанию `hsites-ahau`). **Запуск Cursor после туннеля (macOS):** после `./docs/cursor/scripts/cursor-socks-tunnel.sh start` откройте Cursor как обычно; при настроенных `http.proxy`/`https.proxy` трафик пойдёт через SOCKS. @@ -204,11 +214,12 @@ ssh -D 10809 -f -N -o ServerAliveInterval=30 -o ServerAliveCountMax=6 hsites-aha | Документ | Назначение | |----------|------------| -| [PROXY_GUIDE.md](../../connectivity/PROXY_GUIDE.md) | Прокси ru.hunab.app, SSH ProxyCommand для scp/ssh на прод | -| [BUILD_AAB_FROM_RUSSIA_GUIDE.md](../../connectivity/BUILD_AAB_FROM_RUSSIA_GUIDE.md) | Сборка AAB из РФ через Maven proxy + SSH-туннель | +| [PROXY_GUIDE.md](../connectivity/PROXY_GUIDE.md) | Прокси ru.hunab.app, SSH ProxyCommand для scp/ssh на прод | +| [WINDOWS_CURSOR_TUNNEL.md](../connectivity/WINDOWS_CURSOR_TUNNEL.md) | Cursor из РФ на **Windows**: туннель, скрипт .ps1, автозапуск | +| [BUILD_AAB_FROM_RUSSIA_GUIDE.md](../connectivity/BUILD_AAB_FROM_RUSSIA_GUIDE.md) | Сборка AAB из РФ через Maven proxy + SSH-туннель | | [Cursor Forum — HTTP/2 network error](https://forum.cursor.com/t/http2-network-error-with-cursor-ide-and-cursor-cli/147318) | Обсуждение ошибок HTTP/2 и региональной маршрутизации | | [Cursor Forum — Agent timeouts when HTTP2 disabled](https://forum.cursor.com/t/agent-timeouts-much-more-when-http2-disabled/76517) | Таймауты агента при отключённом HTTP/2 | --- -*Последнее обновление: 2026-03-08* +*Последнее обновление: 2026-04-08* diff --git a/docs/apps/cursor/scripts/cursor-socks-tunnel.ps1 b/docs/apps/cursor/scripts/cursor-socks-tunnel.ps1 new file mode 100644 index 0000000..25a3350 --- /dev/null +++ b/docs/apps/cursor/scripts/cursor-socks-tunnel.ps1 @@ -0,0 +1,107 @@ +# SOCKS5-туннель для Cursor (обход PING timeout из РФ). +# Использование: .\cursor-socks-tunnel.ps1 [start|stop|status] +# См. docs/cursor/README.md и docs/connectivity/WINDOWS_CURSOR_TUNNEL.md + +param( + [Parameter(Position = 0)] + [ValidateSet("start", "stop", "status")] + [string]$Command = "start" +) + +$CURSOR_SOCKS_PORT = if ($env:CURSOR_SOCKS_PORT) { $env:CURSOR_SOCKS_PORT } else { "10809" } +$SSH_HOST = if ($env:CURSOR_TUNNEL_HOST) { $env:CURSOR_TUNNEL_HOST } else { "ahau@149.154.64.19" } +$CursorViaDo = ($env:CURSOR_SOCKS_VIA_DO -ne "0") + +function Test-PortListening { + $conn = Get-NetTCPConnection -LocalPort $CURSOR_SOCKS_PORT -State Listen -ErrorAction SilentlyContinue + return $null -ne $conn +} + +function Get-SshTunnelPids { + # Найти процессы ssh.exe, у которых в командной строке есть -D и наш порт + Get-CimInstance Win32_Process -Filter "Name = 'ssh.exe'" -ErrorAction SilentlyContinue | + Where-Object { $_.CommandLine -match "-D\s+$CURSOR_SOCKS_PORT" } | + ForEach-Object { $_.ProcessId } +} + +function Start-Tunnel { + if (Test-PortListening) { + Write-Host "SOCKS already listening on 127.0.0.1:$CURSOR_SOCKS_PORT" + return + } + $targetHost = $SSH_HOST + $stableOpts = @( + "-o", "TCPKeepAlive=yes", + "-o", "IPQoS=throughput", + "-o", "ServerAliveInterval=60", + "-o", "ServerAliveCountMax=4" + ) + $ruOpts = @( + "-o", "ServerAliveInterval=30", + "-o", "ServerAliveCountMax=6" + ) + if ($CursorViaDo) { + & ssh -o BatchMode=yes -o ConnectTimeout=6 -o ConnectionAttempts=1 hsites-ahau "exit 0" 2>$null + if ($LASTEXITCODE -ne 0) { + Write-Error "hsites-ahau недоступен: SOCKS только по цепочке Mac→RU→DO (без прямого Mac→DO). Восстановите SSH до RU или задайте CURSOR_SOCKS_VIA_DO=0." + exit 1 + } + $targetHost = "telegram-socks-via-do" + } + $sshArgs = @( + "-D", $CURSOR_SOCKS_PORT, + "-f", "-N" + ) + if ($CursorViaDo) { + $sshArgs += $stableOpts + } else { + $sshArgs += $ruOpts + } + $sshArgs += $targetHost + & ssh @sshArgs + if ($LASTEXITCODE -eq 0) { + if ($CursorViaDo) { + Write-Host "SOCKS tunnel started: 127.0.0.1:$CURSOR_SOCKS_PORT -> $targetHost (DO exit)" + } else { + Write-Host "SOCKS tunnel started: 127.0.0.1:$CURSOR_SOCKS_PORT -> $targetHost (RU exit, CURSOR_SOCKS_VIA_DO=0)" + } + } else { + Write-Error "Failed to start tunnel (exit code $LASTEXITCODE). Check SSH config (telegram-socks-via-do) or host: $targetHost" + exit $LASTEXITCODE + } +} + +function Stop-Tunnel { + $pids = Get-SshTunnelPids + if ($pids) { + foreach ($pid in $pids) { + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + } + Write-Host "Tunnel stopped (killed SSH process(es): $($pids -join ', '))." + } else { + # Fallback: убить процесс, занимающий порт 10809 + $conn = Get-NetTCPConnection -LocalPort $CURSOR_SOCKS_PORT -State Listen -ErrorAction SilentlyContinue + if ($conn) { + $conn | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue } + Write-Host "Tunnel stopped (killed process on port $CURSOR_SOCKS_PORT)." + } else { + Write-Host "No SOCKS tunnel found on port $CURSOR_SOCKS_PORT." + } + } +} + +function Get-Status { + if (Test-PortListening) { + Write-Host "SOCKS listening on 127.0.0.1:$CURSOR_SOCKS_PORT" + Get-NetTCPConnection -LocalPort $CURSOR_SOCKS_PORT -State Listen -ErrorAction SilentlyContinue | + Format-Table LocalAddress, LocalPort, State, OwningProcess -AutoSize + } else { + Write-Host "No SOCKS listener on port $CURSOR_SOCKS_PORT" + } +} + +switch ($Command) { + "start" { Start-Tunnel } + "stop" { Stop-Tunnel } + "status" { Get-Status } +} diff --git a/docs/cursor/scripts/cursor-socks-tunnel.sh b/docs/apps/cursor/scripts/cursor-socks-tunnel.sh similarity index 100% rename from docs/cursor/scripts/cursor-socks-tunnel.sh rename to docs/apps/cursor/scripts/cursor-socks-tunnel.sh diff --git a/docs/launch/ALLOWED.md b/docs/apps/launch/ALLOWED.md similarity index 100% rename from docs/launch/ALLOWED.md rename to docs/apps/launch/ALLOWED.md diff --git a/docs/launch/DISALLOWED.md b/docs/apps/launch/DISALLOWED.md similarity index 100% rename from docs/launch/DISALLOWED.md rename to docs/apps/launch/DISALLOWED.md diff --git a/docs/launch/disable_login_items_notion_mega.sh b/docs/apps/launch/disable_login_items_notion_mega.sh similarity index 100% rename from docs/launch/disable_login_items_notion_mega.sh rename to docs/apps/launch/disable_login_items_notion_mega.sh diff --git a/docs/launch/disable_system_launch_items.sh b/docs/apps/launch/disable_system_launch_items.sh similarity index 100% rename from docs/launch/disable_system_launch_items.sh rename to docs/apps/launch/disable_system_launch_items.sh diff --git a/docs/connectivity/HOTSPOT_NETWORK_EMERGENCY.md b/docs/connectivity/HOTSPOT_NETWORK_EMERGENCY.md new file mode 100644 index 0000000..3afa3c1 --- /dev/null +++ b/docs/connectivity/HOTSPOT_NETWORK_EMERGENCY.md @@ -0,0 +1,122 @@ +# Emergency: сеть после смены хотспота (macOS) + +Симптом: **отключили Mac от Wi‑Fi / переподключились к другому хотспоту — «интернета нет», помогает только перезагрузка.** + +Документ описывает **проверенную на машине причину** в вашей конфигурации, **ручное восстановление без перезагрузки** и **скрипт** из репозитория. + +--- + +## Причина (точная по конфигурации) + +На macOS в **Системных настройках → Сеть → Wi‑Fi → Подробнее → Прокси** часто включён **автоматический прокси (PAC)** с URL вида **`http://localhost:1089/proxy.pac`** (см. [PROXY_GUIDE.md](./PROXY_GUIDE.md) → раздел про системный PAC). + +Проверка на рабочей машине (2026): **PAC для сервиса Wi‑Fi включён (Enabled: Yes)**, при этом **на порту 1089 нет процесса**, который отдаёт `proxy.pac` — соединение к `127.0.0.1:1089` отклоняется. + +В таком состоянии: + +1. Система и часть приложений **сначала** должны получить PAC с localhost; при недоступном URL поведение **нестабильно** (часть трафика «в никуда», таймауты). +2. При **смене хотспота** меняются шлюз, DNS, часто появляется **captive portal**; страница входа может **не открыться**, пока PAC мешает или DNS закеширован со старой сети. +3. **Перезагрузка** «лечит», потому что заново поднимаются **configd**, **mDNSResponder**, порядок инициализации Wi‑Fi и иногда снова стартует локальный PAC‑сервер (Clash / Surge / свой сервис на 1089). + +Дополнительные усилители сбоя (проверять при `status`): + +- **Системный SOCKS** на `127.0.0.1:1080` (Shadowsocks и т.д.) — если включён, а клиент не слушает порт, трафик через системный прокси тоже падает. +- **VPN / туннели** с политикой «весь трафик» — после смены сети маршруты могут остаться битые до отключения VPN. + +**SOCKS Telegram (1081) и SSH‑туннели сами по себе не отключают Wi‑Fi** — они приложенческие. Проблема класса «всё мёртво до ребута» у вас с высокой вероятностью — **системный PAC/SOCKS на localhost без живого сервиса + кэш DNS / состояние Wi‑Fi**. + +--- + +## Быстро: один скрипт (рекомендуется) + +Из корня репозитория `local_machine`: + +```bash +chmod +x ./docs/connectivity/scripts/macos-hotspot-network-recover.sh +./docs/connectivity/scripts/macos-hotspot-network-recover.sh status +./docs/connectivity/scripts/macos-hotspot-network-recover.sh recover +``` + +`recover` делает: **выключает PAC** (те же сервисы, что и `macos-system-pac.sh`), **выключает системный SOCKS на Wi‑Fi**, по возможности **сбрасывает DNS** и **кратко перезапускает Wi‑Fi** и DHCP. + +Если скрипт пишет, что нет `sudo` без пароля — выполните вручную команды из раздела «Вручную» ниже. + +После появления интернета, если вам снова нужен PAC: + +```bash +./docs/connectivity/scripts/macos-pac-on.sh +``` + +Только **после** того как процесс на **1089** реально запущен и отдаёт `proxy.pac`. + +--- + +## Вручную (без скрипта) + +### 1. Отключить автоматический PAC + +**GUI:** Системные настройки → Сеть → Wi‑Fi → Подробнее → Прокси → снять **«Автоматическая настройка прокси (PAC)»**. + +**CLI** (из репо): + +```bash +./docs/connectivity/scripts/macos-pac-off.sh +./docs/connectivity/scripts/macos-system-pac.sh status +``` + +### 2. Отключить системный SOCKS (если включали для Shadowsocks) + +```bash +networksetup -setsocksfirewallproxystate "Wi-Fi" off +networksetup -getsocksfirewallproxy "Wi-Fi" +``` + +### 3. Сброс DNS + +```bash +sudo dscacheutil -flushcache +sudo killall -HUP mDNSResponder +``` + +### 4. Обновить DHCP и «пнуть» Wi‑Fi + +Узнать интерфейс Wi‑Fi (часто `en0`): + +```bash +networksetup -listallhardwareports | sed -n '/Hardware Port: Wi-Fi/,/Ethernet Address/p' +``` + +Дальше (подставьте свой `Device`, например `en0`): + +```bash +sudo ipconfig set en0 DHCP +networksetup -setairportpower en0 off +sleep 2 +networksetup -setairportpower en0 on +``` + +### 5. Captive portal + +Откройте в браузере **`http://captive.apple.com`** или **`http://neverssl.com`** — должна открыться страница входа в сеть отели/кафе. + +--- + +## Профилактика (архитектурно) + +1. **Не держать PAC включённым**, если сервис на **1089** не запущен: сначала поднять Clash/Surge/свой HTTP‑сервер с PAC, **затем** `macos-pac-on.sh`. +2. Перед подключением к **чужому хотспоту** с обязательной страницей входа — **временно PAC off** (`macos-pac-off.sh`), залогиниться в сеть, затем при необходимости снова **on**. +3. Разнести роли: **глобальный PAC** — только когда стабильно есть локальный конфиг‑сервер; для «чистого» интернета в дороге — **PAC выключен**, прокси только в приложениях (Telegram SOCKS **1081**, Cursor **10809** и т.д.) — см. [TELEGRAM_GUIDE.md](./TELEGRAM_GUIDE.md), [PROXY_EMERGENCY.md](./PROXY_EMERGENCY.md). + +--- + +## Связанные документы + +| Документ | Зачем | +|----------|--------| +| [PROXY_GUIDE.md](./PROXY_GUIDE.md) | Системный PAC, порты, скрипты `macos-pac-*.sh` | +| [PROXY_EMERGENCY.md](./PROXY_EMERGENCY.md) | SSH, SOCKS 1081, прод | +| [README.md](./README.md) | Оглавление `connectivity/` | + +--- + +*Если после `recover` сеть всё ещё мёртвая — проверьте аппаратный тумблер Wi‑Fi, другое SSID, и логи: Консоль → `Wi-Fi` / `configd`.* diff --git a/docs/connectivity/PROXY_EMERGENCY.md b/docs/connectivity/PROXY_EMERGENCY.md new file mode 100644 index 0000000..a9afb25 --- /dev/null +++ b/docs/connectivity/PROXY_EMERGENCY.md @@ -0,0 +1,172 @@ +# Экстренное восстановление: прокси ru.hunab.app и SSH + +Одна страница на случай, когда «всё слетело». Полная схема, безопасность и нюансы `~/.ssh/config`: [PROXY_GUIDE.md](./PROXY_GUIDE.md). + +**Сеть / хотспот (интернет есть только после перезагрузки):** чаще всего системный **PAC на localhost:1089** без живого сервера или системный SOCKS на **1080**. Пошагово: **[HOTSPOT_NETWORK_EMERGENCY.md](./HOTSPOT_NETWORK_EMERGENCY.md)**. + +--- + +## Константы (запомнить / вставить в заметки) + +| Что | Значение | +|-----|----------| +| RU-прокси (SSH) | **149.154.64.19**, пользователь **ahau**, порт **22** | +| Домен прокси | **ru.hunab.app** | +| Прод (DO) | **209.38.32.21**, SSH **2222**, пользователь **hunab** (или **root**) | +| Прод (веб) | **hunab.app** | + +**Ключи (типично):** + +- RU (ahau): `~/.ssh/id_ed25519` +- RU root: `~/.ssh/hsites_new_deploy_key` → хост **`hsites-new`** +- Прод: `~/.ssh/hunab_deploy_key` + +--- + +## Порядок проверки (2 минуты) + +1. **Интернет и DNS:** открыть в браузере `https://ru.hunab.app/proxy-health` и `https://ru.hunab.app/api/health`. +2. **RU SSH:** `ssh -o ConnectTimeout=10 hsites-ahau "echo RU_OK"` + Без алиаса: `ssh -o ConnectTimeout=10 -i ~/.ssh/id_ed25519 ahau@149.154.64.19 "echo RU_OK"` +3. **Прод через RU:** `ssh -o ConnectTimeout=15 hunab-prod "echo PROD_OK"` +4. **Прод напрямую** (если RU мёртв): `ssh -o ConnectTimeout=15 hunab-prod-direct "echo PROD_DIRECT_OK"` + +Если шаг 2 падает — цепочка через прокси недоступна; используйте **прямой** доступ к DO (шаг 4) и SOCKS «через DO» (ниже). + +--- + +## Минимальные блоки `~/.ssh/config` (восстановить вручную) + +**Важно:** `hunab-prod` и `hunab-prod-root` — **разные** блоки `Host` (разный `User`). Не объединять в одну строку `Host`. + +```sshconfig +Host hsites-ahau + HostName 149.154.64.19 + User ahau + IdentityFile ~/.ssh/id_ed25519 + StrictHostKeyChecking accept-new + ServerAliveInterval 30 + ServerAliveCountMax 8 + +Host hunab-prod hunab-prod-via-ahau + HostName 209.38.32.21 + User hunab + Port 2222 + IdentityFile ~/.ssh/hunab_deploy_key + ProxyJump hsites-ahau + StrictHostKeyChecking accept-new + IdentitiesOnly yes + ServerAliveInterval 30 + ServerAliveCountMax 8 + +Host hunab-prod-direct + HostName 209.38.32.21 + User hunab + Port 2222 + IdentityFile ~/.ssh/hunab_deploy_key + StrictHostKeyChecking accept-new + IdentitiesOnly yes + ServerAliveInterval 30 + ServerAliveCountMax 8 +``` + +При необходимости root на проде — отдельный блок `Host hunab-prod-root` / `hunab-prod-root-direct` с `User root` (см. [PROXY_GUIDE.md](./PROXY_GUIDE.md)). + +**Зависший master-сокет:** +`ssh -O exit hunab-prod 2>/dev/null; ssh -O exit hunab-prod-direct 2>/dev/null; rm -f ~/.ssh/cm-hunab-*` + +--- + +## SOCKS для Telegram (порт **1081**) + +**Только RU (если достаточно выхода с 149.154.64.19):** + +```bash +ssh -D 1081 -N ahau@149.154.64.19 +``` + +**RU → прод → интернет** (когда с RU до Telegram не проходит): + +```bash +ssh -D 1081 -N -J hsites-ahau -i ~/.ssh/hunab_deploy_key -p 2222 hunab@209.38.32.21 +``` + +**Без RU, только DO:** + +```bash +ssh -D 1081 -N -i ~/.ssh/hunab_deploy_key -p 2222 hunab@209.38.32.21 +``` + +Проверка (второй терминал): + +```bash +curl -x socks5h://127.0.0.1:1081 -sI https://api.telegram.org | head -3 +``` + +В Telegram: **SOCKS5**, **127.0.0.1**, **1081**. +Скрипты и launchd: [launchd/README.md](./launchd/README.md), [scripts/telegram-socks-tunnel.sh](./scripts/telegram-socks-tunnel.sh). + +--- + +## scp на прод через RU (если нужен файл «прямо сейчас») + +```bash +PROXY_CMD="ssh -i $HOME/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -W %h:%p hsites-ahau" +scp -i ~/.ssh/hunab_deploy_key -o ProxyCommand="$PROXY_CMD" -o ServerAliveInterval=15 \ + /path/to/file hunab@209.38.32.21:/home/hunab/ +``` + +--- + +## Cursor / PING timeout из РФ + +SOCKS для IDE: [docs/cursor/README.md](../cursor/README.md), порт **10809**. Скрипт **`docs/cursor/scripts/cursor-socks-tunnel.sh`** при **`CURSOR_SOCKS_VIA_DO=1`** — только **Mac→RU→DO** (`telegram-socks-via-do`); **`CURSOR_SOCKS_VIA_DO=0`** — выход с RU. Не путать с **1081** (Telegram). + +--- + +## Скрипт выбора маршрута к проду + +Из репозитория: [scripts/ssh-hunab-prod.sh](./scripts/ssh-hunab-prod.sh) +- `HUNAB_PROD_FORCE_VIA=1` — сразу через RU +- `HUNAB_PROD_FORCE_DIRECT=1` — только прямой DO + +--- + +*Краткая версия от PROXY_GUIDE; при расхождении правьте сначала основной гайд.* + +--- + +## Как сейчас на этой машине (проверено: **2026-04-07**) + +**Среда:** macOS, пользователь **`eternal`**, имя хоста **`Eternals.local`**. + +### `~/.ssh/config` + +- В начале файла: **`Include ~/.colima/ssh_config`** (Colima). +- **RU → прод (основной путь):** `hunab-prod` и `hunab-prod-via-ahau` → `209.38.32.21:2222`, пользователь **`hunab`**, ключ **`~/.ssh/hunab_deploy_key`**, **`ProxyJump hsites-ahau`**, плюс **ControlMaster** / **`ControlPath ~/.ssh/cm-hunab-prod-%C`**, **ControlPersist 30m**, keepalive, **UseKeychain**. +- **Прод без RU:** `hunab-prod-direct`, root-вариант **`hunab-prod-root-direct`** — те же порт/ключ, без `ProxyJump`, свой **`ControlPath`** (`cm-hunab-direct-*` / `cm-hunab-direct-root-*`). +- **Root через RU:** `hunab-prod-root`, `hunab-prod-root-via-ahau` — как `hunab-prod`, но **`User root`** и **`cm-hunab-prod-root-%C`**. +- **RU SSH (ahau):** **`hsites-ahau`** → **`149.154.64.19`**, **`~/.ssh/id_ed25519`**, keepalive, **UseKeychain** / **AddKeysToAgent**. +- **RU SSH (root):** **`hsites-new`** → тот же IP, **`~/.ssh/hsites_new_deploy_key`**. +- **Альтернатива через root на RU:** **`prod-ru`**, **`hunab-prod-via-ru`** — **`ProxyJump hsites-new`** (не `hsites-ahau`). В конфиге блок **`Host prod-ru` продублирован дважды** — лишнее можно схлопнуть при правке. +- **Туннель через локальный SOCKS 1090:** **`prod-tunnel`** — `ProxyCommand nc -X 5 -x 127.0.0.1:1090 %h %p`. +- **SOCKS для Telegram:** только **`telegram-socks-via-do`** (Mac→RU→DO). **`telegram-socks-direct-do`** — опционально для ручного SSH к DO без RU, не для автозамены SOCKS. +- Дополнительно в файле: старые хосты **`hsites` / `hsites.hunab`**, **`ahau@ru.hunab`**, боты **`hunab-bot` / `hunab.bots`**, алиас **`tunnel`** на `149.154.64.19`. + +**Ключи на диске (есть):** `~/.ssh/id_ed25519`, `~/.ssh/hunab_deploy_key`, `~/.ssh/hsites_new_deploy_key`. + +### Автозапуск SOCKS **1081** (Telegram) + +- **LaunchAgent:** `~/Library/LaunchAgents/com.hunab.telegram-socks-via-do.plist` — **RunAtLoad**, **KeepAlive**, лог **`/tmp/telegram-socks-tunnel-via-do.log`**. +- **Скрипт:** `~/Library/Application Support/hunab/telegram-socks-launch.sh` — всегда **`ssh -D 1081 -N telegram-socks-via-do`** (Mac→RU→DO). +- Состояние на момент проверки: задание в **launchctl** в статусе **running**; при обрыве цепочки SSH в логе сыплются ошибки до следующего успешного подключения (смотреть хвост лога). + +### Ручной запуск из репозитория + +- Скрипт: **`docs/connectivity/scripts/telegram-socks-tunnel.sh`** — порт **1081**; без `TELEGRAM_SOCKS_VIA_DO` — только RU; с **`TELEGRAM_SOCKS_VIA_DO=1`** — только Mac→RU→DO (`telegram-socks-via-do`), как LaunchAgent. + +### Прочее на машине + +- В **launchctl** также видны **Shadowsocks** (`com.shadowsocks.local`, `com.qiuyuzhou.shadowsocksX-NG.local`) — к hunab-прокси не относятся, но могут влиять на маршрутизацию, если включены. + +*Порт **1081** и SSH в момент автоматической проверки из среды агента могли быть пустыми или с ошибками сети; ориентируйтесь на `lsof -i :1081` и лог выше.* diff --git a/docs/connectivity/PROXY_GUIDE.md b/docs/connectivity/PROXY_GUIDE.md index 4b73cfb..725f3b4 100644 --- a/docs/connectivity/PROXY_GUIDE.md +++ b/docs/connectivity/PROXY_GUIDE.md @@ -6,13 +6,49 @@ ## Оглавление -1. [Доступ и файлы](#-доступ-и-файлы) -2. [Команды управления](#-команды-управления) -3. [Безопасность](#-безопасность) -4. [Запросы-сканеры (serverless и др.)](#-запросы-сканеры-serverless-и-др) -5. [Редирект на прокси](#-автоматический-редирект) -6. [Скорость и тесты](#-проверка-скорости) -7. [Telegram и SOCKS при старте](#-telegram-и-socks-при-старте) +1. [Схема соединений](#схема-соединений) +2. [Доступ и файлы](#-доступ-и-файлы) +3. [Клиентский SSH config и KexAlgorithms](#клиентский-ssh-config-и-kexalgorithms) +4. [Команды управления](#-команды-управления) +5. [Безопасность](#-безопасность) +6. [Запросы-сканеры (serverless и др.)](#-запросы-сканеры-serverless-и-др) +7. [Редирект на прокси](#-автоматический-редирект) +8. [Скорость и тесты](#-проверка-скорости) +9. [Telegram и SOCKS при старте](#-telegram-и-socks-при-старте) +10. [SSH к продакшену (hunab-prod)](#ssh-к-продакшену-hunab-prod) +11. [macOS: системный PAC](#-macos-системный-pac) + +--- + +## Схема соединений + +Текущая логика с **вашего Mac** (или другой клиентской машины с тем же `~/.ssh/config`): + +```mermaid +flowchart TB + subgraph client [Клиент] + M["SSH, scp, Docker CLI,\nTelegram с SOCKS"] + end + RU["RU-прокси 149.154.64.19\nru.hunab.app, SSH 22, user ahau\n(Host hsites-ahau)"] + DO["Прод 209.38.32.21 DigitalOcean\nSSH 2222, hunab / root\nDocker, Nginx, приложения"] + NET["Интернет (выход с DO)"] + + M -->|"Основной: ssh hunab-prod,\nSOCKS telegram-socks-via-do"| RU + RU -->|ProxyJump / туннель| DO + M -->|"Прямой SSH: hunab-prod-direct\n(SOCKS через DO — только via-do)"| DO + DO --> NET +``` + +**Кратко:** + +| Направление | Как задаётся | +|-------------|----------------| +| Mac → RU | `ssh hsites-ahau`, первый прыжок в **ProxyJump** | +| RU → прод DO | Второй hop SSH на **209.38.32.21:2222** | +| Mac → прод без RU | `hunab-prod-direct`; **`telegram-socks-direct-do`** — тот же hop, для ручного SSH/scp, не для авто-SOCKS | +| Telegram / Cursor SOCKS → интернет с IP DO | Только **Mac→RU→DO** (`telegram-socks-via-do`); прямой Mac→DO в скриптах не используется | + +Прод и RU — **разные** машины; ключи разные: к RU обычно **`id_ed25519`** (ahau), к проду на DO — **`hunab_deploy_key`**. --- @@ -36,6 +72,79 @@ - Health: https://ru.hunab.app/proxy-health - API: https://ru.hunab.app/api/health +### Клиентский SSH config и KexAlgorithms + +Файл на той машине, **с которой** вы подключаетесь (обычно Mac): + +- путь **`~/.ssh/config`**; +- на Mac это тот же путь в домашнем каталоге, например **`/Users/<ваш-логин>/.ssh/config`**. + +Сюда добавляются `Host`, `ProxyJump`, ключи и при необходимости опции вроде **`KexAlgorithms`**. + +#### Нельзя смешивать `hunab-prod` и `hunab-prod-root` в одном блоке `Host` + +В одной строке **`Host hunab-prod hunab-prod-root`** может быть только **один** `User`. Если указать `User hunab`, то при вызове `ssh hunab-prod-root` клиент всё равно пойдёт как **hunab** — это **ошибка**. + +**Правильно:** два отдельных блока — один с **`User hunab`**, второй с **`User root`** (и при необходимости разные `ControlPath` / алиасы). + +**Можно** в одном блоке перечислить алиасы с **одним и тем же** пользователем и маршрутом, например: **`Host hunab-prod hunab-prod-via-ahau`** — оба имени ведут на прод как **hunab** через **`ProxyJump hsites-ahau`**. + +#### Имеет ли смысл `KexAlgorithms`? + +На актуальных **macOS** и **OpenSSH** на Ubuntu 24 набор алгоритмов обмена ключей по умолчанию уже **нормальный** (в т.ч. curve25519). Явно задавать **`KexAlgorithms`** стоит **не «на всякий случай»**, а когда есть **конкретная** проблема: сообщение вроде *Unable to negotiate … no matching key exchange method*, старый сервер или нестандартный посредник в сети. + +**Минусы** жёсткого списка: если на сервере **sshd** не поддерживает ни один из перечисленных KEX, подключение **сломается**. Поэтому список должен **пересекаться** с тем, что включено на сервере (`ssh -Q kex` на клиенте, настройки `sshd_config` на сервере). + +Если всё же добавляете — вставляйте строку **в каждый нужный блок отдельно** (`hunab-prod`, `hunab-prod-root`, при цепочке через RU при желании и **`Host hsites-ahau`**), **не объединяя** hunab и root в один блок из‑за разного `User`. + +Пример **опционально** (без `ProxyJump` здесь не показан — в реальном конфиге сохраняйте свои `ProxyJump`, `Port`, `ControlPath` и т.д.): + +``` +Host hunab-prod + HostName 209.38.32.21 + User hunab + Port 2222 + IdentityFile ~/.ssh/hunab_deploy_key + StrictHostKeyChecking accept-new + IdentitiesOnly yes + KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 + +Host hunab-prod-root + HostName 209.38.32.21 + User root + Port 2222 + IdentityFile ~/.ssh/hunab_deploy_key + StrictHostKeyChecking accept-new + IdentitiesOnly yes + KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 +``` + +У **текущего** репозиторийного сценария **`hunab-prod` по умолчанию через `hsites-ahau`** — в блок **`Host hunab-prod`** нужно добавлять и **`ProxyJump hsites-ahau`** (как в [TELEGRAM_GUIDE](./TELEGRAM_GUIDE.md) и полных шаблонах ниже), а не только поля из примера выше. + +### SSH к продакшену (hunab-prod) + +Прод: **209.38.32.21**, SSH **порт 2222**, пользователь **hunab** (или **root**), ключ **`hunab_deploy_key`**. + +| Host в `~/.ssh/config` | Назначение | +|------------------------|------------| +| **hunab-prod** (и алиас **hunab-prod-via-ahau**) | **По умолчанию:** **Mac → hsites-ahau → прод**. Обычная команда `ssh -T hunab-prod 'docker …'` сразу идёт через RU — **не нужен** `export HUNAB_PROD_FORCE_VIA` (эта переменная только для скрипта `ssh-hunab-prod.sh`). | +| **hunab-prod-direct** | Прямое TCP к **209.38.32.21:2222** — когда RU недоступен, а DO доступен. | +| **hunab-prod-root** / **hunab-prod-root-via-ahau** | Root через **hsites-ahau**; **hunab-prod-root-direct** — прямой root. | +| **hunab-prod-via-ru**, **prod-ru** | ProxyJump через **hsites-new** (root); ключ **`hsites_new_deploy_key`**. | + +**Два бэкенда в логах (пример):** + +```bash +ssh -T hunab-prod 'docker logs -f --tail=80 hunab-prod-backend-green 2>&1 | sed -u "s/^/[GREEN] /" & docker logs -f --tail=80 hunab-prod-backend 2>&1 | sed -u "s/^/[BLUE ] /" & wait' +``` + +**Скрипт** `ssh-hunab-prod.sh` — при необходимости сначала пробует **hunab-prod-direct** (8 с), иначе **hunab-prod**: +`HUNAB_PROD_FORCE_VIA=1` — сразу через RU; `HUNAB_PROD_FORCE_DIRECT=1` — только прямой. + +Проверка: `ssh hunab-prod "echo OK"` (через RU), `ssh hunab-prod-direct "echo OK"` (прямой). + +**Стабильность:** **ServerAliveInterval 30**, **ServerAliveCountMax 8**, **ControlMaster** / **ControlPersist 30m** на прод-хостах и **hsites-ahau**. Сброс: `ssh -O exit hunab-prod`, `ssh -O exit hunab-prod-direct`, при необходимости `rm -f ~/.ssh/cm-hunab-*`. + --- ## 🔧 Команды управления @@ -105,9 +214,36 @@ ssh -i ~/.ssh/hunab_deploy_key -o ProxyCommand="$PROXY_CMD" hunab@209.38.32.21 " ## Cursor IDE (PING timeout из РФ) -Если Cursor выдаёт **PING timed out** при работе из России, трафик можно пустить через тот же сервер по SOCKS (SSH -D). Подробно: +Если Cursor выдаёт **PING timed out** при работе из России, трафик можно пустить через SOCKS (SSH `-D`) на **127.0.0.1:10809**. Скрипт **`docs/cursor/scripts/cursor-socks-tunnel.sh`** при **`CURSOR_SOCKS_VIA_DO=1`** поднимает **только Mac→RU→DO** (`telegram-socks-via-do`); без доступа к RU — ошибка (без прямого Mac→DO). **`CURSOR_SOCKS_VIA_DO=0`** — выход только с RU (`hsites-ahau`). -📖 **[docs/cursor/README.md](../docs/cursor/README.md)** — обход PING timeout через SOCKS-туннель (без отключения HTTP/2). +📖 **[docs/cursor/README.md](../cursor/README.md)** — пошагово, проверки и Windows (`cursor-socks-tunnel.ps1`). + +--- + +## 🖥️ macOS: системный PAC + +В **Системных настройках → Сеть** на Mac может быть включён **автоматический прокси** с файлом конфигурации **PAC** (часто `http://localhost:1089/proxy.pac`). Пока PAC включён, браузер и часть приложений ходят в интернет по правилам из PAC; для **локальной админки роутера** (`192.168.x.x`), **чистого Wi‑Fi без прокси** или диагностики удобно **временно выключить** PAC и потом снова включить. + +**Важно:** если PAC **включён**, а по выбранному URL **ничего не слушает** (например, порт **1089** пуст), macOS и приложения ведут себя непредсказуемо; при **смене хотспота** часто кажется, что «сеть мёртвая до перезагрузки». Это не баг Wi‑Fi как такового, а **нерабочий PAC + кэш DNS**. Скрипт восстановления и пошаговый emergency: **[HOTSPOT_NETWORK_EMERGENCY.md](./HOTSPOT_NETWORK_EMERGENCY.md)** (`macos-hotspot-network-recover.sh`). + +**Быстрые скрипты** (из корня репозитория): + +```bash +./docs/connectivity/scripts/macos-pac-on.sh # включить PAC (URL + состояние On) +./docs/connectivity/scripts/macos-pac-off.sh # выключить PAC (URL не стирается, только Off) +./docs/connectivity/scripts/macos-system-pac.sh status # что сейчас в networksetup и scutil +``` + +Полный вариант одной командой: `./docs/connectivity/scripts/macos-system-pac.sh on|off|status`. + +**Переопределения через окружение:** + +- **`MACOS_PAC_URL`** — по умолчанию `http://localhost:1089/proxy.pac`. +- **`MACOS_PAC_SERVICES`** — список имён сетевых сервисов **через символ `|`** (как в `networksetup -listallnetworkservices`). По умолчанию: `Wi-Fi|Thunderbolt Bridge 2`. + +Пример только для Wi‑Fi: `MACOS_PAC_SERVICES=Wi-Fi ./docs/connectivity/scripts/macos-pac-off.sh` + +Убедитесь, что процесс, который отдаёт PAC по выбранному URL (порт **1089**), запущен, иначе после `on` приложения могут не открывать сайты. --- @@ -144,6 +280,68 @@ launchctl load ~/Library/LaunchAgents/com.hunab.telegram-socks.plist В Telegram (или другом клиенте) укажите прокси: **SOCKS5**, **127.0.0.1**, порт **1081**. +### Выход через DigitalOcean (прокси → DO → интернет) + +Если с **149.154.64.19** до инфраструктуры Telegram TLS не проходит (типично DPI), поднимите SOCKS так, чтобы **трафик выходил в интернет с прод-сервера DO** (209.38.32.21), а до RU-прокси шёл только SSH. Цепочка: **Mac → hsites-ahau (149.154.64.19) → hunab@209.38.32.21:2222**. + +**Вручную:** + +```bash +ssh -D 1081 -N -J hsites-ahau -i ~/.ssh/hunab_deploy_key -p 2222 hunab@209.38.32.21 +``` + +**Скрипт из репо** (те же параметры по умолчанию): + +```bash +export TELEGRAM_SOCKS_VIA_DO=1 +./docs/connectivity/scripts/telegram-socks-tunnel.sh start +``` + +Переменные при необходимости: `TELEGRAM_SOCKS_JUMP_HOST`, `TELEGRAM_SOCKS_FINAL_HOST`, `TELEGRAM_SOCKS_FINAL_PORT` (по умолчанию 2222), `TELEGRAM_SOCKS_FINAL_IDENTITY`. + +**`~/.ssh/config`** — удобно для LaunchAgent без длинной строки `ssh`: + +``` +Host telegram-socks-via-do + HostName 209.38.32.21 + User hunab + Port 2222 + IdentityFile ~/.ssh/hunab_deploy_key + ProxyJump hsites-ahau + StrictHostKeyChecking accept-new + ServerAliveInterval 60 + ServerAliveCountMax 4 + ConnectTimeout 60 + TCPKeepAlive yes + IPQoS throughput + UseKeychain yes + AddKeysToAgent yes +``` + +**Прямой SSH к DO** (без RU в цепочке) — для админки вроде `hunab-prod-direct`, **не** для автоматического SOCKS «в обход RU»: + +``` +Host telegram-socks-direct-do + HostName 209.38.32.21 + User hunab + Port 2222 + IdentityFile ~/.ssh/hunab_deploy_key + StrictHostKeyChecking accept-new + ServerAliveInterval 60 + ServerAliveCountMax 4 + ConnectTimeout 60 + TCPKeepAlive yes + IPQoS throughput + UseKeychain yes + AddKeysToAgent yes +``` + +Скрипты **`TELEGRAM_SOCKS_VIA_DO=1`**, **`cursor-socks-tunnel.sh`** при выходе через DO и LaunchAgent **не** переключаются на этот host — только **Mac→RU→DO** через `telegram-socks-via-do`. + +Проверка SOCKS: `ssh -D 1081 -N telegram-socks-via-do` (в другом терминале: `curl -x socks5h://127.0.0.1:1081 -sI https://api.telegram.org | head -3`). + +**Автозапуск (macOS):** `./docs/connectivity/launchd/install-via-do.sh` копирует `telegram-socks-launch.sh`, который всегда запускает **`telegram-socks-via-do`** (Mac→RU→DO). Лог: `/tmp/telegram-socks-tunnel-via-do.log`. Старый RU-only агент: `./docs/connectivity/launchd/uninstall.sh`. Вернуть только RU: `./docs/connectivity/launchd/uninstall-via-do.sh` и `./docs/connectivity/launchd/install.sh`. + --- ## SSL diff --git a/docs/connectivity/README.md b/docs/connectivity/README.md index c3dd482..796acf1 100644 --- a/docs/connectivity/README.md +++ b/docs/connectivity/README.md @@ -9,9 +9,12 @@ | Файл / папка | Назначение | |--------------|------------| | **[PROXY_GUIDE.md](./PROXY_GUIDE.md)** | Основное руководство: SSH, Nginx, безопасность, scp через туннель, Cursor, скорость | -| **[TELEGRAM_GUIDE.md](./TELEGRAM_GUIDE.md)** | Гайд по Telegram: быстрые команды и порядок настройки с нуля на новой машине | +| **[TELEGRAM_GUIDE.md](./TELEGRAM_GUIDE.md)** | Telegram SOCKS: быстрые команды → регламент → развёртывание с нуля по сценариям | | **[PROXY_CHANGELOG.md](./PROXY_CHANGELOG.md)** | История изменений (безопасность, скрипты, документация) | | **[BUILD_AAB_FROM_RUSSIA_GUIDE.md](./BUILD_AAB_FROM_RUSSIA_GUIDE.md)** | Сборка AAB/APK из России: Maven proxy на проде + SSH-туннель | +| **[WINDOWS_CURSOR_TUNNEL.md](./WINDOWS_CURSOR_TUNNEL.md)** | Cursor из РФ на **Windows**: туннель через прокси, скрипт .ps1, автозапуск | +| **[WINDOWS_SSH_KEY_INSTALL.md](./WINDOWS_SSH_KEY_INSTALL.md)** | Установка SSH-ключа на Windows (один файл `setup-ssh-key-for-proxy.ps1`, только для туннеля) | +| **[HOTSPOT_NETWORK_EMERGENCY.md](./HOTSPOT_NETWORK_EMERGENCY.md)** | Сеть после смены хотспота: причина (PAC localhost), ручной сброс без ребута, `macos-hotspot-network-recover.sh` | | **[scripts/](./scripts/)** | Скрипты для туннелей и локальной работы | | **[launchd/](./launchd/)** | LaunchAgent для macOS: автозапуск SOCKS-туннеля при входе | @@ -20,8 +23,8 @@ ## Быстрые ссылки по задачам - **Доступ по SSH к прокси** — [PROXY_GUIDE.md → Доступ и файлы](./PROXY_GUIDE.md#-доступ-и-файлы) -- **Telegram через SOCKS** — [TELEGRAM_GUIDE.md](./TELEGRAM_GUIDE.md) (быстрые команды + гайд для новой машины), [launchd/README.md](./launchd/README.md) -- **Cursor (PING timeout из РФ)** — [PROXY_GUIDE.md → Cursor IDE](./PROXY_GUIDE.md#cursor-ide-ping-timeout-из-рф), в репо: `docs/cursor/README.md`, скрипт `docs/cursor/scripts/cursor-socks-tunnel.sh` +- **Telegram через SOCKS** — [TELEGRAM_GUIDE.md](./TELEGRAM_GUIDE.md) (команды, регламент, сценарии), [launchd/README.md](./launchd/README.md) +- **Cursor (PING timeout из РФ)** — [PROXY_GUIDE.md → Cursor IDE](./PROXY_GUIDE.md#cursor-ide-ping-timeout-из-рф), в репо: `docs/cursor/README.md`, скрипт `docs/cursor/scripts/cursor-socks-tunnel.sh` (macOS/Linux), **[Windows: WINDOWS_CURSOR_TUNNEL.md](./WINDOWS_CURSOR_TUNNEL.md)** и `docs/cursor/scripts/cursor-socks-tunnel.ps1` - **Загрузка файлов на прод через прокси** — [PROXY_GUIDE.md → Загрузка файлов на прод (scp)](./PROXY_GUIDE.md#-загрузка-файлов-на-прод-через-туннель-scp) - **Безопасность прокси (nginx, fail2ban)** — [PROXY_GUIDE.md → Безопасность](./PROXY_GUIDE.md#-безопасность), SECURITY.md (если есть в папке) - **Сборка AAB из РФ** — [BUILD_AAB_FROM_RUSSIA_GUIDE.md](./BUILD_AAB_FROM_RUSSIA_GUIDE.md) @@ -32,7 +35,9 @@ | Скрипт | Описание | |--------|----------| -| **telegram-socks-tunnel.sh** | Поднять/остановить/проверить SOCKS5 на 127.0.0.1:1081 для Telegram (и др.). Хост: ahau@149.154.64.19. Переменные: `TELEGRAM_SOCKS_PORT`, `TELEGRAM_TUNNEL_HOST`. | +| **telegram-socks-tunnel.sh** | SOCKS5 на 1081 для Telegram. `TELEGRAM_SOCKS_VIA_DO=1` — только Mac→RU→DO (`telegram-socks-via-do`); без RU — ошибка. См. PROXY_GUIDE. | +| **macos-hotspot-network-recover.sh** | После смены хотспота: `status` / `recover` (PAC off, SOCKS off на Wi‑Fi, DNS, DHCP, перезапуск радио). См. HOTSPOT_NETWORK_EMERGENCY.md. | +| **ssh-hunab-prod.sh** | Опционально: сначала `hunab-prod-direct`, иначе `hunab-prod` (в конфиге по умолчанию уже через RU). См. PROXY_GUIDE. | --- @@ -40,9 +45,10 @@ Автозапуск SOCKS-туннеля для Telegram при входе в macOS: -- **com.hunab.telegram-socks.plist** — LaunchAgent (RunAtLoad, KeepAlive, порт 1081). -- **install.sh** — один раз скопировать plist и загрузить; дальше туннель поднимается при каждом входе. -- **uninstall.sh** — снять автозапуск. +- **com.hunab.telegram-socks.plist** — LaunchAgent (RunAtLoad, KeepAlive, порт 1081, выход на RU-прокси). +- **com.hunab.telegram-socks-via-do.plist** — только Mac→RU→DO (`telegram-socks-launch.sh` + `telegram-socks-via-do`). +- **install.sh** / **uninstall.sh** — RU-only агент. +- **install-via-do.sh** / **uninstall-via-do.sh** — агент с выходом через DigitalOcean. - **[launchd/README.md](./launchd/README.md)** — установка, удаление, требования (ключ в keychain), возможные конфликты портов. --- @@ -52,4 +58,4 @@ | Порт | Назначение | |------|------------| | **1081** | Telegram (и др. приложения) — SOCKS через `scripts/telegram-socks-tunnel.sh` или LaunchAgent | -| **10809** | Cursor IDE — SOCKS через `docs/cursor/scripts/cursor-socks-tunnel.sh` | +| **10809** | Cursor IDE — SOCKS через `docs/cursor/scripts/cursor-socks-tunnel.sh` / `cursor-socks-tunnel.ps1`; по умолчанию только **Mac→RU→DO** (`CURSOR_SOCKS_VIA_DO=1`, `telegram-socks-via-do`) | diff --git a/docs/connectivity/TELEGRAM_GUIDE.md b/docs/connectivity/TELEGRAM_GUIDE.md index 0bdb1c8..a1de0fd 100644 --- a/docs/connectivity/TELEGRAM_GUIDE.md +++ b/docs/connectivity/TELEGRAM_GUIDE.md @@ -1,146 +1,213 @@ -# Telegram через SOCKS-туннель (ru.hunab.app) +# Telegram через SOCKS (RU-прокси → DigitalOcean) -Трафик Telegram идёт через прокси 149.154.64.19 по SOCKS5 на **127.0.0.1:1081**. Гайд для быстрых команд и для настройки с нуля на новой машине. +На Mac поднимается **SOCKS5** на **127.0.0.1:1081**. Для выхода в интернет с **IP DO** используется **только** цепочка **Mac → 149.154.64.19 → DO (209.38.32.21)** (`telegram-socks-via-do`). Без доступа к RU автоматический SOCKS «напрямую в DO» **не** поднимается. Подробности: [PROXY_GUIDE.md](./PROXY_GUIDE.md). + +Все пути ниже — от корня репозитория **local_machine**. --- -## ⚡ Быстрые команды +## Быстрые команды + +### Туннель вручную (основной: только Mac→RU→DO) ```bash -# Поднять туннель вручную +export TELEGRAM_SOCKS_VIA_DO=1 ./docs/connectivity/scripts/telegram-socks-tunnel.sh start - -# Проверить, слушается ли порт -lsof -i :1081 - -# Статус туннеля -./docs/connectivity/scripts/telegram-socks-tunnel.sh status - -# Остановить туннель ./docs/connectivity/scripts/telegram-socks-tunnel.sh stop - -# Включить автозапуск при входе (один раз) -./docs/connectivity/launchd/install.sh - -# Отключить автозапуск -./docs/connectivity/launchd/uninstall.sh - -# Проверить доступ к Telegram API через прокси -curl -x socks5h://127.0.0.1:1081 -sI https://api.telegram.org | head -3 -``` - -**В Telegram:** Настройки → Данные и память → Прокси → добавить **SOCKS5**, **127.0.0.1**, порт **1081**. - ---- - -## Полный гайд: новая машина (порядок действий) - -### 1. Требования - -- macOS (для LaunchAgent; на Linux можно использовать systemd или скрипт в автозагрузке). -- Доступ по SSH к **149.154.64.19** под пользователем **ahau** (ключ в `~/.ssh/` или алиас в `~/.ssh/config`, например `hsites-ahau`). -- Репозиторий **local_machine** склонирован (нужны скрипты из `docs/connectivity/`). - -### 2. Проверка SSH - -Убедись, что вход без пароля работает: - -```bash -ssh -o BatchMode=yes -o ConnectTimeout=10 ahau@149.154.64.19 "echo OK" -``` - -Если просит пароль или ключ не подхватывается — настрой `~/.ssh/config` или укажи ключ явно: `ssh -i ~/.ssh/id_ed25519 ahau@149.154.64.19`. - -### 3. Поднять туннель вручную (первый раз) - -Из корня репо: - -```bash -./docs/connectivity/scripts/telegram-socks-tunnel.sh start -``` - -Ожидаемый вывод: `SOCKS tunnel started: 127.0.0.1:1081 -> ahau@149.154.64.19`. - -Проверка порта: - -```bash -lsof -i :1081 -``` - -Должен быть процесс `ssh` в состоянии LISTEN. - -### 4. Настройка прокси в Telegram - -1. Открой **Telegram** (Desktop или мобильное приложение). -2. **Настройки** (Settings) → **Данные и память** (Data and Storage) → **Прокси** (Proxy). -3. **Добавить прокси** (Add Proxy): - - Тип: **SOCKS5** - - Сервер: **127.0.0.1** - - Порт: **1081** - - Логин/пароль — не нужны. -4. Сохрани и **включи** прокси. - -После этого Telegram должен подключаться через туннель. Если не подключается — см. раздел [Проверка и диагностика](#проверка-и-диагностика) ниже. - -### 5. Автозапуск туннеля при входе в систему (macOS) - -Чтобы не запускать туннель вручную после каждой перезагрузки: - -```bash -./docs/connectivity/launchd/install.sh -``` - -Скрипт копирует plist в `~/Library/LaunchAgents/` и загружает его. Дальше при каждом входе в систему туннель будет подниматься сам (и перезапускаться при обрыве). - -**Важно:** при старте системы SSH должен иметь доступ к ключу. Добавь ключ в связку с keychain (один раз): - -```bash -ssh-add --apple-use-keychain -# или, если ключ не дефолтный: -ssh-add --apple-use-keychain ~/.ssh/id_ed25519 -``` - -Проверка после перезагрузки: - -```bash -lsof -i :1081 ./docs/connectivity/scripts/telegram-socks-tunnel.sh status ``` -### 6. Проверка и диагностика - -**Туннель слушает порт?** +### Проверка порта и API Telegram ```bash lsof -i :1081 -sTCP:LISTEN +curl -x socks5h://127.0.0.1:1081 -sI --connect-timeout 10 https://api.telegram.org | head -5 ``` -**Доступ к API Telegram через SOCKS?** +### Автозапуск после перезагрузки (via-DO + авто-fallback) + +Скрипт **`~/Library/Application Support/hunab/telegram-socks-launch.sh`** ставится из репо командой ниже. ```bash -curl -x socks5h://127.0.0.1:1081 -sI https://api.telegram.org | head -5 +./docs/connectivity/launchd/install-via-do.sh +./docs/connectivity/launchd/uninstall-via-do.sh +launchctl list | grep telegram-socks +launchctl kickstart -k "gui/$(id -u)/com.hunab.telegram-socks-via-do" ``` -Успех: в ответе есть HTTP-заголовки (например 302 или 200). Таймаут или отказ — туннель не работает или порт занят. +Лог агента: `tail -50 /tmp/telegram-socks-tunnel-via-do.log` -**Лог LaunchAgent (если туннель при старте не поднимается):** +### Ключи в связку (один раз после смены ключей / сбоя SSH) ```bash -tail -50 /tmp/telegram-socks-tunnel.log +ssh-add --apple-use-keychain ~/.ssh/id_ed25519 +ssh-add --apple-use-keychain ~/.ssh/hunab_deploy_key ``` -Типичные причины: ключ не в keychain, ошибка «Permission denied», сеть ещё недоступна при RunAtLoad. +### Прокси в Telegram (обязательно) -### 7. Конфликты портов +| Поле | Значение | +|------|----------| +| Тип | **SOCKS5** | +| Сервер | **127.0.0.1** | +| Порт | **1081** | +| Логин / пароль | *пусто* | -| Порт | Назначение | -|-------|------------| -| 1081 | Telegram SOCKS (этот гайд) | -| 10809 | Cursor IDE SOCKS (другой туннель) | +**Настройки → Данные и память → Прокси** — добавить, сохранить, **включить**. -Если на 1081 уже висит другой прокси (например Shadowsocks), смени порт в plist и в настройках Telegram или отключи автозапуск: `./docs/connectivity/launchd/uninstall.sh`. +--- -### 8. Ссылки +## Регламент -- [PROXY_GUIDE.md](./PROXY_GUIDE.md) — общее руководство по прокси ru.hunab.app (SSH, Nginx, Cursor, scp). -- [launchd/README.md](./launchd/README.md) — детали LaunchAgent (удаление, ThrottleInterval, логи). +Документ задаёт **обязательные** правила и **порядок действий**. Отклонения — только осознанно (обрывы, DPI, конфликт портов). + +### 1. Назначение + +- Выход в интернет через **DO**; предпочтительно через **`hsites-ahau`**, при его недоступности — **прямой** SSH к DO (автоматически). +- Исключить работу Telegram **мимо** туннеля и конкуренцию на порту **1081**. + +### 2. Обязательные нормы (всегда) + +| № | Требование | +|---|------------| +| N1 | **Основной** сценарий: **`TELEGRAM_SOCKS_VIA_DO=1`** и/или LaunchAgent **`com.hunab.telegram-socks-via-do`**, не «только RU без DO», если с RU TLS до Telegram нестабилен. | +| N2 | В Telegram — **SOCKS5**, **127.0.0.1**, **1081**, без логина/пароля (см. таблицу в «Быстрые команды»). | +| N3 | На **1081** только наш `ssh -D`. Другие прокси (Shadowsocks, v2ray и т.п.) на этот порт **не ставить**. | +| N4 | В `~/.ssh/config` есть **`Host telegram-socks-via-do`** (с jump **`hsites-ahau`**) и **`hsites-ahau`** — [PROXY_GUIDE.md](./PROXY_GUIDE.md). | +| N5 | Ключи **id_ed25519** и **hunab_deploy_key** доступны SSH после входа: **связка ключей** (`ssh-add --apple-use-keychain`). | +| N6 | Для работы после перезагрузки: plist **`~/Library/LaunchAgents/com.hunab.telegram-socks-via-do.plist`** загружен. Ручной `start` без агента перезагрузку **не переживает**. | +| N7 | Не держать **два** LaunchAgent на **1081**: `com.hunab.telegram-socks` (только RU) и `com.hunab.telegram-socks-via-do` — **ровно один** вариант. | + +### 3. Первоначальная настройка (один раз) + +Порядок фиксирован (детальнее и варианты — раздел «Развёртывание с нуля»). + +1. Проверка SSH (см. быстрые проверки в сценарии 1). +2. `~/.ssh/config`: **`Host telegram-socks-via-do`** по [PROXY_GUIDE.md](./PROXY_GUIDE.md). +3. `ssh-add --apple-use-keychain` для обоих ключей. +4. Снять старый агент only-RU при необходимости: `./docs/connectivity/launchd/uninstall.sh`. +5. `./docs/connectivity/launchd/install-via-do.sh`. +6. Настроить прокси в Telegram (таблица выше). +7. Контрольные проверки (п. 6). + +### 4. Повседневная эксплуатация + +| Ситуация | Действие | +|----------|----------| +| Обычный день | Ничего не запускать: туннель поднимает LaunchAgent после входа. | +| Ручной перезапуск туннеля | `stop` → `export TELEGRAM_SOCKS_VIA_DO=1` → `start` (см. быстрые команды). | +| После смены plist / обновления репо | `install-via-do.sh` или `kickstart` (см. быстрые команды). | + +### 5. Норматив: клиент Telegram + +Соответствует таблице в разделе **«Быстрые команды»**. Иначе нарушение N2. + +### 6. Контрольные проверки + +```bash +test -f "$HOME/Library/LaunchAgents/com.hunab.telegram-socks-via-do.plist" && echo "plist OK" || echo "FAIL: нет plist" +launchctl list | grep telegram-socks +lsof -i :1081 -sTCP:LISTEN +curl -x socks5h://127.0.0.1:1081 -sI --connect-timeout 10 https://api.telegram.org | head -5 +``` + +Лог: `/tmp/telegram-socks-tunnel-via-do.log`. + +### 7. Запреты и конфликты + +- Не занимать **1081** сторонним ПО (N3). +- Не два LaunchAgent на 1081 (N7). +- Не отключать прокси в Telegram и ожидать тот же доступ «через туннель» (N2). + +| Порт | Назначение | +|------|------------| +| **1081** | Telegram SOCKS | +| **10809** | Cursor IDE SOCKS | + +### 8. Действия при сбое + +1. П. 6 (контрольные проверки). +2. `tail -80 /tmp/telegram-socks-tunnel-via-do.log`. +3. **`Too many open files`**: обновить plist из репозитория, снова `install-via-do.sh`. +4. **`channel … open failed`**: часто сеть или следствие п.3; перезапуск туннеля после правок. +5. После правок `~/.ssh/config`: `stop` → `TELEGRAM_SOCKS_VIA_DO=1` → `start` или `kickstart`. +6. Прокси в Telegram по-прежнему включён (п. 5). +7. **`Connection timed out during banner exchange`** к RU: без доступа к **149.154.64.19** цепочка Mac→RU→DO **невозможна** — восстановите доступ к RU или временно используйте только-RU агент (`install.sh`) / другую сеть; автоматического SOCKS «напрямую в DO» нет. + +### 9. Снятие автозапуска и откат + +- Via-DO: `./docs/connectivity/launchd/uninstall-via-do.sh`. +- Переход на только-RU: `./docs/connectivity/launchd/install.sh` — см. **сценарий 2** ниже. + +--- + +## Развёртывание с нуля (по сценариям) + +Общие требования: **macOS**; репозиторий **local_machine**; SSH без пароля к **209.38.32.21:2222** (hunab, `hunab_deploy_key`) и к **149.154.64.19** (ahau) — для SOCKS через DO нужен **оба** hop (Mac→RU→DO). + +### Сценарий 1: основной — macOS, автозапуск (только Mac→RU→DO) + +Рекомендуемый вариант (регламент N1, N6). + +1. Проверка SSH (для via-DO нужны **оба**): + + ```bash + ssh -o BatchMode=yes -o ConnectTimeout=10 ahau@149.154.64.19 "echo OK" + ssh -o BatchMode=yes -o ConnectTimeout=15 -i ~/.ssh/hunab_deploy_key -p 2222 hunab@209.38.32.21 "echo OK" + ``` + +2. Добавить в `~/.ssh/config` блок **`Host telegram-socks-via-do`** — [PROXY_GUIDE.md](./PROXY_GUIDE.md). + +3. `ssh-add --apple-use-keychain` для `id_ed25519` и `hunab_deploy_key`. + +4. Если ранее стоял агент только-RU: `./docs/connectivity/launchd/uninstall.sh`. + +5. `./docs/connectivity/launchd/install-via-do.sh`. + +6. В Telegram — прокси по таблице в **«Быстрые команды»**. + +7. Проверки из п. 6 регламента. + +### Сценарий 2: только RU-прокси (без DO) + +Если прямой выход с **149.154.64.19** до Telegram устойчив или для диагностики. **Не** ставить одновременно с via-DO на 1081 (N7). + +**Ручной туннель:** + +```bash +./docs/connectivity/scripts/telegram-socks-tunnel.sh start +``` + +**Автозапуск:** `./docs/connectivity/launchd/install.sh` +**Снятие:** `./docs/connectivity/launchd/uninstall.sh` + +Перед переходом на сценарий 1 — `uninstall.sh`, затем сценарий 1 с шага 5. + +### Сценарий 3: без LaunchAgent, только ручной туннель (via-DO) + +Для отладки; после перезагрузки нужно снова поднимать вручную. + +```bash +export TELEGRAM_SOCKS_VIA_DO=1 +./docs/connectivity/scripts/telegram-socks-tunnel.sh start +``` + +Убедиться, что `~/.ssh/config` и ключи настроены как в сценарии 1 (шаги 1–3). Прокси в Telegram — как в таблице. + +### Сценарий 4: смена с via-DO на only-RU или обратно + +- **На only-RU:** `./docs/connectivity/launchd/uninstall-via-do.sh`, затем `./docs/connectivity/launchd/install.sh`. +- **Обратно на via-DO:** `./docs/connectivity/launchd/uninstall.sh`, затем `./docs/connectivity/launchd/install-via-do.sh`. + +Каждый раз проверять: один plist на 1081, `launchctl list | grep telegram-socks`. + +### Сценарий 5: Linux (без LaunchAgent) + +Репозиторий и скрипт `docs/connectivity/scripts/telegram-socks-tunnel.sh`; автозапуск — **systemd**, **cron @reboot**, или ручной `start` как в сценарии 3. Plist macOS на Linux не используется. + +--- + +## Ссылки + +- [PROXY_GUIDE.md](./PROXY_GUIDE.md) — прокси ru.hunab.app, `Host telegram-socks-via-do`, scp. +- [launchd/README.md](./launchd/README.md) — LaunchAgent, логи, порты. diff --git a/docs/connectivity/WINDOWS_CURSOR_TUNNEL.md b/docs/connectivity/WINDOWS_CURSOR_TUNNEL.md new file mode 100644 index 0000000..ae5d266 --- /dev/null +++ b/docs/connectivity/WINDOWS_CURSOR_TUNNEL.md @@ -0,0 +1,269 @@ +# Cursor из РФ на Windows — туннель через прокси ru.hunab.app + +Пошаговая инструкция для тех, кто впервые настраивает Cursor за пределами «обычного» интернета. Все действия выполняются на компьютере Windows. + +--- + +## Зачем это нужно + +В России Cursor иногда выдаёт ошибку **PING timed out**: связь с серверами Cursor обрывается или не устанавливается. Чтобы обойти это, трафик Cursor пускают через **прокси-сервер** ru.hunab.app (149.154.64.19). С вашего Windows до этого сервера создаётся зашифрованный **туннель** по SSH. Cursor тогда подключается к интернету не напрямую, а через этот туннель — и ошибка пропадает. + +**Что вы сделаете по шагам:** +1) проверите/установите программу для туннеля (OpenSSH) и ключ доступа к прокси; +2) запустите туннель (одна команда или скрипт); +3) настроите Cursor так, чтобы он использовал этот туннель; +4) при желании настроите автозапуск туннеля при входе в Windows. + +--- + +## Небольшой словарик + +| Термин | По-простому | +|--------|-------------| +| **Туннель (SOCKS)** | Канал «ваш компьютер → прокси-сервер». Весь трафик Cursor к интернету идёт через этот канал. | +| **Порт 10809** | Номер «входа» на вашем же компьютере. Cursor подключается к `127.0.0.1:10809` — то есть к себе же, но к программе туннеля, которая пересылает данные на прокси. | +| **PowerShell** | Программа в Windows для ввода текстовых команд. Открыть: поиск в Пуске «PowerShell» или Win+X → «Windows PowerShell». | +| **%USERPROFILE%** | Папка вашего пользователя Windows, например `C:\Users\Иван`. В PowerShell то же: `$env:USERPROFILE`. | +| **Корень репо** | Папка, в которой лежит проект local_machine (внутри есть папки `docs`, `scripts` и т.д.). | + +--- + +## Что нужно до начала + +- **SSH-ключ** уже установлен на Windows и проверен (подключение к ahau@149.154.64.19 без пароля). Если ещё нет — сначала сделайте по инструкции [WINDOWS_SSH_KEY_INSTALL.md](./WINDOWS_SSH_KEY_INSTALL.md). +- **Cursor** установлен на Windows. +- Настроено **подключение к интернету** (туннель будет идти через него до прокси). + +--- + +## Содержание + +1. [Шаг 1: Проверить OpenSSH и ключ](#-шаг-1-проверить-openssh-и-ключ) +2. [Шаг 2: Запустить туннель](#-шаг-2-запустить-туннель) +3. [Шаг 3: Настроить Cursor](#-шаг-3-настроить-cursor) +4. [Порядок работы каждый раз](#-порядок-работы-каждый-раз) +5. [Автозапуск туннеля при входе в Windows](#-автозапуск-туннеля-при-входе-в-windows) +6. [Если что-то не работает](#-если-что-то-не-работает) + +--- + +## Шаг 1: Проверить OpenSSH и ключ + +### 1.1 Открыть PowerShell + +- Нажмите клавишу **Win** (или кнопку «Пуск») и начните вводить **PowerShell**. +- Выберите **Windows PowerShell** (лучше не «от имени администратора», если не требуется). + +Откроется синее окно с текстом вроде `PS C:\Users\ВашеИмя>`. В нём вы будете вводить команды. + +### 1.2 Проверить, установлен ли OpenSSH + +Введите команду и нажмите Enter: + +```powershell +ssh -V +``` + +**Хороший результат:** появляется строка с версией, например `OpenSSH_for_Windows_8.1` или похоже. + +**Если пишет «не распознано» или «command not found»:** +Нужно установить клиент OpenSSH: + +1. Откройте **Параметры Windows** (Win+I). +2. Зайдите в **Приложения** → **Дополнительные компоненты** (или «Опциональные компоненты»). +3. Нажмите **Добавить компонент**. +4. Найдите в списке **OpenSSH-клиент** и нажмите **Установить**. + +После установки закройте и снова откройте PowerShell и снова выполните `ssh -V`. + +### 1.3 Убедиться, что ключ на месте и работает + +Ключ должен лежать в папке вашего пользователя, в подпапке `.ssh`, с именем `id_ed25519`. То есть полный путь: + +`C:\Users\**ВашеИмя**\.ssh\id_ed25519` + +Проверка одной командой (скопируйте целиком и вставьте в PowerShell): + +```powershell +ssh -i $env:USERPROFILE\.ssh\id_ed25519 ahau@149.154.64.19 "echo OK" +``` + +При первом запуске может спросить про доверие хосту — введите **yes**. +**Ожидаемый результат:** в следующей строке выводится **OK**, пароль не запрашивается. + +Если получили **OK** — переходите к [Шагу 2](#-шаг-2-запустить-туннель). +Если ошибка (например, «Permission denied», «No such file») — вернитесь к [WINDOWS_SSH_KEY_INSTALL.md](./WINDOWS_SSH_KEY_INSTALL.md) и доведите установку ключа до конца. + +--- + +## Шаг 2: Запустить туннель + +Туннель — это программа, которая держит соединение с прокси и «слушает» на вашем компьютере порт 10809. Cursor потом будет подключаться к этому порту. Окно, в котором запущен туннель, **лучше не закрывать** (можно свернуть). + +### Вариант A: Запуск скриптом (если у вас есть папка с репо local_machine) + +1. Откройте PowerShell. +2. Перейдите в **корень репо** (папка, где лежат `docs`, `scripts`). Пример: + ```powershell + cd C:\Users\ВашеИмя\code\local_machine + ``` + Подставьте свой путь к папке проекта. +3. Разрешить выполнение скриптов (достаточно один раз): + ```powershell + Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned + ``` + На вопрос ответьте **Y**. +4. Запустите туннель: + ```powershell + .\docs\cursor\scripts\cursor-socks-tunnel.ps1 start + ``` +5. **Ожидаемый вывод:** `... -> telegram-socks-via-do (DO exit)` — только цепочка **Mac→RU→DO**; если **hsites-ahau** недоступен, скрипт завершится с ошибкой (прямой Mac→DO не поднимается). Нужен `Host telegram-socks-via-do` в `~/.ssh/config`, см. [PROXY_GUIDE.md](./PROXY_GUIDE.md). Только выход с RU: `$env:CURSOR_SOCKS_VIA_DO="0"; .\docs\cursor\scripts\cursor-socks-tunnel.ps1 start`. Окно можно свернуть. + +Проверить, что туннель работает: в **другом** окне PowerShell выполните: + +```powershell +.\docs\cursor\scripts\cursor-socks-tunnel.ps1 status +``` + +Должно написать, что SOCKS слушает на порту 10809. + +Остановить туннель (когда не нужен): + +```powershell +.\docs\cursor\scripts\cursor-socks-tunnel.ps1 stop +``` + +### Вариант B: Запуск одной командой (без скрипта) + +1. Откройте PowerShell. +2. Выход через **DO** (как скрипт по умолчанию): + +```powershell +ssh -D 10809 -o ServerAliveInterval=60 -o ServerAliveCountMax=4 telegram-socks-via-do +``` + +Только **RU** (без DO в цепочке выхода): + +```powershell +ssh -D 10809 -o ServerAliveInterval=30 -o ServerAliveCountMax=6 ahau@149.154.64.19 +``` + +3. При первом запуске может спросить про доверие хосту — введите **yes**. Пароль спрашивать не должно (работает ключ). +4. **Окно не закрывайте** — пока оно открыто, туннель работает. В заголовке окна может ничего не меняться — это нормально. Можно свернуть окно. + +Проверка: откройте **второе** окно PowerShell и выполните: + +```powershell +netstat -an | findstr "10809" +``` + +Должна появиться строка, где есть `127.0.0.1` и `10809` и состояние **LISTENING**. + +Чтобы остановить туннель — закройте то окно PowerShell, в котором была запущена команда `ssh -D 10809 ...`. + +--- + +## Шаг 3: Настроить Cursor + +Нужно один раз сказать Cursor использовать туннель как прокси. + +1. Откройте **Cursor**. +2. Откройте настройки: + - либо меню **File** → **Preferences** → **Settings** (или **Файл** → **Параметры** → **Настройки**); + - либо нажмите **Ctrl+,** (запятая). +3. Вверху в поиске настроек введите: **proxy**. +4. Найдите пункт **Http: Proxy** (или **Http: Proxy Support** и т.п.) и в поле значения введите **ровно**: + ```text + socks5://127.0.0.1:10809 + ``` +5. Если есть отдельное поле **Https: Proxy** — введите туда то же самое: `socks5://127.0.0.1:10809`. +6. Убедитесь, что **Cursor > General: Disable Http2** выключен (галочка снята). HTTP/2 должен быть включён — так Cursor будет стабильнее работать через туннель. + +**Альтернатива через JSON-настройки:** + +- В Cursor откройте палитру команд: **Ctrl+Shift+P**. +- Введите: **Open User Settings (JSON)** и выберите этот пункт. +- В открывшийся файл добавьте (или допишите к существующему объекту) строки: + +```json +"http.proxy": "socks5://127.0.0.1:10809", +"https.proxy": "socks5://127.0.0.1:10809" +``` + +Сохраните файл. Настройки применятся автоматически. + +--- + +## Порядок работы каждый раз + +1. **Сначала** запустите туннель (скриптом или командой `ssh -D 10809 ...`). Дождитесь, что он поднялся (проверка через `status` или `netstat`). +2. **Потом** откройте Cursor и работайте как обычно. Cursor будет отправлять трафик через туннель на порт 10809. + +Если туннель не запущен, Cursor может снова показывать PING timeout или не подключаться к серверам. + +--- + +## Автозапуск туннеля при входе в Windows + +Чтобы не запускать туннель вручную каждый раз, можно настроить его старт при входе в систему. + +### Способ 1: Планировщик заданий + +1. Нажмите **Win+R**, введите **taskschd.msc** и нажмите Enter. Откроется **Планировщик заданий**. +2. Справа нажмите **Создать задачу…** (не «Создать простую задачу»). +3. Вкладка **Общие**: + - Имя: например **Cursor SOCKS tunnel**. + - Отметьте «Выполнять для всех пользователей» при необходимости; «Выполнять с наивысшими правами» можно не ставить. +4. Вкладка **Триггеры** → **Создать**: + - Начать задачу: **При входе в систему**. + - Пользователь: ваш пользователь Windows. ОК. +5. Вкладка **Действия** → **Создать**: + - Действие: **Запуск программы**. + - Программа или сценарий: **powershell.exe**. + - Добавить аргументы: + - Если используете скрипт из репо (подставьте свой путь к папке local_machine): + ```text + -NoProfile -WindowStyle Hidden -File "C:\Users\ВашеИмя\путь\к\local_machine\docs\cursor\scripts\cursor-socks-tunnel.ps1" start + ``` + - Или без скрипта (фон; только Mac→RU→DO, нужен доступ к RU): + ```text + -NoProfile -WindowStyle Minimized -Command "ssh -D 10809 -o ServerAliveInterval=60 -o ServerAliveCountMax=4 telegram-socks-via-do" + ``` + Нажмите ОК. +6. ОК по задаче. При следующем входе в Windows туннель должен запускаться сам. + +### Способ 2: Ярлык в папке «Автозагрузка» + +1. Нажмите **Win+R**, введите **shell:startup** и нажмите Enter. Откроется папка автозагрузки вашего пользователя. +2. В этой папке создайте ярлык (правый клик → Создать → Ярлык). +3. В качестве расположения объекта укажите, например (подставьте свой путь к репо): + ```text + powershell.exe -NoProfile -WindowStyle Normal -File "C:\Users\ВашеИмя\code\local_machine\docs\cursor\scripts\cursor-socks-tunnel.ps1" start + ``` +4. Имя ярлыка: например **Cursor tunnel**. Готово. При каждом входе в Windows будет запускаться PowerShell и подниматься туннель. + +--- + +## Если что-то не работает + +| Ситуация | Что проверить и сделать | +|----------|-------------------------| +| **Cursor не подключается, ошибки в чате** | 1) Туннель действительно запущен: в PowerShell `.\docs\cursor\scripts\cursor-socks-tunnel.ps1 status` или `netstat -an \| findstr 10809`. 2) В настройках Cursor указано ровно `socks5://127.0.0.1:10809`. 3) Перезапустите Cursor (полностью закрыть и открыть снова). | +| **Всё ещё PING timed out** | Убедитесь, что в настройках Cursor **Disable Http2** выключен (HTTP/2 включён). Перезапустите туннель и Cursor. | +| **Порт 10809 занят** | Другой программой уже занят. Можно сменить порт: например 10810. Тогда в команде туннеля замените 10809 на 10810 и в Cursor укажите `socks5://127.0.0.1:10810`. | +| **Туннель обрывается через время** | В команде SSH должны быть параметры `ServerAliveInterval=30` и `ServerAliveCountMax=6` — в скрипте они уже есть; при ручном запуске используйте команду из Варианта B. | +| **Ошибка при запуске скрипта .ps1** | Выполните один раз: `Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned` и ответьте Y. | +| **SSH просит пароль** | Ключ не подхватывается. Проверьте, что файл лежит по пути `C:\Users\ВашеИмя\.ssh\id_ed25519` и в команде указан `-i $env:USERPROFILE\.ssh\id_ed25519`. При необходимости заново установите ключ по [WINDOWS_SSH_KEY_INSTALL.md](./WINDOWS_SSH_KEY_INSTALL.md). | + +Логи Cursor на Windows лежат в папке: +`%APPDATA%\Cursor\logs\` (файл `main.log`). В проблемных местах ищите слова ERROR, PING, timeout, ECONNREFUSED, ETIMEDOUT. + +--- + +## Ссылки + +| Документ | Для чего | +|----------|----------| +| [WINDOWS_SSH_KEY_INSTALL.md](./WINDOWS_SSH_KEY_INSTALL.md) | Установка SSH-ключа на Windows (если ещё не сделано). | +| [PROXY_GUIDE.md](./PROXY_GUIDE.md) | Общее описание прокси ru.hunab.app. | +| [README.md](./README.md) | Содержимое папки connectivity. | diff --git a/docs/connectivity/WINDOWS_SSH_KEY_INSTALL.md b/docs/connectivity/WINDOWS_SSH_KEY_INSTALL.md new file mode 100644 index 0000000..e2e59aa --- /dev/null +++ b/docs/connectivity/WINDOWS_SSH_KEY_INSTALL.md @@ -0,0 +1,166 @@ +# Установка SSH-ключа на Windows (только для туннеля через прокси) + +Инструкция для тех, кто впервые настраивает такой доступ. Все шаги расписаны по порядку. + +--- + +## Зачем это нужно + +Чтобы Cursor (или Telegram) на Windows могли работать через прокси-сервер **ru.hunab.app**, нужен **SSH-ключ**. Это пара файлов: «приватный» (секретный, хранится только у вас) и «публичный» (его один раз добавляют на сервер). После этого Windows сможет подключаться к серверу **без ввода пароля**, только по ключу. Ключ используется **только для туннеля** — никаких прав на сервере пользователю не нужно. + +**Если с Windows нет доступа к интернет-серверу** (например, вы за корпоративным файрволом), ключ создают на другой машине (Mac), публичный ключ ставят на сервер, а **приватный ключ** переносят на Windows. Дальше в инструкции два варианта: A (ключ создан на Mac) и B (ключ создают на самой Windows). + +--- + +## Небольшой словарик + +| Слово | По-простому | +|-------|-------------| +| **SSH** | Способ безопасно подключаться к удалённому компьютеру по сети. | +| **SSH-ключ** | Пара файлов (приватный + публичный). Вместо пароля сервер проверяет, что у вас есть «ваша половина» ключа. | +| **Приватный ключ** | Файл `id_ed25519`. Храните только у себя, никому не отдавайте. | +| **Публичный ключ** | Файл `id_ed25519.pub`. Его один раз добавляют на сервер в список разрешённых ключей. | +| **%USERPROFILE%** | Папка вашего пользователя Windows, например `C:\Users\Иван`. В PowerShell то же самое записывается как `$env:USERPROFILE`. | +| **PowerShell** | Программа в Windows для ввода текстовых команд. Открывается через поиск по названию «PowerShell» или через меню Win+X → «Windows PowerShell». | + +--- + +## Вариант A: Ключ создан на Mac (с Windows до сервера доступа нет) + +Этот вариант для ситуации, когда с Windows-компьютера вы **не** можете зайти на сервер 149.154.64.19 (нет доступа в интернет к нему или он закрыт). Ключ создаётся на Mac (или другой машине, с которой сервер доступен), затем **один файл** переносится на Windows. + +### Часть 1. На Mac (или другой машине с доступом к серверу) + +1. Откройте **Терминал** на Mac. +2. Перейдите в папку с репозиторием **local_machine** (корень репо — та папка, где лежат папки `docs`, `scripts` и т.д.): + ```bash + cd /путь/к/local_machine + ``` + Пример: `cd /Users/eternal/code/local_machine` +3. Запустите скрипт: + ```bash + ./docs/connectivity/scripts/create-windows-proxy-key.sh + ``` +4. Скрипт создаст ключ и сам добавит публичный ключ на сервер. В конце он выведет текст вроде: + - «Скопировать на Windows один файл: …/windows-proxy-key/id_ed25519» + - «На Windows положить в: %USERPROFILE%\.ssh\id_ed25519» + +Запомните или сохраните этот путь к файлу на Mac. Публичный ключ на сервер уже добавлен, больше на Mac ничего делать не нужно. + +### Часть 2. На Windows — куда положить ключ + +**Куда копировать:** в папку вашего пользователя, в подпапку `.ssh`. Полный путь выглядит так: + +- `C:\Users\**ИмяПользователя**\.ssh\id_ed25519` + +**ИмяПользователя** — это то имя, под которым вы входите в Windows (та же папка, что открывается, когда вы в проводнике заходите в «Этот компьютер» → «Локальный диск (C:)» → «Users»). + +**Как сделать по шагам:** + +1. **Создать папку `.ssh`**, если её ещё нет: + - Нажмите **Win+R** (клавиша с логотипом Windows и буква R). + - Введите: `%USERPROFILE%` и нажмите Enter. Откроется ваша папка пользователя (например, `C:\Users\Иван`). + - В этой папке создайте новую папку с именем **`.ssh`** (точка в начале обязательна). Если Windows спросит про объединение имени — оставьте именно `.ssh`. + +2. **Скопировать файл с Mac на Windows** (флешка, облако, мессенджер — как удобно). Файл должен называться **`id_ed25519`** (без расширения .txt и без .pub). + +3. **Положить файл в папку `.ssh`:** + - Перетащите `id_ed25519` в папку `C:\Users\**ВашеИмя**\.ssh\` + - Итог: файл должен лежать по пути `C:\Users\**ВашеИмя**\.ssh\id_ed25519`. + +### Часть 3. Проверка на Windows + +1. Откройте **PowerShell** (поиск в меню Пуск: «PowerShell» → «Windows PowerShell»). +2. Введите команду (можно скопировать целиком; путь подставится сам): + ```powershell + ssh -i $env:USERPROFILE\.ssh\id_ed25519 ahau@149.154.64.19 "echo OK" + ``` +3. При первом запуске может появиться вопрос «Trust this host?» — введите **yes** и нажмите Enter. +4. **Ожидаемый результат:** в следующей строке выводится только слово **OK**. Пароль спрашивать не должно. + +Если вывело **OK** — ключ установлен правильно. Дальше: настройка туннеля и Cursor по инструкции [WINDOWS_CURSOR_TUNNEL.md](./WINDOWS_CURSOR_TUNNEL.md). + +Если появилась ошибка (например, «Permission denied» или «Could not resolve hostname»), проверьте: +- файл лежит именно в `C:\Users\**ВашеИмя**\.ssh\id_ed25519`; +- файл скопирован полностью, не переименован в `id_ed25519.txt`; +- с Mac скрипт действительно выполнился до конца и написал, что публичный ключ добавлен на сервер. + +--- + +## Вариант B: Ключ создаётся на Windows (с Windows есть доступ к серверу) + +Если с вашего Windows-компьютера **есть** доступ в интернет до 149.154.64.19, ключ можно создать прямо на Windows и один раз добавить публичный ключ на сервер (с этой же Windows или с Mac). + +### Что понадобится + +- Файл **`setup-ssh-key-for-proxy.ps1`** из репо: `docs/connectivity/scripts/setup-ssh-key-for-proxy.ps1`. Скопируйте его на Windows в любую папку (например, «Загрузки» или папку с репо). +- В Windows должен быть установлен **OpenSSH-клиент**. Проверка: откройте PowerShell и введите `ssh -V`. Должна появиться строка с версией (например, OpenSSH_for_Windows_8.x). Если команда не найдена — установите: **Параметры Windows** → **Приложения** → **Дополнительные компоненты** → найдите **OpenSSH-клиент** → Установить. + +### Шаг 1. Разрешить выполнение скриптов (сделать один раз) + +1. Откройте **PowerShell** (не cmd). Лучше «Запуск от имени текущего пользователя». +2. Введите команду: + ```powershell + Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned + ``` +3. На вопрос о смене политики ответьте **Y** (или **A**) и нажмите Enter. + +### Шаг 2. Запустить скрипт установки ключа + +1. В PowerShell перейдите в папку, куда положили `setup-ssh-key-for-proxy.ps1`. Пример для папки «Загрузки»: + ```powershell + cd $env:USERPROFILE\Downloads + ``` + Если файл в другой папке, подставьте свой путь вместо `Downloads`. +2. Запустите скрипт: + ```powershell + .\setup-ssh-key-for-proxy.ps1 + ``` +3. **Если ключа ещё нет:** скрипт спросит, создать ли новый ключ. Введите **Y** и Enter. Он создаст файлы в папке `%USERPROFILE%\.ssh\` и выведет **одну длинную строку** (публичный ключ). Эту строку нужно один раз добавить на сервер (см. ниже). +4. **Если ключ уже лежит в `%USERPROFILE%\.ssh\id_ed25519`:** скрипт просто проверит подключение и напишет что-то вроде `[OK] SSH works`. + +### Шаг 3. Добавить публичный ключ на сервер (только если ключ создали на Windows) + +Если вы только что сгенерировали ключ на Windows, скрипт вывел строку вида: + +`ssh-ed25519 AAAAC3... много символов ... windows-proxy-tunnel` + +Эту строку **целиком** нужно один раз добавить на сервер 149.154.64.19 в файл `~/.ssh/authorized_keys` пользователя **ahau**. Сделать это может кто-то с доступом к серверу (например, с Mac): + +```bash +# Выполнить на сервере 149.154.64.19 под пользователем ahau (или с Mac: ssh ahau@149.154.64.19) +echo "ВСТАВЬТЕ_СЮДА_ЦЕЛИКОМ_СТРОКУ_ИЗ_СКРИПТА" >> ~/.ssh/authorized_keys +chmod 600 ~/.ssh/authorized_keys +``` + +После этого на Windows снова запустите `.\setup-ssh-key-for-proxy.ps1` — должно появиться `[OK] SSH works`. + +### Шаг 4. Проверить подключение вручную + +В PowerShell: + +```powershell +ssh -i $env:USERPROFILE\.ssh\id_ed25519 ahau@149.154.64.19 "echo OK" +``` + +Должно вывести **OK** без запроса пароля. + +--- + +## Дальше + +- Настройка туннеля и Cursor: [WINDOWS_CURSOR_TUNNEL.md](./WINDOWS_CURSOR_TUNNEL.md). +- Приватный ключ (`id_ed25519`) никому не передавайте и не выкладывайте никуда. + +--- + +## Кратко: какие файлы для чего + +| Файл | Где запускать | Что делает | +|------|----------------|------------| +| **create-windows-proxy-key.sh** | На **Mac** (в папке репо) | Создаёт ключ, добавляет публичный ключ на сервер. Приватный ключ остаётся в папке `windows-proxy-key/` — его нужно скопировать на Windows. | +| **setup-ssh-key-for-proxy.ps1** | На **Windows** | Проверяет или создаёт ключ в `%USERPROFILE%\.ssh\`, проверяет подключение к серверу, при необходимости дописывает в конфиг SSH хост `hsites-ahau`. | + +Пути в репозитории: +`docs/connectivity/scripts/create-windows-proxy-key.sh` +`docs/connectivity/scripts/setup-ssh-key-for-proxy.ps1` diff --git a/docs/connectivity/launchd/README.md b/docs/connectivity/launchd/README.md index 40f3231..d05f639 100644 --- a/docs/connectivity/launchd/README.md +++ b/docs/connectivity/launchd/README.md @@ -1,17 +1,30 @@ # LaunchAgent: SOCKS-туннель для Telegram при входе в систему -Поднимает `ssh -D 1081 ahau@149.154.64.19` при логине и перезапускает при обрыве. +**Основной сценарий:** выход в интернет с **IP DO** только по цепочке **Mac→RU→DO** — `install-via-do.sh` ставит **`telegram-socks-launch.sh`**, который всегда запускает **`telegram-socks-via-do`**. Прямой Mac→DO для SOCKS не используется. Нужны **`Host telegram-socks-via-do`** (с `ProxyJump hsites-ahau`) и доступ к RU. См. [TELEGRAM_GUIDE.md](../TELEGRAM_GUIDE.md). + +**Альтернатива:** только RU прокси — `install.sh` (`ssh -D 1081 ahau@149.154.64.19`). - **Порт:** 1081 (отдельно от Cursor 10809, конфликтов нет) -- **Сервер:** 149.154.64.19 (ru.hunab.app), пользователь ahau -## Установка (один раз — дальше работает при каждом входе) +## Установка (один раз — дальше работает при каждом входе и после перезагрузки) + +**Через DO (основной):** ключи **ahau** + **`hunab_deploy_key`**, Host **`telegram-socks-via-do`** — [PROXY_GUIDE.md](../PROXY_GUIDE.md). ```bash # из корня репо local_machine -./connectivity/launchd/install.sh +./docs/connectivity/launchd/install-via-do.sh ``` +Лог: `/tmp/telegram-socks-tunnel-via-do.log`. Снять: `./docs/connectivity/launchd/uninstall-via-do.sh`. + +### Только RU (без DO) + +```bash +./docs/connectivity/launchd/install.sh +``` + +Не держите оба plist на **1081** одновременно — перед `install-via-do` скрипт выгружает старый `com.hunab.telegram-socks` при наличии. + Скрипт копирует plist в `~/Library/LaunchAgents/` и выполняет `launchctl load`. После этого туннель будет автоматически подниматься при каждом старте/входе в систему. Проверка: туннель уже должен быть поднят (RunAtLoad), порт слушает: @@ -19,26 +32,29 @@ ```bash lsof -i :1081 # или -./connectivity/scripts/telegram-socks-tunnel.sh status +./docs/connectivity/scripts/telegram-socks-tunnel.sh status ``` ## Удаление ```bash -./connectivity/launchd/uninstall.sh +./docs/connectivity/launchd/uninstall-via-do.sh # сценарий через DO +./docs/connectivity/launchd/uninstall.sh # сценарий только RU ``` +Файлы в репозитории: **telegram-socks-launch.sh** (только `telegram-socks-via-do`), **com.hunab.telegram-socks-via-do.plist**. Копия скрипта: **`~/Library/Application Support/hunab/telegram-socks-launch.sh`**. + ## Поведение - **RunAtLoad** — запуск при входе в систему - **KeepAlive** — при обрыве SSH launchd перезапускает туннель - **ThrottleInterval 30** — не чаще одного перезапуска в 30 секунд при постоянных падениях -- Лог: `/tmp/telegram-socks-tunnel.log` (при желании замените путь в plist после копирования) +- Логи: `/tmp/telegram-socks-tunnel-via-do.log` (via-DO), `/tmp/telegram-socks-tunnel.log` (только RU) ## Требования -- SSH-ключ для `ahau@149.154.64.19` должен быть доступен при логине (в клюchain или без пароля), иначе туннель не поднимется. Проверка вручную: `ssh -D 1081 -N ahau@149.154.64.19`. -- Если используете алиас из `~/.ssh/config` (например `hsites-ahau`), в plist можно заменить `ahau@149.154.64.19` на `hsites-ahau`. +- **via-DO:** Host `telegram-socks-via-do` (+ jump `hsites-ahau`); ключи **ahau** и **hunab** в keychain. Проверка: `ssh -N -D 1082 telegram-socks-via-do`. +- **Только RU:** SSH-ключ для `ahau@149.154.64.19` при логине; проверка: `ssh -D 1081 -N hsites-ahau`. ## Конфликты портов diff --git a/docs/connectivity/launchd/com.hunab.telegram-socks-via-do.plist b/docs/connectivity/launchd/com.hunab.telegram-socks-via-do.plist new file mode 100644 index 0000000..aa63727 --- /dev/null +++ b/docs/connectivity/launchd/com.hunab.telegram-socks-via-do.plist @@ -0,0 +1,35 @@ + + + + + + Label + com.hunab.telegram-socks-via-do + ProgramArguments + + /bin/bash + -c + exec /bin/bash "$HOME/Library/Application Support/hunab/telegram-socks-launch.sh" + + RunAtLoad + + KeepAlive + + ThrottleInterval + 15 + SoftResourceLimits + + NumberOfFiles + 65536 + + HardResourceLimits + + NumberOfFiles + 65536 + + StandardOutPath + /tmp/telegram-socks-tunnel-via-do.log + StandardErrorPath + /tmp/telegram-socks-tunnel-via-do.log + + diff --git a/docs/connectivity/launchd/install-via-do.sh b/docs/connectivity/launchd/install-via-do.sh new file mode 100755 index 0000000..904caee --- /dev/null +++ b/docs/connectivity/launchd/install-via-do.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# LaunchAgent: SOCKS на 1081 с выходом через DigitalOcean (прокси → DO), см. PROXY_GUIDE.md. +# Требуется блок Host telegram-socks-via-do в ~/.ssh/config. +set -e +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PLIST_NAME="com.hunab.telegram-socks-via-do.plist" +DST="$HOME/Library/LaunchAgents/$PLIST_NAME" + +mkdir -p "$HOME/Library/LaunchAgents" +HUNAB_SUPPORT="$HOME/Library/Application Support/hunab" +mkdir -p "$HUNAB_SUPPORT" +cp "$SCRIPT_DIR/telegram-socks-launch.sh" "$HUNAB_SUPPORT/telegram-socks-launch.sh" +chmod +x "$HUNAB_SUPPORT/telegram-socks-launch.sh" +# Не использовать имя UID — в bash это readonly (присваивание ломает скрипт). +GUI_UID=$(id -u) +launchctl unload "$HOME/Library/LaunchAgents/com.hunab.telegram-socks.plist" 2>/dev/null || true +launchctl bootout "gui/$GUI_UID/com.hunab.telegram-socks-via-do" 2>/dev/null || true +launchctl unload "$DST" 2>/dev/null || true +cp "$SCRIPT_DIR/$PLIST_NAME" "$DST" +if launchctl bootstrap "gui/$GUI_UID" "$DST" 2>/dev/null; then + : +else + launchctl load "$DST" || true +fi +echo "Installed: $DST (loaded). Survives reboot while this plist exists." +echo "Launch helper: $HUNAB_SUPPORT/telegram-socks-launch.sh (only Mac→RU→DO via telegram-socks-via-do)." +echo "Ensure ~/.ssh/config defines Host telegram-socks-via-do (+ ProxyJump hsites-ahau) — PROXY_GUIDE.md / TELEGRAM_GUIDE.md." +echo "Verify autostart: launchctl list | grep telegram-socks" +lsof -i :1081 -sTCP:LISTEN 2>/dev/null && echo "Port 1081 is listening." || echo "Check in a few seconds: lsof -i :1081" diff --git a/docs/connectivity/launchd/telegram-socks-launch.sh b/docs/connectivity/launchd/telegram-socks-launch.sh new file mode 100755 index 0000000..023c5ae --- /dev/null +++ b/docs/connectivity/launchd/telegram-socks-launch.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# LaunchAgent: SOCKS на 1081 только по цепочке Mac → RU → DO (Host telegram-socks-via-do). +# Прямой Mac → DO для SOCKS не используется. +ulimit -n 65536 2>/dev/null || ulimit -n 10240 2>/dev/null || true + +SSH_CMD=(/usr/bin/ssh -D 1081 -N \ + -o TCPKeepAlive=yes \ + -o IPQoS=throughput \ + -o ServerAliveInterval=60 \ + -o ServerAliveCountMax=4 \ + -o ConnectTimeout=30) + +exec "${SSH_CMD[@]}" telegram-socks-via-do diff --git a/docs/connectivity/launchd/uninstall-via-do.sh b/docs/connectivity/launchd/uninstall-via-do.sh new file mode 100755 index 0000000..5d0929c --- /dev/null +++ b/docs/connectivity/launchd/uninstall-via-do.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Снимает LaunchAgent com.hunab.telegram-socks-via-do. +set -e +PLIST_NAME="com.hunab.telegram-socks-via-do.plist" +DST="$HOME/Library/LaunchAgents/$PLIST_NAME" + +GUI_UID=$(id -u) +launchctl bootout "gui/$GUI_UID/com.hunab.telegram-socks-via-do" 2>/dev/null || true +launchctl unload "$DST" 2>/dev/null || true +rm -f "$DST" +rm -f "$HOME/Library/Application Support/hunab/telegram-socks-launch.sh" +echo "Uninstalled: $DST (and launch helper if present)" diff --git a/docs/connectivity/matrix/Caddyfile.example b/docs/connectivity/matrix/Caddyfile.example new file mode 100644 index 0000000..fd15271 --- /dev/null +++ b/docs/connectivity/matrix/Caddyfile.example @@ -0,0 +1,27 @@ +# Пример для Caddy 2: TLS автоматически (Let's Encrypt). +# Замените matrix.example.com и example.com на свои домены. +# +# Запуск Caddy на том же хосте, что и docker-compose (Synapse на 127.0.0.1:8008/8448). + +matrix.example.com { + reverse_proxy /_matrix/* 127.0.0.1:8008 + reverse_proxy /_synapse/* 127.0.0.1:8008 +} + +# Федерация Matrix: S2S часто идёт на :8448. Проброс на тот же процесс Synapse. +matrix.example.com:8448 { + reverse_proxy 127.0.0.1:8448 +} + +# Клиенты ищут .well-known на apex-домене (если server_name = example.com). +example.com { + encode gzip + handle /.well-known/matrix/server { + header Content-Type application/json + respond `{"m.server": "matrix.example.com:443"}` + } + handle /.well-known/matrix/client { + header Content-Type application/json + respond `{"m.homeserver": {"base_url": "https://matrix.example.com"}}` + } +} diff --git a/docs/connectivity/matrix/docker-compose.yml b/docs/connectivity/matrix/docker-compose.yml new file mode 100644 index 0000000..bb02874 --- /dev/null +++ b/docs/connectivity/matrix/docker-compose.yml @@ -0,0 +1,45 @@ +# Synapse + PostgreSQL на одном хосте (DigitalOcean и т.п.). +# Перед первым запуском: см. MATRIX_SYNAPSE_DO.md — generate и homeserver.yaml. +# +# Использование на сервере: +# export MATRIX_DOMAIN=matrix.example.com # хост, где слушает Synapse за прокси +# export SYNAPSE_SERVER_NAME=example.com # публичное имя сервера (часто apex) +# docker compose up -d +# +# Порты 8008/8448 не публикуем наружу, если перед Synapse стоит Caddy/Nginx на 443. + +services: + db: + image: docker.io/postgres:15-alpine + restart: unless-stopped + environment: + POSTGRES_USER: synapse + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} + POSTGRES_DB: synapse + POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C + volumes: + - synapse-db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U synapse"] + interval: 10s + timeout: 5s + retries: 5 + + synapse: + image: docker.io/matrixdotorg/synapse:latest + restart: unless-stopped + depends_on: + db: + condition: service_healthy + volumes: + - synapse-data:/data + environment: + SYNAPSE_NO_TLS: "1" + ports: + - "127.0.0.1:8008:8008" + - "127.0.0.1:8448:8448" + command: run + +volumes: + synapse-data: + synapse-db: diff --git a/docs/connectivity/matrix/env.example b/docs/connectivity/matrix/env.example new file mode 100644 index 0000000..c579196 --- /dev/null +++ b/docs/connectivity/matrix/env.example @@ -0,0 +1,2 @@ +# Скопируйте в .env рядом с docker-compose.yml и заполните. +POSTGRES_PASSWORD=сгенерируйте_длинный_случайный_пароль diff --git a/docs/connectivity/scripts/create-windows-proxy-key.sh b/docs/connectivity/scripts/create-windows-proxy-key.sh new file mode 100755 index 0000000..f6df6e4 --- /dev/null +++ b/docs/connectivity/scripts/create-windows-proxy-key.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Создать SSH-ключ для туннеля на Windows и добавить публичный ключ на прокси. +# Запускать на машине, с которой ЕСТЬ доступ к 149.154.64.19 (например Mac). +# Приватный ключ остаётся в windows-proxy-key/ — скопировать на Windows в %USERPROFILE%\.ssh\id_ed25519 +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KEY_DIR="${SCRIPT_DIR}/windows-proxy-key" +KEY_FILE="${KEY_DIR}/id_ed25519" +PROXY_HOST="${CURSOR_TUNNEL_HOST:-hsites-ahau}" + +echo "=== Ключ для туннеля на Windows (только туннелирование, прав на сервере не нужно) ===" +echo " Прокси: $PROXY_HOST (149.154.64.19)" +echo "" + +mkdir -p "$KEY_DIR" +chmod 700 "$KEY_DIR" + +if [[ -f "$KEY_FILE" ]]; then + echo "Ключ уже есть: $KEY_FILE" + echo "Публичный ключ на сервере уже добавлен (если скрипт выполнялся до конца)." + echo "" + echo "Скопируйте на Windows один файл (приватный ключ):" + echo " Источник: $KEY_FILE" + echo " На Windows: %USERPROFILE%\\.ssh\\id_ed25519" + echo "" + exit 0 +fi + +echo "Создаю ключ в $KEY_DIR ..." +ssh-keygen -t ed25519 -f "$KEY_FILE" -N "" -C "windows-proxy-tunnel" + +echo "" +echo "Добавляю публичный ключ на сервер $PROXY_HOST ..." +cat "${KEY_FILE}.pub" | ssh "$PROXY_HOST" "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" +echo "[OK] Публичный ключ добавлен в ~/.ssh/authorized_keys на сервере." + +echo "" +echo "Проверка подключения с этим ключом..." +ssh -i "$KEY_FILE" -o BatchMode=yes -o ConnectTimeout=10 "$PROXY_HOST" "echo OK" || { echo "Проверка не удалась."; exit 1; } +echo "[OK] Подключение работает." +echo "" +echo "--- Что сделать дальше ---" +echo "1. Скопировать на Windows ОДИН файл (приватный ключ, никому не передавать):" +echo " $KEY_FILE" +echo " На Windows положить в: %USERPROFILE%\\.ssh\\id_ed25519" +echo " (папку .ssh создать, если нет.)" +echo "" +echo "2. На Windows запустить туннель: см. docs/connectivity/WINDOWS_CURSOR_TUNNEL.md" +echo " или: ssh -D 10809 -o ServerAliveInterval=30 -o ServerAliveCountMax=6 ahau@149.154.64.19" +echo "" diff --git a/docs/connectivity/scripts/macos-hotspot-network-recover.sh b/docs/connectivity/scripts/macos-hotspot-network-recover.sh new file mode 100755 index 0000000..69c6150 --- /dev/null +++ b/docs/connectivity/scripts/macos-hotspot-network-recover.sh @@ -0,0 +1,105 @@ +#!/bin/sh +# Сброс сетевого стека macOS после смены хотспота / «интернет не поднимается без перезагрузки». +# Типичная причина в связке с docs/connectivity: системный PAC на localhost:1089 при мёртвом/ещё не +# поднятом PAC-сервере + кэш DNS / Wi‑Fi. См. HOTSPOT_NETWORK_EMERGENCY.md +# +# Использование (из корня репозитория): +# ./docs/connectivity/scripts/macos-hotspot-network-recover.sh status +# ./docs/connectivity/scripts/macos-hotspot-network-recover.sh recover +# +# Переменные: +# MACOS_PAC_SERVICES — как в macos-system-pac.sh (по умолчанию Wi-Fi|Thunderbolt Bridge 2) +# WIFI_DEVICE — например en0; иначе авто: первый Device у Hardware Port: Wi-Fi + +set -e +ROOT="$(cd "$(dirname "$0")" && pwd)" +PAC_SCRIPT="$ROOT/macos-system-pac.sh" + +wifi_device() { + if [ -n "${WIFI_DEVICE:-}" ]; then + printf '%s\n' "$WIFI_DEVICE" + return + fi + # "Device: en0" под блоком "Hardware Port: Wi-Fi" + networksetup -listallhardwareports 2>/dev/null \ + | awk '/Hardware Port: Wi-Fi$/ {p=1; next} p && /^Device: / {print $2; exit}' +} + +usage() { + printf '%s\n' "Usage: $(basename "$0") status|recover" >&2 + exit 1 +} + +[ "$#" -eq 1 ] || usage +cmd=$1 + +case "$cmd" in + status) + echo "=== Wi-Fi device (hardware) ===" + wifi_device || true + echo "=== networksetup -listallnetworkservices (excerpt) ===" + networksetup -listallnetworkservices 2>/dev/null | head -15 || true + echo "=== PAC / autoproxy (Wi-Fi + Thunderbolt Bridge 2 if present) ===" + if [ -x "$PAC_SCRIPT" ]; then + MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" status || true + fi + echo "=== SOCKS firewall (Wi-Fi) ===" + networksetup -getsocksfirewallproxy "Wi-Fi" 2>/dev/null || true + echo "=== Web proxy (Wi-Fi) ===" + networksetup -getwebproxy "Wi-Fi" 2>/dev/null || true + echo "=== Listener on 1089 (PAC URL default) ===" + if lsof -i :1089 -sTCP:LISTEN 2>/dev/null | head -5; then + : + else + echo "(none — при Enabled PAC в Safari/системе возможны сбои)" + fi + echo "=== Default route ===" + route -n get default 2>/dev/null | head -12 || true + echo "=== Quick reachability (ICMP may be blocked on some hotspots) ===" + ping -c 1 -W 2000 1.1.1.1 >/dev/null 2>&1 && echo "ping 1.1.1.1: ok" || echo "ping 1.1.1.1: fail" + ;; + recover) + echo "1) Отключаем автоматический PAC на выбранных сервисах..." + if [ -x "$PAC_SCRIPT" ]; then + MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" off + else + echo "WARN: нет $PAC_SCRIPT — вручную: Системные настройки → Сеть → Wi‑Fi → Подробнее → Прокси → снять «Автоматическая настройка прокси»" >&2 + fi + echo "2) Отключаем системный SOCKS-прокси на Wi-Fi (если был включён Shadowsocks и т.п.)..." + if networksetup -listallnetworkservices 2>/dev/null | grep -Fqx "Wi-Fi"; then + networksetup -setsocksfirewallproxystate "Wi-Fi" off 2>/dev/null || true + fi + echo "3) Сброс DNS-кэша (нужен sudo)..." + if sudo -n true 2>/dev/null; then + sudo dscacheutil -flushcache 2>/dev/null || true + sudo killall -HUP mDNSResponder 2>/dev/null || true + echo " mDNSResponder перезагружен." + else + echo " Пропуск (нет sudo без пароля). Выполните вручную:" + echo " sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder" + fi + dev="$(wifi_device)" + if [ -n "$dev" ]; then + echo "4) Обновление DHCP на $dev (нужен sudo)..." + if sudo -n true 2>/dev/null; then + sudo ipconfig set "$dev" DHCP 2>/dev/null || true + else + echo " Пропуск. Вручную: sudo ipconfig set $dev DHCP" + fi + echo "5) Краткое выключение/включение Wi‑Fi радиомодуля ($dev)..." + networksetup -setairportpower "$dev" off 2>/dev/null || true + sleep 2 + networksetup -setairportpower "$dev" on 2>/dev/null || true + else + echo "4–5) Не удалось определить Wi‑Fi device — пропуск DHCP/airport power. Вручную выключите/включите Wi‑Fi в меню." + fi + echo "=== Готово. Проверка: ===" + "$0" status + echo "" + echo "Если интернет всё ещё нет: откройте хотспот в браузере (captive portal), затем при необходимости снова включите PAC:" + echo " ./docs/connectivity/scripts/macos-pac-on.sh" + ;; + *) + usage + ;; +esac diff --git a/docs/connectivity/scripts/macos-pac-off.sh b/docs/connectivity/scripts/macos-pac-off.sh new file mode 100755 index 0000000..a363292 --- /dev/null +++ b/docs/connectivity/scripts/macos-pac-off.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec "$(dirname "$0")/macos-system-pac.sh" off diff --git a/docs/connectivity/scripts/macos-pac-on.sh b/docs/connectivity/scripts/macos-pac-on.sh new file mode 100755 index 0000000..fe43871 --- /dev/null +++ b/docs/connectivity/scripts/macos-pac-on.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec "$(dirname "$0")/macos-system-pac.sh" on diff --git a/docs/connectivity/scripts/macos-system-pac.sh b/docs/connectivity/scripts/macos-system-pac.sh new file mode 100755 index 0000000..f4a4019 --- /dev/null +++ b/docs/connectivity/scripts/macos-system-pac.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# Включает/выключает системный «автоматический прокси» (PAC) в macOS через networksetup. +# По умолчанию: PAC http://localhost:1089/proxy.pac на сервисах Wi-Fi и Thunderbolt Bridge 2. +# +# Переменные окружения: +# MACOS_PAC_URL — URL proxy.pac (по умолчанию http://localhost:1089/proxy.pac) +# MACOS_PAC_SERVICES — список имён сетевых сервисов через «|» (см. networksetup -listallnetworkservices) + +usage() { + printf '%s\n' "Usage: $(basename "$0") on|off|status" >&2 + exit 1 +} + +[ "$#" -eq 1 ] || usage +cmd=$1 + +PAC_URL="${MACOS_PAC_URL:-http://localhost:1089/proxy.pac}" +SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" + +svc_exists() { + networksetup -listallnetworkservices 2>/dev/null | grep -Fqx "$1" +} + +case "$cmd" in + on) + OLDIFS=$IFS + IFS='|' + for svc in $SERVICES; do + IFS=$OLDIFS + [ -z "$svc" ] && continue + if ! svc_exists "$svc"; then + echo "skip (нет сервиса): $svc" >&2 + continue + fi + networksetup -setautoproxyurl "$svc" "$PAC_URL" + networksetup -setautoproxystate "$svc" on + echo "PAC on: $svc" + done + ;; + off) + OLDIFS=$IFS + IFS='|' + for svc in $SERVICES; do + IFS=$OLDIFS + [ -z "$svc" ] && continue + if ! svc_exists "$svc"; then + echo "skip (нет сервиса): $svc" >&2 + continue + fi + networksetup -setautoproxystate "$svc" off + echo "PAC off: $svc" + done + ;; + status) + printf '%s\n' "PAC_URL=$PAC_URL" + printf '%s\n' "SERVICES=$SERVICES" + printf '%s\n' "---" + OLDIFS=$IFS + IFS='|' + for svc in $SERVICES; do + IFS=$OLDIFS + [ -z "$svc" ] && continue + if svc_exists "$svc"; then + echo "[$svc]" + networksetup -getautoproxyurl "$svc" + else + echo "[$svc] (нет такого сервиса)" + fi + done + printf '%s\n' "--- scutil --proxy ---" + scutil --proxy 2>/dev/null || true + ;; + *) + usage + ;; +esac diff --git a/docs/connectivity/scripts/setup-ssh-key-for-proxy.ps1 b/docs/connectivity/scripts/setup-ssh-key-for-proxy.ps1 new file mode 100644 index 0000000..e194db8 --- /dev/null +++ b/docs/connectivity/scripts/setup-ssh-key-for-proxy.ps1 @@ -0,0 +1,97 @@ +# Setup SSH key for proxy tunneling only (ru.hunab.app / 149.154.64.19). +# Usage: run in PowerShell from repo root or pass path; no admin rights needed. +# The key is ONLY for SSH tunnels (-D SOCKS, -L). User ahau on server needs no extra rights. + +param( + [string]$SshDir = "$env:USERPROFILE\.ssh", + [string]$KeyName = "id_ed25519", + [string]$ProxyUser = "ahau", + [string]$ProxyHost = "149.154.64.19" +) + +$KeyPath = Join-Path $SshDir $KeyName +$KeyPubPath = "$KeyPath.pub" + +Write-Host "=== SSH key for proxy tunnel (Cursor/Telegram) ===" -ForegroundColor Cyan +Write-Host " Server: ${ProxyUser}@${ProxyHost} (ru.hunab.app)" +Write-Host " Purpose: tunneling only. No shell or other rights on server." +Write-Host "" + +# Ensure .ssh exists +if (-not (Test-Path $SshDir)) { + New-Item -ItemType Directory -Path $SshDir -Force | Out-Null + Write-Host "[OK] Created $SshDir" +} else { + Write-Host "[OK] Directory exists: $SshDir" +} + +# Check OpenSSH +try { + $null = Get-Command ssh -ErrorAction Stop +} catch { + Write-Host "[ERROR] OpenSSH client not found. Install: Settings -> Apps -> Optional features -> OpenSSH Client" -ForegroundColor Red + exit 1 +} + +if (-not (Test-Path $KeyPath)) { + Write-Host "" + Write-Host "No key found at $KeyPath" -ForegroundColor Yellow + Write-Host " Option A: Copy id_ed25519 (and id_ed25519.pub) from your Mac to $SshDir" + Write-Host " Option B: Generate a new key now (only for tunneling; add .pub to server once)" + $choice = Read-Host "Generate new key? (y/N)" + if ($choice -match '^[yY]') { + ssh-keygen -t ed25519 -f $KeyPath -N '""' + if (-not (Test-Path $KeyPath)) { Write-Host "Key creation failed." -ForegroundColor Red; exit 1 } + Write-Host "[OK] Key created: $KeyPath" + } else { + Write-Host "Copy id_ed25519 and id_ed25519.pub to $SshDir then run this script again." + exit 0 + } +} else { + Write-Host "[OK] Key exists: $KeyPath" +} + +# Show public key (user must add to server if new) +if (Test-Path $KeyPubPath) { + Write-Host "" + Write-Host "Public key (add one line to ~/.ssh/authorized_keys of ${ProxyUser}@${ProxyHost}):" -ForegroundColor Cyan + Write-Host (Get-Content $KeyPubPath -Raw).Trim() + Write-Host "" + Write-Host "On server (once): append the line above to /home/${ProxyUser}/.ssh/authorized_keys" + Write-Host "This key is only used for tunneling; no sudo or other rights needed for ahau." +} else { + Write-Host "[WARN] No $KeyPubPath found. Generate or copy from Mac." -ForegroundColor Yellow +} + +# Test connection +Write-Host "" +Write-Host "Testing connection to ${ProxyUser}@${ProxyHost} ..." +$testResult = & ssh -i $KeyPath -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${ProxyUser}@${ProxyHost}" "echo OK" 2>&1 +if ($LASTEXITCODE -eq 0 -and $testResult -eq "OK") { + Write-Host "[OK] SSH works. You can start the tunnel (cursor-socks-tunnel.ps1 start)." -ForegroundColor Green +} else { + Write-Host "[FAIL] SSH test failed. Ensure the public key is in authorized_keys on the server." -ForegroundColor Red + Write-Host $testResult + exit 1 +} + +# Optional: ensure config has hsites-ahau +$ConfigPath = Join-Path $SshDir "config" +$configEntry = @" +Host hsites-ahau + HostName $ProxyHost + User $ProxyUser + IdentityFile ~/.ssh/$KeyName + ServerAliveInterval 30 + ServerAliveCountMax 6 +"@ +if (-not (Test-Path $ConfigPath)) { + Set-Content -Path $ConfigPath -Value $configEntry.Trim() + Write-Host "[OK] Created $ConfigPath with Host hsites-ahau" +} elseif ((Get-Content $ConfigPath -Raw) -notmatch "hsites-ahau") { + Add-Content -Path $ConfigPath -Value "`n$configEntry" + Write-Host "[OK] Appended hsites-ahau to $ConfigPath" +} + +Write-Host "" +Write-Host "Done. Run: .\docs\cursor\scripts\cursor-socks-tunnel.ps1 start" -ForegroundColor Green diff --git a/docs/connectivity/scripts/ssh-hunab-prod.sh b/docs/connectivity/scripts/ssh-hunab-prod.sh new file mode 100755 index 0000000..ccaee3a --- /dev/null +++ b/docs/connectivity/scripts/ssh-hunab-prod.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# SSH к проду: по умолчанию в конфиге hunab-prod уже через hsites-ahau. +# Если прямой DO доступен — быстрее hunab-prod-direct (пробуем за 8 с). +# HUNAB_PROD_FORCE_VIA=1 — не пробовать прямой, сразу hunab-prod (через RU). +# HUNAB_PROD_FORCE_DIRECT=1 — только hunab-prod-direct. +# Использование: ./ssh-hunab-prod.sh [аргументы ssh] +set -e +KA=( -o ServerAliveInterval=30 -o ServerAliveCountMax=8 -o TCPKeepAlive=yes ) + +if [ "${HUNAB_PROD_FORCE_DIRECT:-0}" = "1" ]; then + exec ssh "${KA[@]}" hunab-prod-direct "$@" +fi + +if [ "${HUNAB_PROD_FORCE_VIA:-0}" != "1" ]; then + if ssh "${KA[@]}" -o BatchMode=yes -o ConnectTimeout=8 -o ConnectionAttempts=1 hunab-prod-direct "exit 0" 2>/dev/null; then + exec ssh "${KA[@]}" hunab-prod-direct "$@" + fi +fi +exec ssh "${KA[@]}" hunab-prod "$@" diff --git a/docs/connectivity/scripts/telegram-socks-tunnel.sh b/docs/connectivity/scripts/telegram-socks-tunnel.sh index b5f8503..eef7a29 100755 --- a/docs/connectivity/scripts/telegram-socks-tunnel.sh +++ b/docs/connectivity/scripts/telegram-socks-tunnel.sh @@ -1,26 +1,81 @@ #!/usr/bin/env bash # SOCKS5-туннель для Telegram (и др. приложений) через прокси 149.154.64.19. # Порт 1081 — отдельно от Cursor (10809). Использование: ./telegram-socks-tunnel.sh [start|stop|status] -# См. connectivity/PROXY_GUIDE.md +# См. connectivity/PROXY_GUIDE.md (в т.ч. цепочка через DigitalOcean: TELEGRAM_SOCKS_VIA_DO=1) set -e TELEGRAM_SOCKS_PORT="${TELEGRAM_SOCKS_PORT:-1081}" SSH_HOST="${TELEGRAM_TUNNEL_HOST:-ahau@149.154.64.19}" +TELEGRAM_SOCKS_VIA_DO="${TELEGRAM_SOCKS_VIA_DO:-0}" +TELEGRAM_SOCKS_JUMP_HOST="${TELEGRAM_SOCKS_JUMP_HOST:-hsites-ahau}" +TELEGRAM_SOCKS_FINAL_HOST="${TELEGRAM_SOCKS_FINAL_HOST:-hunab@209.38.32.21}" +TELEGRAM_SOCKS_FINAL_PORT="${TELEGRAM_SOCKS_FINAL_PORT:-2222}" +TELEGRAM_SOCKS_FINAL_IDENTITY="${TELEGRAM_SOCKS_FINAL_IDENTITY:-$HOME/.ssh/hunab_deploy_key}" +# Пусто = только loopback (по умолчанию в OpenSSH). 0.0.0.0 — слушать на всех интерфейсах (телефон → SOCKS на Mac по хотспоту). +TELEGRAM_SOCKS_BIND="${TELEGRAM_SOCKS_BIND:-}" +# SOCKS держит много соединений (Telegram); без повышения ulimit — «Too many open files». +TELEGRAM_SSH_STABLE_OPTS="-o TCPKeepAlive=yes -o IPQoS=throughput -o ServerAliveInterval=60 -o ServerAliveCountMax=4" + +raise_fd_limit() { + ulimit -n 65536 2>/dev/null || ulimit -n 10240 2>/dev/null || true +} + +socks_listening() { + lsof -i :"$TELEGRAM_SOCKS_PORT" -sTCP:LISTEN -t >/dev/null 2>&1 +} + +kill_listener_on_port() { + # PIDs listening on TELEGRAM_SOCKS_PORT (usually ssh -D; LaunchAgent may respawn — loop a few times) + local i=0 + while socks_listening && [ "$i" -lt 20 ]; do + lsof -ti :"$TELEGRAM_SOCKS_PORT" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true + sleep 0.35 + i=$((i + 1)) + done +} start() { - if lsof -i :"$TELEGRAM_SOCKS_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then - echo "SOCKS already listening on 127.0.0.1:$TELEGRAM_SOCKS_PORT" - return 0 + if socks_listening; then + if [ "$TELEGRAM_SOCKS_VIA_DO" = "1" ]; then + echo "Port $TELEGRAM_SOCKS_PORT busy — replacing with via-DO tunnel..." + kill_listener_on_port + if socks_listening; then + echo "Port $TELEGRAM_SOCKS_PORT still in use. If LaunchAgent keeps respawning the old tunnel, run:" + echo " launchctl unload \"\$HOME/Library/LaunchAgents/com.hunab.telegram-socks.plist\" 2>/dev/null" + echo " (or ./docs/connectivity/launchd/uninstall.sh) then: TELEGRAM_SOCKS_VIA_DO=1 $0 start" + return 1 + fi + else + echo "SOCKS already listening on port $TELEGRAM_SOCKS_PORT" + return 0 + fi + fi + raise_fd_limit + local d_spec="$TELEGRAM_SOCKS_PORT" + if [ -n "$TELEGRAM_SOCKS_BIND" ]; then + d_spec="${TELEGRAM_SOCKS_BIND}:${TELEGRAM_SOCKS_PORT}" + fi + if [ "$TELEGRAM_SOCKS_VIA_DO" = "1" ]; then + if ! ssh -o BatchMode=yes -o ConnectTimeout=6 -o ConnectionAttempts=1 hsites-ahau "exit 0" 2>/dev/null; then + echo "Ошибка: hsites-ahau недоступен. TELEGRAM_SOCKS_VIA_DO=1 — только Mac→RU→DO; прямой Mac→DO для SOCKS отключён." >&2 + exit 1 + fi + # shellcheck disable=SC2086 + ssh -D "$TELEGRAM_SOCKS_PORT" -f -N \ + $TELEGRAM_SSH_STABLE_OPTS \ + telegram-socks-via-do + echo "SOCKS started: 127.0.0.1:$TELEGRAM_SOCKS_PORT -> hsites-ahau -> DO (telegram-socks-via-do)" + else + # shellcheck disable=SC2086 + ssh -D "$TELEGRAM_SOCKS_PORT" -f -N \ + $TELEGRAM_SSH_STABLE_OPTS \ + "$SSH_HOST" + echo "SOCKS tunnel started: 127.0.0.1:$TELEGRAM_SOCKS_PORT -> $SSH_HOST" fi - ssh -D "$TELEGRAM_SOCKS_PORT" -f -N \ - -o ServerAliveInterval=30 \ - -o ServerAliveCountMax=6 \ - "$SSH_HOST" - echo "SOCKS tunnel started: 127.0.0.1:$TELEGRAM_SOCKS_PORT -> $SSH_HOST" } stop() { - pkill -f "ssh.*-D $TELEGRAM_SOCKS_PORT.*$SSH_HOST" 2>/dev/null || true + pkill -f "ssh.*-D $TELEGRAM_SOCKS_PORT" 2>/dev/null || true echo "Tunnel stopped (if it was running)." } @@ -39,7 +94,8 @@ case "${1:-start}" in status) status ;; *) echo "Usage: $0 {start|stop|status}" - echo "Env: TELEGRAM_SOCKS_PORT=$TELEGRAM_SOCKS_PORT, TELEGRAM_TUNNEL_HOST=$SSH_HOST" + echo "Env: TELEGRAM_SOCKS_PORT, TELEGRAM_TUNNEL_HOST (default exit: ru proxy)" + echo " TELEGRAM_SOCKS_VIA_DO=1 — только Mac→RU→DO (telegram-socks-via-do); без прямого DO" exit 1 ;; esac diff --git a/docs/connectivity/vpn/README.md b/docs/connectivity/vpn/README.md index e1abfbd..4e0e61e 100644 --- a/docs/connectivity/vpn/README.md +++ b/docs/connectivity/vpn/README.md @@ -9,6 +9,12 @@ --- +## SSH SOCKS: выход в интернет через DO без WireGuard / Shadowsocks + +Тот же сервер **209.38.32.21**. Для **Cursor** (10809) и **Telegram** (1081) можно обойтись без Docker-VPN: локальный SOCKS через SSH, снаружи — IP DO только по цепочке **Mac→RU→DO** (`telegram-socks-via-do`). Прямой Mac→DO для этих скриптов не используется. Быстрое переключение режимов: [SSH_EXIT_MODES.md](./SSH_EXIT_MODES.md), скрипты в **`scripts/vpn/`** (из корня репозитория: `./scripts/vpn/ssh-exit-mode.sh`). Подробности по IDE: [docs/cursor/README.md](../../cursor/README.md). + +--- + ## 🚀 Быстрый старт ### Shadowsocks (Рекомендуется для обхода блокировок) @@ -41,6 +47,8 @@ Method: chacha20-ietf-poly1305 ### Основные руководства +- **[SSH_EXIT_MODES.md](./SSH_EXIT_MODES.md)** — быстрое переключение: локальный IP / RU / Mac→RU→DO; скрипты: **`scripts/vpn/`** в корне репозитория (`./scripts/vpn/ssh-exit-mode.sh`) + - **[SHADOWSOCKS.md](./SHADOWSOCKS.md)** - Полное руководство по Shadowsocks - Развертывание сервера - Настройка клиентов (Android, macOS, CLI) @@ -69,6 +77,9 @@ Method: chacha20-ietf-poly1305 Все скрипты находятся в папке `scripts/`: +- **`scripts/vpn/ssh-exit-mode.sh`** — режимы SOCKS: `local` | `ru` | `ru-do` | `status` | `verify` (см. [SSH_EXIT_MODES.md](./SSH_EXIT_MODES.md)) +- **`scripts/vpn/exit-via-local.sh`**, **`exit-via-ru.sh`**, **`exit-via-ru-do.sh`** — короткие вызовы тех же режимов + - `scripts/toggle-shadowsocks-proxy.sh` - Переключение системного прокси - `scripts/check-proxy-status.sh` - Проверка статуса прокси - `scripts/fix-shadowsocks.sh` - Автоматическое исправление diff --git a/docs/connectivity/vpn/SSH_EXIT_MODES.md b/docs/connectivity/vpn/SSH_EXIT_MODES.md new file mode 100644 index 0000000..d5a055d --- /dev/null +++ b/docs/connectivity/vpn/SSH_EXIT_MODES.md @@ -0,0 +1,95 @@ +# SSH SOCKS: быстрое переключение выхода в интернет + +Три режима без WireGuard/Shadowsocks: **локальный IP**, **RU-прокси**, **RU → DO** (снаружи виден **IP прод-сервера DO**). + +Политика: для выхода с **IP DO** используется **только** цепочка **Mac → RU (`hsites-ahau`) → DO** (`telegram-socks-via-do`). Прямой Mac→DO скрипты не поднимают. + +--- + +## Ожидаемые IP (проверка «снаружи») + +| Режим | Как проверить | Ожидаемый публичный IP (на момент настройки репо) | +|--------|----------------|-----------------------------------------------------| +| **local** | `curl -sS https://ipinfo.io/ip` | Ваш домашний/мобильный IP | +| **ru** | `curl -sS -x socks5h://127.0.0.1:10809 https://ipinfo.io/ip` | **149.154.64.19** (RU VPS, `ru.hunab.app`) | +| **ru-do** | то же `-x socks5h://127.0.0.1:10809` | **209.38.32.21** (DO, `hunab.app`) | + +Если IP у VPS сменился — сверяйте с [PROXY_GUIDE.md](../PROXY_GUIDE.md) / [PROXY_EMERGENCY.md](../PROXY_EMERGENCY.md). + +Быстрая проверка одной командой (порт SOCKS по умолчанию **10809**): + +```bash +./scripts/vpn/ssh-exit-mode.sh verify +``` + +После **`ru-do`** в блоке «Через SOCKS» должно быть **209.38.32.21** (или актуальный IP DO). После **`ru`** — **149.154.64.19**. + +--- + +## Скрипты (из корня репозитория `local_machine`) + +**Один вход:** + +```bash +./scripts/vpn/ssh-exit-mode.sh COMMAND +``` + +**Короткие алиасы** (то же самое): + +```bash +./scripts/vpn/exit-via-local.sh # стоп туннелей +./scripts/vpn/exit-via-ru.sh # только RU +./scripts/vpn/exit-via-ru-do.sh # RU → DO (IP DO снаружи) +``` + +| Команда | Действие | +|---------|----------| +| **`local`** \| **`off`** \| **`stop`** | Гасит SSH SOCKS на портах **10809**, **1081**, **1082**. Дальше: в **Cursor** уберите `http.proxy` / `https.proxy` (или отключите системный SOCKS) — трафик пойдёт с **локального публичного IP**. | +| **`ru`** | Поднимает SOCKS **`127.0.0.1:10809`** → выход с **RU** (`hsites-ahau`). В Cursor: `socks5://127.0.0.1:10809`. | +| **`ru-do`** | Проверка RU (6 с), затем SOCKS на **10809** через **`telegram-socks-via-do`**. Снаружи — **IP DO**. В Cursor: тот же `socks5://127.0.0.1:10809`. | +| **`status`** | Кто слушает порты из списка остановки. | +| **`verify`** | IP без прокси и через SOCKS на `SSH_EXIT_MAIN_PORT`. | + +**Переменные:** `SSH_EXIT_MAIN_PORT` (по умолчанию **10809**), `SSH_EXIT_STOP_PORTS`, `SSH_EXIT_RU_HOST` (по умолчанию `hsites-ahau`). + +**`~/.ssh/config`:** `hsites-ahau`, **`telegram-socks-via-do`** с `ProxyJump hsites-ahau` — шаблоны в [PROXY_GUIDE.md](../PROXY_GUIDE.md). + +--- + +## Cursor за 30 секунд + +1. **`./scripts/vpn/exit-via-ru-do.sh`** (или `./scripts/vpn/ssh-exit-mode.sh ru-do`). +2. **`./scripts/vpn/ssh-exit-mode.sh verify`** — убедиться, что через SOCKS IP **DO**. +3. В Cursor: **Settings → proxy** → `socks5://127.0.0.1:10809` (и для HTTPS то же). **Disable Http2** — выкл. + +На **локальный IP:** `exit-via-local.sh`, затем в Cursor **убрать** proxy. + +--- + +## Telegram (порт 1081) + +Скрипт **`local`** останавливает и **1081**. Если нужен Telegram через DO при том же «полном стопе», после `local` поднимите туннель отдельно: [TELEGRAM_GUIDE.md](../TELEGRAM_GUIDE.md), `telegram-socks-tunnel.sh` / LaunchAgent. + +--- + +## Схема + +```mermaid +flowchart LR + subgraph off [local] + A[Приложения без SOCKS] --> NET1[Интернет] + end + subgraph r [ru] + B[SOCKS :10809] --> RU[hsites-ahau] + RU --> NET2[Интернет] + end + subgraph rd [ru-do] + C[SOCKS :10809] --> RU2[hsites-ahau] + RU2 --> DO[DO 209.38.32.21] + DO --> NET3[Интернет] + end +``` + +--- + +*Обновлено: 2026-04-08* diff --git a/docs/disk_cleanup/DISK_CLEANUP.md b/docs/disk_cleanup/DISK_CLEANUP.md index e8bfa86..416c216 100644 --- a/docs/disk_cleanup/DISK_CLEANUP.md +++ b/docs/disk_cleanup/DISK_CLEANUP.md @@ -1,7 +1,7 @@ # 🧹 Очистка диска Mac — сводный гайд Один документ: что не трогать, что можно удалять, быстрые команды, скрипты. -**Скрипт:** [cleanup-disk-now.sh](./cleanup-disk-now.sh) · **Спец. фикс:** [ANYTYPE_WORKSPACE_FIX.md](./ANYTYPE_WORKSPACE_FIX.md) · **История:** [CHANGELOG.md](./CHANGELOG.md) +**Запрещено удалять:** [DO_NOT_DELETE.md](./DO_NOT_DELETE.md) · **Скрипт:** [cleanup-disk-now.sh](./cleanup-disk-now.sh) · **Спец. фикс:** [ANYTYPE_WORKSPACE_FIX.md](./ANYTYPE_WORKSPACE_FIX.md) · **История:** [CHANGELOG.md](./CHANGELOG.md) --- @@ -20,8 +20,13 @@ ## ❌ Что НЕ удалять +**Полный белый список:** [DO_NOT_DELETE.md](./DO_NOT_DELETE.md) + +Краткая выжимка: + | Категория | Пути | Причина | |-----------|------|---------| +| **Thunderbird** | `~/Library/Thunderbird/`, `Profiles/*/Mail/*/Inbox` | Почта, не кэш | | Brave | `~/Library/Application Support/BraveSoftware`, `~/Library/Caches/BraveSoftware` | Настройки, закладки, история | | Cursor History | `~/Library/Application Support/Cursor/User/History` | История изменений файлов | | Cursor workspaceStorage | `~/Library/Application Support/Cursor/User/workspaceStorage` | Настройки workspace | @@ -64,7 +69,10 @@ |-----|------|----------| | ShipIt (Cursor) | `~/Library/Caches/com.todesktop.230313mzl4w4u92.ShipIt` | 768 MB | | anytype-updater | `~/Library/Caches/anytype-updater` | 453 MB | -| com.brave.Browser | `~/Library/Caches/com.brave.Browser` | 423 MB | +| ms-playwright | `~/Library/Caches/ms-playwright` | ~800 MB | +| responsivelyapp-updater | `~/Library/Caches/responsivelyapp-updater` | ~100 MB | +| ResponsivelyApp | `~/Library/Application Support/ResponsivelyApp` | ~90 MB | +| GeoServices | `~/Library/Caches/GeoServices` | ~45 MB | | Firefox | `~/Library/Caches/Firefox` | 83 MB | | Google | `~/Library/Caches/Google` | — | | Homebrew | `~/Library/Caches/Homebrew` | — | @@ -136,7 +144,8 @@ rm -f "$HOME/Library/Application Support/Cursor/User/globalStorage/state.vscdb.b # Library/Caches (не Brave) rm -rf "$HOME/Library/Caches/com.todesktop.230313mzl4w4u92.ShipIt" rm -rf "$HOME/Library/Caches/anytype-updater" -rm -rf "$HOME/Library/Caches/com.brave.Browser" +rm -rf "$HOME/Library/Caches/ms-playwright" +rm -rf "$HOME/Library/Caches/responsivelyapp-updater" rm -rf "$HOME/Library/Caches/Firefox" rm -rf "$HOME/Library/Caches/Google" rm -rf "$HOME/Library/Caches/Homebrew" @@ -157,7 +166,7 @@ find "$HOME/Library/Logs" -type f -size 0 -delete 2>/dev/null ## 🔄 Регулярность очистки - **Еженедельно:** build-артефакты, пустые логи. -- **Ежемесячно:** кэши разработки (npm, node-gyp, TypeScript, pip, .yarn), Library/Caches (Google, Firefox, ShipIt, anytype-updater), Cursor WebStorage/Partitions/projects. +- **Ежемесячно:** кэши разработки (npm, node-gyp, TypeScript, pip, .yarn), Library/Caches (Google, Firefox, ShipIt, anytype-updater, ms-playwright, responsivelyapp-updater), Cursor WebStorage/Partitions/projects, уровень 1 из cleanup-disk-now.sh. - **При заполнении диска >80%:** всё из ежемесячного + Colima (если не нужен), state.vscdb.backup, приложения (Notion, zoom, LibreOffice, Movavi, Ledger), записи экрана. --- @@ -172,7 +181,8 @@ find "$HOME/Library/Logs" -type f -size 0 -delete 2>/dev/null | Файл | Назначение | |------|------------| -| [cleanup-disk-now.sh](./cleanup-disk-now.sh) | Интерактивная безопасная очистка (согласовано с DO_NOT_DELETE) | +| [DO_NOT_DELETE.md](./DO_NOT_DELETE.md) | **Запрещено удалять** — белый список | +| [cleanup-disk-now.sh](./cleanup-disk-now.sh) | Интерактивная безопасная очистка (см. DO_NOT_DELETE.md) | | [scripts/scan-cleanup-items.sh](./scripts/scan-cleanup-items.sh) | Сканирование: что можно удалить и размеры | | [scripts/compress-screenrecordings.sh](./scripts/compress-screenrecordings.sh) | Сжатие записей экрана | | [scripts/quick-cleanup-asahi.sh](./scripts/quick-cleanup-asahi.sh) | Быстрая очистка под Asahi | diff --git a/docs/disk_cleanup/DO_NOT_DELETE.md b/docs/disk_cleanup/DO_NOT_DELETE.md new file mode 100644 index 0000000..8ab0fad --- /dev/null +++ b/docs/disk_cleanup/DO_NOT_DELETE.md @@ -0,0 +1,204 @@ +# ⛔ Запрещено удалять при очистке диска + +**Источник истины** для [cleanup-disk-now.sh](./cleanup-disk-now.sh), [scan-cleanup-items.sh](./scripts/scan-cleanup-items.sh) и любых ручных `rm`. + +Сводный гайд по безопасной очистке: [DISK_CLEANUP.md](./DISK_CLEANUP.md). + +--- + +## Правила + +1. **Не удалять целиком** пути из таблиц ниже — только явно разрешённые подпути из [DISK_CLEANUP.md](./DISK_CLEANUP.md). +2. Скрипты очистки **обязаны** сверяться с этим файлом перед добавлением новых `rm`. +3. При сомнении — **не удалять**; перенести на внешний диск ([scan-move-candidates.sh](./scripts/scan-move-candidates.sh)). +4. **Фото и видео** (личные медиа) — отдельная политика: не трогать без явного решения пользователя (см. раздел «Медиа»). + +--- + +## Почта — Thunderbird + +| Путь | Ориентир | Причина | +|------|----------|---------| +| `~/Library/Thunderbird/` | ~2 GB | Профили, настройки, локальная почта | +| `~/Library/Thunderbird/Profiles/*/Mail/` | — | Все папки ящиков (Inbox, Sent, …) | +| `~/Library/Thunderbird/Profiles/*/Mail/*/Inbox` | **~470 MB+** | **Почта, не кэш** — удаление = потеря писем | +| `~/Library/Thunderbird/Profiles/*/ImapMail/` | — | Локальные копии IMAP | +| `~/Library/Thunderbird/Profiles/*/global-messages-db.sqlite` | — | Индекс сообщений | + +**Разрешено (осторожно):** только кэши обновлений Thunderbird в `~/Library/Caches/`, если появятся — **не** трогать `Application Support` и `Profiles`. + +--- + +## Браузеры + +| Путь | Причина | +|------|---------| +| `~/Library/Application Support/BraveSoftware` | Закладки, история, настройки | +| `~/Library/Caches/BraveSoftware` | Связан с Brave (не путать с `com.brave.Browser` без проверки) | + +Firefox / Chrome: не удалять `Application Support` целиком; кэши в `~/Library/Caches/` — по [DISK_CLEANUP.md](./DISK_CLEANUP.md). + +--- + +## Cursor / VS Code + +| Путь | Причина | +|------|---------| +| `~/Library/Application Support/Cursor/User/History` | История изменений файлов | +| `~/Library/Application Support/Cursor/User/workspaceStorage` | Настройки workspace | +| `~/.cursor/extensions` | Расширения (долгая переустановка) | +| `~/Library/Application Support/Cursor/logs` | Логи; удаление ухудшает отладку | +| `~/Library/Application Support/Cursor/CachedData` | Кэш редактора | +| `~/.cursor/ai-tracking` | Трекинг AI | +| `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb` | Активная БД состояния (**опционально** чистят вручную при нехватке места — см. DISK_CLEANUP) | + +**Разрешено:** `state.vscdb.backup`, `User/temp/` (устаревшие копии), `WebStorage`, `Partitions`, `~/.cursor/projects`, ShipIt в Caches. + +--- + +## Мессенджеры и заметки + +| Путь | Причина | +|------|---------| +| `~/Library/Application Support/Telegram Desktop` | Данные и кэш Telegram | +| `~/Library/Application Support/anytype` (целиком) | Workspace'ы Anytype — см. [ANYTYPE_WORKSPACE_FIX.md](./ANYTYPE_WORKSPACE_FIX.md) | +| `~/Library/Application Support/direct-commander` | Настройки файлового менеджера | + +--- + +## Разработка — Android / Node + +| Путь | Причина | +|------|---------| +| `~/Library/Android/sdk` | Android SDK | +| `~/.gradle` | Кэш Gradle | +| `~/code/hunabapp-dev/frontend/android/app/build` | Артефакты сборки frontend Android | +| `~/code/hunabapp-dev/frontend/node_modules` | Зависимости frontend | +| `~/Library/pnpm` | Store pnpm (~2 GB, долгая перекачка) | +| `~/Library/Caches/pnpm` | Кэш pnpm | + +**Опционально (не в автоочистке):** `~/.android/avd/` — эмуляторы; удалять только если не нужны. + +--- + +## Проекты и бэкапы в `~/code` + +| Путь | Причина | +|------|---------| +| `~/code/bots/backup_*` | Бэкапы ботов | +| `~/code/bots/archives_*` | Архивы ботов | +| `~/code/methatron/pics/tablecloth/1-7.zip` | Архив проекта | +| `~/code/methatron/.system/db.mysql.persicia` | БД MySQL проекта (~1.7 GB) | +| `~/code/methatron/.system/db.mysql.*` | Прочие БД methatron | +| Архивы в `~/code/legal/` (zip/rar по делам) | Юридические материалы | +| `~/code/legal/legal_education/` | Рабочие материалы (в т.ч. видео курсов) | + +**Не удалять автоматически:** `~/code/archive/` — проверять вручную. + +--- + +## Медиа (фото / видео) + +| Путь | Причина | +|------|---------| +| `~/Pictures/` | Фото и альбомы | +| `~/Videos/` | Видео | +| `~/Movies/` | Фильмы / iMovie | +| `~/Music/` | Музыка | +| `~/Library/Photos/` | Библиотека Фото (системная) | +| `~/Library/ScreenRecordings/` | Записи экрана — только по явному согласию | +| Видео в `~/code/legal/`, `~/MEGA/`, `~/gdrive*`, `~/g.drive mikhevel/` | Не считать «кэшем» | + +--- + +## Облака и синхронизация + +| Путь | Причина | +|------|---------| +| `~/MEGA/` | Локальная копия MEGA | +| `~/gdrive.hoonabku`, `~/gdrive.sendusletters`, `~/g.drive mikhevel/` | Google Drive / локальные зеркала | +| `~/My Drive (*@gmail.com)` | Google Drive File Stream | + +--- + +## Игры и Wine + +| Путь | Причина | +|------|---------| +| `~/Library/Application Support/Steam` | Данные Steam | +| `~/Library/Containers/com.isaacmarovitz.Whisky/` | Bottles Whisky (игры, Wine) | +| `~/Library/Application Support/com.isaacmarovitz.Whisky` | Данные Whisky | + +--- + +## Git-бэкапы hunabapp-dev (Google Drive) + +Каталог: `~/gdrive.hoonabku/h.app_bac/` (~900 MB+). + +**Политика:** при очистке диска **всегда сохранять последний git bundle** и его checksum. При появлении нового бэкапа — **обновить таблицу ниже**; более старые bundle в этом каталоге можно удалять вручную только после проверки, что новый бэкап на месте и в Google Drive. + +| Файл | Размер (ориентир) | Дата | Статус | +|------|-------------------|------|--------| +| `hunabapp-dev_git_20260524_235507.bundle` | 463 MB | 2026-05-24 | **Последний — не удалять** | +| `hunabapp-dev_20260524_235507.sha256` | 208 B | 2026-05-24 | **Пара к bundle — не удалять** | + +Также **не удалять без явного решения:** + +| Файл | Причина | +|------|---------| +| `~/gdrive.hoonabku/h.app_bac/.env.development` | Секреты окружения | +| `~/gdrive.hoonabku/h.app_bac/.env.production` | Секреты окружения | + +**Можно удалить после появления более нового bundle (проверить вручную):** + +| Файл | Размер (ориентир) | Дата | +|------|-------------------|------| +| `~/gdrive.hoonabku/h.app_bac/.tar.gz` | 449 MB | 2026-03-10 | + +--- + +## Git (не удалять, только оптимизировать) + +| Путь | Действие | +|------|----------| +| `~/code/*/.git/` | `git gc --aggressive` — сжатие, **не** `rm -rf` | +| Крупные pack-файлы в `.git/objects/pack/` | Часть истории репозитория | + +--- + +## Системное + +| Путь | Причина | +|------|---------| +| `~/Library/Keychains/` | Пароли | +| `~/Library/Application Support/com.apple.sharedfilelist/` | Недавние файлы | +| `~/Library/Mobile Documents/` | iCloud Drive | +| Снимки Time Machine (локальные) | `tmutil listlocalsnapshots` — не удалять вручную без понимания | + +--- + +## Шаблон для скриптов (bash) + +```bash +# Пример: пропуск защищённого пути +case "$path" in + *"/Thunderbird"*|*"/Thunderbird/"*) return 0 ;; + *"/BraveSoftware"*) return 0 ;; + *"/Cursor/User/History"*) return 0 ;; + *"/anytype"*) return 0 ;; + *"/gdrive.hoonabku/h.app_bac/"*) return 0 ;; + *"/h.app_bac/"*) return 0 ;; +esac +``` + +--- + +## Связанные файлы + +| Файл | Назначение | +|------|------------| +| [DISK_CLEANUP.md](./DISK_CLEANUP.md) | Что **можно** удалять | +| [cleanup-disk-now.sh](./cleanup-disk-now.sh) | Интерактивная очистка | +| [scripts/scan-cleanup-items.sh](./scripts/scan-cleanup-items.sh) | Сканирование кандидатов | + +**Последнее обновление:** 2026-05-27 — git-бэкап hunabapp-dev в `h.app_bac/` (последний bundle 2026-05-24). diff --git a/docs/disk_cleanup/cleanup-disk-now.sh b/docs/disk_cleanup/cleanup-disk-now.sh index 57b58d6..3afd52a 100755 --- a/docs/disk_cleanup/cleanup-disk-now.sh +++ b/docs/disk_cleanup/cleanup-disk-now.sh @@ -1,11 +1,11 @@ #!/bin/bash # Безопасная очистка диска — по списку из docs/disk_cleanup/DISK_CLEANUP.md. -# НЕ трогает: Brave, Cursor History/workspaceStorage/extensions, Telegram, pnpm, anytype. +# Запрещено удалять: docs/disk_cleanup/DO_NOT_DELETE.md set -e -echo "🧹 Безопасная очистка диска (согласовано с DO_NOT_DELETE)" +echo "🧹 Безопасная очистка диска (см. DO_NOT_DELETE.md)" echo "==========================================================" echo "" @@ -34,6 +34,7 @@ cleanup_dir "$HOME/Library/Caches/node-gyp" "node-gyp кэш" cleanup_dir "$HOME/Library/Caches/typescript" "TypeScript кэш" cleanup_dir "$HOME/Library/Caches/pip" "pip кэш" cleanup_dir "$HOME/.yarn" ".yarn кэш" +cleanup_dir "$HOME/Library/Caches/ms-playwright" "Playwright кэш (браузеры для тестов)" echo "" # 2. Windsurf полностью (кэш + данные; приложение — удалить вручную из Программы) @@ -49,7 +50,34 @@ echo "" echo "=== 3. Кэши браузеров (не Brave) ===" cleanup_dir "$HOME/Library/Caches/Google" "Google кэш" cleanup_dir "$HOME/Library/Caches/Firefox" "Firefox кэш" -cleanup_dir "$HOME/Library/Caches/com.brave.Browser" "com.brave.Browser кэш" +echo "" + +# 3b. Уровень 1 — лёгкие кэши и временные артефакты (~0.5 GB) +echo "=== 3b. Уровень 1 (кэши приложений, Apple, hunabapp temp) ===" +cleanup_dir "$HOME/Library/Caches/responsivelyapp-updater" "responsivelyapp-updater кэш" +cleanup_dir "$HOME/Library/Application Support/ResponsivelyApp" "ResponsivelyApp кэш" +cleanup_dir "$HOME/Library/Caches/GeoServices" "GeoServices кэш" +cleanup_dir "$HOME/Library/Caches/com.apple.helpd" "com.apple.helpd кэш" +cleanup_dir "$HOME/Library/Caches/com.apple.CloudTelemetry" "com.apple.CloudTelemetry кэш" +cleanup_dir "$HOME/Library/Caches/com.apple.ctcategories.service" "com.apple.ctcategories.service кэш" +HUNAB_TEMP="$HOME/code/hunabapp-dev/temp" +if [ -d "$HUNAB_TEMP" ]; then + find "$HUNAB_TEMP" -maxdepth 1 -type f \( -name 'hunab-backend-bundle-*.tar.gz' -o -name 'hunab-backend-logs-*.tar.gz' \) -print0 2>/dev/null | while IFS= read -r -d '' f; do + size=$(du -sh "$f" 2>/dev/null | cut -f1) + echo "🗑️ Удаление hunabapp temp: $(basename "$f") ($size)..." + rm -f "$f" && echo " ✅ удалён" || echo " ⚠️ не удалось" + done +fi +echo " Build/dist в ~/code (кроме frontend/android)..." +find "$HOME/code" -type d \( -name "build" -o -name "dist" -o -name ".next" -o -name "out" \) \ + -not -path "*/node_modules/*" \ + -not -path "*/venv/*" \ + -not -path "*/.venv/*" \ + -not -path "*/site-packages/*" \ + -not -path "*/env/*" \ + -not -path "*/hunabapp-dev/frontend/android/*" \ + -exec rm -rf {} + 2>/dev/null || true +echo " ✅ Build-артефакты уровня 1 очищены" echo "" # 4. Приложения — Notion, zoom, LibreOffice, Movavi, Ledger (Telegram в DO_NOT_DELETE) @@ -83,6 +111,7 @@ echo "" # 6. Homebrew, .cache, пустые логи echo "=== 6. Homebrew, .cache, пустые логи ===" +cleanup_dir "$HOME/Library/Caches/anytype-updater" "anytype-updater кэш" cleanup_dir "$HOME/Library/Caches/Homebrew" "Homebrew кэш" cleanup_dir "$HOME/.cache" ".cache" find "$HOME/Library/Logs" -type f -size 0 -delete 2>/dev/null && echo " ✅ Пустые логи удалены" || true @@ -162,6 +191,7 @@ echo "📊 Состояние диска после очистки:" df -h / | tail -1 echo "" echo "✅ Очистка завершена. Перезапустите приложения при необходимости." -echo " Список: docs/disk_cleanup/DISK_CLEANUP.md" +echo " Разрешено: docs/disk_cleanup/DISK_CLEANUP.md" +echo " Запрещено: docs/disk_cleanup/DO_NOT_DELETE.md" echo "" diff --git a/docs/disk_cleanup/scripts/scan-cleanup-items.sh b/docs/disk_cleanup/scripts/scan-cleanup-items.sh index d1b4c8d..ecd8108 100755 --- a/docs/disk_cleanup/scripts/scan-cleanup-items.sh +++ b/docs/disk_cleanup/scripts/scan-cleanup-items.sh @@ -33,7 +33,14 @@ scan_dir() { if [ "$exclude_protected" = "true" ]; then # Проверяем, не является ли это защищенным элементом case "$path" in + *"/Thunderbird"*) return ;; + *"/Pictures"*) return ;; + *"/Videos"*) return ;; *"/BraveSoftware"*) return ;; + *"/Steam"*) return ;; + *"/Whisky"*) return ;; + *"/gdrive.hoonabku/h.app_bac"*) return ;; + *"/h.app_bac/"*) return ;; *"/Cursor/User/History"*) return ;; *"/Cursor/User/workspaceStorage"*) return ;; *"/Cursor/extensions"*) return ;; @@ -120,6 +127,9 @@ scan_dir "$HOME/.yarn" ".yarn кэш" true scan_dir "$HOME/.colima" "Colima кэш" true scan_dir "$HOME/Library/Caches/colima" "Colima кэш (Library)" true +# Playwright кэш (браузеры для e2e-тестов) +scan_dir "$HOME/Library/Caches/ms-playwright" "Playwright кэш" true + # Codeium/Windsurf кэши scan_dir "$HOME/.codeium/windsurf" "Codeium/Windsurf кэш" true @@ -135,7 +145,7 @@ find ~/code -type d -name "node_modules" \ -not -path "*/hunabapp-dev/frontend/node_modules" \ -exec du -sh {} \; 2>/dev/null | sort -hr | head -5 | while read size path; do printf " %-60s %8s\n" "$path" "$size" - local size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) + size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) if [ -n "$size_mb" ]; then TOTAL_SIZE=$((TOTAL_SIZE + size_mb)) ((ITEMS_FOUND++)) @@ -156,7 +166,7 @@ find ~/code -type d \( -name "build" -o -name "dist" -o -name ".next" -o -name " -not -path "*/hunabapp-dev/frontend/android/app/build*" \ -exec du -sh {} \; 2>/dev/null | sort -hr | head -10 | while read size path; do printf " %-60s %8s\n" "$path" "$size" - local size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) + size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) if [ -n "$size_mb" ]; then TOTAL_SIZE=$((TOTAL_SIZE + size_mb)) ((ITEMS_FOUND++)) @@ -200,6 +210,7 @@ scan_dir "$HOME/Library/Application Support/Syncthing" "Syncthing кэш" true # ResponsivelyApp scan_dir "$HOME/Library/Application Support/ResponsivelyApp" "ResponsivelyApp кэш" true +scan_dir "$HOME/Library/Caches/responsivelyapp-updater" "responsivelyapp-updater кэш" true echo "" echo -e "${CYAN}=== CURSOR КЭШИ (кроме защищенных) ===${NC}" @@ -219,7 +230,7 @@ scan_dir "$HOME/Library/Application Support/Cursor/CachedExtensionVSIXs" "Cursor # Cursor logs (старые, если есть) if [ -d "$HOME/Library/Application Support/Cursor/logs" ]; then - local logs_size=$(du -sm "$HOME/Library/Application Support/Cursor/logs" 2>/dev/null | cut -f1) + logs_size=$(du -sm "$HOME/Library/Application Support/Cursor/logs" 2>/dev/null | cut -f1) if [ -n "$logs_size" ] && [ "$logs_size" -gt 10 ]; then printf " %-60s %8sMB\n" "Cursor logs (старые)" "$logs_size" TOTAL_SIZE=$((TOTAL_SIZE + logs_size)) @@ -237,6 +248,9 @@ echo "" # Homebrew кэш scan_dir "$HOME/Library/Caches/Homebrew" "Homebrew кэш" true +# anytype-updater кэш +scan_dir "$HOME/Library/Caches/anytype-updater" "anytype-updater кэш" true + # GeoServices кэш scan_dir "$HOME/Library/Caches/GeoServices" "GeoServices кэш" true @@ -266,8 +280,8 @@ echo "" scan_dir "$HOME/Library/Logs/Google" "Логи Google" true # Пустые логи -local empty_logs=$(find ~/Library/Logs -type f -size 0 2>/dev/null | wc -l | tr -d ' ') -if [ "$empty_logs" -gt 0 ]; then +empty_logs=$(find ~/Library/Logs -type f -size 0 2>/dev/null | wc -l | tr -d ' ') +if [ -n "$empty_logs" ] && [ "$empty_logs" -gt 0 ] 2>/dev/null; then printf " %-60s %8s файлов\n" "Пустые логи" "$empty_logs" fi @@ -280,7 +294,7 @@ find ~/code -type d -name "__pycache__" \ -path "*/venv/*" -o -path "*/.venv/*" -o -path "*/env/*" \ -exec du -sh {} \; 2>/dev/null | sort -hr | head -10 | while read size path; do printf " %-60s %8s\n" "$path" "$size" - local size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) + size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) if [ -n "$size_mb" ]; then TOTAL_SIZE=$((TOTAL_SIZE + size_mb)) ((ITEMS_FOUND++)) @@ -306,7 +320,7 @@ find ~/code -type f \( -name "*.zip" -o -name "*.rar" -o -name "*.tar.gz" -o -na -size +10M \ -exec du -sh {} \; 2>/dev/null | sort -hr | head -10 | while read size path; do printf " %-60s %8s\n" "$path" "$size" - local size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) + size_mb=$(du -sm "$path" 2>/dev/null | cut -f1) if [ -n "$size_mb" ]; then TOTAL_SIZE=$((TOTAL_SIZE + size_mb)) ((ITEMS_FOUND++)) @@ -335,7 +349,7 @@ echo -e "Найдено элементов для очистки: ${YELLOW}$ITEM echo "" if [ "$TOTAL_SIZE" -gt 0 ]; then - local total_gb=$(echo "scale=2; $TOTAL_SIZE / 1024" | bc 2>/dev/null || echo "0") + total_gb=$(echo "scale=2; $TOTAL_SIZE / 1024" | bc 2>/dev/null || echo "0") echo -e "Общий размер: ${GREEN}${TOTAL_SIZE}MB (~${total_gb}GB)${NC}" else echo -e "Общий размер: ${GREEN}0MB${NC}" @@ -343,7 +357,7 @@ fi echo "" echo -e "${YELLOW}⚠️ ВАЖНО:${NC}" -echo " - Защищённые элементы (см. DISK_CLEANUP.md) исключены из сканирования" +echo " - Защищённые элементы: docs/disk_cleanup/DO_NOT_DELETE.md" echo " - Проверьте список перед удалением" echo " - Некоторые элементы могут быть пересозданы автоматически" echo "" diff --git a/docs/disk_cleanup/videos/LOSSLESS_VIDEO_COMPRESSION_GUIDE.md b/docs/disk_cleanup/videos/LOSSLESS_VIDEO_COMPRESSION_GUIDE.md new file mode 100644 index 0000000..e7ca700 --- /dev/null +++ b/docs/disk_cleanup/videos/LOSSLESS_VIDEO_COMPRESSION_GUIDE.md @@ -0,0 +1,128 @@ +# Сжатие видео: lossless и почти lossless + +Гайд для `ffmpeg` с акцентом на две задачи: +- **строго без потерь** (математический lossless); +- **существенно меньше размер** с визуально очень близким качеством. + +## Ключевое правило + +Нельзя одновременно гарантировать: +1) **существенно меньший размер**, +2) **строго нулевые потери**. + +Для реально меньших файлов обычно используют режим "почти lossless" (visually lossless). + +## Быстрый выбор режима + +- Нужен архив "бит-в-бит по кадрам" -> `x265 lossless` или `x264 lossless`. +- Нужен заметно меньший размер и хорошая совместимость (QuickTime, мессенджеры) -> `x264 CRF 18-20`. + +--- + +## 1) Проверка исходника + +```bash +ffprobe -v error -show_entries format=filename,format_name,duration,size,bit_rate -of default=nw=1:nk=0 "input.mov" +``` + +## 2) Строгий lossless (HEVC / x265) + +```bash +ffmpeg -i "input.mov" \ + -map 0 \ + -c:v libx265 -x265-params lossless=1 \ + -preset medium \ + -c:a copy \ + -movflags +faststart \ + "output_lossless_x265.mp4" +``` + +Примечания: +- `lossless=1` — математически без потерь; +- `-c:a copy` — аудио без перекодирования; +- размер может стать больше исходника. + +## 3) Строгий lossless (H.264, совместимее) + +```bash +ffmpeg -i "input.mov" \ + -map 0 \ + -c:v libx264 -crf 0 -preset medium \ + -pix_fmt yuv420p \ + -c:a copy \ + -movflags +faststart \ + "output_lossless_x264.mp4" +``` + +## 4) Практичный режим: существенно меньше, визуально очень близко + +Рекомендуемый профиль для QuickTime: + +```bash +ffmpeg -i "input.mov" \ + -map 0 \ + -c:v libx264 -preset slow -crf 20 \ + -profile:v high -pix_fmt yuv420p \ + -c:a copy \ + -movflags +faststart \ + "output_smaller.mp4" +``` + +Подстройка качества: +- `crf 18` — выше качество, больше размер; +- `crf 20` — хороший баланс; +- `crf 22` — меньше размер, больше риск артефактов. + +## 5) Как проверить, что lossless действительно lossless + +```bash +ffmpeg -i "input.mov" -map 0:v:0 -f framemd5 "input.framemd5" +ffmpeg -i "output_lossless_x265.mp4" -map 0:v:0 -f framemd5 "output.framemd5" +diff "input.framemd5" "output.framemd5" +``` + +Если `diff` пустой — кадры эквивалентны после декодирования. + +## 6) Batch-команда для папки (`.mov`) + +### 6.1 Строгий lossless + +```bash +for f in *.mov; do + ffmpeg -i "$f" \ + -map 0 \ + -c:v libx265 -x265-params lossless=1 \ + -preset medium \ + -c:a copy \ + -movflags +faststart \ + "${f%.mov}_lossless.mp4" +done +``` + +### 6.2 Существенно меньше, но визуально близко + +```bash +for f in *.mov; do + ffmpeg -i "$f" \ + -map 0 \ + -c:v libx264 -preset slow -crf 20 \ + -profile:v high -pix_fmt yuv420p \ + -c:a copy \ + -movflags +faststart \ + "${f%.mov}_smaller.mp4" +done +``` + +## 7) Проблемы с открытием файла + +Если файл не открывается в QuickTime или мессенджере: + +1. Проверь контейнер: +```bash +ffprobe -v error "video.mp4" +``` +2. Перемести метаданные в начало файла: +```bash +ffmpeg -i "video.mp4" -c copy -movflags +faststart "video_fixed.mp4" +``` +3. Если кодек не поддерживается, перекодируй в H.264 (`libx264`) с `yuv420p`. diff --git a/docs/games/GOG_GALAXY_7H_RCA.md b/docs/games/GOG_GALAXY_7H_RCA.md new file mode 100644 index 0000000..108b8e8 --- /dev/null +++ b/docs/games/GOG_GALAXY_7H_RCA.md @@ -0,0 +1,117 @@ +# GOG Galaxy — RCA за ~7 часов (2026-05-28) + +Bottle: **GOG-Install** (`9ee55e7c-a58e-411e-a93f-30530d4f1073`). + +## Swap / подвисание Mac + +Запуски Galaxy (CEF + несколько `GalaxyClient Helper` + MoltenVK в foreground) **грузят RAM и swap**. При `used swap` > ~400MB скрипты **блокируют** новый запуск. + +```bash +bash ~/code/local_machine/scripts/games/gog/emergency-stop.sh +# перезагрузка Mac — если система всё ещё тяжёлая +``` + +**Не запускать подряд:** `launch-galaxy.sh`, `launch-galaxy-ui.sh`, Whisky pin — без `emergency-stop` между попытками. + +## Статус (честно) + +| Что проверено | Результат | +|---------------|-----------| +| `GalaxyClient.exe` в процессах после `launch-galaxy.sh` | **Да** (многократно) | +| `GalaxyInitialization.log`: `will continue` | **Да** | +| `GalaxyClient.log`: `Showing login form` | **Да** | +| `GalaxyClient.log`: `Successfully brought window to the foreground` | **Да** | +| **Окно видно пользователю на macOS** | **Нет** (не подтверждено) | + +**Вывод:** клиент **запускается**, login UI **в логах есть**; проблема — **отрисовка/привязка окна CEF в Wine на Mac**, не «бинарника нет». + +Собрать свежие логи: + +```bash +bash ~/code/local_machine/scripts/games/gog/collect-gog-logs-7h.sh +``` + +## Хронология (по логам bottle, MSK) + +| Время (≈) | Событие | Значение | +|-------------|---------|----------| +| 04:56 | `Second client instance` → `will exit` | повторный launch без `reset.sh` | +| 05:08 | то же | lock / второй экземпляр | +| 05:15–05:18 | `will continue`, login form, foreground | **рабочий запуск процесса** | +| 05:19–05:20 | `DeelevateStrategy` fatal, exit **-31** | запуск **без** `/deelevated` (vdesktop) | +| 05:21+ | `/deelevated` в Init, login form | фикс в `config.sh` | +| 05:29–05:35 | login + foreground (vdesktop) | процесс OK, окно на Mac — нет | +| 06:53 | Init: `/deelevated /runWithoutUpdating`, login form | последний автотест | + +## Типичные строки в логах + +**Успех процесса:** + +``` +Initialization strategy 'InitClientStrategy' ... The client will continue. +No refresh token found. Showing login form. +Successfully brought window to the foreground. +``` + +**Падение (старый антипаттерн):** + +``` +Something went wrong during DeelevateStrategy. +... exit code -31 (Privilege de-elevation is needed.). The client will exit. +``` + +**Окно на Mac (CEF):** + +``` +Received message from source not matching parent window. +eglInitialize: No available renderers. +``` + +## JA3 без Galaxy UI (рекомендуется на этом Mac) + +Galaxy login в Wine **не показывает окно** → ставить игру через **offline installer** в браузере: + +1. https://www.gog.com/ → Library → Jagged Alliance 3 → **Download offline backup** +2. На Mac: + ```bash + bash ~/code/local_machine/scripts/games/gog/emergency-stop.sh + bash ~/code/local_machine/scripts/games/gog/install-ja3-offline.sh ~/Downloads/setup_jagged_alliance_3*.exe + ``` + +## Канон запуска Galaxy (best effort, может не быть окна) + +1. **Whisky GUI (предпочтительно для окна):** + +```bash +bash ~/code/local_machine/scripts/games/gog/launch-galaxy-whisky.sh +# → pin «gog-galaxy» → Run +``` + +2. **Терминал (процесс + логин в логе):** + +```bash +bash ~/code/local_machine/scripts/games/gog/reset.sh +bash ~/code/local_machine/scripts/games/gog/launch-galaxy.sh +``` + +3. **Если окна нет — foreground + vdesktop:** + +```bash +bash ~/code/local_machine/scripts/games/gog/launch-galaxy-ui.sh +``` + +4. **A/B CEF (как Steam):** + +```bash +GOG_CEF_PROFILE=steam bash ~/code/local_machine/scripts/games/gog/launch-galaxy-ui.sh +``` + +## Что не считать «успехом» + +- Скрипт печатает `OK: GOG Galaxy запущен` — только процесс + лог, **не** видимое окно. +- Web-stub / `GalaxySetup.exe` в Wine — **не** доводят установку (gdi32/Inno). + +## Связанные документы + +- [TROUBLESHOOTING.md — GOG Galaxy: окно](TROUBLESHOOTING.md#gog-galaxy-окно-не-открывается) +- [gog/README.md](../../scripts/games/gog/README.md) diff --git a/docs/games/JA3_MAC_WITHOUT_GALAXY.md b/docs/games/JA3_MAC_WITHOUT_GALAXY.md new file mode 100644 index 0000000..546a65e --- /dev/null +++ b/docs/games/JA3_MAC_WITHOUT_GALAXY.md @@ -0,0 +1,216 @@ +# Jagged Alliance 3 на Mac без GOG Galaxy + +**Полный runbook (установка + графика + RCA + антипаттерны):** [JA3_RUNBOOK.md](JA3_RUNBOOK.md) + +Источники: [GOG — offline installers](https://support.gog.com/hc/en-us/articles/213148105-How-do-I-download-my-purchased-items), [Porting Kit / JA3](https://www.paulthetall.com/jagged-aliance-3-for-mac/), практика на этой машине (**установка 2026-05-28**, **запуск CrossOver 2026-05-29**). + +## Главное + +| | | +|--|--| +| **GOG Galaxy** | **не нужен** для одиночной игры (DRM-free) | +| **Galaxy** | только для мультиплеера (по карточке GOG) | +| **Что нужно** | **Offline backup** (Windows): `setup_*.exe` + `setup_*-1.bin` … `-4.bin` | +| **Установка** | **`innoextract`** в bottle — **не** Wine-Inno (падает на botva2/gdi32) | +| **Исполняемый файл** | `C:\GOG Games\Jagged Alliance 3\JA3.exe` | +| **Bottle** | **GOG-Install** (`scripts/games/gog/bottle.env`) — не HoMM/Steam | +| **Galaxy в Wine** | **не использовать** для JA3 — CEF, swap ([GOG_GALAXY_7H_RCA.md](GOG_GALAXY_7H_RCA.md)) | +| **Запуск игры** | **CrossOver** `run-ja3-crossover.sh` — см. [JA3_RUNBOOK.md](JA3_RUNBOOK.md) | +| **Steam.app / Steam.exe** | **не в контуре** — отдельный bottle HoMM; в GOG-bottle клиента Steam быть не должно | + +### Не путать с Steam + +В логе JA3 бывает `Steam initialized (AppId …, Beta branch GOG)` — это **GOG Galaxy wrapper** (`steam_api64.dll` + `Galaxy.dll` в папке игры), **не** установленный Steam client и **не** `Steam.app` на Mac. + +| Файл | Это | +|------|-----| +| `steam_api64.dll` в папке JA3 | GOG API-шим, **не удалять** | +| `Program Files (x86)/Steam/steam.exe` | Steam client — **удалить** из GOG-bottle | + +```bash +# только GOG-bottle: +bash ~/code/local_machine/scripts/games/gog/purge-steam-from-bottle.sh +# все bottle + остатки macOS: +bash ~/code/local_machine/scripts/games/purge-steam-wine.sh +``` + +--- + +## Runbook: установка multipart с экономией диска + +Сценарий: все части offline backup лежат в **`~/Games/`** (или `JA3_GAMES_DIR`). На диске мало места (~10–12 GB свободно) — **нельзя** копировать 15 GB архивов в bottle и **нельзя** распаковывать во вторую папку `cache/`. + +### Файлы (5 частей, одна папка) + +| Файл | Размер | Роль | +|------|--------|------| +| `setup_jagged_alliance_3_1.5.2_(80730).exe` | ~1.6 MB | Лаунчер Inno (обязателен) | +| `setup_jagged_alliance_3_1.5.2_(80730)-1.bin` … `-4.bin` | ~15 GB суммарно | Данные установщика | + +Имя `.exe` должно **совпадать с префиксом `.bin`** (без ` (1)` от браузера). Если скачали как `…(80730) (1).exe` — переименовать в `…(80730).exe`. + +### Почему не Wine + +```bash +# ❌ install-ja3-multipart.sh / install-ja3-offline.sh через wine64 +# Падает за ~10 с — Inno Setup + botva2 в Wine (как GalaxySetup). +``` + +Канон при **наличии архивов**: **`install-ja3-innoextract.sh`** + **`run-ja3.sh`** (флаг `-unpacked`). + +**Важно:** одного innoextract **мало** — Inno не выполняет пост-скрипты (Lua/VFS). Скрипты копируют `CommonLua` и запускают с **`-unpacked`**. Если графика падает (`Failed creating render device`) — доведите установку Wine-инсталлятором: **`install-ja3-wine-finish.sh`** (нужны все 5 файлов в `~/Games/`) или [Porting Kit](https://www.paulthetall.com/jagged-aliance-3-for-mac/). + +### Алгоритм экономии места (что делает скрипт) + +1. **Перед стартом** — безопасная очистка cache (не трогает архивы в `~/Games`): + - `scripts/games/cache/gog-galaxy-extract-gog-bottle` + - `scripts/games/cache/ja3-extract-test` + - `setup_galaxy.exe` в cache, если Galaxy уже установлен в bottle +2. **Распаковка сразу в bottle** — `…/drive_c/GOG Games/Jagged Alliance 3/` (без дубликата в `cache/`). +3. **По ходу `innoextract`** — при сообщении `Opening "…-2.bin"` удаляется `-1.bin`, при `-3.bin` → `-2.bin`, при `-4.bin` → `-3.bin` (освобождает ~4 GB за шаг). +4. **Флаг `-m`** — не извлекать временные файлы Inno (`tmp/`). +5. **После успеха** — удаляются оставшиеся `.bin` и `.exe` в `~/Games` (~15 GB). Отключить: `JA3_KEEP_BINS=1`. + +Пиковая нагрузка на диск: **~16 GB** под игру в bottle + оставшиеся `.bin` (уменьшаются по шагам 3), а не **~31 GB** (архивы + полная копия в cache). + +Минимум свободного места на Data: **~800 MB** во время работы (`JA3_MIN_FREE_MB`, по умолчанию 800), при условии что срабатывает удаление `.bin` по ходу. Если диск забит на 100% — сначала освободить 1–2 GB вручную (Корзина, cache), иначе `innoextract` упадёт на больших `Packs/*.hpk`. + +### Команды (один раз) + +```bash +# 1) Bottle GOG-Install (если ещё нет) +bash ~/code/local_machine/scripts/games/gog/list-bottles.sh +bash ~/code/local_machine/scripts/games/gog/use-bottle.sh GOG-Install +bash ~/code/local_machine/scripts/games/gog/setup-ja3-bottle.sh # DXVK в GOG-bottle + +# 2) Все 5 файлов в ~/Games/ +ls ~/Games/setup_jagged_alliance_3_1.5.2_\(80730\)* + +# 3) Установка (innoextract + экономия диска) +bash ~/code/local_machine/scripts/games/gog/install-ja3-innoextract.sh +# Лог: /tmp/ja3-innoextract-GOG-Install.log +``` + +Обёртка с автодетектом multipart в `~/Games`: + +```bash +bash ~/code/local_machine/scripts/games/gog/install-ja3.sh +``` + +Недостающий только `.exe` (~1 MB): + +```bash +# account.gog.com → JA3 → offline backup, первая часть +# или: +lgogdownloader --login +bash ~/code/local_machine/scripts/games/gog/fetch-ja3-setup-exe.sh +``` + +### Переменные окружения + +| Переменная | По умолчанию | Назначение | +|------------|--------------|------------| +| `JA3_GAMES_DIR` | `~/Games` | Папка с `setup_*.exe` и `.bin` | +| `JA3_MIN_FREE_MB` | `800` | Стоп, если свободно меньше (MB) | +| `JA3_KEEP_BINS` | `0` | `1` — не удалять архивы после установки | +| `JA3_CLEANUP_PARTIAL` | `1` | Удалить незавершённую распаковку в bottle при ошибке | + +### Графика: DirectX 12 (обязательно на Mac) + +JA3 на Windows использует **D3D12**. **DXVK-macOS** (только D3D10/11) **не заменяет** D3D12. + +#### Корневая причина «Failed to initialize graphics subsystem» (RCA 2026-05-29, M1 Pro) + +| Слой | Что происходит | +|------|----------------| +| **Whisky / Gcenx Wine 11.9** | `d3d12.dll` = wined3d → **Vulkan** (`d3d12_get_vk_physical_device Could not find Vulkan physical device`). **Нет D3DMetal hooks** ([Gcenx#97](https://github.com/Gcenx/macOS_Wine_builds/issues/97)). | +| **GPTK PE + Gcenx ntdll** | `__wine_unix_call` / `dxgi.dll not found` — PE DLL GPTK несовместимы с чужим Wine. | +| **GPTK Wine 7.7 alone** | `comctl32.dll failed to initialize` на этой macOS — не запускается даже notepad. | +| **CrossOver 26 + D3DMetal** | **Работает.** Встроенные `apple_gptk` + `WINED3DMETAL=1`. | + +Сообщения GOG API (`MatchmakingServers`, `Music`, `Game name not defined`) — **не причина** краша. + +#### Канон запуска (подтверждено на этой машине) + +```bash +brew install --cask crossover # один раз +bash ~/code/local_machine/scripts/games/gog/setup-ja3-crossover.sh +bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh +``` + +CrossOver bottle **JA3-GOG** (win10_64, D3DMetal on). Игра клонируется APFS clonefile в bottle (Wine **не читает** symlink за пределы prefix → `CommonLua/Core/autorun.lua not found`). + +**Whisky `run-ja3.sh` для D3D12 на Mac не работает** — только CrossOver (или Porting Kit GUI). + +Альтернатива GUI: [Porting Kit](https://www.paulthetall.com/jagged-aliance-3-for-mac/) (тот же GPTK/D3DMetal стек). + +### Запуск игры + +```bash +bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh +``` + +Whisky (`run-ja3.sh`) — только установка innoextract в GOG-Install; **графика D3D12 → CrossOver**. + +Обход (тормоза, на свой риск): + +```bash +JA3_IGNORE_SWAP=1 bash ~/code/local_machine/scripts/games/gog/run-ja3.sh +``` + +Cmd+Tab → `wine64` / `JA3`. В графике при артефактах: **отключить heat haze**. + +**Ошибка «Failed to initialize graphics subsystem»:** Whisky/Gcenx Wine **не поддерживает D3DMetal** для d3d12. Используйте **CrossOver**: `setup-ja3-crossover.sh` + `run-ja3-crossover.sh`. Из Whisky Run **не запускать** — только CrossOver bottle JA3-GOG с `start /d` (CWD + CommonLua). + +### Итог на диске (после успеха на этой машине) + +| До | После | +|----|--------| +| ~15 GB архивы в `~/Games` | архивы удалены | +| bottle ~1.4 GB | bottle **+~16 GB** (`GOG Games/Jagged Alliance 3`) | +| свободно ~10 GB | свободно **~12 GB** (архивы сняты, игра в bottle) | + +--- + +## Скачивание (браузер на Mac) + +1. https://www.gog.com/account → **Jagged Alliance 3** +2. **Windows**, язык → **Download offline backup game installers** +3. Скачать **все 5 частей** (`.exe` ~1 MB + четыре `.bin`) в одну папку, например `~/Games/` + +--- + +## Альтернатива: один .exe без .bin + +Если GOG отдал один большой `setup_jagged_alliance_3_*.exe` (без multipart): + +```bash +bash ~/code/local_machine/scripts/games/gog/install-ja3-offline.sh ~/Downloads/setup_jagged_alliance_3_*.exe +``` + +В Wine тоже может упасть — тогда тот же **`install-ja3-innoextract.sh`**, положив единственный `.exe` в `JA3_GAMES_DIR` (без `.bin` innoextract возьмёт данные из exe). + +--- + +## Чего не делать + +| ❌ | ✅ | +|----|-----| +| `GOG_Galaxy_Jagged_Alliance_3.exe` web-stub | offline backup в `~/Games` | +| `install-ja3-multipart.sh` (Wine `/VERYSILENT`) | `install-ja3-innoextract.sh` | +| Распаковка в `scripts/games/cache/ja3-*` | сразу в `drive_c/GOG Games/…` | +| Держать `.bin` после установки без нужды | скрипт удалит (~15 GB) | +| `launch-galaxy.sh` / Galaxy CEF для JA3 | **`run-ja3-crossover.sh`** ([JA3_RUNBOOK.md](JA3_RUNBOOK.md)) | +| Whisky `run-ja3.sh` для D3D12 | CrossOver + D3DMetal | +| `RetinaMode=y` + `LogPixels=192` для мыши JA3 | Клип курсора на середине экрана → **legacy** ([JA3_RUNBOOK.md](JA3_RUNBOOK.md) §9) | +| Жёсткий стоп JA3 при swap 800 MB без Wine | Устарело; warn до 4 GB — [JA3_RUNBOOK.md](JA3_RUNBOOK.md) §4 | + +--- + +## Связанные документы + +- **[JA3_RUNBOOK.md](JA3_RUNBOOK.md)** — канон запуска, RCA 28–29.05, антипаттерны +- [WINE_APPS_LAUNCH_RUNBOOK.md](WINE_APPS_LAUNCH_RUNBOOK.md) — §5 JA3 +- [WINE_MAC_RUNBOOK.md](WINE_MAC_RUNBOOK.md) — §3.5 +- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — GOG Inno / swap +- [GOG_GALAXY_7H_RCA.md](GOG_GALAXY_7H_RCA.md) diff --git a/docs/games/JA3_RUNBOOK.md b/docs/games/JA3_RUNBOOK.md new file mode 100644 index 0000000..98611de --- /dev/null +++ b/docs/games/JA3_RUNBOOK.md @@ -0,0 +1,297 @@ +# Jagged Alliance 3 (GOG) — runbook Mac / Apple Silicon + +**Машина:** M1 Pro, macOS Sonoma+, bottle Whisky **GOG-Install** (`9ee55e7c-a58e-411e-a93f-30530d4f1073`). +**Проверено:** 2026-05-28 (установка) · 2026-05-29 (запуск через CrossOver). + +Связанные документы: [JA3_MAC_WITHOUT_GALAXY.md](JA3_MAC_WITHOUT_GALAXY.md) (установка offline), [TROUBLESHOOTING.md](TROUBLESHOOTING.md), [GOG_GALAXY_7H_RCA.md](GOG_GALAXY_7H_RCA.md). + +--- + +## 1. Быстрый путь (канон) + +### Установка (один раз, Whisky) + +```bash +# Offline backup (5 файлов) в ~/Games/ +bash ~/code/local_machine/scripts/games/gog/install-ja3-innoextract.sh +bash ~/code/local_machine/scripts/games/gog/finish-ja3-layout.sh +``` + +### Запуск (каждый раз, CrossOver + D3DMetal) + +```bash +brew install --cask crossover # один раз +bash ~/code/local_machine/scripts/games/gog/setup-ja3-crossover.sh # один раз +bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh +``` + +- **Cmd+Tab** → окно `JA3.exe` +- В настройках графики: **отключить heat haze** +- **Swap:** «залипший» swap после Wine (2–4 GB без процессов) **не блокирует** запуск — только предупреждение. Стоп: swap ≥ **4096 MB** без Wine, или swap ≥ **1200 MB** пока **Wine/JA3 ещё работают** → `reset-ja3-crossover.sh` / reboot. Обход: `JA3_IGNORE_SWAP=1` + +### Два «контура» + +| Контур | Роль | Инструмент | +|--------|------|------------| +| **GOG-Install** (Whisky) | innoextract, файлы игры, CommonLua, реестр GOG | `install-ja3-innoextract.sh`, `finish-ja3-layout.sh` | +| **JA3-GOG** (CrossOver) | **запуск** D3D12 через D3DMetal | `setup-ja3-crossover.sh`, `run-ja3-crossover.sh` | + +Whisky **`run-ja3.sh` не запускает JA3 с рабочей графикой** — только CrossOver (или Porting Kit GUI). + +--- + +## 2. Почему CrossOver, а не Whisky + +JA3 на Windows использует **DirectX 12**. На Apple Silicon единственный рабочий путь — **Apple D3DMetal** (Game Porting Toolkit), встроенный в **CrossOver 23+** с флагом `WINED3DMETAL=1`. + +**Whisky** использует **Gcenx Wine** — maintainer явно пишет, что сборки **не содержат D3DMetal hooks** ([Gcenx/macOS_Wine_builds#97](https://github.com/Gcenx/macOS_Wine_builds/issues/97)). В этом случае `d3d12.dll` (94 KB) идёт через **wined3d → Vulkan**, а на Mac нет Vulkan physical device: + +```text +fixme:d3d12:d3d12_get_vk_physical_device Could not find Vulkan physical device for DXGI adapter. +``` + +→ UI: **«Failed to initialize graphics subsystem»** +→ лог JA3: **`Failed creating render device`** + +Копирование `D3DMetal.framework` + `libd3dshared.dylib` + моста `d3d12.so` **не достаточно**, если PE `d3d12.dll` / `ntdll` от Gcenx — unix-вызовы D3DMetal не подключены. + +--- + +## 3. RCA: злоключения 2026-05-28 — 2026-05-29 + +Хронология попыток на этой машине (чат + логи `JA3.exe-*.log`, Wine debug). + +### Фаза A — установка и ложные следы (28.05) + +| # | Симптом / действие | Реальная причина | Итог | +|---|-------------------|------------------|------| +| A1 | GOG web-stub, пустые кнопки | Нет **gdiplus** (winetricks) | ✅ `install-gdiplus.sh` + corefonts | +| A2 | Galaxy CEF, окна нет | HWND/CEF в Wine, swap | Galaxy **не нужен** для JA3 offline | +| A3 | Лог `Steam initialized … Beta branch GOG` | GOG API-шим (`steam_api64.dll`), **не** Steam.app | ❌ не причина краша | +| A4 | `CreateSubsystem failed MatchmakingServers/Music` | GOG wrapper без Galaxy service | ❌ шум, не графика | +| A5 | `Game name not defined` | innoextract не выполнил Inno-постскрипты | ⚠️ косметика; фикс: `register-ja3-registry.sh` | +| A6 | Swap ~3 GB, скрипт блокирует запуск | «Залипший» swap после Wine; macOS не отдаёт без reboot | ✅ `gog_swap_guard_ja3_launch`: warn до 4 GB без процессов; reboot если Mac тормозит | + +### Фаза B — «графика» в Whisky (28–29.05) + +| # | Попытка | Лог / ошибка | Почему не работает | +|---|---------|--------------|-------------------| +| B1 | DXVK + `-graphicsapi d3d11` | `Failed creating render device` | DXVK = D3D10/11, **не D3D12** | +| B2 | DXMT v0.80 | `dxgi.dll` **c000007b** | Несовместимость с Gcenx Wine 11 + overrides | +| B3 | `upgrade-whisky-wine-gcenx.sh` (11.9) + GPTK bridge | `d3d12_get_vk_physical_device` | Gcenx d3d12 = **Vulkan**, не Metal | +| B4 | GPTK PE `d3d12.dll`/`dxgi.dll` (114/86 KB) в `system32` | Всё ещё Vulkan path или `dxgi not found` | PE GPTK 7.7 ≠ ntdll Gcenx 11 | +| B5 | GPTK PE в `wine/lib/x86_64-windows` | `Library dxgi.dll not found` (c0000135) | `__wine_unix_call` не реализован в Gcenx ntdll | +| B6 | Замена `ntdll.so` на GPTK | `virtual_alloc_first_teb` / wine не стартует | Нельзя мешать ntdll разных Wine | +| B7 | `ditto` всего GPTK `lib/` в Whisky | Wine **7.7 GPTK** поверх bottle | Перезаписал `wine64` → comctl32 и др. | +| B8 | `prepare-ja3-d3dmetal.sh` (убрать d3d12 из system32) | Всё равно render device fail | Builtin Gcenx d3d12 без Metal | +| B9 | `forceD3D11: true` в Metadata.plist | Противоречие с D3D12 backend | Исправлено на `false` — без эффекта на Gcenx | +| B10 | GPTK Wine 7.7, отдельный prefix | `comctl32.dll failed to initialize` (c0000005) | GPTK Wine 7.7 **сломан** на этой macOS | +| B11 | `fixmywhisky.sh` (ditto GPTK lib) | Как B7 | Только D3DMetal.framework + bridge **без** подмены всего Wine | + +### Фаза C — CrossOver (29.05, **успех**) + +| # | Шаг | Результат | +|---|-----|-----------| +| C1 | `brew install --cask crossover` | CrossOver 26.1, встроенный `apple_gptk` | +| C2 | Bottle **JA3-GOG**, template **win10_64** (не win32 Wow64!) | `kernel32` c000007b на 32-bit bottle | +| C3 | `WINED3DMETAL=1`, `WINEDXVK=0` в `cxbottle.conf` | D3D12 → Metal | +| C4 | Symlink игры из Whisky bottle | `CommonLua/Core/autorun.lua: File Not Found` — Wine **не читает** symlink **вне** prefix | +| C5 | `cp -cR` (APFS clonefile) игры в CrossOver bottle | Файлы видны Wine; **~0** доп. места на APFS | +| C6 | `start /d "C:\GOG Games\Jagged Alliance 3" JA3.exe -unpacked` | CWD с `CommonLua/`; не `--cx-app` без `/d` | +| C7 | Запуск | **JA3.exe** стабильно работает, нет `Failed creating render device` | +| C8 | Курсор: смещение клика → `RetinaMode=y` + `LogPixels=192` | Курсор **не опускается ниже середины экрана** (Y делится пополам) | ✅ профиль **`legacy`**: `RetinaMode=n`, без LogPixels — **подтверждено 2026-05-29** | + +### Сигнатуры в логах (диагностика) + +| Строка | Значение | +|--------|----------| +| `Failed creating render device` | D3D12 не дошёл до GPU — Whisky/Gcenx, не CrossOver | +| `d3d12_get_vk_physical_device Could not find Vulkan` | Gcenx wined3d path — нужен CrossOver D3DMetal | +| `Failed to initialize graphics subsystem` (UI) | То же, пользовательский текст JA3 | +| `Error opening 'CommonLua/Core/autorun.lua'` | Нет CWD (`start /d`) или symlink вместо клона в bottle | +| `comctl32.dll failed to initialize` | GPTK Wine 7.7 — не использовать | +| `__wine_unix_call` / `aborting` | GPTK PE на чужом Wine | +| `dxgi.dll` **c000007b** | DXMT + неправильные overrides | +| `Language: English` без `Failed creating` | Графика OK; смотреть CommonLua / crash дальше | +| Курсор виден, клик смещён по Y | HiDPI без согласованного масштаба Wine | `fix-ja3-crossover-mouse.sh` → профиль **legacy** | +| Курсор **не ниже середины экрана** | `RetinaMode=y` + `LogPixels=192` / двойной DPI | **legacy** (не retina); перезапуск JA3 | + +Логи JA3: +`…/AppData/Roaming/Jagged Alliance 3/logs/JA3.exe-*.log` +Whisky: `…/Containers/com.franke.Whisky/Bottles/9ee55e7c-…/` +CrossOver: `~/Library/Application Support/CrossOver/Bottles/JA3-GOG/` + +--- + +## 4. Антипаттерны (полный список) + +### Установка + +| ❌ Не делать | ✅ Вместо | +|-------------|----------| +| GOG web-stub `GOG_Galaxy_Jagged_Alliance_3.exe` | Offline backup 5 частей в `~/Games/` | +| `install-ja3-multipart.sh` (Wine Inno) | `install-ja3-innoextract.sh` | +| Распаковка в `scripts/games/cache/` | Сразу в `drive_c/GOG Games/…` | +| Bottle **HoMM-OldenEra** (Steam) | Bottle **GOG-Install** | +| Удалять `steam_api64.dll` в папке игры | Это GOG-шим, **оставить** | +| Steam client в GOG-bottle | `purge-steam-from-bottle.sh` | + +### Графика / Wine + +| ❌ Не делать | ✅ Вместо | +|-------------|----------| +| Whisky `run-ja3.sh` для игры | **`run-ja3-crossover.sh`** | +| `upgrade-whisky-wine-gcenx.sh` «для D3DMetal» | Gcenx **не** поддерживает D3DMetal hooks | +| `ditto` всего GPTK `lib/` в Whisky Libraries | Только `D3DMetal.framework` + `libd3dshared` + `.so` bridge (см. `install-gptk-whisky.sh`) | +| GPTK PE `d3d12/dxgi` в system32 **Whisky** | CrossOver со встроенным `apple_gptk` | +| Замена `ntdll.so` GPTK в Gcenx Wine | Ломает preloader | +| DXVK / DXMT для JA3 как «замена D3D12» | Не подтверждено; JA3 = D3D12 | +| `dxgi,d3d11=n,b` с DXMT | Ломает цепочку → c000007b | +| Whisky Run **`JA3.exe`** без CWD | `start /d` или `run-ja3.cmd` (Whisky) / CrossOver `start /d` | +| Symlink игры из Whisky в CrossOver bottle | **`cp -cR`** (APFS clone) | +| CrossOver bottle **win32** / Wow64 | **`win10_64`** | +| `--cx-app JA3.exe` без `start /d` | `start /d "…Jagged Alliance 3" JA3.exe -unpacked` | +| `forceD3D11: true` + backend d3dMetal | `forceD3D11: false` (Metadata Whisky — не решает Gcenx) | + +### Окружение + +| ❌ Не делать | ✅ Вместо | +|-------------|----------| +| Считать «залипший» swap 2–4 GB блокером JA3 | Запуск разрешён (warn); reboot — если Mac тормозит | +| Запуск JA3 при swap ≥ **1200 MB** + **Wine/JA3 ещё работают** | `reset-ja3-crossover.sh` → reboot | +| Запуск JA3 при swap ≥ **4096 MB** без Wine | Reboot Mac или `JA3_IGNORE_SWAP=1` | +| Жёсткий лимит **800 MB** для `run-ja3-crossover.sh` | Устарело; см. `gog_swap_guard_ja3_launch` в `config.sh` | +| Путать лог Galaxy с JA3 | JA3 offline **без** Galaxy UI | +| Чинить «graphics» через Galaxy service | `MatchmakingServers`/`Music` — шум GOG API | + +### Мышь (CrossOver / Retina) + +| ❌ Не делать | ✅ Вместо | +|-------------|----------| +| `RetinaMode=y` + `LogPixels=192` + `Win8DpiScaling=1` «по умолчанию» | **`fix-ja3-crossover-mouse.sh`** — профиль **legacy** (`RetinaMode=n`) | +| CrossOver GUI **High Resolution Mode** без проверки | Если клип Y на середине → **legacy** | +| Менять реестр мыши без перезапуска JA3 | `reset-ja3-crossover.sh` → `run-ja3-crossover.sh` | +| Профиль **retina** при клипе на середине экрана | **legacy**; при смещении клика — **retina-lite** | +| Ожидать фикс мыши «на лету» | Workaround: верхний левый угол окна = угол экрана | + +--- + +## 5. Скрипты + +| Скрипт | Назначение | Статус | +|--------|------------|--------| +| `install-ja3-innoextract.sh` | Multipart offline → bottle | ✅ канон установки | +| `finish-ja3-layout.sh` | CommonLua, реестр, upscale DLL | ✅ после innoextract | +| `setup-ja3-crossover.sh` | Bottle JA3-GOG, clone игры, D3DMetal | ✅ один раз | +| **`run-ja3-crossover.sh`** | **Запуск игры** | ✅ **канон** | +| `reset-ja3-crossover.sh` | Stop JA3 / Wine перед перезапуском | ✅ | +| `fix-ja3-crossover-mouse.sh` | Профили мыши (legacy / retina-lite / retina) | ✅ default **legacy** | +| `run-ja3.sh` | Whisky (эксперимент; графика fail) | ⚠️ только с предупреждением | +| `prepare-ja3-d3dmetal.sh` | Whisky D3DMetal prep | ⚠️ не решает Gcenx d3d12 | +| `upgrade-whisky-wine-gcenx.sh` | Wine 11.9 | ❌ для JA3 не использовать | +| `run-ja3-gptk-wine.sh` | GPTK Wine 7.7 prefix | ❌ comctl32 на этой Mac | +| `install-ja3-dxmt.sh` | DXMT fallback d3d11 | ❌ c000007b | +| `emergency-stop.sh` | Kill Wine/GOG перед запуском | ✅ | + +--- + +## 6. CrossOver bottle (детали) + +**Путь:** `~/Library/Application Support/CrossOver/Bottles/JA3-GOG/` + +**cxbottle.conf** (минимум): + +```ini +"WINED3DMETAL" = "1" +"WINEDXVK" = "0" +``` + +**Игра:** `drive_c/GOG Games/Jagged Alliance 3/` — APFS-клон из Whisky GOG-Install (не symlink). + +**Проверка:** + +```bash +pgrep -fl 'JA3\.exe' +grep -E 'Failed creating|autorun|CRASH' \ + "$HOME/Library/Application Support/CrossOver/Bottles/JA3-GOG/drive_c/users/"*/AppData/Roaming/Jagged\ Alliance\ 3/logs/*.log | tail +``` + +--- + +## 7. Альтернативы + +| Вариант | Комментарий | +|---------|-------------| +| [Porting Kit](https://www.paulthetall.com/jagged-aliance-3-for-mac/) + GOG offline | Тот же GPTK/D3DMetal; GUI, «просто работает» на M-series | +| CrossOver GUI | Bottle JA3-GOG → Install → Run exe; D3DMetal в Advanced Settings | +| Whisky + GPTK drag DMG | [Whisky#124](https://github.com/Whisky-App/Whisky/issues/124) — для **DX12** на Gcenx всё равно нет hooks | + +--- + +## 9. Курсор: HiDPI / Retina (CrossOver) + +**Проверено 2026-05-29** на M1 Pro + CrossOver 26.1, bottle **JA3-GOG**. + +### Симптомы + +| Симптом | Типичная причина | +|---------|------------------| +| Клик «выше/ниже» UI, курсор виден | Wine отдаёт logical pixels, игра рисует в другом масштабе | +| **Курсор не опускается ниже середины экрана** | `RetinaMode=y` + `LogPixels=192` — координата Y делится пополам | + +### Профили (`fix-ja3-crossover-mouse.sh`) + +| Профиль | Реестр | Когда | +|---------|--------|-------| +| **`legacy`** (default) | `RetinaMode=n`, LogPixels/Win8Dpi **удалены** | **Канон** — полный ход курсора по Y; возможен небольшой сдвиг клика | +| `retina-lite` | `RetinaMode=y`, без LogPixels | Если legacy смещает клик, но **нет** клипа на середине | +| `retina` | `RetinaMode=y`, `LogPixels=192`, `Win8DpiScaling=1` | ❌ часто **клип Y на 50%** — не использовать как default | + +```bash +# Канон (после смены профиля — перезапуск JA3) +bash ~/code/local_machine/scripts/games/gog/fix-ja3-crossover-mouse.sh +bash ~/code/local_machine/scripts/games/gog/reset-ja3-crossover.sh +bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh + +# Если legacy смещает клик +JA3_MOUSE_PROFILE=retina-lite bash ~/code/local_machine/scripts/games/gog/fix-ja3-crossover-mouse.sh +bash ~/code/local_machine/scripts/games/gog/reset-ja3-crossover.sh +bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh +``` + +**Без перезапуска (workaround):** перетащите окно JA3 так, чтобы **верхний левый угол окна** совпал с **верхним левым углом экрана**. + +CrossOver GUI: Bottle **JA3-GOG** → **High Resolution Mode** — при клипе на середине **выключить** (эквивалент **legacy**). + +--- + +## 10. Быстрые команды (терминал) + +| Действие | Команда | +|----------|---------| +| **Запуск** | `bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh` | +| **Стоп / reset Wine** | `bash ~/code/local_machine/scripts/games/gog/reset-ja3-crossover.sh` | +| **Стоп всё GOG/Wine** | `bash ~/code/local_machine/scripts/games/gog/emergency-stop.sh` | +| **Первичная настройка bottle** | `bash ~/code/local_machine/scripts/games/gog/setup-ja3-crossover.sh` | +| **Фикс мыши** | `bash ~/code/local_machine/scripts/games/gog/fix-ja3-crossover-mouse.sh` | +| Swap «залип» 2–4 GB, Wine нет | просто `run-ja3-crossover.sh` (warn) | +| Swap + Wine/JA3 живы ≥ 1200 MB | `reset-ja3-crossover.sh` → reboot | +| Swap ≥ 4096 MB без Wine | reboot Mac | +| Обход swap | `JA3_IGNORE_SWAP=1 bash …/run-ja3-crossover.sh` | +| Профиль мыши | `JA3_MOUSE_PROFILE=legacy\|retina-lite\|retina` + fix + reset + run | + +**Полный цикл перезапуска:** + +```bash +bash ~/code/local_machine/scripts/games/gog/reset-ja3-crossover.sh +bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh +``` + +--- + +## 8. Ссылки + +- [Gcenx — нет D3DMetal в packages](https://github.com/Gcenx/macOS_Wine_builds/issues/97) +- [CrossOver — D3DMetal advanced settings](https://support.codeweavers.com/miscellanous/advanced-settings-in-crossover-mac-26) +- [Whisky — GPU / GPTK tracking](https://github.com/Whisky-App/Whisky/issues/124) +- [Proton JA3 — graphics / LocalStorage](https://github.com/ValveSoftware/Proton/issues/7039) (Linux; отражения Off/Low — аналог для Wine при других GPU) diff --git a/docs/games/README.md b/docs/games/README.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/games/TROUBLESHOOTING.md b/docs/games/TROUBLESHOOTING.md new file mode 100644 index 0000000..746711f --- /dev/null +++ b/docs/games/TROUBLESHOOTING.md @@ -0,0 +1,962 @@ +# HoMM Olden Era — troubleshooting (Whisky / Wine) + +- **Запуск приложений:** [WINE_APPS_LAUNCH_RUNBOOK.md](./WINE_APPS_LAUNCH_RUNBOOK.md) +- **Подготовка Wine/GOG:** [WINE_MAC_RUNBOOK.md](./WINE_MAC_RUNBOOK.md) +- **GOG web-stub (отдельный bottle):** [`../../scripts/games/gog/README.md`](../../scripts/games/gog/README.md) +- **JA3 (GOG):** [JA3_RUNBOOK.md](./JA3_RUNBOOK.md) +- **Мышь / Unity:** [UNITY_POINTER_MOUSE_FIX.md](./UNITY_POINTER_MOUSE_FIX.md) +- **Скрипты:** [`../../scripts/games`](../../scripts/games) + +Bottle HoMM/Steam: `38412122-AAE1-4831-9F2E-6A0EB364B3F9` · Steam App ID: `3105440` +GOG web-stub: bottle **`GOG-Install`** (`scripts/games/gog/bottle.env`) — **не** HoMM UUID. + +--- + +## Быстрая таблица + +| Симптом | Первый шаг | Раздел | +|---------|------------|--------| +| Пустой UI Steam (кнопки без текста) | `fix-steam-ui` → `reset` → `steam-ui` | [§ Кривой UI Steam](#кривой-ui-steam-пустой-vgui) | +| **Окно Steam не появляется** | `diagnose-steam-ui` → kill Mac Steam → `reset` → `steam-ui` | [§ Нет окна Steam](#нет-окна-steam-ui) | +| Игра есть, **сейвы пустые** | `diagnose-saves` → login → `play` | [§ Сейвы](#игра-запустилась-сейвы-не-подтянулись) | +| `play` висит, exe не стартует | `reset` → `steam-ui` → login → `play` | [§ Нет прогресса](#нет-прогресса-play-не-доходит-до-игры) | +| Игра не стартует / D3D11 crash | **не** Play в Steam → `play` | [§ Запуск игры](#запуск-игры-канон) | +| DirectX 11 | `dxvk` → `play` | [§ Графика](#графика-directx-11--failed-to-initialize) | +| ЛКМ не работает | `diagnose` | [UNITY_POINTER_MOUSE_FIX.md](./UNITY_POINTER_MOUSE_FIX.md) | +| **Connection Problem** в Steam | `osascript` quit Steam → `reset` → `steam-ui` → Retry / Offline | [§ Connection Problem](#connection-problem-steam-не-подключается-к-серверам) | +| Steam завис / нет места | `disk` → `reset` | [§ Steam зависает](#steam-зависает--мало-места) | +| Пустой текст / `FontFamily` / `winFont` | см. профиль: Steam → `dwrite`; GOG stub → `gdiplus` | [§ Шрифты — антипаттерны](#шрифты-антипаттерны-сводка) | +| GOG stub: пустые кнопки / `FontFamily` | `gog/install-gdiplus` + `gog/install-corefonts` в **GOG-bottle** | [§ GOG web-stub](#gog-web-stub-отдельный-bottle) | +| **GOG Galaxy: окна нет**, скрипт OK | offline installer JA3; Galaxy — best effort | [§ GOG Galaxy — окно](#gog-galaxy-окно-не-открывается) · [GOG_GALAXY_7H_RCA.md](GOG_GALAXY_7H_RCA.md) | +| GOG Galaxy → **swap**, Mac «встал» | `gog/emergency-stop.sh` → перезагрузка | [GOG_GALAXY_7H_RCA.md](GOG_GALAXY_7H_RCA.md) § Swap | +| GOG stub: «запустил, окна нет» | **Cmd+Tab** → `GOG_Galaxy_*.exe` / `wine64` | [§ GOG — проверка успеха](#gog-web-stub-отдельный-bottle) | +| **JA3: Failed to initialize graphics subsystem** | CrossOver D3DMetal, не Whisky | [JA3_RUNBOOK.md](JA3_RUNBOOK.md) §2–4 | +| **JA3: курсор не ниже середины / смещение клика** | профиль мыши **legacy** vs retina | [JA3_RUNBOOK.md](JA3_RUNBOOK.md) §9 | +| **JA3 без Galaxy** | install → Whisky; **запуск → CrossOver** | [JA3_RUNBOOK.md](JA3_RUNBOOK.md) | +| Установить через web-stub | только GalaxySetup (ломается в Wine) | [§ GOG web-stub](#gog-web-stub-отдельный-bottle) | +| `fork failed`, Mac «встал», swap 20+ GB | `winedbg` / Steam assert loop → § Swap | [§ Swap и winedbg](#захламление-swap-и-утечка-winedbg) | +| «20 GB съело» после GOG | обычно **не** stub — swap + дампы Steam | [§ Swap](#захламление-swap-и-утечка-winedbg) · [§ GOG](#gog-web-stub-отдельный-bottle) | + +--- + +## Антипаттерны (не делать) + +| ❌ Антипаттерн | Почему ломается | ✅ Вместо этого | +|----------------|-----------------|-----------------| +| **Play** у Olden Era **в окне Steam UI** | applaunch **без** `export_wine_env_game` → D3D11 / «Bad EXE format» в `Player.log` | Steam **Logged On** → **`start.sh play`** (Terminal) | +| **`start.sh game`** / exe **без** Steam в bottle | `SteamApi_Init failed` · слоты пустые | Whisky → Steam → Logged On → **`play`** | +| **`start.sh play`** при **Logged Off** | нет Steam API → пустые сейвы / отказ | `diagnose-saves` → login / Offline → `play` | +| **`-applaunch 3105440`** до login | триггер CEF `message_window.cc` · UI мёртв | сначала **Logged On**, потом **`play`** | +| **`HOMM_STEAM_VDESKTOP=1`** | CEF хуже · `Failed to set primary display settings` | **`steam-ui`** / Whisky Run (**не** VDESKTOP) | +| **Terminal `steam-ui` + Whisky Run** параллельно | двойной Wine · зомби | **один** контур: Whisky **или** Terminal | +| **`reset` без** `.exe`-зомби | `pkill` не ловит `C:\…\SteamService.exe` | **`reset`** (2026-05-27) или nuclear recovery | +| Запуск с **`-silent`** (старые скрипты / ручной `Steam.exe -silent`) | `forcing desktopui via -silent` → VGUI + `winFont` → пустой диалог | `steam-ui` или `play` **без** `-silent` | +| Только скопировать шрифты в `Fonts/` без `fix-steam-ui` | `winFont` остаётся — DWrite не отключён | **`bash start.sh fix-steam-ui`** (реестр + шрифты + dwrite override) | +| Игра «открылась» = сейвы работают | FileManager может видеть папку `Steam_/saves`, но **Store** не инициализирован без Steam | Проверять `Player.log` и `diagnose-saves` | +| Закрыть пустой диалог Steam крестиком | Steam остаётся **Logged Off**; следующий запуск снова без сейвов | **Правая** кнопка внизу или Tab → Enter | +| Нативный **Mac Steam** + bottle | Connection Problem · `steamid=0` | **`killall steam_osx ipcserver`** → только Wine-Steam | +| **`osascript quit Steam`** при отмене (-128) | Mac Steam жив | **Cmd+Q** / **`killall steam_osx`** | +| Импорт bottle **isaacmarovitz**, Whisky **franke** пустой | другой контейнер | **`migrate-bottle-to-franke-whisky.sh`** | +| Whisky «Launched steam.exe» = UI OK | нужен **steamwebhelper** (CEF) | `diagnose-steam-ui` · `cef_log` | +| **`HOMM_GAME_FORCE=1`** каждый день | пустые слоты | только отладка графики | +| Смотреть только `cache/*.tsv` | не диагностика bottle | `Steam/logs/*.txt`, **`Player.log`**, **`diagnose-*`** | +| **GOG web-stub** в bottle **HoMM-OldenEra** | общий префикс со **Steam** → `winedbg`, дампы `Steam/dumps/`, шрифты из `Steam/resource/fonts`, `export_wine_env_steam` ломает .NET | **`scripts/games/gog/`** + новый bottle → `gog/use-bottle.sh` | +| **`setup-wine-bottle.sh`** для GOG JA3 | скрипт для **Steam/HoMM**, не для GOG stub | `gog/setup-bottle.sh` | +| **`start.sh reset`** после GOG в том же bottle | убивает **Steam** и игру; не лечит GOG-шрифты | `gog/reset.sh` в GOG-bottle; HoMM: отдельная сессия | +| **`export_wine_env_steam`** / `dwrite=n,b` для GOG .NET installer | `FontFamilyNotFound` / пустой WinForms | `gog/install-gdiplus.sh` + `gog/run-web-installer.sh` (`gdiplus=n,b`) | +| GOG stub **без** `winetricks gdiplus` | `GenericSansSerif` / unimplemented GDI+ | `bash …/gog/install-gdiplus.sh` в GOG-bottle | +| Диагностика GOG через **`Steam/dumps`** | ложный след; stub **не** пишет туда | `/tmp/gog-web-installer-*.log`, `gog/diagnose-session.sh` | +| Думать, что stub **скачал 20 GB** | stub ~500 KB; при `FontFamily` падает **до** загрузки игры | `df -h`; искать swap / `setup_galaxy.exe`×2 / `Steam/dumps` | +| **`wineboot -k`** «на всякий случай» при каждом GOG | трогает **весь** префикс (Steam, CEF-кэш) | GOG-bottle: `gog/setup-bottle.sh --fonts-only`; HoMM: `reset` только когда нужен Steam | +| Параллельно **isaac** + **franke** Whisky / старый `wineserver` | второй `wineserver`, сотни `winedbg`, swap под 100% | `pkill -9 winedbg`; `pkill wineserver`; один контейнер Whisky | +| **`pkill -f GOG`** в HoMM-bottle при живом Steam | не связано со stub; мешает отладке | разнести bottle; GOG: `gog/reset.sh` | +| Только `install-corefonts` **без** `install-gdiplus` | шрифты в `Fonts/` есть, но .NET падает на **GenericSansSerif** (Wine GDI+ stub) | **`gog/install-gdiplus.sh`** → `WINEDLLOVERRIDES=gdiplus=n,b` | +| Debian URL для `arial32.exe` (404) | corefonts неполные | зеркало SourceForge в `gog/install-corefonts.sh` | +| **Whisky → Fonts / winetricks corefonts** | зеркала Microsoft **404** ([Whisky #515](https://github.com/Whisky-App/Whisky/issues/515)) | macOS → `Fonts/` + наши скрипты; GOG: `gog/install-corefonts.sh` | +| **`WINEDLLOVERRIDES=gdiplus=b`** для GOG **web-stub** | builtin gdiplus не даёт **GenericSansSerif** для .NET | **`gdiplus=n,b`** (`export_wine_env_gog`) | +| **`--profile steam`** / `dwrite=n,b` для GOG .NET | другой стек, не GDI+ | `run-wine-exe.sh --profile gog` или `gog/run-web-installer.sh` | +| Только **`winetricks liberation`** без macOS Fonts + реестр | частично; Steam/GOG UI всё ещё пустые | `setup-wine-bottle.sh` / `gog/setup-bottle.sh` + `fix-steam-ui` (Steam) | +| Скопировать `.ttf` **без** `reg add …\Fonts` | GDI не видит файлы | `fix-steam-ui`, `gog/wine-fonts.sh` (`--fonts-only`) | +| **`install-corefonts`** без **`cabextract`** | `.exe` MS fonts не распакуются | `brew install cabextract` | +| **`install-gdiplus`** до **`setup-bottle`** | нет базовых шрифтов/реестра в bottle | порядок: `setup-bottle` → `install-gdiplus` → `install-corefonts` | +| «26 файлов в Fonts» = stub OK | без **native gdiplus** .NET всё равно падает | маркер `.gog_gdiplus_installed` + нет `FontFamily` в логе | +| Пустой **GalaxySetup** (красный X, gdi32) | **не** нехватка Arial — **Inno/botva2 + gdi32** | `gog/install-galaxy-extract.sh` + `launch-galaxy.sh`; см. [§ Setup gdi32](#setup-красный-x-пустое-окно-gdi32-rca-2026-05-28) | + +Полная сводка по шрифтам и `WINEDLLOVERRIDES`: [§ Шрифты — антипаттерны](#шрифты-антипаттерны-сводка). + +--- + +## Шрифты: антипаттерны (сводка) + +Три **разных** механизма — не смешивать лечение: + +| Контур | API / лог | Профиль DLL | Скрипты | +|--------|-----------|-------------|---------| +| **Steam VGUI** (login, опросы) | `winFont` в `console_log.txt` | **`dwrite=n,b`** + `DWriteEnable=0` | `fix-steam-ui`, `setup-wine-bottle.sh`, `steam-ui` | +| **GOG web-stub** (.NET WinForms) | `FontFamilyNotFound`, `GenericSansSerif`, `GdipGetGenericFontFamilySansSerif` | **`gdiplus=n,b`** (native gdiplus) | `gog/install-gdiplus.sh`, `gog/install-corefonts.sh`, `gog/setup-bottle.sh` | +| **GOG Galaxy** (CEF, login) | обычно **не** `FontFamily`; сеть/auth | `gdiplus=n,b` при запуске клиента | `gog/launch-galaxy.sh` | +| **Inno + botva2** (`GalaxySetup.exe`) | `gdi32.dll` AV, пустой MsgBox | `gdiplus=b` **не спасает** | **не Wine-Inno** → `innoextract` / клиент Galaxy | + +### Антипаттерны (шрифты и overrides) + +| ❌ | Почему | ✅ | +|----|--------|-----| +| Копировать шрифты в `C:\windows\Fonts` **без** реестра `…\CurrentVersion\Fonts` | Wine/GDI не подхватывает файлы | `fix-steam-ui` / `gog/setup-bottle.sh` / `setup-wine-bottle.sh --fonts-only` | +| Только файлы в `Fonts/`, **без** `DWriteEnable=0` (Steam) | DWrite-заглушка, пустой VGUI | **`start.sh fix-steam-ui`** | +| Steam **без** `WINEDLLOVERRIDES=dwrite=n,b` при запуске | подгружается сломанный `dwrite.dll` | `export_wine_env_steam` в `config.sh` → `steam-ui` / `play` | +| GOG stub **без** winetricks **gdiplus** | Wine GDI+ stub → **GenericSansSerif** | **`gog/install-gdiplus.sh`** (~900 MB) | +| GOG stub с **`gdiplus=b`** (только builtin) | нет native **GenericSansSerif** | **`gdiplus=n,b`** | +| GOG stub с **`dwrite=n,b`** (профиль Steam) | не тот стек для .NET WinForms | **`export_wine_env_gog`** | +| Только **`install-corefonts`**, без **gdiplus** | `.ttf` есть, лог всё равно `FontFamilyNotFound` | оба скрипта + порядок ниже | +| **`setup-wine-bottle.sh`** для GOG web-stub | DWrite/Steam-реестр, **нет** gdiplus | **`gog/setup-bottle.sh`** | +| **`setup-gog-bottle.sh`** (HoMM) для JA3 | устаревшая обёртка | **`gog/setup-bottle.sh`** | +| **winetricks corefonts** из Whisky GUI | 404 на CDN Microsoft | SourceForge в **`gog/install-corefonts.sh`**; macOS Fonts | +| **Debian-only** URL `msttcorefonts` | 404 | primary: `downloads.sourceforge.net/corefonts/…` | +| **`winetricks liberation`** как единственный шаг | мало гарантий для VGUI/.NET | macOS + corefonts + профиль DLL | +| **`WINE_SETUP_WINETRICKS=1`** вместо наших скриптов | долго, зеркала падают | `setup-wine-bottle.sh` (macOS Fonts); GOG: `gog/*` | +| **`wineboot -k`** «обновить шрифты» в HoMM-bottle | сбрасывает Steam/CEF | GOG: `gog/setup-bottle.sh --fonts-only`; HoMM: `--fonts-only` без `-k` | +| GOG в **HoMM-bottle** «шрифты уже есть от Steam» | **`export_wine_env_steam`**, чужие `Steam/resource/fonts` | отдельный bottle **`GOG-Install`** | +| Считать **GalaxySetup** пустым окном проблемой **шрифтов** | это **gdi32 + botva2**, не Arial | [§ Setup gdi32](#setup-красный-x-пустое-окно-gdi32-rca-2026-05-28) | +| **`run-wine-exe.sh --profile plain`** для .NET installer | нет overrides | **`--profile gog`** или **`steam`** по таблице выше | +| Запуск stub **без** проверки лога | «OK» в shell, процесс уже умер | `grep -i FontFamily /tmp/gog-web-installer-*.log` | +| **`launch-galaxy.sh` OK**, окна нет | Second client / lock | `gog/reset.sh` → снова launch; [§ GOG Galaxy — окно](#gog-galaxy-окно-не-открывается) | +| Повторный launch без **`reset.sh`** | `Second client instance` → exit | `reset` убивает процессы и `lock-files/*.lock` | + +### Порядок (GOG-bottle, web-stub) + +```bash +bash …/gog/setup-bottle.sh # macOS Fonts + реестр + сеть +bash …/gog/install-gdiplus.sh # native gdiplus, gdiplus=n,b +brew install cabextract # для corefonts +bash …/gog/install-corefonts.sh # arial32 / times32 / andale32 +bash …/gog/run-web-installer.sh …/GOG_Galaxy_*.exe +``` + +Маркеры: `.gog_gdiplus_installed`, `.gog_corefonts_installed`, `.gog_setup_v1`. + +### Порядок (HoMM-bottle, Steam VGUI) + +```bash +bash …/setup-wine-bottle.sh # один раз +bash …/start.sh fix-steam-ui # DWriteEnable=0 + шрифты +bash …/start.sh reset && steam-ui # перезапуск с dwrite=n,b +``` + +### Проверка + +```bash +# Steam +grep winFont …/Steam/logs/console_log.txt | tail -5 + +# GOG stub +grep -i FontFamily /tmp/gog-web-installer-GOG-Install.log || echo "OK: нет FontFamily" + +# Сколько файлов в Fonts (не доказательство gdiplus!) +ls …/drive_c/windows/Fonts | wc -l +test -f …/Bottles//.gog_gdiplus_installed && echo "gdiplus marker OK" +``` + +Детали: [§ Кривой UI Steam](#кривой-ui-steam-пустой-vgui) · [§ GOG web-stub](#gog-web-stub-отдельный-bottle) · [WINE_MAC_RUNBOOK.md §2](./WINE_MAC_RUNBOOK.md). + +--- + +### Канонический контур (GOG web-stub / JA3) + +**Проверено 2026-05-28:** stub `GOG_Galaxy_Jagged_Alliance_3.exe` в bottle **`GOG-Install`** — окно с подписями, скачивание `GalaxySetup.exe` (~99 MB). + +```bash +# один раз (создаёт bottle, gdiplus ~900 MB, шрифты, запускает stub): +bash ~/code/local_machine/scripts/games/gog/install-all.sh \ + ~/path/to/GOG_Galaxy_Jagged_Alliance_3.exe + +# или по шагам — см. scripts/games/gog/README.md +``` + +После stub: **Cmd+Tab** → установщик → дождаться JA3 (**≥20 GB** на диске). + +### Канонический контур (HoMM / Steam) + +1. **`killall steam_osx ipcserver`** (если был Mac Steam) +2. **Whisky** → **HoMM-OldenEra** → **Run** → **Logged On** (или Offline) +3. **`bash ~/code/local_machine/scripts/games/start.sh play`** +4. В игре: **Загрузить** → skirmish → **1 / 2 / 3** + +**Recovery CEF/UI:** + +```bash +osascript -e 'quit app "Whisky"' 2>/dev/null; sleep 2 +pkill -9 -f 'SteamService.exe|steamwebhelper|38412122|Steam.exe' 2>/dev/null +bash ~/code/local_machine/scripts/games/start.sh reset +open -a Whisky +# Run Steam только из Whisky — не steam-ui параллельно +``` + +--- + +## Кривой UI Steam (пустой VGUI) + +**Кратко:** Steam в Wine рисует кнопки/radio, но **без текста** → login/applaunch зависают. Лечится **один раз** `fix-steam-ui`, затем всегда запуск через **`steam-ui`** (не `-silent`). + +### Симптомы + +- Тёмное окно: **5 radio слева**, **2 кнопки внизу**, подписей нет. +- Иногда после частичного фикса: **Connection Problem** / login **с текстом** — это уже другой слой (сеть), VGUI-фикс сработал. +- Это **Steam VGUI**, не Heroes Olden Era. + +Лог `…/Steam/logs/console_log.txt`: + +```text +src\vgui2\src\surface_gdiwin32.cpp (1336) : winFont +``` + +(много строк подряд = шрифты/DWrite всё ещё ломают отрисовку) + +### Причина + +| Слой | Что ломается | +|------|----------------| +| **DirectWrite** | Steam на Windows ждёт DWrite; в WhiskyWine заглушка → GDI не рисует глифы | +| **GDI / winFont** | VGUI-диалоги (опрос, interstitial, часть login) остаются «пустыми» | +| **`-silent`** | Форсит `desktopui`/VGUI — усугубляет проблему | + +Только копировать шрифты в `Fonts/` **без** `DWriteEnable=0` и **без** `dwrite=n,b` — **недостаточно** → [§ Шрифты — антипаттерны](#шрифты-антипаттерны-сводка). + +--- + +### Решение (канон, один раз + перезапуск) + +```bash +osascript -e 'quit app "Steam"' 2>/dev/null || true +bash ~/code/local_machine/scripts/games/start.sh fix-steam-ui +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh steam-ui +``` + +#### Шаг A — `fix-steam-ui` (скрипт `fix-homm-steam-ui.sh`) + +| # | Действие | Зачем | +|---|----------|--------| +| 1 | `HKCU\Software\Valve\Steam` → **`DWriteEnable = 0`** | Steam не использует сломанный DWrite в Wine | +| 2 | Копия **Arial, Tahoma, Verdana, Times, Courier, Marlett, GoNoto** → `drive_c/windows/Fonts/` | GDI находит базовые шрифты | +| 3 | При каждом `steam-ui` / `play`: **`WINEDLLOVERRIDES=dwrite=n,b`** (`config.sh` → `export_wine_env_steam`) | Wine не подгружает native dwrite.dll | + +Повторять `fix-steam-ui` нужно только после **нового bottle** или если вручную сбросили реестр Wine. + +#### Шаг B — `reset` + +Обязателен после фикса: старый `Steam.exe` стартовал **без** новых overrides и с кэшем VGUI. + +#### Шаг C — `steam-ui` + +| ✅ Правильно | ❌ Неправильно | +|-------------|----------------| +| `bash ~/code/local_machine/scripts/games/start.sh steam-ui` | `Steam.exe -silent` | +| Whisky → Run Steam → **`start.sh play`** | **Play** у Olden Era в Steam UI (нет DXVK env) | +| `start.sh play` при **Logged On** | `play` / `game` при **Logged Off** | +| `start.sh game` (= то же что `play` при login) | `HOMM_GAME_FORCE=1` для сейвов | + +--- + +### Проверка, что UI починился + +1. **Визуально:** login / **Connection Problem** / библиотека — **есть читаемый текст** (не пустые прямоугольники). +2. **Лог:** после нового запуска в `console_log.txt` **нет** новой пачки `winFont` при открытии того же диалога. +3. **Login:** в `connection_log.txt` eventually `Logged On` или осмысленный **Retry** / **Offline Mode** (см. [§ Connection Problem](#connection-problem-steam-не-подключается-к-серверам)). + +```bash +tail -20 ~/Library/Containers/com.franke.Whisky/Bottles/38412122-*/drive_c/Program\ Files\ \(x86\)/Steam/logs/console_log.txt | grep winFont || echo "winFont нет — хорошо" +``` + +*(Если bottle ещё в `com.isaacmarovitz.Whisky` — замените префикс контейнера.)* + +--- + +### Если текст **всё ещё** пустой (обход до перезапуска) + +Диалог **вслепую** (опрос GPU / interstitial **до** login): + +1. Верхний radio — оставить (дефолт). +2. **Правая** кнопка внизу → OK / Continue / Launch. +3. Или: `Tab` несколько раз → `Enter`. + +Затем снова **`reset` + `fix-steam-ui` + `steam-ui`** — часто VGUI остаётся пустым, пока висит процесс от старой сессии. + +--- + +### После починки UI → сейвы + +Пустой UI и **Connection Problem** — разные проблемы. Когда текст появился, но сейвы пустые → [§ Сейвы](#игра-запустилась-сейвы-не-подтянулись). + +```bash +bash ~/code/local_machine/scripts/games/start.sh diagnose-saves +``` + +--- + +### Не путать окна + +| Окно | Признак | +|------|---------| +| **Steam VGUI (кривой)** | Серый flat UI, 5 radio, 2 кнопки **без** текста | +| **Steam CEF (норм)** | Login, Connection Problem, библиотека — **с текстом** | +| **Heroes Olden Era** | 3D, Unfrozen, меню игры | + +--- + +## Нет окна Steam UI + +### Whisky пустой / UI не появляется из Terminal + +Bottle мог быть в **старом** контейнере `isaacmarovitz` — **Whisky 3 (franke) его не видит** → окна нет. + +**Один раз:** + +```bash +bash ~/code/local_machine/scripts/games/migrate-bottle-to-franke-whisky.sh +``` + +Затем: + +1. **Открыть Whisky.app** — в списке должен быть **HoMM-OldenEra**. +2. В bottle: **Run** → `C:\Program Files (x86)\Steam\Steam.exe` (или закреплённый Steam). +3. Либо Terminal (если окно появилось): + +```bash +killall steam_osx ipcserver 2>/dev/null +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh steam-ui +``` + +Диагностика: + +```bash +bash ~/code/local_machine/scripts/games/start.sh diagnose-steam-ui +``` + +### Симптом + +`bash …/start.sh steam-ui` — в Terminal пара строк и **нет окна**; или окно не найти. + +### Диагностика + +```bash +bash ~/code/local_machine/scripts/games/start.sh diagnose-steam-ui +``` + +| Строка | Плохо | Что делать | +|--------|-------|------------| +| Mac Steam | ❌ steam_osx | `killall steam_osx ipcserver` (см. § Connection Problem) | +| Steam.exe | ❌ | `reset` → `steam-ui` снова | +| steamwebhelper | ❌ | CEF не поднялся — см. ниже | +| winFont | ⚠️ | `fix-steam-ui` | + +### Запуск (копипаст по порядку) + +```bash +killall steam_osx ipcserver 2>/dev/null +bash ~/code/local_machine/scripts/games/start.sh fix-steam-ui +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh steam-ui +``` + +- Terminal **зависнет** на `steam-ui` — так и должно быть, Steam работает. +- Окно искать: **Cmd+Tab** (wine64), **Mission Control** — не в Whisky. +- Если `diagnose-steam-ui` показывает ❌ steamwebhelper — **CEF не поднялся** (см. [§ CEF / steamwebhelper](#cef--steamwebhelper-пустой-ui-после-update)): + +```bash +osascript -e 'quit app "Whisky"' 2>/dev/null; sleep 2 +pkill -9 -f 'SteamService.exe|steamwebhelper|38412122|Steam.exe' 2>/dev/null +bash ~/code/local_machine/scripts/games/start.sh reset +open -a Whisky +# HoMM-OldenEra → Run Steam (не Terminal steam-ui параллельно) +``` + +❌ **Не использовать** `HOMM_STEAM_VDESKTOP=1` — ухудшает CEF (`Failed to set primary display settings`). + +- Тёмное окно **без текста** — [§ Кривой UI Steam](#кривой-ui-steam-пустой-vgui): Tab → Enter или правая кнопка. + +--- + +## CEF / steamwebhelper (пустой UI после update) + +### Симптомы + +- Whisky пишет «Launched: steam.exe», но **нет библиотеки / login** (чёрное окно или только VGUI). +- `diagnose-steam-ui`: ❌ **steamwebhelper**. +- `cef_log.txt`: + +```text +message_window.cc: Failed to create a message-only window: Class does not exist +``` + +→ **crash loop** steamwebhelper → нет CEF UI. + +### Типичные триггеры (антипаттерны) + +1. **`Steam.exe -applaunch 3105440`** при **Logged Off** (первый applaunch без UI). +2. Параллельный запуск **Terminal + Whisky**. +3. Зомби **`SteamService.exe` / `steamwebhelper`** после `reset` со старыми `pkill`-паттернами. +4. Авто-update Steam client (утренний build ~242 MB) + грязная сессия. + +### Recovery (единственный надёжный порядок) + +```bash +killall steam_osx ipcserver 2>/dev/null +osascript -e 'quit app "Whisky"' 2>/dev/null; sleep 2 +pkill -9 -f 'SteamService.exe|steamwebhelper|38412122|Steam.exe' 2>/dev/null +bash ~/code/local_machine/scripts/games/start.sh reset +pgrep -lf Steam || echo "OK: процессов Steam нет" +open -a Whisky +``` + +В Whisky: **HoMM-OldenEra** → **Run** на `Steam.exe` — **не** `steam-ui` из Terminal одновременно. + +Если после чистой сессии CEF всё ещё мёртв: + +- Заблокировать авто-update: `steam.cfg` с `BootStrapperInhibitAll=1` в папке Steam. +- Откат Steam client на предыдущий build (операторский скрипт `downgrade-steam-client.sh` — по запросу). + +Проверка login: + +```bash +tail -3 ~/Library/Containers/com.franke.Whisky/Bottles/38412122-*/drive_c/Program\ Files\ \(x86\)/Steam/logs/connection_log.txt +``` + +Нужна строка **`Logged On`**, не `Logged Off`. + +--- + +## Игра запустилась — сейвы не подтянулись + +- Окно Heroes открывается, меню **Загрузить** пустое. +- На Mac файлы **есть**: `1.saveskirmish`, `2.saveskirmish`, `3.saveskirmish`. +- В `Player.log`: + +```text +SteamApi_Init failed with FailedGeneric - error: ConnectToGlobalUser failed. +Store state changed to Failed +``` + +### Причина (корень) + +Сейвы лежат в `users/Steam_/saves/singleplayer/`, но **слоты в UI** требуют живый **Steam-клиент в том же bottle** (`Logged On`). Прямой exe обходит Steam → Store не инициализируется. + +### Диагностика одной командой + +```bash +bash ~/code/local_machine/scripts/games/start.sh diagnose-saves +``` + +| Проверка | OK | Плохо | +|----------|-----|-------| +| Файлы `*.saveskirmish` | 3 файла в singleplayer | папка пустая | +| `connection_log.txt` | `[Logged On, …]` | `[Logged Off, 0, 0]` | +| `Player.log` | нет `SteamApi_Init failed` | `ConnectToGlobalUser failed` | + +### Рабочий цикл (сейвы 1 / 2 / 3) + +1. Steam в bottle: **Logged On** (Whisky → Run или `steam-ui`). +2. **Не** жмите Play в Steam UI. + +```bash +bash ~/code/local_machine/scripts/games/start.sh play +bash ~/code/local_machine/scripts/games/start.sh diagnose-saves +``` + +В игре: **Загрузить** → **skirmish / одиночная** (не кампания) → слоты **1**, **2**, **3**. + +Путь к файлам: + +```bash +bash ~/code/local_machine/scripts/games/start.sh saves +``` + +--- + +## Запуск игры (канон) + +| Шаг | Действие | +|-----|----------| +| 1 | Mac Steam **выключен** (`pgrep -x Steam` → пусто) | +| 2 | Wine-Steam **Logged On** (Whisky → Run или `steam-ui` → Retry / Offline) | +| 3 | **`bash ~/code/local_machine/scripts/games/start.sh play`** — **не** кнопка Play в Steam | + +`play` при **Logged On** вызывает **`game`** (DXVK-macOS + Unity windowed). Это тот же контур, что работал «утром». + +### Если игра падает на D3D11 + +`Player.log`: `GfxDevice: creating device client` / `d3d11.dll` — вы запустили через **Steam UI Play**, не через `play`. + +```bash +bash ~/code/local_machine/scripts/games/start.sh diagnose +bash ~/code/local_machine/scripts/games/start.sh play +``` + +Альтернатива (явный exe, Steam не закрывать): + +```bash +bash ~/code/local_machine/scripts/games/start.sh game +``` + +--- + +## Нет прогресса: `play` не доходит до игры + +### Чеклист по логам + +| Проверка | Где | Плохо | Хорошо | +|----------|-----|-------|--------| +| Steam login | `connection_log.txt` | `Logged Off`, `steamid=0` | `Logged On` | +| applaunch | `console_log.txt` | нет `GameAction` / `Game process added` | `AppID 3105440 … HeroesOldenEra.exe` | +| Steam API | `Player.log` | `SteamApi_Init failed` | нет ошибки Store | + +### Рабочий цикл + +```bash +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh fix-steam-ui +bash ~/code/local_machine/scripts/games/start.sh steam-ui +# login / Offline Mode → затем play (не Play в Steam UI) +bash ~/code/local_machine/scripts/games/start.sh play +``` + +--- + +## Захламление swap и утечка winedbg + +### Симптомы + +| Признак | Что это | +|---------|---------| +| Terminal: **`fork failed: resource temporarily unavailable`** | ядро не создаёт процессы — часто **swap исчерпан** | +| `sysctl vm.swapusage` → **used ≈ total** (например 22/23 GB) | macOS вынесла память в swap; система «тяжёлая» | +| **`pgrep winedbg \| wc -l`** → сотни / тысячи | отладчики Wine после **assert/crash Steam** (не от GOG stub) | +| `wineserver` из **`com.isaacmarovitz.Whisky`** при bottle во **franke** | legacy-процесс, лишний CPU | +| Диск **98%**, «пропало 20 GB» | чаще **не одна** операция, а swap + дубликаты файлов (см. ниже) | + +GOG web-stub **сам по себе** почти не ест RAM: при `FontFamilyNotFound` процесс **сразу выходит**; загрузка JA3 **не начинается**. + +### Типичная цепочка (антипаттерн) + +1. Bottle **HoMM-OldenEra** (Steam + HoMM + эксперименты с GOG в том же префиксе). +2. Steam CEF / VGUI в цикле → `Steam/dumps/assert_steam.exe_*.dmp` (сотни MB). +3. На каждый assert Wine поднимает **`winedbg --auto`** → сотни процессов → **swap**. +4. Параллельно Cursor, Zoom, GOG Galaxy CEF → усиление. +5. Любой `bash` / `brew` → **`fork failed`**. + +Это **не** «JA3 скачал 20 GB», а **давление памяти + мусор на диске**. + +### Что реально занимает диск (проверка) + +```bash +# swap +sysctl vm.swapusage + +# утечка процессов +pgrep winedbg | wc -l +pgrep -lf wineserver + +# HoMM-bottle (если GOG гоняли там — будет мусор) +BOTTLE=~/Library/Containers/com.franke.Whisky/Bottles/38412122-AAE1-4831-9F2E-6A0EB364B3F9 +du -sh "$BOTTLE/drive_c/Program Files (x86)/Steam/dumps" 2>/dev/null +du -sh "$BOTTLE/drive_c/setup_galaxy.exe" 2>/dev/null # дубликат ~277M — можно rm +du -sh ~/code/local_machine/scripts/games/cache/setup_galaxy.exe 2>/dev/null +``` + +| Путь | Ориентир | Действие | +|------|----------|----------| +| `Steam/dumps/*.dmp` | до **~400 MB** | удалить старые `.dmp` (Steam пересоздаст при новом crash) | +| `drive_c/setup_galaxy.exe` | **~277 MB** | `rm` если есть копия в `scripts/games/cache/` | +| `cache/setup_galaxy.exe` | **~277 MB** | оставить один экземпляр | +| Swap **used** | десятки GB | см. экстренный сброс ниже | + +### Экстренный сброс (копипаст) + +```bash +# 1) Убить зомби-отладчики (безопасно для данных bottle) +pkill -9 winedbg 2>/dev/null +pkill -9 wineserver 2>/dev/null + +# 2) HoMM/Steam bottle — только если не нужен сейчас живой Steam +bash ~/code/local_machine/scripts/games/start.sh reset + +# 3) Дампы Steam (опционально, освобождает сотни MB) +rm -f ~/Library/Containers/com.franke.Whisky/Bottles/38412122-*/drive_c/Program\ Files\ \(x86\)/Steam/dumps/*.dmp + +# 4) Дубликат установщика GOG в drive_c (если копировали в HoMM-bottle) +rm -f ~/Library/Containers/com.franke.Whisky/Bottles/38412122-*/drive_c/setup_galaxy.exe +``` + +После этого: **перезагрузка Mac** — самый надёжный способ **освободить swap** (macOS не отдаёт swap мгновенно только kill процессов). + +Профилактика: + +- **Не** гонять GOG web-stub в HoMM-bottle → [§ GOG web-stub](#gog-web-stub-отдельный-bottle). +- После серии падений Steam: `reset` + очистка `Steam/dumps`. +- Держать **≥15–20 GB свободно** на Data перед установкой игр (не путать с «занятым swap»). + +--- + +## GOG web-stub (отдельный bottle) + +Файл `GOG_Galaxy_*.exe` (~500 KB) — загрузчик полного установщика с gog.com, **не** игра. Скачивает `GalaxySetup.exe` и ставит игру через GOG installer в Wine. + +**Не путать с:** GOG Galaxy 2.0 (лаунчер в HoMM-bottle, `start.sh gog`, логин через CEF) — к web-stub **не** относится. + +### Что сработало (RCA 2026-05-28, JA3) + +| Попытка | Результат | +|---------|-----------| +| HoMM-bottle + `setup-wine-bottle.sh` + stub | `FontFamilyNotFound` / пустые кнопки; в логе **FATAL** `GenericSansSerif` | +| Тот же bottle + 26 шрифтов (macOS + corefonts) **без gdiplus** | снова `FontFamilyNotFound` — файлы есть, Wine **не отдаёт** generic font family | +| Bottle **`GOG-Install`** + `install-gdiplus.sh` + `install-corefonts.sh` + stub | **OK:** процесс жив, подписи в UI, в `Temp/GalaxyInstaller_*/GalaxySetup.exe` **~99 MB** | + +**Корневая причина:** .NET WinForms вызывает `GdipGetGenericFontFamilySansSerif` → без **native `gdiplus.dll`** (winetricks) Wine отвечает `FontFamilyNotFound`, даже при полном `windows/Fonts`. + +**Обязательный порядок в GOG-bottle:** + +1. `gog/create-bottle.sh` или `gog/use-bottle.sh ` → `bottle.env` +2. `gog/setup-bottle.sh` — шрифты macOS + реестр + сеть Wine +3. **`gog/install-gdiplus.sh`** — winetricks, ~900 MB скачивание, `gdiplus=n,b` +4. `gog/install-corefonts.sh` — SourceForge `arial32` / `times32` / `andale32` +5. `gog/run-web-installer.sh` путь к `GOG_Galaxy_*.exe` + +Одной командой: `bash …/gog/install-all.sh /path/GOG_Galaxy_*.exe` + +### Проверка успеха (после запуска stub) + +```bash +# процесс +pgrep -fl 'GOG_Galaxy_Jagged' + +# лог без FontFamily (имя bottle в пути) +grep -i FontFamily /tmp/gog-web-installer-GOG-Install.log || echo "OK: нет FontFamily" + +# скачан полный setup (путь меняется: GalaxyInstaller_) +find ~/Library/Containers/com.franke.Whisky/Bottles -path '*/GalaxyInstaller_*/GalaxySetup.exe' -ls 2>/dev/null +``` + +| Признак | Значение | +|---------|----------| +| Лог | `/tmp/gog-web-installer-GOG-Install.log` — нет `FontFamily could not be found` | +| Temp | `…/users/eternal/AppData/Local/Temp/GalaxyInstaller_*/GalaxySetup.exe` растёт (~100 MB+) | +| Окно | **Cmd+Tab** → `GOG_Galaxy_Jagged_Alliance_3.exe` (не в окне Whisky) | +| Bottle | только `gog/bottle.env` — **не** смотреть `Steam/dumps` в HoMM-bottle | + +### Антипаттерны (кратко) + +Полный список по шрифтам: [§ Шрифты — антипаттерны](#шрифты-антипаттерны-сводка). + +| ❌ | ✅ | +|----|-----| +| HoMM-bottle + `setup-wine-bottle.sh` + stub | `gog/install-all.sh` или `gog/README.md` | +| Только corefonts, **без** gdiplus | `setup-bottle` → **`install-gdiplus`** → `install-corefonts` | +| `dwrite=n,b` или `gdiplus=b` для stub | **`gdiplus=n,b`** (`export_wine_env_gog`) | +| GalaxySetup пустой X = «добавить шрифты» | **gdi32/Inno** → `install-galaxy-extract` + `launch-galaxy` | +| Смотреть `Steam/dumps` | `/tmp/gog-web-installer-*.log`, `gog/diagnose-session.sh` | +| `start.sh reset` для GOG | `bash …/gog/reset.sh` | +| 20 GB «съел stub» при `FontFamily` в логе | падение **до** загрузки игры — [§ Swap](#захламление-swap-и-утечка-winedbg) | + +### Симптомы и фикс + +| Симптом | Причина | Действие | +|---------|---------|----------| +| Пустые radio/кнопки, окно есть | нет corefonts **или** нет gdiplus | `install-gdiplus.sh` **и** `install-corefonts.sh` | +| `FontFamilyNotFound` / `GenericSansSerif` | нет native **gdiplus** (типично) | `bash …/gog/install-gdiplus.sh` → снова stub | +| Скрипт пишет OK, окна нет | см. [§ GOG Galaxy — окно](#gog-galaxy-окно-не-открывается) | `reset.sh` → `launch-galaxy.sh` | +| «Installer Already Running» | второй stub | `gog/reset.sh` | +| Нет `bottle.env` | GOG-bottle не создан | `gog/create-bottle.sh` или `gog/use-bottle.sh ` | +| Stub завис на загрузке | мало места на Data | `df -h` → **≥20 GB** свободно | + +### GOG Galaxy: окно не открывается + +**RCA за 7 часов (2026-05-28):** [GOG_GALAXY_7H_RCA.md](GOG_GALAXY_7H_RCA.md) — хронология запусков, что **подтверждено в логах** (процесс + login form), и что **не** подтверждено (видимое окно на macOS). + +Собрать логи: `bash …/gog/collect-gog-logs-7h.sh` → `temp/gog-logs-7h-*`. + +`launch-galaxy.sh` пишет OK, а окна нет — чаще всего **не «нет приложения»**, а **CEF/HWND в Wine** или второй экземпляр убил новое окно. + +| Лог (`GalaxyInitialization.log`) | Что значит | +|----------------------------------|------------| +| `Second client instance detected` → `The client will exit` | висел старый `GalaxyClient` / lock; **новое окно не создаётся** | +| `will continue` + в `ps` есть `GalaxyClient.exe` | клиент **жив** — **Cmd+Tab** → `GalaxyClient` / `wine64` | +| `Failed to locate Galaxy Client window` | restore не нашёл окно (часто после «Second client») | +| `DeelevateStrategy` / `Privilege de-elevation` / exit **-31** | нет `/deelevated` или сервис 1053 | `launch-galaxy.sh` (уже передаёт `/runWithoutUpdating /deelevated`) | + +**Канон (2026-05-28):** + +```bash +# 1) Whisky GUI (лучше для окна на Mac): +bash ~/code/local_machine/scripts/games/gog/launch-galaxy-whisky.sh +# → pin «gog-galaxy» → Run + +# 2) Терминал (процесс; окно может не появиться): +bash ~/code/local_machine/scripts/games/gog/reset.sh +bash ~/code/local_machine/scripts/games/gog/launch-galaxy.sh + +# 3) Если окна нет — foreground + vdesktop: +bash ~/code/local_machine/scripts/games/gog/launch-galaxy-ui.sh +``` + +В `GalaxyClient.log` бывает `Successfully brought window to the foreground`, а на Mac пусто — CEF в `cef.log`: `Received message from source not matching parent window` (HWND родителя в Wine). **Виртуальный desktop** (`explorer /desktop=GOGGalaxy,1280x800`) даёт отдельное окно Wine; **foreground** (`launch-galaxy-ui.sh`) привязывает его к Terminal. + +RCA: `bash …/gog/diagnose-galaxy-window.sh` + +Один раз: `bash …/gog/pin-galaxy-whisky.sh` → Whisky → bottle **GOG-Install** → pin **gog-galaxy** → Run. + +A/B CEF: `GOG_CEF_PROFILE=steam bash …/launch-galaxy-ui.sh` (добавляет `-cef-in-process-gpu`, как Steam). + +| ❌ Антипаттерн | ✅ | +|----|-----| +| `reset.sh` старый (не убивал `GalaxyClient.exe`) | обновлённый `gog/reset.sh` + `gog-process.sh` | +| Повторный `launch-galaxy` без reset | всегда `reset` → `launch` | +| Смотреть только Whisky, не Cmd+Tab | окно Wine **вне** главного окна Whisky | +| Запуск из `~/code/local_machine` без `cd drive_c` | `launch-galaxy.sh` сам ставит cwd в `drive_c` | + +**JA3 на Mac:** установка — offline backup + `install-ja3-innoextract.sh` (Whisky GOG-Install). **Запуск — CrossOver D3DMetal:** `setup-ja3-crossover.sh` + `run-ja3-crossover.sh`. Whisky `run-ja3.sh` **не** даёт рабочую D3D12. Runbook: [JA3_RUNBOOK.md](JA3_RUNBOOK.md). + +### Setup: красный X, пустое окно, gdi32 (RCA 2026-05-28) + +После успешного **web-stub** (скачан `GalaxySetup.exe` ~290 MB) второй этап — **Inno Setup + botva2.dll**. В Wine типично: + +| Лог Inno (`Setup Log … #00N.txt`) | Значение | +|-----------------------------------|----------| +| `Found GalaxyClientExePath` + `Showing Galaxy already running dialog` | рядом висит `GalaxyInstaller.exe` или в реестре уже есть путь к Galaxy | +| `GalaxyClient registry found, but executable returned an empty version` | Wine не отдаёт `FileVersion` для `GalaxyClient.exe` | +| `Access violation … gdi32.dll` Read of address `00000068` | отрисовка MsgBox (текст пустой — тот же класс багов, что и пустой UI) | + +**Рабочий обход (проверено):** + +1. **GOG Galaxy** — `innoextract` + VC++ redist из `setup_galaxy.exe`, **не** `GalaxySetup.exe` в Wine: + ```bash + bash ~/code/local_machine/scripts/games/gog/install-galaxy-extract.sh + ``` +2. **Jagged Alliance 3** — из клиента Galaxy после входа (CEF, не botva2): + ```bash + bash ~/code/local_machine/scripts/games/gog/launch-galaxy.sh + # → Cmd+Tab → GalaxyClient → Install JA3 + ``` + Или одной цепочкой: `bash …/gog/finish-ja3-install.sh` + +Web-stub + `GalaxySetup /VERYSILENT` на этом Wine **не доводят** установку до конца (gdi32 на любом bottle). + +Снять логи: `bash …/gog/collect-setup-rca.sh` → `temp/gog-setup-rca-*` + +### Повторный запуск stub + +```bash +bash ~/code/local_machine/scripts/games/gog/reset.sh +# Предпочтительно без stub: +bash ~/code/local_machine/scripts/games/gog/finish-ja3-install.sh +# Или принудительно stub (часто упирается в gdi32 на GalaxySetup): +GOG_USE_WEB_STUB=1 bash ~/code/local_machine/scripts/games/gog/install-all.sh \ + ~/code/hunabapp-dev/temp/GOG_Galaxy_Jagged_Alliance_3.exe +``` + +Диагностика: + +```bash +bash ~/code/local_machine/scripts/games/gog/diagnose-session.sh +bash ~/code/local_machine/scripts/games/gog/collect-setup-rca.sh +``` + +См. также: [`scripts/games/gog/README.md`](../../scripts/games/gog/README.md), [WINE_MAC_RUNBOOK.md §3.3](./WINE_MAC_RUNBOOK.md). + +--- + +## Steam зависает / мало места + +### Симптомы + +`Failed creating file mapping`, `CSteamEngine::BMainLoop stalled`, `wineserver crashed`, **`winedbg`** размножается → см. [§ Swap](#захламление-swap-и-утечка-winedbg). + +### Действия + +```bash +bash ~/code/local_machine/scripts/games/start.sh disk # ≥10 GB +pkill -9 winedbg 2>/dev/null +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh steam-ui +``` + +Очистка: `Steam/dumps/*.dmp`, `scripts/games/cache/*.dmg`, `Libraries.tar.gz` (после установки Wine), Корзина. + +--- + +## Графика DirectX 11 / Failed to initialize + +```bash +bash ~/code/local_machine/scripts/games/start.sh dxvk +bash ~/code/local_machine/scripts/games/start.sh play +``` + +Только **Gcenx DXVK-macOS**, не vanilla DXVK 2.5. Запуск — **`play`** (DXVK env), **не** Play в Steam UI. + +--- + +## Мышь: курсор двигается, ЛКМ нет + +```bash +bash ~/code/local_machine/scripts/games/start.sh diagnose +``` + +→ [UNITY_POINTER_MOUSE_FIX.md](./UNITY_POINTER_MOUSE_FIX.md) + +--- + +## Whisky «Zero KB» + +```bash +bash ~/code/local_machine/scripts/games/install-whiskywine-manual.sh +``` + +--- + +--- + +## Connection Problem: Steam не подключается к серверам + +### Симптомы + +Диалог **Connection Problem** — *There was a problem communicating with Steam*. Кнопки **Retry** и **Enter Offline Mode**. + +Интернет на Mac есть, но Wine-Steam **не логинится** → сейвы в игре пустые. + +### Что видно в логах + +`Steam/logs/connection_log.txt`: + +| Этап | OK | Плохо | +|------|-----|-------| +| HTTP probe | `Connectivity test … OK!` | timeout | +| CM / WebSocket | `ConnectionCompleted` → `Logged On` | `ConnectionDisconnected('Disconnected By Remote Host')` | +| console_log | — | `LogonFailure No Connection` | + +Утром (04:08–04:10) было **2 обрыва** WebSocket, на **3-й** попытке (CM `cmp2-lhr1.steamserver.net:443`) — `Logged On`. Днём сессии часто **не доходят** до CM, если параллельно открыт Mac Steam. + +### Причины (по частоте) + +1. **Запущен нативный Steam.app** на Mac (сейчас типичный случай) — конфликт с bottle. +2. **WebSocket к Steam CM** нестабилен в Wine — нужны повторные Retry (как в 04:08–04:10). +3. VPN / смена сети — CM отваливается (`Disconnected By Remote Host`). +4. IPv6 в Wine падает (не критично, если WebSocket:443 жив). + +### Что делать (по порядку) + +Команды ниже — **копировать в Terminal целиком по шагам** (путь абсолютный, переменная `G` не нужна). + +**1. Полностью закрыть Mac Steam** + +Удаление **Steam.app** из `/Applications` **не убирает** клиент: он часто остаётся в +`~/Library/Application Support/Steam/Steam.AppBundle/` и процессах `steam_osx` / `ipcserver`. + +```bash +killall steam_osx 2>/dev/null +killall ipcserver 2>/dev/null +pkill -f 'Steam.AppBundle/Steam/Contents/MacOS' 2>/dev/null +pgrep -f 'Steam.AppBundle/Steam/Contents/MacOS/steam_osx' >/dev/null && echo "⚠️ Mac Steam ещё жив" || echo "OK: Mac Steam остановлен" +``` + +(Если был установлен: `osascript -e 'quit app "Steam"'` — при ошибке `-128` используйте `killall` выше.) + +**2. Перезапуск bottle** + +```bash +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh steam-ui +``` + +**3. В диалоге Connection Problem** (окно Wine-Steam) + +- **Retry** — 2–3 раза, пауза **15–30 с** между попытками. +- Если не коннектится — **Enter Offline Mode**. + +Offline Mode для Heroes **нормален**: сейвы **локальные** (`1/2/3.saveskirmish` уже на диске), облако Steam не обязательно. + +После **Logged On** или **Offline Mode**: + +```bash +bash ~/code/local_machine/scripts/games/start.sh play +``` + +❌ **Не** нажимать **Play** у Olden Era в библиотеке Steam — applaunch без game-env → D3D11 crash. + +**4. Проверка** + +```bash +bash ~/code/local_machine/scripts/games/start.sh diagnose-saves +``` + +Нужно **не** `Logged Off`, а **`Logged On`** или Steam в offline с поднятым `Steam.exe`. + +**Всё одним блоком** (шаг 3 — вручную в окне Steam): + +```bash +osascript -e 'quit app "Steam"' +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh steam-ui +# → Retry 2–3× или Enter Offline Mode → затем: +bash ~/code/local_machine/scripts/games/start.sh play +bash ~/code/local_machine/scripts/games/start.sh diagnose-saves +``` + +### Антипаттерн + +❌ Жать **Retry** при открытом Mac Steam — почти всегда снова Connection Problem. + +--- + +## Steam offline (ручной режим) + +1. Whisky → Kill All Bottles +2. Закрыть **Steam.app** на Mac +3. `bash ~/code/local_machine/scripts/games/start.sh steam-ui` + +--- + +## Полезные команды + +**HoMM / Steam (bottle HoMM-OldenEra):** + +```bash +bash ~/code/local_machine/scripts/games/start.sh fix-steam-ui +bash ~/code/local_machine/scripts/games/start.sh diagnose-saves +bash ~/code/local_machine/scripts/games/start.sh reset +bash ~/code/local_machine/scripts/games/start.sh steam-ui +bash ~/code/local_machine/scripts/games/start.sh play +bash ~/code/local_machine/scripts/games/start.sh saves +``` + +**GOG web-stub (отдельный bottle):** + +```bash +bash ~/code/local_machine/scripts/games/gog/install-all.sh /path/GOG_Galaxy_*.exe +bash ~/code/local_machine/scripts/games/gog/install-gdiplus.sh +bash ~/code/local_machine/scripts/games/gog/install-corefonts.sh +bash ~/code/local_machine/scripts/games/gog/run-web-installer.sh /path/GOG_Galaxy_*.exe +bash ~/code/local_machine/scripts/games/gog/diagnose-session.sh +bash ~/code/local_machine/scripts/games/gog/reset.sh +``` + +**Память / swap:** + +```bash +sysctl vm.swapusage +pgrep winedbg | wc -l +pkill -9 winedbg; pkill -9 wineserver +``` + +Логи: + +``` +# Steam / HoMM +~/Library/Containers/…/Bottles/38412122-…/drive_c/Program Files (x86)/Steam/logs/ +~/Library/Containers/…/…/AppData/LocalLow/Unfrozen/HeroesOldenEra/Player.log + +# GOG web-stub (имя bottle в имени файла) +/tmp/gog-web-installer-GOG-Install.log +``` + +--- + +*Последняя сверка: 2026-05-28 (§ шрифты: сводка антипаттернов Steam dwrite / GOG gdiplus; GOG-Install stub OK; GalaxySetup → gdi32) · Whisky 3 · WhiskyWine 11* diff --git a/docs/games/UNITY_POINTER_MOUSE_FIX.md b/docs/games/UNITY_POINTER_MOUSE_FIX.md new file mode 100644 index 0000000..2c3c3b7 --- /dev/null +++ b/docs/games/UNITY_POINTER_MOUSE_FIX.md @@ -0,0 +1,48 @@ +# HoMM Olden Era: курсор двигается, клики не работают (Whisky / Wine на macOS) + +## Корневая причина (не «битая мышь») + +Современный Unity (в т.ч. ветки с Unity 6 input stack) вызывает Windows API **`EnableMouseInPointer`** и дальше ждёт сообщения **`WM_POINTER`**. В типичном Wine из Whisky эта функция сейчас **заглушка** → в `Player.log` появляется строка: + +```text +EnableMouseInPointer failed with the following error: Call not implemented. +``` + +Пока это так, **никакие** танцы с `WineBus` / SDL / виртуальным desktop / оконным fullscreen **не обязаны** починить ЛКМ: курсор может двигаться, а нажатия и hover в UI игнорируются. + +Проверка одной командой (после хотя бы одного запуска игры): + +```bash +bash ~/code/local_machine/scripts/games/diagnose-homm-mouse.sh +``` + +--- + +## Что реально работает сегодня + +### Вариант A — CrossOver (рекомендуется для Unity с этим симптомом) + +1. Установить **CrossOver** для macOS (CodeWeavers). +2. При необходимости применить готовый патч слоя Wine под `EnableMouseInPointer` (см. репозиторий с инструкцией и бинарниками под конкретную версию CrossOver): + [dabielf/crossover-unity-mouse-fix](https://github.com/dabielf/crossover-unity-mouse-fix) +3. Запускать игру **из CrossOver**, а не из «голого» WhiskyWine, если патч ставился в CrossOver. + +Важно: патч из репозитория рассчитан на **пути внутри `CrossOver.app`**, а не на `~/Library/Application Support/com.isaacmarovitz.Whisky/Libraries/`. Копирование DLL из CrossOver в Whisky **вручную** — высокий риск несовместимости ABI (краши Wine). + +### Вариант B — ждать/переезжать на Wine с upstream-фиксом + +Следить за [Wine Bug #53847](https://bugs.winehq.org/show_bug.cgi?id=53847) и обновлениями **WhiskyWine**, когда туда попадёт полноценная реализация (или использовать сборку Wine, где она уже есть), и только потом снова тестировать в Whisky. + +--- + +## Связь с нашими скриптами + +- `start.sh input` / `fix-homm-wine-input.sh` — про **HID/SDL и геймпады**, не про `EnableMouseInPointer`. +- `start.sh game` + DXVK-macOS — про **графику (D3D11)**, не про pointer stack Unity. + +--- + +## Быстрый чеклист + +1. `bash ~/code/local_machine/scripts/games/diagnose-homm-mouse.sh` +2. Если exit code **2** и есть строка про `EnableMouseInPointer` → идём в CrossOver/другой Wine, а не в «ещё один SDL-твик». diff --git a/docs/games/WINE_APPS_LAUNCH_RUNBOOK.md b/docs/games/WINE_APPS_LAUNCH_RUNBOOK.md new file mode 100644 index 0000000..504eda3 --- /dev/null +++ b/docs/games/WINE_APPS_LAUNCH_RUNBOOK.md @@ -0,0 +1,225 @@ +# Runbook: запуск Windows-приложений в Wine (Whisky) + +**Главный документ по пуску** любых `.exe` в bottle на Mac. Подготовка bottle, шрифты, GOG/сеть — [WINE_MAC_RUNBOOK.md](./WINE_MAC_RUNBOOK.md). Сбои — [TROUBLESHOOTING.md](./TROUBLESHOOTING.md). **Шрифты / пустой UI:** [TROUBLESHOOTING § шрифты](./TROUBLESHOOTING.md#шрифты-антипаттерны-сводка) (`dwrite` для Steam, `gdiplus=n,b` для GOG stub). + +- **Скрипты:** [`../../scripts/games`](../../scripts/games) +- **Универсальный запуск:** `bash …/scripts/games/run-wine-exe.sh` (см. §2) + +--- + +## 1. Новая программа — пройти по порядку + +Скопируйте чеклист; для каждого пункта есть команда ниже. + +| # | Вопрос | Действие | +|---|--------|----------| +| 1 | **Какой тип?** | Таблица §1.1 → профиль Wine | +| 2 | **Свой bottle?** | Steam/Unity/много игр → часто **да**; GOG web-stub → **отдельный** `GOG-Install`; один офлайн-setup → можно общий | +| 3 | **Место на диске** | `bash …/start.sh disk` → **≥20 GB** для установщиков/игр, **≥3 GB** для лаунчера | +| 4 | **Один раз: bottle** | Whisky → **Windows 10 64-bit** → Sync **MSync**; Wine: `install-whiskywine-manual.sh` | +| 5 | **Один раз: шрифты/сеть** | `bash …/setup-wine-bottle.sh` (HoMM/Steam bottle) или `gog/setup-bottle.sh` (GOG bottle) | +| 6 | **Установка** | Steam/GOG/офлайн `.exe` — §1.1; **не** два установщика GOG одновременно | +| 7 | **Запуск** | §2 — `run-wine-exe.sh`, `start.sh …`, или Whisky → Run | +| 8 | **Зафиксировать** | Добавить строку в §6 и при необходимости обёртку в `scripts/games/` | + +### 1.1 Тип приложения → установка и профиль + +| Тип | Примеры | Установка (один раз) | Профиль запуска (`run-wine-exe.sh`) | +|-----|---------|----------------------|-------------------------------------| +| **Steam + игра** | HoMM Olden Era | `install-steam-extract.sh`, игра из Steam | `steam` → UI; `game` → exe + DXVK | +| **GOG Galaxy (лаунчер)** | Galaxy 2.0 | `start.sh gog-install` или `gog/install-galaxy-extract.sh` | `gog` | +| **GOG web-stub** | `GOG_Galaxy_*.exe` | Только bottle `gog/` — [gog/README.md](../../scripts/games/gog/README.md) | `gog` | +| **Офлайн setup .exe** | backup с gog.com | Скопировать в `drive_c/`, запустить установщик | `plain` или `gog` | +| **GOG multipart + innoextract** | JA3 offline backup | `gog/install-ja3-innoextract.sh` — [JA3 runbook](JA3_RUNBOOK.md) | **CrossOver:** `run-ja3-crossover.sh` | +| **.NET WinForms installer** | web-stub, мастера GOG | **`gog/install-gdiplus.sh`** + corefonts | `gog` | +| **CEF (Steam/GOG UI)** | Steam.exe, GalaxyClient | CEF-флаги в `config.sh` / `gog/config.sh` | `steam` / `gog` | +| **DX11 игра (Unity и т.д.)** | Olden Era | `install-dxvk-macos.sh` | `game` | +| **Прочий Win32 .exe** | утилиты, старые игры | Обычно только setup bottle + шрифты | `plain` | + +**Inno Setup + botva2** (GOG `GalaxySetup.exe`, **JA3 offline setup**): в Wine часто **gdi32** — не полагаться на `/VERYSILENT`. Galaxy — **innoextract**; **JA3 multipart** — `gog/install-ja3-innoextract.sh`. Запуск JA3 — [JA3_RUNBOOK.md](JA3_RUNBOOK.md). См. [TROUBLESHOOTING.md § Setup](./TROUBLESHOOTING.md). + +Подробная подготовка по типам: [WINE_MAC_RUNBOOK.md §3](./WINE_MAC_RUNBOOK.md). + +--- + +## 2. Запуск (любая программа) + +### 2.1 Универсально: `run-wine-exe.sh` + +```bash +# Справка +bash ~/code/local_machine/scripts/games/run-wine-exe.sh --help + +# Программа уже в drive_c (путь Wine) +bash ~/code/local_machine/scripts/games/run-wine-exe.sh \ + --bottle HoMM-OldenEra \ + --profile game \ + 'C:\Program Files (x86)\Steam\steamapps\common\My Game\game.exe' + +# Установщик с Mac (копируется в C:\) +bash ~/code/local_machine/scripts/games/run-wine-exe.sh \ + --bottle GOG-Install \ + --profile gog \ + ~/Downloads/Setup_Game.exe + +# Bottle GOG из gog/bottle.env (без --bottle) +bash ~/code/local_machine/scripts/games/run-wine-exe.sh --profile gog 'C:\Program Files (x86)\GOG Galaxy\GalaxyClient.exe' +``` + +| `--profile` | `WINEDLLOVERRIDES` / env | Когда | +|-------------|--------------------------|--------| +| `plain` | по умолчанию Wine | утилиты, простые exe | +| `steam` | `dwrite=n,b` | Steam.exe, VGUI | +| `gog` | `gdiplus=n,b` | GOG Galaxy, .NET installers | +| `game` | DXVK + `d3d11=n,b` | DX11/Unity после `install-dxvk-macos.sh` | + +Переменные bottle: `WINE_BOTTLE_ID=` или `WINE_BOTTLE_NAME=HoMM-OldenEra`. GOG-bottle: `GOG_BOTTLE=1` (читает `gog/bottle.env`). + +Фон + лог: + +```bash +bash …/run-wine-exe.sh --background --log /tmp/my-app.log --profile plain 'C:\app\foo.exe' +``` + +### 2.2 Через Whisky GUI + +1. Открыть bottle → **Run** → выбрать `.exe` из `drive_c` **или** с Mac (Whisky скопирует). +2. Окно: **Cmd+Tab** → `wine64` / имя процесса, не только окно Whisky. + +### 2.3 Готовые обёртки (`start.sh`) + +| Задача | Команда | +|--------|---------| +| Steam UI | `bash …/start.sh steam-ui` | +| HoMM (игра + сейвы) | `bash …/start.sh play` | +| GOG Galaxy | `bash …/start.sh gog` | +| Сброс процессов bottle | `bash …/start.sh reset` | +| Место на диске | `bash …/start.sh disk` | +| GOG bottle: клиент | `bash …/gog/launch-galaxy.sh` | +| GOG bottle: JA3 установка | `bash …/gog/install-ja3-innoextract.sh` | +| GOG bottle: JA3 запуск | `bash …/gog/run-ja3-crossover.sh` — [JA3_RUNBOOK.md](JA3_RUNBOOK.md) | + +Полный список: `bash …/start.sh` без аргументов. + +### 2.4 Вручную (если нет скрипта) + +```bash +export WINEPREFIX="$HOME/Library/Containers/com.franke.Whisky/Bottles/" +WINE64="$HOME/Library/Application Support/com.franke.Whisky/Libraries/Wine/bin/wine64" +export DYLD_LIBRARY_PATH="$HOME/Library/Application Support/com.franke.Whisky/Libraries/Wine/lib" +export WINEMSYNC=1 WINEESYNC=1 +"$WINE64" 'C:\path\app.exe' +``` + +Профили overrides — [WINE_MAC_RUNBOOK.md §4](./WINE_MAC_RUNBOOK.md). + +--- + +## 3. Инфраструктура (один раз на машину) + +| Компонент | Путь | +|-----------|------| +| Whisky 3 | `brew install --cask frankea/whisky/whisky` | +| Wine | `bash …/install-whiskywine-manual.sh` | +| Bottle | `~/Library/Containers/com.franke.Whisky/Bottles//` | +| Кэш установщиков | `scripts/games/cache/` | + +Проверка места: `bash …/start.sh disk`. + +Создание bottle: Whisky **+** → Windows 10 → имя; для GOG: `bash …/gog/create-bottle.sh GOG-Install`. + +--- + +## 4. Шаблон: добавить программу в репозиторий + +1. **Имя и bottle** — зафиксировать UUID в `config.sh` или `gog/bottle.env`. +2. **Скрипт установки** (если повторяемо) — `scripts/games/install-.sh` или секция в `gog/`. +3. **Скрипт запуска** — команда в `start.sh` *или* документированный `run-wine-exe.sh --profile …`. +4. **Строка в §6** ниже — тип, bottle, команда запуска, путь к exe. +5. **Сбои** — краткий симптом в [TROUBLESHOOTING.md](./TROUBLESHOOTING.md). + +--- + +## 5. Уже настроенные приложения + +| Приложение | Bottle | Запуск | Примечание | +|------------|--------|--------|------------| +| **Steam** | HoMM-OldenEra | `start.sh steam-ui` | Не параллельно с Steam.app | +| **HoMM Olden Era** | HoMM-OldenEra | `start.sh play` | Не Play в Steam UI; нужен DXVK — §7 | +| **GOG Galaxy 2.0** | HoMM-OldenEra | `start.sh gog` | Логин в Wine — best effort | +| **Jagged Alliance 3** | GOG-Install + CrossOver **JA3-GOG** | `gog/run-ja3-crossover.sh` | Установка: Whisky `install-ja3-innoextract.sh` ([runbook](JA3_RUNBOOK.md)) | +| **GOG Galaxy 2.0** | GOG-Install | `gog/launch-galaxy.sh` | Best effort; JA3 — **не** через Galaxy на этом Mac | +| **Любой новый .exe** | свой / общий | `run-wine-exe.sh` | §2.1 | + +--- + +## 6. Справочник скриптов + +| Файл | Назначение | +|------|------------| +| **run-wine-exe.sh** | Универсальный пуск exe в выбранном bottle | +| **start.sh** | Steam / HoMM / GOG / reset / disk / dxvk | +| **setup-wine-bottle.sh** | Шрифты + реестр + сеть (HoMM bottle) | +| **config.sh** | UUID HoMM, `export_wine_env_*` | +| **gog/** | Отдельный bottle, gdiplus, web-stub | +| **install-dxvk-macos.sh** | DX11 для Unity | + +--- + +## 7. HoMM Olden Era (ежедневно) + +Краткая выжимка; детали сейвов/графики — прежние §4–5 методички. + +### Однократно + +```bash +brew install p7zip +bash ~/code/local_machine/scripts/games/install-whiskywine-manual.sh +bash ~/code/local_machine/scripts/games/install-steam-extract.sh +bash ~/code/local_machine/scripts/games/install-dxvk-macos.sh +bash ~/code/local_machine/scripts/games/setup-wine-bottle.sh +``` + +Bottle **HoMM-OldenEra**, Sync **MSync**. + +### Каждый запуск + +Steam **Logged On** → затем: + +```bash +bash ~/code/local_machine/scripts/games/start.sh play +``` + +Не **Play** у Olden Era в окне Steam (нет DXVK). + +Альтернатива: `start.sh game`; сейвы: `start.sh saves`; диагностика: `start.sh diagnose-saves`. + +### Переменные (`start.sh game`) + +| Переменная | По умолчанию | Назначение | +|------------|----------------|------------| +| `HOMM_VDESKTOP` | `1920x1080` | Виртуальный стол Wine | +| `HOMM_USE_WINEVDESKTOP` | `1` | `0` — без explorer /desktop | +| `HOMM_UNITY_WINDOWED` | `1` | Оконный режим Unity | +| `HOMM_UNITY_EXTRA` | *(пусто)* | напр. `-popupwindow` | + +### Сейвы (singleplayer 1/2/3) + +``` +~/Library/Containers/com.franke.Whisky/Bottles/38412122-AAE1-4831-9F2E-6A0EB364B3F9/drive_c/users/eternal/AppData/LocalLow/Unfrozen/HeroesOldenEra/users/Steam_/saves/singleplayer/ +``` + +*Bottle ID: `38412122-AAE1-4831-9F2E-6A0EB364B3F9` · Whisky 3 · WhiskyWine 3* + +--- + +## 8. Шпаргалка + +```bash +G=~/code/local_machine/scripts/games +bash "$G/run-wine-exe.sh" --help +bash "$G/start.sh" disk +bash "$G/setup-wine-bottle.sh" +bash "$G/start.sh" play +bash "$G/gog/launch-galaxy.sh" +``` diff --git a/docs/games/WINE_MAC_RUNBOOK.md b/docs/games/WINE_MAC_RUNBOOK.md new file mode 100644 index 0000000..08bd7a6 --- /dev/null +++ b/docs/games/WINE_MAC_RUNBOOK.md @@ -0,0 +1,205 @@ +# Runbook: подготовка Windows-приложений в Wine на macOS (Whisky) + +Один раз: bottle, шрифты, сеть, установка по **типу** приложения. **Ежедневный запуск** любых `.exe` — [WINE_APPS_LAUNCH_RUNBOOK.md](./WINE_APPS_LAUNCH_RUNBOOK.md). + +Антипаттерны (GOG в HoMM-bottle, swap/winedbg): [TROUBLESHOOTING.md](./TROUBLESHOOTING.md). + +Источники: [Whisky](https://github.com/Whisky-App/Whisky) (архив, Apple Silicon + macOS 14+), [Whisky #1220 CEF](https://github.com/IsaacMarovitz/Whisky/issues/1220), [CodeWeavers GOG Galaxy tip](https://www.codeweavers.com/compatibility/crossover/tips/gog-galaxy/gog-galaxy-working), практика bottle на этой машине (2026-05). + +--- + +## 1. Жёсткие требования (без этого не обещаем успех) + +| Условие | Значение | +|--------|----------| +| Mac | **Apple Silicon** (M1+) | +| macOS | **14 Sonoma+** (Whisky 3 / GPTK) | +| Whisky | Установлен, bottle **Windows 10, 64-bit** | +| Свободное место | **≥20 GB** для GOG/игр (web-stub + установка); **≥3 GB** минимум для Steam/лаунчеров | +| Один лаунчер | Не запускать **нативный Steam.app** параллельно с Wine Steam | +| Один установщик | Не открывать **два** `GOG_Galaxy_*.exe` — будет «Installer Already Running» | +| GOG web-stub | **Отдельный bottle** + `scripts/games/gog/` (не HoMM/Steam) | +| Подготовка HoMM/Steam | **Один раз:** `setup-wine-bottle.sh` | + +--- + +## 2. Один раз: подготовка bottle + +```bash +bash ~/code/local_machine/scripts/games/setup-wine-bottle.sh +``` + +Делает автоматически: + +- Копирует **Arial, Tahoma, Verdana** и др. из macOS в `C:\windows\Fonts` +- По возможности `winetricks liberation` (GOG web-installer / .NET WinForms) +- Реестр: **MS Shell Dlg → Tahoma**, GenericSansSerif → Arial +- **DnsIPv6Enabled=0**, ProxyEnable=0 в bottle +- Steam **DWriteEnable=0** (пустой текст VGUI) + +Повтор при сбое шрифтов: + +```bash +bash ~/code/local_machine/scripts/games/setup-wine-bottle.sh --force +``` + +> **Не полагайтесь на `winetricks corefonts` из GUI Whisky** — зеркала Microsoft часто отдают 404 ([Whisky #515](https://github.com/Whisky-App/Whisky/issues/515)). Наш скрипт копирует шрифты с Mac. +> +> Опционально (медленно): `WINE_SETUP_WINETRICKS=1 bash …/setup-wine-bottle.sh --force` + +--- + +## 3. Типы приложений и как запускать + +### 3.1 Steam + Unity (HoMM Olden Era) + +| Шаг | Команда | +|-----|---------| +| DXVK в bottle (один раз) | `bash …/start.sh dxvk` | +| Steam UI + логин | `bash …/start.sh steam-ui` | +| Пустой текст Steam | `bash …/start.sh fix-steam-ui` | +| Игра (сейвы 1/2/3) | `bash …/start.sh play` | +| Зависло | `bash …/start.sh reset` | + +**CEF Steam (обязательно):** `-allosarches -cef-force-32bit -cef-in-process-gpu -no-cef-sandbox -cef-disable-gpu` — уже в `config.sh`. + +**Игра:** DXVK + `d3d11=n,b`. **Не** смешивать с `export_wine_env_steam` при запуске exe. + +### 3.2 GOG Galaxy 2.0 (лаунчер) + +| Шаг | Команда | +|-----|---------| +| Установка (один раз) | `bash …/start.sh gog-install` | +| Запуск | `bash …/start.sh gog` | +| Перезапуск | `bash …/start.sh gog-restart` | +| Логин сбрасывает пароль | `bash …/start.sh fix-gog-auth` + `diagnose-gog-auth` | + +**CEF GOG:** `-allosarches -cef-force-32bit -no-cef-sandbox -cef-disable-gpu` (без `-cef-in-process-gpu` — иначе CPU spin). + +**Ограничение Wine:** нативный HTTP клиент Galaxy (`auth.gog.com/token`, `cfg.gog.com`) часто **таймаут 15–20 с** → логин в клиенте **ненадёжен**. Обходы ниже в §5. + +### 3.3 GOG web-stub (~500 KB) — **отдельный bottle, без Steam** + +**Не** запускать в bottle HoMM-OldenEra (там Steam, дампы, `winedbg`). См. `scripts/games/gog/README.md`. + +```bash +# Whisky → New Bottle → Windows 10 64-bit → «GOG-Install» +bash ~/code/local_machine/scripts/games/gog/list-bottles.sh +bash ~/code/local_machine/scripts/games/gog/use-bottle.sh GOG-Install + +bash ~/code/local_machine/scripts/games/gog/create-bottle.sh GOG-Install +bash ~/code/local_machine/scripts/games/gog/setup-bottle.sh +bash ~/code/local_machine/scripts/games/gog/install-gdiplus.sh # обязательно для .NET +brew install cabextract +bash ~/code/local_machine/scripts/games/gog/install-corefonts.sh + +bash ~/code/local_machine/scripts/games/gog/run-web-installer.sh \ + ~/path/to/GOG_Galaxy_Jagged_Alliance_3.exe +``` + +Старые обёртки (`run-gog-web-installer.sh`, `setup-wine-bottle.sh` для GOG) перенаправляют в `gog/`. + +**Типичные сбои:** + +| Симптом | Действие | +|---------|----------| +| Пустые подписи / `FontFamilyNotFound` | `gog/install-corefonts.sh` → `gog/setup-bottle.sh --force` | +| «Installer Already Running» | `bash …/gog/reset.sh` | +| Нет bottle.env | `gog/use-bottle.sh ` | + +Лог: `/tmp/gog-web-installer-GOG-Install.log` + +### 3.4 GOG offline backup (рекомендуется при проблемах с сетью) + +1. На [gog.com](https://www.gog.com) → библиотека → игра → **offline backup installer** (полный `.exe`, ГБ). +2. Скопировать в bottle: `…/drive_c/Setup_Game.exe` +3. Запуск: + +```bash +source ~/code/local_machine/scripts/games/config.sh +export_wine_env +"$WINE64" 'C:\Setup_Game.exe' +``` + +Или через Whisky: Run → выбрать exe в `drive_c`. + +### 3.5 JA3 multipart — innoextract, экономия диска + +Для **Jagged Alliance 3** (и похожих GOG multipart backup): Wine-Inno **не доводит**; канон — **`gog/install-ja3-innoextract.sh`**. + +Кратко: + +1. Пять файлов в **`~/Games/`**: `setup_jagged_alliance_3_1.5.2_(80730).exe` + `-1.bin` … `-4.bin`. +2. Bottle **GOG-Install** (`gog/bottle.env`). +3. `bash …/gog/install-ja3-innoextract.sh` — распаковка в `drive_c/GOG Games/Jagged Alliance 3/`, `.bin` удаляются по мере чтения, после успеха — все архивы в `~/Games` (~15 GB). +4. Запуск: `bash …/gog/run-ja3-crossover.sh` (swap guard: см. [JA3_RUNBOOK.md](JA3_RUNBOOK.md) §4, §10). + +Полный runbook: [JA3_MAC_WITHOUT_GALAXY.md](JA3_MAC_WITHOUT_GALAXY.md). + +--- + +## 4. Переменные окружения (уже в скриптах) + +```bash +export WINEPREFIX="" +export WINEMSYNC=1 +export WINEESYNC=1 +export DYLD_LIBRARY_PATH="/Libraries/Wine/lib:…" +``` + +| Профиль | WINEDLLOVERRIDES | Когда | +|---------|------------------|--------| +| Steam / GOG UI | `dwrite=n,b` | steam-ui, gog | +| Unity / DX11 игра | `d3d11,d3d10core=n,b` | play, game | + +--- + +## 5. GOG: если «не подключается» / сброс пароля + +1. Логи: `bash …/start.sh diagnose-gog-auth` +2. `bash …/start.sh fix-gog-auth` → `gog-restart` +3. Если в логе `timeout … auth.gog.com/token` — **вход в Galaxy под Wine часто не сохраняется**; используйте **offline installer** или покупку/ключ через браузер на Mac. +4. Mac до `auth.gog.com` отвечает <1 с — проблема в **Poco/Qt в Wine**, не в пароле. + +--- + +## 6. Диагностика (чеклист) + +```bash +bash ~/code/local_machine/scripts/games/start.sh disk # место +bash ~/code/local_machine/scripts/games/start.sh reset # убить Wine Steam/GOG +pgrep -fl 'GalaxyClient|GOG_Galaxy|steam\.exe' # дубликаты +``` + +| Лог | Путь | +|-----|------| +| GOG client | `…/ProgramData/GOG.com/Galaxy/logs/GalaxyClient.log` | +| GOG CEF | `…/ProgramData/GOG.com/Galaxy/logs/cef.log` | +| Web-stub | `/tmp/gog-web-installer.log` | +| Steam | `…/Steam/logs/connection_log.txt` | + +--- + +## 7. Команды (шпаргалка) + +```bash +BASE=~/code/local_machine/scripts/games + +bash "$BASE/setup-wine-bottle.sh" # один раз +bash "$BASE/start.sh" steam-ui # Steam +bash "$BASE/start.sh" play # HoMM +bash "$BASE/start.sh" gog-install && gog # GOG Galaxy +bash "$BASE/run-gog-web-installer.sh" …/GOG_Galaxy_*.exe +bash "$BASE/start.sh" reset # всё убить в bottle +``` + +Документация: [WINE_APPS_LAUNCH_RUNBOOK.md](WINE_APPS_LAUNCH_RUNBOOK.md) · [TROUBLESHOOTING.md](TROUBLESHOOTING.md) + +--- + +## 8. Честные ограничения + +- **Whisky архивен** ([maintenance notice](https://docs.getwhisky.app/maintenance-notice)); альтернатива — [Moonshine](https://github.com/ybmeng/moonshine) (Wine 11.x). +- **GOG Galaxy login** в Wine на этой конфигурации — **best effort**, не гарантия. +- **Web-stub** требует сеть из Wine; при таймаутах — только **offline backup**. +- **Диск <10% свободно** — CEF crash loop, freeze; см. `docs/disk_cleanup/`. diff --git a/docs/git/GITEA_COMPLETE_GUIDE.md b/docs/git/GITEA_COMPLETE_GUIDE.md index 5b83bf6..fd8d8ed 100644 --- a/docs/git/GITEA_COMPLETE_GUIDE.md +++ b/docs/git/GITEA_COMPLETE_GUIDE.md @@ -1,14 +1,17 @@ -# Gitea Complete Guide - Hunab.app +# Gitea Complete Guide — local_machine -> **Для репозитория legal на сервере 206.189.35.205** см. **[GITEA_LEGAL_206_SERVER_GUIDE.md](./GITEA_LEGAL_206_SERVER_GUIDE.md)** (единый стандарт: push, пути, мониторинг). -> Ниже — гайд по Gitea для Hunab.app (основной сервер 209.38.32.21). +> **Этот гайд** — справочник по использованию Gitea для репозитория **local_machine** (документация и скрипты локальной машины). +> Сервер: **gitea.hunab.app** (209.38.32.21). +> **Быстрый старт для local_machine:** [LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md). + +> Для других репозиториев: **legal** на 206.189.35.205 — [GITEA_LEGAL_206_SERVER_GUIDE.md](./GITEA_LEGAL_206_SERVER_GUIDE.md); **hunabapp** — свои скрипты пуша в том репо. --- **Дата:** 2026-01-02 **Версия Gitea:** 1.25.3 **Статус:** ✅ Полностью развернут и работает -**Последнее обновление:** Резервный способ коммитов через временный скрипт (2026-02-16) +**Последнее обновление:** Адаптация под проект local_machine (2026-03-14) --- @@ -39,16 +42,27 @@ Gitea успешно развернут на production сервере как se ## 🚀 Развертывание -### Быстрый старт +### Быстрый старт для local_machine + +Репозиторий **local_machine** не разворачивает Gitea — он только пушит в уже работающий сервер. Первая настройка и пуш: ```bash -# Развертывание Gitea +cd /Users/eternal/code/local_machine +# 1. Создать репо на https://gitea.hunab.app (New Repository → local_machine) +# 2. Инициализация и пуш: +bash scripts/deployment/gitea/setup-and-push.sh +``` + +Подробно: **[LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)**. + +### Развертывание самого Gitea (для админов сервера) + +Скрипты развертывания Gitea на сервере находятся в других репозиториях (например hunabapp). Пример для справки: + +```bash +# Развертывание Gitea (в репо с инфраструктурой) bash scripts/deployment/gitea/deploy-gitea.sh - -# Настройка bash scripts/deployment/gitea/setup-gitea.sh - -# Тестирование bash scripts/deployment/gitea/test-gitea.sh ``` @@ -365,6 +379,26 @@ Value: 0 issue "letsencrypt.org" ## 🔄 Push репозиториев (КРИТИЧЕСКИ ВАЖНО) +### ✅ Для репозитория local_machine + +В этом проекте пуш делается из корня **local_machine**: + +```bash +cd /Users/eternal/code/local_machine +git push -u origin main +``` + +**Если из РФ push по HTTPS зависает** — переключите remote на SSH (порт 2223): + +```bash +git remote set-url origin ssh://git@gitea.hunab.app:2223/hunabgit/local_machine.git +git push -u origin main +``` + +Если и SSH недоступен — используйте [метод через bundle](#резервный-способ-push-через-bundle) ниже. Полная настройка: **[LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)**. + +--- + ### ❌ Проблема: Push через HTTPS зависает При попытке `git push` через HTTPS возникают таймауты: @@ -386,9 +420,9 @@ fatal: the remote end hung up unexpectedly POST запрос доходит до Gitea, но соединение обрывается во время передачи данных. -### ✅ Систематическое решение: Универсальный скрипт +### ✅ Универсальный скрипт пуша (в других репо) -**РЕКОМЕНДУЕТСЯ:** Используйте универсальный скрипт, который автоматически выбирает лучший метод: +В репозиториях с полной инфраструктурой (например hunabapp) используется скрипт, который автоматически выбирает метод (SSH → HTTPS → bundle): ```bash # Обычный скрипт (для работы не из РФ) @@ -398,50 +432,11 @@ bash scripts/deployment/gitea/git-push-gitea.sh [branch] bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh [branch] ``` -**⚠️ КРИТИЧЕСКИ ВАЖНО:** При работе из Российской Федерации **ПРЕДПОЧТИТЕЛЬНО** использовать скрипт с суффиксом `-via-proxy`! +**Для local_machine** такого скрипта нет — используйте `git push origin main` или SSH (см. выше). -**🇷🇺 ПРАВИЛО ДЛЯ РФ:** -- ✅ **ВСЕГДА используйте прокси-версии** для push и создания тегов из РФ -- ✅ **Ускорение в 5-10 раз** по сравнению с прямым подключением -- ✅ **Стабильное соединение** через российский прокси-сервер (149.154.64.19) -- ✅ **Избежание таймаутов** - прямое подключение из РФ часто нестабильно - -**Скрипт автоматически:** -1. Пытается SSH push (если доступен, через прокси для `-via-proxy`) -2. Пытается HTTPS push с оптимизированными настройками (только на Linux с `timeout`) -3. Использует bundle метод (всегда работает, особенно на macOS) -4. ✅ **Мгновенная верификация** - обновляет локальный tracking ref напрямую через `git update-ref` (без медленного `git fetch`) - -**Пример:** -```bash -# Push текущей ветки (по умолчанию dev) -bash scripts/deployment/gitea/git-push-gitea.sh - -# 🇷🇺 ДЛЯ РФ: Push через прокси (рекомендуется из РФ) -bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh - -# Push конкретной ветки -bash scripts/deployment/gitea/git-push-gitea.sh main -bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh main # 🇷🇺 ДЛЯ РФ - -# Режим проверки без пуша (ничего не меняет, только показывает сколько коммитов уйдёт) -bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh --dry-run dev -``` - -**Безопасность скрипта пуша:** -- Не трогает текущую ветку, HEAD, рабочую копию и локальные коммиты. -- Не выполняет: `git reset`, `git checkout --hard`, `git clean`, `git branch -D`, `push --force`. -- Единственная запись в репо: обновление `refs/remotes/origin/` только после успешного пуша и только на кончик текущей ветки (все «ждущие пуша» коммиты при этом уже на сервере). -- Перед пушем можно проверить: `--dry-run` — показывает число коммитов к пушу, ничего не отправляет и не меняет. - -**Git Alias (для удобства):** -Добавьте в `~/.gitconfig`: -```ini -[alias] - gpush = !bash -c 'cd "$(git rev-parse --show-toplevel)" && bash scripts/deployment/gitea/git-push-gitea.sh "$@"' - -``` - -Использование: `git gpush dev` +**🇷🇺 Из РФ:** +- Предпочтительно **SSH** (порт 2223) — часто стабильнее HTTPS. +- Либо метод через bundle (см. раздел ниже). ### ✅ Резервный способ: Временный скрипт для коммита и пуша @@ -453,41 +448,25 @@ bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh --dry-run dev **Процесс:** 1. **Агент создает временный скрипт** в `temp/commit-and-push.sh` с полным коммитом и push командой 2. **Пользователь выполняет скрипт** вручную в терминале -3. **Скрипт автоматически** делает `git add`, `git commit` и `git push` через универсальный скрипт +3. **Скрипт автоматически** делает `git add`, `git commit` и `git push` -**Формат временного скрипта:** +**Формат временного скрипта (для local_machine):** ```bash #!/bin/bash -# Скрипт для коммита и пуша изменений - set -e - echo "📦 Committing changes..." git add -A - echo "💾 Creating commit..." -git commit -m "🎨 feat(frontend): центрирование нод при tidyUp с учетом реальных ширин v3.10.4 - -- ✅ Улучшена функция calculateLevelPositions для центрирования нод -- ✅ Добавлен учет реальных ширин нод (node.width или NODE_WIDTH) -- ✅ Вся группа нод на уровне центрируется относительно START_X -- ✅ Более широкие ноды правильно центрированы относительно узких -- ✅ Обновлена документация (TROUBLESHOOTING_COMPLETE.md, CHANGELOG.md) - -Проблема: Более широкие ноды выравнивались по левому краю -Решение: Центрирование с учетом реальных ширин нод -Результат: Ноды центрируются на уровне, визуально выглядит красиво" - +git commit -m "docs: описание изменений" echo "🚀 Pushing to Gitea..." -bash scripts/deployment/gitea/git-push-gitea.sh dev - +git push origin main echo "✅ Done!" ``` **Требования к скрипту:** - ✅ Должен быть исполняемым (`chmod +x`) - ✅ Должен содержать полное сообщение коммита с описанием изменений -- ✅ Должен использовать универсальный скрипт `git-push-gitea.sh` для push +- ✅ Для local_machine: `git push origin main` (или SSH remote, см. выше) - ✅ Должен быть в папке `temp/` для временных скриптов - ✅ Должен иметь понятные echo сообщения для пользователя @@ -514,41 +493,15 @@ rm temp/commit-and-push.sh - `Made-with: Cursor` и аналогичные метки инструмента - `Co-authored-by:`, `Signed-off-by:` и т.п., если это не требуется явно политикой репозитория -Коммиты должны соответствовать [COMMIT_STANDARDS.md](../../standards/COMMIT_STANDARDS.md) и содержать только заголовок и при необходимости тело сообщения в стандартном формате. См. также раздел «Commit Quality Standards» в `.cursorrules`. +Коммиты должны содержать только заголовок и при необходимости тело сообщения в читаемом формате. -### Неблокирующая проверка типов (при коммитах) +### Проверки перед коммитом (опционально) -Перед коммитом или пушем можно быстро проверить типы (frontend и/или backend) **без блокировки коммита** — скрипт всегда выходит с кодом 0 и только выводит ошибки в консоль. +Если в проекте есть проверка типов или линтеры — их можно запускать перед коммитом по желанию. Для local_machine (документация и скрипты) обычно достаточно `git add` и `git commit`. -```bash -# Frontend + backend (полный отчёт) -bash scripts/maintenance/typecheck-report.sh +(В проекте local_machine нет frontend/backend — при необходимости добавьте свои проверки в скрипты.) -# Только изменённая часть (быстрее) -bash scripts/maintenance/typecheck-report.sh frontend -bash scripts/maintenance/typecheck-report.sh backend -``` - -Отдельно в пакетах: -- `cd frontend && pnpm typecheck` — только frontend -- `cd backend && pnpm typecheck` — только backend - -**Рекомендация:** запускать при коммитах бэкенда/фронта или перед пушем; коммит не блокируется, но ошибки видны в выводе. - -### Проверка прав на .env после деплоя (SECRETS_AND_FILE_PERMISSIONS_POLICY.md §4) - -После деплоя бэкенда на production выполнить проверку прав на `.env.production` (и при необходимости `.env.staging`) на сервере. Встроена в скрипты `deploy-backend-core-only.sh` и `deploy-backend-core-only-via-proxy.sh`; при необходимости запустить вручную: - -```bash -# После деплоя backend на прод (или вручную) -bash scripts/deployment/verify-env-permissions.sh hunab-prod -# либо с явным хостом -bash scripts/deployment/verify-env-permissions.sh hunab@209.38.32.21 -``` - -Ожидается: права `600` и владелец `hunab:hunab` для всех `.env*` в `/opt/app/`. При несоответствии скрипт выводит предупреждение и команду для исправления; деплой не блокируется (exit 0). - -**Чеклист коммитов/деплоя:** после деплоя backend на production — выполнить проверку прав (или убедиться, что она уже выполнена скриптом деплоя). См. [SECRETS_AND_FILE_PERMISSIONS_POLICY.md](../../security/SECRETS_AND_FILE_PERMISSIONS_POLICY.md) §4. +*Для других репо:* проверка прав на `.env` после деплоя и typecheck-скрипты — в документации того проекта. ### Метод 1: HTTPS с оптимизированными настройками @@ -559,28 +512,28 @@ git config http.lowSpeedLimit 0 git config http.lowSpeedTime 0 git config http.timeout 600 -# Попытка push -git push origin dev +# Попытка push (для local_machine — ветка main) +git push origin main ``` **⚠️ На macOS:** HTTPS push часто не работает из-за отсутствия команды `timeout`. Скрипт автоматически пропускает HTTPS и использует bundle метод. -### Метод 2: Bundle метод (надежный fallback) +### Метод 2: Bundle метод (надежный fallback) {#резервный-способ-push-через-bundle} Если HTTPS не работает, используется прямой доступ через файловую систему: ```bash -# Создать bundle -git bundle create /tmp/push.bundle origin/dev..dev +# Создать bundle (для local_machine — ветка main) +git bundle create /tmp/push.bundle origin/main..main -# Скопировать на сервер +# Скопировать на сервер (hunab-prod — хост с доступом к Docker Gitea) scp /tmp/push.bundle hunab-prod:/tmp/ -# Применить в Gitea +# Применить в Gitea (репо local_machine) ssh hunab-prod "docker cp /tmp/push.bundle gitea:/tmp/push.bundle && \ -docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/hunabapp.git && \ +docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && \ git bundle unbundle /tmp/push.bundle && \ -git update-ref refs/heads/dev \$(git bundle list-heads /tmp/push.bundle | grep dev | cut -d\" \" -f1) && \ +git update-ref refs/heads/main \$(git bundle list-heads /tmp/push.bundle | grep main | cut -d\" \" -f1) && \ rm /tmp/push.bundle' && rm /tmp/push.bundle" ``` @@ -598,59 +551,50 @@ rm /tmp/push.bundle' && rm /tmp/push.bundle" ### Миграция репозиториев -**Для регулярных push используйте скрипт:** -```bash -bash scripts/deployment/gitea/git-push-gitea.sh [branch] -``` +**Для регулярных push в local_machine:** `git push origin main` или SSH (см. раздел «Для репозитория local_machine» выше). В других репо может быть скрипт `git-push-gitea.sh`. #### Шаг 1: Создание bundle из локального репозитория ```bash -cd /Users/eternal/code/hunabapp-dev -git bundle create /tmp/hunabapp-migration.bundle --all +cd /Users/eternal/code/local_machine +git bundle create /tmp/local_machine-migration.bundle --all ``` -**Результат:** Bundle ~553M - #### Шаг 2: Копирование bundle на сервер ```bash -scp /tmp/hunabapp-migration.bundle hunab-prod:/tmp/hunabapp-migration.bundle +scp /tmp/local_machine-migration.bundle hunab-prod:/tmp/local_machine-migration.bundle ``` #### Шаг 3: Распаковка bundle на сервере ```bash -ssh hunab-prod "cd /tmp && git clone --mirror hunabapp-migration.bundle hunabapp-mirror.git" +ssh hunab-prod "cd /tmp && git clone --mirror local_machine-migration.bundle local_machine-mirror.git" ``` -**Результат:** Bare репозиторий ~557M - #### Шаг 4: Копирование в директорию Gitea ```bash ssh hunab-prod " docker exec gitea mkdir -p /data/git/repositories/hunabgit/ - docker cp /tmp/hunabapp-mirror.git gitea:/data/git/repositories/hunabgit/hunabapp.git - docker exec gitea chown -R git:git /data/git/repositories/hunabgit/hunabapp.git + docker cp /tmp/local_machine-mirror.git gitea:/data/git/repositories/hunabgit/local_machine.git + docker exec gitea chown -R git:git /data/git/repositories/hunabgit/local_machine.git " ``` -**Результат:** Репозиторий в Gitea (556.6M) - #### Шаг 5: Распаковка refs из packed-refs **КРИТИЧЕСКИ ВАЖНО:** Gitea не видит ветки в `packed-refs`, нужно распаковать: ```bash -ssh hunab-prod "docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/hunabapp.git && cat packed-refs | grep \"refs/heads/\" | while read sha ref; do mkdir -p \$(dirname \$ref) && echo \$sha > \$ref; done'" +ssh hunab-prod "docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && cat packed-refs | grep \"refs/heads/\" | while read sha ref; do mkdir -p \$(dirname \$ref) && echo \$sha > \$ref; done'" ``` #### Шаг 6: Обновление базы данных ```bash ssh hunab-prod " - docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -c \"UPDATE repository SET is_empty = false WHERE name = 'hunabapp';\" + docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -c \"UPDATE repository SET is_empty = false WHERE name = 'local_machine';\" docker exec gitea /usr/local/bin/gitea admin regenerate hooks --config /data/gitea/conf/app.ini docker restart gitea " @@ -661,14 +605,14 @@ ssh hunab-prod " ```bash # Проверка веток через API curl -s -H "Authorization: token YOUR_TOKEN" \ - https://gitea.hunab.app/api/v1/repos/hunabgit/hunabapp/branches | jq '.[] | .name' + https://gitea.hunab.app/api/v1/repos/hunabgit/local_machine/branches | jq '.[] | .name' -# Проверка файлов через API +# Проверка файлов через API (ветка main) curl -s -H "Authorization: token YOUR_TOKEN" \ - https://gitea.hunab.app/api/v1/repos/hunabgit/hunabapp/git/trees/dev?recursive=0 | jq '.tree[] | .path' + https://gitea.hunab.app/api/v1/repos/hunabgit/local_machine/git/trees/main?recursive=0 | jq '.tree[] | .path' -# Проверка через веб-интерфейс -https://gitea.hunab.app/hunabgit/hunabapp +# Веб-интерфейс +# https://gitea.hunab.app/hunabgit/local_machine ``` **✅ Результат:** Репозиторий полностью мигрирован, все файлы доступны через веб-интерфейс и API. @@ -686,9 +630,9 @@ https://gitea.hunab.app/hunabgit/hunabapp #### Использование токена в git ```bash -# В локальном репозитории -cd /Users/eternal/code/hunabapp-dev -git remote set-url origin https://TOKEN@gitea.hunab.app/hunabgit/hunabapp.git +# В локальном репозитории local_machine +cd /Users/eternal/code/local_machine +git remote set-url origin https://TOKEN@gitea.hunab.app/hunabgit/local_machine.git ``` --- @@ -705,7 +649,7 @@ git remote set-url origin https://TOKEN@gitea.hunab.app/hunabgit/hunabapp.git ```bash # 1. Получить ID репозитория -REPO_ID=$(ssh hunab-prod "docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -t -c \"SELECT id FROM repository WHERE lower_name = 'hunabapp';\" | tr -d ' '") +REPO_ID=$(ssh hunab-prod "docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -t -c \"SELECT id FROM repository WHERE lower_name = 'local_machine';\" | tr -d ' '") # 2. Создать Release в базе данных ssh hunab-prod "docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -c \" @@ -744,7 +688,7 @@ ssh hunab-prod "docker restart gitea" ``` **Параметры:** -- `repo_id` - ID репозитория (получить через `SELECT id FROM repository WHERE lower_name = 'hunabapp';`) +- `repo_id` - ID репозитория (получить через `SELECT id FROM repository WHERE lower_name = 'local_machine';`) - `publisher_id` - ID пользователя (обычно 1 для первого пользователя) - `tag_name` - Имя тега (например, `v1.3.7-CRM`) - `lower_tag_name` - Имя тега в нижнем регистре (например, `v1.3.7-crm`) @@ -758,7 +702,7 @@ ssh hunab-prod "docker restart gitea" Если нужно создать тег вручную: -1. Откройте: https://gitea.hunab.app/hunabgit/hunabapp/releases/new +1. Откройте: https://gitea.hunab.app/hunabgit/local_machine/releases/new 2. В поле "Tag" введите имя тега (например, `v1.3.7-CRM`) 3. В поле "Target" выберите commit из списка или введите SHA 4. Заполните Title и Description @@ -773,7 +717,7 @@ ssh hunab-prod "docker restart gitea" TOKEN=$(ssh hunab-prod "docker exec gitea /usr/local/bin/gitea admin user generate-access-token --username hunabgit --scopes 'write:repository' --name 'api-tag-creation' 2>&1 | grep -o '[a-z0-9]\{40\}' | head -1") # 2. Создать Release через API -curl -X POST "https://gitea.hunab.app/api/v1/repos/hunabgit/hunabapp/releases" \ +curl -X POST "https://gitea.hunab.app/api/v1/repos/hunabgit/local_machine/releases" \ -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ @@ -807,7 +751,7 @@ git push origin v1.3.7-CRM # Даже после push тег не появит - [ ] Получить SHA коммита для тега - [ ] Создать Release в таблице `release` базы данных - [ ] Перезапустить Gitea для обновления индекса -- [ ] Проверить что тег появился в веб-интерфейсе: https://gitea.hunab.app/hunabgit/hunabapp/releases +- [ ] Проверить что тег появился в веб-интерфейсе: https://gitea.hunab.app/hunabgit/local_machine/releases ### 🔧 Скрипт для автоматического создания тега @@ -886,13 +830,13 @@ openssl s_client -connect gitea.hunab.app:443 -servername gitea.hunab.app /dev/null || true cp -r /tmp/repo-clean.git/refs/heads refs/ 2>/dev/null || true cp -r /tmp/repo-clean.git/refs/tags refs/ 2>/dev/null || true - echo \"ref: refs/heads/dev\" > HEAD + echo \"ref: refs/heads/main\" > HEAD rm -rf refs/original filter-repo git reflog expire --expire=now --all 2>/dev/null git gc --prune=now 2>&1 | tail -5 @@ -1642,7 +1583,7 @@ ssh hunab-prod "docker exec gitea sh -c 'cd $GITEA_REPO && \ echo \"Файлов dist в истории: \$(git rev-list --all --objects | grep -cE \"dist-zero-downtime|dist-fast-no-image|dist-via-proxy|dist-cache-busting\" || echo 0)\" && \ echo \"Веток: \$(git branch | wc -l)\" && \ echo \"Тегов: \$(git tag | wc -l)\" && \ - git log --oneline -3 dev'" + git log --oneline -3 main'" ``` **✅ Результат:** Gitea репозиторий обновлен очищенной версией @@ -1811,7 +1752,7 @@ ssh hunab-prod "docker exec gitea rm -rf $GITEA_REPO && \ - [ ] Удалены файлы из tracking: `git rm --cached -r frontend/dist-*/` - [ ] Проверено что файлы в staging для удаления: `git status --short | grep "^D "` - [ ] Создан коммит с описанием удаления -- [ ] Push в Gitea через скрипт: `bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh dev` +- [ ] Push в Gitea (local_machine: `git push origin main` или SSH) - [ ] Проверено что файлы больше не отслеживаются: `git ls-files | grep dist- | wc -l` = 0 - [ ] Обновлен CHANGELOG с описанием операции diff --git a/docs/git/LOCAL_MACHINE_GITEA_SETUP.md b/docs/git/LOCAL_MACHINE_GITEA_SETUP.md index feed27b..c388b7c 100644 --- a/docs/git/LOCAL_MACHINE_GITEA_SETUP.md +++ b/docs/git/LOCAL_MACHINE_GITEA_SETUP.md @@ -23,6 +23,8 @@ bash scripts/deployment/gitea/setup-and-push.sh --create-only ### 2. Инициализация и первый пуш +**Важно:** сначала создай репозиторий на Gitea (шаг 1), иначе `git push` выдаст ошибку. + Из корня **local_machine** (не из родительского репо): ```bash @@ -36,14 +38,24 @@ bash scripts/deployment/gitea/setup-and-push.sh - добавляет `origin` (SSH или HTTPS); - делает первый коммит и `git push -u origin main`. +Если пуш упал (таймаут или «repository not found»): создай репо по шагу 1, затем снова выполни `git push -u origin main` из каталога `local_machine`. + ### 3. Если пуш из РФ зависает По гайду [GITEA_COMPLETE_GUIDE.md](./GITEA_COMPLETE_GUIDE.md): - предпочтительно **SSH** (порт 2223): - `git remote set-url origin git@gitea.hunab.app:hunabgit/local_machine.git` + `git remote set-url origin ssh://git@gitea.hunab.app:2223/hunabgit/local_machine.git` и снова `git push -u origin main`; -- либо использовать прокси/скрипты пуша из основного репо (bundle-метод), если они у вас настроены. +- либо **пуш через прокси на зеркальный сервер 206.189.35.205** (как в hunabapp): + +```bash +# Из корня local_machine. Требуется: прокси hsites-ahau, ключ к 206, репо local_machine создан на Gitea на 206. +bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh +# или с явным хостом: GITEA_SERVER=user@206.189.35.205 bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh +``` + +Скрипт пробует по порядку: SSH через прокси → HTTPS → bundle (копирование на сервер и `docker exec` в контейнер Gitea). Режим проверки без пуша: `bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh --dry-run`. ## .gitignore diff --git a/scripts/deployment/gitea/git-push-gitea-via-proxy.sh b/scripts/deployment/gitea/git-push-gitea-via-proxy.sh new file mode 100755 index 0000000..4e919c2 --- /dev/null +++ b/scripts/deployment/gitea/git-push-gitea-via-proxy.sh @@ -0,0 +1,211 @@ +#!/bin/bash +# Push local_machine to Gitea on server 206.189.35.205 via Proxy (🇷🇺 ДЛЯ РФ) +# Same logic as hunabapp's script: SSH → HTTPS → Bundle +# Repo: local_machine, branch: main, target: 206.189.35.205 +# +# === БЕЗОПАСНОСТЬ (SAFETY) === +# - НЕ трогает текущую ветку, HEAD, рабочую копию, локальные коммиты. +# - НЕ выполняет: git reset, git checkout --hard, git clean, push --force. +# - Обновляет refs/remotes/origin/main только после успешного пуша. +# - --dry-run: только показать, что будет запушено. + +set -e + +DRY_RUN=0 +[[ "${1:-}" = "--dry-run" ]] && DRY_RUN=1 +if [[ "$DRY_RUN" -eq 1 ]]; then + BRANCH="${2:-main}" +else + BRANCH="${1:-main}" +fi + +# Target: Gitea on 206.189.35.205 (mirror server) +# Optional: in ~/.ssh/config set Host gitea-mirror, HostName 206.189.35.205, User +# Then: export GITEA_SERVER=gitea-mirror +GITEA_SSH_HOST="${GITEA_SSH_HOST:-206.189.35.205}" +GITEA_SSH_PORT="${GITEA_SSH_PORT:-2223}" +GITEA_SSH_URL="ssh://git@${GITEA_SSH_HOST}:${GITEA_SSH_PORT}/hunabgit/local_machine.git" +# Host for scp/ssh (docker exec on 206). Default: IP. Or set GITEA_SERVER=gitea-mirror if in SSH config. +SERVER="${GITEA_SERVER:-206.189.35.205}" +# HTTPS (if mirror has own URL, set GITEA_HTTPS_BASE) +GITEA_HTTPS_BASE="${GITEA_HTTPS_BASE:-https://gitea.hunab.app/hunabgit/local_machine.git}" + +# Proxy (🇷🇺 ДЛЯ РФ) +PROXY_SERVER="${PROXY_SERVER:-hsites-ahau}" +PROXY_SSH_KEY="${PROXY_SSH_KEY:-$HOME/.ssh/id_ed25519}" +TARGET_SSH_KEY="${TARGET_SSH_KEY:-$HOME/.ssh/hunab_deploy_key}" + +PROXY_SSH_KEY_EXPANDED="${PROXY_SSH_KEY/#\~/$HOME}" +TARGET_SSH_KEY_EXPANDED="${TARGET_SSH_KEY/#\~/$HOME}" +PROXY_CMD="ssh -i $PROXY_SSH_KEY_EXPANDED -o StrictHostKeyChecking=accept-new -W %h:%p $PROXY_SERVER" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}=== Push local_machine to Gitea via Proxy (206.189.35.205) ===${NC}" +echo -e "Branch: ${YELLOW}$BRANCH${NC}" +echo -e "Proxy: ${YELLOW}$PROXY_SERVER${NC}" +echo -e "Target: ${YELLOW}$GITEA_SSH_HOST${NC} (repo: local_machine)" +echo "" + +# Must be in local_machine repo +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || true) +if [[ ! -f "$REPO_ROOT/.gitignore" ]] || [[ ! -d "$REPO_ROOT/docs" ]]; then + echo -e "${RED}❌ Run from local_machine repo root.${NC}" + exit 1 +fi + +if ! git rev-parse --verify "origin/$BRANCH" >/dev/null 2>&1; then + echo -e "${YELLOW}⚠️ origin/$BRANCH not found, fetching...${NC}" + git fetch origin 2>/dev/null || true +fi + +LOCAL_COMMIT=$(git rev-parse $BRANCH 2>/dev/null || echo "") +REMOTE_COMMIT=$(git rev-parse "origin/$BRANCH" 2>/dev/null || echo "") + +if [ -z "$LOCAL_COMMIT" ]; then + echo -e "${RED}❌ Error: Branch $BRANCH not found${NC}" + exit 1 +fi + +if [ -z "$REMOTE_COMMIT" ]; then + echo -e "${YELLOW}⚠️ Remote branch not found, will create it${NC}" + REMOTE_COMMIT="" +elif [ "$LOCAL_COMMIT" = "$REMOTE_COMMIT" ]; then + echo -e "${GREEN}✅ No commits to push${NC}" + exit 0 +fi + +echo -e "Local commit: ${BLUE}$LOCAL_COMMIT${NC}" +if [ -n "$REMOTE_COMMIT" ]; then + echo -e "Remote commit: ${BLUE}$REMOTE_COMMIT${NC}" +fi +PENDING_COUNT=$(git rev-list --count "origin/$BRANCH..$BRANCH" 2>/dev/null || echo "?") +echo -e "Commits to push: ${BLUE}$PENDING_COUNT${NC}" +echo "" + +if [[ "$DRY_RUN" -eq 1 ]]; then + echo -e "${GREEN}✅ --dry-run: would push $PENDING_COUNT commit(s). No changes made.${NC}" + echo " Run without --dry-run to push: bash $0 $BRANCH" + exit 0 +fi + +# Method 1: SSH via proxy +echo -e "${YELLOW}[1/3] Trying SSH push...${NC}" +CURRENT_URL=$(git remote get-url origin 2>/dev/null || echo "") + +if ssh -p "$GITEA_SSH_PORT" -i "$TARGET_SSH_KEY_EXPANDED" -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new -o ProxyCommand="$PROXY_CMD" "git@$GITEA_SSH_HOST" "echo 'OK'" >/dev/null 2>&1; then + echo -e "${GREEN}✅ SSH connection available${NC}" + if [ -z "$CURRENT_URL" ] || [[ ! "$CURRENT_URL" =~ ssh:// ]]; then + echo -e "${YELLOW}Switching to SSH URL...${NC}" + git remote set-url origin "$GITEA_SSH_URL" + fi + GIT_SSH_COMMAND="ssh -i $TARGET_SSH_KEY_EXPANDED -p $GITEA_SSH_PORT -o ProxyCommand='$PROXY_CMD' -o StrictHostKeyChecking=accept-new" + if GIT_SSH_COMMAND="$GIT_SSH_COMMAND" git push origin "$BRANCH" 2>&1; then + echo -e "${GREEN}✅ Push successful via SSH!${NC}" + exit 0 + else + echo -e "${YELLOW}⚠️ SSH push failed, trying HTTPS...${NC}" + fi +else + echo -e "${YELLOW}⚠️ SSH not available, trying HTTPS...${NC}" +fi + +# Method 2: HTTPS +echo -e "${YELLOW}[2/3] Trying HTTPS push...${NC}" +if [ -z "$CURRENT_URL" ] || [[ "$CURRENT_URL" =~ ssh:// ]]; then + git remote set-url origin "$GITEA_HTTPS_BASE" +fi +git config http.postBuffer 524288000 +git config http.lowSpeedLimit 0 +git config http.lowSpeedTime 0 +git config http.timeout 600 +if command -v timeout >/dev/null 2>&1; then + if timeout 30 git push origin "$BRANCH" 2>&1; then + echo -e "${GREEN}✅ Push successful via HTTPS!${NC}" + exit 0 + fi +else + echo -e "${YELLOW}⚠️ Skipping HTTPS (no timeout cmd), using bundle...${NC}" +fi + +# Method 3: Bundle +echo -e "${YELLOW}[3/3] Using bundle method...${NC}" +BUNDLE_BASE="$REMOTE_COMMIT" +if git remote get-url gitea >/dev/null 2>&1; then + GIT_SSH_COMMAND_FETCH="ssh -i $TARGET_SSH_KEY_EXPANDED -p $GITEA_SSH_PORT -o ProxyCommand='$PROXY_CMD' -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15" + if GIT_SSH_COMMAND="$GIT_SSH_COMMAND_FETCH" git fetch gitea "$BRANCH" 2>/dev/null; then + GITEA_COMMIT=$(git rev-parse "gitea/$BRANCH" 2>/dev/null || echo "") + [ -n "$GITEA_COMMIT" ] && BUNDLE_BASE="$GITEA_COMMIT" && echo -e "Bundle base (gitea/$BRANCH): ${BLUE}$GITEA_COMMIT${NC}" + fi +fi +if [ "$BUNDLE_BASE" = "$REMOTE_COMMIT" ]; then + echo -e "${YELLOW}Querying Gitea refs/heads/$BRANCH on server...${NC}" + GITEA_COMMIT=$(ssh -i "$TARGET_SSH_KEY_EXPANDED" -o ProxyCommand="$PROXY_CMD" "$SERVER" "docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && git rev-parse refs/heads/$BRANCH'" 2>/dev/null || echo "") + if [ -n "$GITEA_COMMIT" ]; then + BUNDLE_BASE="$GITEA_COMMIT" + echo -e "Bundle base (Gitea on server): ${BLUE}$GITEA_COMMIT${NC}" + else + echo -e "${YELLOW}⚠️ Could not get server ref (repo may be new). Using full branch bundle.${NC}" + fi +fi + +mkdir -p "$REPO_ROOT/temp" +BUNDLE_FILE="$REPO_ROOT/temp/gitea-push-${BRANCH}-$(date +%s).bundle" +echo "Creating bundle: $BUNDLE_FILE" +if [ -n "$BUNDLE_BASE" ] && ! git cat-file -e "$BUNDLE_BASE^{commit}" 2>/dev/null; then + echo -e "${YELLOW}⚠️ Server commit not in local repo. Creating full branch bundle.${NC}" + BUNDLE_BASE="" +fi +BUNDLE_FAILED=0 +if [ -n "$BUNDLE_BASE" ]; then + git bundle create "$BUNDLE_FILE" "$BUNDLE_BASE..$BRANCH" || BUNDLE_FAILED=1 +else + git bundle create "$BUNDLE_FILE" "$BRANCH" || BUNDLE_FAILED=1 +fi +if [ "$BUNDLE_FAILED" -eq 1 ]; then + echo -e "${RED}❌ Bundle creation failed.${NC}" + exit 1 +fi + +BUNDLE_SIZE=$(du -h "$BUNDLE_FILE" | cut -f1) +echo "Bundle size: $BUNDLE_SIZE" +echo "Copying bundle to server via proxy..." +scp -i "$TARGET_SSH_KEY_EXPANDED" -o ProxyCommand="$PROXY_CMD" "$BUNDLE_FILE" "$SERVER:/tmp/$(basename "$BUNDLE_FILE")" || { + echo -e "${RED}❌ Failed to copy bundle.${NC}" + echo -e " Check: GITEA_SERVER=$SERVER (e.g. 206.189.35.205 or Host from ~/.ssh/config), proxy $PROXY_SERVER reachable." + rm -f "$BUNDLE_FILE" + exit 1 +} + +NEW_COMMIT=$(git bundle list-heads "$BUNDLE_FILE" | grep "^$BRANCH" | cut -d" " -f1) +[ -z "$NEW_COMMIT" ] && NEW_COMMIT=$(git bundle list-heads "$BUNDLE_FILE" | head -1 | cut -d" " -f1) +if [ -z "$NEW_COMMIT" ]; then + echo -e "${RED}❌ Could not find commit in bundle${NC}" + rm -f "$BUNDLE_FILE" + exit 1 +fi + +BUNDLE_BASENAME=$(basename "$BUNDLE_FILE") +APPLY_OUT=$(mktemp 2>/dev/null || echo "/tmp/gitea-apply-$$.out") +if ! ssh -i "$TARGET_SSH_KEY_EXPANDED" -o ProxyCommand="$PROXY_CMD" "$SERVER" "docker cp /tmp/$BUNDLE_BASENAME gitea:/tmp/push-bundle.bundle && docker exec -e BRANCH='$BRANCH' -e NEW_COMMIT='$NEW_COMMIT' gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && git bundle unbundle /tmp/push-bundle.bundle && git update-ref refs/heads/\$BRANCH \$NEW_COMMIT && rm -f /tmp/push-bundle.bundle' && rm -f /tmp/$BUNDLE_BASENAME" 2>"$APPLY_OUT"; then + echo -e "${RED}❌ Failed to apply bundle${NC}" + [ -s "$APPLY_OUT" ] && cat "$APPLY_OUT" + echo -e "${YELLOW}→ Ensure repo exists on 206: Gitea UI → New Repository → local_machine (empty).${NC}" + echo " Then: git remote add gitea $GITEA_SSH_URL && git fetch gitea $BRANCH" + echo " And re-run: bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh $BRANCH" + rm -f "$APPLY_OUT" "$BUNDLE_FILE" + exit 1 +fi +rm -f "$APPLY_OUT" "$BUNDLE_FILE" + +if [ "$NEW_COMMIT" = "$LOCAL_COMMIT" ]; then + echo -e "${YELLOW}Updating local tracking ref (origin/$BRANCH)...${NC}" + git update-ref "refs/remotes/origin/$BRANCH" "$NEW_COMMIT" 2>/dev/null || true +fi + +echo -e "${GREEN}✅ Push completed via bundle!${NC}" +echo -e " Commit: ${BLUE}$NEW_COMMIT${NC}" diff --git a/scripts/games/README.md b/scripts/games/README.md new file mode 100644 index 0000000..e69de29 diff --git a/scripts/games/cache/.gitignore b/scripts/games/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/scripts/games/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/scripts/games/config.sh b/scripts/games/config.sh new file mode 100755 index 0000000..04294cc --- /dev/null +++ b/scripts/games/config.sh @@ -0,0 +1,214 @@ +# Shared paths for HoMM / Whisky (source from other scripts in this folder). +GAMES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +GAMES_CACHE_DIR="${GAMES_DIR}/cache" + +BOTTLE_ID="38412122-AAE1-4831-9F2E-6A0EB364B3F9" +BOTTLE_NAME="HoMM-OldenEra" +# franke Whisky 3 (Whisky.app) — канон; isaacmarovitz legacy после migrate-bottle-to-franke-whisky.sh +_FRANKE_BOTTLE="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}" +_ISAAC_BOTTLE="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}" +if [[ -d "${_FRANKE_BOTTLE}/drive_c" ]]; then + BOTTLE_DIR="${_FRANKE_BOTTLE}" + WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky" +else + BOTTLE_DIR="${_ISAAC_BOTTLE}" + WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky" +fi +unset _FRANKE_BOTTLE _ISAAC_BOTTLE +WINE64="${WHISKY_LIBS}/Libraries/Wine/bin/wine64" +WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib" +STEAM_DIR="${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam" +STEAM_EXE="${STEAM_DIR}/Steam.exe" +STEAM_SETUP="${GAMES_CACHE_DIR}/SteamSetup.exe" +LIBRARIES_TAR="${GAMES_CACHE_DIR}/Libraries.tar.gz" + +# GOG Galaxy 2.0 (полный offline setup ~290 MB; web stub GOG_Galaxy_2.0.exe — только загрузчик) +GOG_SETUP="${GAMES_CACHE_DIR}/setup_galaxy.exe" +GOG_SETUP_URL="${GOG_SETUP_URL:-}" +GOG_DIR_X86="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy" +GOG_DIR_X64="${BOTTLE_DIR}/drive_c/Program Files/GOG Galaxy" +GOG_EXE="${GOG_DIR_X86}/GalaxyClient.exe" +# CEF (как Steam/Whisky #1220). Без -cef-in-process-gpu — меньше gpu-process CPU spin. +GOG_GALAXY_BASE_ARGS="${GOG_GALAXY_BASE_ARGS:-/runWithoutUpdating /deelevated}" +GOG_GALAXY_CEF_ARGS="${GOG_GALAXY_CEF_ARGS:--allosarches -cef-force-32bit -no-cef-sandbox -cef-disable-gpu}" + +homm_gog_cef_args_line() { + printf '%s %s' "${GOG_GALAXY_BASE_ARGS}" "${GOG_GALAXY_CEF_ARGS}" +} + +homm_gog_cef_args_array() { + local _IFS=$IFS + IFS=' ' + # shellcheck disable=SC2206 + local -a _a=(${GOG_GALAXY_BASE_ARGS} ${GOG_GALAXY_CEF_ARGS}) + IFS=$_IFS + printf '%s\n' "${_a[@]}" +} + +homm_gog_exe() { + if [[ -f "${GOG_EXE}" ]]; then + echo "${GOG_EXE}" + return 0 + fi + local found + found="$(find "${BOTTLE_DIR}/drive_c/Program Files"* -maxdepth 3 -name 'GalaxyClient.exe' 2>/dev/null | head -1)" + if [[ -n "${found}" ]]; then + echo "${found}" + return 0 + fi + return 1 +} + +homm_wine_gog_running() { + pgrep -f "${BOTTLE_ID}.*[Gg]alaxy[Cc]lient" >/dev/null 2>&1 \ + || pgrep -f 'GOG Galaxy.*GalaxyClient' >/dev/null 2>&1 \ + || pgrep -f 'GalaxyClient\.exe' >/dev/null 2>&1 +} + +export_wine_env_gog() { + export_wine_env + unset WINEDLLOVERRIDES 2>/dev/null || true + export WINEDEBUG="${WINEDEBUG:--all}" +} + +# Heroes of Might and Magic: Olden Era (Steam) +HOMM_APP_ID="3105440" +HOMM_DIR="${STEAM_DIR}/steamapps/common/Heroes of Might and Magic Olden Era" +HOMM_EXE="${HOMM_DIR}/HeroesOldenEra.exe" +WINEPATH_BIN="${WHISKY_LIBS}/Libraries/Wine/bin/winepath" + +# Запуск игры: по умолчанию виртуальный стол Wine (часто лечит «двигается, ЛКМ нет»). +# HOMM_USE_WINEVDESKTOP=0 — запуск напрямую без explorer /desktop (быстрый A/B тест). +# HOMM_LAUNCH_DIRECT=1 — legacy-переключатель прямого запуска (оставлен для совместимости). +# HOMM_VDESKTOP=WxH — размер виртуального стола. +# +# По умолчанию Unity принудительно в оконном режиме (-screen-fullscreen 0 …): +# это часто восстанавливает ЛКМ на macOS+Wine при багах полноэкранного режима. +# Выключить: HOMM_UNITY_WINDOWED=0 +HOMM_VDESKTOP="${HOMM_VDESKTOP:-1920x1080}" +HOMM_USE_WINEVDESKTOP="${HOMM_USE_WINEVDESKTOP:-1}" +HOMM_LAUNCH_DIRECT="${HOMM_LAUNCH_DIRECT:-0}" +HOMM_UNITY_WINDOWED="${HOMM_UNITY_WINDOWED:-1}" +# Дополнительные аргументы Unity через пробел, напр.: "-popupwindow" +HOMM_UNITY_EXTRA="${HOMM_UNITY_EXTRA:-}" + +# Steam CEF (steamwebhelper): без этих флагов в Whisky/Wine часто нет окна UI +# (cef_log: message_window.cc Class does not exist, webhelper в цикле перезапуска). +# Источник: Whisky #1220 — -allosarches -cef-force-32bit -cef-in-process-gpu -no-cef-sandbox +HOMM_STEAM_CEF_ARGS="${HOMM_STEAM_CEF_ARGS:--allosarches -cef-force-32bit -cef-in-process-gpu -no-cef-sandbox -cef-disable-gpu}" +# HOMM_STEAM_VDESKTOP=1 — запуск через explorer /desktop (если окно «теряется») +HOMM_STEAM_VDESKTOP="${HOMM_STEAM_VDESKTOP:-0}" + +# Диск: APFS Data volume. Guard: block < HOMM_DISK_MIN_GB (3), warn < HOMM_DISK_WARN_PCT (10). +homm_data_volume_path() { + if [[ -d /System/Volumes/Data ]]; then + echo /System/Volumes/Data + elif [[ -n "${HOME:-}" && -d "${HOME}" ]]; then + echo "${HOME}" + else + echo / + fi +} + +homm_disk_free_kb() { + df -k "$(homm_data_volume_path)" | awk 'NR==2 {print $4}' +} + +homm_disk_total_kb() { + df -k "$(homm_data_volume_path)" | awk 'NR==2 {print $2}' +} + +homm_disk_used_pct() { + df -k "$(homm_data_volume_path)" | awk 'NR==2 {gsub(/%/,"",$5); print $5}' +} + +homm_disk_free_pct() { + local free_kb total_kb + free_kb="$(homm_disk_free_kb)" + total_kb="$(homm_disk_total_kb)" + if [[ "${total_kb}" -gt 0 ]]; then + echo $(( free_kb * 100 / total_kb )) + else + echo 0 + fi +} + +export_wine_env() { + export WINEPREFIX="${BOTTLE_DIR}" + export WINEMSYNC=1 + export WINEESYNC=1 + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" +} + +# Steam UI: без DXVK; dwrite=n,b — классический фикс пустого текста VGUI (см. fix-homm-steam-ui.sh). +export_wine_env_steam() { + export_wine_env + unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true + export WINEDLLOVERRIDES="dwrite=n,b" +} + +homm_kill_mac_steam() { + pkill -9 -f 'Steam.AppBundle/Steam/Contents/MacOS/steam_osx' 2>/dev/null || true + pkill -9 -f 'Steam.AppBundle/Steam/Contents/MacOS/ipcserver' 2>/dev/null || true + pkill -9 -f 'Steam Helper.app/Contents/MacOS/Steam Helper' 2>/dev/null || true +} + +homm_steam_cef_args() { + # shellcheck disable=SC2206 + echo ${HOMM_STEAM_CEF_ARGS} +} + +homm_steam_connection_log() { + echo "${STEAM_DIR}/logs/connection_log.txt" +} + +# Последняя сессия в connection_log — Logged On? +homm_steam_logged_on() { + local log + log="$(homm_steam_connection_log)" + [[ -f "${log}" ]] || return 1 + tail -25 "${log}" | grep -qE '\[Logged On' +} + +# Steam.exe в bottle (Terminal start.sh или Whisky GUI — argv часто C:\…\Steam\steam.exe). +# В regex избегать \S в "\Steam" — иначе ложные совпадения; используйте [/\\] или . +homm_wine_steam_running() { + pgrep -f "${BOTTLE_ID}.*[Ss]team\\.exe" >/dev/null 2>&1 \ + || pgrep -f "Whisky/Bottles/${BOTTLE_ID}.*[Ss]team\\.exe" >/dev/null 2>&1 \ + || pgrep -f 'Steam[/\\]steam\.exe' >/dev/null 2>&1 \ + || pgrep -f 'steamwebhelper\.exe' >/dev/null 2>&1 +} + +# Logged On + живой клиент (для play/game и сейвов). +homm_steam_ready_for_game() { + homm_steam_logged_on || return 1 + homm_wine_steam_running +} + +# Нативный Steam.app на Mac мешает bottle (login / CM / ipc). +homm_mac_steam_running() { + pgrep -f 'Steam\.AppBundle/Steam/Contents/MacOS/steam_osx' >/dev/null 2>&1 +} + +homm_warn_mac_steam() { + if homm_mac_steam_running; then + echo "⚠️ Запущен нативный Steam на Mac (часто остаётся после удаления Steam.app из /Applications)." + echo " Папка: ~/Library/Application Support/Steam/Steam.AppBundle/" + echo " Остановить:" + echo " pkill -f 'Steam.AppBundle/Steam/Contents/MacOS/steam_osx'" + echo " pkill -f 'Steam.AppBundle/Steam/Contents/MacOS/ipcserver'" + echo " Затем: bash ${GAMES_DIR}/start.sh reset && bash ${GAMES_DIR}/start.sh steam-ui" + return 1 + fi + return 0 +} + +# Olden Era (Unity DX11): Gcenx DXVK-macOS в system32 — см. install-dxvk-macos.sh +export_wine_env_game() { + export_wine_env + export WINEDLLOVERRIDES="d3d11,d3d10core=n,b" + export DXVK_ASYNC=1 + export DXVK_HUD=0 + # Меньше конфликтов fullscreen с macOS Stage Manager / края экрана + export SDL_VIDEO_MAC_FULLSCREEN_SPACES="${SDL_VIDEO_MAC_FULLSCREEN_SPACES:-0}" +} diff --git a/scripts/games/diagnose-gog-auth.sh b/scripts/games/diagnose-gog-auth.sh new file mode 100755 index 0000000..2d56c2c --- /dev/null +++ b/scripts/games/diagnose-gog-auth.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Снимок логов GOG login + краткий разбор (auth.gog.com/token, refreshToken). +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +LOGDIR="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs" +OUT="${GAMES_CACHE_DIR}/gog-auth-diagnose-$(date +%Y%m%d-%H%M%S)" +mkdir -p "${OUT}" + +for f in GalaxyClient.log cef.log ClientHelper-Authentication.log GalaxyClientService.log; do + [[ -f "${LOGDIR}/${f}" ]] && cp -f "${LOGDIR}/${f}" "${OUT}/" +done + +{ + echo "=== $(date) ===" + echo "Bottle: ${BOTTLE_NAME} (${BOTTLE_ID})" + df -h "$(homm_data_volume_path)" | awk 'NR==2' + echo "" + echo "=== proxy (macOS) ===" + scutil --proxy 2>/dev/null | head -12 + echo "" + echo "=== GOG processes ===" + pgrep -fl 'GalaxyClient' 2>/dev/null || echo "(none)" + echo "" + echo "=== refreshToken в реестре ===" + export WINEPREFIX="${BOTTLE_DIR}" + "${WINE64}" reg query 'HKCU\Software\GOG.com\Galaxy' /v refreshToken 2>&1 || echo "нет refreshToken" + echo "" + echo "=== последние auth-события (GalaxyClient.log) ===" + grep -iE 'Authentication failed|auth\.gog\.com/token|refreshToken|on_login_success|timeout.*gog|API status' \ + "${LOGDIR}/GalaxyClient.log" 2>/dev/null | tail -25 + echo "" + echo "=== CEF login (cef.log) ===" + grep -iE 'ERROR|message from source|recaptcha|frame-ancestors' "${LOGDIR}/cef.log" 2>/dev/null | tail -15 + echo "" + echo "=== Mac → auth.gog.com/token ===" + curl -sS -o /dev/null -w "http:%{http_code} total:%{time_total}s\n" --max-time 15 \ + -X POST "https://auth.gog.com/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=authorization_code&client_id=x&code=x" 2>&1 || echo "curl FAIL" +} > "${OUT}/summary.txt" + +echo "Архив: ${OUT}" +cat "${OUT}/summary.txt" diff --git a/scripts/games/diagnose-gog-ja3-session.sh b/scripts/games/diagnose-gog-ja3-session.sh new file mode 100755 index 0000000..2145a51 --- /dev/null +++ b/scripts/games/diagnose-gog-ja3-session.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)/gog" +exec bash "${GOG_DIR}/diagnose-session.sh" "$@" diff --git a/scripts/games/diagnose-homm-mouse.sh b/scripts/games/diagnose-homm-mouse.sh new file mode 100755 index 0000000..7a28301 --- /dev/null +++ b/scripts/games/diagnose-homm-mouse.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Диагностика «курсор двигается, клики не работают» для HoMM Olden Era в Whisky. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +LOG="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/Player.log" +LOG_PREV="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/Player-prev.log" + +echo "Bottle: ${BOTTLE_NAME}" +echo "Player.log: ${LOG}" +echo "" + +if [[ ! -f "${LOG}" ]]; then + echo "Нет Player.log — хотя бы один раз запустите игру." + exit 1 +fi + +if grep -q 'EnableMouseInPointer failed' "${LOG}" 2>/dev/null; then + echo "ДИАГНОЗ: Unity + Wine — заглушка EnableMouseInPointer (WM_POINTER)." + echo "Строка из лога:" + grep -n 'EnableMouseInPointer' "${LOG}" | head -5 || true + echo "" + echo "WineBus / виртуальный desktop / SDL это НЕ лечат." + echo "" + echo "Рабочие пути:" + echo " 1) CrossOver (macOS) + при необходимости патч:" + echo " https://github.com/dabielf/crossover-unity-mouse-fix" + echo " 2) Ждать WhiskyWine/Wine с upstream-фиксом (см. Wine bug #53847)." + echo "" + DOCS="$(cd "${GAMES_DIR}/../.." && pwd)/docs/games/UNITY_POINTER_MOUSE_FIX.md" + echo "Подробности: ${DOCS}" + exit 0 +fi + +echo "Строка EnableMouseInPointer в текущем Player.log не найдена." +if [[ -f "${LOG_PREV}" ]] && grep -q 'EnableMouseInPointer failed' "${LOG_PREV}" 2>/dev/null; then + echo "(В Player-prev.log она была — перезапустите игру и снова этот скрипт.)" +fi +echo "Если клики всё равно мертвы — пришлите первые 80 строк Player.log в поддержку." diff --git a/scripts/games/diagnose-homm-saves.sh b/scripts/games/diagnose-homm-saves.sh new file mode 100755 index 0000000..c30c26d --- /dev/null +++ b/scripts/games/diagnose-homm-saves.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Сейвы 1/2/3: файлы на диске vs видимость в игре (Steam API / login). +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +CONN_LOG="${STEAM_DIR}/logs/connection_log.txt" +PLAYER_LOG="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/Player.log" +USERS_ROOT="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/users" + +echo "=== HoMM Olden Era — диагностика сейвов ===" +echo "Bottle: ${BOTTLE_NAME}" +echo "" + +# --- файлы на диске --- +if [[ -d "${USERS_ROOT}" ]]; then + echo "Профили users/:" + ls -1 "${USERS_ROOT}" 2>/dev/null || true + sp="$(find "${USERS_ROOT}" -type d -path '*/saves/singleplayer' 2>/dev/null | head -1)" + if [[ -n "${sp}" ]]; then + echo "" + echo "singleplayer на диске: ${sp}/" + ls -lh "${sp}"/*.saveskirmish 2>/dev/null || echo " (нет *.saveskirmish)" + else + echo "singleplayer: не найден" + fi +else + echo "Нет ${USERS_ROOT} — игра ещё не создавала профиль." +fi + +echo "" + +# --- Steam login --- +if [[ -f "${CONN_LOG}" ]]; then + echo "Steam connection_log (хвост):" + tail -4 "${CONN_LOG}" | sed 's/^/ /' + if tail -20 "${CONN_LOG}" | grep -qE '\[Logged On'; then + echo " → Steam в bottle: Logged On (OK для сейвов)" + STEAM_OK=1 + else + echo " → Steam в bottle: Logged Off (сейвы в меню не подтянутся)" + STEAM_OK=0 + fi +else + echo "Нет connection_log.txt" + STEAM_OK=0 +fi + +echo "" + +# --- Player.log --- +if [[ -f "${PLAYER_LOG}" ]]; then + if grep -q 'SteamApi_Init failed\|ConnectToGlobalUser failed' "${PLAYER_LOG}" 2>/dev/null; then + echo "Player.log: SteamApi_Init FAILED — игра без живого Steam-клиента." + echo " (файлы на диске могут быть, слоты в UI пустые)" + API_OK=0 + elif grep -q 'Steam is probably not running' "${PLAYER_LOG}" 2>/dev/null; then + echo "Player.log: Steam is probably not running" + API_OK=0 + else + echo "Player.log: явных ошибок Steam API в последнем запуске нет." + API_OK=1 + fi + if grep -q 'FileManager.*saves' "${PLAYER_LOG}" 2>/dev/null; then + echo " FileManager видит папку saves (путь профиля на диске совпадает)." + fi +else + echo "Нет Player.log — сначала запустите игру." + API_OK=0 +fi + +echo "" +if [[ "${STEAM_OK:-0}" == "1" && "${API_OK:-0}" == "1" ]]; then + echo "ВЕРДИКТ: контур сейвов выглядит здоровым. В игре: Загрузить → skirmish → слоты 1/2/3." + exit 0 +fi + +echo "ВЕРДИКТ: игра могла открыться, но сейвы не подтянутся, пока Steam не Logged On." +echo "" +echo "Цикл:" +echo " bash ${SCRIPT_DIR}/start.sh reset" +echo " bash ${SCRIPT_DIR}/fix-homm-steam-ui.sh # один раз, если пустой UI Steam" +echo " bash ${SCRIPT_DIR}/start.sh steam-ui # login в аккаунт" +echo " bash ${SCRIPT_DIR}/start.sh play # или Play в библиотеке" +echo "" +echo "Не использовать для сейвов: start.sh game без Logged On Steam." +exit 1 diff --git a/scripts/games/diagnose-homm-steam-ui.sh b/scripts/games/diagnose-homm-steam-ui.sh new file mode 100755 index 0000000..0374031 --- /dev/null +++ b/scripts/games/diagnose-homm-steam-ui.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Почему нет окна Wine-Steam после start.sh steam-ui. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +CEF_LOG="${STEAM_DIR}/logs/cef_log.txt" +CONSOLE_LOG="${STEAM_DIR}/logs/console_log.txt" + +echo "=== Wine-Steam UI — диагностика ===" +echo "Bottle: ${BOTTLE_NAME} (${BOTTLE_ID})" +echo "" + +echo "1) Mac Steam (мешает login / CEF):" +if homm_mac_steam_running; then + echo " ❌ Запущен steam_osx — killall steam_osx ipcserver" +else + echo " ✅ Mac Steam не мешает" +fi +echo "" + +echo "2) Процессы bottle:" +if pgrep -f "${BOTTLE_ID}.*Steam\\.exe" >/dev/null 2>&1; then + echo " ✅ Steam.exe" + pgrep -lf "${BOTTLE_ID}.*Steam\\.exe" 2>/dev/null | head -2 | sed 's/^/ /' +else + echo " ❌ Steam.exe не запущен — bash ${SCRIPT_DIR}/start.sh steam-ui" +fi +if pgrep -f "${BOTTLE_ID}.*steamwebhelper" >/dev/null 2>&1 || pgrep -f "Whisky/Bottles/${BOTTLE_ID}.*steamwebhelper" >/dev/null 2>&1; then + echo " ✅ steamwebhelper (CEF UI)" + pgrep -lf 'steamwebhelper' 2>/dev/null | grep -E "${BOTTLE_ID}|Program Files \(x86\)\\\\Steam" | head -2 | sed 's/^/ /' || pgrep -lf steamwebhelper | head -1 | sed 's/^/ /' +else + echo " ❌ steamwebhelper нет — окно Steam (login) не появится" + echo " Типично: cef_log → message_window.cc Class does not exist" +fi +echo "" + +echo "3) CEF-лог (последние ошибки):" +if [[ -f "${CEF_LOG}" ]]; then + if tail -40 "${CEF_LOG}" | grep -q 'message_window.cc'; then + echo " ❌ message_window.cc — steamwebhelper падает / перезапускается" + tail -40 "${CEF_LOG}" | grep 'message_window\|chrome_elf\|LdrInitialize' | tail -3 | sed 's/^/ /' + echo " Фикс: bash ${SCRIPT_DIR}/start.sh reset && bash ${SCRIPT_DIR}/start.sh steam-ui" + echo " (в start.sh уже: ${HOMM_STEAM_CEF_ARGS})" + else + echo " ✅ Нет свежих message_window в хвосте cef_log" + fi +else + echo " (cef_log.txt ещё нет — Steam не стартовал)" +fi +echo "" + +echo "4) VGUI / шрифты (console_log):" +if [[ -f "${CONSOLE_LOG}" ]] && tail -30 "${CONSOLE_LOG}" | grep -q winFont; then + echo " ⚠️ winFont — пустой текст на кнопках VGUI" + echo " bash ${SCRIPT_DIR}/start.sh fix-steam-ui && bash ${SCRIPT_DIR}/start.sh reset && bash ${SCRIPT_DIR}/start.sh steam-ui" +else + echo " ✅ winFont в хвосте нет (или лог пуст)" +fi +echo "" + +echo "5) Login:" +if homm_steam_logged_on; then + echo " ✅ Logged On" +else + echo " ❌ Logged Off — Retry / Enter Offline Mode в окне Steam" +fi +echo "" + +echo "Где искать окно:" +echo " • Whisky.app → bottle HoMM-OldenEra (после migrate-bottle-to-franke-whisky.sh)" +echo " • Cmd+Tab (wine64), Mission Control" +echo " • Не HOMM_STEAM_VDESKTOP=1 — explorer: Failed to set primary display settings" diff --git a/scripts/games/fix-gog-auth.sh b/scripts/games/fix-gog-auth.sh new file mode 100755 index 0000000..f2599fe --- /dev/null +++ b/scripts/games/fix-gog-auth.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# GOG Galaxy в Wine: логин в CEF ок, но auth.gog.com/token таймаут → сброс на пароль. +# Фиксы: IPv6 в Wine, сеть Poco/OpenSSL, CEF-флаги GOG. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +cmd_wine_check() { + if [[ ! -x "${WINE64}" ]]; then + echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh" + exit 1 + fi +} + +cmd_wine_network_registry() { + export WINEPREFIX="${BOTTLE_DIR}" + echo "Wine: отключаю IPv6 DNS (частая причина зависания WinHTTP/Poco)…" + "${WINE64}" reg add 'HKCU\Software\Wine\Network' /v DnsIPv6Enabled /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true + "${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \ + /v DisableIPv6 /t REG_DWORD /d 1 /f >/dev/null 2>&1 || true + echo "Wine: ProxyEnable=0 (без системного прокси в bottle)…" + "${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \ + /v ProxyEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true +} + +cmd_dll_overrides() { + export WINEPREFIX="${BOTTLE_DIR}" + local key='HKCU\Software\Wine\DllOverrides' + echo "Wine: DllOverrides для OpenSSL/Poco (native)…" + for dll in libssl-1_1 libcrypto-1_1 libssl-3 libcrypto-3; do + "${WINE64}" reg add "${key}" /v "${dll}" /t REG_SZ /d native,builtin /f >/dev/null 2>&1 || true + done +} + +cmd_clear_gog_webcache() { + local cache="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/webcache" + if [[ -d "${cache}" ]]; then + rm -rf "${cache:?}"/* + echo "Очищен webcache: ${cache}" + fi +} + +cmd_update_plist() { + local ps="${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist" + local args + args="$(homm_gog_cef_args_line)" + if [[ -f "${ps}" ]]; then + /usr/bin/plutil -replace arguments -string "${args}" "${ps}" 2>/dev/null \ + || echo " (plist вручную: ${args})" + echo "Обновлён ${ps}" + fi +} + +main() { + cmd_wine_check + if ! homm_gog_exe >/dev/null; then + echo "GOG не установлен: bash ${SCRIPT_DIR}/start.sh gog-install" + exit 1 + fi + cmd_wine_network_registry + cmd_dll_overrides + cmd_clear_gog_webcache + cmd_update_plist + echo "" + echo "OK. Перезапуск GOG:" + echo " bash ${SCRIPT_DIR}/start.sh gog-restart" + echo "" + echo "Если снова сброс на пароль — соберите логи:" + echo " bash ${SCRIPT_DIR}/diagnose-gog-auth.sh" + echo "" + echo "Проверка в логе после входа: НЕ должно быть «timeout … auth.gog.com/token»." +} + +main "$@" diff --git a/scripts/games/fix-homm-steam-ui.sh b/scripts/games/fix-homm-steam-ui.sh new file mode 100755 index 0000000..519a877 --- /dev/null +++ b/scripts/games/fix-homm-steam-ui.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Steam в Wine/Whisky: пустой VGUI (winFont), кнопки без текста → DWriteEnable + шрифты. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +cmd_wine_check() { + if [[ ! -x "${WINE64}" ]]; then + echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh" + exit 1 + fi +} + +cmd_install_fonts() { + local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts" + local steam="${STEAM_DIR}" + mkdir -p "${fonts}" + for f in /System/Library/Fonts/Supplemental/{Arial,Tahoma,Verdana,"Times New Roman","Courier New"}.ttf; do + [[ -f "${f}" ]] && cp -n "${f}" "${fonts}/" 2>/dev/null || true + done + cp -n "${steam}/resource/fonts/marlett.ttf" "${fonts}/" 2>/dev/null || true + cp -n "${steam}/clientui/fonts/"*.ttf "${fonts}/" 2>/dev/null || true + echo "Шрифты в ${fonts}/ ($(ls "${fonts}" | wc -l | tr -d ' ') файлов)" +} + +cmd_dwrite_registry() { + export WINEPREFIX="${BOTTLE_DIR}" + "${WINE64}" reg add 'HKCU\Software\Valve\Steam' /v DWriteEnable /t REG_DWORD /d 0 /f >/dev/null + echo "Registry: HKCU\\Software\\Valve\\Steam\\DWriteEnable = 0" +} + +main() { + cmd_wine_check + cmd_install_fonts + cmd_dwrite_registry + echo "" + echo "OK. Перезапустите Steam:" + echo " bash ${SCRIPT_DIR}/start.sh reset" + echo " bash ${SCRIPT_DIR}/start.sh steam-ui" + echo "" + echo "Если текст всё ещё пустой — пройдите диалог вслепую: правая кнопка внизу или Tab → Enter." +} + +main "$@" diff --git a/scripts/games/fix-homm-wine-input.sh b/scripts/games/fix-homm-wine-input.sh new file mode 100755 index 0000000..f0f1fee --- /dev/null +++ b/scripts/games/fix-homm-wine-input.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +MODE="${1:-sdl-on}" +export_wine_env + +case "$MODE" in + sdl-on) + "${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v DisableHidraw /t REG_DWORD /d 1 /f >/dev/null + "${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v 'Enable SDL' /t REG_DWORD /d 1 /f >/dev/null + echo "WineBus SDL ON" + ;; + sdl-off) + "${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v DisableHidraw /t REG_DWORD /d 0 /f >/dev/null + "${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v 'Enable SDL' /t REG_DWORD /d 0 /f >/dev/null + echo "WineBus SDL OFF" + ;; + show) + "${WINE64}" reg query 'HKLM\System\CurrentControlSet\Services\winebus' /v DisableHidraw || true + "${WINE64}" reg query 'HKLM\System\CurrentControlSet\Services\winebus' /v 'Enable SDL' || true + ;; + *) + echo "Usage: $(basename "$0") [sdl-on|sdl-off|show]"; exit 1 + ;; +esac diff --git a/scripts/games/gog/.gitignore b/scripts/games/gog/.gitignore new file mode 100644 index 0000000..f4e35c3 --- /dev/null +++ b/scripts/games/gog/.gitignore @@ -0,0 +1 @@ +bottle.env diff --git a/scripts/games/gog/README.md b/scripts/games/gog/README.md new file mode 100644 index 0000000..687ce86 --- /dev/null +++ b/scripts/games/gog/README.md @@ -0,0 +1,64 @@ +# GOG web-installer (без Steam) + +Отдельный Whisky-bottle. Скрипты **не** трогают HoMM-OldenEra, Steam, `start.sh play`, дампы Steam. + +Антипаттерны (swap, winedbg, «20 GB»): [docs/games/TROUBLESHOOTING.md](../../../docs/games/TROUBLESHOOTING.md#антипаттерны-не-делать). + +## Один раз + +```bash +# 1) Whisky → New Bottle → Windows 10 64-bit → имя «GOG-Install» +bash ~/code/local_machine/scripts/games/gog/list-bottles.sh +bash ~/code/local_machine/scripts/games/gog/use-bottle.sh GOG-Install + +# 2) Подготовка bottle (или одной командой: bash gog/install-all.sh /path/stub.exe) +bash ~/code/local_machine/scripts/games/gog/create-bottle.sh GOG-Install # если bottle ещё нет +bash ~/code/local_machine/scripts/games/gog/setup-bottle.sh +bash ~/code/local_machine/scripts/games/gog/install-gdiplus.sh # обязательно (~900MB скачивание) +brew install cabextract +bash ~/code/local_machine/scripts/games/gog/install-corefonts.sh +``` + +## Запуск web-stub + +```bash +bash ~/code/local_machine/scripts/games/gog/run-web-installer.sh \ + ~/path/to/GOG_Galaxy_Jagged_Alliance_3.exe +``` + +Лог: `/tmp/gog-web-installer-GOG-Install.log` + +## Jagged Alliance 3 **без GOG Galaxy** (канон) + +**Runbook (установка + графика + RCA + антипаттерны):** [JA3_RUNBOOK.md](../../../docs/games/JA3_RUNBOOK.md) + +```bash +# Установка (Whisky GOG-Install): 5 частей offline backup в ~/Games/ +bash ~/code/local_machine/scripts/games/gog/install-ja3-innoextract.sh +bash ~/code/local_machine/scripts/games/gog/finish-ja3-layout.sh + +# Запуск (CrossOver D3DMetal — один раз setup, затем каждый раз run): +brew install --cask crossover +bash ~/code/local_machine/scripts/games/gog/setup-ja3-crossover.sh +bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh +``` + +| Скрипт | Назначение | +|--------|------------| +| `install-ja3-innoextract.sh` | **Канон установки** (Whisky) | +| `finish-ja3-layout.sh` | CommonLua, реестр GOG | +| `setup-ja3-crossover.sh` | CrossOver bottle JA3-GOG + clone игры | +| **`run-ja3-crossover.sh`** | **Канон запуска** (D3D12 / D3DMetal) | +| `run-ja3.sh` | Whisky — **графика D3D12 не работает** (см. runbook) | + +Galaxy / web-stub — **не использовать** для JA3 ([GOG_GALAXY_7H_RCA.md](../../../docs/games/GOG_GALAXY_7H_RCA.md)). + +## Сброс только GOG + +```bash +bash ~/code/local_machine/scripts/games/gog/reset.sh +``` + +## Устаревшее (не использовать для GOG) + +- `setup-wine-bottle.sh` / bottle **HoMM-OldenEra** — там Steam и общий префикс. diff --git a/scripts/games/gog/bottle.env.example b/scripts/games/gog/bottle.env.example new file mode 100644 index 0000000..c8b3920 --- /dev/null +++ b/scripts/games/gog/bottle.env.example @@ -0,0 +1,6 @@ +# Скопируйте в bottle.env и укажите отдельный Whisky-bottle (без Steam/HoMM). +# Создайте в Whisky: New Bottle → Windows 10 64-bit → имя «GOG-Install» +# UUID: Whisky → bottle → правый клик / About, или: bash list-bottles.sh + +BOTTLE_ID=00000000-0000-0000-0000-000000000000 +BOTTLE_NAME=GOG-Install diff --git a/scripts/games/gog/collect-gog-logs-7h.sh b/scripts/games/gog/collect-gog-logs-7h.sh new file mode 100755 index 0000000..b1cfa7d --- /dev/null +++ b/scripts/games/gog/collect-gog-logs-7h.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Собрать GOG/Whisky логи за N часов (по умолчанию 7) + timeline событий. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +HOURS="${1:-7}" +OUT="${2:-${HOME}/code/local_machine/temp/gog-logs-${HOURS}h-$(date +%Y%m%d-%H%M%S)}" +GALOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs" +mkdir -p "${OUT}" + +cp -R "${GALOG}" "${OUT}/galaxy-logs" 2>/dev/null || true +mkdir -p "${OUT}/whisky-logs" +cp "${HOME}/Library/Logs/com.franke.Whisky"/*.log "${OUT}/whisky-logs/" 2>/dev/null || true +[[ -f "/tmp/gog-galaxy-client-${BOTTLE_NAME}.log" ]] && cp "/tmp/gog-galaxy-client-${BOTTLE_NAME}.log" "${OUT}/" || true + +df -h "$(gog_data_volume_path)" >"${OUT}/disk.txt" +pgrep -fl 'Galaxy|GOG|explorer.*GOG' >"${OUT}/processes-now.txt" 2>/dev/null || echo "(none)" >"${OUT}/processes-now.txt" +plutil -p "${BOTTLE_DIR}/Metadata.plist" >"${OUT}/metadata.plist.txt" 2>/dev/null || true +[[ -f "${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist" ]] && \ + plutil -p "${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist" >"${OUT}/GalaxyClient.exe.plist.txt" + +python3 <"${OUT}/README.txt" </dev/null || echo "(none)" + echo "" + echo "=== GalaxyClient ===" + find "${BOTTLE_DIR}/drive_c" -name 'GalaxyClient.exe' 2>/dev/null || true +} >"${OUT}/summary.txt" + +for f in \ + "/tmp/gog-web-installer-${BOTTLE_NAME}.log" \ + "/tmp/gog-galaxy-setup-${BOTTLE_NAME}.log"; do + [[ -f "${f}" ]] && cp "${f}" "${OUT}/" +done + +find "${BOTTLE_DIR}/drive_c" -name 'Setup Log*.txt' 2>/dev/null | while read -r f; do + cp "${f}" "${OUT}/$(basename "${f}" | tr ' #' '_')" +done + +find "${BOTTLE_DIR}/drive_c/ProgramData/GOG.com" -name '*.log' 2>/dev/null | while read -r f; do + cp "${f}" "${OUT}/$(basename "${f}")" +done + +echo "${OUT}" diff --git a/scripts/games/gog/config.sh b/scripts/games/gog/config.sh new file mode 100755 index 0000000..8242dd2 --- /dev/null +++ b/scripts/games/gog/config.sh @@ -0,0 +1,238 @@ +# GOG-only Wine/Whisky — без Steam, HoMM, export_wine_env_steam. +GOG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +GAMES_CACHE_DIR="${GAMES_DIR}/cache" + +_env_file="${GOG_DIR}/bottle.env" +if [[ -f "${_env_file}" ]]; then + # shellcheck source=/dev/null + source "${_env_file}" +elif [[ -n "${GOG_BOTTLE_ID:-}" ]]; then + BOTTLE_ID="${GOG_BOTTLE_ID}" + BOTTLE_NAME="${GOG_BOTTLE_NAME:-GOG-Install}" +else + echo "❌ Нет bottle для GOG." + echo " 1) Whisky → New Bottle (Windows 10 64-bit), имя GOG-Install" + echo " 2) bash ${GOG_DIR}/list-bottles.sh" + echo " 3) bash ${GOG_DIR}/use-bottle.sh " + echo "" + echo " Не используйте bottle HoMM-OldenEra — там Steam, дампы и winedbg." + exit 1 +fi +unset _env_file + +_FRANKE="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}" +_ISAAC="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}" +if [[ -d "${_FRANKE}/drive_c" ]]; then + BOTTLE_DIR="${_FRANKE}" + WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky" +elif [[ -d "${_ISAAC}/drive_c" ]]; then + BOTTLE_DIR="${_ISAAC}" + WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky" +else + echo "❌ Bottle не найден: ${BOTTLE_NAME} (${BOTTLE_ID})" + echo " Проверьте UUID: bash ${GOG_DIR}/list-bottles.sh" + exit 1 +fi +unset _FRANKE _ISAAC + +WINE64="${WHISKY_LIBS}/Libraries/Wine/bin/wine64" +WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib" +WINEPATH_BIN="${WHISKY_LIBS}/Libraries/Wine/bin/winepath" + +gog_data_volume_path() { + if [[ -d /System/Volumes/Data ]]; then + echo /System/Volumes/Data + else + echo "${HOME}" + fi +} + +gog_disk_free_kb() { + df -k "$(gog_data_volume_path)" | awk 'NR==2 {print $4}' +} + +gog_disk_guard() { + local min_gb="${1:-5}" + local min_kb free_kb + min_kb=$((min_gb * 1024 * 1024)) + free_kb="$(gog_disk_free_kb)" + if [[ "${free_kb}" -lt "${min_kb}" ]]; then + echo "❌ Мало места (<${min_gb} GB на Data)." + return 1 + fi + return 0 +} + +gog_swap_used_mb() { + local used + used="$(sysctl vm.swapusage 2>/dev/null | sed -n 's/.*used = \([0-9.]*\)M.*/\1/p')" + [[ -n "${used}" ]] || return 1 + printf '%.0f' "${used}" +} + +gog_swap_guard() { + local max_mb="${1:-400}" + local used + used="$(gog_swap_used_mb)" || return 0 + if awk -v u="${used}" -v m="${max_mb}" 'BEGIN{exit !(u>=m)}'; then + echo "❌ Swap уже ~${used}MB (лимит ${max_mb}MB) — запуск GOG/Wine запрещён." + echo " bash ${GOG_DIR}/emergency-stop.sh" + echo " затем перезагрузка Mac (swap macOS отдаёт медленно)" + return 1 + fi + return 0 +} + +# JA3/CrossOver: жёсткий стоп только если swap высокий И Wine/JA3 ещё живы. +# «Залипший» swap после закрытия (macOS не отдал) — предупреждение, запуск разрешён. +gog_wine_or_ja3_active() { + pgrep -f 'JA3\.exe|wineserver|wine64-preloader|winedbg|wine64\.exe|CrossOver.*wine' >/dev/null 2>&1 +} + +gog_swap_guard_ja3_launch() { + local soft_mb="${1:-1200}" + local hard_mb="${2:-4096}" + local used + used="$(gog_swap_used_mb)" || return 0 + if [[ "${JA3_IGNORE_SWAP:-0}" == "1" ]]; then + echo "⚠️ JA3_IGNORE_SWAP=1 — swap ~${used}MB" + return 0 + fi + if gog_wine_or_ja3_active; then + if awk -v u="${used}" -v m="${soft_mb}" 'BEGIN{exit !(u>=m)}'; then + echo "❌ Swap ~${used}MB и Wine/JA3 ещё работают (лимит ${soft_mb}MB)." + echo " bash ${GOG_DIR}/reset-ja3-crossover.sh или bash ${GOG_DIR}/emergency-stop.sh" + echo " затем reboot Mac. Или: JA3_IGNORE_SWAP=1 bash …/run-ja3-crossover.sh" + return 1 + fi + return 0 + fi + if awk -v u="${used}" -v m="${hard_mb}" 'BEGIN{exit !(u>=m)}'; then + echo "❌ Swap ~${used}MB (лимит ${hard_mb}MB) — reboot Mac." + echo " Или: JA3_IGNORE_SWAP=1 bash …/run-ja3-crossover.sh" + return 1 + fi + if awk -v u="${used}" -v m="${soft_mb}" 'BEGIN{exit !(u>=m)}'; then + echo "⚠️ Swap ~${used}MB — залип после прошлых сессий (Wine не запущен). Запуск JA3…" + echo " (reboot снизит swap; JA3_IGNORE_SWAP=1 — принудительный игнор)" + fi + return 0 +} + +# Виртуальный стол Wine — окно CEF часто «есть в логах», но не видно на macOS без /desktop. +GOG_VDESKTOP="${GOG_VDESKTOP:-1280x800}" +# 0 = прямой GalaxyClient (рекомендуется; vdesktop — в Whisky Metadata или launch-galaxy-ui.sh). +GOG_USE_WINEVDESKTOP="${GOG_USE_WINEVDESKTOP:-0}" +# GOG_LAUNCH_FG=1 — exec в текущем терминале (лучше для видимости окна, блокирует shell). +GOG_LAUNCH_FG="${GOG_LAUNCH_FG:-0}" +# GOG_CEF_PROFILE=steam — добавить -cef-in-process-gpu (A/B если login пустой). +# /deelevated — обязательно в Wine (иначе DeelevateStrategy + exit -31, см. WineHQ #41584). +GOG_GALAXY_BASE_ARGS="${GOG_GALAXY_BASE_ARGS:-/runWithoutUpdating /deelevated}" +GOG_GALAXY_CEF_ARGS="${GOG_GALAXY_CEF_ARGS:--allosarches -cef-force-32bit -no-cef-sandbox -cef-disable-gpu}" + +gog_galaxy_cef_args_array() { + local -a a=(${GOG_GALAXY_BASE_ARGS} ${GOG_GALAXY_CEF_ARGS}) + if [[ "${GOG_CEF_PROFILE:-}" == "steam" ]]; then + a+=(-cef-in-process-gpu) + fi + printf '%s\n' "${a[@]}" +} + +export_wine_env_gog() { + export WINEPREFIX="${BOTTLE_DIR}" + export WINEMSYNC=1 + unset WINEESYNC 2>/dev/null || true + local _wine_ext="${WHISKY_LIBS}/Libraries/Wine/lib/external" + if [[ -d "${_wine_ext}/D3DMetal.framework" ]]; then + export DYLD_FRAMEWORK_PATH="${_wine_ext}:${DYLD_FRAMEWORK_PATH:-}" + export DYLD_LIBRARY_PATH="${_wine_ext}:${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + else + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + fi + unset _wine_ext + export WINEDLLOVERRIDES="gdiplus=n,b" + unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true + export WINEDEBUG=-all + export MVK_CONFIG_LOG_LEVEL=0 +} + +ja3_gptk_available() { + [[ -d "${WHISKY_LIBS}/Libraries/Wine/lib/external/D3DMetal.framework" ]] && return 0 + [[ -f "${WHISKY_LIBS}/Libraries/.gptk_installed" ]] && return 0 + [[ -d "${WHISKY_LIBS}/D3DMetal" ]] && return 0 + [[ -d "${WHISKY_LIBS}/GPTK" ]] && return 0 + return 1 +} + +ja3_bottle_uses_d3dmetal() { + local meta="${BOTTLE_DIR}/Metadata.plist" + [[ -f "${meta}" ]] || return 1 + plutil -extract graphicsConfig.backend raw -o - "${meta}" 2>/dev/null | grep -q d3dMetal +} + +ja3_dxmt_installed() { + [[ -f "${BOTTLE_DIR}/.ja3_dxmt_installed" ]] +} + +# JA3 = D3D12 (Porting Kit / GPTK). Без GPTK — best-effort D3D11+DXVK (d3d12=d). +export_wine_env_ja3() { + export_wine_env_gog + if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + export WINED3DMETAL=1 + unset DXVK_ASYNC DXVK_HUD DXVK_CONFIG_FILE 2>/dev/null || true + # builtin d3d12/dxgi + libd3dshared.so; без n,b на GPTK PE + export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b" + return 0 + fi + if ja3_dxmt_installed; then + # DXMT builtin: не ставить dxgi/d3d11=n,b (ломает загрузку, c000007b). + export WINEDLLOVERRIDES="d3d12=d;libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b" + else + export WINEDLLOVERRIDES="d3d11,d3d10core=n,b;d3d12=d;libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b" + fi + unset DXVK_ASYNC DXVK_HUD DXVK_CONFIG_FILE 2>/dev/null || true + export SDL_VIDEO_MAC_FULLSCREEN_SPACES="${SDL_VIDEO_MAC_FULLSCREEN_SPACES:-0}" + export MVK_CONFIG_RESUME_LOST_DEVICE=1 +} + +export_wine_env_ja3_d3dmetal() { + export_wine_env_gog + export WINED3DMETAL=1 + export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b" + unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true +} + +gog_steam_client_in_bottle() { + local f + [[ -d "${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam" ]] && return 0 + [[ -f "${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam/steam.exe" ]] && return 0 + f="$(find "${BOTTLE_DIR}/drive_c" -maxdepth 4 -iname 'steam.exe' -print -quit 2>/dev/null || true)" + [[ -n "${f}" ]] +} + +gog_assert_no_steam_client() { + if gog_steam_client_in_bottle; then + echo "❌ В bottle ${BOTTLE_NAME} найден Steam client — контур GOG/JA3 без Steam." + echo " bash ${GOG_DIR}/purge-steam-from-bottle.sh" + return 1 + fi + if [[ "${BOTTLE_ID}" == "38412122-AAE1-4831-9F2E-6A0EB364B3F9" ]]; then + echo "❌ Это bottle HoMM-OldenEra (Steam). JA3 только в GOG-Install:" + echo " bash ${GOG_DIR}/use-bottle.sh " + return 1 + fi + return 0 +} + +gog_wine_check() { + if [[ ! -x "${WINE64}" ]]; then + echo "Нет Wine: bash ${GAMES_DIR}/install-whiskywine-manual.sh" + exit 1 + fi + if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then + echo "❌ Нет drive_c в ${BOTTLE_DIR}" + echo " Откройте bottle «${BOTTLE_NAME}» в Whisky один раз." + exit 1 + fi +} diff --git a/scripts/games/gog/configure-ja3-d3dmetal.sh b/scripts/games/gog/configure-ja3-d3dmetal.sh new file mode 100755 index 0000000..b3b24d1 --- /dev/null +++ b/scripts/games/gog/configure-ja3-d3dmetal.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# JA3 = DirectX 12. На Apple Silicon канон: Whisky + Game Porting Toolkit (D3DMetal), не DXVK. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +META="${BOTTLE_DIR}/Metadata.plist" +[[ -f "${META}" ]] || { + echo "❌ Нет ${META} — откройте bottle ${BOTTLE_NAME} в Whisky." + exit 1 +} + +if ! ja3_gptk_available; then + if [[ -d "/Applications/Game Porting Toolkit.app" ]]; then + bash "${GOG_DIR}/install-gptk-whisky.sh" + else + echo "❌ Game Porting Toolkit не установлен." + echo " brew install --cask gcenx/wine/game-porting-toolkit" + echo " bash ${GOG_DIR}/install-gptk-whisky.sh" + exit 1 + fi +fi + +/usr/libexec/PlistBuddy -c 'Set :graphicsConfig:backend d3dMetal' "${META}" 2>/dev/null || \ + /usr/libexec/PlistBuddy -c 'Add :graphicsConfig:backend string d3dMetal' "${META}" +/usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk false' "${META}" 2>/dev/null || true + +bash "${GOG_DIR}/force-ja3-d3d11.sh" >/dev/null 2>&1 || true +# С GPTK можно d3d12 — сбросить принудительный d3d11 в LocalStorage при желании: +# perl -i -pe 's/GraphicsApi = "d3d11"/GraphicsApi = "d3d12"/' ... + +bash "${GOG_DIR}/prepare-ja3-d3dmetal.sh" + +echo "OK: bottle ${BOTTLE_NAME} → Graphics Backend D3DMetal" +echo " Запуск: bash ${GOG_DIR}/run-ja3.sh" +echo " Whisky Run: C:\\GOG Games\\Jagged Alliance 3\\run-ja3.cmd (не JA3.exe — иначе graphics/CommonLua)" diff --git a/scripts/games/gog/create-bottle.sh b/scripts/games/gog/create-bottle.sh new file mode 100755 index 0000000..7689629 --- /dev/null +++ b/scripts/games/gog/create-bottle.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# Создать пустой Whisky-bottle «GOG-Install» (wineboot --init), без Steam. +set -euo pipefail +GOG_SCRIPTS="$(cd "$(dirname "$0")" && pwd)" +FRANKE="${HOME}/Library/Containers/com.franke.Whisky" +WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky/Libraries" +WINE64="${WHISKY_LIBS}/Wine/bin/wine64" +WINE_LIB="${WHISKY_LIBS}/Wine/lib" +NAME="${1:-GOG-Install}" + +if [[ -f "${GOG_SCRIPTS}/bottle.env" ]]; then + # shellcheck source=/dev/null + source "${GOG_SCRIPTS}/bottle.env" + if [[ -d "${FRANKE}/Bottles/${BOTTLE_ID}/drive_c" ]]; then + echo "Bottle уже есть: ${BOTTLE_NAME} (${BOTTLE_ID})" + exit 0 + fi +fi + +if [[ ! -x "${WINE64}" ]]; then + echo "Нет Wine: bash $(dirname "$GOG_SCRIPTS")/install-whiskywine-manual.sh" + exit 1 +fi + +BOTTLE_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')" +BOTTLE_PATH="${FRANKE}/Bottles/${BOTTLE_ID}" +mkdir -p "${BOTTLE_PATH}" + +echo "Создаю префикс Wine: ${BOTTLE_PATH} (2–5 мин)…" +export WINEPREFIX="${BOTTLE_PATH}" +export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" +export WINEDEBUG=-all +"${WINE64}" wineboot --init + +python3 </dev/null 2>&1; then + whisky add "${BOTTLE_PATH}" 2>/dev/null || true +fi + +cat >"${GOG_SCRIPTS}/bottle.env" </dev/null || echo "(нет процессов)" + echo "" + echo "--- disk ---" + df -h "$(gog_data_volume_path)" 2>/dev/null | tail -1 +} >"${OUT}/summary.txt" + +for f in "${GALAXY_LOG}" "${INIT_LOG}" "${CEF_LOG}" "${LAUNCH_LOG}"; do + [[ -f "${f}" ]] || continue + base="$(basename "${f}")" + tail -80 "${f}" >"${OUT}/${base}.tail" 2>/dev/null || true +done + +grep -E 'Second client|will continue|Showing login|foreground|Failed to locate|parent window' \ + "${GALAXY_LOG}" "${CEF_LOG}" 2>/dev/null >"${OUT}/signals.txt" || true + +echo "OK: ${OUT}" +echo " summary.txt, signals.txt, *.tail" +echo "" +echo "Если в signals.txt есть login/foreground, а окна нет:" +echo " bash ${GOG_DIR}/reset.sh" +echo " bash ${GOG_DIR}/launch-galaxy-ui.sh # vdesktop + foreground" +echo " Cmd+Tab → wine64 / GOGGalaxy / GalaxyClient" diff --git a/scripts/games/gog/diagnose-session.sh b/scripts/games/gog/diagnose-session.sh new file mode 100755 index 0000000..a60b56d --- /dev/null +++ b/scripts/games/gog/diagnose-session.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +RCA="${GAMES_DIR}/../temp/gog-rca-$(date +%Y%m%d-%H%M%S)" +LOG="/tmp/gog-web-installer-${BOTTLE_NAME}.log" +mkdir -p "${RCA}" + +{ + echo "GOG bottle: ${BOTTLE_NAME} (${BOTTLE_ID})" + df -h "$(gog_data_volume_path)" + sysctl vm.swapusage 2>/dev/null || true + echo "" + echo "markers:"; ls -la "${BOTTLE_DIR}"/.gog_* 2>/dev/null || true + echo "" + du -sh "${BOTTLE_DIR}" "${BOTTLE_DIR}/drive_c/windows/Fonts" 2>/dev/null || true + echo "" + echo "log ${LOG}:" + tail -30 "${LOG}" 2>/dev/null || echo "(нет)" + echo "" + echo "winedbg: $(pgrep winedbg 2>/dev/null | wc -l | tr -d ' ')" + pgrep -lf 'GOG_Galaxy|GalaxyWeb' 2>/dev/null || echo "(нет GOG процессов)" +} | tee "${RCA}/report.txt" + +cp "${LOG}" "${RCA}/" 2>/dev/null || true +echo "RCA: ${RCA}" diff --git a/scripts/games/gog/emergency-stop.sh b/scripts/games/gog/emergency-stop.sh new file mode 100755 index 0000000..a27f3f2 --- /dev/null +++ b/scripts/games/gog/emergency-stop.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Снять нагрузку GOG/Wine + winedbg (swap). Безопасно для GOG-bottle. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=gog-process.sh +source "${GOG_DIR}/gog-process.sh" + +_winedbg_count() { + pgrep winedbg 2>/dev/null | wc -l | tr -d ' ' +} + +echo "=== До ===" +sysctl vm.swapusage 2>/dev/null || true +echo "winedbg: $(_winedbg_count)" + +gog_kill_all +pkill -9 winedbg 2>/dev/null || true + +echo "" +echo "=== После kill ===" +pgrep -fl 'Galaxy|GOG Galaxy' 2>/dev/null || echo "Galaxy: (нет)" +echo "winedbg: $(_winedbg_count)" +sysctl vm.swapusage 2>/dev/null || true +echo "" +echo "Если Mac всё ещё тормозит — перезагрузка (swap отдаётся медленно)." +echo "Galaxy UI в Wine на этом Mac не подтверждён — JA3: docs/games/GOG_GALAXY_7H_RCA.md § Offline" diff --git a/scripts/games/gog/fetch-ja3-setup-exe.sh b/scripts/games/gog/fetch-ja3-setup-exe.sh new file mode 100755 index 0000000..049c4ec --- /dev/null +++ b/scripts/games/gog/fetch-ja3-setup-exe.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Скачать только лаунчер (~1 MB) для multipart offline backup JA3. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}" +OUT="${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730).exe" + +mkdir -p "${JA3_GAMES_DIR}" + +if [[ -f "${OUT}" ]]; then + echo "Уже есть: ${OUT}" + exit 0 +fi + +if ! command -v lgogdownloader >/dev/null 2>&1; then + echo "❌ Нужен lgogdownloader: brew install lgogdownloader" + exit 1 +fi + +echo "Скачивание en1installer0 (~1 MB) → ${OUT}" +echo "Если ошибка login — выполните: lgogdownloader --login" +echo "" + +lgogdownloader \ + --download-file "jagged_alliance_3/en1installer0" \ + --output-file "$(basename "${OUT}")" \ + --directory "${JA3_GAMES_DIR}" + +if [[ -f "${OUT}" ]]; then + ls -lh "${OUT}" + echo "OK. Дальше: bash ${GOG_DIR}/install-ja3-multipart.sh" +else + # lgogdownloader может сохранить под другим именем + f="$(find "${JA3_GAMES_DIR}" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print -quit 2>/dev/null || true)" + if [[ -n "${f}" ]]; then + echo "Скачан как: ${f}" + echo "Запуск: JA3_SETUP_EXE='${f}' bash ${GOG_DIR}/install-ja3-multipart.sh" + else + echo "❌ .exe не появился в ${JA3_GAMES_DIR}" + exit 1 + fi +fi diff --git a/scripts/games/gog/finish-ja3-install.sh b/scripts/games/gog/finish-ja3-install.sh new file mode 100755 index 0000000..908d108 --- /dev/null +++ b/scripts/games/gog/finish-ja3-install.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Устарело: Galaxy UI. Канон — install-ja3.sh (offline backup без Galaxy). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +echo "Канон JA3 без Galaxy: bash ${GOG_DIR}/install-ja3.sh" +echo "Док: docs/games/JA3_MAC_WITHOUT_GALAXY.md" +exec bash "${GOG_DIR}/install-ja3.sh" diff --git a/scripts/games/gog/finish-ja3-layout.sh b/scripts/games/gog/finish-ja3-layout.sh new file mode 100755 index 0000000..3da0bed --- /dev/null +++ b/scripts/games/gog/finish-ja3-layout.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Доп. шаги после innoextract: layout GOG, реестр, DXVK, CommonLua. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +JA3="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3" + +[[ -f "${JA3}/JA3.exe" ]] || { + echo "❌ Нет JA3 — сначала install-ja3-innoextract.sh" + exit 1 +} + +# Inno Setup копирует CommonLua в корень; innoextract — только ModTools/ (Wine не видит symlink) +if [[ ! -f "${JA3}/CommonLua/Core/autorun.lua" ]]; then + rm -rf "${JA3}/CommonLua" + cp -R "${JA3}/ModTools/Src/CommonLua" "${JA3}/CommonLua" + echo " CommonLua скопирован из ModTools/Src (~9 MB)" +fi + +bash "${GOG_DIR}/register-ja3-registry.sh" +bash "${GOG_DIR}/install-ja3-redist.sh" 2>/dev/null || true +if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + bash "${GOG_DIR}/prepare-ja3-d3dmetal.sh" 2>/dev/null || bash "${GOG_DIR}/install-gptk-whisky.sh" || true +else + if [[ -d "/Applications/Game Porting Toolkit.app" ]]; then + bash "${GOG_DIR}/install-gptk-whisky.sh" + else + bash "${GOG_DIR}/install-ja3-dxmt.sh" 2>/dev/null || bash "${GOG_DIR}/install-dxvk.sh" + fi +fi +cp -f "${GOG_DIR}/ja3-dxvk.conf" "${JA3}/dxvk.conf" 2>/dev/null || true +if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + perl -i -pe 's/^\t\tGraphicsApi = "d3d11"/\t\tGraphicsApi = "d3d12"/' \ + "${BOTTLE_DIR}/drive_c/users/${WINE_USER:-eternal}/AppData/Roaming/Jagged Alliance 3/LocalStorage.lua" 2>/dev/null || true +else + bash "${GOG_DIR}/force-ja3-d3d11.sh" +fi +# Intel XeSS / DLSS DLL в папке игры ломают D3D11 на MoltenVK — убрать с пути загрузки +mkdir -p "${JA3}/.disabled-upscale" +for dll in libxess.dll nvngx_dlss.dll XeFX.dll XeFX_Loader.dll dxcompiler.dll dxil.dll; do + [[ -f "${JA3}/${dll}" ]] && mv -f "${JA3}/${dll}" "${JA3}/.disabled-upscale/${dll}" +done +bash "${GOG_DIR}/purge-steam-from-bottle.sh" >/dev/null 2>&1 || true + +echo "OK: finish-ja3-layout" diff --git a/scripts/games/gog/fix-ja3-crossover-mouse.sh b/scripts/games/gog/fix-ja3-crossover-mouse.sh new file mode 100755 index 0000000..622f5b4 --- /dev/null +++ b/scripts/games/gog/fix-ja3-crossover-mouse.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Курсор JA3 в CrossOver (Retina / HiDPI). +# Профили (JA3_MOUSE_PROFILE): +# legacy — RetinaMode=n (полный ход по Y; возможен сдвиг клика) — default +# retina-lite — RetinaMode=y без LogPixels (если legacy смещает, но нет клипа) +# retina — RetinaMode=y + LogPixels=192 (клип на середине экрана — не default) +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver" +CX_BOTTLE="JA3-GOG" +CX_DIR="${HOME}/Library/Application Support/CrossOver/Bottles/${CX_BOTTLE}" + +[[ -d "${CX}" ]] || { + echo "❌ CrossOver не установлен" + exit 1 +} + +PROFILE="${JA3_MOUSE_PROFILE:-legacy}" +WREG() { "${CX}/bin/wine" --bottle "${CX_BOTTLE}" "$@"; } + +case "${PROFILE}" in + retina) + RETINA="y" + SET_DPI=1 + DPI="${JA3_LOG_PIXELS:-192}" + SET_WIN8=1 + ;; + retina-lite) + RETINA="y" + SET_DPI=0 + SET_WIN8=0 + ;; + legacy|*) + RETINA="n" + SET_DPI=0 + SET_WIN8=0 + PROFILE="legacy" + ;; +esac + +WREG reg add 'HKCU\Software\Wine\Mac Driver' /v RetinaMode /t REG_SZ /d "${RETINA}" /f >/dev/null + +if [[ "${SET_DPI}" == "1" ]]; then + WREG reg add 'HKCU\Control Panel\Desktop' /v LogPixels /t REG_DWORD /d "${DPI}" /f >/dev/null +else + WREG reg delete 'HKCU\Control Panel\Desktop' /v LogPixels /f >/dev/null 2>&1 || true +fi + +if [[ "${SET_WIN8}" == "1" ]]; then + WREG reg add 'HKCU\Control Panel\Desktop' /v Win8DpiScaling /t REG_DWORD /d 1 /f >/dev/null +else + WREG reg delete 'HKCU\Control Panel\Desktop' /v Win8DpiScaling /f >/dev/null 2>&1 || true +fi + +CONF="${CX_DIR}/cxbottle.conf" +if [[ -f "${CONF}" ]] && ! grep -q 'SDL_VIDEO_MAC_FULLSCREEN_SPACES' "${CONF}"; then + sed -i '' '/^\[EnvironmentVariables\]/a\ +"SDL_VIDEO_MAC_FULLSCREEN_SPACES" = "0" +' "${CONF}" 2>/dev/null || true +fi + +echo "OK: profile=${PROFILE} RetinaMode=${RETINA} → ${CX_BOTTLE}" +echo " Перезапуск: bash ${GOG_DIR}/reset-ja3-crossover.sh && bash ${GOG_DIR}/run-ja3-crossover.sh" +case "${PROFILE}" in + legacy) + echo " Если клик смещён: JA3_MOUSE_PROFILE=retina-lite bash ${GOG_DIR}/fix-ja3-crossover-mouse.sh" + echo " Без перезапуска: верхний левый угол окна = угол экрана." + ;; + retina-lite) + echo " Если смещение осталось: JA3_MOUSE_PROFILE=legacy …" + echo " Если клип на середине: JA3_MOUSE_PROFILE=legacy …" + ;; + retina) + echo " ⚠️ retina+LogPixels=192 часто режет курсор на середине экрана." + echo " Если так: JA3_MOUSE_PROFILE=legacy bash ${GOG_DIR}/fix-ja3-crossover-mouse.sh" + ;; +esac diff --git a/scripts/games/gog/force-ja3-d3d11.sh b/scripts/games/gog/force-ja3-d3d11.sh new file mode 100755 index 0000000..ba33ff7 --- /dev/null +++ b/scripts/games/gog/force-ja3-d3d11.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# JA3 на Mac (Whisky без GPTK): рендер через D3D11 + DXVK, не D3D12 (wine d3d12 → «Failed creating render device»). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +WINE_USER="${WINE_USER:-eternal}" +LS="${BOTTLE_DIR}/drive_c/users/${WINE_USER}/AppData/Roaming/Jagged Alliance 3/LocalStorage.lua" + +if [[ ! -f "${LS}" ]]; then + mkdir -p "$(dirname "${LS}")" + cat >"${LS}" <<'EOF' +return { + Options = { + GraphicsApi = "d3d11", + VideoPreset = "Low", + }, +} +EOF + echo "OK: создан ${LS} (GraphicsApi=d3d11)" + exit 0 +fi + +if grep -q 'GraphicsApi = "d3d11"' "${LS}"; then + echo "OK: GraphicsApi уже d3d11" + exit 0 +fi + +perl -i -pe ' + if (/^\t\tGraphicsApi = /) { + $_ = "\t\tGraphicsApi = \"d3d11\",\n"; + } +' "${LS}" +echo "OK: ${LS} → GraphicsApi=d3d11 (было d3d12 или пусто)" diff --git a/scripts/games/gog/gog-process.sh b/scripts/games/gog/gog-process.sh new file mode 100755 index 0000000..edab2c9 --- /dev/null +++ b/scripts/games/gog/gog-process.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Общие kill/lock для GOG Galaxy в GOG-bottle. +GOG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +WINE_SERV="${WHISKY_LIBS}/Libraries/Wine/bin/wineserver" + +gog_lock_files_dir() { + echo "${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/lock-files" +} + +gog_remove_lock_files() { + local d + d="$(gog_lock_files_dir)" + [[ -d "${d}" ]] || return 0 + rm -f "${d}"/*.lock 2>/dev/null || true +} + +gog_kill_all() { + pkill -9 -f 'explorer.exe /desktop=GOGGalaxy' 2>/dev/null || true + pkill -9 -f 'explorer /desktop=GOGGalaxy' 2>/dev/null || true + pkill -9 -f 'GalaxyClient Helper' 2>/dev/null || true + pkill -9 -f 'GalaxyClient' 2>/dev/null || true + pkill -9 -f 'GalaxyUpdater' 2>/dev/null || true + pkill -9 -f 'GalaxyInstaller' 2>/dev/null || true + pkill -9 -f 'GalaxySetup' 2>/dev/null || true + pkill -9 -f 'GalaxyWebInstaller' 2>/dev/null || true + pkill -9 -f 'GOG_Galaxy_' 2>/dev/null || true + pkill -9 -f 'GOG Galaxy' 2>/dev/null || true + pkill -9 -f "${BOTTLE_ID}.*[Ss]team" 2>/dev/null || true + pkill -9 -f "${BOTTLE_DIR}.*steam\.exe" 2>/dev/null || true + pkill -9 -f "${BOTTLE_ID}.*wine" 2>/dev/null || true + pkill -9 -f 'winedevice\.exe' 2>/dev/null || true + pkill -9 -f 'wine64-preloader' 2>/dev/null || true + pkill -9 -f 'wine64\.exe' 2>/dev/null || true + sleep 1 + if [[ -d "${BOTTLE_DIR}" ]]; then + export WINEPREFIX="${BOTTLE_DIR}" + [[ -x "${WINE_SERV}" ]] && "${WINE_SERV}" -k 2>/dev/null || true + sleep 1 + gog_remove_lock_files + fi + sleep 1 +} + +gog_galaxy_running() { + pgrep -f 'GalaxyClient\.exe' >/dev/null 2>&1 \ + || pgrep -f 'GalaxyClient Helper' >/dev/null 2>&1 +} + +gog_wait_log_line() { + local log="$1" pattern="$2" timeout="${3:-30}" + local i + for i in $(seq 1 "${timeout}"); do + [[ -f "${log}" ]] && grep -q "${pattern}" "${log}" 2>/dev/null && return 0 + sleep 1 + done + return 1 +} + +gog_mac_activate_wine() { + osascript -e ' +tell application "System Events" + repeat with proc in (every process whose name contains "wine" or name contains "Wine") + try + set frontmost of proc to true + exit repeat + end try + end repeat +end tell' 2>/dev/null || true +} diff --git a/scripts/games/gog/install-all.sh b/scripts/games/gog/install-all.sh new file mode 100755 index 0000000..75a6626 --- /dev/null +++ b/scripts/games/gog/install-all.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Полный сетап GOG-bottle + web-stub. Аргумент: путь к GOG_Galaxy_*.exe +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +STUB="${1:-}" + +if [[ ! -f "${GOG_DIR}/bottle.env" ]]; then + bash "${GOG_DIR}/create-bottle.sh" GOG-Install +fi + +bash "${GOG_DIR}/setup-bottle.sh" --force + +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +if [[ ! -f "${BOTTLE_DIR}/.gog_gdiplus_installed" ]]; then + bash "${GOG_DIR}/install-gdiplus.sh" +fi + +command -v cabextract >/dev/null || brew install cabextract +bash "${GOG_DIR}/install-corefonts.sh" +bash "${GOG_DIR}/install-galaxy-extract.sh" + +if [[ -z "${STUB}" ]]; then + echo "Готово. Установка JA3:" + echo " bash ${GOG_DIR}/launch-galaxy.sh # вход → Install в библиотеке" + echo " bash ${GOG_DIR}/finish-ja3-install.sh" + exit 0 +fi + +if [[ "${GOG_USE_WEB_STUB:-0}" == "1" ]]; then + echo "Web-stub (эксперимент; GalaxySetup часто падает на gdi32 в Wine)…" + bash "${GOG_DIR}/run-web-installer.sh" "${STUB}" +else + echo "Galaxy уже распакован. Для игры — launch-galaxy.sh (не web-stub)." + echo "Принудительно stub: GOG_USE_WEB_STUB=1 bash $0 ${STUB}" + bash "${GOG_DIR}/launch-galaxy.sh" +fi diff --git a/scripts/games/gog/install-corefonts.sh b/scripts/games/gog/install-corefonts.sh new file mode 100755 index 0000000..fbf2918 --- /dev/null +++ b/scripts/games/gog/install-corefonts.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +# shellcheck source=wine-fonts.sh +source "${GOG_DIR}/wine-fonts.sh" + +CACHE="${GAMES_CACHE_DIR}/corefonts" +FONTS="${BOTTLE_DIR}/drive_c/windows/Fonts" + +cmd_fetch() { + local name="$1" url="$2" + mkdir -p "${CACHE}" + if [[ -f "${CACHE}/${name}" ]] && [[ "$(wc -c <"${CACHE}/${name}")" -gt 10000 ]]; then + return 0 + fi + echo "Скачиваю ${name}…" + curl -fsSL --retry 3 -o "${CACHE}/${name}" "${url}" || return 1 +} + +cmd_extract_cab() { + local cab="$1" out="${CACHE}/extract-${2:-x}" + rm -rf "${out}" + mkdir -p "${out}" + cabextract -d "${out}" "${cab}" + find "${out}" -iname '*.ttf' -exec cp -n {} "${FONTS}/" \; +} + +gog_wine_check +command -v cabextract >/dev/null || { echo "brew install cabextract"; exit 1; } +mkdir -p "${FONTS}" "${CACHE}" + +cmd_fetch andale32.exe "https://downloads.sourceforge.net/corefonts/andale32.exe" \ + || cmd_fetch andale32.exe "https://ftp.debian.org/debian/pool/contrib/m/msttcorefonts/andale32.exe" || true +cmd_fetch arial32.exe "https://downloads.sourceforge.net/corefonts/arial32.exe" \ + || cmd_fetch arial32.exe "https://ftp.debian.org/debian/pool/contrib/m/msttcorefonts/arial32.exe" || true +cmd_fetch times32.exe "https://downloads.sourceforge.net/corefonts/times32.exe" \ + || cmd_fetch times32.exe "https://ftp.debian.org/debian/pool/contrib/m/msttcorefonts/times32.exe" || true + +for cab in andale32.exe arial32.exe times32.exe; do + [[ -f "${CACHE}/${cab}" ]] && [[ "$(wc -c <"${CACHE}/${cab}")" -gt 10000 ]] \ + && cmd_extract_cab "${CACHE}/${cab}" "${cab%.exe}" || true +done + +gog_wine_ensure_fonts 1 +touch "${BOTTLE_DIR}/.gog_corefonts_installed" +echo "OK: corefonts в GOG bottle ($(ls "${FONTS}" | wc -l | tr -d ' ') файлов)" diff --git a/scripts/games/gog/install-dxvk.sh b/scripts/games/gog/install-dxvk.sh new file mode 100755 index 0000000..9bb34ad --- /dev/null +++ b/scripts/games/gog/install-dxvk.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# DXVK-macOS в GOG-bottle (для JA3). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +# install-dxvk-macos.sh привязан к HoMM config — дублируем install для GOG-bottle +DXVK_CACHE="${GAMES_DIR}/cache/dxvk-macos" +DXVK_TAR="${DXVK_CACHE}/dxvk-macOS-async.tar.gz" +DXVK_URL="https://github.com/Gcenx/DXVK-macOS/releases/download/v1.10.3-20230507-repack/dxvk-macOS-async-v1.10.3-20230507-repack.tar.gz" +DXVK_DIR="${DXVK_CACHE}/dxvk-macOS-async-v1.10.3-20230507-repack" +DXVK_X64="${DXVK_DIR}/x64" +WINE_LIB64="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-windows" +SYS32="${BOTTLE_DIR}/drive_c/windows/system32" + +gog_wine_check +mkdir -p "${DXVK_CACHE}" +[[ -f "${DXVK_TAR}" ]] || curl -L --fail -o "${DXVK_TAR}" "${DXVK_URL}" +[[ -f "${DXVK_X64}/d3d11.dll" ]] || tar -xzf "${DXVK_TAR}" -C "${DXVK_CACHE}" +cp -f "${WINE_LIB64}/dxgi.dll" "${SYS32}/dxgi.dll" +cp -f "${DXVK_X64}/d3d11.dll" "${SYS32}/d3d11.dll" +cp -f "${DXVK_X64}/d3d10core.dll" "${SYS32}/d3d10core.dll" +export WINEPREFIX="${BOTTLE_DIR}" +export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" +"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d11 /d native,b /f >/dev/null +"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d10core /d native,b /f >/dev/null +"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\Direct3D' /v renderer /t REG_SZ /d vulkan /f >/dev/null 2>&1 || true +# Whisky UI: DXVK для HoMM; JA3 предпочитает D3DMetal (configure-ja3-d3dmetal.sh) +if [[ -f "${BOTTLE_DIR}/Metadata.plist" ]]; then + /usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || \ + /usr/libexec/PlistBuddy -c 'Add :dxvkConfig:dxvk bool true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true +fi +echo "OK: DXVK-macOS в ${BOTTLE_NAME}" diff --git a/scripts/games/gog/install-galaxy-extract.sh b/scripts/games/gog/install-galaxy-extract.sh new file mode 100755 index 0000000..ecc8ffe --- /dev/null +++ b/scripts/games/gog/install-galaxy-extract.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# GOG Galaxy в GOG-bottle без Wine-Inno (gdi32/botva2). Как install-gog-galaxy.sh для HoMM. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +GOG_SETUP="${GAMES_CACHE_DIR:-${GAMES_DIR}/cache}/setup_galaxy.exe" +EXTRACT_DIR="${GAMES_CACHE_DIR:-${GAMES_DIR}/cache}/gog-galaxy-extract-gog-bottle" +GOG_PF="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy" +GOG_PD="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy" +MARKER="${BOTTLE_DIR}/.gog_galaxy_extract_installed" + +gog_galaxy_exe() { + if [[ -f "${GOG_PF}/GalaxyClient.exe" ]]; then + echo "${GOG_PF}/GalaxyClient.exe" + return 0 + fi + local found + found="$(find "${BOTTLE_DIR}/drive_c/Program Files"* -maxdepth 3 -name 'GalaxyClient.exe' 2>/dev/null | head -1)" + [[ -n "${found}" ]] && echo "${found}" && return 0 + return 1 +} + +homm_gog_setup_url() { + if [[ -n "${GOG_SETUP_URL:-}" ]]; then + echo "${GOG_SETUP_URL}" + return 0 + fi + local json url + json="$(curl -fsSL "https://remote-config.gog.com/components/webinstaller?component_version=2.0.0")" + url="$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["content"]["windows"]["downloadLink"])' <<<"${json}" 2>/dev/null)" || true + if [[ -z "${url}" ]]; then + url="https://content-system.gog.com/open_link/download?path=/open/galaxy/client/setup_galaxy_2.0.100.1.exe" + fi + echo "${url}" +} + +main() { + gog_wine_check + gog_disk_guard 3 || exit 1 + + if gog_galaxy_exe >/dev/null 2>&1; then + echo "GOG Galaxy уже в bottle: $(gog_galaxy_exe)" + exit 0 + fi + + if [[ ! -f "${GOG_SETUP}" ]]; then + mkdir -p "$(dirname "${GOG_SETUP}")" + dl_url="$(homm_gog_setup_url)" + echo "Скачиваю setup_galaxy.exe (~290 MB)…" + curl -L --fail --retry 3 -C - -o "${GOG_SETUP}" "${dl_url}" + fi + + if ! command -v innoextract >/dev/null 2>&1; then + echo "Нужен innoextract: brew install innoextract" + exit 1 + fi + + echo "Распаковка GOG Galaxy (innoextract) → ${BOTTLE_NAME}…" + rm -rf "${EXTRACT_DIR}" + mkdir -p "${EXTRACT_DIR}" + innoextract -e -d "${EXTRACT_DIR}" "${GOG_SETUP}" + + mkdir -p "${GOG_PF}" "${GOG_PD}" + cp -R "${EXTRACT_DIR}/app/." "${GOG_PF}/" + local pd_src + pd_src="$(find "${EXTRACT_DIR}" -type d -path '*/GOG.com/Galaxy' 2>/dev/null | head -1)" + if [[ -n "${pd_src}" ]]; then + cp -R "${pd_src}/." "${GOG_PD}/" + fi + + if ! gog_galaxy_exe >/dev/null 2>&1; then + echo "❌ GalaxyClient.exe не найден после распаковки" + exit 1 + fi + + echo "VC++ redist (из setup_galaxy)…" + local vc_x86="${EXTRACT_DIR}/tmp/VC_redist.x86.exe" + local vc_x64="${EXTRACT_DIR}/tmp/VC_redist.x64.exe" + if [[ -f "${vc_x86}" ]]; then + export_wine_env_gog + cp -f "${vc_x86}" "${BOTTLE_DIR}/drive_c/VC_redist.x86.exe" + cp -f "${vc_x64}" "${BOTTLE_DIR}/drive_c/VC_redist.x64.exe" + "${WINE64}" C:\\VC_redist.x86.exe /install /quiet /norestart + "${WINE64}" C:\\VC_redist.x64.exe /install /quiet /norestart + fi + + local fonts_dst="${BOTTLE_DIR}/drive_c/windows/Fonts" + mkdir -p "${fonts_dst}" + cp -f "${EXTRACT_DIR}"/fonts/*.ttf "${fonts_dst}/" 2>/dev/null || true + + bash "${GOG_DIR}/register-galaxy-registry.sh" + + date -u +%Y-%m-%dT%H:%M:%SZ >"${MARKER}" + echo "OK: $(gog_galaxy_exe)" +} + +main "$@" diff --git a/scripts/games/gog/install-gdiplus.sh b/scripts/games/gog/install-gdiplus.sh new file mode 100755 index 0000000..88e6cc5 --- /dev/null +++ b/scripts/games/gog/install-gdiplus.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# MS gdiplus.dll — без этого .NET падает на GenericSansSerif (GdipGetGenericFontFamilySansSerif). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +gog_wine_check +command -v winetricks >/dev/null || { echo "brew install winetricks"; exit 1; } + +export WINE="${WINE64}" +export WINEPREFIX="${BOTTLE_DIR}" +export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" +export WINEDEBUG=-all + +echo "winetricks gdiplus (~3–5 мин, скачает win7sp1)…" +winetricks -q gdiplus + +touch "${BOTTLE_DIR}/.gog_gdiplus_installed" +echo "OK: gdiplus в ${BOTTLE_NAME}" diff --git a/scripts/games/gog/install-gptk-whisky.sh b/scripts/games/gog/install-gptk-whisky.sh new file mode 100755 index 0000000..c1566d1 --- /dev/null +++ b/scripts/games/gog/install-gptk-whisky.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Apple D3DMetal (GPTK) в Whisky Libraries — только framework + unix-мост. +# НЕ ditto всего GPTK lib (перезапишет wine64 на 7.7 и сломает bottle). +# Канон: https://github.com/Whisky-App/Whisky/issues/124 +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +GPTK_APP="${GPTK_APP:-/Applications/Game Porting Toolkit.app}" +GPTK_WINE="${GPTK_APP}/Contents/Resources/wine" +WHISKY_WINE="${WHISKY_LIBS}/Libraries/Wine" +EXT="${WHISKY_WINE}/lib/external" +WINE_UNIX="${WHISKY_WINE}/lib/wine/x86_64-unix" + +[[ -d "${GPTK_WINE}/lib/external/D3DMetal.framework" ]] || { + echo "❌ Нет Game Porting Toolkit.app" + echo " brew install --cask gcenx/wine/game-porting-toolkit" + exit 1 +} + +mkdir -p "${EXT}" +ditto "${GPTK_WINE}/lib/external/D3DMetal.framework" "${EXT}/D3DMetal.framework" +cp -f "${GPTK_WINE}/lib/external/libd3dshared.dylib" "${EXT}/libd3dshared.dylib" + +# @rpath @loader_path — копия в корень lib/ (fixmywhisky) +cp -Rf "${EXT}/D3DMetal.framework" "${WHISKY_WINE}/lib/D3DMetal.framework" +cp -f "${EXT}/libd3dshared.dylib" "${WHISKY_WINE}/lib/libd3dshared.dylib" + +fix_d3dmetal_fw() { + local fw="$1" + [[ -d "${fw}/Versions" ]] || return 0 + rm -f "${fw}/D3DMetal" "${fw}/Resources" 2>/dev/null || true + ln -sf Versions/Current/D3DMetal "${fw}/D3DMetal" + ln -sf Versions/Current/Resources "${fw}/Resources" + chmod +x "${fw}/Versions/Current/D3DMetal" 2>/dev/null || true + xattr -d com.apple.quarantine "${fw}" 2>/dev/null || true +} +fix_d3dmetal_fw "${EXT}/D3DMetal.framework" +fix_d3dmetal_fw "${WHISKY_WINE}/lib/D3DMetal.framework" +chmod +x "${EXT}/libd3dshared.dylib" "${WHISKY_WINE}/lib/libd3dshared.dylib" 2>/dev/null || true +xattr -d com.apple.quarantine "${EXT}/libd3dshared.dylib" 2>/dev/null || true + +# Мост D3DMetal: unix .so → libd3dshared. PE d3d12/dxgi — от Gcenx Wine 11.x (не GPTK 7.7). +mkdir -p "${WINE_UNIX}" +ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/d3d12.so" +ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/dxgi.so" + +touch "${WHISKY_LIBS}/Libraries/.gptk_installed" +echo "OK: GPTK D3DMetal + d3d12/dxgi bridge → ${WHISKY_WINE}" +echo " Запуск: bash ${GOG_DIR}/prepare-ja3-d3dmetal.sh && bash ${GOG_DIR}/run-ja3.sh" diff --git a/scripts/games/gog/install-ja3-dxmt.sh b/scripts/games/gog/install-ja3-dxmt.sh new file mode 100755 index 0000000..82da9d3 --- /dev/null +++ b/scripts/games/gog/install-ja3-dxmt.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# DXMT (D3D10/11 → Metal) для JA3 без GPTK. JA3 на D3D12 всё равно нужен GPTK; скрипт + -graphicsapi d3d11. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +DXMT_CACHE="${GAMES_DIR}/cache/dxmt" +DXMT_TAR="${DXMT_CACHE}/dxmt-v0.80-builtin.tar.gz" +DXMT_URL="https://github.com/3Shain/dxmt/releases/download/v0.80/dxmt-v0.80-builtin.tar.gz" +WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib" +WINE_LIB64="${WINE_LIB}/wine/x86_64-windows" +WINE_UNIX="${WINE_LIB}/wine/x86_64-unix" +SYS32="${BOTTLE_DIR}/drive_c/windows/system32" + +gog_wine_check +mkdir -p "${DXMT_CACHE}" "${SYS32}" +[[ -f "${DXMT_TAR}" ]] || curl -L --fail -o "${DXMT_TAR}" "${DXMT_URL}" +tar -xzf "${DXMT_TAR}" -C "${DXMT_CACHE}" + +# builtin layout: bin/ or root with dlls — find files +DXMT_ROOT="${DXMT_CACHE}" +for sub in dxmt-v0.80-builtin bin .; do + [[ -f "${DXMT_CACHE}/${sub}/d3d11.dll" ]] && DXMT_ROOT="${DXMT_CACHE}/${sub}" && break +done +[[ -f "${DXMT_ROOT}/d3d11.dll" ]] || DXMT_ROOT="$(find "${DXMT_CACHE}" -name d3d11.dll -print -quit | xargs dirname)" + +install_one() { + local src="$1" dest="$2" + [[ -f "${src}" ]] || return 0 + cp -f "${src}" "${dest}/" +} + +install_one "${DXMT_ROOT}/winemetal.so" "${WINE_UNIX}" +install_one "${DXMT_ROOT}/winemetal.dll" "${WINE_LIB64}" +install_one "${DXMT_ROOT}/winemetal.dll" "${SYS32}" +install_one "${DXMT_ROOT}/d3d11.dll" "${WINE_LIB64}" +install_one "${DXMT_ROOT}/dxgi.dll" "${WINE_LIB64}" +install_one "${DXMT_ROOT}/d3d10core.dll" "${WINE_LIB64}" +# Только wine lib + system32 (не в папку игры — иначе конфликт с overrides). +install_one "${DXMT_ROOT}/d3d11.dll" "${SYS32}" +install_one "${DXMT_ROOT}/dxgi.dll" "${SYS32}" +install_one "${DXMT_ROOT}/d3d10core.dll" "${SYS32}" + +export WINEPREFIX="${BOTTLE_DIR}" +export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" +"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d12 /d disabled /f >/dev/null 2>&1 || true +"${WINE64}" reg delete 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d11 /f >/dev/null 2>&1 || true +"${WINE64}" reg delete 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d10core /f >/dev/null 2>&1 || true +"${WINE64}" reg delete 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v dxgi /f >/dev/null 2>&1 || true + +touch "${BOTTLE_DIR}/.ja3_dxmt_installed" +if [[ -f "${BOTTLE_DIR}/Metadata.plist" ]]; then + /usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true + /usr/libexec/PlistBuddy -c 'Set :performanceConfig:forceD3D11 true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true +fi +echo "OK: DXMT v0.80 в ${BOTTLE_NAME} (+ game dir)" diff --git a/scripts/games/gog/install-ja3-innoextract.sh b/scripts/games/gog/install-ja3-innoextract.sh new file mode 100755 index 0000000..42da0b7 --- /dev/null +++ b/scripts/games/gog/install-ja3-innoextract.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +# JA3: multipart GOG → innoextract в bottle (без Wine-Inno). Экономия диска: .bin удаляются по ходу. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}" +JA3_PREFIX='setup_jagged_alliance_3_1.5.2_(80730)' +JA3_EXE="${JA3_GAMES_DIR}/${JA3_PREFIX}.exe" +JA3_TARGET="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3" +MARKER="${BOTTLE_DIR}/.ja3_installed" +LOG="/tmp/ja3-innoextract-${BOTTLE_NAME}.log" +JA3_MIN_FREE_MB="${JA3_MIN_FREE_MB:-800}" +JA3_KEEP_BINS="${JA3_KEEP_BINS:-0}" + +gog_disk_free_mb() { + local kb + kb="$(gog_disk_free_kb)" + echo $((kb / 1024)) +} + +gog_disk_watch() { + local min_mb="$1" + local free + free="$(gog_disk_free_mb)" + if [[ "${free}" -lt "${min_mb}" ]]; then + echo "❌ Мало места на диске: ${free} MB свободно (нужно ≥${min_mb} MB)" >&2 + df -h "$(gog_data_volume_path)" | awk 'NR==2' >&2 + return 1 + fi + return 0 +} + +gog_ja3_safe_cleanup() { + local freed=0 + local d f + + for d in \ + "${GAMES_CACHE_DIR}/gog-galaxy-extract-gog-bottle" \ + "${GAMES_CACHE_DIR}/ja3-extract-test" \ + "${GAMES_CACHE_DIR}/ja3-innoextract-staging"; do + if [[ -d "${d}" ]]; then + local sz + sz="$(du -sk "${d}" | awk '{print $1}')" + rm -rf "${d}" + freed=$((freed + sz)) + echo " удалён cache: ${d}" + fi + done + + if [[ -f "${GAMES_CACHE_DIR}/setup_galaxy.exe" ]] \ + && [[ -f "${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy/GalaxyClient.exe" ]]; then + f="${GAMES_CACHE_DIR}/setup_galaxy.exe" + sz="$(du -sk "${f}" | awk '{print $1}')" + rm -f "${f}" + freed=$((freed + sz)) + echo " удалён setup_galaxy.exe (Galaxy уже в bottle)" + fi + + if [[ "${freed}" -gt 0 ]]; then + echo "Освобождено ~$((freed / 1024)) MB (cache)" + fi +} + +ja3_bin_path() { + echo "${JA3_GAMES_DIR}/${JA3_PREFIX}-$1.bin" +} + +ja3_delete_bin() { + local n="$1" + local p + p="$(ja3_bin_path "${n}")" + if [[ -f "${p}" ]]; then + rm -f "${p}" + echo " 🗑 удалён $(basename "${p}") (~$(du -sh "${p}" 2>/dev/null | cut -f1 || echo '?'))" + gog_disk_watch "${JA3_MIN_FREE_MB}" || true + fi +} + +ja3_delete_all_bins() { + local n + for n in 1 2 3 4; do + ja3_delete_bin "${n}" || true + done + if [[ -f "${JA3_EXE}" && "${JA3_KEEP_BINS}" != "1" ]]; then + rm -f "${JA3_EXE}" + echo " 🗑 удалён $(basename "${JA3_EXE}")" + fi +} + +ja3_watch_bins() { + local line opened=0 + while IFS= read -r line; do + printf '%s\n' "${line}" + case "${line}" in + *"Opening \"${JA3_PREFIX}-2.bin\""*) + ja3_delete_bin 1 + opened=2 + ;; + *"Opening \"${JA3_PREFIX}-3.bin\""*) + ja3_delete_bin 2 + opened=3 + ;; + *"Opening \"${JA3_PREFIX}-4.bin\""*) + ja3_delete_bin 3 + opened=4 + ;; + esac + gog_disk_watch "${JA3_MIN_FREE_MB}" || return 1 + done + if [[ "${opened}" -ge 4 ]]; then + ja3_delete_bin 4 || true + fi +} + +verify() { + local n + [[ -f "${JA3_EXE}" ]] || { + echo "❌ Нет: ${JA3_EXE}" + return 1 + } + for n in 1 2 3 4; do + [[ -f "$(ja3_bin_path "${n}")" ]] || { + echo "❌ Нет: $(ja3_bin_path "${n}")" + return 1 + } + done + command -v innoextract >/dev/null 2>&1 || { + echo "❌ brew install innoextract" + return 1 + } +} + +main() { + verify || exit 1 + gog_wine_check + + if [[ -f "${JA3_TARGET}/JA3.exe" ]]; then + echo "JA3 уже установлен: ${JA3_TARGET}/JA3.exe" + exit 0 + fi + + bash "${GOG_DIR}/emergency-stop.sh" || true + + echo "=== Очистка cache (без трогания архивов игры) ===" + gog_ja3_safe_cleanup + + gog_disk_watch "${JA3_MIN_FREE_MB}" || { + echo "❌ Перед установкой нужно ≥$((JA3_MIN_FREE_MB / 1024)) GB свободно." + exit 1 + } + + echo "" + echo "Свободно на Data: $(gog_disk_free_mb) MB" + echo "Распаковка JA3 → ${JA3_TARGET}" + echo "Лог: ${LOG}" + echo "(архивы .bin удаляются по мере чтения innoextract)" + echo "" + + rm -rf "${JA3_TARGET}" + mkdir -p "${JA3_TARGET}" + : >"${LOG}" + + cd "${JA3_GAMES_DIR}" + set +e + innoextract -e -m -d "${JA3_TARGET}" "$(basename "${JA3_EXE}")" 2>&1 \ + | tee "${LOG}" | ja3_watch_bins + local rc=${PIPESTATUS[0]} + set -e + + if [[ "${rc}" -ne 0 ]]; then + echo "" + echo "❌ innoextract exit ${rc}" + tail -15 "${LOG}" 2>/dev/null || true + df -h "$(gog_data_volume_path)" | awk 'NR==2' + if [[ "${JA3_CLEANUP_PARTIAL:-1}" == "1" ]]; then + echo "Удаляю незавершённую распаковку в bottle…" + rm -rf "${JA3_TARGET}" + fi + exit 1 + fi + + if [[ -f "${JA3_TARGET}/JA3.exe" ]]; then + bash "${GOG_DIR}/finish-ja3-layout.sh" + date -u +"%Y-%m-%dT%H:%M:%SZ" >"${MARKER}" + if [[ "${JA3_KEEP_BINS}" != "1" ]]; then + echo "" + echo "Удаляю GOG-архивы (~15 GB)…" + ja3_delete_all_bins + fi + echo "" + echo "OK: JA3 установлен." + echo " Свободно: $(gog_disk_free_mb) MB" + echo " Запуск: bash ${GOG_DIR}/run-ja3.sh" + else + echo "❌ JA3.exe не найден" + find "${JA3_TARGET}" -maxdepth 2 -iname 'JA3.exe' 2>/dev/null | head -5 + exit 1 + fi +} + +main "$@" diff --git a/scripts/games/gog/install-ja3-multipart.sh b/scripts/games/gog/install-ja3-multipart.sh new file mode 100755 index 0000000..56e1a03 --- /dev/null +++ b/scripts/games/gog/install-ja3-multipart.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# JA3 из GOG offline backup: setup_*.exe + setup_*-1.bin … -4.bin в одной папке. +# Galaxy не нужен. Установщик читает .bin рядом с .exe (не копируем 15 GB в bottle). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}" +JA3_SETUP_EXE="${JA3_SETUP_EXE:-}" +LOG="/tmp/ja3-multipart-install-${BOTTLE_NAME}.log" + +# Имена GOG v1.5.2 (80730); при другой версии задайте JA3_SETUP_EXE. +JA3_EXE_DEFAULT="${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730).exe" +JA3_BINS=( + "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-1.bin" + "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-2.bin" + "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-3.bin" + "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-4.bin" +) + +gog_unix_to_win() { + local u="$1" + if [[ -x "${WINEPATH_BIN}" ]]; then + "${WINEPATH_BIN}" -w "${u}" + else + # fallback: Z: = / + local rest="${u#/}" + echo "Z:\\\\${rest//\//\\\\}" + fi +} + +find_setup_exe() { + if [[ -n "${JA3_SETUP_EXE}" && -f "${JA3_SETUP_EXE}" ]]; then + echo "${JA3_SETUP_EXE}" + return 0 + fi + if [[ -f "${JA3_EXE_DEFAULT}" ]]; then + echo "${JA3_EXE_DEFAULT}" + return 0 + fi + local f + f="$(find "${JA3_GAMES_DIR}" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print -quit 2>/dev/null || true)" + [[ -n "${f}" && -f "${f}" ]] && echo "${f}" && return 0 + return 1 +} + +usage() { + cat <"${LOG}" + + # Inno ищет .bin в каталоге установщика — запуск из JA3_GAMES_DIR + cd "${JA3_GAMES_DIR}" + if ! "${WINE64}" "${win_exe}" "${SILENT[@]}" >>"${LOG}" 2>&1; then + echo "⚠️ Wine-Inno не прошёл — пробуем innoextract (без botva2/gdi32)…" + if command -v innoextract >/dev/null 2>&1; then + exec bash "${GOG_DIR}/install-ja3-innoextract.sh" + fi + echo "❌ Установщик завершился с ошибкой:" + strings "${LOG}" 2>/dev/null | tail -20 || tail -20 "${LOG}" + exit 1 + fi + + if [[ -f "${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then + date -u +"%Y-%m-%dT%H:%M:%SZ" >"${BOTTLE_DIR}/.ja3_installed" + echo "" + echo "OK: JA3 установлен." + echo " Запуск: bash ${GOG_DIR}/run-ja3.sh" + echo " После проверки игры можно удалить .bin в ${JA3_GAMES_DIR} (~15 GB)." + else + echo "⚠️ JA3.exe не найден в bottle — tail лога:" + tail -40 "${LOG}" + exit 1 + fi +} + +main "$@" diff --git a/scripts/games/gog/install-ja3-offline.sh b/scripts/games/gog/install-ja3-offline.sh new file mode 100755 index 0000000..a6a4a0f --- /dev/null +++ b/scripts/games/gog/install-ja3-offline.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# JA3 из GOG offline backup installer — без Galaxy. Inno /VERYSILENT. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +INSTALLER="${1:-}" +LOG="/tmp/ja3-offline-install-${BOTTLE_NAME}.log" + +usage() { + cat <"${LOG}" + +cd "${BOTTLE_DIR}/drive_c" +if ! "${WINE64}" "${win}" "${SILENT[@]}" >>"${LOG}" 2>&1; then + echo "❌ Установщик завершился с ошибкой. tail:" + tail -40 "${LOG}" + exit 1 +fi + +if [[ -f "${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then + date -u +"%Y-%m-%dT%H:%M:%SZ" >"${BOTTLE_DIR}/.ja3_installed" + echo "" + echo "OK: JA3 установлен." + echo " Запуск: bash ${GOG_DIR}/run-ja3.sh" +else + echo "⚠️ JA3.exe пока не найден — проверьте лог, возможно нужны ещё offline .exe (DLC/base):" + tail -30 "${LOG}" + exit 1 +fi diff --git a/scripts/games/gog/install-ja3-redist.sh b/scripts/games/gog/install-ja3-redist.sh new file mode 100755 index 0000000..ccde9cb --- /dev/null +++ b/scripts/games/gog/install-ja3-redist.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# VC++ 2015 из __redist JA3 (innoextract не запускает установщики). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +JA3="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3" +VC_X86="${JA3}/__redist/MSVC2015/VC_redist.x86.exe" +VC_X64="${JA3}/__redist/MSVC2015_x64/VC_redist.x64.exe" +MARKER="${BOTTLE_DIR}/.ja3_redist_installed" + +[[ -f "${JA3}/JA3.exe" ]] || { + echo "❌ Сначала install-ja3-innoextract.sh" + exit 1 +} + +if [[ -f "${MARKER}" ]]; then + echo "JA3 redist уже ставился: ${MARKER}" + exit 0 +fi + +export_wine_env_gog +gog_wine_check + +for vc in "${VC_X86}" "${VC_X64}"; do + [[ -f "${vc}" ]] || continue + base="$(basename "${vc}")" + cp -f "${vc}" "${BOTTLE_DIR}/drive_c/${base}" + echo "VC++ ${base}…" + "${WINE64}" "C:\\${base}" /install /quiet /norestart || true +done + +date -u +"%Y-%m-%dT%H:%M:%SZ" >"${MARKER}" +echo "OK: VC++ redist для JA3" diff --git a/scripts/games/gog/install-ja3-wine-finish.sh b/scripts/games/gog/install-ja3-wine-finish.sh new file mode 100755 index 0000000..22aa722 --- /dev/null +++ b/scripts/games/gog/install-ja3-wine-finish.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Довести установку JA3 через Wine-Inno (пост-скрипты GOG), если есть архивы в JA3_GAMES_DIR. +# innoextract одного не хватает для Lua/VFS — Wine-установщик создаёт правильный layout. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}" +PREFIX='setup_jagged_alliance_3_1.5.2_(80730)' +EXE="${JA3_GAMES_DIR}/${PREFIX}.exe" +TARGET='C:\GOG Games\Jagged Alliance 3' + +verify_archives() { + [[ -f "${EXE}" ]] || { + echo "❌ Нет ${EXE}" + echo " Положите 5 частей offline backup в ${JA3_GAMES_DIR}" + exit 1 + } + local n + for n in 1 2 3 4; do + [[ -f "${JA3_GAMES_DIR}/${PREFIX}-${n}.bin" ]] || { + echo "❌ Нет ${JA3_GAMES_DIR}/${PREFIX}-${n}.bin" + exit 1 + } + done +} + +main() { + verify_archives + bash "${GOG_DIR}/emergency-stop.sh" || true + gog_swap_guard "${GOG_SWAP_MAX_MB:-800}" || exit 1 + gog_disk_guard 20 || exit 1 + + export_wine_env_gog + gog_wine_check + + local win + win="$("${WINEPATH_BIN}" -w "${EXE}")" + echo "Wine-установщик GOG (пост-скрипты, Lua.hpk)…" + echo " ${win}" + cd "${JA3_GAMES_DIR}" + if ! "${WINE64}" "${win}" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /DIR="${TARGET}"; then + echo "❌ Wine-Inno упал — см. docs/games/TROUBLESHOOTING.md § Setup gdi32" + echo " Запасной путь: innoextract + -unpacked (run-ja3.sh)" + exit 1 + fi + + bash "${GOG_DIR}/finish-ja3-layout.sh" + echo "OK. Запуск: bash ${GOG_DIR}/run-ja3.sh" +} + +main "$@" diff --git a/scripts/games/gog/install-ja3.sh b/scripts/games/gog/install-ja3.sh new file mode 100755 index 0000000..bca1576 --- /dev/null +++ b/scripts/games/gog/install-ja3.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# JA3 без GOG Galaxy: подготовка bottle + подсказка по offline installer. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "=== Jagged Alliance 3 без GOG Galaxy ===" +echo "Док: docs/games/JA3_MAC_WITHOUT_GALAXY.md" +echo "" + +bash "${GOG_DIR}/emergency-stop.sh" || true + +USED="$(bash -c 'source '"${GOG_DIR}"'/config.sh 2>/dev/null; gog_swap_used_mb' 2>/dev/null || echo 0)" +if [[ "${USED:-0}" -gt 400 ]] 2>/dev/null; then + echo "❌ Swap ~${USED}MB — сначала перезагрузите Mac, затем снова этот скрипт." + exit 1 +fi + +bash "${GOG_DIR}/setup-ja3-bottle.sh" + +echo "" +echo "=== Скачайте offline installer (Windows) ===" +echo " https://www.gog.com/account → Jagged Alliance 3" +echo " → Download offline backup game installers (все части)" +echo "" +open "https://www.gog.com/account" 2>/dev/null || true + +JA3_GAMES_DIR="${HOME}/Games" +ja3_bins_ok=1 +for n in 1 2 3 4; do + [[ -f "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-${n}.bin" ]] || ja3_bins_ok=0 +done +if [[ "${ja3_bins_ok}" -eq 1 ]]; then + echo "Найдены 4× .bin в ${JA3_GAMES_DIR} — multipart offline backup." + if [[ -f "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730).exe" ]] \ + || find "${JA3_GAMES_DIR}" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print -quit 2>/dev/null | grep -q .; then + echo "Запуск install-ja3-innoextract.sh (innoextract, без Wine-Inno) …" + exec bash "${GOG_DIR}/install-ja3-innoextract.sh" + fi + echo "❌ Не хватает лаунчера .exe (~1 MB) рядом с .bin." + echo " Скачайте с https://www.gog.com/account или:" + echo " bash ${GOG_DIR}/fetch-ja3-setup-exe.sh" + exit 1 +fi + +# Автоустановка, если installer уже в Downloads +found=() +while IFS= read -r -d '' f; do + found+=("$f") +done < <(find "${HOME}/Downloads" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print0 2>/dev/null) + +if [[ ${#found[@]} -eq 0 ]]; then + echo "Когда .exe скачан (или все 5 частей в ~/Games):" + echo " bash ${GOG_DIR}/install-ja3-offline.sh ~/Downloads/setup_jagged_alliance_3_*.exe" + echo " bash ${GOG_DIR}/install-ja3-multipart.sh" + exit 0 +fi + +echo "Найдены installers в Downloads:" +printf ' %s\n' "${found[@]}" +echo "" +for f in "${found[@]}"; do + echo "Установка: ${f}" + bash "${GOG_DIR}/install-ja3-offline.sh" "${f}" || exit 1 +done +echo "" +echo "Запуск: bash ${GOG_DIR}/run-ja3.sh" diff --git a/scripts/games/gog/ja3-dxvk.conf b/scripts/games/gog/ja3-dxvk.conf new file mode 100644 index 0000000..c05546f --- /dev/null +++ b/scripts/games/gog/ja3-dxvk.conf @@ -0,0 +1,6 @@ +# JA3 на Apple Silicon + MoltenVK (Whisky / DXVK-macOS) +dxgi.hideIntelGpu = True +d3d11.enableFeatureLevel11_1 = True +d3d11.maxFeatureLevel = 12_1 +d3d11.dxgiBindBackBufferAsRT = True +dxgi.deferSurfaceCreation = True diff --git a/scripts/games/gog/launch-galaxy-ui.sh b/scripts/games/gog/launch-galaxy-ui.sh new file mode 100755 index 0000000..4f3da6d --- /dev/null +++ b/scripts/games/gog/launch-galaxy-ui.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Запуск GOG Galaxy с максимальным шансом увидеть окно на macOS: +# виртуальный desktop Wine + foreground в этом терминале. +set -euo pipefail +# Suppress verbose MoltenVK capability dump in foreground mode. +export MVK_CONFIG_LOG_LEVEL=0 +export GOG_USE_WINEVDESKTOP=1 +export GOG_LAUNCH_FG=1 +exec bash "$(cd "$(dirname "$0")" && pwd)/launch-galaxy.sh" "$@" diff --git a/scripts/games/gog/launch-galaxy-whisky.sh b/scripts/games/gog/launch-galaxy-whisky.sh new file mode 100755 index 0000000..eb6911b --- /dev/null +++ b/scripts/games/gog/launch-galaxy-whisky.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Запуск GOG Galaxy через Whisky GUI (pin gog-galaxy) — лучший шанс увидеть окно на macOS. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +bash "${GOG_DIR}/pin-galaxy-whisky.sh" >/dev/null 2>&1 || bash "${GOG_DIR}/pin-galaxy-whisky.sh" +bash "${GOG_DIR}/reset.sh" + +echo "Открываю Whisky → bottle «${BOTTLE_NAME}»." +echo "" +echo "В GUI Whisky:" +echo " 1) Выберите bottle «${BOTTLE_NAME}»" +echo " 2) Pin «gog-galaxy» → Run (не GalaxySetup_full / не web-stub)" +echo " 3) Cmd+Tab → wine64 / GOG Galaxy" +echo "" +echo "Если pin нет: bash ${GOG_DIR}/pin-galaxy-whisky.sh" +echo "Логи: bash ${GOG_DIR}/collect-gog-logs-7h.sh" +echo "" + +open -a Whisky 2>/dev/null || open -a "Whisky.app" diff --git a/scripts/games/gog/launch-galaxy.sh b/scripts/games/gog/launch-galaxy.sh new file mode 100755 index 0000000..c81720e --- /dev/null +++ b/scripts/games/gog/launch-galaxy.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# GOG Galaxy в GOG-bottle (CEF). По умолчанию — виртуальный desktop Wine (окно видно на Mac). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +# shellcheck source=gog-process.sh +source "${GOG_DIR}/gog-process.sh" + +LOG="/tmp/gog-galaxy-client-${BOTTLE_NAME}.log" +GALAXY_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/GalaxyClient.log" +INIT_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/GalaxyInitialization.log" +CEF_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/cef.log" + +gog_launch_wine() { + local win='C:\Program Files (x86)\GOG Galaxy\GalaxyClient.exe' + local -a args=() + mapfile -t args < <(gog_galaxy_cef_args_array) + + cd "${BOTTLE_DIR}/drive_c" + if [[ "${GOG_USE_WINEVDESKTOP:-1}" == "1" ]]; then + local w h + IFS='x' read -r w h <<< "${GOG_VDESKTOP:-1280x800}" + echo "Режим: explorer /desktop=GOGGalaxy,${w}x${h}" + if [[ "${GOG_LAUNCH_FG:-0}" == "1" ]]; then + exec "${WINE64}" explorer "/desktop=GOGGalaxy,${w}x${h}" "${win}" "${args[@]}" + fi + nohup "${WINE64}" explorer "/desktop=GOGGalaxy,${w}x${h}" "${win}" "${args[@]}" >>"${LOG}" 2>&1 & + else + echo "Режим: прямой GalaxyClient (GOG_USE_WINEVDESKTOP=0)" + if [[ "${GOG_LAUNCH_FG:-0}" == "1" ]]; then + exec "${WINE64}" "${win}" "${args[@]}" + fi + nohup "${WINE64}" "${win}" "${args[@]}" >>"${LOG}" 2>&1 & + fi +} + +main() { + gog_wine_check + gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1 + gog_disk_guard 8 || exit 1 + if [[ ! -f "${BOTTLE_DIR}/.gog_galaxy_extract_installed" ]]; then + echo "Сначала: bash ${GOG_DIR}/install-galaxy-extract.sh" + exit 1 + fi + if ! find "${BOTTLE_DIR}/drive_c/Program Files"* -name 'GalaxyClient.exe' -print -quit 2>/dev/null | grep -q .; then + bash "${GOG_DIR}/install-galaxy-extract.sh" + fi + + echo "Останавливаем старые процессы GOG Galaxy…" + gog_kill_all + pkill -f 'explorer.exe /desktop=GOGGalaxy' 2>/dev/null || true + + if gog_galaxy_running; then + echo "❌ Galaxy всё ещё в процессах — повторите: bash ${GOG_DIR}/reset.sh" + pgrep -fl 'Galaxy' 2>/dev/null || true + exit 1 + fi + + export_wine_env_gog + echo "Bottle: ${BOTTLE_NAME}" + echo "Лог: ${LOG}" + : >"${LOG}" + + if [[ "${GOG_LAUNCH_FG:-0}" == "1" ]]; then + echo "" + echo "Foreground: окно Wine привязано к этому терминалу. Закройте Galaxy — Ctrl+C." + echo "Ищите окно «GOGGalaxy» или полосу заголовка Wine desktop." + echo "" + gog_launch_wine + exit 0 + fi + + gog_launch_wine + echo "Запуск wine64…" + + local ok=0 i + for i in $(seq 1 30); do + sleep 1 + if grep -q 'Second client instance detected' "${INIT_LOG}" 2>/dev/null; then + if tail -5 "${INIT_LOG}" 2>/dev/null | grep -q 'The client will exit'; then + echo "❌ Второй экземпляр: bash ${GOG_DIR}/reset.sh && bash $0" + tail -8 "${INIT_LOG}" + exit 1 + fi + fi + if [[ -f "${GALAXY_LOG}" ]] && grep -qE 'Showing login form|No refresh token found|will continue' "${GALAXY_LOG}" 2>/dev/null; then + ok=1 + break + fi + if [[ -f "${INIT_LOG}" ]] && grep -q 'will continue' "${INIT_LOG}" 2>/dev/null; then + ok=1 + break + fi + if ! gog_galaxy_running; then + sleep 2 + if ! gog_galaxy_running; then + break + fi + fi + done + + if ! gog_galaxy_running; then + echo "❌ GalaxyClient не остался в процессах:" + tail -25 "${LOG}" + [[ -f "${INIT_LOG}" ]] && tail -15 "${INIT_LOG}" + exit 1 + fi + + sleep 2 + gog_mac_activate_wine || true + + echo "" + echo "OK: GOG Galaxy запущен (процесс + login в логе — окно на Mac не гарантировано)." + echo " • Окно: bash ${GOG_DIR}/launch-galaxy-whisky.sh → Whisky → pin gog-galaxy → Run" + echo " • RCA/логи 7ч: docs/games/GOG_GALAXY_7H_RCA.md | collect-gog-logs-7h.sh" + if [[ "${GOG_USE_WINEVDESKTOP:-1}" == "1" ]]; then + echo " • Cmd+Tab → «wine64» / «GOGGalaxy» — отдельное окно виртуального стола Wine" + else + echo " • Cmd+Tab → «GalaxyClient» или «wine64»" + fi + echo " • Не видно окна? bash ${GOG_DIR}/launch-galaxy-ui.sh (vdesktop + foreground)" + echo " • Whisky → «${BOTTLE_NAME}» → pin gog-galaxy → Run" + echo " • Лог: ${GALAXY_LOG}" + if [[ "${ok}" -eq 1 ]]; then + echo " • В логе login UI — войдите → Jagged Alliance 3 → Install" + if [[ -f "${CEF_LOG}" ]] && grep -q 'parent window' "${CEF_LOG}" 2>/dev/null; then + echo " • cef.log: «parent window» — типично для Wine; vdesktop/Whisky Run часто лечит" + fi + fi +} + +main "$@" diff --git a/scripts/games/gog/list-bottles.sh b/scripts/games/gog/list-bottles.sh new file mode 100755 index 0000000..0996094 --- /dev/null +++ b/scripts/games/gog/list-bottles.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail +echo "Whisky bottles (franke):" +for meta in "${HOME}/Library/Containers/com.franke.Whisky/Bottles/"*/Metadata.plist; do + [[ -f "${meta}" ]] || continue + id="$(basename "$(dirname "${meta}")")" + name="$(plutil -extract info.name raw "${meta}" 2>/dev/null || echo "?")" + du -sh "$(dirname "${meta}")" 2>/dev/null | awk -v id="${id}" -v n="${name}" '{printf " %s %-20s %s\n", id, n, $1}' +done +echo "" +echo "Привязать GOG: bash $(dirname "$0")/use-bottle.sh [имя]" diff --git a/scripts/games/gog/pin-galaxy-whisky.sh b/scripts/games/gog/pin-galaxy-whisky.sh new file mode 100755 index 0000000..970df76 --- /dev/null +++ b/scripts/games/gog/pin-galaxy-whisky.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Pin GOG Galaxy в Whisky для bottle GOG-Install (как start.sh gog в HoMM). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +GOG_EXE="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy/GalaxyClient.exe" +[[ -f "${GOG_EXE}" ]] || { echo "Нет GalaxyClient — install-galaxy-extract.sh"; exit 1; } + +METADATA="${BOTTLE_DIR}/Metadata.plist" +PROG_SETTINGS="${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist" +GOG_URL="file://${BOTTLE_DIR}/drive_c/Program%20Files%20(x86)/GOG%20Galaxy/GalaxyClient.exe" +CEF="$(gog_galaxy_cef_args_array | tr '\n' ' ')" + +python3 </dev/null || true + fi +done + +# Мост Metal + Gcenx PE (94208/200704). НЕ GPTK PE — __wine_unix_call на Wine 11. +# НЕ копировать в system32 DXVK/GPTK DLL. +mkdir -p "${WINE_UNIX}" +ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/d3d12.so" +ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/dxgi.so" +# system32: убрать overrides — builtin из Wine lib +for dll in d3d12.dll dxgi.dll d3d11.dll d3d10core.dll d3d12core.dll winemetal.dll; do + if [[ -f "${SYS32}/${dll}" ]]; then + mv -f "${SYS32}/${dll}" "${BAK}/${dll}" 2>/dev/null || true + fi +done + +export WINEPREFIX="${BOTTLE_DIR}" +export DYLD_LIBRARY_PATH="${WHISKY_LIBS}/Libraries/Wine/lib/external:${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" +export WINED3DMETAL=1 +export WINEDEBUG=-all + +"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v d3d11 /f >/dev/null 2>&1 || true +"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v d3d10core /f >/dev/null 2>&1 || true +"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v dxgi /f >/dev/null 2>&1 || true +"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v d3d12 /f >/dev/null 2>&1 || true +"${WINE64}" reg delete 'HKCU\Software\Wine\Direct3D' /v renderer /f >/dev/null 2>&1 || true + +if [[ -f "${LS}" ]]; then + perl -i -pe 's/^\t\tGraphicsApi = ".*"/\t\tGraphicsApi = "d3d12"/' "${LS}" +else + mkdir -p "$(dirname "${LS}")" + cat >"${LS}" <<'EOF' +return { + Options = { + GraphicsApi = "d3d12", + VideoPreset = "Low", + }, +} +EOF +fi + +if [[ -f "${BOTTLE_DIR}/Metadata.plist" ]]; then + /usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true + /usr/libexec/PlistBuddy -c 'Set :performanceConfig:forceD3D11 false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || \ + /usr/libexec/PlistBuddy -c 'Add :performanceConfig:forceD3D11 bool false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true + /usr/libexec/PlistBuddy -c 'Set :graphicsConfig:backend d3dMetal' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true + /usr/libexec/PlistBuddy -c 'Set :metalConfig:sequoiaCompatMode true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true +fi + +touch "${BOTTLE_DIR}/.ja3_d3dmetal_prepared" +# Ярлык для Whisky Run (тот же CWD + D3DMetal env из Metadata) +cat >"${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3/run-ja3.cmd" <<'EOF' +@echo off +cd /d "C:\GOG Games\Jagged Alliance 3" +start "" "JA3.exe" -unpacked %* +EOF + +echo "OK: D3DMetal graphics для ${BOTTLE_NAME}" +echo " Скрипт: bash ${GOG_DIR}/run-ja3.sh" +echo " Whisky Run: C:\\GOG Games\\Jagged Alliance 3\\run-ja3.cmd (не JA3.exe напрямую)" diff --git a/scripts/games/gog/purge-steam-from-bottle.sh b/scripts/games/gog/purge-steam-from-bottle.sh new file mode 100755 index 0000000..1a8bc00 --- /dev/null +++ b/scripts/games/gog/purge-steam-from-bottle.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Удалить клиент Steam из GOG-bottle (если попал). JA3/GOG — без Steam.app. +# Не трогает steam_api64.dll в папке игры (GOG Galaxy wrapper, не Steam client). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +STEAM_DIRS=( + "${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam" + "${BOTTLE_DIR}/drive_c/Program Files/Steam" + "${BOTTLE_DIR}/drive_c/Steam" +) + +main() { + echo "=== Purge Steam из bottle ${BOTTLE_NAME} ===" + + pkill -9 -f "${BOTTLE_ID}.*[Ss]team" 2>/dev/null || true + pkill -9 -f "${BOTTLE_DIR}.*steam\.exe" 2>/dev/null || true + + local d removed=0 + for d in "${STEAM_DIRS[@]}"; do + if [[ -d "${d}" ]]; then + echo " удаляю: ${d}" + rm -rf "${d}" + removed=1 + fi + done + + find "${BOTTLE_DIR}/drive_c" -maxdepth 4 \( -iname 'steam.exe' -o -iname 'steamservice.exe' \) 2>/dev/null | while read -r f; do + echo " удаляю: ${f}" + rm -f "${f}" + removed=1 + done + + export WINEPREFIX="${BOTTLE_DIR}" + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + if [[ -x "${WINE64}" ]]; then + "${WINE64}" reg delete 'HKCU\Software\Valve' /f >/dev/null 2>&1 || true + "${WINE64}" reg delete 'HKLM\Software\Valve' /f >/dev/null 2>&1 || true + fi + + if [[ "${removed}" -eq 0 ]]; then + echo "OK: каталога Steam в bottle не было." + else + echo "OK: Steam client удалён из bottle." + fi + echo "" + echo "Примечание: JA3.exe использует steam_api64.dll + Galaxy.dll (GOG), это не Steam.app." +} + +main "$@" diff --git a/scripts/games/gog/register-galaxy-registry.sh b/scripts/games/gog/register-galaxy-registry.sh new file mode 100755 index 0000000..e8af7a1 --- /dev/null +++ b/scripts/games/gog/register-galaxy-registry.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Реестр после innoextract — web-installer видит установленный Galaxy. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +GOG_PF='C:\Program Files (x86)\GOG Galaxy' +CLIENT="${GOG_PF}\\GalaxyClient.exe" +SERVICE="${GOG_PF}\\GalaxyClientService.exe" + +gog_wine_check +export_wine_env_gog + +"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\GalaxyClient' /v version /t REG_SZ /d '2.0.100.1' /f +"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\GalaxyClient\paths' /v GalaxyClientExePath /t REG_SZ /d "${CLIENT}" /f +"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\GalaxyClient\paths' /v GalaxyClientServiceExePath /t REG_SZ /d "${SERVICE}" /f +"${WINE64}" reg add 'HKCU\Software\GOG.com\Galaxy' /f 2>/dev/null || true + +echo "OK: GalaxyClient paths → ${CLIENT}" diff --git a/scripts/games/gog/register-ja3-registry.sh b/scripts/games/gog/register-ja3-registry.sh new file mode 100755 index 0000000..3517f2d --- /dev/null +++ b/scripts/games/gog/register-ja3-registry.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Реестр GOG для JA3 (innoextract не запускает Inno-скрипт с ключами). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +GAME_ID='1556263729' +JA3_WIN='C:\GOG Games\Jagged Alliance 3' +JA3_EXE="${JA3_WIN}\\JA3.exe" +KEY="HKLM\\Software\\WOW6432Node\\GOG.com\\Games\\${GAME_ID}" + +gog_wine_check +export_wine_env_gog + +"${WINE64}" reg add "${KEY}" /v gameName /t REG_SZ /d 'Jagged Alliance 3' /f +"${WINE64}" reg add "${KEY}" /v exe /t REG_SZ /d 'JA3.exe' /f +"${WINE64}" reg add "${KEY}" /v path /t REG_SZ /d "${JA3_WIN}\\" /f +"${WINE64}" reg add "${KEY}" /v startMenu /t REG_SZ /d 'Jagged Alliance 3\' /f +"${WINE64}" reg add "${KEY}" /v version /t REG_SZ /d '1.5.2' /f +"${WINE64}" reg add "${KEY}" /v language /t REG_SZ /d 'English' /f +"${WINE64}" reg add "${KEY}" /v launchParam /t REG_SZ /d '' /f +"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\DefaultPackPath' /ve /t REG_SZ /d 'C:\GOG Games' /f 2>/dev/null || true + +bash "${GOG_DIR}/register-galaxy-registry.sh" 2>/dev/null || true + +echo "OK: GOG registry → ${KEY}" diff --git a/scripts/games/gog/reset-ja3-crossover.sh b/scripts/games/gog/reset-ja3-crossover.sh new file mode 100755 index 0000000..d17e1dd --- /dev/null +++ b/scripts/games/gog/reset-ja3-crossover.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Остановить JA3 / Wine (CrossOver bottle JA3-GOG) перед перезапуском. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=gog-process.sh +source "${GOG_DIR}/gog-process.sh" + +bash "${GOG_DIR}/emergency-stop.sh" 2>/dev/null || gog_kill_all +pkill -f 'JA3\.exe' 2>/dev/null || true +pkill -f 'wineserver.*JA3-GOG' 2>/dev/null || true + +CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver" +if [[ -x "${CX}/bin/wine" ]]; then + "${CX}/bin/wine" --bottle JA3-GOG wineboot --kill 2>/dev/null || true +fi +sleep 2 +pgrep -fl 'JA3\.exe|wineserver' 2>/dev/null | head -5 || echo "OK: процессы JA3/Wine остановлены" diff --git a/scripts/games/gog/reset.sh b/scripts/games/gog/reset.sh new file mode 100755 index 0000000..f010b46 --- /dev/null +++ b/scripts/games/gog/reset.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Только GOG web-installer / Galaxy в этом bottle — без Steam, wineboot -k всего префикса. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=gog-process.sh +source "${GOG_DIR}/gog-process.sh" + +gog_kill_all +echo "OK: GOG-процессы остановлены, lock снят (bottle ${BOTTLE_NAME})." diff --git a/scripts/games/gog/run-galaxy-setup.sh b/scripts/games/gog/run-galaxy-setup.sh new file mode 100755 index 0000000..17830d9 --- /dev/null +++ b/scripts/games/gog/run-galaxy-setup.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# Запуск GalaxySetup.exe (JA3 и др.) без родительского GalaxyInstaller — обход «Galaxy already running» + gdi32. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +PRODUCT_ID="${1:-1556263729}" +GAME_NAME="${2:-Jagged Alliance 3}" +LOG="/tmp/gog-galaxy-setup-${BOTTLE_NAME}.log" + +gog_kill_all_galaxy() { + pkill -f 'GOG_Galaxy_' 2>/dev/null || true + pkill -f 'GalaxyWebInstaller' 2>/dev/null || true + pkill -f 'GalaxyInstaller' 2>/dev/null || true + pkill -f 'GalaxySetup' 2>/dev/null || true + pkill -f "${BOTTLE_ID}.*Galaxy" 2>/dev/null || true + sleep 2 +} + +find_galaxy_setup() { + local d + for d in "${BOTTLE_DIR}/drive_c/users/"*/AppData/Local/Temp/GalaxyInstaller_*; do + [[ -f "${d}/GalaxySetup.exe" ]] || continue + echo "${d}/GalaxySetup.exe" + return 0 + done + return 1 +} + +export_wine_env_gog_setup() { + export WINEPREFIX="${BOTTLE_DIR}" + export WINEMSYNC=1 + export WINEESYNC=1 + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + # Inno/botva2: native gdiplus ломает gdi32 в MsgBox; stub/.NET — gdiplus=n,b + export WINEDLLOVERRIDES="gdiplus=b" + export WINEDEBUG="${WINEDEBUG:--all}" + unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true +} + +main() { + gog_wine_check + + if [[ ! -f "${BOTTLE_DIR}/.gog_galaxy_extract_installed" ]] \ + && ! find "${BOTTLE_DIR}/drive_c/Program Files"* -name 'GalaxyClient.exe' -print -quit 2>/dev/null | grep -q .; then + echo "Сначала: bash ${GOG_DIR}/install-galaxy-extract.sh" + exit 1 + fi + + local setup + setup="$(find_galaxy_setup || true)" + if [[ -z "${setup}" ]]; then + echo "Нет GalaxySetup.exe в Temp. Сначала web-stub:" + echo " bash ${GOG_DIR}/run-web-installer.sh /path/GOG_Galaxy_*.exe" + echo "Дождитесь скачивания ~277 MB, затем снова этот скрипт." + exit 1 + fi + + gog_kill_all_galaxy + export_wine_env_gog_setup + + local win="Z:${setup#${BOTTLE_DIR}}" + win="${win//\//\\\\}" + # Wine path: C:\users\... + local cpath + cpath="$("${WINEPATH_BIN}" -w "${setup}" 2>/dev/null || true)" + if [[ -z "${cpath}" ]]; then + cpath="C:\\users\\eternal\\AppData\\Local\\Temp\\$(basename "$(dirname "${setup}")")\\GalaxySetup.exe" + fi + + echo "Setup: ${setup}" + echo "Лог: ${LOG}" + : >"${LOG}" + + "${WINE64}" "${cpath}" \ + /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /FORCECLOSEAPPLICATIONS /SP- \ + /lang=en_US /webinstaller "/product_id=${PRODUCT_ID}" "/game_name=${GAME_NAME}" \ + >>"${LOG}" 2>&1 & + local pid=$! + + local i + for i in $(seq 1 120); do + sleep 5 + if ! kill -0 "${pid}" 2>/dev/null; then + break + fi + if grep -qi 'Install successful\|Successfully installed' "${LOG}" 2>/dev/null; then + break + fi + done + + gog_kill_all_galaxy + + local setup_log + setup_log="$(find "${BOTTLE_DIR}/drive_c/users" -name 'Setup Log*.txt' -print 2>/dev/null | sort | tail -1)" + if [[ -n "${setup_log}" ]]; then + echo "--- Inno Setup log (tail) ---" + tail -30 "${setup_log}" + fi + + if grep -qi 'Install successful\|Successfully installed' "${setup_log:-/dev/null}" 2>/dev/null; then + echo "OK: установка завершена." + exit 0 + fi + if grep -q 'Deinitializing Setup' "${setup_log:-/dev/null}" 2>/dev/null \ + && ! grep -q 'Access violation' "${setup_log:-/dev/null}" 2>/dev/null; then + echo "OK: Setup завершился без AV (проверьте игру в Galaxy)." + exit 0 + fi + + echo "Лог Wine:" + tail -25 "${LOG}" + exit 1 +} + +main "$@" diff --git a/scripts/games/gog/run-ja3-crossover.sh b/scripts/games/gog/run-ja3-crossover.sh new file mode 100755 index 0000000..8fde15e --- /dev/null +++ b/scripts/games/gog/run-ja3-crossover.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# JA3 (GOG) через CrossOver + D3DMetal — D3D12 на Apple Silicon. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +# shellcheck source=gog-process.sh +source "${GOG_DIR}/gog-process.sh" + +CX="${HOME}/Applications/CrossOver.app/Contents/SharedSupport/CrossOver" +[[ -d "/Applications/CrossOver.app" ]] && CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver" +CX_BOTTLE="JA3-GOG" +CX_DIR="${HOME}/Library/Application Support/CrossOver/Bottles/${CX_BOTTLE}" + +[[ -d "${CX}" ]] || { + echo "❌ CrossOver не установлен: brew install --cask crossover" + exit 1 +} + +if [[ ! -f "${CX_DIR}/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then + bash "${GOG_DIR}/setup-ja3-crossover.sh" +fi + +bash "${GOG_DIR}/emergency-stop.sh" >/dev/null 2>&1 || gog_kill_all +sleep 2 + +gog_swap_guard_ja3_launch "${JA3_SWAP_SOFT_MB:-1200}" "${JA3_SWAP_HARD_MB:-4096}" || exit 1 + +echo "Запуск JA3 (CrossOver D3DMetal; без Steam.app)…" +echo "Cmd+Tab → JA3. В графике: отключить heat haze." +echo "(лог «Steam … GOG» = GOG API-шим, не Steam client)" + +# start /d — CWD с CommonLua/ (обязательно; --cx-app без /d → autorun.lua not found) +exec "${CX}/bin/wine" --bottle "${CX_BOTTLE}" start /d "C:\\GOG Games\\Jagged Alliance 3" \ + "C:\\GOG Games\\Jagged Alliance 3\\JA3.exe" ${JA3_EXTRA_ARGS:--unpacked} diff --git a/scripts/games/gog/run-ja3-gptk-wine.sh b/scripts/games/gog/run-ja3-gptk-wine.sh new file mode 100755 index 0000000..0bfe106 --- /dev/null +++ b/scripts/games/gog/run-ja3-gptk-wine.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# JA3 через Wine 7.7 + D3DMetal (Game Porting Toolkit). Канон для D3D12 на Apple Silicon. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +# shellcheck source=gog-process.sh +source "${GOG_DIR}/gog-process.sh" + +GPTK_WINE="/Applications/Game Porting Toolkit.app/Contents/Resources/wine" +GPTK_WINE64="${GPTK_WINE}/bin/wine64" +JA3_WIN='C:\GOG Games\Jagged Alliance 3\JA3.exe' +MARKER="${GAMES_CACHE_DIR}/.ja3_gptk_prefix" + +[[ -x "${GPTK_WINE64}" ]] || { + echo "❌ brew install --cask gcenx/wine/game-porting-toolkit" + exit 1 +} + +if [[ -f "${MARKER}" ]]; then + GPTK_PREFIX="$(cat "${MARKER}")" +else + GPTK_PREFIX="${JA3_GPTK_PREFIX:-${GAMES_CACHE_DIR}/ja3-gptk-prefix}" +fi + +if [[ ! -f "${GPTK_PREFIX}/system.reg" ]]; then + bash "${GOG_DIR}/setup-ja3-gptk-prefix.sh" + GPTK_PREFIX="$(cat "${MARKER}" 2>/dev/null || echo "${GPTK_PREFIX}")" +fi + +JA3_UNIX="${GPTK_PREFIX}/drive_c/GOG Games/Jagged Alliance 3" +[[ -f "${JA3_UNIX}/JA3.exe" ]] || JA3_UNIX="$(readlink "${JA3_UNIX}" 2>/dev/null || true)" +[[ -f "${JA3_UNIX}/JA3.exe" ]] || { + echo "❌ Нет JA3 в GPTK prefix — bash ${GOG_DIR}/setup-ja3-gptk-prefix.sh" + exit 1 +} + +bash "${GOG_DIR}/emergency-stop.sh" >/dev/null 2>&1 || gog_kill_all +sleep 2 + +if [[ "${JA3_IGNORE_SWAP:-0}" != "1" ]] && ! gog_swap_guard "${JA3_SWAP_MAX_MB:-800}"; then + exit 1 +fi + +export WINEPREFIX="${GPTK_PREFIX}" +export WINEARCH=win64 +export WINED3DMETAL=1 +export WINEMSYNC=1 +export WINEDEBUG=-all +export MVK_CONFIG_LOG_LEVEL=0 +export DYLD_LIBRARY_PATH="${GPTK_WINE}/lib:${DYLD_LIBRARY_PATH:-}" +export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b" + +echo "Запуск JA3 (GPTK Wine 7.7 + D3DMetal, prefix ${GPTK_PREFIX})…" +cd "${JA3_UNIX}" +exec "${GPTK_WINE64}" "${JA3_WIN}" ${JA3_EXTRA_ARGS:--unpacked} diff --git a/scripts/games/gog/run-ja3.sh b/scripts/games/gog/run-ja3.sh new file mode 100755 index 0000000..406785f --- /dev/null +++ b/scripts/games/gog/run-ja3.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# Запуск Jagged Alliance 3 (GOG) без Steam client. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +# shellcheck source=gog-process.sh +source "${GOG_DIR}/gog-process.sh" + +JA3_WIN='C:\GOG Games\Jagged Alliance 3\JA3.exe' +JA3_UNIX="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3" +JA3_EXE="${JA3_UNIX}/JA3.exe" +GALAXY_SVC='C:\Program Files (x86)\GOG Galaxy\GalaxyClientService.exe' + +if [[ ! -f "${JA3_EXE}" ]]; then + echo "❌ Нет ${JA3_EXE}" + echo " Установите: bash ${GOG_DIR}/install-ja3-innoextract.sh" + exit 1 +fi + +bash "${GOG_DIR}/emergency-stop.sh" >/dev/null 2>&1 || gog_kill_all +sleep 2 + +if [[ "${JA3_IGNORE_SWAP:-0}" == "1" ]]; then + echo "⚠️ JA3_IGNORE_SWAP=1 — swap $(gog_swap_used_mb 2>/dev/null || echo '?')MB" +elif ! gog_swap_guard_ja3_launch "${JA3_SWAP_SOFT_MB:-1200}" "${JA3_SWAP_HARD_MB:-4096}"; then + used="$(gog_swap_used_mb 2>/dev/null || echo '?')" + echo "" + echo "Swap ~${used}MB — bash ${GOG_DIR}/reset-ja3-crossover.sh или reboot Mac" + echo "Или: JA3_IGNORE_SWAP=1 bash ${GOG_DIR}/run-ja3.sh" + exit 1 +fi + +gog_assert_no_steam_client || exit 1 + +if ! ja3_gptk_available && ! ja3_bottle_uses_d3dmetal; then + if [[ -d "/Applications/Game Porting Toolkit.app" ]]; then + bash "${GOG_DIR}/install-gptk-whisky.sh" >/dev/null 2>&1 || true + bash "${GOG_DIR}/configure-ja3-d3dmetal.sh" >/dev/null 2>&1 || true + else + echo "" + echo "⚠️ JA3 нужен D3D12 (GPTK). Установите:" + echo " brew install --cask gcenx/wine/game-porting-toolkit" + echo " bash ${GOG_DIR}/install-gptk-whisky.sh && bash ${GOG_DIR}/configure-ja3-d3dmetal.sh" + echo "" + fi +fi + +CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver" +if [[ ! -x "${CX}/bin/wine" ]]; then + : +elif [[ ! -f "${HOME}/Library/Application Support/CrossOver/Bottles/JA3-GOG/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then + echo "" + echo "ℹ️ JA3 (D3D12) на Mac: Whisky/Gcenx не поддерживает D3DMetal." + echo " brew install --cask crossover" + echo " bash ${GOG_DIR}/setup-ja3-crossover.sh && bash ${GOG_DIR}/run-ja3-crossover.sh" + echo "" +fi + +# Whisky D3DMetal path — не работает для JA3/D3D12 на Gcenx (см. JA3_MAC_WITHOUT_GALAXY.md) +if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + bash "${GOG_DIR}/prepare-ja3-d3dmetal.sh" >/dev/null 2>&1 || true +fi +bash "${GOG_DIR}/finish-ja3-layout.sh" >/dev/null 2>&1 || true +if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + : +else + bash "${GOG_DIR}/force-ja3-d3d11.sh" >/dev/null 2>&1 || true +fi + +export_wine_env_ja3 +gog_wine_check + +# Фоновый Galaxy service (Music/Matchmaking wrapper — не Steam) +if [[ -f "${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy/GalaxyClientService.exe" ]]; then + if ! pgrep -f 'GalaxyClientService' >/dev/null 2>&1; then + nohup "${WINE64}" "${GALAXY_SVC}" >>/tmp/ja3-galaxy-service.log 2>&1 & + sleep 2 + fi +fi + +if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + echo "⚠️ Whisky D3DMetal не поддерживает JA3/D3D12 (Gcenx без D3DMetal hooks)." + echo " Используйте: bash ${GOG_DIR}/run-ja3-crossover.sh" + echo "" +fi +if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + echo "Запуск JA3 (Whisky — эксперимент; D3D12 → CrossOver)…" +else + echo "Запуск JA3 (GOG, DXVK D3D11 fallback; без Steam.app)…" +fi +echo "Cmd+Tab → wine64. В графике: отключить heat haze." +echo "(лог «Steam … GOG» = GOG API-шим, не Steam client)" +if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then + _ja3_args="${JA3_EXTRA_ARGS:--unpacked}" + export MTL_HUD_ENABLED="${MTL_HUD_ENABLED:-0}" +else + _ja3_args="${JA3_EXTRA_ARGS:--unpacked -graphicsapi d3d11}" +fi +# start /d — CWD = папка игры (CommonLua/, иначе «graphics subsystem»/autorun.lua из Whisky Run) +# innoextract не запускает Inno-постскрипты; -unpacked + CommonLua на диске обязательны +# shellcheck disable=SC2086 +exec "${WINE64}" start /d "C:\\GOG Games\\Jagged Alliance 3" "${JA3_WIN}" ${_ja3_args} diff --git a/scripts/games/gog/run-web-installer.sh b/scripts/games/gog/run-web-installer.sh new file mode 100755 index 0000000..de0c073 --- /dev/null +++ b/scripts/games/gog/run-web-installer.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# GOG web-stub в отдельном GOG-bottle. Без Steam/HoMM. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +# shellcheck source=wine-fonts.sh +source "${GOG_DIR}/wine-fonts.sh" + +GOG_SETUP_MARKER="${BOTTLE_DIR}/.gog_setup_v1" +LOG="/tmp/gog-web-installer-${BOTTLE_NAME}.log" + +usage() { + cat < + bash ${GOG_DIR}/setup-bottle.sh + bash ${GOG_DIR}/install-corefonts.sh + + Лог: ${LOG} +EOF +} + +gog_kill_installers() { + pkill -f 'GOG_Galaxy_' 2>/dev/null || true + pkill -f 'GalaxyWebInstaller' 2>/dev/null || true + sleep 1 +} + +main() { + local src="${1:-}" + if [[ -z "${src}" || "${src}" == "-h" || "${src}" == "--help" ]]; then + usage + exit 0 + fi + [[ -f "${src}" ]] || { echo "Нет файла: ${src}"; exit 1; } + + gog_wine_check + gog_disk_guard 5 || exit 1 + + if [[ ! -f "${BOTTLE_DIR}/.gog_gdiplus_installed" ]]; then + echo "⚠️ Нет gdiplus — один раз: bash ${GOG_DIR}/install-gdiplus.sh" + fi + if [[ ! -f "${BOTTLE_DIR}/.gog_corefonts_installed" ]]; then + echo "⚠️ Сначала: bash ${GOG_DIR}/install-corefonts.sh" + fi + + [[ -f "${GOG_SETUP_MARKER}" ]] || bash "${GOG_DIR}/setup-bottle.sh" + gog_wine_ensure_fonts 0 + + local base dest_win + base="$(basename "${src}")" + dest_win="C:\\${base}" + cp -f "${src}" "${BOTTLE_DIR}/drive_c/${base}" + + gog_kill_installers + export_wine_env_gog + + echo "Bottle: ${BOTTLE_NAME}" + echo "Запуск: ${dest_win}" + echo "Лог: ${LOG}" + + : >"${LOG}" + nohup "${WINE64}" "${dest_win}" >"${LOG}" 2>&1 & + local i alive=0 + for i in 1 2 3 4 5 6; do + sleep 2 + if pgrep -f "${base}" >/dev/null 2>&1; then + alive=1 + break + fi + done + + if [[ "${alive}" -eq 1 ]]; then + sleep 3 + if grep -q 'FontFamily could not be found' "${LOG}" 2>/dev/null; then + echo "❌ FontFamily в логе — bash ${GOG_DIR}/install-gdiplus.sh && install-corefonts.sh" + tail -5 "${LOG}" + exit 1 + fi + echo "OK: установщик запущен (Cmd+Tab → ${base})." + exit 0 + fi + if grep -q 'FontFamily could not be found' "${LOG}" 2>/dev/null; then + echo "❌ FontFamilyNotFound:" + tail -8 "${LOG}" + exit 1 + fi + echo "Процесс завершился:" + tail -20 "${LOG}" + exit 1 +} + +main "$@" diff --git a/scripts/games/gog/setup-bottle.sh b/scripts/games/gog/setup-bottle.sh new file mode 100755 index 0000000..f4286bb --- /dev/null +++ b/scripts/games/gog/setup-bottle.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Один раз: GOG-bottle — шрифты + сеть Wine. Без Steam/HoMM. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +# shellcheck source=wine-fonts.sh +source "${GOG_DIR}/wine-fonts.sh" + +GOG_SETUP_VERSION="1" +GOG_SETUP_MARKER="${BOTTLE_DIR}/.gog_setup_v${GOG_SETUP_VERSION}" + +main() { + local force=0 fonts_only=0 + for arg in "$@"; do + case "${arg}" in + --force) force=1 ;; + --fonts-only) fonts_only=1 ;; + esac + done + + gog_wine_check + + if [[ "${fonts_only}" -eq 1 ]]; then + gog_wine_ensure_fonts 1 + exit 0 + fi + + if [[ -f "${GOG_SETUP_MARKER}" && "${force}" -eq 0 ]]; then + echo "GOG bottle готов: ${GOG_SETUP_MARKER}" + echo " Шрифты: bash ${GOG_DIR}/setup-bottle.sh --fonts-only" + echo " Заново: bash ${GOG_DIR}/setup-bottle.sh --force" + exit 0 + fi + + echo "Подготовка GOG bottle «${BOTTLE_NAME}» (v${GOG_SETUP_VERSION})…" + gog_wine_ensure_fonts 1 + export_wine_env_gog + "${WINE64}" reg add 'HKCU\Software\Wine\Network' /v DnsIPv6Enabled /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true + "${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \ + /v DisableIPv6 /t REG_DWORD /d 1 /f >/dev/null 2>&1 || true + "${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \ + /v ProxyEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true + echo "Wine: DnsIPv6Enabled=0, ProxyEnable=0" + date -u +"%Y-%m-%dT%H:%M:%SZ" >"${GOG_SETUP_MARKER}" + echo "" + echo "OK. Дальше:" + echo " bash ${GOG_DIR}/install-gdiplus.sh # обязательно для .NET" + echo " bash ${GOG_DIR}/install-corefonts.sh" + echo " bash ${GOG_DIR}/run-web-installer.sh /path/GOG_Galaxy_*.exe" +} + +main "$@" diff --git a/scripts/games/gog/setup-ja3-bottle.sh b/scripts/games/gog/setup-ja3-bottle.sh new file mode 100755 index 0000000..e66e2b6 --- /dev/null +++ b/scripts/games/gog/setup-ja3-bottle.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Bottle только для JA3 (offline GOG installer + игра). Без Galaxy CEF. +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" + +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" +gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1 +gog_disk_guard 10 || exit 1 + +if [[ ! -f "${GOG_DIR}/bottle.env" ]]; then + echo "Сначала: Whisky → New Bottle → bash ${GOG_DIR}/create-bottle.sh GOG-Install" + exit 1 +fi + +echo "=== Подготовка bottle для JA3 (без Galaxy) ===" +bash "${GOG_DIR}/setup-bottle.sh" +bash "${GOG_DIR}/install-dxvk.sh" 2>/dev/null || bash "${GAMES_DIR}/install-dxvk-macos.sh" 2>/dev/null || \ + echo "⚠️ DXVK не установлен — JA3 может не стартовать (D3D11). Повторите install-dxvk." + +echo "" +echo "OK. Дальше:" +echo " bash ${GOG_DIR}/install-ja3-offline.sh ~/Downloads/setup_jagged_alliance_3_*.exe" diff --git a/scripts/games/gog/setup-ja3-crossover.sh b/scripts/games/gog/setup-ja3-crossover.sh new file mode 100755 index 0000000..afebe5f --- /dev/null +++ b/scripts/games/gog/setup-ja3-crossover.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# CrossOver bottle JA3-GOG: D3D12 через D3DMetal (единственный подтверждённый путь на M1 Pro). +# Whisky/Gcenx Wine не содержит D3DMetal hooks (см. Gcenx/macOS_Wine_builds#97). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +CX_APP="/Applications/CrossOver.app" +CX="${CX_APP}/Contents/SharedSupport/CrossOver" +CX_BOTTLE="JA3-GOG" +CX_DIR="${HOME}/Library/Application Support/CrossOver/Bottles/${CX_BOTTLE}" +JA3_SRC="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3" +JA3_DEST="${CX_DIR}/drive_c/GOG Games/Jagged Alliance 3" + +[[ -d "${CX_APP}" ]] || { + echo "❌ Нет CrossOver.app" + echo " brew install --cask crossover" + exit 1 +} +[[ -f "${JA3_SRC}/JA3.exe" ]] || { + echo "❌ Нет JA3 в ${BOTTLE_NAME} — install-ja3-innoextract.sh" + exit 1 +} + +bash "${GOG_DIR}/finish-ja3-layout.sh" >/dev/null 2>&1 || true + +if [[ ! -f "${CX_DIR}/system.reg" ]]; then + echo "Создание CrossOver bottle ${CX_BOTTLE} (win10_64)…" + "${CX}/bin/cxbottle" --create --bottle "${CX_BOTTLE}" --description "Jagged Alliance 3 GOG" --template win10_64 +fi + +CONF="${CX_DIR}/cxbottle.conf" +touch "${CONF}" +sed -i '' '/^"WINED3DMETAL"/d;/^"WINEDXVK"/d' "${CONF}" 2>/dev/null || true +{ + echo '"WINED3DMETAL" = "1"' + echo '"WINEDXVK" = "0"' +} >> "${CONF}" + +mkdir -p "${CX_DIR}/drive_c/GOG Games" +if [[ -L "${JA3_DEST}" ]] || [[ ! -d "${JA3_DEST}" ]]; then + rm -f "${JA3_DEST}" + echo "Клон игры в CrossOver bottle (APFS clonefile, ~0 доп. места)…" + cp -cR "${JA3_SRC}" "${JA3_DEST}" +elif [[ ! -f "${JA3_DEST}/CommonLua/Core/autorun.lua" ]]; then + echo "Обновление CommonLua в CrossOver bottle…" + rm -rf "${JA3_DEST}/CommonLua" + cp -R "${JA3_SRC}/CommonLua" "${JA3_DEST}/CommonLua" +fi + +LS="${CX_DIR}/drive_c/users/crossover/AppData/Roaming/Jagged Alliance 3" +mkdir -p "${LS}" +if [[ ! -f "${LS}/LocalStorage.lua" ]]; then + cat >"${LS}/LocalStorage.lua" <<'EOF' +return { + Options = { + GraphicsApi = "d3d12", + VideoPreset = "Low", + }, +} +EOF +fi + +export WINEPREFIX="${CX_DIR}" +"${CX}/bin/wine" --bottle "${CX_BOTTLE}" reg add 'HKLM\Software\WOW6432Node\GOG.com\Games\1556263729' /v gameName /t REG_SZ /d 'Jagged Alliance 3' /f >/dev/null 2>&1 || true +"${CX}/bin/wine" --bottle "${CX_BOTTLE}" reg add 'HKLM\Software\WOW6432Node\GOG.com\Games\1556263729' /v exe /t REG_SZ /d 'JA3.exe' /f >/dev/null 2>&1 || true +"${CX}/bin/wine" --bottle "${CX_BOTTLE}" reg add 'HKLM\Software\WOW6432Node\GOG.com\Games\1556263729' /v path /t REG_SZ /d 'C:\GOG Games\Jagged Alliance 3\' /f >/dev/null 2>&1 || true + +echo "OK: CrossOver ${CX_BOTTLE} + D3DMetal" +bash "${GOG_DIR}/fix-ja3-crossover-mouse.sh" +echo " Запуск: bash ${GOG_DIR}/run-ja3-crossover.sh" diff --git a/scripts/games/gog/setup-ja3-gptk-prefix.sh b/scripts/games/gog/setup-ja3-gptk-prefix.sh new file mode 100755 index 0000000..9b5fedc --- /dev/null +++ b/scripts/games/gog/setup-ja3-gptk-prefix.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Отдельный WINEPREFIX под Wine 7.7 из Game Porting Toolkit (D3D12 + D3DMetal). +# Whisky Wine 11 не грузит GPTK dxgi/d3d12 PE — JA3 падает на «Failed creating render device». +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +GPTK_WINE="/Applications/Game Porting Toolkit.app/Contents/Resources/wine" +GPTK_WINE64="${GPTK_WINE}/bin/wine64" +JA3_SRC="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3" +GPTK_PREFIX="${JA3_GPTK_PREFIX:-${GAMES_CACHE_DIR}/ja3-gptk-prefix}" + +[[ -x "${GPTK_WINE64}" ]] || { + echo "❌ brew install --cask gcenx/wine/game-porting-toolkit" + exit 1 +} +[[ -f "${JA3_SRC}/JA3.exe" ]] || { + echo "❌ Нет JA3 в bottle ${BOTTLE_NAME} — install-ja3-innoextract.sh" + exit 1 +} + +export WINEARCH=win64 +export WINEPREFIX="${GPTK_PREFIX}" +export WINED3DMETAL=1 +export WINEMSYNC=1 +export WINEDEBUG=-all +export DYLD_LIBRARY_PATH="${GPTK_WINE}/lib:${DYLD_LIBRARY_PATH:-}" + +mkdir -p "${GPTK_PREFIX}" + +if [[ ! -f "${GPTK_PREFIX}/system.reg" ]]; then + echo "Инициализация GPTK prefix: ${GPTK_PREFIX}" + "${GPTK_WINE64}" wineboot -i +fi + +GOG_DRIVE="${GPTK_PREFIX}/drive_c/GOG Games" +JA3_LINK="${GOG_DRIVE}/Jagged Alliance 3" +mkdir -p "${GOG_DRIVE}" +if [[ -L "${JA3_LINK}" ]]; then + : +elif [[ -d "${JA3_LINK}" ]]; then + echo " JA3 уже в prefix (не symlink)" +else + ln -s "${JA3_SRC}" "${JA3_LINK}" +fi + +# CommonLua + upscale cleanup (на исходной копии в GOG bottle) +bash "${GOG_DIR}/finish-ja3-layout.sh" >/dev/null 2>&1 || true + +WINE64="${GPTK_WINE64}" BOTTLE_DIR="${GPTK_PREFIX}" export_wine_env_gog +export WINED3DMETAL=1 +export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b" + +GAME_ID='1556263729' +KEY="HKLM\\Software\\WOW6432Node\\GOG.com\\Games\\${GAME_ID}" +"${GPTK_WINE64}" reg add "${KEY}" /v gameName /t REG_SZ /d 'Jagged Alliance 3' /f >/dev/null +"${GPTK_WINE64}" reg add "${KEY}" /v exe /t REG_SZ /d 'JA3.exe' /f >/dev/null +"${GPTK_WINE64}" reg add "${KEY}" /v path /t REG_SZ /d 'C:\GOG Games\Jagged Alliance 3\' /f >/dev/null + +echo "${GPTK_PREFIX}" >"${GAMES_CACHE_DIR}/.ja3_gptk_prefix" +echo "OK: GPTK prefix → ${GPTK_PREFIX}" +echo " Запуск: bash ${GOG_DIR}/run-ja3-gptk-wine.sh" diff --git a/scripts/games/gog/upgrade-whisky-wine-gcenx.sh b/scripts/games/gog/upgrade-whisky-wine-gcenx.sh new file mode 100755 index 0000000..e7b26ca --- /dev/null +++ b/scripts/games/gog/upgrade-whisky-wine-gcenx.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Whisky Libraries Wine 11.0 → Gcenx wine-staging 11.9 + GPTK D3DMetal bridge (JA3 / D3D12). +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)" +# shellcheck source=config.sh +source "${GOG_DIR}/config.sh" + +CACHE="${GAMES_CACHE_DIR}" +TAR="${CACHE}/wine-staging-11.9-osx64.tar.xz" +URL="https://github.com/Gcenx/macOS_Wine_builds/releases/download/11.9/wine-staging-11.9-osx64.tar.xz" +WHISKY_WINE="${WHISKY_LIBS}/Libraries/Wine" +STAMP="${WHISKY_LIBS}/Libraries/.wine_gcenx_11.9" + +[[ -f "${TAR}" ]] || curl -L --fail -o "${TAR}" "${URL}" +EXTRACT="${CACHE}/wine-staging-11.9-extract" +rm -rf "${EXTRACT}" +mkdir -p "${EXTRACT}" +tar -xJf "${TAR}" -C "${EXTRACT}" +SRC="${EXTRACT}/Wine Staging.app/Contents/Resources/wine" + +[[ -d "${SRC}/bin" ]] || { + echo "❌ Неверная структура архива" + exit 1 +} + +if [[ ! -f "${STAMP}" ]]; then + bak="${WHISKY_WINE}.bak.$(date +%Y%m%d%H%M)" + echo "Бэкап Wine → ${bak}" + cp -a "${WHISKY_WINE}" "${bak}" +fi + +rsync -a --delete \ + --exclude 'lib/external/' \ + "${SRC}/" "${WHISKY_WINE}/" + +# Gcenx: wine64 → wine; Whisky-скрипты ожидают bin/wine64 +if [[ -x "${WHISKY_WINE}/bin/wine" ]] && [[ ! -e "${WHISKY_WINE}/bin/wine64" ]]; then + ln -sf wine "${WHISKY_WINE}/bin/wine64" +fi + +bash "${GOG_DIR}/install-gptk-whisky.sh" +touch "${STAMP}" +echo "OK: Whisky Wine → staging 11.9 + GPTK D3DMetal" +"${WHISKY_WINE}/bin/wine64" --version 2>&1 | head -1 diff --git a/scripts/games/gog/use-bottle.sh b/scripts/games/gog/use-bottle.sh new file mode 100755 index 0000000..2720d43 --- /dev/null +++ b/scripts/games/gog/use-bottle.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail +GOG_DIR="$(cd "$(dirname "$0")" && pwd)" +id="${1:-}" +name="${2:-GOG-Install}" +if [[ -z "${id}" ]]; then + echo "Использование: $(basename "$0") [имя]" + bash "${GOG_DIR}/list-bottles.sh" + exit 1 +fi +if [[ "${id}" == "38412122-AAE1-4831-9F2E-6A0EB364B3F9" ]]; then + echo "❌ Это bottle HoMM-OldenEra (Steam/HoMM). Создайте новый bottle в Whisky." + exit 1 +fi +cat >"${GOG_DIR}/bottle.env" </dev/null || true + done + # .NET GenericSansSerif → Microsoft Sans Serif / Arial + cp -n "${fonts}/Arial.ttf" "${fonts}/sans-serif.ttf" 2>/dev/null || true + cp -n "${fonts}/Arial.ttf" "${fonts}/arial.ttf" 2>/dev/null || true + cp -n "${fonts}/Arial.ttf" "${fonts}/micross.ttf" 2>/dev/null || true + cp -n "${fonts}/Arial.ttf" "${fonts}/Microsoft Sans Serif.ttf" 2>/dev/null || true + cp -n "${fonts}/Tahoma.ttf" "${fonts}/tahoma.ttf" 2>/dev/null || true +} + +gog_wine_register_font_file() { + local reg_name="$1" + local ttf_file="$2" + export WINEPREFIX="${BOTTLE_DIR}" + "${WINE64}" reg add 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Fonts' \ + /v "${reg_name} (TrueType)" /t REG_SZ /d "${ttf_file}" /f >/dev/null 2>&1 || true +} + +gog_wine_register_all_fonts_dir() { + local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts" + local f base reg + for f in "${fonts}"/*; do + [[ -f "${f}" ]] || continue + [[ "${f}" == *.ttf || "${f}" == *.TTF ]] || continue + base="$(basename "${f}")" + reg="${base%.*}" + gog_wine_register_font_file "${reg}" "${base}" + done +} + +gog_wine_wineboot_fonts() { + export WINEPREFIX="${BOTTLE_DIR}" + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + "${WINE64}" wineboot -u 2>/dev/null || true +} + +gog_wine_register_fonts_registry() { + export WINEPREFIX="${BOTTLE_DIR}" + local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts" + + gog_wine_register_font_file 'Arial' 'arial.ttf' + gog_wine_register_font_file 'Tahoma' 'tahoma.ttf' + gog_wine_register_font_file 'Verdana' 'verdana.ttf' + gog_wine_register_font_file 'Times New Roman' 'Times New Roman.ttf' + gog_wine_register_font_file 'Courier New' 'Courier New.ttf' + gog_wine_register_font_file 'Microsoft Sans Serif' 'micross.ttf' + + local sub='HKLM\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes' + "${WINE64}" reg add "${sub}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'MS Shell Dlg 2' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'MS Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'Segoe UI' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + + local rep='HKCU\Software\Wine\Fonts\Replacements' + "${WINE64}" reg add "${rep}" /v '@@MS_SHELLDLG' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${rep}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${rep}" /v 'Microsoft Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${rep}" /v 'SansSerif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'SansSerif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true + + gog_wine_register_all_fonts_dir +} + +gog_wine_ensure_fonts() { + local rebuild="${1:-0}" + export_wine_env_gog + gog_wine_install_fonts_mac + gog_wine_register_fonts_registry + if [[ "${rebuild}" == "1" ]]; then + echo "wineboot -u (~30–60 с)…" + gog_wine_wineboot_fonts + fi + local n + n="$(ls "${BOTTLE_DIR}/drive_c/windows/Fonts" 2>/dev/null | wc -l | tr -d ' ')" + echo "Шрифты (${BOTTLE_NAME}): ${n} файлов" +} diff --git a/scripts/games/install-dxvk-macos.sh b/scripts/games/install-dxvk-macos.sh new file mode 100755 index 0000000..5678300 --- /dev/null +++ b/scripts/games/install-dxvk-macos.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Gcenx/DXVK-macOS — patched DX11 for Apple Silicon + MoltenVK (Whisky credits this fork). +# Vanilla DXVK 2.x and «D3DMetal» без Apple GPTK на зеркале frankea Libraries не поднимают DX11. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +DXVK_CACHE="${GAMES_CACHE_DIR}/dxvk-macos" +DXVK_TAR="${DXVK_CACHE}/dxvk-macOS-async.tar.gz" +DXVK_URL="https://github.com/Gcenx/DXVK-macOS/releases/download/v1.10.3-20230507-repack/dxvk-macOS-async-v1.10.3-20230507-repack.tar.gz" +DXVK_DIR="${DXVK_CACHE}/dxvk-macOS-async-v1.10.3-20230507-repack" +DXVK_X64="${DXVK_DIR}/x64" +WINE_LIB64="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-windows" +SYS32="${BOTTLE_DIR}/drive_c/windows/system32" + +cmd_wine_check() { + if [[ ! -x "${WINE64}" ]]; then + echo "Сначала: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh" + exit 1 + fi +} + +cmd_download() { + mkdir -p "${DXVK_CACHE}" + if [[ ! -f "${DXVK_TAR}" ]]; then + echo "==> Скачивание DXVK-macOS (~2.7 MB)..." + curl -L -o "${DXVK_TAR}" "${DXVK_URL}" + fi + tar -xzf "${DXVK_TAR}" -C "${DXVK_CACHE}" +} + +cmd_install() { + if [[ ! -f "${DXVK_X64}/d3d11.dll" ]]; then + cmd_download + fi + if [[ ! -d "${SYS32}" ]]; then + echo "Нет Wine prefix (drive_c). Откройте bottle в Whisky один раз." + exit 1 + fi + echo "==> Копирование d3d11/d3d10core в system32, dxgi — wine builtin..." + cp -f "${WINE_LIB64}/dxgi.dll" "${SYS32}/dxgi.dll" + cp -f "${DXVK_X64}/d3d11.dll" "${SYS32}/d3d11.dll" + cp -f "${DXVK_X64}/d3d10core.dll" "${SYS32}/d3d10core.dll" + export WINEPREFIX="${BOTTLE_DIR}" + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + "${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d11 /d native,b /f >/dev/null + "${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d10core /d native,b /f >/dev/null + echo "OK: DXVK-macOS v1.10.3-async в bottle ${BOTTLE_NAME}" +} + +cmd_wine_check +cmd_install diff --git a/scripts/games/install-gog-galaxy.sh b/scripts/games/install-gog-galaxy.sh new file mode 100755 index 0000000..9627cba --- /dev/null +++ b/scripts/games/install-gog-galaxy.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# GOG Galaxy 2.0: скачать официальный установщик и поставить в bottle HoMM-OldenEra. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +SILENT="${GOG_INSTALL_SILENT:-0}" + +if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then + echo "Bottle ещё не инициализирован." + echo "1) open -a Whisky" + echo "2) Откройте bottle «${BOTTLE_NAME}»" + echo "3) Запустите этот скрипт снова" + open -a Whisky + exit 1 +fi + +if exe="$(homm_gog_exe)"; [[ -n "${exe}" ]]; then + echo "GOG Galaxy уже установлен: ${exe}" + echo "Запуск: bash ${SCRIPT_DIR}/start.sh gog" + exit 0 +fi + +mkdir -p "${GAMES_CACHE_DIR}" + +homm_gog_setup_url() { + if [[ -n "${GOG_SETUP_URL:-}" ]]; then + echo "${GOG_SETUP_URL}" + return 0 + fi + local json url + json="$(curl -fsSL "https://remote-config.gog.com/components/webinstaller?component_version=2.0.0")" + url="$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["content"]["windows"]["downloadLink"])' <<<"${json}" 2>/dev/null)" || true + if [[ -z "${url}" ]]; then + url="https://content-system.gog.com/open_link/download?path=/open/galaxy/client/setup_galaxy_2.0.100.1.exe" + fi + echo "${url}" +} + +if [[ ! -f "${GOG_SETUP}" ]]; then + dl_url="$(homm_gog_setup_url)" + echo "Скачиваю полный установщик GOG Galaxy (~290 MB)…" + echo " ${dl_url}" + curl -L --fail --retry 3 -C - -o "${GOG_SETUP}" "${dl_url}" +fi + +echo "Размер установщика: $(du -sh "${GOG_SETUP}" | awk '{print $1}')" +min_gb="${HOMM_DISK_MIN_GB:-3}" +min_kb=$((min_gb * 1024 * 1024)) +if [[ "$(homm_disk_free_kb)" -lt "${min_kb}" ]]; then + echo "❌ Мало места на диске (<${min_gb} GB). GOG Galaxy ~300+ MB." + exit 1 +fi + +homm_gog_install_from_extract() { + local extract_dir="${GAMES_CACHE_DIR}/gog-galaxy-extract" + local gog_pf="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy" + local gog_pd="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy" + if ! command -v innoextract >/dev/null 2>&1; then + echo "Нужен innoextract: brew install innoextract" + return 1 + fi + echo "Распаковка установщика (innoextract) — обход gdi32 в Wine-инсталляторе…" + rm -rf "${extract_dir}" + mkdir -p "${extract_dir}" + innoextract -e -d "${extract_dir}" "${GOG_SETUP}" + mkdir -p "${gog_pf}" "${gog_pd}" + cp -R "${extract_dir}/app/." "${gog_pf}/" + local pd_src + pd_src="$(find "${extract_dir}" -type d -path '*/GOG.com/Galaxy' 2>/dev/null | head -1)" + if [[ -n "${pd_src}" ]]; then + cp -R "${pd_src}/." "${gog_pd}/" + fi + homm_gog_exe >/dev/null +} + +export_wine_env_steam + +# Wine Inno Setup часто падает на gdi32 в /VERYSILENT; по умолчанию — распаковка. +if [[ "${GOG_INSTALL_WINE:-0}" == "1" && "${SILENT}" == "1" ]]; then + echo "Тихая установка через Wine (эксперимент)…" + cp -f "${GOG_SETUP}" "${BOTTLE_DIR}/drive_c/setup_galaxy.exe" 2>/dev/null || true + "${WINE64}" 'C:\\setup_galaxy.exe' /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /FORCECLOSEAPPLICATIONS /SP- || true + sleep 10 +elif [[ "${SILENT}" == "1" ]]; then + homm_gog_install_from_extract || exit 1 +else + echo "Запуск GUI-установщика GOG Galaxy в bottle «${BOTTLE_NAME}»…" + echo "Дождитесь завершения мастера установки, затем:" + echo " bash ${SCRIPT_DIR}/start.sh gog" + if command -v whisky >/dev/null 2>&1; then + whisky run "${BOTTLE_NAME}" "${GOG_SETUP}" + else + "${WINE64}" "${GOG_SETUP}" + fi + exit 0 +fi + +if exe="$(homm_gog_exe)"; [[ -n "${exe}" ]]; then + echo "OK: GOG Galaxy → ${exe}" + bash "${SCRIPT_DIR}/pin-gog-whisky.sh" 2>/dev/null || true + echo "Запуск: bash ${SCRIPT_DIR}/start.sh gog" +else + echo "GalaxyClient.exe не найден." + echo " GUI: GOG_INSTALL_SILENT=0 bash ${SCRIPT_DIR}/install-gog-galaxy.sh" + echo " Wine silent (часто ломается): GOG_INSTALL_WINE=1 GOG_INSTALL_SILENT=1 …" + exit 1 +fi diff --git a/scripts/games/install-steam-extract.sh b/scripts/games/install-steam-extract.sh new file mode 100755 index 0000000..afc9cab --- /dev/null +++ b/scripts/games/install-steam-extract.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Steam в bottle без GUI-мастера (распаковка SteamSetup.exe). +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +if [[ ! -f "${STEAM_SETUP}" ]]; then + echo "Скачиваю SteamSetup.exe..." + mkdir -p "${GAMES_CACHE_DIR}" + curl -L --fail -o "${STEAM_SETUP}" \ + "https://cdn.fastly.steamstatic.com/client/installer/SteamSetup.exe" +fi + +command -v 7z >/dev/null || { echo "Нужен: brew install p7zip"; exit 1; } + +pkill -f wineserver 2>/dev/null || true +sleep 1 + +EXTRACT="/tmp/steam-extract-$$" +rm -rf "${EXTRACT}" +mkdir -p "${EXTRACT}" +7z x -o"${EXTRACT}" "${STEAM_SETUP}" -y >/dev/null + +rm -rf "${STEAM_DIR}" +mkdir -p "${STEAM_DIR}" +cp -R "${EXTRACT}/Steam.exe" "${EXTRACT}/uninstall.exe" \ + "${EXTRACT}/bin" "${EXTRACT}/public" "${STEAM_DIR}/" +rm -rf "${EXTRACT}" + +echo "OK: Steam скопирован в bottle. Запуск: bash ${SCRIPT_DIR}/start.sh" diff --git a/scripts/games/install-steam-in-bottle.sh b/scripts/games/install-steam-in-bottle.sh new file mode 100755 index 0000000..28ddb21 --- /dev/null +++ b/scripts/games/install-steam-in-bottle.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Steam через GUI-установщик в Whisky (если extract не подошёл). +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +if [[ ! -f "${STEAM_SETUP}" ]]; then + echo "Скачиваю SteamSetup.exe..." + mkdir -p "${GAMES_CACHE_DIR}" + curl -L --fail -o "${STEAM_SETUP}" \ + "https://cdn.fastly.steamstatic.com/client/installer/SteamSetup.exe" +fi + +if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then + echo "Bottle ещё не инициализирован." + echo "1) open -a Whisky" + echo "2) Откройте bottle «${BOTTLE_NAME}» и дождитесь создания Windows-префикса" + echo "3) Запустите этот скрипт снова" + open -a Whisky + exit 1 +fi + +echo "Запуск установщика Steam в bottle «${BOTTLE_NAME}»..." +whisky run "${BOTTLE_NAME}" "${STEAM_SETUP}" diff --git a/scripts/games/install-whiskywine-manual.sh b/scripts/games/install-whiskywine-manual.sh new file mode 100755 index 0000000..db0c726 --- /dev/null +++ b/scripts/games/install-whiskywine-manual.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Workaround: official data.getwhisky.app is dead (404). Mirror from frankea/Whisky. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +LIB_URL="https://github.com/frankea/Whisky/releases/download/v3.0.0/Libraries.tar.gz" +APP_SUPPORT="${WHISKY_LIBS}" + +mkdir -p "${GAMES_CACHE_DIR}" + +echo "==> Download Libraries.tar.gz (~314 MB) if missing..." +if [[ ! -f "${LIBRARIES_TAR}" ]] || [[ $(stat -f%z "${LIBRARIES_TAR}" 2>/dev/null || stat -c%s "${LIBRARIES_TAR}") -lt 100000000 ]]; then + curl -L --fail -o "${LIBRARIES_TAR}" "${LIB_URL}" +fi + +echo "==> Extract to ${APP_SUPPORT}" +mkdir -p "${APP_SUPPORT}" +tar -xzf "${LIBRARIES_TAR}" -C "${APP_SUPPORT}" + +echo "==> Done. WhiskyWine version:" +plutil -p "${APP_SUPPORT}/Libraries/WhiskyWineVersion.plist" +echo "Restart Whisky (Cmd+Q → open -a Whisky)." diff --git a/scripts/games/install-wine-corefonts.sh b/scripts/games/install-wine-corefonts.sh new file mode 100644 index 0000000..f170eae --- /dev/null +++ b/scripts/games/install-wine-corefonts.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# → GOG bottle only (см. gog/README.md). +exec "$(cd "$(dirname "$0")" && pwd)/gog/install-corefonts.sh" "$@" diff --git a/scripts/games/migrate-bottle-to-franke-whisky.sh b/scripts/games/migrate-bottle-to-franke-whisky.sh new file mode 100755 index 0000000..9074cad --- /dev/null +++ b/scripts/games/migrate-bottle-to-franke-whisky.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Bottle создан в com.isaacmarovitz.Whisky — franke Whisky 3 его не видит → нет окна. +# Перенос bottle + Libraries symlink + whisky add (один раз). +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +ISAAC_BOTTLE="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}" +FRANKE_BOTTLE="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}" +FRANKE_LIBS="${HOME}/Library/Application Support/com.franke.Whisky/Libraries" +ISAAC_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky/Libraries" + +echo "=== Миграция bottle → franke Whisky 3 ===" + +if [[ ! -d "${ISAAC_BOTTLE}/drive_c" && -d "${FRANKE_BOTTLE}/drive_c" ]]; then + echo "Bottle уже в franke: ${FRANKE_BOTTLE}" +elif [[ -d "${ISAAC_BOTTLE}/drive_c" ]]; then + mkdir -p "${HOME}/Library/Containers/com.franke.Whisky/Bottles" + if [[ -e "${FRANKE_BOTTLE}" ]]; then + echo "Удаляю битую ссылку/папку: ${FRANKE_BOTTLE}" + rm -rf "${FRANKE_BOTTLE}" + fi + echo "Перенос ~11 GB (1–3 мин на SSD)…" + mv "${ISAAC_BOTTLE}" "${FRANKE_BOTTLE}" + echo "OK: ${FRANKE_BOTTLE}" +else + echo "❌ Не найден bottle: ${ISAAC_BOTTLE}" + exit 1 +fi + +if [[ ! -x "${FRANKE_LIBS}/Wine/bin/wine64" ]]; then + echo "WhiskyWine в franke Libraries нет — symlink на isaacmarovitz…" + rm -rf "${FRANKE_LIBS}" + ln -s "${ISAAC_LIBS}" "${FRANKE_LIBS}" +fi + +if command -v whisky >/dev/null 2>&1; then + if ! whisky list 2>/dev/null | grep -q "${BOTTLE_NAME}"; then + whisky add "${FRANKE_BOTTLE}" || echo "⚠️ whisky add вручную: Whisky → Add → ${FRANKE_BOTTLE}" + fi + echo "OK. BottleVM.plist обновлён для Whisky.app." +fi + +# Whisky.app читает BottleVM.plist +BOTTLE_VM="${HOME}/Library/Containers/com.franke.Whisky/BottleVM.plist" +if [[ -f "${BOTTLE_VM}" ]] && ! grep -q "${BOTTLE_ID}" "${BOTTLE_VM}" 2>/dev/null; then + echo "Добавьте bottle в Whisky вручную: Whisky → + → Add existing →" + echo " ${FRANKE_BOTTLE}" +fi diff --git a/scripts/games/pin-gog-whisky.sh b/scripts/games/pin-gog-whisky.sh new file mode 100755 index 0000000..0a88de3 --- /dev/null +++ b/scripts/games/pin-gog-whisky.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Закрепить GOG Galaxy в Whisky (Metadata.plist pins + Program Settings), как steam. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +if ! homm_gog_exe >/dev/null; then + echo "GOG Galaxy не установлен: bash ${SCRIPT_DIR}/start.sh gog-install" + exit 1 +fi + +GOG_EXE_PATH="$(homm_gog_exe)" +METADATA="${BOTTLE_DIR}/Metadata.plist" +PROG_SETTINGS="${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist" +# file URL как у steam (пробелы → %20) +GOG_URL="file://${BOTTLE_DIR}/drive_c/Program%20Files%20(x86)/GOG%20Galaxy/GalaxyClient.exe" + +if grep -q 'gog' "${METADATA}" 2>/dev/null; then + echo "Pin «gog» уже есть в ${METADATA}" +else + python3 </dev/null || true + pkill -9 -f "${bottle_dir}.*steam\.exe" 2>/dev/null || true + + export WINEPREFIX="${bottle_dir}" + export DYLD_LIBRARY_PATH="${wine_lib}:${DYLD_LIBRARY_PATH:-}" + local wineserver="${wine64%/wine64}/wineserver" + [[ -x "${wineserver}" ]] && "${wineserver}" -k 2>/dev/null || true + sleep 1 + + local removed=0 d + for d in \ + "${bottle_dir}/drive_c/Program Files (x86)/Steam" \ + "${bottle_dir}/drive_c/Program Files/Steam" \ + "${bottle_dir}/drive_c/Steam"; do + if [[ -d "${d}" ]]; then + echo " rm -rf ${d}" + rm -rf "${d}" + removed=1 + fi + done + + if [[ -x "${wine64}" ]]; then + "${wine64}" reg delete 'HKCU\Software\Valve' /f >/dev/null 2>&1 || true + "${wine64}" reg delete 'HKLM\Software\Valve' /f >/dev/null 2>&1 || true + fi + + if [[ "${removed}" -eq 1 ]]; then + echo " OK: Steam client удалён" + else + echo " OK: Steam client не найден" + fi +} + +purge_macos_steam() { + echo "" + echo "=== macOS Steam.app ===" + local app + for app in /Applications/Steam.app "${HOME}/Applications/Steam.app"; do + if [[ -d "${app}" ]]; then + echo " rm -rf ${app}" + rm -rf "${app}" + fi + done + if [[ -d "${HOME}/Library/Application Support/Steam" ]]; then + echo " rm -rf ~/Library/Application Support/Steam" + rm -rf "${HOME}/Library/Application Support/Steam" + fi + if ! [[ -d /Applications/Steam.app || -d "${HOME}/Applications/Steam.app" ]]; then + echo " Steam.app не установлен" + fi +} + +main() { + echo "Удаление Steam (Wine + macOS), без steam_api64.dll в GOG Games/…" + + # GOG-Install + if [[ -f "${GAMES_DIR}/gog/bottle.env" ]]; then + # shellcheck source=gog/config.sh + source "${GAMES_DIR}/gog/config.sh" + purge_one_bottle "${BOTTLE_DIR}" "${BOTTLE_NAME}" "${WINE64}" "${WINE_LIB}" + fi + + # HoMM / общий config + # shellcheck source=config.sh + source "${GAMES_DIR}/config.sh" + purge_one_bottle "${BOTTLE_DIR}" "${BOTTLE_NAME}" "${WINE64}" "${WINE_LIB}" + + purge_macos_steam + echo "" + echo "Готово." +} + +main "$@" diff --git a/scripts/games/run-gog-web-installer.sh b/scripts/games/run-gog-web-installer.sh new file mode 100755 index 0000000..f08c829 --- /dev/null +++ b/scripts/games/run-gog-web-installer.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# → GOG-only pipeline (отдельный bottle, без Steam). +exec "$(cd "$(dirname "$0")" && pwd)/gog/run-web-installer.sh" "$@" diff --git a/scripts/games/run-homm-bottle.sh b/scripts/games/run-homm-bottle.sh new file mode 100755 index 0000000..b462395 --- /dev/null +++ b/scripts/games/run-homm-bottle.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# Legacy alias → start.sh +exec "$(dirname "$0")/start.sh" steam "$@" diff --git a/scripts/games/run-wine-exe.sh b/scripts/games/run-wine-exe.sh new file mode 100755 index 0000000..312c9f6 --- /dev/null +++ b/scripts/games/run-wine-exe.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# Универсальный запуск .exe в Whisky-bottle. См. docs/games/WINE_APPS_LAUNCH_RUNBOOK.md +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +BOTTLE_NAME="${WINE_BOTTLE_NAME:-}" +BOTTLE_ID="${WINE_BOTTLE_ID:-}" +GOG_BOTTLE_MODE="${GOG_BOTTLE:-0}" +PROFILE="plain" +BACKGROUND=0 +LOG="" +EXE_ARG="" + +usage() { + cat <<'EOF' +Использование: run-wine-exe.sh [опции] + +Опции: + --bottle NAME Имя bottle в Whisky (напр. HoMM-OldenEra) + --bottle-id UUID UUID bottle (приоритет над --bottle) + --gog-bottle Bottle из scripts/games/gog/bottle.env + --profile PROF plain | steam | gog | game | ja3 (по умолчанию plain) + --background nohup, не ждать + --log FILE лог при --background + -h, --help + +Примеры: + run-wine-exe.sh --profile steam 'C:\Program Files (x86)\Steam\Steam.exe' + run-wine-exe.sh --gog-bottle --profile gog ~/Downloads/setup.exe + run-wine-exe.sh --bottle HoMM-OldenEra --profile game 'C:\...\game.exe' + +Документация: docs/games/WINE_APPS_LAUNCH_RUNBOOK.md +EOF +} + +resolve_bottle_dir() { + if [[ "${GOG_BOTTLE_MODE}" != "1" && -z "${BOTTLE_ID}" && -z "${BOTTLE_NAME}" ]]; then + # config.sh: HoMM-OldenEra по умолчанию + return 0 + fi + if [[ "${GOG_BOTTLE_MODE}" == "1" ]]; then + local gog_cfg="${SCRIPT_DIR}/gog/bottle.env" + if [[ ! -f "${gog_cfg}" ]]; then + echo "❌ Нет gog/bottle.env — bash ${SCRIPT_DIR}/gog/use-bottle.sh " + exit 1 + fi + # shellcheck source=/dev/null + source "${gog_cfg}" + BOTTLE_ID="${BOTTLE_ID:?}" + BOTTLE_NAME="${BOTTLE_NAME:-GOG-Install}" + fi + if [[ -n "${BOTTLE_ID}" ]]; then + local _f="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}" + local _i="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}" + if [[ -d "${_f}/drive_c" ]]; then + BOTTLE_DIR="${_f}" + WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky" + elif [[ -d "${_i}/drive_c" ]]; then + BOTTLE_DIR="${_i}" + WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky" + else + echo "❌ Bottle не найден: ${BOTTLE_ID}" + exit 1 + fi + return 0 + fi + if [[ -n "${BOTTLE_NAME}" ]]; then + local d + for d in "${HOME}/Library/Containers/com.franke.Whisky/Bottles"/* \ + "${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles"/*; do + [[ -d "${d}/drive_c" ]] || continue + if [[ "$(basename "${d}")" == "${BOTTLE_NAME}" ]] || \ + grep -q "BOTTLE_NAME=${BOTTLE_NAME}" "${d}/Metadata.plist" 2>/dev/null; then + BOTTLE_DIR="${d}" + if [[ "${d}" == *com.franke* ]]; then + WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky" + else + WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky" + fi + return 0 + fi + done + echo "❌ Bottle по имени не найден: ${BOTTLE_NAME}" + exit 1 + fi + # default: HoMM from config.sh (already sourced) +} + +to_win_path() { + local arg="$1" + if [[ "${arg}" =~ ^[A-Za-z]:\\ ]]; then + echo "${arg}" + return 0 + fi + if [[ ! -f "${arg}" ]]; then + echo "❌ Нет файла: ${arg}" >&2 + exit 1 + fi + local base dest + base="$(basename "${arg}")" + dest="${BOTTLE_DIR}/drive_c/${base}" + cp -f "${arg}" "${dest}" + echo "C:\\${base}" +} + +apply_profile() { + case "${PROFILE}" in + plain) + export WINEPREFIX="${BOTTLE_DIR}" + export WINEMSYNC=1 WINEESYNC=1 + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + unset WINEDLLOVERRIDES 2>/dev/null || true + ;; + steam) + export_wine_env_steam + ;; + gog) + if [[ -f "${SCRIPT_DIR}/gog/config.sh" ]]; then + # shellcheck source=/dev/null + source "${SCRIPT_DIR}/gog/config.sh" + export_wine_env_gog + else + export_wine_env_steam + export WINEDLLOVERRIDES="gdiplus=n,b" + fi + ;; + game) + export_wine_env_game + ;; + ja3) + if [[ -f "${SCRIPT_DIR}/gog/config.sh" ]]; then + # shellcheck source=/dev/null + source "${SCRIPT_DIR}/gog/config.sh" + export_wine_env_ja3 + else + export_wine_env_game + fi + ;; + *) + echo "❌ Неизвестный профиль: ${PROFILE}" + exit 1 + ;; + esac + export WINEDEBUG="${WINEDEBUG:--all}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --bottle) BOTTLE_NAME="$2"; shift 2 ;; + --bottle-id) BOTTLE_ID="$2"; shift 2 ;; + --gog-bottle) GOG_BOTTLE_MODE=1; shift ;; + --profile) PROFILE="$2"; shift 2 ;; + --background) BACKGROUND=1; shift ;; + --log) LOG="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + -*) echo "Неизвестная опция: $1"; usage; exit 1 ;; + *) EXE_ARG="$1"; shift ;; + esac +done + +[[ -n "${EXE_ARG}" ]] || { usage; exit 1; } + +resolve_bottle_dir +WINE64="${WHISKY_LIBS}/Libraries/Wine/bin/wine64" +WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib" +[[ -x "${WINE64}" ]] || { echo "❌ Нет Wine: install-whiskywine-manual.sh"; exit 1; } +[[ -d "${BOTTLE_DIR}/drive_c" ]] || { echo "❌ Нет drive_c в ${BOTTLE_DIR}"; exit 1; } + +WIN_EXE="$(to_win_path "${EXE_ARG}")" +apply_profile + +echo "Bottle: ${BOTTLE_DIR##*/}" +echo "Профиль: ${PROFILE}" +echo "Запуск: ${WIN_EXE}" + +if [[ "${BACKGROUND}" -eq 1 ]]; then + LOG="${LOG:-/tmp/wine-exe-$(basename "${BOTTLE_DIR}").log}" + : >"${LOG}" + nohup "${WINE64}" "${WIN_EXE}" >>"${LOG}" 2>&1 & + echo "Фон (PID $!), лог: ${LOG}" + exit 0 +fi + +exec "${WINE64}" "${WIN_EXE}" diff --git a/scripts/games/setup-gog-bottle.sh b/scripts/games/setup-gog-bottle.sh new file mode 100755 index 0000000..7b5f101 --- /dev/null +++ b/scripts/games/setup-gog-bottle.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec "$(cd "$(dirname "$0")" && pwd)/gog/setup-bottle.sh" "$@" diff --git a/scripts/games/setup-wine-bottle.sh b/scripts/games/setup-wine-bottle.sh new file mode 100755 index 0000000..4ba9a9d --- /dev/null +++ b/scripts/games/setup-wine-bottle.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# HoMM / Steam bottle (HoMM-OldenEra): шрифты + сеть Wine для Steam UI. +# GOG web-stub: scripts/games/gog/ — отдельный bottle, без Steam. +# См. docs/games/WINE_MAC_RUNBOOK.md +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" +if [[ -z "${BOTTLE_DIR:-}" ]]; then + echo "❌ config.sh не задал BOTTLE_DIR (файл пустой или повреждён?)" + exit 1 +fi +if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then + echo "❌ Нет bottle drive_c: ${BOTTLE_DIR}" + echo " Откройте Whisky → bottle «${BOTTLE_NAME:-HoMM-OldenEra}» и дождитесь создания префикса." + exit 1 +fi +# shellcheck source=wine-fonts.sh +source "${SCRIPT_DIR}/wine-fonts.sh" + +WINE_SETUP_VERSION="3" +WINE_SETUP_MARKER="${BOTTLE_DIR}/.wine_mac_setup_v${WINE_SETUP_VERSION}" + +cmd_wine_check() { + if [[ ! -x "${WINE64}" ]]; then + echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh" + exit 1 + fi +} + +cmd_install_fonts_winetricks() { + [[ "${WINE_SETUP_WINETRICKS:-0}" == "1" ]] || return 0 + if ! command -v winetricks >/dev/null 2>&1; then + echo "winetricks: brew install winetricks, затем WINE_SETUP_WINETRICKS=1" + return 0 + fi + export WINEPREFIX="${BOTTLE_DIR}" + export WINE="${WINE64}" + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + export WINEDEBUG=-all + echo "winetricks liberation (до 3 мин)…" + if command -v gtimeout >/dev/null 2>&1; then + gtimeout 180 winetricks -q liberation 2>&1 | tail -5 || true + elif command -v timeout >/dev/null 2>&1; then + timeout 180 winetricks -q liberation 2>&1 | tail -5 || true + else + winetricks -q liberation 2>&1 | tail -5 || true + fi + homm_wine_register_fonts_registry +} + +cmd_wine_network() { + export WINEPREFIX="${BOTTLE_DIR}" + "${WINE64}" reg add 'HKCU\Software\Wine\Network' /v DnsIPv6Enabled /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true + "${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \ + /v DisableIPv6 /t REG_DWORD /d 1 /f >/dev/null 2>&1 || true + "${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \ + /v ProxyEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true + echo "Wine: DnsIPv6Enabled=0, ProxyEnable=0" +} + +main() { + local force=0 fonts_only=0 + for arg in "$@"; do + case "${arg}" in + --force) force=1 ;; + --fonts-only) fonts_only=1 ;; + esac + done + + cmd_wine_check + + if [[ "${fonts_only}" -eq 1 ]]; then + homm_wine_ensure_fonts 1 + exit 0 + fi + + if [[ -f "${WINE_SETUP_MARKER}" && "${force}" -eq 0 ]]; then + echo "Bottle подготовлен: ${WINE_SETUP_MARKER}" + echo "Обновить только шрифты: bash ${SCRIPT_DIR}/setup-wine-bottle.sh --fonts-only" + echo "Полный повтор: bash ${SCRIPT_DIR}/setup-wine-bottle.sh --force" + exit 0 + fi + + echo "Подготовка bottle «${BOTTLE_NAME}» (v${WINE_SETUP_VERSION})…" + homm_wine_ensure_fonts 1 + cmd_install_fonts_winetricks + homm_wine_ensure_fonts 0 + cmd_wine_network + date -u +"%Y-%m-%dT%H:%M:%SZ" >"${WINE_SETUP_MARKER}" + echo "" + echo "OK. Steam/HoMM: bash ${SCRIPT_DIR}/start.sh steam-ui" + echo "GOG (другой bottle): bash ${SCRIPT_DIR}/gog/README.md" +} + +main "$@" diff --git a/scripts/games/start.sh b/scripts/games/start.sh new file mode 100755 index 0000000..d223bcc --- /dev/null +++ b/scripts/games/start.sh @@ -0,0 +1,281 @@ +#!/usr/bin/env bash +# HoMM Olden Era / Steam / GOG в bottle Whisky. См. docs/games/WINE_APPS_LAUNCH_RUNBOOK.md +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=config.sh +source "${SCRIPT_DIR}/config.sh" + +HOMM_DISK_MIN_GB="${HOMM_DISK_MIN_GB:-3}" +HOMM_DISK_WARN_PCT="${HOMM_DISK_WARN_PCT:-10}" + +usage() { + cat < + + play — игра (DXVK + Unity windowed; Steam Logged On) + game — то же, явный запуск exe + steam-ui — Steam с окном (CEF + шрифты) + steam — Steam -silent (без UI) + reset — wineboot -k, убить Steam/GOG/игру в bottle + dxvk — установить DXVK-macOS в bottle + disk — свободное место на диске + saves — открыть папку сейвов в Finder + fix-steam-ui — шрифты + dwrite для пустого текста Steam + fix-gog-auth — сеть Wine для GOG login + diagnose — общая диагностика (mouse) + diagnose-steam-ui + diagnose-saves + diagnose-gog-auth + input — WineBus SDL (мышь/геймпад) + gog-install — GOG Galaxy 2.0 в bottle + gog — запустить GOG Galaxy + gog-restart — убить GOG и запустить снова + gog-bottle / gog-use-bottle / gog-setup / gog-corefonts / gog-game-install / gog-reset + — GOG в отдельном bottle (см. scripts/games/gog/README.md) + wine-setup — HoMM/Steam bottle (не GOG) + whisky — открыть Whisky.app + help — эта справка (-h / --help) +EOF +} + +cmd_wine_check() { + if [[ ! -x "${WINE64}" ]]; then + echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh" + exit 1 + fi +} + +cmd_disk_guard() { + local min_kb free_kb pct + min_kb=$((HOMM_DISK_MIN_GB * 1024 * 1024)) + free_kb="$(homm_disk_free_kb)" + pct="$(homm_disk_free_pct)" + if [[ "${free_kb}" -lt "${min_kb}" ]]; then + echo "❌ Мало места (<${HOMM_DISK_MIN_GB} GB свободно на Data)." + return 1 + fi + if [[ "${pct}" -lt "${HOMM_DISK_WARN_PCT}" ]]; then + echo "⚠️ Диск заполнен ~${pct}% свободно — риск зависаний CEF/Wine." + fi + return 0 +} + +cmd_disk() { + df -h "$(homm_data_volume_path)" | tail -1 + echo "Свободно: $(homm_disk_free_pct)% ($(homm_disk_free_kb) KB)" +} + +homm_steam_cef_args_array() { + local _IFS=$IFS + IFS=' ' + # shellcheck disable=SC2206 + local -a _a=(${HOMM_STEAM_CEF_ARGS}) + IFS=$_IFS + printf '%s\n' "${_a[@]}" +} + +homm_unity_args_array() { + local -a args=() + local w h + if [[ "${HOMM_UNITY_WINDOWED:-1}" == "1" ]]; then + IFS='x' read -r w h <<< "${HOMM_VDESKTOP:-1920x1080}" + args+=(-screen-fullscreen 0 -screen-width="${w}" -screen-height="${h}") + fi + if [[ -n "${HOMM_UNITY_EXTRA:-}" ]]; then + local _IFS=$IFS + IFS=' ' + # shellcheck disable=SC2206 + local -a extra=(${HOMM_UNITY_EXTRA}) + IFS=$_IFS + args+=("${extra[@]}") + fi + if [[ ${#args[@]} -eq 0 ]]; then + return 0 + fi + printf '%s\n' "${args[@]}" +} + +cmd_reset() { + cmd_wine_check + export_wine_env + echo "wineboot -k…" + "${WINE64}" wineboot -k 2>/dev/null || true + pkill -f 'HeroesOldenEra\.exe' 2>/dev/null || true + pkill -f 'Steam\.exe' 2>/dev/null || true + pkill -f 'steamwebhelper' 2>/dev/null || true + pkill -f 'SteamService' 2>/dev/null || true + pkill -f 'GalaxyClient' 2>/dev/null || true + pkill -f 'GOG_Galaxy' 2>/dev/null || true + pkill -f 'GalaxyWebInstaller' 2>/dev/null || true + pkill -f 'explorer.exe /desktop' 2>/dev/null || true + sleep 1 + echo "OK: процессы bottle остановлены." +} + +cmd_steam_ui() { + local -a cef_args=() + cmd_wine_check + cmd_disk_guard || exit 1 + homm_kill_mac_steam || true + homm_warn_mac_steam || true + export_wine_env_steam + mapfile -t cef_args < <(homm_steam_cef_args_array) + echo "Запуск Steam UI (Cmd+Tab → wine64 / Steam)…" + if [[ "${HOMM_STEAM_VDESKTOP:-0}" == "1" ]]; then + local w h + IFS='x' read -r w h <<< "${HOMM_VDESKTOP:-1920x1080}" + exec "${WINE64}" explorer "/desktop=SteamUI,${w}x${h}" "${STEAM_EXE}" "${cef_args[@]}" + else + exec "${WINE64}" "${STEAM_EXE}" "${cef_args[@]}" + fi +} + +cmd_steam() { + cmd_wine_check + export_wine_env_steam + echo "Steam -silent…" + exec "${WINE64}" "${STEAM_EXE}" -silent +} + +cmd_game() { + local -a unity=() + cmd_wine_check + cmd_disk_guard || exit 1 + if [[ ! -f "${HOMM_EXE}" ]]; then + echo "Нет ${HOMM_EXE} — установите Olden Era в Steam UI." + exit 1 + fi + if [[ "${HOMM_GAME_FORCE:-0}" != "1" ]] && ! homm_steam_ready_for_game; then + echo "❌ Steam не Logged On или не запущен." + echo " bash ${SCRIPT_DIR}/start.sh steam-ui → login → play" + exit 1 + fi + mapfile -t unity < <(homm_unity_args_array) + export_wine_env_game + echo "Запуск HoMM (DXVK + windowed)…" + if [[ "${HOMM_USE_WINEVDESKTOP:-1}" == "1" && "${HOMM_LAUNCH_DIRECT:-0}" != "1" ]]; then + local w h + IFS='x' read -r w h <<< "${HOMM_VDESKTOP:-1920x1080}" + exec "${WINE64}" explorer "/desktop=HoMM,${w}x${h}" "${HOMM_EXE}" "${unity[@]}" + else + exec "${WINE64}" "${HOMM_EXE}" "${unity[@]}" + fi +} + +cmd_play() { + if homm_steam_ready_for_game; then + cmd_game + else + homm_warn_mac_steam || true + echo "Нужен Steam Logged On. Сначала steam-ui, затем снова play." + if ! homm_wine_steam_running; then + cmd_steam_ui + else + exit 1 + fi + fi +} + +cmd_saves() { + local sp + sp="$(find "${BOTTLE_DIR}/drive_c/users" -type d -path '*/Unfrozen/HeroesOldenEra/users/*/saves/singleplayer' 2>/dev/null | head -1)" + if [[ -z "${sp}" ]]; then + echo "Папка сейвов не найдена — запустите игру хотя бы раз." + exit 1 + fi + open "${sp}" + echo "${sp}" +} + +cmd_gog() { + local gog_exe win_exe + local -a cef_args=() + cmd_wine_check + cmd_disk_guard || exit 1 + if ! gog_exe="$(homm_gog_exe)"; then + echo "GOG не установлен: bash ${SCRIPT_DIR}/start.sh gog-install" + exit 1 + fi + if homm_wine_gog_running; then + echo "GOG уже запущен (Cmd+Tab → GalaxyClient)." + exit 0 + fi + homm_kill_mac_steam || true + export_wine_env_gog + mapfile -t cef_args < <(homm_gog_cef_args_array) + if [[ -x "${WINEPATH_BIN}" ]]; then + win_exe="$("${WINEPATH_BIN}" -w "${gog_exe}")" + else + win_exe='C:\Program Files (x86)\GOG Galaxy\GalaxyClient.exe' + fi + echo "Запуск GOG Galaxy…" + nohup "${WINE64}" "${win_exe}" "${cef_args[@]}" >/tmp/gog-launch.log 2>&1 & + sleep 3 + if homm_wine_gog_running; then + echo "OK: Cmd+Tab → GalaxyClient" + else + echo "Не стартовал — tail /tmp/gog-launch.log" + tail -10 /tmp/gog-launch.log 2>/dev/null || true + exit 1 + fi +} + +cmd_gog_restart() { + pkill -f 'GalaxyClient' 2>/dev/null || true + pkill -f 'GOG Galaxy' 2>/dev/null || true + sleep 2 + cmd_gog +} + +cmd_whisky() { + open -a Whisky 2>/dev/null || open -a "Whisky.app" 2>/dev/null || { + echo "Установите Whisky (franke) или откройте bottle вручную." + exit 1 + } +} + +main() { + local cmd="${1:-steam}" + shift || true + case "${cmd}" in + -h|--help|help) usage; exit 0 ;; + play) cmd_play "$@" ;; + game) cmd_game "$@" ;; + steam-ui) cmd_steam_ui "$@" ;; + steam) cmd_steam "$@" ;; + reset) cmd_reset "$@" ;; + dxvk) exec bash "${SCRIPT_DIR}/install-dxvk-macos.sh" "$@" ;; + disk) cmd_disk "$@" ;; + saves) cmd_saves "$@" ;; + fix-steam-ui) exec bash "${SCRIPT_DIR}/fix-homm-steam-ui.sh" "$@" ;; + fix-gog-auth) exec bash "${SCRIPT_DIR}/fix-gog-auth.sh" "$@" ;; + diagnose) exec bash "${SCRIPT_DIR}/diagnose-homm-mouse.sh" "$@" ;; + diagnose-steam-ui) exec bash "${SCRIPT_DIR}/diagnose-homm-steam-ui.sh" "$@" ;; + diagnose-saves) exec bash "${SCRIPT_DIR}/diagnose-homm-saves.sh" "$@" ;; + diagnose-gog-auth) exec bash "${SCRIPT_DIR}/diagnose-gog-auth.sh" "$@" ;; + input) exec bash "${SCRIPT_DIR}/fix-homm-wine-input.sh" "$@" ;; + gog-install) exec bash "${SCRIPT_DIR}/install-gog-galaxy.sh" "$@" ;; + gog) cmd_gog "$@" ;; + gog-restart) cmd_gog_restart "$@" ;; + gog-bottle) exec bash "${SCRIPT_DIR}/gog/list-bottles.sh" "$@" ;; + gog-use-bottle) shift; exec bash "${SCRIPT_DIR}/gog/use-bottle.sh" "$@" ;; + gog-setup) exec bash "${SCRIPT_DIR}/gog/setup-bottle.sh" "$@" ;; + gog-install-all) shift; exec bash "${SCRIPT_DIR}/gog/install-all.sh" "$@" ;; + gog-gdiplus) exec bash "${SCRIPT_DIR}/gog/install-gdiplus.sh" "$@" ;; + gog-create-bottle) exec bash "${SCRIPT_DIR}/gog/create-bottle.sh" "$@" ;; + gog-corefonts) exec bash "${SCRIPT_DIR}/gog/install-corefonts.sh" "$@" ;; + gog-game-install) exec bash "${SCRIPT_DIR}/gog/run-web-installer.sh" "$@" ;; + gog-reset) exec bash "${SCRIPT_DIR}/gog/reset.sh" "$@" ;; + wine-setup) exec bash "${SCRIPT_DIR}/setup-wine-bottle.sh" "$@" ;; + wine-corefonts) echo "Для GOG: bash ${SCRIPT_DIR}/gog/install-corefonts.sh"; exit 1 ;; + whisky) cmd_whisky "$@" ;; + "") cmd_steam "$@" ;; + *) + echo "Неизвестная команда: ${cmd}" + usage + exit 1 + ;; + esac +} + +main "$@" diff --git a/scripts/games/wine-fonts.sh b/scripts/games/wine-fonts.sh new file mode 100644 index 0000000..0896858 --- /dev/null +++ b/scripts/games/wine-fonts.sh @@ -0,0 +1,104 @@ +# shellcheck shell=bash +# Шрифты для HoMM/Steam bottle. GOG: scripts/games/gog/wine-fonts.sh (без Steam). + +homm_wine_install_fonts_mac() { + local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts" + local steam="${STEAM_DIR}" + mkdir -p "${fonts}" + for f in /System/Library/Fonts/Supplemental/{Arial,Tahoma,Verdana,"Times New Roman","Courier New"}.ttf; do + [[ -f "${f}" ]] && cp -n "${f}" "${fonts}/" 2>/dev/null || true + done + cp -n "${fonts}/Arial.ttf" "${fonts}/arial.ttf" 2>/dev/null || true + cp -n "${fonts}/Arial.ttf" "${fonts}/micross.ttf" 2>/dev/null || true + cp -n "${fonts}/Arial.ttf" "${fonts}/Microsoft Sans Serif.ttf" 2>/dev/null || true + cp -n "${fonts}/Tahoma.ttf" "${fonts}/tahoma.ttf" 2>/dev/null || true + [[ -f "${fonts}/liberationsans-regular.ttf" ]] || { + if command -v winetricks >/dev/null 2>&1 && [[ "${WINE_SETUP_WINETRICKS:-0}" == "1" ]]; then + : + fi + } + if [[ "${WINE_FONTS_USE_STEAM:-1}" == "1" && -d "${steam}" ]]; then + cp -n "${steam}/resource/fonts/marlett.ttf" "${fonts}/" 2>/dev/null || true + cp -n "${steam}/clientui/fonts/"*.ttf "${fonts}/" 2>/dev/null || true + fi +} + +homm_wine_register_font_file() { + local reg_name="$1" + local ttf_file="$2" + export WINEPREFIX="${BOTTLE_DIR}" + "${WINE64}" reg add 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Fonts' \ + /v "${reg_name} (TrueType)" /t REG_SZ /d "${ttf_file}" /f >/dev/null 2>&1 || true +} + +homm_wine_register_all_fonts_dir() { + local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts" + local f base reg + for f in "${fonts}"/*; do + [[ -f "${f}" ]] || continue + [[ "${f}" == *.ttf || "${f}" == *.TTF ]] || continue + base="$(basename "${f}")" + reg="${base%.*}" + homm_wine_register_font_file "${reg}" "${base}" + done +} + +homm_wine_wineboot_fonts() { + export WINEPREFIX="${BOTTLE_DIR}" + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + "${WINE64}" wineboot -u 2>/dev/null || true +} + +homm_wine_register_fonts_registry() { + export WINEPREFIX="${BOTTLE_DIR}" + local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts" + + homm_wine_register_font_file 'Arial' 'arial.ttf' + homm_wine_register_font_file 'Tahoma' 'tahoma.ttf' + homm_wine_register_font_file 'Verdana' 'verdana.ttf' + homm_wine_register_font_file 'Times New Roman' 'Times New Roman.ttf' + homm_wine_register_font_file 'Courier New' 'Courier New.ttf' + homm_wine_register_font_file 'Microsoft Sans Serif' 'micross.ttf' + + [[ -f "${fonts}/liberationsans-regular.ttf" ]] && \ + homm_wine_register_font_file 'Liberation Sans' 'liberationsans-regular.ttf' + [[ -f "${fonts}/liberationsans-bold.ttf" ]] && \ + homm_wine_register_font_file 'Liberation Sans Bold' 'liberationsans-bold.ttf' + + # Substitutes — .NET GenericSansSerif / MS Shell Dlg + local sub='HKLM\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes' + "${WINE64}" reg add "${sub}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'MS Shell Dlg 2' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'MS Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'Segoe UI' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${sub}" /v 'MessageBox' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + + # Wine-internal mapping (libgdiplus / GenericSansSerif) + local rep='HKCU\Software\Wine\Fonts\Replacements' + "${WINE64}" reg add "${rep}" /v '@@MS_SHELLDLG' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${rep}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${rep}" /v 'MS Shell Dlg 2' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true + "${WINE64}" reg add "${rep}" /v 'Microsoft Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true + + "${WINE64}" reg add 'HKCU\Software\Valve\Steam' /v DWriteEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true + homm_wine_register_all_fonts_dir +} + +homm_wine_ensure_fonts() { + local rebuild="${1:-0}" + if [[ ! -x "${WINE64}" ]]; then + echo "Нет Wine64: ${WINE64}" + return 1 + fi + export WINEPREFIX="${BOTTLE_DIR}" + export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}" + homm_wine_install_fonts_mac + homm_wine_register_fonts_registry + if [[ "${rebuild}" == "1" ]]; then + echo "wineboot -u (перечитать шрифты, ~30–60 с)…" + homm_wine_wineboot_fonts + fi + local n + n="$(ls "${BOTTLE_DIR}/drive_c/windows/Fonts" 2>/dev/null | wc -l | tr -d ' ')" + echo "Шрифты Wine: ${n} файлов, реестр FontSubstitutes + Wine\\Fonts\\Replacements" +} diff --git a/scripts/vpn/exit-via-local.sh b/scripts/vpn/exit-via-local.sh new file mode 100755 index 0000000..c02ebdf --- /dev/null +++ b/scripts/vpn/exit-via-local.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# См. docs/connectivity/vpn/SSH_EXIT_MODES.md +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-exit-mode.sh" local "$@" diff --git a/scripts/vpn/exit-via-ru-do.sh b/scripts/vpn/exit-via-ru-do.sh new file mode 100755 index 0000000..f24bf6e --- /dev/null +++ b/scripts/vpn/exit-via-ru-do.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# См. docs/connectivity/vpn/SSH_EXIT_MODES.md +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-exit-mode.sh" ru-do "$@" diff --git a/scripts/vpn/exit-via-ru.sh b/scripts/vpn/exit-via-ru.sh new file mode 100755 index 0000000..c91074c --- /dev/null +++ b/scripts/vpn/exit-via-ru.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# См. docs/connectivity/vpn/SSH_EXIT_MODES.md +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-exit-mode.sh" ru "$@" diff --git a/scripts/vpn/ssh-exit-mode.sh b/scripts/vpn/ssh-exit-mode.sh new file mode 100755 index 0000000..c169e55 --- /dev/null +++ b/scripts/vpn/ssh-exit-mode.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# Переключение режима выхода в интернет через SSH SOCKS (macOS / Linux). +# См. docs/connectivity/vpn/SSH_EXIT_MODES.md + +set -e + +SSH_EXIT_MAIN_PORT="${SSH_EXIT_MAIN_PORT:-10809}" +SSH_EXIT_STOP_PORTS="${SSH_EXIT_STOP_PORTS:-10809 1081 1082}" +SSH_EXIT_RU_HOST="${SSH_EXIT_RU_HOST:-hsites-ahau}" +SSH_SSH_STABLE_OPTS="-o TCPKeepAlive=yes -o IPQoS=throughput -o ServerAliveInterval=60 -o ServerAliveCountMax=4" +SSH_SSH_RU_OPTS="-o TCPKeepAlive=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=6" + +usage() { + cat </dev/null || true + pkill -f "ssh.*-D ${p}\$" 2>/dev/null || true + done + sleep 0.4 + for p in $SSH_EXIT_STOP_PORTS; do + if command -v lsof >/dev/null 2>&1; then + # shellcheck disable=SC2046 + lsof -ti ":$p" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true + fi + done +} + +cmd_local() { + stop_dynamic_tunnels + echo "SSH SOCKS на портах ($SSH_EXIT_STOP_PORTS) остановлены. Выход в интернет — с локального IP (если в приложениях отключён SOCKS-прокси)." +} + +ensure_port_free() { + if command -v lsof >/dev/null 2>&1; then + if lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then + echo "Порт $SSH_EXIT_MAIN_PORT занят — освобождаю..." + pkill -f "ssh.*-D ${SSH_EXIT_MAIN_PORT} " 2>/dev/null || true + sleep 0.4 + # shellcheck disable=SC2046 + lsof -ti ":$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true + sleep 0.3 + fi + fi + if lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then + echo "Ошибка: порт $SSH_EXIT_MAIN_PORT всё ещё занят. Закройте процесс вручную или: $0 local" + exit 1 + fi +} + +cmd_ru() { + ensure_port_free + # shellcheck disable=SC2086 + ssh -D "$SSH_EXIT_MAIN_PORT" -f -N $SSH_SSH_RU_OPTS "$SSH_EXIT_RU_HOST" + echo "SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → $SSH_EXIT_RU_HOST (выход с IP RU-прокси)." +} + +cmd_ru_do() { + ensure_port_free + if ! ssh -o BatchMode=yes -o ConnectTimeout=6 -o ConnectionAttempts=1 "$SSH_EXIT_RU_HOST" "exit 0" 2>/dev/null; then + echo "Ошибка: $SSH_EXIT_RU_HOST недоступен. Режим ru-do только Mac→RU→DO; прямой Mac→DO не используется." >&2 + exit 1 + fi + # shellcheck disable=SC2086 + ssh -D "$SSH_EXIT_MAIN_PORT" -f -N $SSH_SSH_STABLE_OPTS telegram-socks-via-do + echo "SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → $SSH_EXIT_RU_HOST → DO (telegram-socks-via-do). Снаружи виден IP DO." +} + +cmd_status() { + local p any=0 + if ! command -v lsof >/dev/null 2>&1; then + echo "lsof не найден — статус портов недоступен." + exit 1 + fi + for p in $SSH_EXIT_STOP_PORTS; do + if lsof -i :"$p" -sTCP:LISTEN -t >/dev/null 2>&1; then + echo "--- :$p ---" + lsof -i :"$p" -sTCP:LISTEN + any=1 + fi + done + if [ "$any" = 0 ]; then + echo "Ни один из портов не слушает: $SSH_EXIT_STOP_PORTS" + fi +} + +cmd_verify() { + echo "Без прокси (локальный выход):" + curl -sS --max-time 12 https://ipinfo.io/ip 2>/dev/null || echo "(ошибка curl)" + echo "" + echo "Через SOCKS socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT:" + curl -sS --max-time 12 -x "socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT" https://ipinfo.io/ip 2>/dev/null || echo "(нет ответа — туннель не поднят или порт другой)" + echo "" + echo "Кратко через SOCKS (ip + страна):" + curl -sS --max-time 12 -x "socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT" https://ipinfo.io/json 2>/dev/null \ + | sed -n 's/.*"ip": *"\([^"]*\)".*/IP: \1/p; s/.*"country": *"\([^"]*\)".*/country: \1/p' 2>/dev/null | head -4 || true +} + +case "${1:-}" in + local|off|stop) cmd_local ;; + ru) cmd_ru ;; + ru-do|ru_do) cmd_ru_do ;; + status) cmd_status ;; + verify) cmd_verify ;; + -h|--help|help) usage ;; + "") + usage + exit 1 + ;; + *) + echo "Неизвестная команда: $1" + usage + exit 1 + ;; +esac