diff --git a/family/how-to/kraken-portainer-access.md b/family/how-to/kraken-portainer-access.md index 4a4b5596..d1cba4ab 100644 --- a/family/how-to/kraken-portainer-access.md +++ b/family/how-to/kraken-portainer-access.md @@ -1,168 +1,244 @@ # Kraken — Portainer Access & Cloudflared SSH Tunnel -> Статус: ⏳ cloudflared pending deploy (см. инструкции ниже) +> Статус: ⏳ cloudflared pending — ждём CF Tunnel token от Alex ## Portainer | Параметр | Значение | |---|---| -| URL (локальный) | http://localhost:9000 | +| URL | `http://localhost:9000` | | Auth header | `X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=` | -| Endpoint ID | 1 (local Docker) | +| Endpoint ID | уточнить через `GET /api/endpoints` (обычно `1` для local Docker) | -## Cloudflare Tunnel Token +### Portainer API — Quick Reference +```bash +BASE=http://localhost:9000 +KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" +EP=1 # endpoint ID + +# Список endpoints (узнать EP) +curl -s -H "X-API-Key: $KEY" $BASE/api/endpoints | jq '[.[].Id]' + +# Список контейнеров +curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json?all=true" | jq '[.[] | {name: .Names[0], status: .Status, image: .Image}]' + +# Создать контейнер +curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ + "$BASE/api/endpoints/$EP/docker/containers/create?name=" \ + -d '' + +# Запустить контейнер +curl -s -X POST -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers//start" + +# Логи +curl -s -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers//logs?stdout=1&stderr=1&tail=50" + +# Удалить контейнер +curl -s -X DELETE -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/" ``` -eyJhIjoiODFlOGYwYThmMzg5ZTllNzg3NGFmMTE3NGQxYzIxOTIiLCJ0IjoiNjQxMGI0YTYtZWUyMS00MTczLWJhMzItMTcwOGY1MjNlNDMyIiwicyI6Ik9XWmpNVEkxWVRFdE9EWXpZaTAwWlRjNExXSTBaVFl0TkdNeU16Z3daV0pqTW1RMyJ9 + +### Временный Alpine-контейнер (запустить команду на хосте) + +Для разовых задач (записать файл, проверить путь и т.д.) — alpine с bind `/:/host`: + +```bash +BASE=http://localhost:9000 +KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" +EP=1 + +# 1. Создать +ID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ + "$BASE/api/endpoints/$EP/docker/containers/create?name=alpine-tmp-$$" \ + -d '{ + "Image": "alpine", + "Cmd": ["sh", "-c", "echo hello from host > /host/tmp/test.txt && echo DONE"], + "HostConfig": { + "Binds": ["/:/host"], + "AutoRemove": false + } + }' | jq -r '.Id') + +# 2. Запустить +curl -s -X POST -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$ID/start" + +# 3. Подождать завершения (1-2 сек) и забрать логи +sleep 2 +curl -s -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$ID/logs?stdout=1&stderr=1" + +# 4. Удалить +curl -s -X DELETE -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$ID" ``` +> ⚠️ `AutoRemove: false` — обязательно, иначе контейнер удалится до того как получишь логи +> ⚠️ Используй `alpine`, не `alpine/git` и подобные — у них есть ENTRYPOINT, `sh -c` не сработает + --- -## Задача для Кракен Hermes +## Cloudflared — деплой -Поднять `cloudflare/cloudflared` в Docker через Portainer API. После запуска Alex добавит SSH-поддомен в Cloudflare Zero Trust (вручную, как делал для `zulip.qentra.top`). +### Шаг 1 — Получить CF Tunnel token (делает Alex вручную) -### Шаг 1 — Проверить Portainer и Docker endpoint +1. Открыть [Cloudflare Zero Trust → Networks → Tunnels](https://one.dash.cloudflare.com/) +2. **Create a tunnel** → Cloudflared → назвать `kraken` +3. На шаге "Install connector": скопировать команду вида: + ``` + cloudflared service install eyJ... + ``` + Нас интересует только токен — длинная строка после `service install` +4. Сохранить токен — вписать в эту заметку ниже -```bash -# Получить список endpoints (endpoint ID нужен для всех последующих запросов) -curl -s -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \ - http://localhost:9000/api/endpoints | jq '.[].Id' -# Ожидаем: 1 +**CF Tunnel Token:** +``` +<ВСТАВИТЬ СЮДА ТОКЕН ПОСЛЕ СОЗДАНИЯ ТОННЕЛЯ> ``` -### Шаг 2 — Создать контейнер cloudflared через Portainer API +### Шаг 2 — Создать docker-compose.yml через alpine + +Используем alpine-контейнер с bind `/:/host` для записи файла: ```bash -# Создать контейнер (не запускать) -curl -s -X POST \ - -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \ - -H "Content-Type: application/json" \ - "http://localhost:9000/api/endpoints/1/docker/containers/create?name=cloudflared" \ - -d '{ - "Image": "cloudflare/cloudflared:latest", - "Cmd": ["tunnel", "--no-autoupdate", "run"], - "Env": [ - "TUNNEL_TOKEN=eyJhIjoiODFlOGYwYThmMzg5ZTllNzg3NGFmMTE3NGQxYzIxOTIiLCJ0IjoiNjQxMGI0YTYtZWUyMS00MTczLWJhMzItMTcwOGY1MjNlNDMyIiwicyI6Ik9XWmpNVEkxWVRFdE9EWXpZaTAwWlRjNExXSTBaVFl0TkdNeU16Z3daV0pqTW1RMyJ9" - ], - "HostConfig": { - "RestartPolicy": {"Name": "unless-stopped"}, - "NetworkMode": "host" +BASE=http://localhost:9000 +KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" +EP=1 +TOKEN="" + +COMPOSE_B64=$(echo "services: + cloudflared: + image: cloudflare/cloudflared:latest + container_name: cloudflared + restart: unless-stopped + command: [\"tunnel\", \"--no-autoupdate\", \"run\"] + environment: + - TUNNEL_TOKEN=${TOKEN} + network_mode: host" | base64 | tr -d '\n') + +ID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ + "$BASE/api/endpoints/$EP/docker/containers/create?name=alpine-setup-$$" \ + -d "{ + \"Image\": \"alpine\", + \"Cmd\": [\"sh\", \"-c\", \"mkdir -p /host/home/kraken/cloudflared && echo ${COMPOSE_B64} | base64 -d > /host/home/kraken/cloudflared/docker-compose.yml && echo DONE\"], + \"HostConfig\": { + \"Binds\": [\"/:/host\"], + \"AutoRemove\": false } - }' | jq -r '.Id' -# Сохранить полученный container ID + }" | jq -r '.Id') + +curl -s -X POST -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$ID/start" +sleep 2 +curl -s -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$ID/logs?stdout=1&stderr=1" +curl -s -X DELETE -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$ID" ``` -### Шаг 3 — Запустить контейнер +### Шаг 3 — Поднять cloudflared контейнер через Portainer API ```bash -CONTAINER_ID="" +BASE=http://localhost:9000 +KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" +EP=1 +TOKEN="" -curl -s -X POST \ - -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \ - "http://localhost:9000/api/endpoints/1/docker/containers/${CONTAINER_ID}/start" -# Ожидаем: пустой ответ или 204 No Content = OK +# Создать контейнер +CID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ + "$BASE/api/endpoints/$EP/docker/containers/create?name=cloudflared" \ + -d "{ + \"Image\": \"cloudflare/cloudflared:latest\", + \"Cmd\": [\"tunnel\", \"--no-autoupdate\", \"run\"], + \"Env\": [\"TUNNEL_TOKEN=${TOKEN}\"], + \"HostConfig\": { + \"RestartPolicy\": {\"Name\": \"unless-stopped\"}, + \"NetworkMode\": \"host\" + } + }" | jq -r '.Id') + +# Запустить +curl -s -X POST -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$CID/start" + +echo "Container ID: $CID" ``` ### Шаг 4 — Проверить логи ```bash -curl -s \ - -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \ - "http://localhost:9000/api/endpoints/1/docker/containers/${CONTAINER_ID}/logs?stdout=true&stderr=true&tail=30" \ - | strings -# Ожидаем в логах: -# INF Registered tunnel connection connIndex=0 ... protocol=http2 -# INF Connection ... registered connIndex=0..3 +BASE=http://localhost:9000 +KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" +EP=1 +CID=$(curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json?all=true" | jq -r '.[] | select(.Names[] | contains("cloudflared")) | .Id') + +curl -s -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/$CID/logs?stdout=1&stderr=1&tail=30" ``` -### Шаг 5 — Создать docker-compose.yml для постоянного управления - -Через Alpine-контейнер с доступом к хосту: - -```bash -# Создать alpine контейнер, который запишет compose-файл на хост -curl -s -X POST \ - -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \ - -H "Content-Type: application/json" \ - "http://localhost:9000/api/endpoints/1/docker/containers/create?name=alpine-setup" \ - -d '{ - "Image": "alpine:latest", - "Cmd": ["sh", "-c", "mkdir -p /host/home/kraken/cloudflared && cat > /host/home/kraken/cloudflared/docker-compose.yml << '"'"'EOF'"'"'\nservices:\n cloudflared:\n image: cloudflare/cloudflared:latest\n container_name: cloudflared\n restart: unless-stopped\n command: [\"tunnel\", \"--no-autoupdate\", \"run\"]\n environment:\n - TUNNEL_TOKEN=eyJhIjoiODFlOGYwYThmMzg5ZTllNzg3NGFmMTE3NGQxYzIxOTIiLCJ0IjoiNjQxMGI0YTYtZWUyMS00MTczLWJhMzItMTcwOGY1MjNlNDMyIiwicyI6Ik9XWmpNVEkxWVRFdE9EWXpZaTAwWlRjNExXSTBaVFl0TkdNeU16Z3daV0pqTW1RMyJ9\n network_mode: host\nEOF\necho DONE"], - "HostConfig": { - "Binds": ["/:/host"], - "AutoRemove": true - } - }' | jq -r '.Id' - -# Запустить alpine контейнер -curl -s -X POST \ - -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \ - "http://localhost:9000/api/endpoints/1/docker/containers//start" +Ожидаем в логах: +``` +INF Registered tunnel connection connIndex=0 ... protocol=http2 +INF Connection ... registered connIndex=0 +INF Connection ... registered connIndex=1 +... ``` -### Шаг 6 — Обновить Obsidian +Если видишь все 4 соединения — туннель активен. -После успешного запуска cloudflared — обновить статус в этой заметке: `Статус: ⏳` → `Статус: ✅ cloudflared запущен`. +### Шаг 5 — Обновить статус в этой заметке + +Заменить `⏳ cloudflared pending` в заголовке на `✅ cloudflared запущен`. --- -## Что делает Alex вручную (после запуска) +## Что делает Alex после запуска (вручную в CF Zero Trust) -1. Открыть [Cloudflare Zero Trust → Networks → Tunnels](https://one.dash.cloudflare.com/) -2. Найти туннель (должен появиться как активный после запуска контейнера) -3. Нажать **Edit** → вкладка **Public Hostname** → **Add a public hostname**: +После того как cloudflared запущен и туннель появился как **Active** в Zero Trust: + +1. Открыть туннель `kraken` → **Edit** → вкладка **Public Hostname** → **Add a public hostname** +2. Добавить поддомен SSH: - Subdomain: `ssh` - - Domain: `kraken.qentra.top` (или другой по вкусу) + - Domain: `kraken.qentra.top` (создать заранее в DNS) - Service type: **SSH** - URL: `localhost:22` -4. Сохранить → DNS запись создаётся автоматически +3. Сохранить — DNS запись создаётся автоматически -> Повторить для других сервисов если нужно: -> - `omv.kraken.qentra.top` → HTTP → `localhost:80` -> - `portainer.kraken.qentra.top` → HTTP → `localhost:9000` +Повторить для других сервисов если нужно: +- `omv.kraken.qentra.top` → HTTP → `localhost:80` (OMV Web UI) +- `portainer.kraken.qentra.top` → HTTP → `localhost:9000` + +> ⚠️ Аналогично тому как настраивался `zulip.qentra.top` на Eagle: +> токен получается при создании тоннеля в Zero Trust → вставляется в `TUNNEL_TOKEN` --- -## SSH-доступ через Cloudflare Tunnel (с любой машины) +## SSH-доступ через Cloudflare Tunnel (с Eagle) -После настройки поддомена Alex создаёт скилл на Eagle для доступа к Кракену через тоннель (аналог Таига через alpine+portainer): +После настройки поддомена добавить в `~/.ssh/config` на Eagle: -```bash -# Один раз настроить ~/.ssh/config на Eagle: +``` Host ssh.kraken.qentra.top ProxyCommand cloudflared access ssh --hostname %h User kraken IdentityFile ~/.ssh/id_rsa StrictHostKeyChecking no +``` -# Подключиться: +Затем: +```bash ssh ssh.kraken.qentra.top ``` -> ⚠️ `cloudflared` должен быть установлен на клиентской машине (Eagle). -> На Eagle уже есть — он используется для `zulip.qentra.top`. - ---- - -## Проверка что туннель работает - -```bash -# На Кракене: посмотреть статус контейнера -docker ps | grep cloudflared - -# Логи cloudflared -docker logs cloudflared --tail 20 - -# С Eagle (после настройки DNS): тест SSH -ssh -o ProxyCommand="cloudflared access ssh --hostname ssh.kraken.qentra.top" \ - -i ~/.ssh/id_rsa kraken@ssh.kraken.qentra.top whoami -``` +> `cloudflared` на Eagle уже есть (используется для `zulip.qentra.top`) --- ## Связанные заметки - [[openmediavault-rpi5]] — настройка Kraken (Docker, Hermes, структура папок) -- [[truenas-infrastructure]] — Taiga (аналогичный подход через Portainer) +- [[truenas-infrastructure]] — Taiga (аналогичный Portainer API подход) - [[obsidian-sync]] — синхронизация vault