ММФБ Юрий: апгрейд Win10→Win11 25H2 + отчёт клиенту PDF

This commit is contained in:
dttb
2026-04-29 07:51:50 +03:00
parent 441491ea5d
commit 5956b21fcc
77 changed files with 8664 additions and 55 deletions

View 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).
Для одного клиента **достаточно одного** из них, кто работает на вашем сервере.

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

View 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»

View 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

View 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

View 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
View 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()

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