Compare commits
27 Commits
89fbfec1b8
...
claude/gre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a1b07a47e | ||
|
|
4e88741d08 | ||
| 97841320f2 | |||
|
|
d00d856513 | ||
| 329a280cb3 | |||
|
|
6dabc7749c | ||
| d82531ec58 | |||
|
|
47394e668e | ||
| 94aae3ca26 | |||
|
|
1748562756 | ||
|
|
f0b7feadc1 | ||
|
|
d4433bd0a8 | ||
|
|
b16ecdae37 | ||
|
|
b411e3b308 | ||
|
|
cda539b9a1 | ||
|
|
bac376992d | ||
|
|
f7d06c0a35 | ||
|
|
843b9780c8 | ||
|
|
24fe1d3f88 | ||
|
|
1ae613b2bd | ||
|
|
d754de8378 | ||
|
|
80fd8ca7bf | ||
|
|
bf845e2dcb | ||
| d9c00c31e0 | |||
| f6bf12ccf9 | |||
|
|
3220238c67 | ||
|
|
265d99b378 |
37
audit/2026-05-06-objects-audit.md
Normal file
37
audit/2026-05-06-objects-audit.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: audit
|
||||||
|
source: scripts/kb-objects-audit.py
|
||||||
|
tags: [audit, objects, frontmatter, links]
|
||||||
|
score: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
# KB objects audit — 2026-05-06
|
||||||
|
|
||||||
|
**Score (меньше = лучше): `6`**
|
||||||
|
|
||||||
|
- Проектов с frontmatter: **12/12** (0 проблем)
|
||||||
|
- NetBird online-пиров без проектной карточки: **3**
|
||||||
|
- Битых wiki-ссылок `[[...]]`: **0**
|
||||||
|
|
||||||
|
## Frontmatter в projects/
|
||||||
|
|
||||||
|
✅ все проекты имеют валидный frontmatter
|
||||||
|
|
||||||
|
## Online netbird-пиры без проектной карточки
|
||||||
|
|
||||||
|
Эти пиры онлайн в NetBird, но не привязаны ни к одной projects/-странице.
|
||||||
|
Бот не сможет ответить «найди X» осмысленно — нет файла или alias.
|
||||||
|
|
||||||
|
Лечение: либо создать stub в `projects/<slug>/README.md` (см. `projects/lipki/` как образец),
|
||||||
|
либо добавить имя пира как полную строку в `aliases` подходящего проекта.
|
||||||
|
|
||||||
|
| NetBird-имя | IP | OS | Город |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `DESKTOP-2IOQS54` | 100.70.82.83 | Windows 10 | Saransk |
|
||||||
|
| `DESKTOP-AGBMLPN` | 100.70.0.106 | Windows 11 | Helsinki |
|
||||||
|
| `DESKTOP-HL0BB05` | 100.70.235.80 | Windows 11 | Lipetsk |
|
||||||
|
|
||||||
|
## Битые wiki-ссылки
|
||||||
|
|
||||||
|
✅ битых ссылок не найдено
|
||||||
1024
audit/objects-map.json
Normal file
1024
audit/objects-map.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ tags: [decision, niikn, network, dns, podkop]
|
|||||||
|
|
||||||
## Диагноз
|
## Диагноз
|
||||||
|
|
||||||
По алгоритму [[notes/govru-diagnosis]]:
|
По алгоритму [[../projects/niikn/govru-quickfix-playbook]]:
|
||||||
|
|
||||||
| Точка | HTTP | Real IP |
|
| Точка | HTTP | Real IP |
|
||||||
|-------|------|---------|
|
|-------|------|---------|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ RustDesk при первом запуске парсит своё имя фай
|
|||||||
|
|
||||||
## TODO / следующие шаги
|
## TODO / следующие шаги
|
||||||
|
|
||||||
- [ ] Олег создаёт public share-link в Nextcloud (с паролем), кладёт ссылку в [[../snippets/clients/]]
|
- [ ] Олег создаёт public share-link в Nextcloud (с паролем), кладёт ссылку в `snippets/clients/`
|
||||||
- [ ] Подготовить **one-liner-скрипты** на сервере (NPM static path `/install/` → S3 или rustdesk-api resources/public)
|
- [ ] Подготовить **one-liner-скрипты** на сервере (NPM static path `/install/` → S3 или rustdesk-api resources/public)
|
||||||
- [ ] **Configuration Strategy** в админке lejianwen-api — push config existing peers
|
- [ ] **Configuration Strategy** в админке lejianwen-api — push config existing peers
|
||||||
- [ ] Скачать `rustdesk-utils` 2.x (lejianwen-pro) для генерации `rustdesk-licensed-*.exe` — single-file deployment без скриптов
|
- [ ] Скачать `rustdesk-utils` 2.x (lejianwen-pro) для генерации `rustdesk-licensed-*.exe` — single-file deployment без скриптов
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ tags: [decision, niikn, network, dns, podkop]
|
|||||||
|
|
||||||
## Диагноз
|
## Диагноз
|
||||||
|
|
||||||
По алгоритму [[notes/govru-diagnosis]]:
|
По алгоритму [[../projects/niikn/govru-quickfix-playbook]]:
|
||||||
|
|
||||||
| Точка | HTTP | Real IP |
|
| Точка | HTTP | Real IP |
|
||||||
|-------|------|---------|
|
|-------|------|---------|
|
||||||
|
|||||||
@@ -75,6 +75,6 @@ ssh root@10.0.0.1 "cp /etc/adguardhome.yaml.bak.20260430_001129 /etc/adguardhome
|
|||||||
## Ссылки
|
## Ссылки
|
||||||
|
|
||||||
- [[../projects/dttb/openwrt-router]] — карточка роутера
|
- [[../projects/dttb/openwrt-router]] — карточка роутера
|
||||||
- [[../claude-memory/podkop]] — справка по подкопу (схемы AGH, fakeip 198.18.0.0/15)
|
- [[2026-04-17-peredelki-podkop-stability-fix]] — справка по подкопу (схемы AGH, fakeip 198.18.0.0/15)
|
||||||
- https://podkop.net/docs/adguard/
|
- https://podkop.net/docs/adguard/
|
||||||
- https://podkop.net/docs/dont-touch-my-dhcp/
|
- https://podkop.net/docs/dont-touch-my-dhcp/
|
||||||
|
|||||||
@@ -125,8 +125,27 @@ Grizzlysms TJ-pool **полностью забанен** Apple на текущи
|
|||||||
|
|
||||||
**Когда НЕ подходит:** долгосрочное хранение Apple Cash, привязка карт с большим балансом, использование как primary iCloud (там этот US Apple ID лучше не использовать вообще, только App Store).
|
**Когда НЕ подходит:** долгосрочное хранение Apple Cash, привязка карт с большим балансом, использование как primary iCloud (там этот US Apple ID лучше не использовать вообще, только App Store).
|
||||||
|
|
||||||
|
## Статус на 2026-05-06
|
||||||
|
|
||||||
|
**Прогресс:**
|
||||||
|
- ✅ IPRoyal Royal Residential куплен (1 GB pay-as-you-go), TJ residential подтверждён рабочим (см. [[../snippets/iproyal-gost-relay]])
|
||||||
|
- ✅ Локальный gost-relay отлажен (порт 9999 для TJ, 9998 для FI), FoxyProxy multi-port
|
||||||
|
- ✅ Firefox настроен: `intl.accept_languages = tg-TJ`, WebRTC отключён, приватное окно
|
||||||
|
- ✅ Apple footer показывает «Tajikistan» при правильной комбинации IP + locale + cookies-чистка
|
||||||
|
- ✅ US Adidas-аккаунт куплен (`hbuggle819@icloud.com`), 2FA через trusted devices, карта привязана
|
||||||
|
- ⚠️ **Сложности с virtual SMS** — grizzlysms TJ-pool забанен Apple, требуются альтернативы (5sim, smsbower) или реальная TJ SIM
|
||||||
|
- ⚠️ **Митинский радиорынок «высох»** (2026-05-04 проверено лично): продажа TJ-SIM мигрантам сократилась после ужесточения ФЗ-178. Альтернативные источники: Avito с доставкой (1500-3500 ₽), Telegram-каналы, знакомые в TJ
|
||||||
|
- 🔴 **Регион US Adidas пока не менялся на TJ** — ждём реальную TJ-SIM либо premium DID. Аккаунт пока используется только для скачивания приложений.
|
||||||
|
|
||||||
|
**Текущий план:**
|
||||||
|
1. Найти реальную TJ SIM-карту (Avito / Telegram / знакомые)
|
||||||
|
2. После получения SIM — менять регион Adidas-аккаунта на TJ через TJ-IP (gost), привязка к новой TJ-карте
|
||||||
|
3. До этого момента — US Adidas-аккаунт используется только в App Store на iPhone для скачивания заблокированных в RU приложений (по схеме [[../snippets/apple-id-us-on-russia]])
|
||||||
|
|
||||||
|
**Параллельная находка:** Google гораздо строже Apple к virtual SMS — на TJ-IP при регистрации нового @gmail сразу требует QR-верификацию через trusted device. Web-регистрация Google под TJ через grizzlysms — путь в тупик, требует Android-эмулятор или готовый купленный аккаунт.
|
||||||
|
|
||||||
## Связанные
|
## Связанные
|
||||||
|
|
||||||
- [[../README]] — индекс vault
|
- [[../README]] — индекс vault
|
||||||
- TODO в MEMORY был «отложено с 2026-04-23», теперь активирован.
|
- TODO в MEMORY был «отложено с 2026-04-23», теперь активирован.
|
||||||
- Решение по [[../snippets/clients/]] для US Apple ID на RU iPhone — структурно тот же подход (proxy + foreign SMS).
|
- Решение по `snippets/clients/` для US Apple ID на RU iPhone — структурно тот же подход (proxy + foreign SMS).
|
||||||
|
|||||||
72
decisions/2026-05-05-mac-dictation-groq-hammerspoon.md
Normal file
72
decisions/2026-05-05-mac-dictation-groq-hammerspoon.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# 2026-05-05 — Mac dictation: Hammerspoon + Groq Whisper
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
Олег на Intel MacBook Pro (i9-9880H, 2019), нужна голосовая диктовка для русского. Все современные приложения (superwhisper PRO, VoiceInk, Spokenly Parakeet) либо требуют Apple Silicon, либо платную PRO-лицензию ($150-250). Бесплатный free-tier у superwhisper исчерпан и валидируется на их сервере (см. [feedback_superwhisper_no_license](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_superwhisper_no_license.md)).
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
Свой скрипт + Hammerspoon + Groq Whisper API:
|
||||||
|
- **Hammerspoon** ловит глобальный hotkey `⌘⇧D`
|
||||||
|
- **Bash скрипт** toggle-режим: первый запуск → ffmpeg запись с микрофона; второй → стоп → POST в Groq → текст в `/tmp/groq-dictate.last`
|
||||||
|
- **Lua callback** в Hammerspoon читает файл, кладёт в pasteboard, нажимает Cmd+V через `hs.eventtap.keyStroke`
|
||||||
|
- **Groq Whisper-large-v3-turbo** — бесплатно ~14400 запросов/день, 0.5с на 4-сек запись, RU IP не блочится
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
- `~/bin/groq-dictate.sh` — скрипт записи + Groq POST + **fallback на whisper-cpp** + write to `/tmp/groq-dictate.last`
|
||||||
|
- `~/bin/dictation-doctor.sh` — health-check всех компонентов (Hammerspoon / TCC / зависимости / Groq / mic / Fn-key); запускать когда «не работает»
|
||||||
|
- `~/.hammerspoon/init.lua` — Fn (одиночное нажатие) trigger через eventtap, paste через `hs.eventtap.keyStroke`
|
||||||
|
- `~/.cache/whisper-cpp/ggml-tiny-q5_1.bin` — 31MB локальная модель для offline fallback
|
||||||
|
- Groq API key — в `reference_groq_api.md` private memory
|
||||||
|
|
||||||
|
## Финальный hotkey
|
||||||
|
**Fn (Globe) одиночное нажатие** — toggle (старт/стоп). Срабатывает быстрее ⌘⇧D, освобождает руки. Apple Dictation на двойное Fn остаётся (если не отключить «Нажатие клавиши Fn» в System Settings → Keyboard).
|
||||||
|
|
||||||
|
## Критические грабли (и фиксы)
|
||||||
|
1. **Hammerspoon Accessibility кеширует статус** — после Enable в System Settings нужен **`killall Hammerspoon && open -a Hammerspoon`**, иначе Hammerspoon продолжает показывать WARNING и hotkey не работает.
|
||||||
|
|
||||||
|
2. **`hs.hotkey.bind({"cmd","shift"}, "d", ...)`** на русской раскладке выдаёт warning `key 'd' not found in active keymap; using ANSI-standard US keyboard layout as fallback, returning '2'`. Решение: **биндить по числовому keycode** — `hs.hotkey.bind({"cmd","shift"}, 2, ...)` (2 = физическая клавиша D). Так работает на любой раскладке.
|
||||||
|
|
||||||
|
3. **`osascript -e 'tell application "System Events" to keystroke "v" using command down'`** на русской раскладке вместо Cmd+V вставляет UTF-8 байты текста как символы → получается мусор типа `—В—ь—А—∞—ь—В?` для строки «Ты меня слышишь?». Решение: **никогда не использовать `keystroke` для paste**. Использовать `hs.eventtap.keyStroke({"cmd"}, "v")` напрямую из Lua (отправляет настоящий low-level KeyDown event).
|
||||||
|
|
||||||
|
4. **Toggle через PID-файл** — `/tmp/groq-dictate.pid`. Если процесс упал/убит — удалить руками. Скрипт устойчив: `kill -INT` корректно закрывает .wav, ждёт до 0.5с дописать заголовок.
|
||||||
|
|
||||||
|
5. **ffmpeg avfoundation `:0`** = default mic. Если нужен другой — `ffmpeg -f avfoundation -list_devices true -i ""`.
|
||||||
|
|
||||||
|
## Стоимость
|
||||||
|
- Hammerspoon бесплатный
|
||||||
|
- Groq бесплатный (14400 req/day, ~120 минут диктовки в день — намного больше нужного)
|
||||||
|
- Итого: 0₽
|
||||||
|
|
||||||
|
## Альтернативы которые НЕ подошли
|
||||||
|
- **superwhisper** — free tier 530 сек, потом сервер бракует
|
||||||
|
- **VoiceInk** — официально Apple Silicon only, на Intel CPU крутит большую модель часами
|
||||||
|
- **Spokenly** — `cdn.spokenly.app` блокирует RU IP (3.8 KB/s), Parakeet требует Neural Engine
|
||||||
|
- **Wispr Flow** — `dl.wisprflow.com` блокирует RU IP, плюс $144/год Pro для регулярного использования
|
||||||
|
- **MacWhisper** — $59 lifetime + не пробовали (всё уже работало бесплатно)
|
||||||
|
- **OpenWhispr** — 273MB dmg в GitHub, не докачался
|
||||||
|
|
||||||
|
## Связано
|
||||||
|
- [RU-заблокированные сервисы](../notes/ru-geoblocked-services.md)
|
||||||
|
- [Groq API](../../.claude/projects/-Users-ai-knowledge-base/memory/reference_groq_api.md)
|
||||||
|
- [Superwhisper — нет PRO](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_superwhisper_no_license.md)
|
||||||
|
|
||||||
|
## Воспроизведение на новом Mac
|
||||||
|
```bash
|
||||||
|
brew install --cask hammerspoon
|
||||||
|
brew install whisper-cpp jq ffmpeg
|
||||||
|
mkdir -p ~/bin ~/.hammerspoon ~/.cache/whisper-cpp
|
||||||
|
|
||||||
|
# скопировать ~/bin/groq-dictate.sh + ~/bin/dictation-doctor.sh → chmod +x
|
||||||
|
# скопировать ~/.hammerspoon/init.lua
|
||||||
|
|
||||||
|
# скачать локальную fallback-модель (31 MB, GitHub не блочит RU)
|
||||||
|
curl -sSL -o ~/.cache/whisper-cpp/ggml-tiny-q5_1.bin \
|
||||||
|
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.bin?download=true"
|
||||||
|
|
||||||
|
open -a Hammerspoon
|
||||||
|
# Hammerspoon Preferences → Enable Accessibility → System Settings → включить
|
||||||
|
killall Hammerspoon && open -a Hammerspoon # ОБЯЗАТЕЛЬНО после grant — кеш
|
||||||
|
|
||||||
|
# System Settings → Клавиатура → Нажатие клавиши Fn → "Действие не требуется"
|
||||||
|
~/bin/dictation-doctor.sh # должно быть всё зелёное
|
||||||
|
```
|
||||||
|
Готово. **Fn → говори → Fn** — текст вставится в активное окно.
|
||||||
101
decisions/2026-05-06-kb-search-overhaul.md
Normal file
101
decisions/2026-05-06-kb-search-overhaul.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: decision
|
||||||
|
tags: [kb, openclaw, search, fts, infra, summary]
|
||||||
|
status: applied
|
||||||
|
---
|
||||||
|
|
||||||
|
# Перестройка KB-поиска и синхронизации (8 фаз за один день)
|
||||||
|
|
||||||
|
## Что было до
|
||||||
|
|
||||||
|
Запрос «OpenWRT Липки» Максимке возвращал `netbird-inventory.md`, потому что:
|
||||||
|
- слова «Липки» во всём vault'е была одна строка в табличке инвентаря,
|
||||||
|
- слово «OpenWRT» статистически перевешивали НИИКН-документы (`govru-quickfix`, `niikn-openwrt-awg-fix`),
|
||||||
|
- кросс-ссылок и aliases не было, embeddings не было,
|
||||||
|
- лаг push→бот доходил до 16 минут (cron `*/5` Mac→Gitea + cron `*/15` Gitea→openclaw).
|
||||||
|
|
||||||
|
Пользователь жаловался: «бот путает, я ему про Липки, он мне про НИИКН».
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
| Фаза | Эффект |
|
||||||
|
|---|---|
|
||||||
|
| 1. Embeddings (ollama+bge-m3) | ⛔ откачено — openclaw 2026.5.2 при `provider: ollama` шлёт inputs целиком, ollama тратит >5 мин/запрос, undici fetch валится по headers timeout. ollama на LXC 132 готова (uptime, bge-m3 1024d), но сторона openclaw нуждается в фиксе chunking |
|
||||||
|
| 2. Webhook + auto-reindex | Gitea push→listener (HMAC SHA-256)→`kb-pull.sh`→`openclaw memory index`. **push→FTS: 15 мин → 50 c**. См. [[../snippets/openclaw-kb-webhook]] и [[2026-05-06-openclaw-kb-webhook-deployment]] |
|
||||||
|
| 3. objects-map.json + _index.md | Скрипт `scripts/kb-objects-map.py` парсит netbird-inventory + frontmatter и пишет JSON для бота + human-readable [[../projects/_index]]. Базовый структурный слой |
|
||||||
|
| 4. Stubs + frontmatter | Создано 6 README (`znamenskoye`, `mmfb`, `lipki` уже был, + 4 stub'а клиентских OpenWrt). Обогащён frontmatter в `niikn`, `dttb`, `glavtorg`, `krasnogorsk`, `zelenograd` с aliases для netbird-имён. **38 → 14 orphan-пиров** |
|
||||||
|
| 5.1. Слить дубли видео | Удалён `dttb/video-surveillance-report.md` (297 строк, неполная копия), canonical `videonablyudenie-znam.md` обогащён aliases |
|
||||||
|
| 5.2. Архив audit/ | 18 устаревших drift-отчётов 2026-04-* перенесены в `audit/archive/`, в active остались 7 свежих |
|
||||||
|
| 6. Recall + dreaming | `openclaw memory promote --apply`: 17/39 коротких воспоминаний переехали в долгую. `dreaming.enabled=true` (cron `0 3 * * *`). Weekly cron на promote |
|
||||||
|
| 7. Расширенный kb-audit | Скрипт `scripts/kb-objects-audit.py` пишет еженедельный отчёт с score: проверяет frontmatter, online-orphans, битые wiki-ссылки. Первый запуск — score=84 (12/12 frontmatter ✓, 3 online orphan, 26 битых wiki) |
|
||||||
|
| 8. UX бота | `memorySearch.query.minScore=0.4`, IDENTITY.md разводит двух Максимок (homelab vs НИИКН), INFRASTRUCTURE.md переписан как навигатор по vault'у |
|
||||||
|
|
||||||
|
## Метрики до / после
|
||||||
|
|
||||||
|
| Параметр | До | После |
|
||||||
|
|---|---|---|
|
||||||
|
| «OpenWRT Липки» top-1 | `netbird-inventory.md` ❌ | `projects/lipki/README.md` ✅ |
|
||||||
|
| «Антон клиент Звенигород» top-1 | ничего | `projects/lipki/README.md` ✅ |
|
||||||
|
| «AgentDVR ЧОП Знаменское» top-1 | три дубля | `videonablyudenie-znam.md` ✅ |
|
||||||
|
| «Константин Вишневый сад» top-1 | `netbird-inventory.md` | `projects/vishnevyy-sad/README.md` ✅ |
|
||||||
|
| Лаг push → видно боту | 5–16 мин | **~50 секунд** |
|
||||||
|
| Проектов с netbird-привязкой | 0 | **9** |
|
||||||
|
| Структурный объект-граф | нет | `objects-map.json` (34 entries) |
|
||||||
|
| Online orphan-пиров | ~30+ | **3** |
|
||||||
|
| Auto-reindex после pull | нет | есть |
|
||||||
|
| Recall promoted | 0 (mёртв 16 дней) | 17 + weekly cron |
|
||||||
|
| Dreaming | off | on (ночной cron) |
|
||||||
|
| FTS-шум audit/ | 24 файла | 7 active + 18 в archive |
|
||||||
|
|
||||||
|
## Грабли (для будущей памяти)
|
||||||
|
|
||||||
|
1. **Gitea SSRF-protection.** Без `webhook.ALLOWED_HOST_LIST` в `app.ini` Gitea silently отказывает доставлять webhook на private IP. В docker-логах: `webhook can only call allowed HTTP servers, deny '10.0.0.239'`. Listener при этом получает 0 запросов, journal пустой.
|
||||||
|
|
||||||
|
2. **`flock -n` теряет двойные push.** Если webhook прилетел во время pull/reindex — non-blocking lock молча выходит. Заменить на `flock -w 180`.
|
||||||
|
|
||||||
|
3. **openclaw 2026.5.2 ollama-провайдер игнорирует chunking.** При `provider: ollama` шлёт файлы целиком в `/api/embed`, ollama buffer растёт до 700 MB, обработка >5 мин, fetch валится. Не лечится `chunking.tokens=512`, `nonBatchConcurrency=1`, `batch.enabled=false`. Bug либо в openclaw, либо неочевидный конфиг-флаг.
|
||||||
|
|
||||||
|
4. **substring matching → false positives.** Alias `cloud` подхватывал `Cloud-NIIKN New niikn.com`. Перешли на exact-match с нормализацией `ye→e`. Aliases теперь должны содержать **полные** имена пиров как в netbird-inventory.
|
||||||
|
|
||||||
|
5. **Heredoc через ssh с backticks** — bash интерпретирует backticks как command-substitution внутри пути файла. Для записи структурных markdown'ов с inline-кодом — пушить через base64 или scp/pct push, не heredoc.
|
||||||
|
|
||||||
|
6. **`git commit` без `-a` или явного `git add`** забирает только staged. Удаление через `git rm` помечается автоматом, а modify — нет. Один коммит может оставить часть правок несинхронизированными — дополнительный коммит лечит.
|
||||||
|
|
||||||
|
## Что отложено
|
||||||
|
|
||||||
|
| Задача | Причина | Когда возвращаться |
|
||||||
|
|---|---|---|
|
||||||
|
| Векторный поиск через ollama | bug в openclaw 2026.5.2 ollama-провайдере | при апгрейде openclaw, или попробовать `provider: local` (transformers.js встроен в openclaw) |
|
||||||
|
| 14 orphan-пиров (Денис Тихая, DESKTOP-2IOQS54 Saransk, KOMPUTER, MastaNotebook, …) | нужны факты от Олега чьи это машины | по мере выяснения — добавлять в aliases подходящих проектов |
|
||||||
|
| 26 битых wiki-ссылок | автоматический фикс не всегда возможен (некоторые цели реально удалены) | semi-auto проход: показать список, для очевидных — починить, остальное — оставить TODO |
|
||||||
|
| Singleton-проекты (`projects/*.md`) без frontmatter | не критично — они top-level note'ы | при касании |
|
||||||
|
|
||||||
|
## Артефакты в vault
|
||||||
|
|
||||||
|
- [[../projects/lipki/README]], [[../projects/znamenskoye/README]], [[../projects/mmfb/README]]
|
||||||
|
- [[../projects/sergey/README]], [[../projects/benilux/README]], [[../projects/vishnevyy-sad/README]], [[../projects/openwrt-4/README]]
|
||||||
|
- [[../projects/_index]] — авто-генерированный реестр
|
||||||
|
- `audit/objects-map.json` — машинно-читаемый граф
|
||||||
|
- [[../audit/2026-05-06-objects-audit]] — первый health-check
|
||||||
|
- `scripts/kb-objects-map.py`, `scripts/kb-objects-audit.py`
|
||||||
|
- [[../snippets/openclaw-kb-webhook]]
|
||||||
|
- [[2026-05-06-openclaw-kb-webhook-deployment]]
|
||||||
|
- frontmatter добавлен в `projects/{niikn,dttb,glavtorg,krasnogorsk,zelenograd}/README.md`
|
||||||
|
|
||||||
|
## Артефакты вне vault
|
||||||
|
|
||||||
|
**LXC 137 (openclaw):**
|
||||||
|
- `/usr/local/bin/kb-pull-webhook.py` (Python listener, HMAC)
|
||||||
|
- `/etc/systemd/system/kb-pull-webhook.service`
|
||||||
|
- Обновлённый `/usr/local/bin/kb-pull.sh` (`flock -w 180`, hash-diff, auto-reindex)
|
||||||
|
- Cron `0 4 * * 1` weekly memory promote
|
||||||
|
- Config: `memorySearch.query.minScore=0.4`, `dreaming.enabled=true`, ollama-провайдер откачен (бэкап `/root/.openclaw/openclaw.json.bak.embed-2026-05-06-112441`)
|
||||||
|
- Переписаны `/root/clawd/IDENTITY.md`, `INFRASTRUCTURE.md`
|
||||||
|
|
||||||
|
**LXC 136 (Gitea):**
|
||||||
|
- `webhook.ALLOWED_HOST_LIST = 10.0.0.0/24,*.dttb.ru,private` в `/opt/gitea/data/gitea/conf/app.ini`
|
||||||
|
- Webhook id=1 на репо `oleg/knowledge-base`
|
||||||
|
|
||||||
|
**LXC 132 (code-server):**
|
||||||
|
- ollama systemd override `OLLAMA_HOST=0.0.0.0:11434, KEEP_ALIVE=24h` (для будущего возврата к embeddings)
|
||||||
78
decisions/2026-05-06-openclaw-kb-webhook-deployment.md
Normal file
78
decisions/2026-05-06-openclaw-kb-webhook-deployment.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: decision
|
||||||
|
tags: [openclaw, gitea, webhook, kb-sync, performance]
|
||||||
|
status: applied
|
||||||
|
---
|
||||||
|
|
||||||
|
# Webhook Gitea → openclaw kb-pull (Фаза 2 плана улучшения KB-поиска)
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
После публикации `projects/lipki/README.md` стало видно, что openclaw (Максимка) видит новые правки vault только через cron `*/15` — лаг до 15 минут. Это первое, что бьёт по UX («только что записал → бот всё ещё не знает»).
|
||||||
|
|
||||||
|
План улучшения KB-поиска расписан в чате с Claude Code 2026-05-06 утром, Фаза 2 — webhook + автоматический FTS-реиндекс.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Push-канал Gitea → listener на LXC 137 → `kb-pull.sh` → `openclaw memory index`. Cron `*/15` оставлен как safety net.
|
||||||
|
|
||||||
|
Подробная реализация — [[../snippets/openclaw-kb-webhook]].
|
||||||
|
|
||||||
|
## Что развёрнуто
|
||||||
|
|
||||||
|
| Компонент | Где |
|
||||||
|
|---|---|
|
||||||
|
| `/usr/local/bin/kb-pull-webhook.py` | LXC 137, Python http.server, HMAC-SHA256 проверка, journal-лог |
|
||||||
|
| `/etc/systemd/system/kb-pull-webhook.service` | LXC 137, system unit, Restart=on-failure, secret в env |
|
||||||
|
| Обновлённый `/usr/local/bin/kb-pull.sh` | `flock -w 180`, hash-diff, авто `openclaw memory index` при новом HEAD |
|
||||||
|
| Webhook в Gitea (id=1, репо `oleg/knowledge-base`) | events: push, branch: main, secret в HMAC |
|
||||||
|
| `webhook.ALLOWED_HOST_LIST = 10.0.0.0/24,*.dttb.ru,private` в `app.ini` | LXC 136 (Gitea в docker), `/opt/gitea/data/gitea/conf/app.ini` |
|
||||||
|
|
||||||
|
## Метрика
|
||||||
|
|
||||||
|
End-to-end push → видно боту:
|
||||||
|
|
||||||
|
| Этап | Время |
|
||||||
|
|---|---|
|
||||||
|
| `git push` с Mac → Gitea ack | ~2 c |
|
||||||
|
| Gitea → webhook listener | ~3 c |
|
||||||
|
| listener → kb-pull.sh → git pull → новый HEAD | ~6 c |
|
||||||
|
| **Итого до видимости HEAD на 137** | **~11 c** |
|
||||||
|
| FTS reindex (875 файлов / 1791 chunk) | +38 c |
|
||||||
|
| **Итого до видимости в `openclaw memory search`** | **~50 c** |
|
||||||
|
|
||||||
|
Было: до 15 минут (cron */15) + до 38 c reindex (если бы он раньше был) = до **~16 минут**.
|
||||||
|
|
||||||
|
Улучшение: **×20 латентности до видимости HEAD, ×16 до полного FTS**.
|
||||||
|
|
||||||
|
## Грабли (для будущей памяти)
|
||||||
|
|
||||||
|
1. **Gitea SSRF-protection.** Без `ALLOWED_HOST_LIST` Gitea silently отказывает доставлять webhook на private IP. Test delivery возвращает `204`, но в Gitea log: `webhook can only call allowed HTTP servers, deny '10.0.0.239'`. Listener при этом не видит ничего — пустой journal. Это была первая причина почему ничего не работало.
|
||||||
|
|
||||||
|
2. **`flock -n` теряет вторую серию push'ов.** Если webhook прилетел во время уже идущего pull/reindex (быстрый двойной push) — non-blocking flock молча выходит. Надо `-w 180` (ждать в очереди до 3 мин).
|
||||||
|
|
||||||
|
3. **sed по строке с `||` ломается, если разделитель `|`.** `sed -i 's|flock -n|flock -w 180|'` падает с `unknown option to s`. Использовать `#` или другой разделитель — `sed -i 's#flock -n#flock -w 180#'`.
|
||||||
|
|
||||||
|
4. **Listener `log_message: pass` гасит **все** логи http.server.** Если хочется видеть приём POST — переопределить как `sys.stderr.write(...)`. Иначе debug невозможен.
|
||||||
|
|
||||||
|
## Альтернативы, которые отвергнуты
|
||||||
|
|
||||||
|
- **Полностью убрать cron**: рискованно — если listener умер и не заметили, vault зависнет. Cron `*/15` ничего не стоит и страхует.
|
||||||
|
- **Webhook напрямую через NPM/публичный URL**: лишний хоп, нужен HTTPS, всё в LAN — не нужно.
|
||||||
|
- **iptables на 18790**: HMAC-secret уже отсекает левые запросы. Homelab trusted. Не делал.
|
||||||
|
|
||||||
|
## Откат
|
||||||
|
|
||||||
|
См. секцию «Откат» в [[../snippets/openclaw-kb-webhook]]. Один rm-рецепт + удаление webhook через Gitea API.
|
||||||
|
|
||||||
|
## Что дальше
|
||||||
|
|
||||||
|
Фаза 1 плана (embeddings ollama × bge-m3) — пока **отложена**. При попытке развёртывания openclaw 2026.5.2 со всеми правильными config-полями (`provider=ollama`, `remote.baseUrl`, `model=bge-m3`, `chunking.tokens=512`) **игнорирует chunking** и шлёт в `/api/embed` файлы целиком — output buffer ollama растёт до 700+ MB, обработка >5 мин, undici fetch падает по дефолтному 5-min Headers Timeout. Конфигурация откачена, состояние FTS-only сохранено.
|
||||||
|
|
||||||
|
Возможные пути возврата к векторному поиску:
|
||||||
|
- `provider: local` (transformers.js встроенный) — попробовать без сетевого хопа
|
||||||
|
- Ждать openclaw 2026.5.3+ с фиксом chunking
|
||||||
|
- GitHub Copilot embeddings (требует токен)
|
||||||
|
|
||||||
|
Следующая фаза по плану — **Фаза 3** (`audit/objects-map.json` + `projects/_index.md`) или **Фаза 5.1** (слить дубли видеонаблюдения).
|
||||||
119
decisions/2026-05-06-openclaw-opus-4-7-via-max-cliproxy.md
Normal file
119
decisions/2026-05-06-openclaw-opus-4-7-via-max-cliproxy.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: decision
|
||||||
|
tags: [decision, openclaw, omniroute, claude, opus, cliproxy, max-plan]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2026-05-06: openclaw перешёл на Claude Opus 4.7 через Max-подписку (cc/* в OmniRoute)
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Искали стабильный источник Claude Opus 4.6/4.7 для openclaw — обсуждали построение Workspace-фарма на новом домене с пулом аккаунтов под Antigravity. Перед тем как затевать фарм ($12/домен + время на регистрации + риск массового бана), проверили что фактически работает в текущей OmniRoute.
|
||||||
|
|
||||||
|
## Что обнаружили в OmniRoute (10.0.0.179:20128)
|
||||||
|
|
||||||
|
В каталоге **17 моделей с opus в id**, но реально пингуются только три:
|
||||||
|
|
||||||
|
| Model ID | Источник | Статус |
|
||||||
|
|---|---|---|
|
||||||
|
| **`cc/claude-opus-4-7`** | Claude Code OAuth (Max-подписка Олега) | ✅ Работает |
|
||||||
|
| `cc/claude-opus-4-6` | то же | ✅ Работает |
|
||||||
|
| `claude/claude-opus-4-7` | прямой Anthropic API | ✅ Работает (платный per-token) |
|
||||||
|
| `gh/claude-opus-4.6` | GitHub Copilot integrator MS | ❌ MS убрала Opus 4.6 из integrator scope, отдаёт «model not available» |
|
||||||
|
| `gh/claude-opus-4.7` | то же | ❌ ID mismatch: OmniRoute шлёт `4.7`, MS ждёт `4-7` (баг mapping в текущей версии) |
|
||||||
|
| `kr/claude-opus-4.6` / `4.7` | Kiro/AWS | ❌ Kiro Opus не выдаёт, только Sonnet |
|
||||||
|
| `kiro/claude-opus-*` | то же | ❌ |
|
||||||
|
| `kc/anthropic/claude-opus-4.7` | KiloCode | ❌ Empty response |
|
||||||
|
| `kilocode/anthropic/claude-opus-4.7` | то же | ❌ |
|
||||||
|
| `antigravity/claude-opus-4-6-thinking` | Google Antigravity | ❌ «Missing Google projectId — reconnect OAuth in Providers → Antigravity» |
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
**Workspace-фарм для Antigravity не нужен.** У Олега уже есть рабочий Opus 4.7 через **`cc/claude-opus-4-7`** — это его собственная Max-подписка Anthropic, проксированная через CLIProxy в OmniRoute. Лучше любого фарма потому что:
|
||||||
|
|
||||||
|
- Нет per-token биллинга, фиксированная стоимость Max ($200/мес за Max20x)
|
||||||
|
- Не нужны множественные аккаунты, recovery emails, virtual cards
|
||||||
|
- Один источник = один ключ = простая интеграция
|
||||||
|
- Нет риска массового бана как у фарм-аккаунтов
|
||||||
|
- Самая свежая модель Opus 4.7 (новее чем 4.6 которая в фармовых Antigravity)
|
||||||
|
|
||||||
|
### Изменения в `/root/.openclaw/openclaw.json` (LXC 137)
|
||||||
|
|
||||||
|
**Было:**
|
||||||
|
```json
|
||||||
|
"agents.defaults.model": {
|
||||||
|
"primary": "omniroute/kr/claude-sonnet-4.5",
|
||||||
|
"fallbacks": [
|
||||||
|
"omniroute/cc/claude-sonnet-4-6",
|
||||||
|
"omniroute/cx/gpt-5.4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Стало:**
|
||||||
|
```json
|
||||||
|
"agents.defaults.model": {
|
||||||
|
"primary": "omniroute/cc/claude-opus-4-7",
|
||||||
|
"fallbacks": [
|
||||||
|
"omniroute/cc/claude-sonnet-4-6",
|
||||||
|
"omniroute/kr/claude-sonnet-4.5",
|
||||||
|
"omniroute/cx/gpt-5.4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Также добавлена запись в `models.providers.omniroute.models[]`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "cc/claude-opus-4-7",
|
||||||
|
"name": "Claude Opus 4.7 (CLIProxy/Max)",
|
||||||
|
"reasoning": false,
|
||||||
|
"input": ["text", "image"],
|
||||||
|
"contextWindow": 200000,
|
||||||
|
"maxTokens": 8192
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Без этой записи openclaw fallback'нет на следующий — модель должна быть в каталоге провайдера.
|
||||||
|
|
||||||
|
### Применение
|
||||||
|
|
||||||
|
OpenClaw подхватил изменения **через hot-reload без рестарта** (видно в журнале: `[reload] config hot reload applied`). После я ещё раз сделал `systemctl --user restart openclaw-gateway.service` для чистоты, но это было необязательно.
|
||||||
|
|
||||||
|
В логах после старта:
|
||||||
|
```
|
||||||
|
[gateway] agent model: omniroute/cc/claude-opus-4-7
|
||||||
|
[gateway] http server listening (7 plugins: ..., telegram; 8.6s)
|
||||||
|
[telegram] [default] starting provider (@maxim_dttb_bot)
|
||||||
|
[gateway] ready
|
||||||
|
```
|
||||||
|
|
||||||
|
## Бэкап и rollback
|
||||||
|
|
||||||
|
Автобэкап перед изменениями: `/root/.openclaw/openclaw.json.bak.opus47-20260506-090832`
|
||||||
|
|
||||||
|
Rollback одной командой:
|
||||||
|
```bash
|
||||||
|
sshpass -p '1qaz!QAZ' ssh root@10.0.0.250 "pct exec 137 -- bash -c '
|
||||||
|
cp /root/.openclaw/openclaw.json.bak.opus47-20260506-090832 /root/.openclaw/openclaw.json
|
||||||
|
XDG_RUNTIME_DIR=/run/user/0 systemctl --user restart openclaw-gateway.service
|
||||||
|
'"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Известные риски и мониторинг
|
||||||
|
|
||||||
|
1. **Max-подписка fair-use лимиты** — Anthropic размывает по нагрузке, точных цифр нет. Когда упрёмся — openclaw сам перейдёт на `cc/claude-sonnet-4-6` (видно в логах `model fallback decision`).
|
||||||
|
2. **CLIProxy OAuth refresh** — может протухнуть. Мониторинг: `journalctl --user -u openclaw-gateway -f` на LXC 137. При ошибке 401 от Anthropic API → переподключить Claude Code OAuth в OmniRoute (Dashboard → Providers → Claude → Reconnect).
|
||||||
|
3. **Скорость** — Opus 4.7 в среднем 2-5 сек медленнее Sonnet 4.5. Для интерактива в Telegram это норма.
|
||||||
|
|
||||||
|
## Параллельные находки
|
||||||
|
|
||||||
|
- **Antigravity** требует OAuth re-connect. По заметке `feedback_antigravity_onboarding.md`: открыть [antigravity.google](https://antigravity.google), залогиниться, создать Cloud Code project → projectId подтянется → `antigravity/claude-opus-4-6-thinking` оживёт. Сейчас не критично.
|
||||||
|
- **`Unknown model: cx/gpt-5.4`** — каждые 30 минут в логах ошибка. Health-check тыкается в модель которой нет в каталоге `omniroute.models[]` openclaw. Не блокер — failover работает. Решение: либо добавить `cx/gpt-5.4` в каталог openclaw, либо убрать из health-check'ов.
|
||||||
|
|
||||||
|
## Связанные
|
||||||
|
|
||||||
|
- [[../claude-memory/omniroute]] — OmniRoute setup, провайдеры, версии
|
||||||
|
- [[../projects/dttb/openclaw]] — справочник по openclaw
|
||||||
|
- [[../snippets/clawdbot-cliproxy-config]] — старый шаблон CLIProxy
|
||||||
|
- [[../snippets/omniroute-models-audit]] — шаблон smoke-тестов и парсинга ошибок (использовался при этом аудите)
|
||||||
90
decisions/2026-05-07-buzharovo-1c-rmngr-loop-after-crash.md
Normal file
90
decisions/2026-05-07-buzharovo-1c-rmngr-loop-after-crash.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-07
|
||||||
|
type: decision
|
||||||
|
tags: [buzharovo, server1c, 1c, troubleshooting]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Бужарово 1С сервер — rmngr-loop после грязного ребута: рецепт
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
07.05.2026 утром локальные пользователи в Бужарово начали жаловаться на резкое замедление 1С. NetBird и удалённый доступ ни при чём — тормозило в локальной сети офиса.
|
||||||
|
|
||||||
|
Сервер: `Server1C` (Win 2012 R2, 100.70.75.103 / 185.13.47.2), 1С 8.3.27.1606, MSSQL.
|
||||||
|
|
||||||
|
## Что нашли
|
||||||
|
|
||||||
|
Сегодня в **09:05 сервер ушёл в crash**:
|
||||||
|
- `System / Kernel-Power Event 41` (Critical): "rebooted without cleanly shutting down — system stopped responding, crashed, or lost power"
|
||||||
|
- `System / EventLog 6008`: "previous shutdown was unexpected"
|
||||||
|
|
||||||
|
После загрузки:
|
||||||
|
- `rmngr.exe` (менеджер кластера 1С) держит **~1.7 ядра постоянно** в idle (норма <2% одного ядра).
|
||||||
|
- `rphost`, `sqlservr`, диски (avg sec/write 1.6 ms, queue 0.03), память (5/64 GB) — **в норме**.
|
||||||
|
- `rac.exe localhost:1540 cluster list` → "ошибка соединения с сервером", exit -1 — **admin-канал rmngr повис**, кластером невозможно управлять извне.
|
||||||
|
- `netbird.exe` параллельно жжёт 1.4 ядра — побочный reconnect-loop после crash, после ребута сам успокоился.
|
||||||
|
|
||||||
|
## Что не помогло
|
||||||
|
|
||||||
|
**Полный ребут сервера** (через `Restart-Computer`) — НЕ решает проблему. На свежезагруженном сервере с uptime=37 секунд rmngr опять на 178% одного ядра. То есть проблема воспроизводится при каждом первом запуске агента после crash.
|
||||||
|
|
||||||
|
## Что помогло
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Restart-Service -Name '1C:Enterprise 8.3 Server Agent (x86-64)' -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
После рестарта службы (не сервера!):
|
||||||
|
|
||||||
|
| Процесс | До рестарта | После рестарта |
|
||||||
|
|---|---|---|
|
||||||
|
| rmngr | 178% ядра | 3% ядра |
|
||||||
|
| rphost | 19% | 1% |
|
||||||
|
| ragent | 0% | 1% |
|
||||||
|
|
||||||
|
Все клиенты в этот момент вылетают — нужно предупредить за 2-3 минуты. Перезаход штатный.
|
||||||
|
|
||||||
|
## Побочный эффект
|
||||||
|
|
||||||
|
После рестарта остался орфан-процесс старого `ragent` от прошлого запуска (не слушает 1540, висит в памяти). Можно безопасно прибить:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-Process ragent | Where-Object { $_.Id -ne (Get-NetTCPConnection -LocalPort 1540).OwningProcess } | Stop-Process -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
## Корневая причина — открыта
|
||||||
|
|
||||||
|
Почему `rmngr` зацикливается после первого запуска ragent — точно не выяснено. Гипотезы:
|
||||||
|
1. Повреждённый кэш кластера `C:\Program Files\1cv8\srvinfo\reg_*\` после crash.
|
||||||
|
2. Регресс в 8.3.27.1606 при восстановлении сеансов.
|
||||||
|
3. Disabled-служба `RagentServer_8327` (старый дубликат) что-то мешает в реестре при первом старте.
|
||||||
|
|
||||||
|
Если повторится — смотреть `C:\Program Files\1cv8\srvinfo\reg_*\1Cv8FTLog\` и `1Cv8Log` на ошибки.
|
||||||
|
|
||||||
|
## Что сделать долгосрочно
|
||||||
|
|
||||||
|
В кластере 1С сейчас **один rphost на всех локальных пользователей** — бутылочное горлышко. Через консоль администрирования сервера 1С увеличить количество рабочих процессов: 1 rphost на 8-12 сеансов. Это отдельная задача, не на горячую.
|
||||||
|
|
||||||
|
## Команды диагностики (для повтора)
|
||||||
|
|
||||||
|
WinRM-сессия с Mac:
|
||||||
|
```python
|
||||||
|
import winrm
|
||||||
|
s = winrm.Session('http://100.70.75.103:5985/wsman',
|
||||||
|
auth=('dttb','1qaz!QAZ'),
|
||||||
|
transport='basic')
|
||||||
|
```
|
||||||
|
|
||||||
|
Дельта CPU за 5 сек по процессам:
|
||||||
|
```powershell
|
||||||
|
$names='rmngr','rphost','netbird','sqlservr','ragent'
|
||||||
|
$first=@{}; $last=@{}
|
||||||
|
Get-Process | ?{$names -contains $_.Name} | %{ $first[$_.Name]=$_.CPU }
|
||||||
|
Start-Sleep 5
|
||||||
|
Get-Process | ?{$names -contains $_.Name} | %{ $last[$_.Name]=$_.CPU }
|
||||||
|
foreach($n in $names){ "$n delta=$($last[$n]-$first[$n])s" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Если у rmngr дельта >50 за 5 секунд — диагноз "rmngr-loop" подтверждён, делать рестарт службы.
|
||||||
|
|
||||||
|
См. также [[projects/buzharovo/server1c]].
|
||||||
75
decisions/2026-05-08-buzharovo-sql-native-backup.md
Normal file
75
decisions/2026-05-08-buzharovo-sql-native-backup.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-08
|
||||||
|
type: decision
|
||||||
|
tags: [decision, buzharovo, 1c, backup, mssql, effector-saver]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2026-05-08: Бэкап Бужарово 1С — переход с Effector Saver DT на native MS SQL Backup
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Вечером 2026-05-08, после починки rmngr-loop, регулярная задача `Бэкап 1Cv8` в Effector Saver Free 4.8/2 на `server1c.netbird.cloud` (Win 2012 R2, MSSQL 2012 SP4, 1С 8.3.27.1606) **отказывалась завершаться успешно**. Шесть подряд запусков (19:51 → 22:21) падали с одной и той же связкой ошибок:
|
||||||
|
|
||||||
|
1. `HRESULT=800401F3` — `V83.ComConnector` не зарегистрирован → Olег зарегистрировал через UI Effector Saver, переключился на 64-bit
|
||||||
|
2. `HRESULT=80004005` — "Администратор кластера не аутентифицирован" — у кластера 1С есть проверка авторизации, но **в кластере нет ни одного admin'а**, а добавить нельзя:
|
||||||
|
- В Серверной консоли 1С: `Локальный кластер → Администраторы` показывает количество=0, форма "Новый администратор" заполнена, но при OK **просит логин/пароль и не принимает agent-уровневый admin** (создан Olегом отдельно)
|
||||||
|
- Через COM `V83.COMConnector`: `AuthenticateAgent('admin', '1qaz!QAZ')` проходит, но `RegClusterAdmin` падает "пользователь не выполнил аутентификацию для требуемой операции" (chicken-and-egg: для создания первого cluster admin нужен уже cluster admin)
|
||||||
|
- Через `rac` с `--agent-user`: cluster operations не принимают agent-уровневую аутентификацию (design choice 1С)
|
||||||
|
|
||||||
|
3. `Ошибка исключительной блокировки информационной базы` — даже без cluster admin'а Effector Saver продолжает выгрузку, но не может получить эксклюзив, потому что в БД активные сессии. Особенно "вечная" сессия `КулябинПИ sid=4514, начат 12:55:42` — после моего рестарта службы 1С в 19:30 и последующих SQL `KILL` сессий, она **раз за разом возвращается** (вероятно, реально открытый где-то тонкий клиент Павла Ивановича + persistent state в `1CV8Clst.lst`).
|
||||||
|
|
||||||
|
## Что пробовали и почему не сработало
|
||||||
|
|
||||||
|
| Попытка | Результат |
|
||||||
|
|---|---|
|
||||||
|
| `regsvr32` x64 `comcntr.dll` | ✅ COM зарегистрирован, переключение Effector Saver на 64-bit убрало `800401F3` |
|
||||||
|
| `Restart-Service '1C:Enterprise 8.3 Server Agent'` | ✅ rmngr вылечен, но session 4514 в реестре кластера **persists** между рестартами |
|
||||||
|
| `KILL` SQL-сессий через `sa/Qwer1122334400` | ✅ временно (sess=0, locks=0), но 1С rphost восстанавливает соединения за 1-2 мин и сессия 4514 reanimates |
|
||||||
|
| `rac cluster admin register` без auth | ❌ "оператор не существует" |
|
||||||
|
| `RegClusterAdmin` через COM с `AuthenticateAgent` | ❌ "пользователь не аутентифицирован для требуемой операции" |
|
||||||
|
| GUI Серверной консоли 1С — добавить cluster admin | ❌ форма не сохраняет, требует cluster auth (которой нет) |
|
||||||
|
|
||||||
|
Тупик: **в кластере БД 1С нет cluster admin'а, и зарегистрировать первого нельзя ни через GUI, ни через rac, ни через COM.** Возможный единственный путь — обнулить `srvinfo\reg_1541\1CV8Clst.lst` целиком (потеря и админов, и регистрации ИБ — нужна перерегистрация ИБ с SQL params; рискованно).
|
||||||
|
|
||||||
|
## Решение: native MS SQL Backup
|
||||||
|
|
||||||
|
Effector Saver делает **DT-выгрузку через 1С Конфигуратор** (`1cv8.exe DESIGNER /DumpIB`), которая требует эксклюзив на ИБ. Это исторический способ для **файловых** ИБ. Для **клиент-серверных** ИБ на MS SQL правильный путь — **`BACKUP DATABASE` на уровне SQL Server**:
|
||||||
|
|
||||||
|
- ✅ **Online backup** — снимает копию во время работы, не требует эксклюзива
|
||||||
|
- ✅ Не зависит от 1С-кластера, cluster admin'а, активных сессий
|
||||||
|
- ✅ С `WITH COMPRESSION` файл сжимается ~3:1 (3.8 GB → 1.1 GB)
|
||||||
|
- ✅ Быстрее — у нас 2 секунды на 3.8 GB БД
|
||||||
|
- ✅ Восстанавливается одним запросом `RESTORE DATABASE`
|
||||||
|
|
||||||
|
Реализация:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BACKUP DATABASE [RitmUl]
|
||||||
|
TO DISK = N'C:\backup\RitmUl_<ts>.bak'
|
||||||
|
WITH FORMAT, INIT, NAME = N'RitmUl-Full',
|
||||||
|
SKIP, NOREWIND, NOUNLOAD, COMPRESSION, COPY_ONLY,
|
||||||
|
STATS = 5
|
||||||
|
```
|
||||||
|
|
||||||
|
`COPY_ONLY` — чтобы наш бэкап не ломал log chain если потом настроят differential/log backups.
|
||||||
|
|
||||||
|
Запускается через WinRM `python3 + System.Data.SqlClient` с LXC 139 `severny-les` (бот). Storage: `C:\backup\` на server1c (374 GB свободно).
|
||||||
|
|
||||||
|
## Артефакт
|
||||||
|
|
||||||
|
- **Первый успешный бэкап 2026-05-08:** `C:\backup\RitmUl_2026-05-08_2225.bak` (1105 MB)
|
||||||
|
- **Скрипт:** `/root/clawd/scripts/sql_native_backup.py` на LXC 139
|
||||||
|
|
||||||
|
## TODO (после возвращения Olега из Египта)
|
||||||
|
|
||||||
|
1. **Автоматизировать** — добавить cron на LXC 139 `severny-les`: каждый день в 03:00 МСК запускать `sql_native_backup.py`, ротировать (хранить N дней). Алерт в Telegram-группу при сбое.
|
||||||
|
2. **Ротация и трансфер** — настроить копирование `.bak` файлов на внешний носитель (Nextcloud / S3 / Gitea-LFS).
|
||||||
|
3. **Тест восстановления** — раз в N дней автоматически развернуть бэкап в тестовую БД и проверить целостность.
|
||||||
|
4. **Effector Saver** оставить как есть, не чинить (чинить cluster admin = разбирать `1CV8Clst.lst` бинарник, риск убить ИБ). Можно отключить регулярную задачу `Бэкап 1Cv8` в Effector Saver чтобы не плодились алерты "ошибка".
|
||||||
|
5. **TODO документировать** SQL creds в `projects/dttb/credentials.md` (см. блок Бужарово).
|
||||||
|
|
||||||
|
## Связанные
|
||||||
|
|
||||||
|
- [[projects/buzharovo/server1c]] — обновлён с SQL backup как новый канон
|
||||||
|
- [[projects/buzharovo/severny-les-bot]] — бот теперь умеет бэкапить через WinRM+SQL
|
||||||
|
- [[decisions/2026-05-07-buzharovo-1c-rmngr-loop-after-crash]] — про rmngr (отдельная история, починена)
|
||||||
77
decisions/2026-05-08-severny-les-bot-buzharovo.md
Normal file
77
decisions/2026-05-08-severny-les-bot-buzharovo.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-08
|
||||||
|
type: decision
|
||||||
|
tags: [decision, buzharovo, bot, openclaw, watchdog, telegram]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2026-05-08: Северный лес — отдельный AI-ассистент + watchdog для server1c (Бужарово)
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Олег уезжает в отпуск в Египет 2026-05-09 → 2026-05-22. На server1c (Бужарово, VDS 185.13.47.2 / NetBird 100.70.75.103) недавно (2026-05-07) был rmngr-loop, который лечится только `Restart-Service '1C:Enterprise 8.3 Server Agent (x86-64)' -Force` — ребут не помогает (см. [[decisions/2026-05-07-buzharovo-1c-rmngr-loop-after-crash]]).
|
||||||
|
|
||||||
|
Пока Олег в отпуске, нужно:
|
||||||
|
1. Чтобы кто-то узнавал когда сервер упал (Telegram-группа руководящего состава Северного леса);
|
||||||
|
2. Чтобы можно было дёрнуть восстановительное действие (`/approve restart_1c`) не дожидаясь возвращения Олега из Египта.
|
||||||
|
|
||||||
|
Через ~2 недели (после Египта) планируется миграция server1c с VDS на собственный сервер. Бот должен работать **до и после** миграции — поэтому он не на самом server1c, а на dttb-Proxmox через NetBird.
|
||||||
|
|
||||||
|
## Развилка: clawdbot vs openclaw vs другой watchdog
|
||||||
|
|
||||||
|
Рассматривались три варианта:
|
||||||
|
|
||||||
|
| Вариант | Плюсы | Минусы |
|
||||||
|
|---|---|---|
|
||||||
|
| **clawdbot** (как у [[projects/niikn/clawdbot-niikn|Максимки-Мауля]]) | Проверенный рецепт, проще | Старый стек, не обновляется. exec-approvals самописные. |
|
||||||
|
| **openclaw** (свежий стек 137) | Встроенный `exec-approvals.json` whitelist для shell-команд. Plugins, skills, делегирование на Opus 4.7 через Max. Свежий, активная разработка. | Жёсткая schema, есть тонкости (bonjour, IPv6, FakeIP DNS) — но они уже разобраны на 137. |
|
||||||
|
| **Голый watchdog без AI** | Минимум зависимостей. | Нет диагностики "почему упало". Невозможно дёрнуть `/restart_1c` через `/approve` — только ручной WinRM. |
|
||||||
|
|
||||||
|
**Решение:** **openclaw** — встроенный whitelist для shell-команд (`exec-approvals.json`) — это прямо то что нужно для `/approve` flow. Плюс Опус 4.7 через Max.
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
**Изоляция от Максимки (LXC 137):** не подвешиваем как доп.канал на 137 — если openclaw на 137 упадёт (а это бывает: bonjour, FakeIP, Kiro 402), упадут и алерты Бужарово. Для критичной мониторинг-задачи нужен **отдельный** инстанс.
|
||||||
|
|
||||||
|
**Хост:** новый LXC 139 на dttb (10.0.0.240, NetBird 100.70.212.78, Ubuntu 24.04, 2c/4GB/10GB).
|
||||||
|
|
||||||
|
**Два слоя независимых:**
|
||||||
|
1. **buzharovo-watchdog** — bash + curl→TG bot API напрямую, systemd timer 60s. **Не зависит от openclaw.** Если AI-часть упала, алерт всё равно дойдёт.
|
||||||
|
2. **openclaw 2026.5.7** — AI-помощник для диагностики и `/approve`-action'ов через WinRM.
|
||||||
|
|
||||||
|
**Алерт-уровни:**
|
||||||
|
- `OK` — всё доступно;
|
||||||
|
- `WARNING` — часть проверок упала;
|
||||||
|
- `WARNING_NETBIRD` — NetBird до server1c лежит, публично сервер виден;
|
||||||
|
- `CRITICAL` — сервер не отвечает ни публично, ни через NetBird.
|
||||||
|
|
||||||
|
Антиспам: алерт шлётся **только при смене уровня**, состояние в `/var/lib/severny-les/state.json`.
|
||||||
|
|
||||||
|
**WinRM-actions с подтверждением:**
|
||||||
|
- read-only без approval (`/status`, `/check_1c`, `/check_rmngr`);
|
||||||
|
- destructive с обязательным `/approve` от Олега `1292155421` (`/restart_1c`, `/kill_orphan_ragent`);
|
||||||
|
- ребута сервера НЕ даём (по опыту 2026-05-07 не помогает rmngr-loop).
|
||||||
|
|
||||||
|
## Превентивные правки на старте (уроки 137)
|
||||||
|
|
||||||
|
Все три "ловушки openclaw" пропатчены сразу:
|
||||||
|
1. `plugins.entries.bonjour.enabled = false` — против mDNS crash-loop (см. [[projects/dttb/openclaw#Crash-loop-каждые-40-сек]]).
|
||||||
|
2. `pct set 139 --nameserver '1.1.1.1 8.8.8.8'` + правка `/etc/resolv.conf` — против FakeIP DNS от 10.0.0.1.
|
||||||
|
3. `NODE_OPTIONS=--dns-result-order=ipv4first` в systemd unit — против IPv6-сбоев Telegram API.
|
||||||
|
|
||||||
|
systemd unit для openclaw — **system-level** (`/etc/systemd/system/openclaw-gateway.service`), а не `--user` как на 137. В LXC без user-session `systemctl --user` не работает (`Failed to connect to bus`).
|
||||||
|
|
||||||
|
## Что осталось сделать после возвращения Олега
|
||||||
|
|
||||||
|
1. **NetBird ACL** `severny-les` → `server1c` (порт 5985 TCP минимум) — без него WinRM-actions не работают, watchdog мониторит только публичные проверки.
|
||||||
|
2. **Добавить @bz_sl_bot в TG-группу** руководящего состава, узнать `chat_id`, обновить `/etc/severny-les/watchdog.env` `BZ_TG_CHAT` и `openclaw.json` `groupAllowFrom`.
|
||||||
|
3. После миграции server1c на свой сервер — обновить IP в `/root/clawd/INFRASTRUCTURE.md` и в `buzharovo-watchdog.sh`.
|
||||||
|
|
||||||
|
## Артефакты
|
||||||
|
|
||||||
|
- LXC 139 `severny-les` (10.0.0.240)
|
||||||
|
- TG bot `@bz_sl_bot` (token `8322860033:...`)
|
||||||
|
- Справочник: [[projects/buzharovo/severny-les-bot]]
|
||||||
|
- Persona: `/root/clawd/{IDENTITY,INFRASTRUCTURE,USER,SOUL,TOOLS,MEMORY,HEARTBEAT}.md`
|
||||||
|
- Скрипты: `/root/clawd/scripts/check_buzharovo.sh`, `winrm_lib.py`, `check_1c_service.py`, `check_rmngr_cpu.py`, `restart_1c_agent.py`, `kill_orphan_ragent.py`, `heartbeat.sh`
|
||||||
|
- Watchdog: `/usr/local/bin/buzharovo-watchdog.sh` + `.service` + `.timer` (60s); `netbird-watchdog` clone с 137 (2 мин)
|
||||||
|
- Whitelist: `/root/.openclaw/exec-approvals.json`
|
||||||
85
decisions/2026-05-14-buzharovo-watchdog-public-only.md
Normal file
85
decisions/2026-05-14-buzharovo-watchdog-public-only.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-14
|
||||||
|
type: decision
|
||||||
|
tags: [decision, buzharovo, watchdog, netbird, monitoring, openclaw]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2026-05-14: Watchdog Бужарово — только публичный канал, NetBird вынесен из alert level
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Олег в Египте, прислал что бот "сыпал ошибками вчера, попросил отключить мониторинг". Разведка:
|
||||||
|
|
||||||
|
1. **Все сервисы на LXC 139 живы** (`openclaw-gateway`, `buzharovo-watchdog.timer`, `netbird-watchdog.timer`, `netbird.service` — `active+enabled`). Олег ничего не отключал.
|
||||||
|
2. **Watchdog v1 правильно держал `WARNING_NETBIRD`** (последний алерт 13 мая ~19:32 МСК), антиспам корректный — повторных алертов не слал.
|
||||||
|
3. **Истинный источник "ошибок"** — `openclaw primary model = omniroute/cc/claude-opus-4-7` упёрся в лимит Max-подписки:
|
||||||
|
```
|
||||||
|
omniroute (cc/claude-opus-4-7) returned a billing error — your API key has run out of credits
|
||||||
|
400 [400]: You're out of extra usage. Add more at claude.ai/settings/usage and keep going.
|
||||||
|
```
|
||||||
|
Каждое сообщение в боте + каждое ночное `memory-core dreaming` (cron 03:00) → billing 400 → failover на `kr/claude-sonnet-4.5`. На клиенте часть запросов могла отдаться с ошибкой раньше чем failover отработал.
|
||||||
|
4. **Server1C NetBird daemon (Windows)** регулярно flap'ает, `last_seen=2026-05-13T08:24:26` — > 25 часов вне mesh, хотя сервер сам публично жив (ping + RDP 3389 OK).
|
||||||
|
|
||||||
|
## Решения
|
||||||
|
|
||||||
|
### Фикс 1: primary model → free Sonnet 4.5
|
||||||
|
|
||||||
|
`/root/.openclaw/openclaw.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"primary": "omniroute/kr/claude-sonnet-4.5",
|
||||||
|
"fallbacks": [
|
||||||
|
"omniroute/cc/claude-sonnet-4-6",
|
||||||
|
"omniroute/gh/claude-sonnet-4.5",
|
||||||
|
"omniroute/cc/claude-opus-4-7"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Hot-reload подхватился. Backup конфига — `/root/.openclaw/openclaw.json.bak.opus-billing-<ts>`.
|
||||||
|
|
||||||
|
**Возврат на Opus как primary — только после пополнения Max** или подключения второго конектора в OmniRoute.
|
||||||
|
|
||||||
|
### Фикс 2: watchdog v2 — только публичный канал
|
||||||
|
|
||||||
|
Переписан `/usr/local/bin/buzharovo-watchdog.sh`. Логика alert-level **больше не учитывает NetBird-проверки**:
|
||||||
|
|
||||||
|
- `OK` — `ping 185.13.47.2` ✓ + `TCP 3389` (RDP) ✓
|
||||||
|
- `DEGRADED` — один из публичных упал
|
||||||
|
- `CRITICAL` — оба публичных упали
|
||||||
|
|
||||||
|
NetBird-уровень (`ping 100.70.75.103` + `TCP 5985`) **только логируется** в `state.json` (`ping_nb`, `winrm_nb`), но не меняет level и не порождает алерт.
|
||||||
|
|
||||||
|
При первом алерте в новую сессию (prev_level=INIT) добавляется пометка:
|
||||||
|
> _NetBird до сервера сейчас лежит — это известная регулярная проблема со стороны Windows-сервера, не влияет на работу 1С для пользователей. Watchdog проверяет только публичный канал._
|
||||||
|
|
||||||
|
**Почему так:** NetBird daemon на Server1C (Windows 2012 R2) теряет mesh-сессию регулярно (memory `feedback_netbird_watchdog`). Лечится `Restart-Service netbird` через RDP — но это ручная операция, и поток алертов из-за этого был шумом, а не сигналом. Сервер для пользователей в Бужарово при этом работает — 1С локально доступна.
|
||||||
|
|
||||||
|
**Что теряем:** WinRM-actions (`check_1c_service.py`, `check_rmngr_cpu.py`, `restart_1c_agent.py`, `sql_native_backup.py`) идут через NetBird (`100.70.75.103:5985`). Когда NetBird падает — эти actions недоступны. Бот в группе об этом честно скажет: "Не могу проверить состояние службы 1С — туннель до сервера временно лежит". Восстанавливается после `Restart-Service netbird` на server1c через RDP.
|
||||||
|
|
||||||
|
**Что НЕ теряем:** алерты о реально критичных событиях (сервер физически лёг публично, сеть провайдера упала, RDP закрылся).
|
||||||
|
|
||||||
|
## Деплой
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pct push 139 wd.sh /usr/local/bin/buzharovo-watchdog.sh
|
||||||
|
chmod +x /usr/local/bin/buzharovo-watchdog.sh
|
||||||
|
chown root:root /usr/local/bin/buzharovo-watchdog.sh
|
||||||
|
echo "{}" > /var/lib/severny-les/state.json # force re-evaluate
|
||||||
|
# Manual run → level=OK, alert "Мониторинг включён" ушёл в группу
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновления в vault и persona
|
||||||
|
|
||||||
|
- `/root/clawd/MEMORY.md` на LXC 139 — добавлены уроки про Opus billing + watchdog v2
|
||||||
|
- [[projects/buzharovo/severny-les-bot]] — обновить ссылку на watchdog v2 (TODO)
|
||||||
|
- Этот decision-файл
|
||||||
|
|
||||||
|
## NetBird route 10.0.0.0/24 — попутно
|
||||||
|
|
||||||
|
Существующий route `Dom` (`cud7q73l0ubs73dr3gc0`) advertised через peer `pve 100.70.121.235 (Эстония)`, **disconnected**. Переключил routing peer на **`openclaw` (`d79s9g2fadhs739mihkg`)** через PUT `/api/routes/cud7q73l0ubs73dr3gc0`. Mac получил доступ к 10.0.0.0/24 через NetBird → openclaw → LAN.
|
||||||
|
|
||||||
|
## TODO (опционально, не сейчас)
|
||||||
|
|
||||||
|
- **Reverse SSH-туннель** server1c → severny-les для WinRM-actions без зависимости от NetBird. Server1C сам делает outbound SSH → LXC 139 пробрасывает 127.0.0.1:55985 → server1c:5985. WinRM-скрипты целятся в `localhost:55985`. Решает проблему "NetBird лёг — WinRM недоступен".
|
||||||
|
- **HTTPS WinRM (5986)** публично с certificate-pin'ом — альтернатива через интернет, но требует настройки SSL и хорошего firewall.
|
||||||
|
- **Ночной cron sql_native_backup.py** — автоматизация ежедневных бэкапов БД (TODO от 2026-05-08, см. соответствующий decision).
|
||||||
30
notes/ru-geoblocked-services.md
Normal file
30
notes/ru-geoblocked-services.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Сервисы, заблокированные для RU IP
|
||||||
|
|
||||||
|
Реестр CDN/сайтов/сервисов, которые блокируют российские IP — чтобы не искать причину каждый раз. Обход — через NetBird `Trance` group → finland exit-node, либо через App Store (Apple не блочит RU аккаунты), либо через GitHub Releases (не блочит).
|
||||||
|
|
||||||
|
## Скачивание / CDN
|
||||||
|
|
||||||
|
| Сервис | Домен | Симптом | Обход |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **superwhisper PRO API** | `*.superwhisper.com` (api endpoints) | Облачные модели возвращают пустой `result`, free tier 530 сек кончился | Купить PRO ($249) или альтернативы |
|
||||||
|
| **Spokenly CDN** | `cdn.spokenly.app` | TCP качает ~150 Б/с, dmg на 21 MB → 36 мин не докачивается | App Store / NetBird finland / VoiceInk вместо |
|
||||||
|
| **MS Windows Update / setup.exe** | `download.microsoft.com`, `dl.delivery.mp.microsoft.com` | Геоблок | NetBird `Trance` group → finland exit-node ([feedback_win11_unattended_upgrade](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_win11_unattended_upgrade.md)) |
|
||||||
|
| **Apple ID TJ** | account.apple.com (region change) | KYC требует TJ-резидентства | IPRoyal residential proxy (см. [2026-05-02-apple-id-tj-via-residential-proxy](../decisions/2026-05-02-apple-id-tj-via-residential-proxy.md)) |
|
||||||
|
| **НСПД (gov.ru, кадастр)** | `nspd.gov.ru`, customers_p2p_b16 | МТС B2B блокирует | NetBird route `2.63.246.0/24` → `pve-LionART` ([feedback_nspd_blocks_mts](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_nspd_blocks_mts.md)) |
|
||||||
|
| **gov.ru разные** | `*.gov.ru` | Несколько классов: FakeIP, WAF-MTS, ru-trust, ГОСТ-mTLS, anti-bot | См. playbook `projects/niikn/govru-quickfix-playbook.md` |
|
||||||
|
|
||||||
|
## Принцип
|
||||||
|
- Если при первом запуске любой `brew install --cask`, `curl`, `wget`, `npm install` падает по timeout с CDN — **сразу** проверять RU-блок (curl с timeout 5 + look at recv speed).
|
||||||
|
- Не дёргать `--retry` / `-C -` — потеря времени.
|
||||||
|
- Default решения: 1) App Store, 2) GitHub Releases, 3) NetBird finland exit-node, 4) IPRoyal residential.
|
||||||
|
|
||||||
|
## Проверка скорости (быстрый snippet)
|
||||||
|
```bash
|
||||||
|
curl -sS -o /dev/null --max-time 5 -w "HTTP %{http_code} speed=%{speed_download}B/s\n" <URL>
|
||||||
|
```
|
||||||
|
Если speed < 50 KB/s на CDN — почти наверняка геоблок/throttle.
|
||||||
|
|
||||||
|
## Альтернативы по категориям
|
||||||
|
- **Mac dictation app** (вместо superwhisper / Spokenly из CDN): VoiceInk из GitHub Releases (open source, $40 lifetime или собрать самому), Apple Dictation встроенная.
|
||||||
|
- **Win update** (MS блок): NetBird Trance → finland.
|
||||||
|
- **gov.ru**: см. govru playbook.
|
||||||
66
projects/_index.md
Normal file
66
projects/_index.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-07
|
||||||
|
type: index
|
||||||
|
source: scripts/kb-objects-map.py
|
||||||
|
tags: [index, registry, objects, netbird]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Реестр объектов и netbird-пиров
|
||||||
|
|
||||||
|
Авто-сгенерировано `2026-05-07T07:10` из [[dttb/netbird-inventory]] + frontmatter в `projects/`.
|
||||||
|
**Не править вручную** — перепишется. Источник правды — frontmatter в каждом README.
|
||||||
|
|
||||||
|
- Проектов: **21**, из них с netbird-привязкой: **9**
|
||||||
|
- NetBird-пиров без projects-страницы: **14** (TODO — создать стабы)
|
||||||
|
|
||||||
|
## Проекты с netbird-привязкой
|
||||||
|
|
||||||
|
| ID | Имена | NetBird IP | OS | Город | Файл | Статус |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| benilux | Benilux, OpenWrt Benilux, benilux | 100.70.207.97 | OpenWrt 24.10.3 | Istra | [[projects/benilux/README]] | stub |
|
||||||
|
| dttb | Cups-Server, HomeLab, MacBook-Pro, OpenWrt 1, Work Server dttb | 100.70.242.212, 100.70.239.211, 100.70.200.150, 100.70.152.70, 100.70.92.138, 100.70.0.15, 100.70.121.235, 100.70.191.161, 100.70.219.93, 100.70.57.167, 100.70.12.3, 100.70.211.159, 100.70.121.49, 100.70.128.10, 100.70.100.82 | Darwin 26.3.1, Debian GNU/Linux 12, Debian GNU/Linux 13, OpenWrt 24.10.3, Ubuntu 22.04, Ubuntu 24.04, Windows Server 2025, iOS 26.3.1, iPadOS 18.6.2 | Helsinki, Istra, Moscow | [[projects/dttb/README]] | active |
|
||||||
|
| lipki | Lipki, OpenWrt_Lipki, PSP_Network, lipki, Антон | 100.70.35.234 | OpenWrt 24.10.3 | Istra | [[projects/lipki/README]] | active |
|
||||||
|
| mmfb | DESKTOP-UFULDJQ, LionART, Yuri Vitalievich, mmfb, pve LionART | 100.70.128.49 | Debian GNU/Linux 12 | Istra | [[projects/mmfb/README]] | active |
|
||||||
|
| niikn | Cloud-NIIKN New niikn.com, DESKTOP-IC5A0K2 M.Maul, Kripto-ARM, M.Maul, Maxim Maul | 100.70.117.21, 100.70.145.223, 100.70.120.229, 100.70.178.190 | Debian GNU/Linux 12, Ubuntu 24.04, Windows 10, Windows 11 | Istra | [[projects/niikn/README]] | active |
|
||||||
|
| openwrt-4 | OpenWrt_4, openwrt-4, openwrt4 | 100.70.235.2 | OpenWrt 24.10.3 | Moscow | [[projects/openwrt-4/README]] | stub |
|
||||||
|
| sergey | OpenWrt_Sergey, Sergey, sergey, Одинцово | 100.70.110.164 | OpenWrt 24.10.3 | Odintsovo | [[projects/sergey/README]] | stub |
|
||||||
|
| vishnevyy-sad | OpenWrt Вишневый сад ( Константин ), vishnevyy-sad, Вишневый сад, Вишнёвый сад, Константин | 100.70.152.137 | OpenWrt 24.10.3 | Moscow | [[projects/vishnevyy-sad/README]] | stub |
|
||||||
|
| znamenskoye | 89-111-140-86.swtest.ru, OpenWrt_Znamenskoe_Home, OpenWrt_ohothozyistvo, VPS 89.111.140.86, Znamenskoe | 100.70.93.36, 100.70.54.204, 100.70.63.67, 100.70.137.181, 100.70.100.155 | Debian GNU/Linux 11, OpenWrt 21.02.1, OpenWrt 24.10.3, Ubuntu 24.04 | Helsinki, Istra, Moscow | [[projects/znamenskoye/README]] | active |
|
||||||
|
|
||||||
|
## Проекты без netbird-привязки
|
||||||
|
|
||||||
|
| ID | Тип | Файл | Статус |
|
||||||
|
|---|---|---|---|
|
||||||
|
| all-projects-summary | project-note | [[projects/all-projects-summary]] | unknown |
|
||||||
|
| bitrix-sites | project-note | [[projects/bitrix-sites]] | unknown |
|
||||||
|
| buzharovo | project | [[projects/buzharovo/README]] | active |
|
||||||
|
| clawdbot-bots | project-note | [[projects/clawdbot-bots]] | unknown |
|
||||||
|
| glavtorg | project | [[projects/glavtorg/README]] | active |
|
||||||
|
| homelab-proxmox | project-note | [[projects/homelab-proxmox]] | unknown |
|
||||||
|
| infrastructure-overview | project-note | [[projects/infrastructure-overview]] | unknown |
|
||||||
|
| krasnogorsk | project | [[projects/krasnogorsk/README]] | active |
|
||||||
|
| nextcloud | project-note | [[projects/nextcloud]] | unknown |
|
||||||
|
| unresolved-issues | project-note | [[projects/unresolved-issues]] | unknown |
|
||||||
|
| video-surveillance | project-note | [[projects/video-surveillance]] | unknown |
|
||||||
|
| zelenograd | project | [[projects/zelenograd/README]] | active |
|
||||||
|
|
||||||
|
## NetBird-пиры без projects-страницы — TODO
|
||||||
|
|
||||||
|
Эти пиры есть в инвентаре, но у них нет своей карточки в `projects/`. Бот не сможет ответить на «найди мне X» — нет файла. Нужно создать стабы (Фаза 4 плана).
|
||||||
|
|
||||||
|
| Имя в NetBird | IP | OS | Город | Версия |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `DESKTOP-2IOQS54` | 100.70.82.83 | Windows 10 | Saransk | 0.50.3 |
|
||||||
|
| `DESKTOP-5RGAUUG` | 100.70.221.26 | Windows 11 | Istra | |
|
||||||
|
| `DESKTOP-9VJ949T скоморохова` | 100.70.44.183 | Windows 10 | St Petersburg | |
|
||||||
|
| `DESKTOP-AGBMLPN` | 100.70.0.106 | Windows 11 | Helsinki | 0.66.2 |
|
||||||
|
| `DESKTOP-HL0BB05` | 100.70.235.80 | Windows 11 | Lipetsk | 0.59.7 |
|
||||||
|
| `DESKTOP-LBD73OR` | 100.70.78.170 | Windows 11 | Astana | |
|
||||||
|
| `KOMPUTER` | 100.70.83.120 | Windows 11 | St Petersburg | |
|
||||||
|
| `Kolyadenko` | 100.70.146.58 | Windows 10 | | |
|
||||||
|
| `LAPTOP-3IR5EA9J` | 100.70.149.179 | Windows 11 | Mérida | |
|
||||||
|
| `MacBook-Pro-Vera.local` | 100.70.252.228 | Darwin 26.3.1 | St Petersburg | |
|
||||||
|
| `MastaNotebook` | 100.70.116.166 | Windows 11 | Moscow | |
|
||||||
|
| `WIN-BC0OTBOBBCH` | 100.70.181.152 | Windows Server 2025 | Moscow | |
|
||||||
|
| `iPhone-netbird` | 100.70.18.13 | iOS 26.3.1 | | |
|
||||||
|
| `Денис Тихая` | 100.70.155.107 | Windows 10 | | |
|
||||||
35
projects/benilux/README.md
Normal file
35
projects/benilux/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: project
|
||||||
|
status: stub
|
||||||
|
tags: [object, openwrt, netbird, client, todo, istra]
|
||||||
|
aliases: [Benilux, benilux, "OpenWrt Benilux"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Benilux — клиентский OpenWrt в Истре
|
||||||
|
|
||||||
|
**Стаб-заметка**, фактов мало. Создан, чтобы Максимка (FTS) не терял этот объект.
|
||||||
|
|
||||||
|
## Что известно (из NetBird)
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Имя пира | `OpenWrt Benilux` |
|
||||||
|
| NetBird IP | `100.70.207.97` |
|
||||||
|
| Локация (NetBird) | Istra |
|
||||||
|
| OpenWrt | 24.10.3 |
|
||||||
|
| NetBird agent | 0.59.13 |
|
||||||
|
| Группы | `All`, `OpenWRT VPN` |
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
|
||||||
|
- [ ] Что такое «Benilux» — название объекта / магазина / клиента?
|
||||||
|
- [ ] Контактное лицо
|
||||||
|
- [ ] Точный адрес в Истре
|
||||||
|
- [ ] Что роутер обслуживает
|
||||||
|
- [ ] LAN, провайдер, WAN
|
||||||
|
- [ ] Пароль/ключ доступа
|
||||||
|
|
||||||
|
## Aliases для FTS
|
||||||
|
|
||||||
|
`Benilux`, `OpenWrt Benilux`, `100.70.207.97`, `Истра-Benilux`.
|
||||||
27
projects/buzharovo/README.md
Normal file
27
projects/buzharovo/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [buzharovo, client, 1c, windows-server]
|
||||||
|
aliases: [Бужарово, buzharovo, Server1C]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Бужарово
|
||||||
|
|
||||||
|
Боевой сервер 1С организации в селе Бужарово.
|
||||||
|
|
||||||
|
## Хосты
|
||||||
|
|
||||||
|
- **Server1C** — Windows Server 2012 R2, 1С:Предприятие 8.3.27.1606 + MSSQL.
|
||||||
|
- Публичный IP: 185.13.47.2 (RDP:3389)
|
||||||
|
- Netbird: 100.70.75.103 (server1c.netbird.cloud)
|
||||||
|
- Подробности и runbook: [[projects/buzharovo/server1c]]
|
||||||
|
|
||||||
|
- **Северный лес — AI-ассистент** — LXC 139 на dttb-Proxmox, openclaw + watchdog для server1c.
|
||||||
|
- LAN: 10.0.0.240, NetBird: 100.70.212.78
|
||||||
|
- Telegram: `@bz_sl_bot` ("ИИ Ассистент Бужарово ( Северный лес )")
|
||||||
|
- Справочник и runbook: [[projects/buzharovo/severny-les-bot]]
|
||||||
|
- Решение о создании: [[decisions/2026-05-08-severny-les-bot-buzharovo]]
|
||||||
|
|
||||||
|
## Известные рецепты
|
||||||
|
|
||||||
|
- **rmngr-loop после crash** (07.05.2026): тормоза локальных пользователей → `Restart-Service '1C:Enterprise 8.3 Server Agent (x86-64)' -Force`. Полный ребут сервера НЕ помогает. Полный разбор: [[decisions/2026-05-07-buzharovo-1c-rmngr-loop-after-crash]].
|
||||||
91
projects/buzharovo/server1c.md
Normal file
91
projects/buzharovo/server1c.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
date: 2026-04-17
|
||||||
|
type: project
|
||||||
|
tags: [dttb]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Server1C — Сервер 1С в Бужарово
|
||||||
|
|
||||||
|
## Подключение
|
||||||
|
- **Публичный IP:** 185.13.47.2 (RDP:3389)
|
||||||
|
- **Netbird IP:** 100.70.75.103 (server1c.netbird.cloud)
|
||||||
|
- **WinRM:** порт 5985, basic auth
|
||||||
|
- **Учётка:** dttb / 1qaz!QAZ
|
||||||
|
- **OS:** Windows Server 2012 R2 (6.3.9600)
|
||||||
|
- **Hostname:** Server1C
|
||||||
|
- **Локация:** Бужарово
|
||||||
|
|
||||||
|
## 1С:Предприятие
|
||||||
|
Три службы агента:
|
||||||
|
1. `1C:Enterprise 8.3 Server Agent` — StartType: Automatic
|
||||||
|
2. `1C:Enterprise 8.3 Server Agent (x86-64)` — StartType: Automatic
|
||||||
|
3. `RagentServer_8327` — версия 8.3.27.1606, StartType: Automatic
|
||||||
|
|
||||||
|
### Решено: конфликт служб при загрузке (2026-04-16)
|
||||||
|
**Проблема:** 3 службы с Automatic стартовали одновременно, боролись за порты 1540/1541.
|
||||||
|
- Служба 8.3.18 (x86) — бинарник удалён, падала с ошибкой "файл не найден"
|
||||||
|
- RagentServer_8327 — дубликат без параметров, таймаут на портах
|
||||||
|
- Рабочая: `1C:Enterprise 8.3 Server Agent (x86-64)` (8.3.27.1606)
|
||||||
|
|
||||||
|
**Решение:** отключены лишние службы (Disabled), оставлена только x86-64.
|
||||||
|
|
||||||
|
### Решено: rmngr-loop после грязного ребута (2026-05-07)
|
||||||
|
**Симптомы:** утром локальные пользователи в Бужарово жалуются на резко тормозящую 1С. Удалённое подключение к 1С через NetBird ни при чём — проблема видна и в локальной сети.
|
||||||
|
|
||||||
|
**Диагноз:**
|
||||||
|
- Сервер ушёл в crash в 09:05 (Event 41 Kernel-Power: rebooted without cleanly shutting down + EventLog 6008: previous shutdown was unexpected).
|
||||||
|
- После загрузки `rmngr.exe` (менеджер кластера 1С) держит ~1.7 ядра постоянно вместо штатных <2% в idle. `rphost`, `sqlservr`, диски, сеть — в норме.
|
||||||
|
- `rac.exe localhost:1540 cluster list` отваливается с "ошибка соединения с сервером" — admin-канал rmngr повис, кластер не отвечает на управление.
|
||||||
|
- `netbird.exe` параллельно крутит 1.4 ядра — это reconnect-loop как побочка crash, после ребута сам приходит в норму.
|
||||||
|
|
||||||
|
**Рецепт:**
|
||||||
|
1. Замерить дельту CPU через `Get-Process | %{$_.CPU}` за 5 секунд — если у `rmngr` >50% одного ядра в idle, диагноз подтверждён.
|
||||||
|
2. **Полный ребут сервера НЕ помогает** — после загрузки rmngr опять начинает жрать CPU. На свежезагруженном сервере uptime=0.6 min: rmngr уже на 178% ядра.
|
||||||
|
3. **Помогает рестарт службы:** `Restart-Service -Name '1C:Enterprise 8.3 Server Agent (x86-64)' -Force`. После рестарта rmngr возвращается к 3% ядра. Все активные сеансы пользователей вылетят — нужно их предупредить.
|
||||||
|
4. После рестарта может остаться орфан-процесс `ragent` от прошлого старта (не слушает 1540, висит). Прибить вручную: `Stop-Process -Id <pid> -Force`.
|
||||||
|
|
||||||
|
**Why:** причина рута rmngr-loop неясна — возможно повреждение кэша `srvinfo`, регресс 8.3.27.1606, или Disabled-служба `RagentServer_8327` мешает первому запуску ragent. Если повторится — смотреть `C:\Program Files\1cv8\srvinfo\reg_*\1Cv8FTLog\` на ошибки.
|
||||||
|
|
||||||
|
**Долгосрочно:** настроить несколько `rphost` в кластере (по одному на 8-12 сеансов) — сейчас один rphost на всех локальных юзеров = бутылочное горлышко.
|
||||||
|
|
||||||
|
## MS SQL Server (для server1c\RitmUl)
|
||||||
|
|
||||||
|
- **Instance:** `localhost` (default, MSSQL11 = SQL Server 2012 SP4)
|
||||||
|
- **SA / Qwer1122334400** (полные права на все БД)
|
||||||
|
- **БД:** `RitmUl` (~3.8 GB), также есть `Accounting`, `Retail_2021`, `Retail_2021demo`
|
||||||
|
- **Connection string:** `Server=localhost;Database=master;User Id=sa;Password=Qwer1122334400;`
|
||||||
|
|
||||||
|
## Бэкапы — native SQL, не Effector Saver
|
||||||
|
|
||||||
|
**Канон от 2026-05-08:** `BACKUP DATABASE` через SQL Server, не DT-выгрузка через Effector Saver. Подробности и причины — в [[decisions/2026-05-08-buzharovo-sql-native-backup]].
|
||||||
|
|
||||||
|
Команда:
|
||||||
|
```sql
|
||||||
|
BACKUP DATABASE [RitmUl]
|
||||||
|
TO DISK = N'C:\backup\RitmUl_<timestamp>.bak'
|
||||||
|
WITH FORMAT, INIT, COMPRESSION, COPY_ONLY, STATS = 5
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Папка:** `C:\backup\` (на C: было 374 GB свободно на 2026-05-08)
|
||||||
|
- **Время:** ~2 сек на 3.8 GB БД
|
||||||
|
- **Размер:** ~30% от оригинала (3.8 GB → 1.1 GB сжатый)
|
||||||
|
- **Online:** не требует отключения пользователей, не требует cluster admin'а 1С
|
||||||
|
- **Скрипт:** `/root/clawd/scripts/sql_native_backup.py` на LXC 139 (severny-les bot)
|
||||||
|
|
||||||
|
## Кластер 1С — известные проблемы
|
||||||
|
|
||||||
|
### Cluster admin отсутствует, и его нельзя добавить
|
||||||
|
Серверная консоль 1С → `Локальный кластер → Администраторы` показывает 0, но при попытке создать через GUI требует логин cluster admin'а (которого нет) — chicken-and-egg. Через `rac` и `V83.COMConnector` — то же самое. Agent admin (создан 2026-05-08, `admin/1qaz!QAZ` на уровне `(*)Server1C → Администраторы`) **не поднимает права на cluster operations**.
|
||||||
|
|
||||||
|
**Последствия:**
|
||||||
|
- Effector Saver задача `Бэкап 1Cv8` падает с `Администратор кластера не аутентифицирован (HRESULT=80004005)` → не может вызвать `TerminateSession` → не может получить эксклюзив на ИБ.
|
||||||
|
- Все cluster operations (просмотр сессий, kill сессий, блокировка соединений) недоступны через API.
|
||||||
|
|
||||||
|
**Что НЕ помогло:** SQL `KILL` сессий через sa — 1С rphost восстанавливает соединения за 1-2 мин, и persistent session_id (например `КулябинПИ 4514` от 12:55:42 в день 2026-05-08) reanimate.
|
||||||
|
|
||||||
|
**Workaround:** SQL native backup (см. выше) — обходит всю эту историю с эксклюзивом.
|
||||||
|
|
||||||
|
**Как лечить (не сделано, рискованно):** обнулить `C:\Program Files\1cv8\srvinfo\reg_1541\1CV8Clst.lst` → потеряются и админы и регистрация ИБ → перерегистрировать ИБ через SQL params (`SA/Qwer1122334400`, host `localhost`, db `RitmUl`).
|
||||||
|
|
||||||
|
### V83.COMConnector x64 зарегистрирован
|
||||||
|
2026-05-08 я через `regsvr32` зарегистрировал `C:\Program Files\1cv8\8.3.27.1606\bin\comcntr.dll` в `HKLM\SOFTWARE\Classes\V83.COMConnector` (только x64; x86 платформа на сервере не установлена). В Effector Saver вручную переключено на "64-разрядный V83.ComConnector" → `HRESULT=800401F3` ушёл.
|
||||||
173
projects/buzharovo/severny-les-bot.md
Normal file
173
projects/buzharovo/severny-les-bot.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-08
|
||||||
|
type: project
|
||||||
|
tags: [buzharovo, bot, openclaw, watchdog, monitoring]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Северный лес — AI-ассистент для server1c
|
||||||
|
|
||||||
|
> Создан 2026-05-08, до отпуска Олега в Египте (2026-05-09 → 2026-05-22). Цель — пока Олега нет, кто-то на стороне Бужарово видит в Telegram-группе что происходит с сервером и может ткнуть `/approve` на восстановительные действия.
|
||||||
|
|
||||||
|
## Что это
|
||||||
|
|
||||||
|
Отдельный AI-бот на стеке **openclaw 2026.5.7**, заточен только под мониторинг и реагирование на инциденты сервера 1С в Бужарово. Watchdog-слой работает независимо от openclaw и шлёт алерты в Telegram напрямую через bot API — даже если AI-часть упала, уведомления всё равно дойдут.
|
||||||
|
|
||||||
|
**Не путать с:**
|
||||||
|
- LXC 137 [[projects/dttb/openclaw|Максимка]] — основной AI-бот Олега, обслуживает всю инфраструктуру.
|
||||||
|
- LXC 114 [[projects/niikn/clawdbot-niikn|Максимка-Мауля]] — бот в НИИКН для Максима Мауля.
|
||||||
|
|
||||||
|
## Расположение
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Proxmox LXC | **139** (hostname `severny-les`) |
|
||||||
|
| IP LAN | `10.0.0.240` |
|
||||||
|
| NetBird IP | `100.70.212.78` (FQDN `severny-les.netbird.cloud`) |
|
||||||
|
| Ресурсы | 2 cores / 4 GB RAM / 10 GB disk (Ubuntu 24.04) |
|
||||||
|
| Доступ | `sshpass -p '1qaz!QAZ' ssh root@10.0.0.250 "pct exec 139 -- bash"` |
|
||||||
|
|
||||||
|
## Telegram
|
||||||
|
|
||||||
|
- **Bot username:** `@bz_sl_bot`
|
||||||
|
- **Display name:** "ИИ Ассистент Бужарово ( Северный лес )"
|
||||||
|
- **Bot ID:** `8322860033`
|
||||||
|
- **Token:** см. `/root/.openclaw/openclaw.json` → `channels.telegram.botToken` (или `/etc/severny-les/watchdog.env`)
|
||||||
|
- **Allowlist:** Олег `1292155421` (DM). Группа — пока пустая, обновим `groupAllowFrom` когда бот будет добавлен в TG-группу руководящего состава.
|
||||||
|
|
||||||
|
## Стек и сервисы
|
||||||
|
|
||||||
|
### openclaw 2026.5.7 (system-level systemd)
|
||||||
|
- **Конфиг:** `/root/.openclaw/openclaw.json`
|
||||||
|
- **Workspace:** `/root/clawd/` (IDENTITY/INFRASTRUCTURE/USER/SOUL/MEMORY/TOOLS/HEARTBEAT.md + `scripts/`)
|
||||||
|
- **Unit:** `/etc/systemd/system/openclaw-gateway.service` (НЕ `--user` как на 137 — в LXC без sessions это не работает)
|
||||||
|
- **Gateway port:** `18790` (на 137 — 18789, чтобы не путать)
|
||||||
|
- **Primary model:** `omniroute/cc/claude-opus-4-7` (Opus 4.7 через Max), fallbacks: `kr/claude-sonnet-4.5` → `cc/claude-sonnet-4-6`
|
||||||
|
- **Plugin bonjour:** disabled (превентивно от mDNS-крэшей в LXC)
|
||||||
|
- **NODE_OPTIONS:** `--dns-result-order=ipv4first` (превентивно от Telegram IPv6-сбоев)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl status openclaw-gateway.service
|
||||||
|
systemctl restart openclaw-gateway.service
|
||||||
|
journalctl -u openclaw-gateway.service -n 50 --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
### buzharovo-watchdog (system-level systemd timer, каждые 60s)
|
||||||
|
- **Скрипт:** `/usr/local/bin/buzharovo-watchdog.sh`
|
||||||
|
- **Unit:** `/etc/systemd/system/buzharovo-watchdog.{service,timer}`
|
||||||
|
- **Env:** `/etc/severny-les/watchdog.env` (TG token + chat_id)
|
||||||
|
- **State:** `/var/lib/severny-les/state.json` — антиспам (алерт только при смене уровня)
|
||||||
|
|
||||||
|
Уровни: `OK` / `WARNING` (часть проверок упала) / `WARNING_NETBIRD` (NB до server1c лежит, публично OK) / `CRITICAL` (сервер недоступен и публично, и через NetBird).
|
||||||
|
|
||||||
|
Алерт уходит в TG **напрямую** через `https://api.telegram.org/bot.../sendMessage`, мимо openclaw. Если openclaw упал — алерт всё равно придёт.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -t buzharovo-watchdog --since "1 hour ago" -n 30
|
||||||
|
systemctl list-timers buzharovo-watchdog.timer
|
||||||
|
# Тестовый прогон:
|
||||||
|
set -a; . /etc/severny-les/watchdog.env; set +a; /usr/local/bin/buzharovo-watchdog.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### netbird-watchdog (как на LXC 132/137)
|
||||||
|
- **Скрипт:** `/usr/local/bin/netbird-watchdog.sh` (порт с LXC 137)
|
||||||
|
- **Unit:** `/etc/systemd/system/netbird-watchdog.{service,timer}` (каждые 2 мин)
|
||||||
|
- При `Relays: 0/N` или `Peers: 0/N` (когда N>0) и Management=Connected — `systemctl restart netbird` (минимум 5 мин между рестартами).
|
||||||
|
|
||||||
|
### Heartbeat (cron */5)
|
||||||
|
- `/etc/cron.d/severny-les-heartbeat` → `/root/clawd/scripts/heartbeat.sh` пишет timestamp в `/tmp/severny-les-heartbeat`
|
||||||
|
- buzharovo-watchdog проверяет: если heartbeat старше 600s — добавляет в алерт строчку про "openclaw молчит" (анти-спам: не чаще 1 раз в час).
|
||||||
|
|
||||||
|
## Что бот может делать (TOOLS.md)
|
||||||
|
|
||||||
|
### Без `/approve` (read-only)
|
||||||
|
- `/status` — общий статус (ping/порты публично + через NetBird, состояние 1С службы, CPU rmngr)
|
||||||
|
- `/check_1c` — три 1С службы через WinRM (Running/Stopped/Disabled)
|
||||||
|
- `/check_rmngr` — детектор rmngr-loop (CPU rmngr.exe за 5 секунд, >50% = диагноз)
|
||||||
|
|
||||||
|
### С `/approve` от Олега `1292155421`
|
||||||
|
- `/restart_1c` → `Restart-Service '1C:Enterprise 8.3 Server Agent (x86-64)' -Force`. Все сеансы 1С вылетят. Это рецепт от 2026-05-07 rmngr-loop ([[decisions/2026-05-07-buzharovo-1c-rmngr-loop-after-crash]]).
|
||||||
|
- `/kill_orphan_ragent` — найти `ragent.exe` без LISTENING на 1540 и `Stop-Process -Force` (зомби после restart_1c).
|
||||||
|
|
||||||
|
Ребут сервера НЕ даём — по опыту 2026-05-07 ребут rmngr-loop не помогает, простой добавляет.
|
||||||
|
|
||||||
|
`exec-approvals.json` лежит в `/root/.openclaw/exec-approvals.json` — это whitelist для openclaw. Команды не из whitelist openclaw отказывается выполнять.
|
||||||
|
|
||||||
|
## WinRM на server1c
|
||||||
|
|
||||||
|
- **Адрес:** `100.70.75.103:5985` (через NetBird, basic, http) — публично 5985 закрыт
|
||||||
|
- **Учётка:** `dttb` / `1qaz!QAZ`
|
||||||
|
- **Python:** `pywinrm` уже стоит. Обёртка — `/root/clawd/scripts/winrm_lib.py`.
|
||||||
|
|
||||||
|
⚠️ **На 2026-05-08 NetBird ACL до server1c пока НЕ работает** — handshake не идёт (`Required key not available`). Олегу нужно в NetBird Dashboard разрешить `severny-les` доступ к `server1c` хотя бы на порты 5985 (WinRM) + 1 ICMP + 3389 (опц.). До этого WinRM-actions не работают, watchdog мониторит только публичные проверки.
|
||||||
|
|
||||||
|
## Скрипты в `/root/clawd/scripts/`
|
||||||
|
|
||||||
|
| Скрипт | Что делает |
|
||||||
|
|---|---|
|
||||||
|
| `check_buzharovo.sh` | bash, общий статус (ping+порты), без WinRM |
|
||||||
|
| `winrm_lib.py` | обёртка pywinrm, переиспользуют все py-скрипты |
|
||||||
|
| `check_1c_service.py` | 3 службы 1С через WinRM |
|
||||||
|
| `check_rmngr_cpu.py` | детектор rmngr-loop |
|
||||||
|
| `restart_1c_agent.py` | `Restart-Service '1C:...'` (требует /approve) |
|
||||||
|
| `kill_orphan_ragent.py` | убить зомби ragent (требует /approve) |
|
||||||
|
| `heartbeat.sh` | cron, пишет timestamp |
|
||||||
|
| `sql_native_backup.py` | **бэкап ИБ через MS SQL Server `BACKUP DATABASE` с компрессией** (см. [[decisions/2026-05-08-buzharovo-sql-native-backup]]). Online, ~2 сек на 3.8 GB, не требует cluster admin 1С |
|
||||||
|
|
||||||
|
## Сценарии работы
|
||||||
|
|
||||||
|
### Олег в отпуске, ночью упал rmngr
|
||||||
|
1. Watchdog на 60-й секунде заметил: WinRM `1C:Enterprise 8.3 Server Agent (x86-64)` всё ещё Running, **но** `check_rmngr_cpu.py` вернул `RMNGR_LOOP`.
|
||||||
|
2. Watchdog шлёт в TG-группу: 🚨 Северный лес — rmngr-loop на server1c. CPU rmngr 67%. Предлагаю `/restart_1c`.
|
||||||
|
3. Дежурный из руководящего состава отвечает в группу, бот ему: "Ок, но нужно `/approve` от Олега. Можете позвонить ему? Или подождать утра — время до критичного простоя ~2 часа."
|
||||||
|
4. Олег с пляжа делает `/approve restart_1c` → бот выполняет → отписывается в группу что прошло.
|
||||||
|
|
||||||
|
### Сервер недоступен публично
|
||||||
|
1. Watchdog: 3 подряд провала ping `185.13.47.2` за 3 минуты + RDP не отвечает.
|
||||||
|
2. Шлёт в группу: 🚨 server1c НЕДОСТУПЕН. Не отвечает ни публично, ни через NetBird. Похоже сервер лёг или сеть провайдера.
|
||||||
|
3. Бот ничего не может сделать сам — это VDS у внешнего провайдера. Эскалирует на Олега, ждёт ручного вмешательства.
|
||||||
|
|
||||||
|
### NetBird до server1c упал, публично всё OK
|
||||||
|
1. Watchdog: ping `185.13.47.2` ОК, ping `100.70.75.103` нет.
|
||||||
|
2. Шлёт: ⚠️ NetBird до server1c лежит. Публично сервер виден. WinRM-actions недоступны.
|
||||||
|
3. Сервер сам по себе работает — пользователи в Бужарово 1С видят. Но бот не может делать диагностику/рестарты пока NetBird не починен.
|
||||||
|
|
||||||
|
## Чек-лист после возвращения Олега из Египта
|
||||||
|
|
||||||
|
- [ ] Прописать в NetBird Dashboard ACL `severny-les` → `server1c` (5985 TCP минимум).
|
||||||
|
- [ ] Добавить @bz_sl_bot в TG-группу руководящего состава Северного леса; узнать `chat_id` группы.
|
||||||
|
- [ ] Обновить `/etc/severny-les/watchdog.env` BZ_TG_CHAT на групповой chat_id.
|
||||||
|
- [ ] Обновить `/root/.openclaw/openclaw.json` `channels.telegram.groupAllowFrom` — добавить chat_id группы.
|
||||||
|
- [ ] Сделать smoke-test `/restart_1c` (на тестовых выходных, не в боевые часы) — убедиться что openclaw реально дёргает скрипт после `/approve`.
|
||||||
|
- [ ] После миграции server1c на свой сервер — обновить IP в `/root/clawd/INFRASTRUCTURE.md` и в watchdog-скрипте.
|
||||||
|
|
||||||
|
## Известные ограничения и риски
|
||||||
|
|
||||||
|
- **NetBird ACL** на момент создания не пускает severny-les к server1c. Watchdog мониторит публично + через NetBird пингом; WinRM-команды (диагностика 1С, рестарт службы) не работают пока ACL не настроен.
|
||||||
|
- **Группа TG ещё не настроена** — алерты идут в личку Олегу. Когда @bz_sl_bot добавят в группу — поправить env+config.
|
||||||
|
- **Bonjour отключен** превентивно (был crash-loop на 137, см. [[projects/dttb/openclaw#Проблема-Crash-loop-каждые-40-сек]]).
|
||||||
|
- **DNS LXC 139** идёт на `1.1.1.1`/`8.8.8.8` напрямую (через `pct set --nameserver`), не на `10.0.0.1` — иначе FakeIP от Mihomo ломает Telegram API.
|
||||||
|
- **openclaw на system-level** (не --user как на 137). В LXC без user-session systemd --user недоступен.
|
||||||
|
|
||||||
|
## Бэкап
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# на самом LXC 139
|
||||||
|
tar czf /root/severny-les-state-$(date +%F).tar.gz /root/.openclaw /root/clawd /etc/systemd/system/buzharovo-watchdog.* /etc/systemd/system/netbird-watchdog.* /etc/systemd/system/openclaw-gateway.service /etc/severny-les /etc/cron.d/severny-les-heartbeat /usr/local/bin/buzharovo-watchdog.sh /usr/local/bin/netbird-watchdog.sh
|
||||||
|
|
||||||
|
# скопировать наружу
|
||||||
|
sshpass -p '1qaz!QAZ' ssh root@10.0.0.250 "pct pull 139 /root/severny-les-state-*.tar.gz /var/lib/vz/dump/"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Откат
|
||||||
|
|
||||||
|
Если бот сломал что-то и его нужно убрать целиком:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# stop and disable
|
||||||
|
pct exec 139 -- systemctl disable --now openclaw-gateway.service buzharovo-watchdog.timer netbird-watchdog.timer
|
||||||
|
|
||||||
|
# либо целиком LXC
|
||||||
|
pct stop 139 && pct destroy 139
|
||||||
|
```
|
||||||
|
|
||||||
|
NetBird-пир `severny-les.netbird.cloud` останется в Dashboard — нужно удалить руками.
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
---
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [dttb, homelab, infrastructure]
|
||||||
|
aliases: [HomeLab, dttb, "dttb.ru", "Work Server dttb", "code-server", "rustdeskserver", "MacBook-Pro", "iPhone-batlaew", "iPad-batlaew", "finland5870.com", "cloud", "kasm", "pdm", "OpenWrt 1", "pve ded_mozay", "Cups-Server", "clawdbot", "clawdbot-1"]
|
||||||
|
---
|
||||||
|
|
||||||
# Проект DTTB (HomeLab)
|
# Проект DTTB (HomeLab)
|
||||||
|
|
||||||
## Инфраструктура
|
## Инфраструктура
|
||||||
@@ -50,9 +57,7 @@
|
|||||||
- [[projects/dttb/openclaw]]
|
- [[projects/dttb/openclaw]]
|
||||||
- [[projects/dttb/openwrt-router]]
|
- [[projects/dttb/openwrt-router]]
|
||||||
- [[projects/dttb/proxmox-inventory]]
|
- [[projects/dttb/proxmox-inventory]]
|
||||||
- [[projects/dttb/server1c]]
|
|
||||||
- [[projects/dttb/spaceweb-dns]]
|
- [[projects/dttb/spaceweb-dns]]
|
||||||
- [[projects/dttb/video-surveillance-report]]
|
|
||||||
- [[projects/dttb/videonablyudenie-znam]]
|
- [[projects/dttb/videonablyudenie-znam]]
|
||||||
- [[projects/dttb/znamenskoye-network-topology]]
|
- [[projects/dttb/znamenskoye-network-topology]]
|
||||||
- [[projects/dttb/nextcloud-talk-bot/README]]
|
- [[projects/dttb/nextcloud-talk-bot/README]]
|
||||||
|
|||||||
138
projects/dttb/graphify-out/.graphify_analysis.json
Normal file
138
projects/dttb/graphify-out/.graphify_analysis.json
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
{
|
||||||
|
"communities": {
|
||||||
|
"0": [
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_get_ai_reply",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_main",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_poll_new_messages",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_114",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_153",
|
||||||
|
"nextcloud_talk_bot_py"
|
||||||
|
],
|
||||||
|
"1": [
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_join_room",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_nc_request",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_130",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_141",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_93",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_send_message"
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_build_system_prompt",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_load_knowledge_base",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_35",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_71"
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_get_last_message_id",
|
||||||
|
"nextcloud_talk_bot_nextcloud_talk_bot_rationale_107"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cohesion": {
|
||||||
|
"0": 0.47,
|
||||||
|
"1": 0.4,
|
||||||
|
"2": 0.5,
|
||||||
|
"3": 1.0
|
||||||
|
},
|
||||||
|
"gods": [
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request",
|
||||||
|
"label": "nc_request()",
|
||||||
|
"degree": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_main",
|
||||||
|
"label": "main()",
|
||||||
|
"degree": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_send_message",
|
||||||
|
"label": "send_message()",
|
||||||
|
"degree": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_build_system_prompt",
|
||||||
|
"label": "build_system_prompt()",
|
||||||
|
"degree": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_get_last_message_id",
|
||||||
|
"label": "get_last_message_id()",
|
||||||
|
"degree": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_poll_new_messages",
|
||||||
|
"label": "poll_new_messages()",
|
||||||
|
"degree": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_join_room",
|
||||||
|
"label": "join_room()",
|
||||||
|
"degree": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_load_knowledge_base",
|
||||||
|
"label": "load_knowledge_base()",
|
||||||
|
"degree": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_get_ai_reply",
|
||||||
|
"label": "get_ai_reply()",
|
||||||
|
"degree": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_35",
|
||||||
|
"label": "Load all .md files from knowledge-base repo into context string",
|
||||||
|
"degree": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"surprises": [
|
||||||
|
{
|
||||||
|
"source": "build_system_prompt()",
|
||||||
|
"target": "main()",
|
||||||
|
"source_files": [
|
||||||
|
"nextcloud-talk-bot.py",
|
||||||
|
"nextcloud-talk-bot.py"
|
||||||
|
],
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"relation": "calls",
|
||||||
|
"note": "Bridges community 2 \u2192 community 0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "nc_request()",
|
||||||
|
"target": "get_last_message_id()",
|
||||||
|
"source_files": [
|
||||||
|
"nextcloud-talk-bot.py",
|
||||||
|
"nextcloud-talk-bot.py"
|
||||||
|
],
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"relation": "calls",
|
||||||
|
"note": "Bridges community 1 \u2192 community 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "nc_request()",
|
||||||
|
"target": "poll_new_messages()",
|
||||||
|
"source_files": [
|
||||||
|
"nextcloud-talk-bot.py",
|
||||||
|
"nextcloud-talk-bot.py"
|
||||||
|
],
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"relation": "calls",
|
||||||
|
"note": "Bridges community 1 \u2192 community 0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "get_last_message_id()",
|
||||||
|
"target": "main()",
|
||||||
|
"source_files": [
|
||||||
|
"nextcloud-talk-bot.py",
|
||||||
|
"nextcloud-talk-bot.py"
|
||||||
|
],
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"relation": "calls",
|
||||||
|
"note": "Bridges community 3 \u2192 community 0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tokens": {
|
||||||
|
"input": 0,
|
||||||
|
"output": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
1
projects/dttb/graphify-out/.graphify_root
Normal file
1
projects/dttb/graphify-out/.graphify_root
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/root/knowledge-base/projects/dttb
|
||||||
74
projects/dttb/graphify-out/GRAPH_REPORT.md
Normal file
74
projects/dttb/graphify-out/GRAPH_REPORT.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Graph Report - dttb (2026-05-06)
|
||||||
|
|
||||||
|
## Corpus Check
|
||||||
|
- 1 files · ~22,438 words
|
||||||
|
- Verdict: corpus is large enough that graph structure adds value.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- 18 nodes · 28 edges · 4 communities (3 shown, 1 thin omitted)
|
||||||
|
- Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
|
||||||
|
- Token cost: 0 input · 0 output
|
||||||
|
|
||||||
|
## Graph Freshness
|
||||||
|
- Built from commit: `3220238c`
|
||||||
|
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||||
|
- Run `graphify update .` after code changes (no API cost).
|
||||||
|
|
||||||
|
## Community Hubs (Navigation)
|
||||||
|
- [[_COMMUNITY_Community 0|Community 0]]
|
||||||
|
- [[_COMMUNITY_Community 1|Community 1]]
|
||||||
|
- [[_COMMUNITY_Community 2|Community 2]]
|
||||||
|
- [[_COMMUNITY_Community 3|Community 3]]
|
||||||
|
|
||||||
|
## God Nodes (most connected - your core abstractions)
|
||||||
|
1. `nc_request()` - 6 edges
|
||||||
|
2. `main()` - 6 edges
|
||||||
|
3. `send_message()` - 5 edges
|
||||||
|
4. `build_system_prompt()` - 4 edges
|
||||||
|
5. `get_last_message_id()` - 4 edges
|
||||||
|
6. `poll_new_messages()` - 4 edges
|
||||||
|
7. `join_room()` - 4 edges
|
||||||
|
8. `load_knowledge_base()` - 3 edges
|
||||||
|
9. `get_ai_reply()` - 3 edges
|
||||||
|
10. `Load all .md files from knowledge-base repo into context string` - 1 edges
|
||||||
|
|
||||||
|
## Surprising Connections (you probably didn't know these)
|
||||||
|
- `main()` --calls--> `build_system_prompt()` [EXTRACTED]
|
||||||
|
nextcloud-talk-bot.py → nextcloud-talk-bot.py _Bridges community 2 → community 0_
|
||||||
|
- `get_last_message_id()` --calls--> `nc_request()` [EXTRACTED]
|
||||||
|
nextcloud-talk-bot.py → nextcloud-talk-bot.py _Bridges community 1 → community 3_
|
||||||
|
- `poll_new_messages()` --calls--> `nc_request()` [EXTRACTED]
|
||||||
|
nextcloud-talk-bot.py → nextcloud-talk-bot.py _Bridges community 1 → community 0_
|
||||||
|
- `main()` --calls--> `get_last_message_id()` [EXTRACTED]
|
||||||
|
nextcloud-talk-bot.py → nextcloud-talk-bot.py _Bridges community 3 → community 0_
|
||||||
|
|
||||||
|
## Communities (4 total, 1 thin omitted)
|
||||||
|
|
||||||
|
### Community 0 - "Community 0"
|
||||||
|
Cohesion: 0.47
|
||||||
|
Nodes (5): get_ai_reply(), main(), poll_new_messages(), Long-poll for new messages after last_id, Get reply from Claude via cliproxy
|
||||||
|
|
||||||
|
### Community 1 - "Community 1"
|
||||||
|
Cohesion: 0.4
|
||||||
|
Nodes (6): join_room(), nc_request(), Join conversation as bot user, Send message as bot user, Nextcloud OCS API request, send_message()
|
||||||
|
|
||||||
|
### Community 2 - "Community 2"
|
||||||
|
Cohesion: 0.5
|
||||||
|
Nodes (4): build_system_prompt(), load_knowledge_base(), Load all .md files from knowledge-base repo into context string, Build system prompt with knowledge base
|
||||||
|
|
||||||
|
## Knowledge Gaps
|
||||||
|
- **8 isolated node(s):** `Load all .md files from knowledge-base repo into context string`, `Build system prompt with knowledge base`, `Nextcloud OCS API request`, `Get the highest message ID in the conversation`, `Long-poll for new messages after last_id` (+3 more)
|
||||||
|
These have ≤1 connection - possible missing edges or undocumented components.
|
||||||
|
- **1 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||||
|
|
||||||
|
## Suggested Questions
|
||||||
|
_Questions this graph is uniquely positioned to answer:_
|
||||||
|
|
||||||
|
- **Why does `nc_request()` connect `Community 1` to `Community 0`, `Community 3`?**
|
||||||
|
_High betweenness centrality (0.176) - this node is a cross-community bridge._
|
||||||
|
- **Why does `main()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`?**
|
||||||
|
_High betweenness centrality (0.132) - this node is a cross-community bridge._
|
||||||
|
- **Why does `send_message()` connect `Community 1` to `Community 0`?**
|
||||||
|
_High betweenness centrality (0.129) - this node is a cross-community bridge._
|
||||||
|
- **What connects `Load all .md files from knowledge-base repo into context string`, `Build system prompt with knowledge base`, `Nextcloud OCS API request` to the rest of the system?**
|
||||||
|
_8 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||||
File diff suppressed because one or more lines are too long
305
projects/dttb/graphify-out/graph.html
Normal file
305
projects/dttb/graphify-out/graph.html
Normal file
File diff suppressed because one or more lines are too long
464
projects/dttb/graphify-out/graph.json
Normal file
464
projects/dttb/graphify-out/graph.json
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
{
|
||||||
|
"directed": false,
|
||||||
|
"multigraph": false,
|
||||||
|
"graph": {},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"label": "nextcloud-talk-bot.py",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L1",
|
||||||
|
"community": 0,
|
||||||
|
"norm_label": "nextcloud-talk-bot.py",
|
||||||
|
"id": "nextcloud_talk_bot_py"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "load_knowledge_base()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L34",
|
||||||
|
"community": 2,
|
||||||
|
"norm_label": "load_knowledge_base()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_load_knowledge_base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "build_system_prompt()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L70",
|
||||||
|
"community": 2,
|
||||||
|
"norm_label": "build_system_prompt()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_build_system_prompt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "nc_request()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L92",
|
||||||
|
"community": 1,
|
||||||
|
"norm_label": "nc_request()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "get_last_message_id()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L106",
|
||||||
|
"community": 3,
|
||||||
|
"norm_label": "get_last_message_id()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_get_last_message_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "poll_new_messages()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L113",
|
||||||
|
"community": 0,
|
||||||
|
"norm_label": "poll_new_messages()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_poll_new_messages"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "join_room()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L129",
|
||||||
|
"community": 1,
|
||||||
|
"norm_label": "join_room()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_join_room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "send_message()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L140",
|
||||||
|
"community": 1,
|
||||||
|
"norm_label": "send_message()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_send_message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "get_ai_reply()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L152",
|
||||||
|
"community": 0,
|
||||||
|
"norm_label": "get_ai_reply()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_get_ai_reply"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "main()",
|
||||||
|
"file_type": "code",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L196",
|
||||||
|
"community": 0,
|
||||||
|
"norm_label": "main()",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Load all .md files from knowledge-base repo into context string",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L35",
|
||||||
|
"community": 2,
|
||||||
|
"norm_label": "load all .md files from knowledge-base repo into context string",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build system prompt with knowledge base",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L71",
|
||||||
|
"community": 2,
|
||||||
|
"norm_label": "build system prompt with knowledge base",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_71"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Nextcloud OCS API request",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L93",
|
||||||
|
"community": 1,
|
||||||
|
"norm_label": "nextcloud ocs api request",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_93"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Get the highest message ID in the conversation",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L107",
|
||||||
|
"community": 3,
|
||||||
|
"norm_label": "get the highest message id in the conversation",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_107"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Long-poll for new messages after last_id",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L114",
|
||||||
|
"community": 0,
|
||||||
|
"norm_label": "long-poll for new messages after last_id",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_114"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Join conversation as bot user",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L130",
|
||||||
|
"community": 1,
|
||||||
|
"norm_label": "join conversation as bot user",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_130"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Send message as bot user",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L141",
|
||||||
|
"community": 1,
|
||||||
|
"norm_label": "send message as bot user",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_141"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Get reply from Claude via cliproxy",
|
||||||
|
"file_type": "rationale",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L153",
|
||||||
|
"community": 0,
|
||||||
|
"norm_label": "get reply from claude via cliproxy",
|
||||||
|
"id": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_153"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L34",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_load_knowledge_base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L70",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_build_system_prompt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L92",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L106",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_get_last_message_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L113",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_poll_new_messages"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L129",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_join_room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L140",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_send_message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L152",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_get_ai_reply"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "contains",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L196",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_py",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L72",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_load_knowledge_base",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_build_system_prompt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L35",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_load_knowledge_base",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L204",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_build_system_prompt",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L71",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_build_system_prompt",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_71"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L108",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_get_last_message_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L116",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_poll_new_messages"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L132",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_join_room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L146",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_send_message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L93",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_nc_request",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_93"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L208",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_get_last_message_id",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L107",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_get_last_message_id",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_107"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L214",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_poll_new_messages",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L114",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_poll_new_messages",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_114"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L142",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_join_room",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_send_message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L130",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_join_room",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_130"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L242",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_send_message",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L141",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_send_message",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_141"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "calls",
|
||||||
|
"context": "call",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L249",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_get_ai_reply",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": "rationale_for",
|
||||||
|
"confidence": "EXTRACTED",
|
||||||
|
"source_file": "nextcloud-talk-bot.py",
|
||||||
|
"source_location": "L153",
|
||||||
|
"weight": 1.0,
|
||||||
|
"confidence_score": 1.0,
|
||||||
|
"source": "nextcloud_talk_bot_nextcloud_talk_bot_get_ai_reply",
|
||||||
|
"target": "nextcloud_talk_bot_nextcloud_talk_bot_rationale_153"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hyperedges": [],
|
||||||
|
"built_at_commit": "f6bf12ccf957ac62fd694aca23076d9cdedb1581"
|
||||||
|
}
|
||||||
134
projects/dttb/graphify-out/manifest.json
Normal file
134
projects/dttb/graphify-out/manifest.json
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"/root/knowledge-base/projects/dttb/nextcloud-talk-bot/nextcloud-talk-bot.py": {
|
||||||
|
"mtime": 1772553250.5498495,
|
||||||
|
"hash": "37c31f385bf367dffa5410fca5db2aad"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/npm-homelab.md": {
|
||||||
|
"mtime": 1776533040.2587712,
|
||||||
|
"hash": "9037da8a80c3728a1c3ae9e30307d4c1"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/nextcloud.md": {
|
||||||
|
"mtime": 1776533040.2587712,
|
||||||
|
"hash": "0b1fac8b98394b2eeaab825e73725b54"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/vpn-clients.md": {
|
||||||
|
"mtime": 1777491602.2761946,
|
||||||
|
"hash": "6829b65fd532c33c8afc596c01b86c25"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/rustdesk.md": {
|
||||||
|
"mtime": 1777535102.3840268,
|
||||||
|
"hash": "5a1ea382a62ecce3751bbccf82c2e2da"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/proxmox-inventory.md": {
|
||||||
|
"mtime": 1777491602.2761946,
|
||||||
|
"hash": "ceccd235f7a6871628985d294d7799a2"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/README.md": {
|
||||||
|
"mtime": 1776533040.256771,
|
||||||
|
"hash": "d913624f91ab13167e7b2084c15ea3ec"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/server1c.md": {
|
||||||
|
"mtime": 1776533040.259771,
|
||||||
|
"hash": "074469e3b11d518deaef534a78682b02"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/znamenskoye-network-topology.md": {
|
||||||
|
"mtime": 1776454954.6540399,
|
||||||
|
"hash": "069efe059e04d48a73318ad4a60d4659"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/agentdvr-home.md": {
|
||||||
|
"mtime": 1776533040.256771,
|
||||||
|
"hash": "4389dc7a8af5a8eec0e9078cf1543cdf"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/memory-inventory.md": {
|
||||||
|
"mtime": 1776533040.257771,
|
||||||
|
"hash": "7c2cbd84335b1d5c36998ff3381bc184"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/video-surveillance-report.md": {
|
||||||
|
"mtime": 1776533040.259771,
|
||||||
|
"hash": "20a162cad1d26a84cbbf23e77ecb2e52"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/openclaw.md": {
|
||||||
|
"mtime": 1777274101.853409,
|
||||||
|
"hash": "c8544464ec9ce9294f696f3e333cb65d"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/rustdesk-runbook.md": {
|
||||||
|
"mtime": 1777535102.3830266,
|
||||||
|
"hash": "db23daa2ddd9c22087fa84e7d864087f"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/oleg-agent.md": {
|
||||||
|
"mtime": 1776533040.2587712,
|
||||||
|
"hash": "66764bf64eb9c10bd3ba7b9821694b55"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/netbird-inventory.md": {
|
||||||
|
"mtime": 1776533040.257771,
|
||||||
|
"hash": "c33a5181e1ad45b6accb77c77cd83bef"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/clawdbot.md": {
|
||||||
|
"mtime": 1776582820.6267729,
|
||||||
|
"hash": "0e0c17505e600fb3e2e5906cea9c3cea"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/clawdbot-znam.md": {
|
||||||
|
"mtime": 1776582820.6267729,
|
||||||
|
"hash": "c7456b12fd420333c1be65b87ce36561"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/vps-swtest.md": {
|
||||||
|
"mtime": 1776791401.979136,
|
||||||
|
"hash": "708e0875a4692ee4d075e023cf3aeab8"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/spaceweb-dns.md": {
|
||||||
|
"mtime": 1776582820.6277728,
|
||||||
|
"hash": "7be7f83e0cf7e91d5655be9611014564"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/npm-proxy-hosts.md": {
|
||||||
|
"mtime": 1776533040.2587712,
|
||||||
|
"hash": "d4023b30e51211fcd1719c36af5ddcf6"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/gpu-passthrough.md": {
|
||||||
|
"mtime": 1776533040.256771,
|
||||||
|
"hash": "014042fb4d0e68c3d4ce8e306fccb352"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/openwrt-router.md": {
|
||||||
|
"mtime": 1777498202.2161224,
|
||||||
|
"hash": "ca6584ae5ecbc94cb0a48de74c67099f"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/znamenskoye-log.md": {
|
||||||
|
"mtime": 1776866401.9426904,
|
||||||
|
"hash": "e613a7ccbc964f60ef4c613c727c3399"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/gitea.md": {
|
||||||
|
"mtime": 1776533040.256771,
|
||||||
|
"hash": "6af3c6fea7388b1e8529a46ad3ca15dc"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/mailcow-dttb.md": {
|
||||||
|
"mtime": 1776533040.257771,
|
||||||
|
"hash": "736ee5b42265c72908fb1e6d8efadb7c"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/videonablyudenie-znam.md": {
|
||||||
|
"mtime": 1776533040.2607713,
|
||||||
|
"hash": "3ea218acd3308070da6bfbb874b31e27"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/matrix-homelab.md": {
|
||||||
|
"mtime": 1776533040.257771,
|
||||||
|
"hash": "a650a7f600fbb5d7522bd24924820d6a"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/network-topology-diagram.md": {
|
||||||
|
"mtime": 1777874102.1657124,
|
||||||
|
"hash": "46077e02378c57f65b427f6aab0aa075"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/homeassistant.md": {
|
||||||
|
"mtime": 1776533040.257771,
|
||||||
|
"hash": "3c8ee8300606414c61d31fff665391e3"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/network-topology.md": {
|
||||||
|
"mtime": 1776533040.257771,
|
||||||
|
"hash": "9bfd2213d2f1b0c8955086f17a6df1b7"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/nextcloud-talk-bot/README.md": {
|
||||||
|
"mtime": 1776533040.2587712,
|
||||||
|
"hash": "f57c75af81c6a6af6a30f3d99341bf31"
|
||||||
|
},
|
||||||
|
"/root/knowledge-base/projects/dttb/oleg-agent/docker-compose.yml": {
|
||||||
|
"mtime": 1772553250.5505955,
|
||||||
|
"hash": "d765ad412d55282485eec876cda9bc8b"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -157,7 +157,7 @@ Pre-production audit (2026-04-30) с найденными уязвимостям
|
|||||||
- **NPM streams через UI/API не публикуются наружу docker-контейнера.** Нужно править compose-файл и пересоздавать контейнер. Compose лежит в `/data/compose/2/docker-compose.yml` на host LXC 103 (мы его положили туда, ранее Portainer хранил только в своём volume). При обновлении stack ВАЖНО: `cd /data/compose/2 && docker compose -p npm up -d` — relative `./data` resolve в `/data/compose/2/data` (где реальные данные NPM). НЕ обновляй stack через Portainer UI — он работает со своей копией compose в `/var/lib/docker/volumes/portainer_data/_data/compose/2/`, может перезаписать. Текущий compose содержит ports: 80, 81, 443, 21115-21119 (TCP+UDP где надо).
|
- **NPM streams через UI/API не публикуются наружу docker-контейнера.** Нужно править compose-файл и пересоздавать контейнер. Compose лежит в `/data/compose/2/docker-compose.yml` на host LXC 103 (мы его положили туда, ранее Portainer хранил только в своём volume). При обновлении stack ВАЖНО: `cd /data/compose/2 && docker compose -p npm up -d` — relative `./data` resolve в `/data/compose/2/data` (где реальные данные NPM). НЕ обновляй stack через Portainer UI — он работает со своей копией compose в `/var/lib/docker/volumes/portainer_data/_data/compose/2/`, может перезаписать. Текущий compose содержит ports: 80, 81, 443, 21115-21119 (TCP+UDP где надо).
|
||||||
- **WebClient в браузере** работает на инфра-уровне (wss://remot.dttb.ru:21118/ws/id и :21119/ws/relay → 101 Switching Protocols). TLS termination для 21118/21119 через `/data/compose/2/data/nginx/custom/stream.conf` (custom stream block в NPM с `listen 21118 ssl` + `ssl_certificate /etc/letsencrypt/live/npm-41/...`). Streams 21115/21117/21116-tcp/udp идут через обычные NPM streams (id 38, 39, 40, 43). Чтобы реально использовать — нужны online peers с api-server login. Подключение Mac→Mac через WebClient невозможно (это и есть `MUST_LOGIN`-style ошибка "не удалось подключиться к серверу ретрансляции").
|
- **WebClient в браузере** работает на инфра-уровне (wss://remot.dttb.ru:21118/ws/id и :21119/ws/relay → 101 Switching Protocols). TLS termination для 21118/21119 через `/data/compose/2/data/nginx/custom/stream.conf` (custom stream block в NPM с `listen 21118 ssl` + `ssl_certificate /etc/letsencrypt/live/npm-41/...`). Streams 21115/21117/21116-tcp/udp идут через обычные NPM streams (id 38, 39, 40, 43). Чтобы реально использовать — нужны online peers с api-server login. Подключение Mac→Mac через WebClient невозможно (это и есть `MUST_LOGIN`-style ошибка "не удалось подключиться к серверу ретрансляции").
|
||||||
- **community-script может пытаться обновить пакеты** — `apt-mark hold` защищает hbbs/hbbr, но если запустить полный re-run скрипта community-scripts, могут быть сюрпризы. Не запускать без необходимости.
|
- **community-script может пытаться обновить пакеты** — `apt-mark hold` защищает hbbs/hbbr, но если запустить полный re-run скрипта community-scripts, могут быть сюрпризы. Не запускать без необходимости.
|
||||||
- **`/proc/loadavg` в LXC = нагрузка хоста**, не контейнера ([[../../../knowledge-base/feedback_lxc_loadavg]] в memory).
|
- **`/proc/loadavg` в LXC = нагрузка хоста**, не контейнера (`feedback_lxc_loadavg` (user memory) в memory).
|
||||||
|
|
||||||
## Развёртывание клиентов
|
## Развёртывание клиентов
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
date: 2026-04-17
|
|
||||||
type: project
|
|
||||||
tags: [dttb]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Server1C — Сервер 1С в Бужарово
|
|
||||||
|
|
||||||
## Подключение
|
|
||||||
- **Публичный IP:** 185.13.47.2 (RDP:3389)
|
|
||||||
- **Netbird IP:** 100.70.75.103 (server1c.netbird.cloud)
|
|
||||||
- **WinRM:** порт 5985, basic auth
|
|
||||||
- **Учётка:** dttb / 1qaz!QAZ
|
|
||||||
- **OS:** Windows Server 2012 R2 (6.3.9600)
|
|
||||||
- **Hostname:** Server1C
|
|
||||||
- **Локация:** Бужарово
|
|
||||||
|
|
||||||
## 1С:Предприятие
|
|
||||||
Три службы агента:
|
|
||||||
1. `1C:Enterprise 8.3 Server Agent` — StartType: Automatic
|
|
||||||
2. `1C:Enterprise 8.3 Server Agent (x86-64)` — StartType: Automatic
|
|
||||||
3. `RagentServer_8327` — версия 8.3.27.1606, StartType: Automatic
|
|
||||||
|
|
||||||
### Решено: конфликт служб при загрузке (2026-04-16)
|
|
||||||
**Проблема:** 3 службы с Automatic стартовали одновременно, боролись за порты 1540/1541.
|
|
||||||
- Служба 8.3.18 (x86) — бинарник удалён, падала с ошибкой "файл не найден"
|
|
||||||
- RagentServer_8327 — дубликат без параметров, таймаут на портах
|
|
||||||
- Рабочая: `1C:Enterprise 8.3 Server Agent (x86-64)` (8.3.27.1606)
|
|
||||||
|
|
||||||
**Решение:** отключены лишние службы (Disabled), оставлена только x86-64.
|
|
||||||
@@ -1,297 +0,0 @@
|
|||||||
---
|
|
||||||
date: 2026-03-13
|
|
||||||
type: project
|
|
||||||
tags: [dttb, video]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Система видеонаблюдения — Полный отчёт
|
|
||||||
> Дата: 16 февраля 2026
|
|
||||||
> Статус: Все 3 локации подключены
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Общая архитектура
|
|
||||||
|
|
||||||
Три удалённые локации объединены через VPN-туннели WireGuard к единому VPS-серверу со статическим IP.
|
|
||||||
VPS выступает точкой агрегации: принимает внешние подключения из интернета и пробрасывает их через DNAT к камерам и регистраторам в локальных сетях.
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────────────────────┐
|
|
||||||
│ VPS 89.111.140.86 │
|
|
||||||
│ WireGuard: 10.5.0.1 │
|
|
||||||
│ NetBird: 100.70.93.36 │
|
|
||||||
│ Ubuntu 24.04.3 LTS │
|
|
||||||
└───┬──────────┬──────────┬────┘
|
|
||||||
│ │ │
|
|
||||||
10.5.0.2 10.5.0.3 10.5.0.4
|
|
||||||
│ │ │
|
|
||||||
▼ ▼ ▼
|
|
||||||
┌──────────┐ ┌──────────┐ ┌──────────────┐
|
|
||||||
│Знаменск29│ │Охотхоз-во│ │Знаменск Home │
|
|
||||||
│Mikrotik │ │Mikrotik │ │ONT→OpenWrt→WG│
|
|
||||||
│1 камера │ │NVR+6 кам │ │UDM Pro → NVR │
|
|
||||||
└──────────┘ └──────────┘ └──────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## VPS-сервер
|
|
||||||
|
|
||||||
| Параметр | Значение |
|
|
||||||
|----------|----------|
|
|
||||||
| IP-адрес | 89.111.140.86 |
|
|
||||||
| ОС | Ubuntu 24.04.3 LTS, ядро 6.17.0-14-generic |
|
|
||||||
| SSH | Ключ /Users/ai/Downloads/id_rsa |
|
|
||||||
| WireGuard | wg0, порт 51820/UDP, IP 10.5.0.1/24 |
|
|
||||||
| Публичный ключ WG | v95Qiu4diw2EBghyQK4obptxnJ7EhAAUXxNflMS0DTw= |
|
|
||||||
| NetBird | 100.70.93.36 |
|
|
||||||
| Конфиг WG | /etc/wireguard/wg0.conf |
|
|
||||||
| Правила iptables | /etc/iptables/rules.v4 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Локация 1: Знаменское 29
|
|
||||||
|
|
||||||
### Оборудование
|
|
||||||
|
|
||||||
| Устройство | IP-адрес | Логин / Пароль |
|
|
||||||
|-----------|----------|----------------|
|
|
||||||
| Камера HiWatch (1 шт.) | 192.168.88.42 | admin / 1qaz!QAZ |
|
|
||||||
| Mikrotik | 192.168.88.1 | admin / admin01 |
|
|
||||||
|
|
||||||
### Подключение к VPS
|
|
||||||
|
|
||||||
| Параметр | Значение |
|
|
||||||
|----------|----------|
|
|
||||||
| WireGuard IP | 10.5.0.2 |
|
|
||||||
| Публичный ключ | 5ZFvQNSCyJwRn3IdLrxFzFh+BEFeejA8VzUoV5a++jY= |
|
|
||||||
| Локальная подсеть | 192.168.88.0/24 |
|
|
||||||
|
|
||||||
### Схема
|
|
||||||
|
|
||||||
Простейшая из трёх локаций. Mikrotik поднимает WireGuard-туннель напрямую к VPS.
|
|
||||||
Один роутер, одна камера.
|
|
||||||
|
|
||||||
```
|
|
||||||
Интернет → VPS (89.111.140.86)
|
|
||||||
│ DNAT
|
|
||||||
▼
|
|
||||||
WireGuard туннель
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Mikrotik (192.168.88.1)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Камера HiWatch (192.168.88.42)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Внешний доступ
|
|
||||||
|
|
||||||
| Сервис | Адрес | Проброс на |
|
|
||||||
|--------|-------|-----------|
|
|
||||||
| Веб-интерфейс | http://89.111.140.86:8080 | 192.168.88.42:80 |
|
|
||||||
| SDK (iVMS-4200) | 89.111.140.86:8082 | 192.168.88.42:8000 |
|
|
||||||
| RTSP-поток | 89.111.140.86:8554 | 192.168.88.42:554 |
|
|
||||||
|
|
||||||
RTSP-ссылка: `rtsp://admin:1qaz!QAZ@89.111.140.86:8554/Streaming/Channels/101`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Локация 2: Охотхозяйство
|
|
||||||
|
|
||||||
### Оборудование
|
|
||||||
|
|
||||||
| Устройство | IP-адрес | Логин / Пароль |
|
|
||||||
|-----------|----------|----------------|
|
|
||||||
| NVR HiWatch DS-N316(D) | 192.168.8.247 | admin / 1qaz!QAZ |
|
|
||||||
| Mikrotik | 192.168.8.1 | admin / 1qaz!QAZ |
|
|
||||||
| OpenWrt (NetBird хост) | 192.168.8.108 | root / 1qaz!QAZ |
|
|
||||||
| Камера 1 | 192.168.8.2 | — |
|
|
||||||
| Камера 2 | 192.168.8.3 | — |
|
|
||||||
| Камера 3 | 192.168.8.102 | — |
|
|
||||||
| Камера 4 | 192.168.8.110 | — |
|
|
||||||
| Камера 5 | 192.168.8.113 | — |
|
|
||||||
| Камера 6 | 192.168.8.120 | — |
|
|
||||||
|
|
||||||
### Подключение к VPS
|
|
||||||
|
|
||||||
| Параметр | Значение |
|
|
||||||
|----------|----------|
|
|
||||||
| WireGuard IP | 10.5.0.3 |
|
|
||||||
| Публичный ключ | zZ4UoWNwTxBODr8xZmoCREBL2zXJcmdcxKIPGp/xBC8= |
|
|
||||||
| Локальная подсеть | 192.168.8.0/24 |
|
|
||||||
|
|
||||||
### Схема
|
|
||||||
|
|
||||||
Mikrotik поднимает WireGuard-туннель к VPS. OpenWrt обеспечивает NetBird-доступ для удалённого управления.
|
|
||||||
На VPS DNAT пробрасывает порты как NVR, так и каждой камеры индивидуально.
|
|
||||||
|
|
||||||
```
|
|
||||||
Интернет → VPS (89.111.140.86)
|
|
||||||
│ DNAT
|
|
||||||
▼
|
|
||||||
WireGuard туннель
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Mikrotik (192.168.8.1)
|
|
||||||
│
|
|
||||||
┌─────┼──────────────────────┐
|
|
||||||
│ │ │
|
|
||||||
▼ ▼ ▼
|
|
||||||
NVR Камеры 1-6 OpenWrt (NetBird)
|
|
||||||
(.247) (.2,.3,.102,.110, (.108)
|
|
||||||
.113,.120)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Внешний доступ к NVR
|
|
||||||
|
|
||||||
| Сервис | Адрес | Проброс на |
|
|
||||||
|--------|-------|-----------|
|
|
||||||
| Веб-интерфейс | http://89.111.140.86:8180 | 192.168.8.247:80 |
|
|
||||||
| SDK (iVMS-4200) | 89.111.140.86:8100 | 192.168.8.247:8000 |
|
|
||||||
| RTSP-поток | 89.111.140.86:8555 | 192.168.8.247:554 |
|
|
||||||
|
|
||||||
RTSP NVR (каналы 1-6):
|
|
||||||
- `rtsp://admin:1qaz!QAZ@89.111.140.86:8555/Streaming/Channels/101`
|
|
||||||
- `rtsp://admin:1qaz!QAZ@89.111.140.86:8555/Streaming/Channels/201`
|
|
||||||
- `rtsp://admin:1qaz!QAZ@89.111.140.86:8555/Streaming/Channels/301`
|
|
||||||
- `rtsp://admin:1qaz!QAZ@89.111.140.86:8555/Streaming/Channels/401`
|
|
||||||
- `rtsp://admin:1qaz!QAZ@89.111.140.86:8555/Streaming/Channels/501`
|
|
||||||
- `rtsp://admin:1qaz!QAZ@89.111.140.86:8555/Streaming/Channels/601`
|
|
||||||
|
|
||||||
### Внешний доступ к камерам напрямую
|
|
||||||
|
|
||||||
| Камера | IP | RTSP-порт VPS | SDK-порт VPS |
|
|
||||||
|--------|-----|--------------|-------------|
|
|
||||||
| 1 | 192.168.8.2 | 8561 | 8201 |
|
|
||||||
| 2 | 192.168.8.3 | 8562 | 8202 |
|
|
||||||
| 3 | 192.168.8.102 | 8563 | 8203 |
|
|
||||||
| 4 | 192.168.8.110 | 8564 | 8204 |
|
|
||||||
| 5 | 192.168.8.113 | 8565 | 8205 |
|
|
||||||
| 6 | 192.168.8.120 | 8566 | 8206 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Локация 3: Знаменское Home
|
|
||||||
|
|
||||||
### Оборудование
|
|
||||||
|
|
||||||
| Устройство | IP-адрес | Логин / Пароль |
|
|
||||||
|-----------|----------|----------------|
|
|
||||||
| NVR HiWatch DS-N316(D) | 192.168.1.123 | admin / 1qaz!QAZ |
|
|
||||||
| Huawei HG8245H (ONT) | 192.168.100.1 | root / admin |
|
|
||||||
| OpenWrt_3 (WAN) | 192.168.100.3 | root / 1qaz!QAZ |
|
|
||||||
| OpenWrt_3 (LAN) | 10.3.0.1 | — |
|
|
||||||
| OpenWrt_3 (NetBird) | 100.70.54.204 | — |
|
|
||||||
| UDM Pro (Ubiquiti) | 10.3.0.175 | SSH: k9gLi2C / xdjM0eQkIeZfmCFBYo9DP |
|
|
||||||
|
|
||||||
| Параметр NVR | Значение |
|
|
||||||
|-------------|----------|
|
|
||||||
| Серийный номер | DS-N316(D)1620250625CCRRGC0949997WCVU |
|
|
||||||
| Прошивка | V4.76.015 (build 250210) |
|
|
||||||
|
|
||||||
### Подключение к VPS
|
|
||||||
|
|
||||||
| Параметр | Значение |
|
|
||||||
|----------|----------|
|
|
||||||
| WireGuard IP | 10.5.0.4 |
|
|
||||||
| Публичный ключ | HRsAUPwDOh+36EoHrXVYY5t6YVdb612N+E+3I+o6RTw= |
|
|
||||||
| Приватный ключ | 4C9B6iHRRARQfFGBoXimIeznJKj8NX7QmUBW3O+pklE= |
|
|
||||||
| Локальные подсети | 192.168.1.0/24, 192.168.100.0/24 |
|
|
||||||
|
|
||||||
### Топология
|
|
||||||
|
|
||||||
Самая сложная из трёх локаций. Провайдерский GPON-терминал Huawei раздаёт интернет.
|
|
||||||
За ним OpenWrt_3 поднимает WireGuard-туннель к VPS. На LAN-стороне OpenWrt_3 стоит
|
|
||||||
Ubiquiti UDM Pro, за которым NVR на подсети 192.168.1.0/24.
|
|
||||||
|
|
||||||
```
|
|
||||||
Интернет
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Huawei HG8245H ONT (192.168.100.1)
|
|
||||||
│ Провайдерский GPON-терминал
|
|
||||||
│ LAN: 192.168.100.0/24
|
|
||||||
▼
|
|
||||||
OpenWrt_3
|
|
||||||
│ WAN: 192.168.100.3
|
|
||||||
│ LAN: 10.3.0.1
|
|
||||||
│ WireGuard wg0 → VPS (10.5.0.4 ↔ 10.5.0.1)
|
|
||||||
│ AmneziaWG awg0 (10.8.1.7)
|
|
||||||
│ NetBird wt0 (100.70.54.204)
|
|
||||||
│ Podkop + sing-box (обход блокировок)
|
|
||||||
│
|
|
||||||
│ LAN: 10.3.0.0/24
|
|
||||||
▼
|
|
||||||
UDM Pro (10.3.0.175)
|
|
||||||
│ MAC: 9c:05:d6:ac:98:b8
|
|
||||||
│ LAN: 192.168.1.0/24
|
|
||||||
▼
|
|
||||||
NVR HiWatch DS-N316(D) (192.168.1.123)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Цепочка трафика
|
|
||||||
|
|
||||||
`Интернет → VPS (DNAT) → WireGuard → OpenWrt_3 → UDM Pro → NVR`
|
|
||||||
|
|
||||||
### Ключевые настройки
|
|
||||||
|
|
||||||
| Элемент | Настройка |
|
|
||||||
|---------|-----------|
|
|
||||||
| Маршрут на OpenWrt_3 | 192.168.1.0/24 via 10.3.0.175 (UCI persistent) |
|
|
||||||
| Файрвол OpenWrt_3 | Зона wg: masq=1, форвардинг wg↔lan, wg↔wan |
|
|
||||||
| VPS AllowedIPs | 10.5.0.4/32, 192.168.1.0/24, 192.168.100.0/24 |
|
|
||||||
| VPS маршруты | 192.168.1.0/24 via 10.5.0.4, 192.168.100.0/24 via 10.5.0.4 |
|
|
||||||
|
|
||||||
### Внешний доступ к NVR
|
|
||||||
|
|
||||||
| Сервис | Адрес | Проброс на |
|
|
||||||
|--------|-------|-----------|
|
|
||||||
| Веб-интерфейс | http://89.111.140.86:8280 | 192.168.1.123:80 |
|
|
||||||
| SDK (iVMS-4200) | 89.111.140.86:8282 | 192.168.1.123:8000 |
|
|
||||||
| RTSP-поток | 89.111.140.86:8284 | 192.168.1.123:554 |
|
|
||||||
|
|
||||||
RTSP-ссылка: `rtsp://admin:1qaz!QAZ@89.111.140.86:8284/Streaming/Channels/101`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Сводная таблица портов VPS
|
|
||||||
|
|
||||||
Все внешние порты доступны по адресу 89.111.140.86:
|
|
||||||
|
|
||||||
| Порт | Протокол | Назначение | Локация |
|
|
||||||
|------|----------|-----------|---------|
|
|
||||||
| 51820 | UDP | WireGuard | Служебный |
|
|
||||||
| | | | |
|
|
||||||
| 8080 | TCP | Веб камеры | Знаменское 29 |
|
|
||||||
| 8082 | TCP+UDP | SDK камеры | Знаменское 29 |
|
|
||||||
| 8554 | TCP | RTSP камеры | Знаменское 29 |
|
|
||||||
| | | | |
|
|
||||||
| 8180 | TCP | Веб NVR | Охотхозяйство |
|
|
||||||
| 8100 | TCP | SDK NVR | Охотхозяйство |
|
|
||||||
| 8555 | TCP | RTSP NVR | Охотхозяйство |
|
|
||||||
| 8201–8206 | TCP | SDK камер 1–6 | Охотхозяйство |
|
|
||||||
| 8561–8566 | TCP | RTSP камер 1–6 | Охотхозяйство |
|
|
||||||
| | | | |
|
|
||||||
| 8280 | TCP | Веб NVR | Знаменское Home |
|
|
||||||
| 8282 | TCP | SDK NVR | Знаменское Home |
|
|
||||||
| 8284 | TCP | RTSP NVR | Знаменское Home |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WireGuard-пиры VPS
|
|
||||||
|
|
||||||
| Локация | WG IP | Публичный ключ | AllowedIPs |
|
|
||||||
|---------|-------|---------------|------------|
|
|
||||||
| Знаменское 29 | 10.5.0.2 | 5ZFvQNSCyJwRn3Id... | 10.5.0.2/32, 192.168.88.0/24 |
|
|
||||||
| Охотхозяйство | 10.5.0.3 | zZ4UoWNwTxBODr8x... | 10.5.0.3/32, 192.168.8.0/24 |
|
|
||||||
| Знаменское Home | 10.5.0.4 | HRsAUPwDOh+36EoH... | 10.5.0.4/32, 192.168.1.0/24, 192.168.100.0/24 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Известные особенности
|
|
||||||
|
|
||||||
- **UDM Pro (Знаменское Home):** SSH-логин работает, но Network Application не запускается — веб-интерфейс управления Ubiquiti недоступен
|
|
||||||
- **Podkop/sing-box на OpenWrt_3:** TPROXY может мешать исходящему TCP. При диагностике проблем со связью рекомендуется временно останавливать: `service podkop stop; service sing-box stop`
|
|
||||||
- **NetBird на OpenWrt_3:** Маршрут NIIKN для 192.168.1.0/24 был деселектирован, чтобы не конфликтовать с WireGuard-маршрутом
|
|
||||||
- **Huawei ONT (192.168.100.1):** Иногда показывает "Waiting..." — перезагрузка решает проблему
|
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
---
|
---
|
||||||
date: 2026-03-03
|
date: 2026-03-03
|
||||||
|
updated: 2026-05-06
|
||||||
type: project
|
type: project
|
||||||
tags: [dttb, video]
|
tags: [dttb, video, znamenskoye, surveillance, agentdvr, wireguard]
|
||||||
|
aliases: [видеонаблюдение Знаменское, камеры Знаменское, surveillance, NVR Охотхозяйство, AgentDVR ЧОП, swtest video, VPS 89.111.140.86]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Система видеонаблюдения — Полный отчёт
|
# Система видеонаблюдения — Полный отчёт (3 локации Знаменского)
|
||||||
|
|
||||||
> **Дата:** 16 февраля 2026
|
> **Дата:** 16 февраля 2026
|
||||||
> **Статус:** Все 3 локации подключены
|
> **Статус:** Все 3 локации подключены
|
||||||
|
> **Объекты:** Знаменское 29, Охотхозяйство, Знаменское Home + AgentDVR ЧОП
|
||||||
|
|
||||||
|
Canonical-документ. Дубль `video-surveillance-report.md` удалён 2026-05-06 — он был более старой неполной версией (без секции AgentDVR ЧОП).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
---
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [glavtorg, client, windows, "1c"]
|
||||||
|
aliases: [Главторг, glavtorg, GLAVTORG, Volkkent, "Diana RDP", "Ярослав Глаvторг"]
|
||||||
|
---
|
||||||
|
|
||||||
# Проект Glavtorg
|
# Проект Glavtorg
|
||||||
|
|
||||||
## Сервер
|
## Сервер
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
---
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [krasnogorsk, client, residential, openwrt, cudy]
|
||||||
|
aliases: [Красногорск, krasnogorsk, Снегири, "Cudy TR3000", "Deco P9"]
|
||||||
|
---
|
||||||
|
|
||||||
# Проект Красногорск
|
# Проект Красногорск
|
||||||
|
|
||||||
## Описание
|
## Описание
|
||||||
|
|||||||
95
projects/lipki/README.md
Normal file
95
projects/lipki/README.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [object, openwrt, cudy, zvenigorod, netbird, residential, client]
|
||||||
|
aliases: [Липки, Lipki, OpenWrt_Lipki, Звенигород, PSP_Network, Антон]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Липки
|
||||||
|
|
||||||
|
Посёлок в **Звенигородском районе** (Московская обл.). Резидентский объект **Антона** — Wi-Fi mesh, Ajax-сигнализация, OpenWrt-роутер. Имя в DHCP (`MBP-Anthony`) — его MacBook. Снято с роутера 2026-05-06.
|
||||||
|
|
||||||
|
## Роутер
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Hostname | `OpenWrt_Lipki` |
|
||||||
|
| Модель | **Cudy TR3000 v1** (mediatek/filogic, aarch64_cortex-a53) |
|
||||||
|
| OpenWrt | 24.10.3 r28872-daca7c049b |
|
||||||
|
| SSH / LuCI | root / `1qaz!QAZ` (тот же, что на homelab) |
|
||||||
|
| Доступ | через NetBird (`100.70.35.234`) — relay через Helsinki |
|
||||||
|
|
||||||
|
## Сеть
|
||||||
|
|
||||||
|
| Слой | Значение |
|
||||||
|
|---|---|
|
||||||
|
| WAN | DHCP на `eth0`, **белый IPv4 `5.101.135.71`**, без CGNAT |
|
||||||
|
| LAN | `192.168.1.0/24`, шлюз `192.168.1.1`, DHCP `.2–.254`, lease 12 ч |
|
||||||
|
| Wi-Fi 2.4 ГГц | SSID **`PSP_Network`** (ch 6, HT20) |
|
||||||
|
| Wi-Fi 5 ГГц | SSID **`OpenWrt`** (ch 40, HE80) — дефолтное имя, стоит переименовать |
|
||||||
|
| NetBird | `100.70.35.234`, agent 0.59.13, группы `All` + `OpenWRT VPN` |
|
||||||
|
| Locality в NetBird | Istra _(NetBird определяет по WAN-IP/AS — реальный адрес в Звенигородском районе)_ |
|
||||||
|
|
||||||
|
WAN — белый IP, прямой DNAT возможен без хаба `swtest.ru`.
|
||||||
|
|
||||||
|
## Клиенты (DHCP, 12 устройств на 2026-05-06 14:00)
|
||||||
|
|
||||||
|
| IP | Имя | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| .14 | `HP8ADD66` | принтер HP |
|
||||||
|
| .31 | _безымянный_ | ? |
|
||||||
|
| .80 | _безымянный_ | ? |
|
||||||
|
| .81 | _безымянный_ | ? |
|
||||||
|
| .137 | `Redmi-Note-13-Pro-5G` | телефон |
|
||||||
|
| .164 | `deco-P9` | TP-Link Deco P9 (mesh-точка) |
|
||||||
|
| .181 | `Domasniinoteatr` | домашний кинотеатр (Smart TV / медиаплеер) |
|
||||||
|
| .191 | `Ajax-001399A3` | хаб Ajax (охранная сигнализация) |
|
||||||
|
| .209 | _безымянный_ | ? |
|
||||||
|
| .234 | `MBP-Anthony` | MacBook Pro Антона (владельца) |
|
||||||
|
| .243 | _безымянный_ | ? |
|
||||||
|
| .244 | _безымянный_ | ? |
|
||||||
|
|
||||||
|
По составу (Ajax, mesh, MacBook, ТВ, принтер) — **жилой дом**, не офис.
|
||||||
|
|
||||||
|
## Источники
|
||||||
|
|
||||||
|
- [[../dttb/netbird-inventory]] — реестр NetBird-пиров
|
||||||
|
- Снято напрямую: `ssh root@100.70.35.234` через LXC 132 (NetBird wt0)
|
||||||
|
|
||||||
|
## Владелец
|
||||||
|
|
||||||
|
**Антон** — клиент. Точный адрес в посёлке и телефон/мессенджер пока не зафиксированы.
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
|
||||||
|
- [ ] Точный адрес в посёлке Липки + контакт Антона (телефон / Telegram)
|
||||||
|
- [ ] Провайдер интернета (whois `5.101.135.71` — фоновое)
|
||||||
|
- [ ] Переименовать SSID `OpenWrt` → что-то осмысленное
|
||||||
|
|
||||||
|
## Связанные
|
||||||
|
|
||||||
|
- [[../znamenskoye/README]] — соседний «куст» в Истре, **не путать**
|
||||||
|
- [[../../claude-memory/znamenskoye-ohothozyistvo]] — соседний OpenWrt 100.70.63.67 (Охотхозяйство)
|
||||||
|
- [[../dttb/openwrt-router]] — homelab-роутер, тот же стек (OpenWrt 24.10.3, 1qaz!QAZ)
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
|
||||||
|
- [ ] Точный адрес в посёлке Липки и контактное лицо
|
||||||
|
- [ ] Чей объект (личный / клиент / семья / часть Знаменского?)
|
||||||
|
- [ ] Провайдер интернета, CGNAT или белый IP
|
||||||
|
- [ ] Что обслуживает роутер (камеры / IoT / рабочие места)
|
||||||
|
- [ ] LAN-подсеть и DHCP-диапазон
|
||||||
|
- [ ] Пароль/ключ доступа на сам OpenWrt
|
||||||
|
- [ ] Есть ли DNAT через VPS-хаб или доступ только через NetBird
|
||||||
|
- [ ] Почему отдельный объект, не часть [[../znamenskoye/README]]
|
||||||
|
|
||||||
|
## Связанные
|
||||||
|
|
||||||
|
- [[../znamenskoye/README]] — другой объект-«куст» в Истре, **не путать**
|
||||||
|
- [[../../claude-memory/znamenskoye-ohothozyistvo]] — соседний OpenWrt 100.70.63.67 (Охотхозяйство)
|
||||||
|
- [[../dttb/netbird-inventory]] — единственный пока источник по Липкам
|
||||||
|
|
||||||
|
## Aliases для FTS
|
||||||
|
|
||||||
|
`Липки`, `Lipki`, `OpenWrt_Lipki`, `100.70.35.234`, `5.101.135.71`, `Cudy TR3000`, `PSP_Network`, `Звенигород`, `Звенигородский район`, `посёлок Липки` — повторено явно, чтобы полнотекстовый поиск Максимки гарантированно цеплял этот файл при любом написании.
|
||||||
26
projects/mmfb/README.md
Normal file
26
projects/mmfb/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [mmfb, client, lionart, proxmox]
|
||||||
|
aliases: [ММФБ, mmfb, "pve LionART", "LionART", "Yuri Vitalievich", "Юрий Витальевич", "DESKTOP-UFULDJQ"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Проект ММФБ
|
||||||
|
|
||||||
|
Клиентский объект с собственным Proxmox `pve LionART` (NetBird `100.70.128.49`, LAN `10.253.1.253`) и MikroTik LionART `10.253.1.1`. WAN `195.26.30.163`.
|
||||||
|
|
||||||
|
Внутри Proxmox: 1С / Win2025 / OVPN / NPM / AgentDVR.
|
||||||
|
|
||||||
|
Подключённый клиентский ПК — **Юрий Витальевич** (`DESKTOP-UFULDJQ`, Win 11 25H2 после апгрейда 2026-04-29, NetBird `100.70.173.66`).
|
||||||
|
|
||||||
|
## Контакты и хосты
|
||||||
|
|
||||||
|
- [[mikrotik]] — MikroTik LionART
|
||||||
|
- [[proxmox-inventory]] — VM/LXC внутри `pve LionART`
|
||||||
|
- [[yuri-vitalievich]] — клиентский ПК Юрия
|
||||||
|
- [[otchet-yuri-2026-04]] — отчёт о работах за апрель
|
||||||
|
|
||||||
|
## Aliases для FTS
|
||||||
|
|
||||||
|
`ММФБ`, `mmfb`, `pve LionART`, `LionART`, `Yuri Vitalievich`, `Юрий Витальевич`, `DESKTOP-UFULDJQ`, `MikroTik LionART`, `10.253.1.0/24`.
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
---
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [niikn, client]
|
||||||
|
aliases: [НИИКН, niikn, "Cloud-NIIKN New niikn.com", pve-niikn, "Kripto-ARM", "DESKTOP-IC5A0K2 M.Maul", "M.Maul", "Maxim Maul", "Максим Мауль"]
|
||||||
|
---
|
||||||
|
|
||||||
# Проект НИИКН
|
# Проект НИИКН
|
||||||
|
|
||||||
## Инфраструктура
|
## Инфраструктура
|
||||||
|
|||||||
35
projects/openwrt-4/README.md
Normal file
35
projects/openwrt-4/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: project
|
||||||
|
status: stub
|
||||||
|
tags: [object, openwrt, netbird, client, todo, moscow, anonymous]
|
||||||
|
aliases: [OpenWrt_4, openwrt-4, openwrt4]
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpenWrt_4 — анонимный клиентский OpenWrt в Москве
|
||||||
|
|
||||||
|
**Стаб-заметка**. Имя пира неинформативное (`OpenWrt_4`), нужно выяснить какого клиента он обслуживает.
|
||||||
|
|
||||||
|
## Что известно (из NetBird)
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Имя пира | `OpenWrt_4` |
|
||||||
|
| NetBird IP | `100.70.235.2` |
|
||||||
|
| Локация (NetBird) | Moscow |
|
||||||
|
| OpenWrt | 24.10.3 |
|
||||||
|
| NetBird agent | 0.50.2 |
|
||||||
|
| Группы | `All`, `OpenWRT VPN`, «Все openwrt» |
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
|
||||||
|
- [ ] **Кто хозяин?** — главный пробел
|
||||||
|
- [ ] Что роутер обслуживает
|
||||||
|
- [ ] Точный адрес в Москве
|
||||||
|
- [ ] LAN, провайдер, WAN
|
||||||
|
- [ ] Пароль/ключ доступа
|
||||||
|
- [ ] Стоит переименовать пир в NetBird на осмысленное имя
|
||||||
|
|
||||||
|
## Aliases для FTS
|
||||||
|
|
||||||
|
`OpenWrt_4`, `100.70.235.2`, openwrt-4, openwrt4.
|
||||||
35
projects/sergey/README.md
Normal file
35
projects/sergey/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: project
|
||||||
|
status: stub
|
||||||
|
tags: [object, openwrt, netbird, client, todo]
|
||||||
|
aliases: [Sergey, sergey, OpenWrt_Sergey, Одинцово]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sergey — клиентский OpenWrt
|
||||||
|
|
||||||
|
**Стаб-заметка**, фактов мало. Создан, чтобы Максимка (FTS) не терял этот объект — раньше упоминался ровно один раз в [[../dttb/netbird-inventory]].
|
||||||
|
|
||||||
|
## Что известно (из NetBird)
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Имя пира | `OpenWrt_Sergey` |
|
||||||
|
| NetBird IP | `100.70.110.164` |
|
||||||
|
| Локация (NetBird) | Odintsovo |
|
||||||
|
| OpenWrt | 24.10.3 |
|
||||||
|
| NetBird agent | 0.59.12 |
|
||||||
|
| Группы | `All`, `OpenWRT VPN` |
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
|
||||||
|
- [ ] Кто такой Сергей — клиент / друг / семья?
|
||||||
|
- [ ] Точный адрес и контакт
|
||||||
|
- [ ] Тот ли это Сергей, что управляющий [[../znamenskoye/README]] (вероятно НЕТ — там объекты в Истре, этот в Одинцово)
|
||||||
|
- [ ] Что роутер обслуживает (камеры / IoT / рабочие места)
|
||||||
|
- [ ] LAN, провайдер, белый WAN или CGNAT
|
||||||
|
- [ ] Пароль/ключ доступа
|
||||||
|
|
||||||
|
## Aliases для FTS
|
||||||
|
|
||||||
|
`Sergey`, `OpenWrt_Sergey`, `100.70.110.164`, `Одинцово`.
|
||||||
35
projects/vishnevyy-sad/README.md
Normal file
35
projects/vishnevyy-sad/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: project
|
||||||
|
status: stub
|
||||||
|
tags: [object, openwrt, netbird, client, todo, moscow]
|
||||||
|
aliases: [Вишневый сад, "Вишнёвый сад", vishnevyy-sad, Константин, "OpenWrt Вишневый сад ( Константин )"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Вишнёвый сад (Константин) — клиентский OpenWrt в Москве
|
||||||
|
|
||||||
|
**Стаб-заметка**. Известно явно — объект **Константина**, явно прописано в названии netbird-пира.
|
||||||
|
|
||||||
|
## Что известно (из NetBird)
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Имя пира | `OpenWrt Вишневый сад ( Константин )` |
|
||||||
|
| NetBird IP | `100.70.152.137` |
|
||||||
|
| Локация (NetBird) | Moscow |
|
||||||
|
| OpenWrt | 24.10.3 |
|
||||||
|
| NetBird agent | 0.59.12 |
|
||||||
|
| Группы | `All`, `OpenWRT VPN` |
|
||||||
|
| Владелец | **Константин** (зафиксирован в имени пира) |
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
|
||||||
|
- [ ] Точный адрес и контакт Константина (телефон / Telegram)
|
||||||
|
- [ ] Что такое «Вишнёвый сад» — посёлок / ЖК / название объекта?
|
||||||
|
- [ ] Что роутер обслуживает
|
||||||
|
- [ ] LAN, провайдер, WAN
|
||||||
|
- [ ] Пароль/ключ доступа
|
||||||
|
|
||||||
|
## Aliases для FTS
|
||||||
|
|
||||||
|
`Вишневый сад`, `Вишнёвый сад`, `Константин`, `OpenWrt Вишневый сад`, `100.70.152.137`.
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
---
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [zelenograd, client, retail, kassa, windows]
|
||||||
|
aliases: [Зеленоград, zelenograd, "DESKTOP-6TF496J", "desktop-6tf496j", стройматериалы, касса]
|
||||||
|
---
|
||||||
|
|
||||||
# Зеленоград — строительный магазин
|
# Зеленоград — строительный магазин
|
||||||
|
|
||||||
## Хосты
|
## Хосты
|
||||||
|
|||||||
39
projects/znamenskoye/README.md
Normal file
39
projects/znamenskoye/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: project
|
||||||
|
status: active
|
||||||
|
tags: [znamenskoye, client, multi-site, video-surveillance, openwrt, mikrotik, wireguard, netbird]
|
||||||
|
aliases: [Знаменское, Znamenskoe, znamenskoye, "Знаменское 29", "Знаменское Home", "Охотхозяйство", "OpenWrt_Znamenskoe_Home", "OpenWrt_ohothozyistvo", "netbird-29-HP", "Сергей знаменское", "89-111-140-86.swtest.ru", "swtest", "VPS 89.111.140.86"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Знаменское — куст из 3 объектов
|
||||||
|
|
||||||
|
Клиент с тремя площадками. Управляющий — **Сергей** (есть «шеф над ним»). 3 объекта обслуживаются через единый WG-хаб (VPS `89.111.140.86` / `swtest.ru`) + NetBird overlay.
|
||||||
|
|
||||||
|
## Объекты
|
||||||
|
|
||||||
|
| Объект | NetBird IP | Роль | Документ |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Знаменское Home | `OpenWrt_Znamenskoe_Home` 100.70.54.204 | UDM Pro + Cudy + камеры | [[network-topology-diagram]], [[../dttb/znamenskoye-network-topology]] |
|
||||||
|
| Охотхозяйство | `OpenWrt_ohothozyistvo` 100.70.63.67 | Mikrotik LTE + Orange Pi OpenWrt + NVR + 6 камер | [[../../claude-memory/znamenskoye-ohothozyistvo]] |
|
||||||
|
| Знаменское 29 | `netbird-29-HP` 100.70.137.181 | Mikrotik + 1 камера HiWatch | [[../dttb/videonablyudenie-znam]] |
|
||||||
|
| Видеонаблюдение (общий отчёт) | — | архитектура DNAT через VPS | [[../dttb/videonablyudenie-znam]] |
|
||||||
|
| WG-хаб VPS | `89-111-140-86.swtest.ru` 100.70.93.36 | DNAT камер, агрегация WG | [[../dttb/vps-swtest]] |
|
||||||
|
|
||||||
|
## Контакты и контекст
|
||||||
|
|
||||||
|
- **Сергей** — управляющий по объектам
|
||||||
|
- Над ним — «шеф» (имя пока не зафиксировано)
|
||||||
|
- 2026: Олег готовит счёт + прайс, есть «горящие дыры»
|
||||||
|
|
||||||
|
## Связанные документы
|
||||||
|
|
||||||
|
- [[network-topology-diagram]] — Mermaid-схема сети дома
|
||||||
|
- [[../dttb/znamenskoye-network-topology]] — детально UDM/Cudy/камеры
|
||||||
|
- [[../dttb/videonablyudenie-znam]] — canonical отчёт по видеонаблюдению (3 локации)
|
||||||
|
- [[../../claude-memory/znamenskoye-ohothozyistvo]] — топология охотхозяйства
|
||||||
|
- [[../dttb/vps-swtest]] — общий WG-хаб
|
||||||
|
|
||||||
|
## Aliases для FTS
|
||||||
|
|
||||||
|
`Знаменское`, `Znamenskoe`, `znamenskoye`, `Сергей знаменское`, `Охотхозяйство`, `Знаменское 29`, `Знаменское Home`, `OpenWrt_Znamenskoe_Home`, `OpenWrt_ohothozyistvo`, `netbird-29-HP`, `swtest`, `VPS 89.111.140.86` — повторено явно для FTS.
|
||||||
205
scripts/kb-objects-audit.py
Normal file
205
scripts/kb-objects-audit.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""kb-objects-audit — еженедельный health-check vault'а.
|
||||||
|
|
||||||
|
Проверки:
|
||||||
|
1. Каждый онлайн-netbird-пир имеет привязку к проекту (через aliases в frontmatter
|
||||||
|
или собственную карточку). Orphans = клиентские машины без описания.
|
||||||
|
2. Каждый projects/<dir>/README.md имеет валидный frontmatter (type, status,
|
||||||
|
aliases как минимум).
|
||||||
|
3. Битые wiki-ссылки `[[...]]` в vault'е (указывают в небытие).
|
||||||
|
4. Дубли по нечёткому совпадению заголовков (опасные близнецы).
|
||||||
|
|
||||||
|
Output: audit/YYYY-MM-DD-objects-audit.md (каждый запуск перезаписывает дневной).
|
||||||
|
|
||||||
|
Запуск ручной или cron weekly:
|
||||||
|
cd ~/knowledge-base && python3 scripts/kb-objects-audit.py
|
||||||
|
"""
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
VAULT = Path(__file__).resolve().parent.parent
|
||||||
|
OBJECTS_MAP = VAULT / "audit/objects-map.json"
|
||||||
|
|
||||||
|
REQUIRED_FRONTMATTER_KEYS = ["type", "status"]
|
||||||
|
PROJECT_FRONTMATTER_KEYS = ["type", "status", "aliases"]
|
||||||
|
|
||||||
|
|
||||||
|
def load_map() -> list:
|
||||||
|
if not OBJECTS_MAP.exists():
|
||||||
|
sys.exit(f"FATAL: нет {OBJECTS_MAP.relative_to(VAULT)} — запусти scripts/kb-objects-map.py сначала")
|
||||||
|
return json.loads(OBJECTS_MAP.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def parse_fm(text: str) -> dict:
|
||||||
|
m = re.match(r"^---\n(.+?)\n---\n", text, re.S)
|
||||||
|
if not m:
|
||||||
|
return {}
|
||||||
|
fm = {}
|
||||||
|
for line in m.group(1).splitlines():
|
||||||
|
if ":" not in line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition(":")
|
||||||
|
fm[k.strip()] = v.strip()
|
||||||
|
return fm
|
||||||
|
|
||||||
|
|
||||||
|
def find_md_files() -> list[Path]:
|
||||||
|
out = []
|
||||||
|
for p in VAULT.rglob("*.md"):
|
||||||
|
rel = p.relative_to(VAULT)
|
||||||
|
if any(part.startswith(".") for part in rel.parts):
|
||||||
|
continue
|
||||||
|
if rel.parts[0] in ("audit",):
|
||||||
|
# audit-отчёты сами не аудируются
|
||||||
|
if "archive" in rel.parts:
|
||||||
|
continue
|
||||||
|
out.append(p)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def check_project_frontmatter(objects: list) -> list[str]:
|
||||||
|
issues = []
|
||||||
|
for o in objects:
|
||||||
|
if o["type"] != "project" or not o["file"]:
|
||||||
|
continue
|
||||||
|
path = VAULT / o["file"]
|
||||||
|
if not path.exists():
|
||||||
|
issues.append(f"- `{o['id']}`: file missing — `{o['file']}`")
|
||||||
|
continue
|
||||||
|
fm = parse_fm(path.read_text())
|
||||||
|
missing = [k for k in PROJECT_FRONTMATTER_KEYS if k not in fm]
|
||||||
|
if missing:
|
||||||
|
issues.append(f"- `{o['id']}`: frontmatter missing {missing} — `{o['file']}`")
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def check_broken_wikilinks(files: list[Path]) -> list[tuple[str, str, str]]:
|
||||||
|
"""Возвращает [(source, link, reason)] для битых [[...]] ссылок.
|
||||||
|
|
||||||
|
Проверка только полноценных wiki-ссылок [[...]] (двойная скобка с обеих сторон)."""
|
||||||
|
issues = []
|
||||||
|
all_basenames = {f.stem for f in files}
|
||||||
|
all_relpaths = {str(f.relative_to(VAULT)).replace(".md", "") for f in files}
|
||||||
|
# match [[target]] / [[target|alias]] / [[target#anchor]]
|
||||||
|
pat = re.compile(r"\[\[([^\]\|#\n]+?)(?:[\|#][^\]\n]*)?\]\]")
|
||||||
|
for f in files:
|
||||||
|
# пропускаем graphify-плагин output и весь audit/ (само-цитирование, autogen)
|
||||||
|
rel = f.relative_to(VAULT)
|
||||||
|
if "graphify-out" in rel.parts:
|
||||||
|
continue
|
||||||
|
if rel.parts[0] == "audit":
|
||||||
|
continue
|
||||||
|
if str(rel) == "CLAUDE.md":
|
||||||
|
continue # обучающие placeholder'ы вроде [[двойные скобки]]
|
||||||
|
try:
|
||||||
|
text = f.read_text()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
for m in pat.finditer(text):
|
||||||
|
target = m.group(1).strip().rstrip("/").removesuffix(".md")
|
||||||
|
if not target or target in (".", ".."):
|
||||||
|
continue
|
||||||
|
# Allow absolute by relpath, basename, or relative-to-source
|
||||||
|
if target in all_relpaths:
|
||||||
|
continue
|
||||||
|
base = target.split("/")[-1]
|
||||||
|
if base in all_basenames:
|
||||||
|
continue
|
||||||
|
src_dir = f.parent.relative_to(VAULT)
|
||||||
|
resolved = str(src_dir / target).replace("./", "")
|
||||||
|
if resolved in all_relpaths:
|
||||||
|
continue
|
||||||
|
issues.append((str(rel), m.group(0), "→ нет такого файла"))
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def check_orphans(objects: list) -> list[dict]:
|
||||||
|
return [o for o in objects if o["type"] == "netbird-only"
|
||||||
|
and o["netbird_peers"] and o["netbird_peers"][0].get("online", True)]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
objects = load_map()
|
||||||
|
files = find_md_files()
|
||||||
|
|
||||||
|
fm_issues = check_project_frontmatter(objects)
|
||||||
|
orphans = check_orphans(objects)
|
||||||
|
wiki_issues = check_broken_wikilinks(files)
|
||||||
|
|
||||||
|
# счётчики
|
||||||
|
n_proj = sum(1 for o in objects if o["type"] == "project")
|
||||||
|
n_proj_with_fm = n_proj - len(fm_issues)
|
||||||
|
n_orphan_online = len(orphans)
|
||||||
|
n_wiki_broken = len(wiki_issues)
|
||||||
|
score = len(fm_issues) * 5 + n_orphan_online * 2 + n_wiki_broken * 3
|
||||||
|
today = date.today().isoformat()
|
||||||
|
out = VAULT / f"audit/{today}-objects-audit.md"
|
||||||
|
|
||||||
|
md = [
|
||||||
|
"---",
|
||||||
|
f"date: {today}",
|
||||||
|
"type: audit",
|
||||||
|
"source: scripts/kb-objects-audit.py",
|
||||||
|
"tags: [audit, objects, frontmatter, links]",
|
||||||
|
f"score: {score}",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
f"# KB objects audit — {today}",
|
||||||
|
"",
|
||||||
|
f"**Score (меньше = лучше): `{score}`**",
|
||||||
|
"",
|
||||||
|
f"- Проектов с frontmatter: **{n_proj_with_fm}/{n_proj}** ({len(fm_issues)} проблем)",
|
||||||
|
f"- NetBird online-пиров без проектной карточки: **{n_orphan_online}**",
|
||||||
|
f"- Битых wiki-ссылок `[[...]]`: **{n_wiki_broken}**",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
md.extend(["## Frontmatter в projects/", ""])
|
||||||
|
if fm_issues:
|
||||||
|
md.extend(fm_issues)
|
||||||
|
else:
|
||||||
|
md.append("✅ все проекты имеют валидный frontmatter")
|
||||||
|
md.append("")
|
||||||
|
|
||||||
|
md.extend(["## Online netbird-пиры без проектной карточки",
|
||||||
|
"",
|
||||||
|
"Эти пиры онлайн в NetBird, но не привязаны ни к одной projects/-странице. ",
|
||||||
|
"Бот не сможет ответить «найди X» осмысленно — нет файла или alias.",
|
||||||
|
"",
|
||||||
|
"Лечение: либо создать stub в `projects/<slug>/README.md` (см. `projects/lipki/` как образец), ",
|
||||||
|
"либо добавить имя пира как полную строку в `aliases` подходящего проекта.",
|
||||||
|
"",
|
||||||
|
"| NetBird-имя | IP | OS | Город |",
|
||||||
|
"|---|---|---|---|"])
|
||||||
|
if orphans:
|
||||||
|
for o in orphans:
|
||||||
|
p = o["netbird_peers"][0]
|
||||||
|
md.append(f"| `{p['name']}` | {p['ip']} | {p.get('os','')} | {p.get('city','')} |")
|
||||||
|
else:
|
||||||
|
md.append("| — | — | — | — |")
|
||||||
|
md.append("")
|
||||||
|
md.append("✅ все онлайн-пиры покрыты")
|
||||||
|
md.append("")
|
||||||
|
|
||||||
|
md.extend(["## Битые wiki-ссылки", ""])
|
||||||
|
if wiki_issues:
|
||||||
|
for src, link, reason in wiki_issues[:50]:
|
||||||
|
md.append(f"- [{src}]({src}) — `{link}` {reason}")
|
||||||
|
if len(wiki_issues) > 50:
|
||||||
|
md.append(f"- ... ещё {len(wiki_issues)-50} (truncated до 50)")
|
||||||
|
else:
|
||||||
|
md.append("✅ битых ссылок не найдено")
|
||||||
|
md.append("")
|
||||||
|
|
||||||
|
out.write_text("\n".join(md))
|
||||||
|
print(f"Wrote {out.relative_to(VAULT)} (score={score})")
|
||||||
|
print(f" frontmatter issues: {len(fm_issues)}")
|
||||||
|
print(f" orphan online peers: {n_orphan_online}")
|
||||||
|
print(f" broken wiki links: {n_wiki_broken}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
240
scripts/kb-objects-map.py
Normal file
240
scripts/kb-objects-map.py
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""kb-objects-map — собирает машиночитаемый реестр объектов и хостов.
|
||||||
|
|
||||||
|
Источники:
|
||||||
|
projects/dttb/netbird-inventory.md — netbird-пиры (источник правды по железу)
|
||||||
|
projects/<dir>/README.md — frontmatter каждого проекта (aliases, tags, status)
|
||||||
|
projects/<file>.md — singleton-проекты
|
||||||
|
|
||||||
|
Output:
|
||||||
|
audit/objects-map.json — структура для бота / structured-поиска
|
||||||
|
projects/_index.md — человеко-читаемый индекс с wiki-ссылками
|
||||||
|
|
||||||
|
Запускать вручную или cron:
|
||||||
|
cd ~/knowledge-base && python3 scripts/kb-objects-map.py
|
||||||
|
"""
|
||||||
|
from datetime import date, datetime
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
VAULT = Path(__file__).resolve().parent.parent
|
||||||
|
INV = VAULT / "projects/dttb/netbird-inventory.md"
|
||||||
|
JSON_OUT = VAULT / "audit/objects-map.json"
|
||||||
|
MD_OUT = VAULT / "projects/_index.md"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_frontmatter(text: str) -> dict:
|
||||||
|
m = re.match(r"^---\n(.+?)\n---\n", text, re.S)
|
||||||
|
if not m:
|
||||||
|
return {}
|
||||||
|
fm = {}
|
||||||
|
for line in m.group(1).splitlines():
|
||||||
|
if ":" not in line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition(":")
|
||||||
|
k, v = k.strip(), v.strip()
|
||||||
|
if v.startswith("[") and v.endswith("]"):
|
||||||
|
v = [x.strip().strip("\"'") for x in v[1:-1].split(",") if x.strip()]
|
||||||
|
elif v.startswith('"') and v.endswith('"'):
|
||||||
|
v = v[1:-1]
|
||||||
|
fm[k] = v
|
||||||
|
return fm
|
||||||
|
|
||||||
|
|
||||||
|
ROW_RE = re.compile(
|
||||||
|
r"^\|\s*([^|]+?)\s*\|\s*(\d+\.\d+\.\d+\.\d+)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_netbird(path: Path) -> list[dict]:
|
||||||
|
"""Парсит online + offline-таблицы netbird-inventory.md."""
|
||||||
|
if not path.exists():
|
||||||
|
return []
|
||||||
|
text = path.read_text()
|
||||||
|
parts = re.split(r"^##\s+(?:Оффлайн|Offline)\b.*$", text, maxsplit=1, flags=re.M)
|
||||||
|
online_text, offline_text = parts[0], parts[1] if len(parts) > 1 else ""
|
||||||
|
|
||||||
|
peers = []
|
||||||
|
for line in online_text.splitlines():
|
||||||
|
m = ROW_RE.match(line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
name = m.group(1).strip()
|
||||||
|
if name in ("Имя", "---"):
|
||||||
|
continue
|
||||||
|
peers.append({
|
||||||
|
"name": name, "ip": m.group(2).strip(),
|
||||||
|
"os": m.group(3).strip(), "city": m.group(4).strip(),
|
||||||
|
"version": m.group(5).strip(), "online": True,
|
||||||
|
})
|
||||||
|
# offline-таблица: | Имя | IP | ОС | Последний раз онлайн | Город |
|
||||||
|
for line in offline_text.splitlines():
|
||||||
|
m = ROW_RE.match(line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
name = m.group(1).strip()
|
||||||
|
if name in ("Имя", "---"):
|
||||||
|
continue
|
||||||
|
peers.append({
|
||||||
|
"name": name, "ip": m.group(2).strip(),
|
||||||
|
"os": m.group(3).strip(),
|
||||||
|
"last_seen": m.group(4).strip(), "city": m.group(5).strip(),
|
||||||
|
"version": "", "online": False,
|
||||||
|
})
|
||||||
|
return peers
|
||||||
|
|
||||||
|
|
||||||
|
def _norm(s: str) -> str:
|
||||||
|
"""Нормализация для нечёткого сравнения. ye→e уравнивает Знаменское/Znamenskoe."""
|
||||||
|
return (s or "").lower().replace("ye", "e").replace(" ", "").replace("_", "").replace("-", "")
|
||||||
|
|
||||||
|
|
||||||
|
def name_match(peer_name: str, candidates: list[str]) -> bool:
|
||||||
|
"""Exact match с нормализацией (ye→e, без пробелов/_/−).
|
||||||
|
Чтобы избежать FP типа `cloud` ⊂ `Cloud-NIIKN`, требуется точное равенство."""
|
||||||
|
pn = _norm(peer_name)
|
||||||
|
if not pn:
|
||||||
|
return False
|
||||||
|
return any(_norm(c) == pn for c in candidates if c)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
peers = parse_netbird(INV)
|
||||||
|
objects = []
|
||||||
|
seen_files = set()
|
||||||
|
|
||||||
|
project_dirs = sorted(p for p in (VAULT / "projects").iterdir() if p.is_dir())
|
||||||
|
for d in project_dirs:
|
||||||
|
readme = d / "README.md"
|
||||||
|
fm = parse_frontmatter(readme.read_text()) if readme.exists() else {}
|
||||||
|
rel_file = f"projects/{d.name}/README.md" if readme.exists() else None
|
||||||
|
aliases = fm.get("aliases", [])
|
||||||
|
if isinstance(aliases, str):
|
||||||
|
aliases = [aliases]
|
||||||
|
names = sorted({d.name, *aliases})
|
||||||
|
nb = [p for p in peers if name_match(p["name"], names)]
|
||||||
|
objects.append({
|
||||||
|
"id": d.name,
|
||||||
|
"type": "project",
|
||||||
|
"names": names,
|
||||||
|
"netbird_peers": nb,
|
||||||
|
"tags": fm.get("tags", []),
|
||||||
|
"owner": fm.get("owner") or fm.get("client"),
|
||||||
|
"region": fm.get("region"),
|
||||||
|
"status": fm.get("status", "unknown"),
|
||||||
|
"file": rel_file,
|
||||||
|
})
|
||||||
|
if rel_file:
|
||||||
|
seen_files.add(rel_file)
|
||||||
|
|
||||||
|
for f in sorted((VAULT / "projects").glob("*.md")):
|
||||||
|
if f.name == "_index.md":
|
||||||
|
continue
|
||||||
|
rel = f"projects/{f.name}"
|
||||||
|
if rel in seen_files:
|
||||||
|
continue
|
||||||
|
fm = parse_frontmatter(f.read_text())
|
||||||
|
objects.append({
|
||||||
|
"id": f.stem,
|
||||||
|
"type": "project-note",
|
||||||
|
"names": [f.stem],
|
||||||
|
"netbird_peers": [],
|
||||||
|
"tags": fm.get("tags", []),
|
||||||
|
"owner": fm.get("owner"),
|
||||||
|
"region": fm.get("region"),
|
||||||
|
"status": fm.get("status", "unknown"),
|
||||||
|
"file": rel,
|
||||||
|
})
|
||||||
|
|
||||||
|
matched_peer_names = {p["name"] for o in objects for p in o["netbird_peers"]}
|
||||||
|
for p in peers:
|
||||||
|
if p["name"] in matched_peer_names:
|
||||||
|
continue
|
||||||
|
objects.append({
|
||||||
|
"id": p["name"].replace(" ", "_"),
|
||||||
|
"type": "netbird-only",
|
||||||
|
"names": [p["name"]],
|
||||||
|
"netbird_peers": [p],
|
||||||
|
"tags": [],
|
||||||
|
"owner": None,
|
||||||
|
"region": p["city"] or None,
|
||||||
|
"status": "no-project-page",
|
||||||
|
"file": None,
|
||||||
|
})
|
||||||
|
|
||||||
|
JSON_OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
JSON_OUT.write_text(json.dumps(objects, indent=2, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
|
today = date.today().isoformat()
|
||||||
|
now = datetime.now().isoformat(timespec="minutes")
|
||||||
|
n_proj = sum(1 for o in objects if o["type"] in ("project", "project-note"))
|
||||||
|
n_with_nb = sum(1 for o in objects if o["type"] == "project" and o["netbird_peers"])
|
||||||
|
n_orphan = sum(1 for o in objects if o["type"] == "netbird-only")
|
||||||
|
|
||||||
|
md = [
|
||||||
|
"---",
|
||||||
|
f"date: {today}",
|
||||||
|
"type: index",
|
||||||
|
"source: scripts/kb-objects-map.py",
|
||||||
|
"tags: [index, registry, objects, netbird]",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"# Реестр объектов и netbird-пиров",
|
||||||
|
"",
|
||||||
|
f"Авто-сгенерировано `{now}` из [[dttb/netbird-inventory]] + frontmatter в `projects/`.",
|
||||||
|
"**Не править вручную** — перепишется. Источник правды — frontmatter в каждом README.",
|
||||||
|
"",
|
||||||
|
f"- Проектов: **{n_proj}**, из них с netbird-привязкой: **{n_with_nb}**",
|
||||||
|
f"- NetBird-пиров без projects-страницы: **{n_orphan}** (TODO — создать стабы)",
|
||||||
|
"",
|
||||||
|
"## Проекты с netbird-привязкой",
|
||||||
|
"",
|
||||||
|
"| ID | Имена | NetBird IP | OS | Город | Файл | Статус |",
|
||||||
|
"|---|---|---|---|---|---|---|",
|
||||||
|
]
|
||||||
|
for o in sorted(objects, key=lambda x: x["id"]):
|
||||||
|
if o["type"] == "project" and o["netbird_peers"]:
|
||||||
|
nb_ip = ", ".join(p["ip"] for p in o["netbird_peers"])
|
||||||
|
nb_os = ", ".join(sorted({p["os"] for p in o["netbird_peers"] if p["os"]}))
|
||||||
|
nb_city = ", ".join(sorted({p["city"] for p in o["netbird_peers"] if p["city"]}))
|
||||||
|
file_link = f"[[{o['file'][:-3]}]]" if o["file"] else "—"
|
||||||
|
names = ", ".join(o["names"][:5])
|
||||||
|
md.append(f"| {o['id']} | {names} | {nb_ip} | {nb_os} | {nb_city} | {file_link} | {o['status']} |")
|
||||||
|
|
||||||
|
md.extend([
|
||||||
|
"",
|
||||||
|
"## Проекты без netbird-привязки",
|
||||||
|
"",
|
||||||
|
"| ID | Тип | Файл | Статус |",
|
||||||
|
"|---|---|---|---|",
|
||||||
|
])
|
||||||
|
for o in sorted(objects, key=lambda x: x["id"]):
|
||||||
|
if o["type"] in ("project", "project-note") and not o["netbird_peers"]:
|
||||||
|
file_link = f"[[{o['file'][:-3]}]]" if o["file"] else "—"
|
||||||
|
md.append(f"| {o['id']} | {o['type']} | {file_link} | {o['status']} |")
|
||||||
|
|
||||||
|
md.extend([
|
||||||
|
"",
|
||||||
|
"## NetBird-пиры без projects-страницы — TODO",
|
||||||
|
"",
|
||||||
|
"Эти пиры есть в инвентаре, но у них нет своей карточки в `projects/`. Бот не сможет ответить на «найди мне X» — нет файла. Нужно создать стабы (Фаза 4 плана).",
|
||||||
|
"",
|
||||||
|
"| Имя в NetBird | IP | OS | Город | Версия |",
|
||||||
|
"|---|---|---|---|---|",
|
||||||
|
])
|
||||||
|
for o in sorted(objects, key=lambda x: x["id"]):
|
||||||
|
if o["type"] == "netbird-only":
|
||||||
|
p = o["netbird_peers"][0]
|
||||||
|
md.append(f"| `{p['name']}` | {p['ip']} | {p['os']} | {p['city']} | {p['version']} |")
|
||||||
|
|
||||||
|
md.append("")
|
||||||
|
MD_OUT.write_text("\n".join(md))
|
||||||
|
|
||||||
|
print(f"Wrote {JSON_OUT.relative_to(VAULT)} ({len(objects)} entries: "
|
||||||
|
f"{n_proj} projects, {n_orphan} netbird-only)")
|
||||||
|
print(f"Wrote {MD_OUT.relative_to(VAULT)}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -163,7 +163,7 @@ vpn://AAAIYXjanVVdcpswEH7PKTyevjl1QNiAM5MH100T4vwY06ZpQscjg-yoIYKAbMfN5Ay9RI_Qh8
|
|||||||
|
|
||||||
### Как отозвать у Ярослава
|
### Как отозвать у Ярослава
|
||||||
|
|
||||||
1. SSH на сервер (см. [[../../projects/dttb/vpn-clients]] → Finland → [[feedback_finland_security]])
|
1. SSH на сервер (см. [[../../projects/dttb/vpn-clients]] → Finland → `feedback_finland_security` (user memory))
|
||||||
2. Найти peer Ярослава в `/opt/amnezia/…/wg0.conf` или в amnezia-docker-контейнере
|
2. Найти peer Ярослава в `/opt/amnezia/…/wg0.conf` или в amnezia-docker-контейнере
|
||||||
3. Удалить `[Peer]` блок → `wg syncconf wg0 <(wg-quick strip wg0)`
|
3. Удалить `[Peer]` блок → `wg syncconf wg0 <(wg-quick strip wg0)`
|
||||||
4. Отметить в таблице: «Отозван: YYYY-MM-DD»
|
4. Отметить в таблице: «Отозван: YYYY-MM-DD»
|
||||||
|
|||||||
100
snippets/iproyal-gost-relay.md
Normal file
100
snippets/iproyal-gost-relay.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: snippet
|
||||||
|
tags: [proxy, iproyal, gost, foxyproxy, apple, google]
|
||||||
|
---
|
||||||
|
|
||||||
|
# IPRoyal Royal Residential через локальный gost-relay
|
||||||
|
|
||||||
|
Постоянный «вечный» способ использовать IPRoyal residential без проблем с FoxyProxy (он режет длинный пароль с password-модификаторами `_country-X_session-Y_lifetime-Z`). Решение: запускаем локальный `gost` который слушает 127.0.0.1:PORT и форвардит на IPRoyal с правильным паролем зашитым в команду. Браузер тогда указывает только `127.0.0.1:PORT` без auth.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install gost # macOS, ~1 мин
|
||||||
|
# или
|
||||||
|
apt install golang && go install github.com/go-gost/gost/cmd/gost@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Базовая команда
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gost -L "http://127.0.0.1:PORT" \
|
||||||
|
-F "http://USER:PASS_country-XX_session-LABEL_lifetime-30m@geo.iproyal.com:12321"
|
||||||
|
```
|
||||||
|
|
||||||
|
Где:
|
||||||
|
- `PORT` — локальный порт для FoxyProxy (выбираешь сам, например 9999)
|
||||||
|
- `USER` / `PASS` — base creds из IPRoyal Dashboard → Royal Residential → Proxy List
|
||||||
|
- `XX` — ISO-код страны **lowercase** (`tj`, `fi`, `us`, `de`...). `country-Tajikistan` НЕ работает.
|
||||||
|
- `LABEL` — любая метка sticky-сессии (одна метка = один и тот же IP в течение `lifetime`)
|
||||||
|
- `lifetime-30m` — IP не меняется 30 минут. Доступно `1m`-`120m`.
|
||||||
|
|
||||||
|
## Twin-port схема (рекомендуется)
|
||||||
|
|
||||||
|
Запусти **два gost одновременно** на разных портах для разных стран — переключаешься между ними в FoxyProxy одной кнопкой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Порт 9999 → TJ (для Apple ID)
|
||||||
|
gost -L "http://127.0.0.1:9999" \
|
||||||
|
-F "http://OYB0rKuOA5qnNS6o:fXtn1jdd0xIt2EPf_country-tj_session-applemod_lifetime-30m@geo.iproyal.com:12321" &
|
||||||
|
|
||||||
|
# Порт 9998 → FI (для Google Antigravity)
|
||||||
|
gost -L "http://127.0.0.1:9998" \
|
||||||
|
-F "http://OYB0rKuOA5qnNS6o:fXtn1jdd0xIt2EPf_country-fi_session-googlemod_lifetime-30m@geo.iproyal.com:12321" &
|
||||||
|
```
|
||||||
|
|
||||||
|
В FoxyProxy создаёшь **два профиля**:
|
||||||
|
| Title | Type | Host | Port |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 🇹🇯 IPRoyal-TJ | HTTP | 127.0.0.1 | 9999 |
|
||||||
|
| 🇫🇮 IPRoyal-FI | HTTP | 127.0.0.1 | 9998 |
|
||||||
|
|
||||||
|
Username/Password — **пустые** (gost уже знает auth).
|
||||||
|
|
||||||
|
## Подтверждённые рабочие IP (история)
|
||||||
|
|
||||||
|
| Дата | Страна | Sticky-метка | Реальный IP | ASN |
|
||||||
|
|------|--------|--------------|-------------|-----|
|
||||||
|
| 2026-05-03 | TJ | `appletj01` | 185.177.2.130 (Dushanbe) | AS51346 Tojiktelecom |
|
||||||
|
| 2026-05-03 | TJ | `appletj02` | 94.199.21.228 (Khujand) | AS24722 Babilon-T |
|
||||||
|
| 2026-05-04 | TJ | `applemod` | 109.75.61.86 (Dushanbe) | AS47139 INDIGO |
|
||||||
|
| 2026-05-04 | TJ | `applemod3` | 109.74.74.60 (Dushanbe) | AS24722 Babilon-T |
|
||||||
|
| 2026-05-04 | FI | `googletest` | 212.149.238.126 (Oulu) | AS16086 DNA Oyj |
|
||||||
|
|
||||||
|
Все ASN из списка реальных residential провайдеров — **Apple/Google не палят как proxy**, в отличие от Hostkey/OVH/AWS.
|
||||||
|
|
||||||
|
## Важно — почему не FoxyProxy напрямую к IPRoyal
|
||||||
|
|
||||||
|
FoxyProxy парсер при сохранении пароля «режет» строку на специальных символах (`_` или после первого `:`). Из-за этого `pass_country-tj_session-X` уходит в IPRoyal как просто `pass` → IPRoyal отдаёт **random country**, не TJ. Подтверждено 2026-05-03: с обрезанным паролем вернулся IP Бразилии (Claro NXT).
|
||||||
|
|
||||||
|
Через gost модификаторы передаются как часть upstream URL и до FoxyProxy не доходят — он видит только `127.0.0.1:PORT` без auth.
|
||||||
|
|
||||||
|
## Тест что работает
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# С FI residential
|
||||||
|
curl -x http://127.0.0.1:9998 https://ipinfo.io
|
||||||
|
# Ожидание: "country":"FI", residential ASN (DNA, Elisa, Telia)
|
||||||
|
|
||||||
|
# С TJ residential
|
||||||
|
curl -x http://127.0.0.1:9999 https://ipinfo.io
|
||||||
|
# Ожидание: "country":"TJ", AS24722 Babilon / AS51346 Tojiktelecom / AS47139 INDIGO
|
||||||
|
```
|
||||||
|
|
||||||
|
## WebRTC + Accept-Language (обязательно для Apple ID)
|
||||||
|
|
||||||
|
Прокси сам по себе **недостаточен** — Firefox шлёт RU-локаль и WebRTC может слить реальный IP.
|
||||||
|
|
||||||
|
В Firefox `about:config`:
|
||||||
|
- `intl.accept_languages` = `tg-TJ, tg, en-US, en` (для TJ) или `fi-FI, fi, en-US, en` (для FI)
|
||||||
|
- `media.peerconnection.enabled` = `false`
|
||||||
|
- `media.peerconnection.ice.default_address_only` = `true`
|
||||||
|
|
||||||
|
И регистрироваться **в приватном окне** (Cmd+Shift+P) чтобы не было RU-cookies.
|
||||||
|
|
||||||
|
## Стоимость
|
||||||
|
|
||||||
|
IPRoyal Royal Residential pay-as-you-go: $7/GB (на момент 2026-05-03). 1 GB трафика хватает на ~10 регистраций Apple ID или ~5 часов работы с веб-консолями.
|
||||||
|
|
||||||
|
См. также: [[../decisions/2026-05-02-apple-id-tj-via-residential-proxy]]
|
||||||
23
snippets/mac-dictation/README.md
Normal file
23
snippets/mac-dictation/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Mac Dictation — Hammerspoon + Groq
|
||||||
|
|
||||||
|
Текущая рабочая версия (2026-05-05) скриптов для голосовой диктовки в любое поле macOS через Groq Whisper API.
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
- `groq-dictate.sh` → `~/bin/groq-dictate.sh` (chmod +x)
|
||||||
|
- `dictation-doctor.sh` → `~/bin/dictation-doctor.sh` (chmod +x)
|
||||||
|
- `init.lua` → `~/.hammerspoon/init.lua`
|
||||||
|
|
||||||
|
## Как это работает
|
||||||
|
- **Fn (Globe)** — одиночное нажатие, toggle (старт/стоп записи)
|
||||||
|
- Запись через `ffmpeg avfoundation :0` → `/tmp/groq-dictate.wav`
|
||||||
|
- Транскрипция: **Groq Whisper-large-v3-turbo** (cloud), fallback → **whisper-cpp tiny** (local 31MB)
|
||||||
|
- Результат → pasteboard → `hs.eventtap.event.newKeyEvent({"cmd"}, 9, true|false):post()` (⌘V на физический keycode 9 = V)
|
||||||
|
|
||||||
|
## Полный гайд
|
||||||
|
[`decisions/2026-05-05-mac-dictation-groq-hammerspoon.md`](../../decisions/2026-05-05-mac-dictation-groq-hammerspoon.md) — детали, грабли, восстановление на новом Mac.
|
||||||
|
|
||||||
|
## Если сломалось
|
||||||
|
```bash
|
||||||
|
~/bin/dictation-doctor.sh
|
||||||
|
```
|
||||||
|
Покажет что сломано (Hammerspoon / TCC / Groq / mic / Fn behavior).
|
||||||
86
snippets/mac-dictation/dictation-doctor.sh
Executable file
86
snippets/mac-dictation/dictation-doctor.sh
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Health-check для голосовой диктовки. Запускать когда «не работает».
|
||||||
|
# Проверяет: Hammerspoon, Accessibility, init.lua, скрипт, модели, Groq, ffmpeg, mic, Fn-key поведение
|
||||||
|
|
||||||
|
set +e
|
||||||
|
GREEN="\033[32m"; RED="\033[31m"; YELLOW="\033[33m"; CYAN="\033[36m"; RST="\033[0m"
|
||||||
|
ok() { echo -e "${GREEN}✅ $1${RST}"; }
|
||||||
|
fail() { echo -e "${RED}❌ $1${RST}"; ((FAILS++)); }
|
||||||
|
warn() { echo -e "${YELLOW}⚠️ $1${RST}"; }
|
||||||
|
hdr() { echo -e "\n${CYAN}── $1 ──${RST}"; }
|
||||||
|
FAILS=0
|
||||||
|
|
||||||
|
hdr "1. Hammerspoon"
|
||||||
|
if pgrep -x Hammerspoon >/dev/null; then ok "Hammerspoon запущен (pid $(pgrep -x Hammerspoon))"
|
||||||
|
else fail "Hammerspoon не запущен → open -a Hammerspoon"; fi
|
||||||
|
|
||||||
|
if [ -f ~/.hammerspoon/init.lua ]; then ok "init.lua на месте"
|
||||||
|
else fail "~/.hammerspoon/init.lua отсутствует"; fi
|
||||||
|
|
||||||
|
hdr "2. Accessibility (TCC)"
|
||||||
|
TCC=$(echo " " | sudo -S sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" \
|
||||||
|
"SELECT auth_value FROM access WHERE service='kTCCServiceAccessibility' AND client='org.hammerspoon.Hammerspoon'" 2>/dev/null)
|
||||||
|
case "$TCC" in
|
||||||
|
2) ok "Hammerspoon Accessibility: ALLOWED" ;;
|
||||||
|
0) fail "Hammerspoon Accessibility: DENIED → System Settings → Universal Access → включить" ;;
|
||||||
|
"") fail "Hammerspoon в TCC отсутствует → System Settings → Universal Access → добавить и включить" ;;
|
||||||
|
*) warn "Hammerspoon TCC: $TCC (странное значение)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
hdr "3. Скрипт диктовки"
|
||||||
|
if [ -x ~/bin/groq-dictate.sh ]; then ok "~/bin/groq-dictate.sh executable"
|
||||||
|
else fail "~/bin/groq-dictate.sh отсутствует или не +x"; fi
|
||||||
|
|
||||||
|
hdr "4. Зависимости"
|
||||||
|
for cmd in ffmpeg jq curl whisper-cli osascript; do
|
||||||
|
if command -v "$cmd" >/dev/null; then ok "$cmd найден ($(command -v $cmd))"
|
||||||
|
else fail "$cmd не установлен → brew install $cmd"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
hdr "5. Модели"
|
||||||
|
if [ -f "$HOME/.cache/whisper-cpp/ggml-tiny-q5_1.bin" ]; then
|
||||||
|
SIZE=$(ls -lh "$HOME/.cache/whisper-cpp/ggml-tiny-q5_1.bin" | awk '{print $5}')
|
||||||
|
ok "whisper-cpp tiny: $SIZE"
|
||||||
|
else
|
||||||
|
warn "локальная модель whisper-cpp отсутствует (fallback будет недоступен)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
hdr "6. Groq API"
|
||||||
|
HTTP=$(curl -sS -o /tmp/_groq_test.json -w "%{http_code}" --max-time 10 \
|
||||||
|
-H "Authorization: Bearer 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" \
|
||||||
|
https://api.groq.com/openai/v1/models 2>/dev/null)
|
||||||
|
case "$HTTP" in
|
||||||
|
200) ok "Groq API отвечает (HTTP 200)" ;;
|
||||||
|
401) fail "Groq: 401 — ключ невалиден или просрочен" ;;
|
||||||
|
403) fail "Groq: 403 — доступ запрещён (возможно, RU IP заблокирован)" ;;
|
||||||
|
429) warn "Groq: 429 — rate limit, подожди" ;;
|
||||||
|
000) fail "Groq не отвечает (нет сети?)" ;;
|
||||||
|
*) fail "Groq HTTP $HTTP" ;;
|
||||||
|
esac
|
||||||
|
rm -f /tmp/_groq_test.json
|
||||||
|
|
||||||
|
hdr "7. Микрофон"
|
||||||
|
DEFAULT_MIC=$(system_profiler SPAudioDataType 2>/dev/null | awk '/Default Input Device: Yes/{found=1} found && /Input Source:/{print $3, $4, $5; exit}')
|
||||||
|
[ -n "$DEFAULT_MIC" ] && ok "Default mic: $DEFAULT_MIC" || warn "Не удалось определить default mic"
|
||||||
|
|
||||||
|
hdr "8. Fn-key behavior"
|
||||||
|
FN_PREF=$(defaults read com.apple.HIToolbox AppleFnUsageType 2>/dev/null)
|
||||||
|
case "$FN_PREF" in
|
||||||
|
0) ok "Fn = 'действие не требуется' (правильно)" ;;
|
||||||
|
1) warn "Fn = 'переключает источник ввода' — конфликт с диктовкой" ;;
|
||||||
|
2) warn "Fn = 'эмодзи панель' — конфликт" ;;
|
||||||
|
3) warn "Fn = 'начинает Apple Dictation' — КОНФЛИКТ! Поменяй на 'Не требуется'" ;;
|
||||||
|
*) warn "Fn behavior unknown: $FN_PREF" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
hdr "9. Лог последних попыток"
|
||||||
|
[ -f /tmp/groq-dictate.log ] && tail -10 /tmp/groq-dictate.log || warn "Лог пуст"
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ $FAILS -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}═══ Всё в порядке ═══${RST}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}═══ Найдено проблем: $FAILS ═══${RST}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
70
snippets/mac-dictation/groq-dictate.sh
Executable file
70
snippets/mac-dictation/groq-dictate.sh
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Toggle dictation: ⌘⇧D / Fn → запись с микрофона; повторное нажатие → Groq Whisper → текст в /tmp/groq-dictate.last
|
||||||
|
# Hammerspoon Lua callback вставляет через hs.eventtap.keyStroke({"cmd"},"v") в активное окно.
|
||||||
|
# Fallback: если Groq недоступен (нет сети / 429 / 5xx) → локальный whisper-cli (whisper-cpp tiny ru).
|
||||||
|
|
||||||
|
PID_FILE="/tmp/groq-dictate.pid"
|
||||||
|
WAV_FILE="/tmp/groq-dictate.wav"
|
||||||
|
WAV_16K="/tmp/groq-dictate-16k.wav"
|
||||||
|
OUT_FILE="/tmp/groq-dictate.last"
|
||||||
|
LOG="/tmp/groq-dictate.log"
|
||||||
|
GROQ_KEY="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"
|
||||||
|
WHISPER_MODEL="$HOME/.cache/whisper-cpp/ggml-tiny-q5_1.bin"
|
||||||
|
|
||||||
|
log() { echo "[$(date +%H:%M:%S)] $*" >> "$LOG"; }
|
||||||
|
notify() { osascript -e "display notification \"$1\" with title \"Groq Dictate\""; }
|
||||||
|
|
||||||
|
groq_transcribe() {
|
||||||
|
local response
|
||||||
|
response=$(curl -sS -X POST "https://api.groq.com/openai/v1/audio/transcriptions" \
|
||||||
|
-H "Authorization: Bearer $GROQ_KEY" \
|
||||||
|
-F "file=@$WAV_FILE" \
|
||||||
|
-F "model=whisper-large-v3-turbo" \
|
||||||
|
-F "language=ru" \
|
||||||
|
-F "response_format=json" \
|
||||||
|
--max-time 15 2>/dev/null) || return 1
|
||||||
|
local code
|
||||||
|
code=$(echo "$response" | jq -r '.error.code // empty' 2>/dev/null)
|
||||||
|
[ -n "$code" ] && { log "Groq error: $response"; return 1; }
|
||||||
|
echo "$response" | jq -r '.text // empty' 2>/dev/null | sed 's/^ *//;s/ *$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
local_transcribe() {
|
||||||
|
[ ! -f "$WHISPER_MODEL" ] && { log "no local model: $WHISPER_MODEL"; return 1; }
|
||||||
|
ffmpeg -hide_banner -loglevel error -i "$WAV_FILE" -ar 16000 -ac 1 -y "$WAV_16K" 2>>"$LOG" || return 1
|
||||||
|
whisper-cli -m "$WHISPER_MODEL" -l ru -nt -np "$WAV_16K" 2>>"$LOG" | sed 's/^ *//;s/ *$//' | tr -d '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -f "$PID_FILE" ]; then
|
||||||
|
# === STOP & TRANSCRIBE ===
|
||||||
|
PID=$(cat "$PID_FILE")
|
||||||
|
log "STOP pid=$PID"
|
||||||
|
kill -INT "$PID" 2>/dev/null
|
||||||
|
for i in 1 2 3 4 5; do kill -0 "$PID" 2>/dev/null || break; sleep 0.1; done
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
|
||||||
|
if [ ! -s "$WAV_FILE" ]; then
|
||||||
|
log "ERR empty wav"; notify "Запись пустая"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "POST size=$(stat -f%z "$WAV_FILE")B"
|
||||||
|
TEXT=$(groq_transcribe)
|
||||||
|
if [ -z "$TEXT" ]; then
|
||||||
|
log "Groq fail → trying local whisper-cli"
|
||||||
|
notify "☁️→💻 Groq не отвечает, локальная модель"
|
||||||
|
TEXT=$(local_transcribe)
|
||||||
|
[ -z "$TEXT" ] && { log "Local also empty"; notify "Не распознано"; exit 1; }
|
||||||
|
log "LOCAL DONE: $TEXT"
|
||||||
|
else
|
||||||
|
log "GROQ DONE: $TEXT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$TEXT" > "$OUT_FILE"
|
||||||
|
else
|
||||||
|
# === START RECORDING ===
|
||||||
|
rm -f "$WAV_FILE" "$WAV_16K" "$OUT_FILE"
|
||||||
|
log "START → $WAV_FILE"
|
||||||
|
ffmpeg -hide_banner -loglevel error -f avfoundation -i ":0" -ar 16000 -ac 1 -y "$WAV_FILE" >/dev/null 2>>"$LOG" &
|
||||||
|
echo $! > "$PID_FILE"
|
||||||
|
notify "🎙️ Говори"
|
||||||
|
fi
|
||||||
52
snippets/mac-dictation/init.lua
Normal file
52
snippets/mac-dictation/init.lua
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-- Groq Dictate: trigger по одиночному нажатию Fn (Globe)
|
||||||
|
-- Fn нельзя биндить через hs.hotkey, поэтому слушаем flagsChanged
|
||||||
|
|
||||||
|
local SCRIPT = os.getenv("HOME") .. "/bin/groq-dictate.sh"
|
||||||
|
|
||||||
|
local function runScript()
|
||||||
|
hs.task.new("/bin/bash", function(exitCode, stdOut, stdErr)
|
||||||
|
local f = io.open("/tmp/groq-dictate.last", "r")
|
||||||
|
if f then
|
||||||
|
local text = f:read("*a")
|
||||||
|
f:close()
|
||||||
|
os.remove("/tmp/groq-dictate.last")
|
||||||
|
if text and #text > 0 then
|
||||||
|
hs.pasteboard.setContents(text)
|
||||||
|
-- keycode 9 = физическая V, в обход keymap lookup чтобы не спамить warnings на ru-раскладке
|
||||||
|
hs.eventtap.event.newKeyEvent({"cmd"}, 9, true):post()
|
||||||
|
hs.eventtap.event.newKeyEvent({"cmd"}, 9, false):post()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, {SCRIPT}):start()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Отслеживаем Fn: keycode 63 — это physical Fn/Globe
|
||||||
|
-- Триггер по press (а не по release), чтобы было снапи
|
||||||
|
local fnPressed = false
|
||||||
|
local fnTime = 0
|
||||||
|
|
||||||
|
fnTap = hs.eventtap.new({hs.eventtap.event.types.flagsChanged}, function(event)
|
||||||
|
local kc = event:getKeyCode()
|
||||||
|
if kc ~= 63 then return false end -- 63 = Fn/Globe
|
||||||
|
|
||||||
|
local flags = event:getFlags()
|
||||||
|
local now = hs.timer.absoluteTime() / 1e6 -- ms
|
||||||
|
|
||||||
|
if flags.fn and not fnPressed then
|
||||||
|
-- Fn pressed
|
||||||
|
fnPressed = true
|
||||||
|
fnTime = now
|
||||||
|
elseif (not flags.fn) and fnPressed then
|
||||||
|
-- Fn released
|
||||||
|
fnPressed = false
|
||||||
|
local elapsed = now - fnTime
|
||||||
|
-- Только короткое нажатие (<400ms) триггерит — long-press для других целей
|
||||||
|
if elapsed < 400 then
|
||||||
|
runScript()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false -- не блокировать event для других слушателей
|
||||||
|
end)
|
||||||
|
fnTap:start()
|
||||||
|
|
||||||
|
hs.alert.show("Groq Dictate: Fn (Globe) ready")
|
||||||
126
snippets/omniroute-models-audit.md
Normal file
126
snippets/omniroute-models-audit.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: snippet
|
||||||
|
tags: [omniroute, claude, opus, smoke-test, audit, lxc-132]
|
||||||
|
---
|
||||||
|
|
||||||
|
# OmniRoute — аудит живости моделей (smoke-test)
|
||||||
|
|
||||||
|
Каталог OmniRoute (`/v1/models`) перечисляет все модели, **подключенные** к провайдерам, но это не значит что они живые. Реальная проверка — пинг через `/v1/chat/completions` с `max_tokens: 10`. Пригождается каждые 1-2 недели или после `npm install omniroute@latest` чтобы убедиться что важные источники Opus/Sonnet не отвалились.
|
||||||
|
|
||||||
|
См. также: [[../decisions/2026-05-06-openclaw-opus-4-7-via-max-cliproxy]] — последний полный аудит и результаты.
|
||||||
|
|
||||||
|
## Где живёт OmniRoute
|
||||||
|
|
||||||
|
| Артефакт | Путь / адрес |
|
||||||
|
|----------|--------------|
|
||||||
|
| LXC | 132, hostname `code-server`, IP `10.0.0.179` |
|
||||||
|
| Service | `systemctl status omniroute` |
|
||||||
|
| Cmd | `/usr/bin/node /root/.npm/_npx/cb5891f90ae65d14/node_modules/omniroute/app/server.js` |
|
||||||
|
| EnvFile | `/root/OmniRoute/.env` |
|
||||||
|
| Repo | `/root/OmniRoute/` (Git) |
|
||||||
|
| Storage | `/root/.omniroute/storage.sqlite` |
|
||||||
|
| Local API | `http://localhost:20128` (порт из `.env` PORT=20128) |
|
||||||
|
| Public | `https://ai.dttb.ru` (через NPM, требует Bearer) |
|
||||||
|
|
||||||
|
## Получить API key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshpass -p '1qaz!QAZ' ssh root@10.0.0.179 \
|
||||||
|
"sqlite3 /root/.omniroute/storage.sqlite \"SELECT id, key, name FROM api_keys;\""
|
||||||
|
```
|
||||||
|
|
||||||
|
Использовать `name=test-key` для тестов чтобы не задеть production `claw`.
|
||||||
|
|
||||||
|
## Список моделей
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KEY='<key-из-sqlite>'
|
||||||
|
sshpass -p '1qaz!QAZ' ssh root@10.0.0.179 \
|
||||||
|
"curl -s -H 'Authorization: Bearer $KEY' http://localhost:20128/v1/models"
|
||||||
|
```
|
||||||
|
|
||||||
|
Парсинг с фильтром по подстроке (например все `opus`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
... | python3 -c '
|
||||||
|
import sys, json
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
models = d.get("data", d) if isinstance(d, dict) else d
|
||||||
|
for m in models:
|
||||||
|
if "opus" in m["id"].lower():
|
||||||
|
print(m["id"])
|
||||||
|
'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Smoke test одной модели
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KEY='<api-key>'
|
||||||
|
MODEL='cc/claude-opus-4-7'
|
||||||
|
|
||||||
|
curl -s --max-time 30 -X POST http://localhost:20128/v1/chat/completions \
|
||||||
|
-H "Authorization: Bearer $KEY" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{
|
||||||
|
"model": "'$MODEL'",
|
||||||
|
"messages": [{"role":"user","content":"Reply only: ok"}],
|
||||||
|
"max_tokens": 10,
|
||||||
|
"stream": false
|
||||||
|
}' | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидание `choices[0].message.content` = `"ok"` (или похожее короткое). Если streaming возвращается даже при `stream:false` — это чистый прокси-bypass от Claude Code OAuth, тоже норм.
|
||||||
|
|
||||||
|
## Batch smoke test нескольких моделей
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshpass -p '1qaz!QAZ' ssh root@10.0.0.179 "
|
||||||
|
KEY='<api-key>'
|
||||||
|
URL='http://localhost:20128/v1/chat/completions'
|
||||||
|
|
||||||
|
for model in 'cc/claude-opus-4-7' 'cc/claude-opus-4-6' 'claude/claude-opus-4-7' 'kr/claude-opus-4.6' 'gh/claude-opus-4.6' 'antigravity/claude-opus-4-6-thinking'; do
|
||||||
|
echo '--- '\$model' ---'
|
||||||
|
curl -s --max-time 25 -X POST \"\$URL\" \\
|
||||||
|
-H \"Authorization: Bearer \$KEY\" \\
|
||||||
|
-H 'Content-Type: application/json' \\
|
||||||
|
-d '{\"model\":\"'\$model'\",\"messages\":[{\"role\":\"user\",\"content\":\"Reply ok\"}],\"max_tokens\":10}' \\
|
||||||
|
| head -c 350
|
||||||
|
echo ''
|
||||||
|
done
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как читать ошибки
|
||||||
|
|
||||||
|
| Сообщение | Что значит | Как чинить |
|
||||||
|
|-----------|------------|-----------|
|
||||||
|
| `Unauthorized` / `Missing API key` | Нет Bearer header или ключ отозван | Достать новый из `api_keys` |
|
||||||
|
| `[400]: The requested model is not available for integrator "vscode-chat"` | MS GitHub Copilot integrator не отдаёт эту модель | Использовать другой источник; проверить какие модели MS сейчас выдаёт (в самом сообщении список Available) |
|
||||||
|
| `[400]: The requested model is not supported` | OmniRoute шлёт ID который провайдер не понимает (часто `4.6` vs `4-6`) | Баг mapping в OmniRoute → `npm install omniroute@latest` или вручную писать поддерживаемый ID |
|
||||||
|
| `[400]: Invalid model. Please select a different model` | Провайдер (Kiro/Kilo) не выдаёт эту модель в их каталоге | Использовать другую модель этого провайдера (у Kiro есть только Sonnet) |
|
||||||
|
| `Missing Google projectId for Antigravity account. Please reconnect OAuth in Providers → Antigravity` | OAuth-сессия Google Antigravity без активного Cloud Code project | Зайти на [antigravity.google](https://antigravity.google), создать project, OmniRoute Dashboard → Providers → Antigravity → Reconnect |
|
||||||
|
| Empty response | Провайдер не отвечает / dead | Проверить аккаунт/ключ провайдера в OmniRoute Dashboard |
|
||||||
|
| `401` от Anthropic | CLIProxy OAuth протух (Claude Code session) | OmniRoute Dashboard → Providers → Claude → Reconnect |
|
||||||
|
|
||||||
|
## Логи OmniRoute
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshpass -p '1qaz!QAZ' ssh root@10.0.0.179 "journalctl -u omniroute --since '1 hour ago' --no-pager | tail -50"
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключевые маркеры в логах:
|
||||||
|
- `[USAGE] <PROVIDER>` — фактические вызовы (KIRO, CC, CLAUDE, ANTIGRAVITY...)
|
||||||
|
- `model fallback decision` — failover включился
|
||||||
|
- `Account quota exceeded` — pool аккаунта провайдера исчерпан
|
||||||
|
- `circuit breaker open` — провайдер временно отключён OmniRoute из-за серии ошибок
|
||||||
|
- `MEMORY_EXTRACTION` — Memory Tool через OmniRoute
|
||||||
|
|
||||||
|
## Известные «вечные» гнилые модели (на 2026-05-06)
|
||||||
|
|
||||||
|
Не тратить на них тесты, пока OmniRoute не обновится:
|
||||||
|
- `gh/claude-opus-4.6` — MS убрала из integrator scope
|
||||||
|
- `gh/claude-opus-4.7` — баг ID mapping
|
||||||
|
- `kr/claude-opus-*` / `kiro/claude-opus-*` — Kiro Opus не выдаёт в принципе
|
||||||
|
- `kc/anthropic/*` / `kilocode/anthropic/*` — провайдеры мёртвы / не настроены
|
||||||
|
- `antigravity/claude-opus-4-6-thinking` — пока OAuth projectId не подключен
|
||||||
218
snippets/openclaw-kb-webhook.md
Normal file
218
snippets/openclaw-kb-webhook.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-06
|
||||||
|
type: snippet
|
||||||
|
tags: [openclaw, gitea, webhook, kb-sync]
|
||||||
|
status: deployed
|
||||||
|
---
|
||||||
|
|
||||||
|
# Webhook Gitea → kb-pull на LXC 137 (Максимка)
|
||||||
|
|
||||||
|
Push с Mac → видно боту в FTS за **~11 секунд** (было 5–15 минут через cron `*/15`). Развёрнуто 2026-05-06. Подробности — [[../decisions/2026-05-06-openclaw-kb-webhook-deployment]].
|
||||||
|
|
||||||
|
Cron `*/15` оставлен safety net — спасает, если listener умер или Gitea недоступна.
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```
|
||||||
|
Mac git push ──► Gitea (LXC 136, 10.0.0.189)
|
||||||
|
│ webhook POST + HMAC-SHA256
|
||||||
|
▼
|
||||||
|
LXC 137 :18790 kb-pull-webhook.service
|
||||||
|
│ subprocess.Popen
|
||||||
|
▼
|
||||||
|
/usr/local/bin/kb-pull.sh
|
||||||
|
│ git fetch + ff-only / auto-reset
|
||||||
|
│ if HEAD changed → openclaw memory index
|
||||||
|
▼
|
||||||
|
FTS реиндекс ~38 сек на 875 файлов / 1791 chunk
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. Listener `/usr/local/bin/kb-pull-webhook.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Listens for Gitea push webhooks, triggers kb-pull.sh."""
|
||||||
|
import hmac, hashlib, http.server, os, subprocess, sys
|
||||||
|
|
||||||
|
SECRET = os.environ.get("GITEA_WEBHOOK_SECRET", "").encode()
|
||||||
|
PULL = "/usr/local/bin/kb-pull.sh"
|
||||||
|
|
||||||
|
class H(http.server.BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
n = int(self.headers.get("Content-Length", 0) or 0)
|
||||||
|
body = self.rfile.read(n)
|
||||||
|
sys.stderr.write("WH POST event=%s sig=%s len=%d\n" % (
|
||||||
|
self.headers.get("X-Gitea-Event", "-"),
|
||||||
|
(self.headers.get("X-Gitea-Signature", "-") or "-")[:12],
|
||||||
|
n)); sys.stderr.flush()
|
||||||
|
if SECRET:
|
||||||
|
sig = self.headers.get("X-Gitea-Signature", "")
|
||||||
|
mac = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
|
||||||
|
if not hmac.compare_digest(sig, mac):
|
||||||
|
sys.stderr.write("WH 401 sig-mismatch\n"); sys.stderr.flush()
|
||||||
|
self.send_error(401); return
|
||||||
|
subprocess.Popen([PULL], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
self.send_response(202); self.end_headers()
|
||||||
|
self.wfile.write(b"queued\n")
|
||||||
|
sys.stderr.write("WH 202 kb-pull launched\n"); sys.stderr.flush()
|
||||||
|
def do_GET(self): # health-check
|
||||||
|
self.send_response(200); self.end_headers()
|
||||||
|
self.wfile.write(b"ok\n")
|
||||||
|
def log_message(self, fmt, *a): # стандартный access log в journal
|
||||||
|
sys.stderr.write("WH-base " + (fmt % a) + "\n"); sys.stderr.flush()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
port = int(os.environ.get("PORT", "18790"))
|
||||||
|
sys.stderr.write("WH listener up on :%d secret=%s\n" % (port, "yes" if SECRET else "NO"))
|
||||||
|
sys.stderr.flush()
|
||||||
|
http.server.HTTPServer(("0.0.0.0", port), H).serve_forever()
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x /usr/local/bin/kb-pull-webhook.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. systemd unit `/etc/systemd/system/kb-pull-webhook.service`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=KB pull webhook listener (Gitea -> kb-pull.sh)
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Environment=GITEA_WEBHOOK_SECRET=<openssl rand -hex 32>
|
||||||
|
Environment=PORT=18790
|
||||||
|
ExecStart=/usr/bin/python3 /usr/local/bin/kb-pull-webhook.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SECRET=$(openssl rand -hex 32)
|
||||||
|
sed -i "s|<openssl rand -hex 32>|$SECRET|" /etc/systemd/system/kb-pull-webhook.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now kb-pull-webhook.service
|
||||||
|
ss -ltnp | grep 18790 # должен слушать на 0.0.0.0:18790
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Хук в `kb-pull.sh` — реиндекс при новом HEAD
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# read-only pull knowledge-base for openclaw context
|
||||||
|
# Auto-reset at divergence; reindex FTS on new commits.
|
||||||
|
set -u
|
||||||
|
exec 9>/tmp/kb-pull.lock
|
||||||
|
flock -w 180 9 || exit 0 # ← ждать до 3 мин, не -n (race на двойном webhook)
|
||||||
|
cd /root/knowledge-base
|
||||||
|
|
||||||
|
LOG=/var/log/kb-pull.log
|
||||||
|
{
|
||||||
|
echo "--- $(date -Iseconds) ---"
|
||||||
|
HEAD_BEFORE=$(git rev-parse HEAD 2>/dev/null)
|
||||||
|
git fetch --quiet origin main
|
||||||
|
if ! git merge-base --is-ancestor HEAD origin/main 2>/dev/null; then
|
||||||
|
echo "divergence detected, resetting to origin/main"
|
||||||
|
git reset --hard origin/main
|
||||||
|
else
|
||||||
|
git pull --ff-only --quiet origin main
|
||||||
|
fi
|
||||||
|
HEAD_AFTER=$(git rev-parse HEAD 2>/dev/null)
|
||||||
|
if [ "$HEAD_BEFORE" != "$HEAD_AFTER" ]; then
|
||||||
|
echo "HEAD: $HEAD_BEFORE -> $HEAD_AFTER, triggering FTS reindex"
|
||||||
|
timeout 120 openclaw memory index 2>&1 | tail -5
|
||||||
|
else
|
||||||
|
echo "no new commits, skip reindex"
|
||||||
|
fi
|
||||||
|
} >> $LOG 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
`flock -w 180` — критично. Если webhook прилетает во время предыдущего pull/reindex (быстрые двойные push), `-n` молча выходит и пропускает push.
|
||||||
|
|
||||||
|
## 4. Регистрация webhook в Gitea (через API)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SECRET=... # тот же, что в systemd unit
|
||||||
|
curl -u oleg:OL260380eg -X POST http://10.0.0.189:3000/api/v1/repos/oleg/knowledge-base/hooks \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"type\":\"gitea\",\"config\":{\"url\":\"http://10.0.0.239:18790/\",\"content_type\":\"json\",\"secret\":\"$SECRET\"},\"events\":[\"push\"],\"active\":true,\"branch_filter\":\"main\"}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Через UI: `git.dttb.ru` → репо → Settings → Webhooks → Add → Gitea, заполнить теми же полями.
|
||||||
|
|
||||||
|
## 5. **Обязательно:** разрешить private IP в Gitea
|
||||||
|
|
||||||
|
По умолчанию Gitea **блокирует webhooks на private адреса** (SSRF-protection). В docker-логах будет:
|
||||||
|
|
||||||
|
> `webhook can only call allowed HTTP servers (check your webhook.ALLOWED_HOST_LIST setting), deny '10.0.0.239'`
|
||||||
|
|
||||||
|
Listener при этом ничего не получит, в его journal пусто. Лечится в `app.ini` Gitea:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[webhook]
|
||||||
|
ALLOWED_HOST_LIST = 10.0.0.0/24,*.dttb.ru,private
|
||||||
|
```
|
||||||
|
|
||||||
|
Конкретно у нас Gitea в docker, конфиг — `/opt/gitea/data/gitea/conf/app.ini` на LXC 136. После правки:
|
||||||
|
```bash
|
||||||
|
docker restart gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Проверка end-to-end
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# на Mac
|
||||||
|
cd ~/knowledge-base
|
||||||
|
git commit --allow-empty -m "smoke" && git push
|
||||||
|
|
||||||
|
# на 137 (через 5–10 сек)
|
||||||
|
pct exec 137 -- journalctl -u kb-pull-webhook.service --since "30 sec ago" -n 5
|
||||||
|
# WH POST event=push sig=... len=...
|
||||||
|
# WH 202 kb-pull launched
|
||||||
|
pct exec 137 -- tail -3 /var/log/kb-pull.log
|
||||||
|
# HEAD: <old> -> <new>, triggering FTS reindex
|
||||||
|
# Memory index updated (main).
|
||||||
|
```
|
||||||
|
|
||||||
|
Измеренный тайминг real push (2026-05-06): T0 git push → T+11s `git rev-parse HEAD` на 137 уже новый.
|
||||||
|
|
||||||
|
## 7. Опционально: ограничить порт по источнику
|
||||||
|
|
||||||
|
Listener слушает `0.0.0.0:18790`. Если нужно жёстко закрыть:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
iptables -A INPUT -p tcp --dport 18790 -s 10.0.0.189 -j ACCEPT
|
||||||
|
iptables -A INPUT -p tcp --dport 18790 -j DROP
|
||||||
|
```
|
||||||
|
|
||||||
|
Не делал — homelab LAN trusted, secret-HMAC уже отсекает левые запросы.
|
||||||
|
|
||||||
|
## Откат
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# на 137
|
||||||
|
systemctl disable --now kb-pull-webhook.service
|
||||||
|
rm /etc/systemd/system/kb-pull-webhook.service /usr/local/bin/kb-pull-webhook.py
|
||||||
|
# восстановить kb-pull.sh из бэкапа
|
||||||
|
cp /usr/local/bin/kb-pull.sh.bak.* /usr/local/bin/kb-pull.sh
|
||||||
|
|
||||||
|
# в Gitea
|
||||||
|
curl -u oleg:OL260380eg -X DELETE http://10.0.0.189:3000/api/v1/repos/oleg/knowledge-base/hooks/<id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Cron `*/15` продолжит работать.
|
||||||
|
|
||||||
|
## Грабли (выписаны из реального деплоя)
|
||||||
|
|
||||||
|
| Симптом | Причина | Где смотреть |
|
||||||
|
|---|---|---|
|
||||||
|
| Test delivery 204 от Gitea, но listener не получил | `webhook.ALLOWED_HOST_LIST` не разрешает private IP | `docker logs gitea` на LXC 136 |
|
||||||
|
| Двойной push — второй пропускается | `flock -n` без ожидания | Заменить на `flock -w 180` |
|
||||||
|
| FTS не обновился, хотя git pull прошёл | `openclaw memory` не дёргается | Хук в `kb-pull.sh` после ff-only |
|
||||||
|
| Listener в restart-loop после правки | sed побил python синтаксис | `journalctl -u kb-pull-webhook --since "1 min ago"` |
|
||||||
Reference in New Issue
Block a user