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 <cursoragent@cursor.com>
This commit is contained in:
ahauimix
2026-06-01 08:12:19 +03:00
parent a39ef89125
commit 1938b7c743
135 changed files with 9933 additions and 388 deletions

3
.gitignore vendored
View File

@@ -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

164
docs/apps/anytype/README.md Normal file
View File

@@ -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)

View File

@@ -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*

View File

@@ -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 }
}

View File

@@ -0,0 +1,122 @@
# Emergency: сеть после смены хотспота (macOS)
Симптом: **отключили Mac от WiFi / переподключились к другому хотспоту — «интернета нет», помогает только перезагрузка.**
Документ описывает **проверенную на машине причину** в вашей конфигурации, **ручное восстановление без перезагрузки** и **скрипт** из репозитория.
---
## Причина (точная по конфигурации)
На macOS в **Системных настройках → Сеть → WiFi → Подробнее → Прокси** часто включён **автоматический прокси (PAC)** с URL вида **`http://localhost:1089/proxy.pac`** (см. [PROXY_GUIDE.md](./PROXY_GUIDE.md) → раздел про системный PAC).
Проверка на рабочей машине (2026): **PAC для сервиса WiFi включён (Enabled: Yes)**, при этом **на порту 1089 нет процесса**, который отдаёт `proxy.pac` — соединение к `127.0.0.1:1089` отклоняется.
В таком состоянии:
1. Система и часть приложений **сначала** должны получить PAC с localhost; при недоступном URL поведение **нестабильно** (часть трафика «в никуда», таймауты).
2. При **смене хотспота** меняются шлюз, DNS, часто появляется **captive portal**; страница входа может **не открыться**, пока PAC мешает или DNS закеширован со старой сети.
3. **Перезагрузка** «лечит», потому что заново поднимаются **configd**, **mDNSResponder**, порядок инициализации WiFi и иногда снова стартует локальный PACсервер (Clash / Surge / свой сервис на 1089).
Дополнительные усилители сбоя (проверять при `status`):
- **Системный SOCKS** на `127.0.0.1:1080` (Shadowsocks и т.д.) — если включён, а клиент не слушает порт, трафик через системный прокси тоже падает.
- **VPN / туннели** с политикой «весь трафик» — после смены сети маршруты могут остаться битые до отключения VPN.
**SOCKS Telegram (1081) и SSHтуннели сами по себе не отключают WiFi** — они приложенческие. Проблема класса «всё мёртво до ребута» у вас с высокой вероятностью — **системный PAC/SOCKS на localhost без живого сервиса + кэш DNS / состояние WiFi**.
---
## Быстро: один скрипт (рекомендуется)
Из корня репозитория `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 на WiFi**, по возможности **сбрасывает DNS** и **кратко перезапускает WiFi** и DHCP.
Если скрипт пишет, что нет `sudo` без пароля — выполните вручную команды из раздела «Вручную» ниже.
После появления интернета, если вам снова нужен PAC:
```bash
./docs/connectivity/scripts/macos-pac-on.sh
```
Только **после** того как процесс на **1089** реально запущен и отдаёт `proxy.pac`.
---
## Вручную (без скрипта)
### 1. Отключить автоматический PAC
**GUI:** Системные настройки → Сеть → WiFi → Подробнее → Прокси → снять **«Автоматическая настройка прокси (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 и «пнуть» WiFi
Узнать интерфейс WiFi (часто `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` сеть всё ещё мёртвая — проверьте аппаратный тумблер WiFi, другое SSID, и логи: Консоль → `Wi-Fi` / `configd`.*

View File

@@ -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` и лог выше.*

View File

@@ -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`), **чистого WiFi без прокси** или диагностики удобно **временно выключить** PAC и потом снова включить.
**Важно:** если PAC **включён**, а по выбранному URL **ничего не слушает** (например, порт **1089** пуст), macOS и приложения ведут себя непредсказуемо; при **смене хотспота** часто кажется, что «сеть мёртвая до перезагрузки». Это не баг WiFi как такового, а **нерабочий 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`.
Пример только для WiFi: `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

View File

@@ -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 на WiFi, 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`) |

View File

@@ -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 (шаги 13). Прокси в 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, логи, порты.

View File

@@ -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. |

View File

@@ -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`

View File

@@ -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`.
## Конфликты портов

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!-- Скрипт: ~/Library/Application Support/hunab/telegram-socks-launch.sh. Только Mac→RU→DO (telegram-socks-via-do). -->
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.hunab.telegram-socks-via-do</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>exec /bin/bash "$HOME/Library/Application Support/hunab/telegram-socks-launch.sh"</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>15</integer>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65536</integer>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65536</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/telegram-socks-tunnel-via-do.log</string>
<key>StandardErrorPath</key>
<string>/tmp/telegram-socks-tunnel-via-do.log</string>
</dict>
</plist>

View File

@@ -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"

View File

@@ -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

View File

@@ -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)"

View File

@@ -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"}}`
}
}

View File

@@ -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:

View File

@@ -0,0 +1,2 @@
# Скопируйте в .env рядом с docker-compose.yml и заполните.
POSTGRES_PASSWORD=сгенерируйте_длинный_случайный_пароль

View File

@@ -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 ""

View File

@@ -0,0 +1,105 @@
#!/bin/sh
# Сброс сетевого стека macOS после смены хотспота / «интернет не поднимается без перезагрузки».
# Типичная причина в связке с docs/connectivity: системный PAC на localhost:1089 при мёртвом/ещё не
# поднятом PAC-сервере + кэш DNS / WiFi. См. 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 — вручную: Системные настройки → Сеть → WiFi → Подробнее → Прокси → снять «Автоматическая настройка прокси»" >&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) Краткое выключение/включение WiFi радиомодуля ($dev)..."
networksetup -setairportpower "$dev" off 2>/dev/null || true
sleep 2
networksetup -setairportpower "$dev" on 2>/dev/null || true
else
echo "45) Не удалось определить WiFi device — пропуск DHCP/airport power. Вручную выключите/включите WiFi в меню."
fi
echo "=== Готово. Проверка: ==="
"$0" status
echo ""
echo "Если интернет всё ещё нет: откройте хотспот в браузере (captive portal), затем при необходимости снова включите PAC:"
echo " ./docs/connectivity/scripts/macos-pac-on.sh"
;;
*)
usage
;;
esac

View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec "$(dirname "$0")/macos-system-pac.sh" off

View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec "$(dirname "$0")/macos-system-pac.sh" on

View File

@@ -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

View File

@@ -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

View File

@@ -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 "$@"

View File

@@ -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

View File

@@ -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` - Автоматическое исправление

View File

@@ -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*

View File

@@ -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 |

View File

@@ -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).

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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`.

View File

@@ -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:1505:18 | `will continue`, login form, foreground | **рабочий запуск процесса** |
| 05:1905:20 | `DeelevateStrategy` fatal, exit **-31** | запуск **без** `/deelevated` (vdesktop) |
| 05:21+ | `/deelevated` в Init, login form | фикс в `config.sh` |
| 05:2905: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)

View File

@@ -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`). На диске мало места (~1012 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% — сначала освободить 12 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 <UUID> 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 2829.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)

297
docs/games/JA3_RUNBOOK.md Normal file
View File

@@ -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 (24 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 (2829.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 24 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 «залип» 24 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)

0
docs/games/README.md Normal file
View File

View File

@@ -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) §24 |
| **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_<ID>/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-UUID>/.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_<ID>/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`.
- Держать **≥1520 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 <UUID>``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_<random>)
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 <UUID>` |
| 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:0804: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:0804: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** — 23 раза, пауза **1530 с** между попытками.
- Если не коннектится — **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 23× или 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*

View File

@@ -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-твик».

View File

@@ -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=<UUID>` или `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/<UUID>"
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/<UUID>/` |
| Кэш установщиков | `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-<app>.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 (ежедневно)
Краткая выжимка; детали сейвов/графики — прежние §45 методички.
### Однократно
```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_<ID>/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"
```

View File

@@ -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`) часто **таймаут 1520 с** → логин в клиенте **ненадёжен**. Обходы ниже в §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 <UUID> 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 <UUID>` |
Лог: `/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="<bottle>"
export WINEMSYNC=1
export WINEESYNC=1
export DYLD_LIBRARY_PATH="<Whisky>/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/`.

View File

@@ -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/<branch>` только после успешного пуша и только на кончик текущей ветки (все «ждущие пуша» коммиты при этом уже на сервере).
- Перед пушем можно проверить: `--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/
```bash
# Ветки
ssh hunab-prod "docker exec gitea git --git-dir=/data/git/repositories/hunabgit/hunabapp.git branch -a | wc -l"
ssh hunab-prod "docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git branch -a | wc -l"
# Размер
ssh hunab-prod "docker exec gitea du -sh /data/git/repositories/hunabgit/hunabapp.git"
ssh hunab-prod "docker exec gitea du -sh /data/git/repositories/hunabgit/local_machine.git"
# Последний коммит
ssh hunab-prod "docker exec gitea git --git-dir=/data/git/repositories/hunabgit/hunabapp.git log -1 --pretty=format:'%H %s'"
ssh hunab-prod "docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git log -1 --pretty=format:'%H %s'"
```
---
@@ -939,10 +883,10 @@ ssh hunab-prod "docker exec gitea /usr/local/bin/gitea admin regenerate hooks --
**Проверка:**
```bash
# Проверка прав на hooks
ssh hunab-prod "docker exec gitea ls -la /data/git/repositories/hunabgit/hunabapp.git/hooks/pre-receive"
ssh hunab-prod "docker exec gitea ls -la /data/git/repositories/hunabgit/local_machine.git/hooks/pre-receive"
# Проверка содержимого hooks
ssh hunab-prod "docker exec gitea cat /data/git/repositories/hunabgit/hunabapp.git/hooks/pre-receive.d/gitea"
ssh hunab-prod "docker exec gitea cat /data/git/repositories/hunabgit/local_machine.git/hooks/pre-receive.d/gitea"
```
**Дополнительно:** Если проблема сохраняется, проверьте:
@@ -1025,7 +969,7 @@ ssh hunab-prod "
2. ✅ **PostgreSQL настроен** (отдельная БД hunabgit)
3. ✅ **SSL/TLS настроен** (Let's Encrypt, A rating)
4. ✅ **Nginx reverse proxy** (с оптимизацией)
5. ✅ **Репозиторий hunabapp мигрирован** (556.8M, 23 ветки, все файлы доступны)
5. ✅ **Репозиторий local_machine** (и при необходимости другие репо) доступен на Gitea
6. ✅ **DNS настроен** (gitea.hunab.app → 209.38.32.21)
7. ✅ **Веб-интерфейс работает** (файлы отображаются корректно)
8. ✅ **API полностью функционален** (все endpoints работают)
@@ -1097,20 +1041,21 @@ ssh hunab-prod "
## 📚 Полезные команды
**Для local_machine:**
```bash
# Push репозитория (РЕКОМЕНДУЕТСЯ)
bash scripts/deployment/gitea/git-push-gitea.sh [branch]
cd /Users/eternal/code/local_machine
git push origin main
# или SSH (если HTTPS таймаутит): git remote set-url origin ssh://git@gitea.hunab.app:2223/hunabgit/local_machine.git
```
# 🇷🇺 ДЛЯ РФ: Push через прокси (ускоряет в 5-10 раз)
bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh [branch]
# Gitea Admin Commands
**Для админов сервера (Gitea Admin):**
```bash
docker exec gitea /usr/local/bin/gitea admin --help
docker exec gitea /usr/local/bin/gitea admin regenerate hooks
# Git операции в Gitea
docker exec gitea git --git-dir=/data/git/repositories/hunabgit/hunabapp.git branch -a
docker exec gitea git --git-dir=/data/git/repositories/hunabgit/hunabapp.git log --oneline -10
# Git операции в Gitea (репо local_machine)
docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git branch -a
docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git log --oneline -10
# PostgreSQL операции
docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -c "SELECT * FROM repository;"
@@ -1176,38 +1121,32 @@ bash scripts/deployment/mirror-all-repos-to-external.sh
- **Веб-интерфейс:** https://gitea.hunab.app
- **API:** https://gitea.hunab.app/api/v1/
- **Репозиторий:** https://gitea.hunab.app/hunabgit/hunabapp
- **Репозиторий local_machine:** https://gitea.hunab.app/hunabgit/local_machine
- **Быстрый старт local_machine:** [LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)
- **Документация Gitea:** https://docs.gitea.io/
- **SSL Report:** https://www.ssllabs.com/ssltest/analyze.html?d=gitea.hunab.app
- **Зеркалирование:** [Gitea Mirroring Guide](./GITEA_MIRRORING_GUIDE.md)
- **Миграция на зеркальный сервер:** [Gitea Migration Plan](./GITEA_MIGRATION_TO_MIRROR_SERVER.md)
- **Индекс документации:** [Gitea Index](./INDEX.md) - Перелинкованный индекс всех документов Gitea
- **Зеркалирование:** [Gitea Mirroring Guide](./GITEA_MIRRORING_GUIDE.md) (если есть)
- **Миграция на зеркальный сервер:** [Gitea Migration Plan](./GITEA_MIGRATION_TO_MIRROR_SERVER.md) (если есть)
---
## ✅ Текущий статус (2025-12-27)
## ✅ Текущий статус
### Репозиторий hunabapp
- ✅ **Размер:** 556.8M
- **Ветки:** 23 (dev, main, и другие)
- ✅ **Файлы:** Все файлы доступны через веб-интерфейс и API
- ✅ **Веб-интерфейс:** Полностью функционален
- ✅ **API:** Работает корректно
- ✅ **Git операции:** Клонирование, push, pull работают
### Репозиторий local_machine
- **URL:** https://gitea.hunab.app/hunabgit/local_machine
- **Ветка по умолчанию:** main
- **Пуш:** `git push origin main` или SSH (порт 2223) — см. [LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)
### Система
- ✅ Gitea контейнер работает стабильно
- ✅ PostgreSQL подключение стабильно
- ✅ SSL/TLS работает корректно
- ✅ Nginx reverse proxy настроен правильно
- ✅ DNS настроен (gitea.hunab.app → 209.38.32.21)
### Система (сервер Gitea)
- ✅ Gitea контейнер на 209.38.32.21
- ✅ PostgreSQL, SSL/TLS, Nginx
- ✅ DNS: gitea.hunab.app → 209.38.32.21
**Последнее обновление:** 2026-02-27
**Версия документа:** 1.4
**Статус:** ✅ Gitea полностью развернут и работает стабильно
**Изменения:**
- Добавлен резервный способ коммитов через временный скрипт (2026-02-16)
- Добавлен гайд по очистке истории Git от build-артефактов (2026-02-27)
**Последнее обновление:** 2026-03-14
**Версия документа:** 2.0 (адаптация под проект local_machine)
**Изменения:**
- Гайд переориентирован на репозиторий local_machine; примеры и команды приведены к этому проекту
- Добавлена ссылка на LOCAL_MACHINE_GITEA_SETUP.md для быстрого старта
---
@@ -1217,7 +1156,9 @@ bash scripts/deployment/mirror-all-repos-to-external.sh
**99% случаев:** Файлы уже в `.gitignore`, но Git их отслеживает (закоммичены до добавления в `.gitignore`)
**Решение:** ✅ **Простое удаление из tracking** (см. ниже) - БЕЗ переписывания истории!
**Решение:** ✅ **Простое удаление из tracking** (см. ниже) БЕЗ переписывания истории!
*Пример ниже — для репо с frontend (build-артефакты). В local_machine: убедиться, что путь в .gitignore, затем `git rm --cached -r путь/` и коммит.*
**1% случаев:** Нужно удалить файлы из ВСЕЙ истории (например, случайно закоммитили секреты)
@@ -1258,8 +1199,8 @@ git commit -m "chore(frontend): удаление dist-* build-артефакто
- Файлы были в .gitignore, но отслеживались Git (закоммичены до добавления в .gitignore)
- Build-артефакты генерируются при деплое и не должны храниться в репозитории"
# 5. Push в Gitea
bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh dev # 🇷🇺 ДЛЯ РФ
# 5. Push в Gitea (для local_machine: git push origin main или SSH)
bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh dev # в других репо; для local_machine: git push origin main
# 6. Проверка
git ls-files | grep -E "^frontend/dist-(zero-downtime|fast-no-image|via-proxy|cache-busting)/" | wc -l
@@ -1374,7 +1315,7 @@ brew install git-filter-repo
# pip3 install git-filter-repo
# Запуск (бэкап создаётся скриптом автоматически)
bash scripts/maintenance/git-history-cleanup-secrets.sh /path/to/hunabapp-dev
bash scripts/maintenance/git-history-cleanup-secrets.sh /path/to/local_machine
```
**Файлы:**
@@ -1387,7 +1328,7 @@ bash scripts/maintenance/git-history-cleanup-secrets.sh /path/to/hunabapp-dev
**Критично:** на production-сервере перед заменой репозитория в Gitea обязательно создать бэкап:
```bash
GITEA_REPO="/data/git/repositories/hunabgit/hunabapp.git"
GITEA_REPO="/data/git/repositories/hunabgit/local_machine.git"
ssh hunab-prod "docker exec gitea cp -r $GITEA_REPO ${GITEA_REPO}.backup-$(date +%Y%m%d)"
```
@@ -1611,7 +1552,7 @@ scp -i ~/.ssh/hunab_deploy_key /tmp/repo-clean.bundle hunab@209.38.32.21:/tmp/
ssh hunab-prod "cd /tmp && git clone --mirror /tmp/repo-clean.bundle repo-clean.git"
# 4. Бэкап текущего Gitea репозитория
GITEA_REPO="/data/git/repositories/hunabgit/hunabapp.git"
GITEA_REPO="/data/git/repositories/hunabgit/local_machine.git"
ssh hunab-prod "docker exec gitea cp -r $GITEA_REPO ${GITEA_REPO}.backup-$(date +%Y%m%d)"
# 5. Копировать очищенный репозиторий в контейнер
@@ -1625,7 +1566,7 @@ ssh hunab-prod "docker exec gitea sh -c '
cp -r /tmp/repo-clean.git/packed-refs . 2>/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 с описанием операции

View File

@@ -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

View File

@@ -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 <your_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}"

0
scripts/games/README.md Normal file
View File

2
scripts/games/cache/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

214
scripts/games/config.sh Executable file
View File

@@ -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}"
}

View File

@@ -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"

View File

@@ -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" "$@"

View File

@@ -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 в поддержку."

View File

@@ -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

View File

@@ -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"

75
scripts/games/fix-gog-auth.sh Executable file
View File

@@ -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 "$@"

View File

@@ -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 "$@"

View File

@@ -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

1
scripts/games/gog/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
bottle.env

View File

@@ -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 <UUID> 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 и общий префикс.

View File

@@ -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

View File

@@ -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 <<PY
import re
from datetime import datetime, timedelta
from pathlib import Path
hours = int("${HOURS}")
out = Path("${OUT}")
cutoff = datetime.now() - timedelta(hours=hours)
pat = re.compile(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})")
keys = (
"will continue", "will exit", "Second client", "Showing login", "foreground",
"Deelevate", "Fatal", "deelevated", "Reloaded without", "Command-line parameters",
"parent window", "eglInitialize",
)
logdir = Path("${GALOG}")
lines = [f"# GOG Galaxy timeline (last {hours}h)\n", f"cutoff_local={cutoff.isoformat()}\n", f"bottle={Path('${BOTTLE_DIR}').name}\n\n"]
for fn in sorted(logdir.glob("*.log")):
lines.append(f"## {fn.name}\n")
for line in fn.read_text(errors="replace").splitlines():
m = pat.match(line)
if not m:
continue
try:
ts = datetime.strptime(m.group(1), "%Y-%m-%d %H:%M:%S.%f")
except ValueError:
continue
if ts < cutoff:
continue
if any(k.lower() in line.lower() for k in keys):
lines.append(line[:600] + "\n")
lines.append("\n")
(out / "timeline.md").write_text("".join(lines), encoding="utf-8")
PY
cat >"${OUT}/README.txt" <<EOF
GOG log bundle (${HOURS}h)
Generated: $(date)
Bottle: ${BOTTLE_NAME} (${BOTTLE_ID})
Read first: timeline.md
Galaxy logs: galaxy-logs/
Whisky: whisky-logs/
EOF
echo "OK: ${OUT}"
echo " timeline.md — события за ${HOURS}ч"
ls -la "${OUT}"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
OUT="${1:-${HOME}/code/local_machine/temp/gog-setup-rca-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "${OUT}"
{
echo "=== $(date) ==="
echo "Bottle: ${BOTTLE_NAME} ${BOTTLE_ID}"
echo ""
echo "=== disk ==="
df -h /System/Volumes/Data | tail -1
echo ""
echo "=== processes ==="
pgrep -fl 'Galaxy|GOG_Galaxy' 2>/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}"

238
scripts/games/gog/config.sh Executable file
View File

@@ -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 <UUID>"
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 <UUID-GOG-Install>"
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
}

View File

@@ -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)"

View File

@@ -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} (25 мин)…"
export WINEPREFIX="${BOTTLE_PATH}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
export WINEDEBUG=-all
"${WINE64}" wineboot --init
python3 <<PY
import plistlib
from pathlib import Path
meta = Path("${BOTTLE_PATH}") / "Metadata.plist"
data = {
"info": {"name": "${NAME}", "pins": [], "blocklist": []},
"displayConfig": {
"customWidth": 1920,
"customHeight": 1080,
"resolutionPreset": "r1920x1080",
"virtualDesktopEnabled": False,
},
"dxvkConfig": {"dxvk": False, "dxvkAsync": True, "dxvkHud": {"off": {}}},
"graphicsConfig": {"backend": "recommended"},
"audioConfig": {
"audioDriver": "auto",
"latencyPreset": "defaultPreset",
"outputDeviceMode": "followSystem",
},
"cleanupConfig": {
"clipboardPolicy": "auto",
"clipboardThreshold": 10240,
"closeWithProcessesPolicy": "ask",
"killOnQuit": "inherit",
},
"customDLLOverrides": [],
"fileVersion": {"major": 1, "minor": 0, "patch": 0, "build": "", "preRelease": ""},
}
with meta.open("wb") as f:
plistlib.dump(data, f)
print("Metadata.plist OK")
PY
BOTTLE_VM="${FRANKE}/BottleVM.plist"
python3 <<PY
import plistlib
from pathlib import Path
vm = Path("${BOTTLE_VM}")
url = "file://${BOTTLE_PATH}"
if vm.exists():
with vm.open("rb") as f:
data = plistlib.load(f)
else:
data = {"fileVersion": {"major": 1, "minor": 0, "patch": 0, "build": "", "preRelease": ""}, "paths": []}
paths = data.setdefault("paths", [])
if not any(p.get("relative", "").endswith("${BOTTLE_ID}") for p in paths):
paths.append({"relative": url})
with vm.open("wb") as f:
plistlib.dump(data, f)
print("BottleVM.plist OK")
PY
if command -v whisky >/dev/null 2>&1; then
whisky add "${BOTTLE_PATH}" 2>/dev/null || true
fi
cat >"${GOG_SCRIPTS}/bottle.env" <<EOF
BOTTLE_ID=${BOTTLE_ID}
BOTTLE_NAME=${NAME}
EOF
echo ""
echo "OK: ${NAME}"
echo " UUID: ${BOTTLE_ID}"
echo " Path: ${BOTTLE_PATH}"
echo "Дальше: bash ${GOG_SCRIPTS}/setup-bottle.sh"

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# RCA: процесс жив, окна нет — логи + процессы (без Apple Events).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
OUT="${1:-${HOME}/code/local_machine/temp/gog-window-rca-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "${OUT}"
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"
LAUNCH_LOG="/tmp/gog-galaxy-client-${BOTTLE_NAME}.log"
{
echo "=== GOG Galaxy window RCA ==="
date
echo "bottle: ${BOTTLE_NAME} ${BOTTLE_ID}"
echo "GOG_USE_WINEVDESKTOP=${GOG_USE_WINEVDESKTOP:-?} GOG_VDESKTOP=${GOG_VDESKTOP:-?}"
echo ""
echo "--- pgrep Galaxy ---"
pgrep -fl 'GalaxyClient|explorer.exe /desktop=GOG' 2>/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"

View File

@@ -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}"

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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 или пусто)"

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 ' ') файлов)"

View File

@@ -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}"

View File

@@ -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 "$@"

View File

@@ -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 (~35 мин, скачает win7sp1)…"
winetricks -q gdiplus
touch "${BOTTLE_DIR}/.gog_gdiplus_installed"
echo "OK: gdiplus в ${BOTTLE_NAME}"

View File

@@ -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"

View File

@@ -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)"

View File

@@ -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 "$@"

View File

@@ -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 <<EOF
Использование: bash $0
Папка с архивами (по умолчанию): ${JA3_GAMES_DIR}
Нужны 5 файлов в одной папке:
setup_jagged_alliance_3_1.5.2_(80730).exe (~1 MB, лаунчер Inno)
setup_jagged_alliance_3_1.5.2_(80730)-1.bin … -4.bin
Скачать недостающий .exe: https://www.gog.com/account → JA3 → offline backup (первая, ~1 MB часть).
Или:
lgogdownloader --login
bash ${GOG_DIR}/fetch-ja3-setup-exe.sh
Переменные: JA3_GAMES_DIR, JA3_SETUP_EXE
EOF
}
verify_parts() {
local exe="$1"
local missing=0
local b
if [[ ! -d "${JA3_GAMES_DIR}" ]]; then
echo "❌ Нет каталога: ${JA3_GAMES_DIR}"
return 1
fi
for b in "${JA3_BINS[@]}"; do
if [[ ! -f "${b}" ]]; then
echo "❌ Нет: ${b}"
missing=1
fi
done
if [[ ! -f "${exe}" ]]; then
echo "❌ Нет лаунчера .exe (~1 MB) в ${JA3_GAMES_DIR}"
echo " У вас есть 4× .bin — без .exe установка GOG не стартует."
echo " Скачайте с account.gog.com первую часть offline backup (installer0, ~1 MB)."
echo " Или: bash ${GOG_DIR}/fetch-ja3-setup-exe.sh"
missing=1
fi
[[ "${missing}" -eq 0 ]]
}
main() {
local exe win_exe
exe="$(find_setup_exe)" || exe=""
if ! verify_parts "${exe}"; then
echo ""
usage
exit 1
fi
bash "${GOG_DIR}/emergency-stop.sh" || true
gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1
# bins (~15 GB) + распаковка в bottle (~16 GB) — пик ~2530 GB на Data
gog_disk_guard "${JA3_MIN_DISK_GB:-25}" || {
echo "❌ Мало места: нужно ≥25 GB свободно на Data (архивы + игра в bottle)."
echo " Сейчас: $(df -h "$(gog_data_volume_path)" | awk 'NR==2 {print $4 " свободно, " $5 " занято"}')"
exit 1
}
export_wine_env_gog
gog_wine_check
win_exe="$(gog_unix_to_win "${exe}")"
SILENT=(/VERYSILENT /SUPPRESSMSGBOXES /NORESTART)
echo "Установка JA3 из multipart (без Galaxy)"
echo " Папка: ${JA3_GAMES_DIR}"
echo " Wine: ${win_exe}"
echo " Лог: ${LOG}"
: >"${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 "$@"

View File

@@ -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 <<EOF
Использование: bash $0 /path/to/setup_jagged_alliance_3_*.exe
Скачать (Windows offline backup):
https://www.gog.com/account → Jagged Alliance 3 → Download offline backup game installers
Документация: docs/games/JA3_MAC_WITHOUT_GALAXY.md
EOF
}
if [[ -z "${INSTALLER}" || ! -f "${INSTALLER}" ]]; then
usage
exit 1
fi
bash "${GOG_DIR}/emergency-stop.sh"
gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1
gog_disk_guard 40 || {
echo "❌ Для JA3 нужно ~40+ GB свободно на Data (игра + установка)."
exit 1
}
export_wine_env_gog
gog_wine_check
# Inno silent (GOG offline installers)
SILENT=(/VERYSILENT /SUPPRESSMSGBOXES /NORESTART)
local_base="$(basename "${INSTALLER}")"
cp -f "${INSTALLER}" "${BOTTLE_DIR}/drive_c/${local_base}"
win="C:\\${local_base}"
echo "Установка JA3 (без Galaxy): ${INSTALLER}"
echo "Лог: ${LOG}"
: >"${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

View File

@@ -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"

View File

@@ -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 "$@"

View File

@@ -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"

View File

@@ -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

View File

@@ -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" "$@"

View File

@@ -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"

View File

@@ -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 "$@"

View File

@@ -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 <UUID> [имя]"

View File

@@ -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 <<PY
import plistlib
from pathlib import Path
meta = Path("${METADATA}")
with meta.open("rb") as f:
data = plistlib.load(f)
pins = data.setdefault("info", {}).setdefault("pins", [])
if not any(p.get("name") == "gog-galaxy" for p in pins):
pins.append({
"name": "gog-galaxy",
"removable": False,
"url": {"relative": "${GOG_URL}"},
})
with meta.open("wb") as f:
plistlib.dump(data, f)
print("pin gog-galaxy added")
else:
print("pin exists")
ps = Path("${PROG_SETTINGS}")
ps.parent.mkdir(parents=True, exist_ok=True)
with ps.open("wb") as f:
plistlib.dump({"arguments": "${CEF}", "environment": {}, "locale": ""}, f)
print("OK:", ps)
PY
echo ""
echo "Whisky → ${BOTTLE_NAME} → pin «gog-galaxy» → Run"
echo "Или: bash ${GOG_DIR}/launch-galaxy.sh"

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# JA3 + D3DMetal: убрать DXVK/GPTK PE из system32, оставить builtin Gcenx + d3d12/dxgi.so → libd3dshared.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
SYS32="${BOTTLE_DIR}/drive_c/windows/system32"
WINE_PE="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-windows"
WINE_UNIX="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-unix"
BAK="${SYS32}/.ja3-d3dmetal-bak"
LS="${BOTTLE_DIR}/drive_c/users/${WINE_USER:-eternal}/AppData/Roaming/Jagged Alliance 3/LocalStorage.lua"
ja3_gptk_available || ja3_bottle_uses_d3dmetal || {
echo "❌ Нет GPTK/D3DMetal — configure-ja3-d3dmetal.sh"
exit 1
}
bash "${GOG_DIR}/install-gptk-whisky.sh"
mkdir -p "${BAK}"
# DXVK / GPTK PE в system32 ломают D3DMetal (114688/86016 → «graphics subsystem»).
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
# Мост 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 напрямую)"

View File

@@ -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 "$@"

View File

@@ -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}"

Some files were not shown because too many files have changed in this diff Show More