ММФБ Юрий: апгрейд Win10→Win11 25H2 + отчёт клиенту PDF
This commit is contained in:
61
snippets/amnezia-vpn-client-instruction.md
Normal file
61
snippets/amnezia-vpn-client-instruction.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
date: 2026-04-24
|
||||
type: snippet
|
||||
tags: [vpn, amneziavpn, client, instruction]
|
||||
---
|
||||
|
||||
# Инструкция для клиента: подключение через AmneziaVPN
|
||||
|
||||
Готовый текст для отправки клиенту. Конфиг-файл `<имя>.vpn` или QR — из `assets/`.
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Подключение к VPN (AmneziaVPN)
|
||||
|
||||
**1. Установите AmneziaVPN:**
|
||||
|
||||
- 📱 iPhone / iPad: https://apps.apple.com/app/amneziavpn/id1600529900
|
||||
- 🤖 Android (Google Play): https://play.google.com/store/apps/details?id=org.amnezia.vpn
|
||||
- 🤖 Android (прямой APK): https://github.com/amnezia-vpn/amnezia-client/releases/latest
|
||||
- 💻 Windows: https://github.com/amnezia-vpn/amnezia-client/releases/latest
|
||||
- 🍎 macOS: https://github.com/amnezia-vpn/amnezia-client/releases/latest
|
||||
- 🐧 Linux: https://github.com/amnezia-vpn/amnezia-client/releases/latest
|
||||
|
||||
Если приложения **нет в российском App Store** — см. [[apple-id-us-on-russia]] (зайти в App Store под американским Apple ID).
|
||||
|
||||
**2. Импорт конфига:**
|
||||
|
||||
У AmneziaVPN **QR в Happ-стиле не работает** (конфиг слишком длинный — QR получается нечитаемым). Используй ссылку или файл.
|
||||
|
||||
- **Ссылка** `vpn://…` (пришлю) — тапнуть по ней в мессенджере на телефоне, AmneziaVPN перехватит. Если не перехватил — скопировать ссылку → в AmneziaVPN: `+` → **«Из буфера обмена»** / **Import from clipboard**.
|
||||
- **Файл** `<имя>.vpn` (альтернатива) — открыть через «Файлы» / проводник → «Открыть в AmneziaVPN» → профиль добавится сам.
|
||||
|
||||
**3. Подключение:**
|
||||
|
||||
В AmneziaVPN на главном экране — большая кнопка включения. При первом запуске разрешите создание VPN-профиля в системе.
|
||||
|
||||
Проверить IP: https://2ip.ru — должен показать страну сервера-выхода.
|
||||
|
||||
---
|
||||
|
||||
### Поделиться с другим своим устройством
|
||||
|
||||
Чтобы не просить новый ключ — AmneziaVPN умеет делиться профилем:
|
||||
|
||||
- **iOS/Android:** тап / долгое нажатие на профиль → «Поделиться» / «Share connection» → получить `vpn://…` → переслать себе (TG «Сохранённые», почта, заметки).
|
||||
- **Windows/macOS:** шестерёнка рядом с сервером → «Поделиться соединением».
|
||||
|
||||
На втором устройстве: установить AmneziaVPN → «+» → «Импорт из буфера обмена».
|
||||
|
||||
### Если не работает
|
||||
|
||||
- Android: выключить «Экономия батареи» для AmneziaVPN — иначе система обрывает туннель
|
||||
- iOS: «Настройки» → «VPN» — должен быть активный профиль AmneziaVPN; если нет, удалить и импортировать заново
|
||||
- Перезапустить приложение, затем само устройство
|
||||
|
||||
### Чем отличается от Happ
|
||||
|
||||
- **Happ** — клиент протокола VLESS/Reality (через XRay). Хорош тем, что работает на Finland-сервере где уже поднят Reality.
|
||||
- **AmneziaVPN** — клиент AmneziaWG / OpenVPN / Cloak. Работает там где поднят Amnezia-сервер (в т.ч. можно поднять свой — инструкция в amnezia.org).
|
||||
|
||||
Для одного клиента **достаточно одного** из них, кто работает на вашем сервере.
|
||||
61
snippets/apple-id-us-on-russia.md
Normal file
61
snippets/apple-id-us-on-russia.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
date: 2026-04-24
|
||||
type: snippet
|
||||
tags: [apple, appleid, vpn, ios, macos]
|
||||
---
|
||||
|
||||
# Американский Apple ID на российском iPhone / iPad / Mac
|
||||
|
||||
Нужен чтобы скачивать **AmneziaVPN, Happ, Instagram, TikTok** и прочие приложения, убранные из российского App Store. Предполагается, что **US Apple ID уже куплен/создан** и пароль + резервные данные на руках.
|
||||
|
||||
## Главный принцип
|
||||
|
||||
Менять аккаунт надо **только в App Store**, НЕ трогая iCloud. Тогда вся почта/фото/контакты останутся на российском ID, а US ID используется только для загрузки приложений.
|
||||
|
||||
## Порядок на iPhone / iPad
|
||||
|
||||
1. «Настройки» → ваш **RU Apple ID** сверху → прокрутить вниз → **«Медиа и покупки»** → **«Выйти»**.
|
||||
⚠️ НЕ нажимать «Выйти» вверху на самом Apple ID — это выкинет из iCloud и сотрёт локальные iCloud-данные, если дисковый кэш настроен плохо.
|
||||
|
||||
2. Открыть **App Store** → тап по иконке профиля в правом верхнем углу → **«Войти»** → ввести **US Apple ID** + пароль.
|
||||
|
||||
3. Подтверждение 2FA — код придёт на то устройство/номер, что привязан к US ID. Если номер российский — обычное SMS; если привязан US-номер, нужен либо виртуальный номер (сервисы типа grizzlysms.com, TJ ≈ $0.43), либо заранее настроенная альтернатива.
|
||||
- **В случае Ярослава (2026-04-24):** trusted номер — у Олега, **заканчивается на `...70`**. Ярослав при входе выбирает из списка номер на `...70`, Олег получает SMS и вручную пересылает код Ярославу.
|
||||
|
||||
4. В App Store убедиться, что регион внизу — **United States** (прокрутить в самый низ экрана «Поиск» или «Сегодня»).
|
||||
|
||||
5. Скачать нужные приложения (AmneziaVPN, Happ и т.д.). Иконки обычных RU-приложений при этом не пропадают — просто обновляться через US ID они не будут.
|
||||
|
||||
6. (опционально) После скачивания можно вернуться в RU Apple ID в «Медиа и покупки» — приложения, уже скачанные из US, **продолжат работать**, но обновления для них появятся, только когда снова войдёшь в US ID.
|
||||
|
||||
## Порядок на Mac
|
||||
|
||||
1. App Store (через Dock / Launchpad) → в левом нижнем углу на имени аккаунта → **«Выйти»**
|
||||
2. Войти под US Apple ID в том же App Store
|
||||
3. Скачать приложения
|
||||
4. (опционально) Выйти и вернуть RU ID
|
||||
|
||||
iCloud / Настройки → Apple ID на Mac — не трогать.
|
||||
|
||||
## Частые ошибки и нюансы
|
||||
|
||||
- **Способ оплаты «None»**. При первом входе US App Store иногда требует принять новые правила и выбрать способ оплаты. Выбрать **None** (или «Нет»). Если такого пункта нет — Apple определил «подозрительный» адрес, см. следующий пункт.
|
||||
- **Адрес**. US ID требует реальный US-адрес. Если его нет — ставить любой рабочий (штат без налога на приложения — Oregon, Delaware, Montana — чтобы не получать комиссию на платные покупки). Реальный номер US-телефона не обязателен, если 2FA завязан на trusted device.
|
||||
- **Apple Pay / платные подписки**. С RU-картой в US-аккаунте работать **не будут**. Для платного покупать iTunes gift card US через редкомпилейшн-магазины.
|
||||
- **iMessage и FaceTime**. Останутся на RU Apple ID и RU номере — US ID для App Store их не трогает.
|
||||
- **iCloud Drive**. Ни в коем случае не входить в iCloud под US ID — это может смержить/потерять данные. App Store и iCloud — разные логины в настройках.
|
||||
- **Семейный доступ**. US ID нельзя добавить в RU Family Sharing и наоборот. Каждый аккаунт существует отдельно.
|
||||
|
||||
## Если всё таки захотел переключиться полностью на US iCloud
|
||||
|
||||
Это **не нужно** для скачивания VPN-приложений. Если всё же: сперва сделать полный бэкап RU-аккаунта (iCloud фото выгрузить локально, контакты экспортировать как `.vcf`), потом выйти из RU ID на устройстве, войти в US ID. Обратный путь — с потерями.
|
||||
|
||||
## Связь с VPN-учётом
|
||||
|
||||
В [[../projects/dttb/vpn-clients]] есть колонка «Заметки». Если клиент столкнулся с тем, что приложения нет в RU App Store — поставить там `нужен US ID` и при выдаче доступа приложить ссылку на этот файл.
|
||||
|
||||
## Ссылки
|
||||
|
||||
- AmneziaVPN (US App Store) — https://apps.apple.com/us/app/amneziavpn/id1600529900
|
||||
- Happ Proxy Utility (US App Store) — https://apps.apple.com/us/app/happ-proxy-utility/id6504287215
|
||||
- Оригинальный гайд Apple по смене региона (рус) — https://support.apple.com/ru-ru/HT201389 (не нужен если просто переключать App Store)
|
||||
169
snippets/clients/yaroslav-amnezia-setup.md
Normal file
169
snippets/clients/yaroslav-amnezia-setup.md
Normal file
@@ -0,0 +1,169 @@
|
||||
---
|
||||
date: 2026-04-24
|
||||
type: snippet
|
||||
tags: [vpn, amneziavpn, client, yaroslav]
|
||||
---
|
||||
|
||||
# Ярослав — подключение AmneziaVPN
|
||||
|
||||
Клиент технически слаб. **Основной канал доставки** — Telegra.ph-страница (Telegram рендерит через Instant View, тап по код-блоку = копирование):
|
||||
|
||||
- **https://telegra.ph/Nastrojka-VPN-04-24-2** — всё внутри: пошагово iPhone/Android/Win/Mac, Apple ID и VPN-ключ в `<pre>` блоках
|
||||
- Редактировать страницу: https://edit.telegra.ph/auth/f1tfgzYpPpGlAr7cYHRzSeH59fYuNVB2V3fbCdypDc (access token в [[../../projects/dttb/credentials]] → Telegra.ph)
|
||||
|
||||
Запасной вариант — отправить тремя сообщениями (ниже в файле):
|
||||
1. Инструкция (markdown ломается в TG — не лучший вариант)
|
||||
2. Apple ID (логин + пароль)
|
||||
3. Ключ `vpn://…`
|
||||
|
||||
Данные:
|
||||
- Apple ID: `hbuggle819@icloud.com` / `App5870w` (см. [[../../projects/dttb/credentials]] → «Apple ID (США)»)
|
||||
- 2FA: номер Олега `...70`, Олег получает SMS и пересылает код вручную
|
||||
- Ключ: `vpn://AAAIYXjanV…VC9O` — полный в сообщении 3 ниже
|
||||
|
||||
---
|
||||
|
||||
## 📩 Сообщение 1 из 3 — инструкция
|
||||
|
||||
Привет! Я пришлю 3 сообщения: **инструкцию** (это), **Apple ID** (скопируешь и введёшь) и **ключ VPN** (тоже скопируешь). Не торопись, делай по шагам.
|
||||
|
||||
---
|
||||
|
||||
**📱 Если у тебя iPhone или iPad**
|
||||
|
||||
**Шаг 1. Выйти из своего Apple ID в App Store.**
|
||||
Это нужно только чтобы скачать приложение. Твои фото и контакты никуда не денутся.
|
||||
|
||||
- Открой **«Настройки»** (серая шестерёнка).
|
||||
- Наверху нажми на **своё имя**.
|
||||
- Пролистай вниз до строки **«Медиа и покупки»** → нажми на неё.
|
||||
- В появившемся окошке нажми **«Выйти»**.
|
||||
|
||||
⚠️ **Важно!** Не нажимай «Выйти» в самом верху на своём имени — это не то. Выходишь **только** в разделе «Медиа и покупки».
|
||||
|
||||
**Шаг 2. Войти в американский Apple ID.**
|
||||
|
||||
- Открой **App Store** (синяя иконка с буквой А).
|
||||
- В правом верхнем углу — кружок с силуэтом → нажми.
|
||||
- Нажми **«Войти»** или **«Использовать другой Apple ID»**.
|
||||
- Введи логин и пароль из **сообщения 2**.
|
||||
|
||||
**Шаг 3. Подтверждение входа.**
|
||||
|
||||
Apple спросит, куда отправить код. Появится список номеров.
|
||||
→ Выбери номер, **который заканчивается на ...70** (это мой).
|
||||
→ Я получу SMS и сразу пришлю тебе цифры.
|
||||
→ Введи их.
|
||||
|
||||
Готово — App Store стал американским.
|
||||
|
||||
**Шаг 4. Скачать AmneziaVPN.**
|
||||
|
||||
В App Store в строке поиска набери **AmneziaVPN** → нажми «Загрузить».
|
||||
|
||||
**Шаг 5. Вернуть свой обычный Apple ID (необязательно, но рекомендую).**
|
||||
|
||||
Повтори Шаг 1, но в конце войди под своим прежним ID. Приложение останется, обновления будут от меня.
|
||||
|
||||
---
|
||||
|
||||
**Шаг 6. Подключить VPN.**
|
||||
|
||||
- Открой мне сообщение 3, зажми пальцем ключ `vpn://...` → **«Копировать»**.
|
||||
- Открой **AmneziaVPN**.
|
||||
- Нажми **«+»** (или «Добавить сервер»).
|
||||
- Нажми **«Вставить из буфера обмена»** (варианты надписи: «Import from clipboard», «Из буфера обмена»).
|
||||
- Сервер добавится сам.
|
||||
|
||||
**Шаг 7. Включить.**
|
||||
|
||||
На главном экране AmneziaVPN — **большая круглая кнопка**. Нажми.
|
||||
|
||||
Всплывёт окошко «Разрешить подключения VPN» → **«Разрешить»**. Введи пароль от телефона если попросит.
|
||||
|
||||
Кнопка станет **зелёной** (или сменит надпись на «Подключено»). VPN работает.
|
||||
|
||||
**Шаг 8. Проверить.**
|
||||
|
||||
Открой в Safari https://2ip.ru
|
||||
Должна показаться **другая страна** (не Россия). Значит всё.
|
||||
|
||||
---
|
||||
|
||||
**💻 Если у тебя компьютер (Windows или Mac)**
|
||||
|
||||
Apple ID не нужен. Просто:
|
||||
1. Скачай AmneziaVPN: https://github.com/amnezia-vpn/amnezia-client/releases/latest (файл `AmneziaVPN_x64_*.exe` для Windows или `AmneziaVPN.dmg` для Mac).
|
||||
2. Установи, открой.
|
||||
3. Скопируй ключ из сообщения 3.
|
||||
4. В программе: «+» → «Вставить из буфера обмена».
|
||||
5. Нажми большую кнопку включения.
|
||||
|
||||
**🤖 Если у тебя Android**
|
||||
|
||||
Apple ID не нужен. Просто:
|
||||
1. Открой Google Play, найди **AmneziaVPN**, установи.
|
||||
(Если в Play нет — скачай APK: https://github.com/amnezia-vpn/amnezia-client/releases/latest)
|
||||
2. Скопируй ключ из сообщения 3.
|
||||
3. В AmneziaVPN: «+» → «Вставить».
|
||||
4. Нажми большую кнопку включения.
|
||||
|
||||
---
|
||||
|
||||
**📲 Хочешь поставить на второе своё устройство?**
|
||||
|
||||
Не проси у меня — AmneziaVPN сам делится:
|
||||
|
||||
1. В AmneziaVPN **на рабочем устройстве** нажми на сервер → иконка «i» / «Подробнее» → **«Поделиться подключением»**.
|
||||
2. Получишь ссылку → отправь себе в Telegram «Избранное» или на почту.
|
||||
3. На втором устройстве установи AmneziaVPN → скопируй ссылку → «+» → «Вставить».
|
||||
|
||||
---
|
||||
|
||||
**❓ Если что-то идёт не так**
|
||||
|
||||
- **Не пришёл код от Apple** — напиши, перевышлю.
|
||||
- **Кнопка в AmneziaVPN не зелёная, пишет «ошибка»** — закрой приложение (свайпни вверх), открой снова. Не помогло — перезагрузи телефон.
|
||||
- **Android**: если VPN отваливается — «Настройки телефона → Приложения → AmneziaVPN → Батарея → Без ограничений».
|
||||
- **iPhone**: если пишет «Профиль VPN не найден» — «Настройки → Основные → VPN и управление устройством». Если профиль AmneziaVPN там есть, удали его, вернись в AmneziaVPN и снова нажми «+» → «Вставить».
|
||||
|
||||
Если совсем не получается — скинь мне скриншот того что видишь, разберёмся вместе.
|
||||
|
||||
---
|
||||
|
||||
## 📩 Сообщение 2 из 3 — Apple ID
|
||||
|
||||
```
|
||||
hbuggle819@icloud.com
|
||||
App5870w
|
||||
```
|
||||
|
||||
Выбирай номер для кода — **на ...70**. Напиши мне когда Apple попросит код.
|
||||
|
||||
---
|
||||
|
||||
## 📩 Сообщение 3 из 3 — ключ VPN
|
||||
|
||||
Зажми блок ниже → «Копировать». Потом в AmneziaVPN «+» → «Вставить из буфера обмена».
|
||||
|
||||
```
|
||||
vpn://AAAIYXjanVVdcpswEH7PKTyevjl1QNiAM5MH100T4vwY06ZpQscjg-yoIYKAbMfN5Ay9RI_Qh850egf3Rl0hDHhMH1LwWOL7Pq1Wq13paacGT90LGceUkTip79duUkw8T3kvVeHFFOhNMCWOVcDrpq4oCtJ0tb5bIUFC0jEM1NE11KmUaKkEqQjs6K1KSUtIVA2Zmm4iU6nSnHhCo1VS9_gxNQATVPOUCb5dyTrpKo12JZctr4oLcMJHEOEJFeGrP7lMwC5EzYVvt4ibW9_NOSS5PGBlTsu4daTKXEtyRYhKpJVNWIbQNqRtQ61tqL0FnXgS0soYRDzzCFzdICiTRLsMO5mLRrsM5sEogTgIwgXxRzRKBHsjcckpzfTdKyxLfH9fQBL5nFvyAkoYt3w5ydX1ZY88ODYd8y_mO2tsX7KO-fHDu4fjuPfYGB7ZSTAJ9g7tcc88KLkjjYA369U2zabaNNVtSRTT-eiOLKUQzd-fzazItr1J_2qpWsNbpnfnR-Z8cnVMNM4YPfcXEekrj1XTRbNxYep_PU8zU5q4sRgn8QR75LPrsq7vxyRJage1fDl7GgLi7bkD4KvB0DrrDj-N4HO39so57F2cv82-QTSAdWJO-mQJ2hct02UnHozRRAfSBLqQI6KPH1NfFPHlqNCHNIEegh7khsuOBVaUEwCSWtcQAJoA8sIBoCVMFtXiMksYES3KWi1rW1nbli38bgaExCJSg9k4oJ5caq_xqfGle5TM8CmPptfzu-lpT_MeHobBZWe-uOh6dnAxPvWn9NA-SMNEklscE1-Odt5wyzBUNRz3LxuOjenRidroRHTv6lZ3rpdTf8GWuvpVH9gtMborq8AaiF3K0363lma6yw6ZH4WUcbEDCmoaalNFTdXU9zUDKSIeAzjyacIhl_qERDigcyK0IqylFLkNE36O70mWsmVLJdU9n60PH6OMR_kckKskGqWzZKbKZR6FMRdw6luBJndFir8oOoXlhMRzEm9Wy4u2Cc6MZ5dVHe7C6fS-EU5XCXiMWSJUUPghD4V25kf1DeHz5rjiOhZqfM_IV4pfi9s3lz3vyDNMXt8-meBZwHv_GpbLEi-mEadhesutvq9-rn6vfv35Bv8_Vj__fMuFLEnvOthk8ZZgJGGRZzm8zg5BbeRGfed55y-IVC9O
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Внутренние заметки (не отправлять клиенту)
|
||||
|
||||
- **Клиент:** Ярослав
|
||||
- **Дата выдачи:** 2026-04-24
|
||||
- **Конфиг:** ссылка `vpn://AAAIYXjanV…VC9O` (AmneziaVPN native format, base64-encoded, содержит параметры сервера и ключи)
|
||||
- **Сервер:** предположительно Finland `78.17.4.225` (уточнить декодом base64 при необходимости)
|
||||
- **US Apple ID:** `hbuggle819@icloud.com` / `App5870w` — в [[../../projects/dttb/credentials]] → «Apple ID (США)». 2FA на номер Олега `...70`
|
||||
- **Запись в реестре:** [[../../projects/dttb/vpn-clients]]
|
||||
|
||||
### Как отозвать у Ярослава
|
||||
|
||||
1. SSH на сервер (см. [[../../projects/dttb/vpn-clients]] → Finland → [[feedback_finland_security]])
|
||||
2. Найти peer Ярослава в `/opt/amnezia/…/wg0.conf` или в amnezia-docker-контейнере
|
||||
3. Удалить `[Peer]` блок → `wg syncconf wg0 <(wg-quick strip wg0)`
|
||||
4. Отметить в таблице: «Отозван: YYYY-MM-DD»
|
||||
7
snippets/netbird-watchdog/netbird-watchdog.service
Normal file
7
snippets/netbird-watchdog/netbird-watchdog.service
Normal file
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=Watchdog: detect stuck NetBird and restart
|
||||
After=netbird.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/netbird-watchdog.sh
|
||||
38
snippets/netbird-watchdog/netbird-watchdog.sh
Normal file
38
snippets/netbird-watchdog/netbird-watchdog.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# netbird-watchdog.sh — рестарт netbird если daemon застрял
|
||||
# (Management Connected, но Relays=0 или Peers=0 при ненулевом total)
|
||||
# Минимум 5 минут между рестартами чтобы не зацикливалось.
|
||||
|
||||
LOCK=/run/netbird-watchdog.last-restart
|
||||
NOW=$(date +%s)
|
||||
|
||||
if [ -f "$LOCK" ]; then
|
||||
LAST=$(cat "$LOCK")
|
||||
if [ $((NOW - LAST)) -lt 300 ]; then
|
||||
exit 0 # был рестарт менее 5 мин назад
|
||||
fi
|
||||
fi
|
||||
|
||||
STATUS=$(netbird status 2>&1) || exit 0
|
||||
echo "$STATUS" | grep -q "^Management: Connected" || exit 0 # даём демону самому подняться
|
||||
|
||||
stuck=0
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
Relays:*|"Peers count:"*)
|
||||
cur=$(echo "$line" | awk '{print $(NF-1)}' | awk -F/ '{print $1}')
|
||||
total=$(echo "$line" | awk '{print $(NF-1)}' | awk -F/ '{print $2}')
|
||||
[ -z "$cur" ] || [ -z "$total" ] && continue
|
||||
if [ "$total" -gt 0 ] && [ "$cur" -eq 0 ]; then
|
||||
logger -t netbird-watchdog "stuck: $line — will restart"
|
||||
stuck=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done <<< "$STATUS"
|
||||
|
||||
if [ "$stuck" = "1" ]; then
|
||||
echo "$NOW" > "$LOCK"
|
||||
systemctl restart netbird
|
||||
logger -t netbird-watchdog "netbird restarted"
|
||||
fi
|
||||
10
snippets/netbird-watchdog/netbird-watchdog.timer
Normal file
10
snippets/netbird-watchdog/netbird-watchdog.timer
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Run NetBird watchdog every 2 minutes
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=2min
|
||||
Unit=netbird-watchdog.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
272
snippets/telegraph-md-to-page.py
Executable file
272
snippets/telegraph-md-to-page.py
Executable file
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Обновить Telegra.ph страницу содержимым markdown-файла."""
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
ACCESS_TOKEN = "c38dcadb86e6edd7efc76496d9171d38beef6dc0f6a7ef2cd79bbae70e46"
|
||||
PATH = "Nastrojka-VPN-04-24-2"
|
||||
TITLE = "Настройка VPN"
|
||||
AUTHOR = "Олег"
|
||||
|
||||
# --- Inline markdown parser ---
|
||||
# Order: 1) protect `code` and [text](url) with placeholders
|
||||
# 2) parse bold/italic on remaining text
|
||||
# 3) restore placeholders
|
||||
|
||||
CODE_RE = re.compile(r"`([^`]+?)`")
|
||||
LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
||||
BOLD_RE = re.compile(r"\*\*(.+?)\*\*")
|
||||
ITALIC_STAR_RE = re.compile(r"(?<![*\w])\*([^\s*][^*]*?)\*(?![*\w])")
|
||||
ITALIC_UND_RE = re.compile(r"(?<![_\w])_([^\s_][^_]*?)_(?![_\w])")
|
||||
|
||||
def parse_inline(text):
|
||||
if not text:
|
||||
return []
|
||||
|
||||
placeholders = {}
|
||||
pc = [0]
|
||||
|
||||
def put(node):
|
||||
key = f"\x00PH{pc[0]}\x00"
|
||||
pc[0] += 1
|
||||
placeholders[key] = node
|
||||
return key
|
||||
|
||||
# 1) Protect code first (so its content is untouched by later regexes)
|
||||
text = CODE_RE.sub(lambda m: put({"tag": "code", "children": [m.group(1)]}), text)
|
||||
# 2) Protect links (link text still can have formatting — we recurse)
|
||||
text = LINK_RE.sub(lambda m: put({"tag": "a", "attrs": {"href": m.group(2)},
|
||||
"children": parse_inline(m.group(1))}), text)
|
||||
|
||||
# 3) Apply bold, then italic
|
||||
def apply_pattern(nodes, pattern, tag):
|
||||
out = []
|
||||
for n in nodes:
|
||||
if not isinstance(n, str):
|
||||
out.append(n)
|
||||
continue
|
||||
last = 0
|
||||
for m in pattern.finditer(n):
|
||||
if m.start() > last:
|
||||
out.append(n[last:m.start()])
|
||||
out.append({"tag": tag, "children": parse_inline(m.group(1))})
|
||||
last = m.end()
|
||||
if last < len(n):
|
||||
out.append(n[last:])
|
||||
return out
|
||||
|
||||
nodes = [text]
|
||||
nodes = apply_pattern(nodes, BOLD_RE, "strong")
|
||||
nodes = apply_pattern(nodes, ITALIC_STAR_RE, "em")
|
||||
nodes = apply_pattern(nodes, ITALIC_UND_RE, "em")
|
||||
|
||||
# 4) Restore placeholders in string nodes
|
||||
result = []
|
||||
for n in nodes:
|
||||
if isinstance(n, str):
|
||||
parts = re.split(r"(\x00PH\d+\x00)", n)
|
||||
for p in parts:
|
||||
if p == "":
|
||||
continue
|
||||
if p in placeholders:
|
||||
result.append(placeholders[p])
|
||||
else:
|
||||
result.append(p)
|
||||
else:
|
||||
result.append(n)
|
||||
return result
|
||||
|
||||
|
||||
# --- Block parser with nested list support ---
|
||||
def indent_of(line):
|
||||
return len(line) - len(line.lstrip(" "))
|
||||
|
||||
def is_ol_item(line):
|
||||
return re.match(r"^\s*\d+\.\s+", line)
|
||||
|
||||
def is_ul_item(line):
|
||||
return re.match(r"^\s*[-*]\s+", line)
|
||||
|
||||
def strip_list_marker(line):
|
||||
m = re.match(r"^\s*(?:\d+\.|[-*])\s+(.*)$", line)
|
||||
return m.group(1) if m else line
|
||||
|
||||
def parse_list(lines, i, base_indent):
|
||||
"""Parse a list starting at lines[i] with base_indent. Returns (node, new_i)."""
|
||||
first = lines[i]
|
||||
is_ol = bool(is_ol_item(first))
|
||||
tag = "ol" if is_ol else "ul"
|
||||
items = []
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
if not line.strip():
|
||||
# blank line — check if list continues
|
||||
if i + 1 < len(lines):
|
||||
nxt = lines[i + 1]
|
||||
if nxt.strip() and indent_of(nxt) == base_indent and (is_ol_item(nxt) or is_ul_item(nxt)):
|
||||
i += 1
|
||||
continue
|
||||
break
|
||||
ind = indent_of(line)
|
||||
if ind < base_indent:
|
||||
break
|
||||
if ind > base_indent:
|
||||
# shouldn't happen at top — break
|
||||
break
|
||||
if not (is_ol_item(line) or is_ul_item(line)):
|
||||
break
|
||||
# same-kind check: if switching from ol→ul at same indent, break
|
||||
if (is_ol and is_ul_item(line) and not is_ol_item(line)) or \
|
||||
(not is_ol and is_ol_item(line) and not is_ul_item(line)):
|
||||
break
|
||||
|
||||
# This is a list item at base_indent
|
||||
text = strip_list_marker(line)
|
||||
i += 1
|
||||
continuation_text = []
|
||||
nested_children = []
|
||||
|
||||
# Consume continuation lines and nested lists
|
||||
while i < len(lines):
|
||||
nl = lines[i]
|
||||
if not nl.strip():
|
||||
# peek ahead
|
||||
if i + 1 >= len(lines):
|
||||
i += 1
|
||||
break
|
||||
nxt = lines[i + 1]
|
||||
if nxt.strip() and indent_of(nxt) > base_indent:
|
||||
i += 1
|
||||
continue
|
||||
# end of item
|
||||
break
|
||||
ni = indent_of(nl)
|
||||
if ni <= base_indent:
|
||||
break
|
||||
# Nested list?
|
||||
if is_ol_item(nl) or is_ul_item(nl):
|
||||
nested, i = parse_list(lines, i, ni)
|
||||
nested_children.append(nested)
|
||||
continue
|
||||
# continuation line
|
||||
continuation_text.append(nl.strip())
|
||||
i += 1
|
||||
|
||||
full_text = text
|
||||
if continuation_text:
|
||||
full_text = (text + " " + " ".join(continuation_text)).strip()
|
||||
children = parse_inline(full_text) + nested_children
|
||||
items.append({"tag": "li", "children": children})
|
||||
|
||||
return {"tag": tag, "children": items}, i
|
||||
|
||||
|
||||
def parse_blocks(md):
|
||||
lines = md.splitlines()
|
||||
nodes = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
if not line.strip():
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# heading
|
||||
m = re.match(r"^(#{1,6})\s+(.+)$", line)
|
||||
if m:
|
||||
level = len(m.group(1))
|
||||
tag = "h3" if level <= 2 else "h4"
|
||||
nodes.append({"tag": tag, "children": parse_inline(m.group(2).strip())})
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# hr
|
||||
if re.match(r"^-{3,}\s*$", line):
|
||||
nodes.append({"tag": "hr"})
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# fenced code
|
||||
if line.strip().startswith("```"):
|
||||
i += 1
|
||||
buf = []
|
||||
while i < len(lines) and not lines[i].strip().startswith("```"):
|
||||
buf.append(lines[i])
|
||||
i += 1
|
||||
i += 1
|
||||
nodes.append({"tag": "pre", "children": ["\n".join(buf)]})
|
||||
continue
|
||||
|
||||
# list
|
||||
if is_ol_item(line) or is_ul_item(line):
|
||||
node, i = parse_list(lines, i, indent_of(line))
|
||||
nodes.append(node)
|
||||
continue
|
||||
|
||||
# paragraph
|
||||
buf = [line]
|
||||
i += 1
|
||||
while i < len(lines):
|
||||
nxt = lines[i]
|
||||
if not nxt.strip():
|
||||
break
|
||||
if re.match(r"^#{1,6}\s", nxt):
|
||||
break
|
||||
if re.match(r"^-{3,}\s*$", nxt):
|
||||
break
|
||||
if is_ol_item(nxt) or is_ul_item(nxt):
|
||||
break
|
||||
if nxt.strip().startswith("```"):
|
||||
break
|
||||
buf.append(nxt)
|
||||
i += 1
|
||||
# Preserve line breaks within paragraph using <br> — Telegraph supports it
|
||||
# Strip trailing markdown hard-break " " marker
|
||||
cleaned = [re.sub(r"\s+$", "", b) for b in buf]
|
||||
# Join with space, inserting br between lines that were originally separated
|
||||
inline_children = []
|
||||
for idx, part in enumerate(cleaned):
|
||||
if idx > 0:
|
||||
inline_children.append({"tag": "br"})
|
||||
inline_children.extend(parse_inline(part))
|
||||
nodes.append({"tag": "p", "children": inline_children})
|
||||
return nodes
|
||||
|
||||
|
||||
def main():
|
||||
md = subprocess.check_output(
|
||||
["sshpass", "-p", "1qaz!QAZ",
|
||||
"ssh", "-o", "StrictHostKeyChecking=no", "root@10.0.0.250",
|
||||
"pct exec 137 -- cat /tmp/vpn-instruction-improved.md"],
|
||||
text=True
|
||||
)
|
||||
|
||||
content = parse_blocks(md)
|
||||
|
||||
# Dry-run preview
|
||||
if "--dry" in sys.argv:
|
||||
print(json.dumps(content, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
data = urllib.parse.urlencode({
|
||||
"access_token": ACCESS_TOKEN,
|
||||
"title": TITLE,
|
||||
"author_name": AUTHOR,
|
||||
"content": json.dumps(content, ensure_ascii=False),
|
||||
"return_content": "false",
|
||||
}).encode()
|
||||
req = urllib.request.Request(f"https://api.telegra.ph/editPage/{PATH}", data=data)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
result = json.loads(resp.read().decode())
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
if not result.get("ok"):
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
171
snippets/telegraph-publish-client-instruction.py
Normal file
171
snippets/telegraph-publish-client-instruction.py
Normal file
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Публикует инструкцию Ярославу на Telegra.ph"""
|
||||
import json
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
ACCESS_TOKEN = "c38dcadb86e6edd7efc76496d9171d38beef6dc0f6a7ef2cd79bbae70e46"
|
||||
|
||||
VPN_KEY = "vpn://AAAIYXjanVVdcpswEH7PKTyevjl1QNiAM5MH100T4vwY06ZpQscjg-yoIYKAbMfN5Ay9RI_Qh850egf3Rl0hDHhMH1LwWOL7Pq1Wq13paacGT90LGceUkTip79duUkw8T3kvVeHFFOhNMCWOVcDrpq4oCtJ0tb5bIUFC0jEM1NE11KmUaKkEqQjs6K1KSUtIVA2Zmm4iU6nSnHhCo1VS9_gxNQATVPOUCb5dyTrpKo12JZctr4oLcMJHEOEJFeGrP7lMwC5EzYVvt4ibW9_NOSS5PGBlTsu4daTKXEtyRYhKpJVNWIbQNqRtQ61tqL0FnXgS0soYRDzzCFzdICiTRLsMO5mLRrsM5sEogTgIwgXxRzRKBHsjcckpzfTdKyxLfH9fQBL5nFvyAkoYt3w5ydX1ZY88ODYd8y_mO2tsX7KO-fHDu4fjuPfYGB7ZSTAJ9g7tcc88KLkjjYA369U2zabaNNVtSRTT-eiOLKUQzd-fzazItr1J_2qpWsNbpnfnR-Z8cnVMNM4YPfcXEekrj1XTRbNxYep_PU8zU5q4sRgn8QR75LPrsq7vxyRJage1fDl7GgLi7bkD4KvB0DrrDj-N4HO39so57F2cv82-QTSAdWJO-mQJ2hct02UnHozRRAfSBLqQI6KPH1NfFPHlqNCHNIEegh7khsuOBVaUEwCSWtcQAJoA8sIBoCVMFtXiMksYES3KWi1rW1nbli38bgaExCJSg9k4oJ5caq_xqfGle5TM8CmPptfzu-lpT_MeHobBZWe-uOh6dnAxPvWn9NA-SMNEklscE1-Odt5wyzBUNRz3LxuOjenRidroRHTv6lZ3rpdTf8GWuvpVH9gtMborq8AaiF3K0363lma6yw6ZH4WUcbEDCmoaalNFTdXU9zUDKSIeAzjyacIhl_qERDigcyK0IqylFLkNE36O70mWsmVLJdU9n60PH6OMR_kckKskGqWzZKbKZR6FMRdw6luBJndFir8oOoXlhMRzEm9Wy4u2Cc6MZ5dVHe7C6fS-EU5XCXiMWSJUUPghD4V25kf1DeHz5rjiOhZqfM_IV4pfi9s3lz3vyDNMXt8-meBZwHv_GpbLEi-mEadhesutvq9-rn6vfv35Bv8_Vj__fMuFLEnvOthk8ZZgJGGRZzm8zg5BbeRGfed55y-IVC9O"
|
||||
|
||||
# Telegraph Node helpers
|
||||
def tag(name, children=None, attrs=None):
|
||||
n = {"tag": name}
|
||||
if children is not None:
|
||||
n["children"] = children if isinstance(children, list) else [children]
|
||||
if attrs:
|
||||
n["attrs"] = attrs
|
||||
return n
|
||||
|
||||
def p(*children): return tag("p", list(children))
|
||||
def h3(text): return tag("h3", [text])
|
||||
def h4(text): return tag("h4", [text])
|
||||
def b(text): return tag("strong", [text])
|
||||
def i(text): return tag("em", [text])
|
||||
def ul(items): return tag("ul", [tag("li", [c]) if not isinstance(c, dict) else tag("li", [c]) for c in items])
|
||||
def li_nodes(children): return tag("li", children)
|
||||
def a(text, href): return tag("a", [text], {"href": href})
|
||||
def code(text): return tag("code", [text])
|
||||
def pre(text): return tag("pre", [text])
|
||||
def hr(): return tag("hr")
|
||||
def br(): return tag("br")
|
||||
|
||||
content = [
|
||||
p("Привет! Это инструкция в 3 шага. Внизу есть ", b("логин/пароль Apple ID"), " и ", b("ключ VPN"),
|
||||
" — их можно скопировать одним тапом."),
|
||||
|
||||
hr(),
|
||||
|
||||
h3("📱 Если у тебя iPhone или iPad"),
|
||||
|
||||
h4("Шаг 1. Выйти из своего Apple ID в App Store"),
|
||||
p("Это нужно только чтобы скачать приложение. Твои фото и контакты никуда не денутся."),
|
||||
tag("ol", [
|
||||
tag("li", ["Открой ", b("«Настройки»"), " (серая шестерёнка)."]),
|
||||
tag("li", ["Наверху нажми на ", b("своё имя"), "."]),
|
||||
tag("li", ["Пролистай вниз до ", b("«Медиа и покупки»"), " → нажми."]),
|
||||
tag("li", ["В окошке нажми ", b("«Выйти»"), "."]),
|
||||
]),
|
||||
p("⚠️ ", b("Важно!"), " Не нажимай «Выйти» в самом верху на своём имени — это не то. Выходишь ",
|
||||
b("только"), " в разделе «Медиа и покупки»."),
|
||||
|
||||
h4("Шаг 2. Войти в американский Apple ID"),
|
||||
tag("ol", [
|
||||
tag("li", ["Открой ", b("App Store"), " (синяя иконка с буквой А)."]),
|
||||
tag("li", ["В правом верхнем углу — кружок с силуэтом → нажми."]),
|
||||
tag("li", [b("«Войти»"), " или ", b("«Использовать другой Apple ID»"), "."]),
|
||||
tag("li", ["Введи логин и пароль — они ниже на этой странице."]),
|
||||
]),
|
||||
|
||||
h4("Шаг 3. Подтверждение входа"),
|
||||
p("Apple спросит, куда отправить код. Появится список номеров."),
|
||||
p("→ Выбери номер, ", b("который заканчивается на ...70"), " (это мой)."),
|
||||
p("→ Я получу SMS и сразу пришлю тебе цифры."),
|
||||
p("→ Введи их. App Store стал американским."),
|
||||
|
||||
h4("Шаг 4. Скачать AmneziaVPN"),
|
||||
p("В App Store в строке поиска набери ", b("AmneziaVPN"), " → «Загрузить»."),
|
||||
|
||||
h4("Шаг 5. Вернуть свой обычный Apple ID (рекомендую)"),
|
||||
p("Повтори Шаг 1, но в конце войди под своим прежним ID. Приложение останется, обновления будут от меня."),
|
||||
|
||||
hr(),
|
||||
|
||||
h3("💻 Если у тебя компьютер (Windows / Mac)"),
|
||||
p("Apple ID не нужен."),
|
||||
tag("ol", [
|
||||
tag("li", ["Скачай AmneziaVPN: ", a("github.com/amnezia-vpn/amnezia-client/releases/latest",
|
||||
"https://github.com/amnezia-vpn/amnezia-client/releases/latest"),
|
||||
". Windows — файл ", code("AmneziaVPN_x64_*.exe"), ", Mac — ", code("AmneziaVPN.dmg"), "."]),
|
||||
tag("li", ["Установи, открой."]),
|
||||
tag("li", ["Скопируй ключ VPN снизу этой страницы."]),
|
||||
tag("li", ["В программе: ", b("«+»"), " → ", b("«Вставить из буфера обмена»"), "."]),
|
||||
tag("li", ["Нажми большую кнопку включения."]),
|
||||
]),
|
||||
|
||||
h3("🤖 Если у тебя Android"),
|
||||
p("Apple ID не нужен."),
|
||||
tag("ol", [
|
||||
tag("li", ["Открой Google Play, найди ", b("AmneziaVPN"), ", установи. Если в Play нет — скачай APK: ",
|
||||
a("github.com/amnezia-vpn/amnezia-client/releases/latest",
|
||||
"https://github.com/amnezia-vpn/amnezia-client/releases/latest"), "."]),
|
||||
tag("li", ["Скопируй ключ VPN снизу этой страницы."]),
|
||||
tag("li", ["В AmneziaVPN: ", b("«+»"), " → ", b("«Вставить»"), "."]),
|
||||
tag("li", ["Нажми большую кнопку включения."]),
|
||||
]),
|
||||
|
||||
hr(),
|
||||
|
||||
h3("🔑 Подключить VPN (после установки)"),
|
||||
tag("ol", [
|
||||
tag("li", ["Скопируй ключ ", code("vpn://..."), " внизу этой страницы (тап по блоку с ключом)."]),
|
||||
tag("li", ["Открой ", b("AmneziaVPN"), "."]),
|
||||
tag("li", ["Нажми ", b("«+»"), " (или «Добавить сервер»)."]),
|
||||
tag("li", [b("«Вставить из буфера обмена»"), " (англ. «Import from clipboard»)."]),
|
||||
tag("li", ["Сервер добавится сам."]),
|
||||
]),
|
||||
|
||||
h3("▶️ Включить"),
|
||||
p("На главном экране AmneziaVPN — ", b("большая круглая кнопка"), ". Нажми."),
|
||||
p("Всплывёт окошко ", b("«Разрешить подключения VPN»"), " → ", b("«Разрешить»"),
|
||||
". Введи пароль от телефона если попросит."),
|
||||
p("Кнопка станет ", b("зелёной"), " — VPN работает."),
|
||||
|
||||
h3("✅ Проверить"),
|
||||
p("Открой в Safari ", a("2ip.ru", "https://2ip.ru"), ". Должна показаться ",
|
||||
b("другая страна"), " (не Россия). Значит всё."),
|
||||
|
||||
hr(),
|
||||
|
||||
h3("📲 Поделиться с другим своим устройством"),
|
||||
p("Не проси у меня — AmneziaVPN сам делится:"),
|
||||
tag("ol", [
|
||||
tag("li", ["В AmneziaVPN на рабочем устройстве нажми на сервер → иконка ", b("«i»"),
|
||||
" / ", b("«Подробнее»"), " → ", b("«Поделиться подключением»"), "."]),
|
||||
tag("li", ["Получишь ссылку → отправь себе в Telegram «Избранное» или на почту."]),
|
||||
tag("li", ["На втором устройстве установи AmneziaVPN → скопируй ссылку → «+» → «Вставить»."]),
|
||||
]),
|
||||
|
||||
hr(),
|
||||
|
||||
h3("🍏 Apple ID (США) — логин и пароль"),
|
||||
p(i("Тапни по строке чтобы скопировать.")),
|
||||
pre("hbuggle819@icloud.com"),
|
||||
pre("App5870w"),
|
||||
p("При входе — выбирай номер для кода ", b("на ...70"),
|
||||
". Напиши мне когда Apple попросит код."),
|
||||
|
||||
hr(),
|
||||
|
||||
h3("🔐 Ключ VPN"),
|
||||
p(i("Тапни по блоку ниже → «Копировать». Потом в AmneziaVPN «+» → «Вставить из буфера обмена».")),
|
||||
pre(VPN_KEY),
|
||||
|
||||
hr(),
|
||||
|
||||
h3("❓ Если что-то идёт не так"),
|
||||
tag("ul", [
|
||||
tag("li", [b("Не пришёл код от Apple"), " — напиши, перевышлю."]),
|
||||
tag("li", [b("Кнопка не зелёная, пишет «ошибка»"),
|
||||
" — закрой приложение (свайпни вверх), открой снова. Не помогло — перезагрузи телефон."]),
|
||||
tag("li", [b("Android, VPN отваливается"),
|
||||
" — «Настройки телефона → Приложения → AmneziaVPN → Батарея → Без ограничений»."]),
|
||||
tag("li", [b("iPhone «Профиль VPN не найден»"),
|
||||
" — «Настройки → Основные → VPN и управление устройством». Если профиль AmneziaVPN там есть, удали его, вернись в AmneziaVPN и снова нажми «+» → «Вставить»."]),
|
||||
]),
|
||||
p("Совсем не получается — скинь мне скриншот того что видишь, разберёмся вместе."),
|
||||
]
|
||||
|
||||
data = urllib.parse.urlencode({
|
||||
"access_token": ACCESS_TOKEN,
|
||||
"title": "Настройка VPN",
|
||||
"author_name": "Олег",
|
||||
"content": json.dumps(content, ensure_ascii=False),
|
||||
"return_content": "false",
|
||||
}).encode()
|
||||
|
||||
req = urllib.request.Request("https://api.telegra.ph/createPage", data=data)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
result = json.loads(resp.read().decode())
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
Reference in New Issue
Block a user