diff --git a/family/how-to/kraken-portainer-access.md b/family/how-to/kraken-portainer-access.md index 53d06a91..01ede0b8 100644 --- a/family/how-to/kraken-portainer-access.md +++ b/family/how-to/kraken-portainer-access.md @@ -1,6 +1,6 @@ # Kraken — Portainer Access & Cloudflared SSH Tunnel -> Статус: ⏳ cloudflared pending — ждём CF Tunnel token от Alex +> Статус: ⏳ cloudflared pending deploy ## Portainer @@ -8,31 +8,20 @@ |---|---| | URL | `http://localhost:9000` | | Auth header | `X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=` | -| Endpoint ID | уточнить через `GET /api/endpoints` (обычно `1` для local Docker) | +| Endpoint ID | уточнить: `curl -s -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" http://localhost:9000/api/endpoints | jq '[.[].Id]'` (обычно `1`) | ### 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]' +EP=1 # Список контейнеров -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 -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" @@ -41,215 +30,145 @@ curl -s -X DELETE -H "X-API-Key: $KEY" \ "$BASE/api/endpoints/$EP/docker/containers/" ``` -### Временный Alpine-контейнер (запустить команду на хосте) +### Временный Alpine-контейнер (выполнить команду на хосте) -Для разовых задач (записать файл, проверить путь и т.д.) — alpine с bind `/:/host`: +Для разовых задач — 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 - } + "Cmd": ["sh", "-c", "YOUR_COMMAND_HERE && 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` не сработает +> ⚠️ `AutoRemove: false` — обязательно, иначе контейнер удалится до получения логов +> ⚠️ Использовать `alpine`, не `alpine/git` — у git-образа свой ENTRYPOINT --- -## Cloudflared — деплой +## Cloudflared — деплой (выполняет Кракен) -### Шаг 1 — Получить CF Tunnel token (делает Alex вручную) - -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. Сохранить токен — вписать в эту заметку ниже +CF Tunnel уже создан Alex'ом (`kraken` в Zero Trust), hostname настроен: `kraken.qentra.top → SSH → localhost:22`. **CF Tunnel Token:** ``` -<ВСТАВИТЬ СЮДА ТОКЕН ПОСЛЕ СОЗДАНИЯ ТОННЕЛЯ> +eyJhIjoiZjE0YTM3NWQ3NmU1ZTU4M2IzNjZhMDJhYjUyNDZiNDciLCJ0IjoiMmRkZDJiMjgtNjYwYy00NjQ0LTk1Y2ItY2Y1ZTQzNTBhMTA1IiwicyI6Ik5HVTRZalZtTURVdFptSm1OeTAwWVdGakxUaGlaamd0WmpFMFlUazVOekt3WTJWaCJ9 ``` -### Шаг 2 — Создать docker-compose.yml через alpine +### Шаг 1 — Убедиться что EP = 1 -Используем alpine-контейнер с bind `/:/host` для записи файла: +```bash +curl -s -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \ + http://localhost:9000/api/endpoints | jq '[.[].Id]' +``` + +Если не `1` — запомнить реальный ID и использовать его вместо `1` в шагах ниже. + +### Шаг 2 — Создать и запустить cloudflared контейнер ```bash BASE=http://localhost:9000 KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" EP=1 -TOKEN="" +TOKEN="eyJhIjoiZjE0YTM3NWQ3NmU1ZTU4M2IzNjZhMDJhYjUyNDZiNDciLCJ0IjoiMmRkZDJiMjgtNjYwYy00NjQ0LTk1Y2ItY2Y1ZTQzNTBhMTA1IiwicyI6Ik5HVTRZalZtTURVdFptSm1OeTAwWVdGakxUaGlaamd0WmpFMFlUazVOekt3WTJWaCJ9" -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') - -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 — Поднять cloudflared контейнер через Portainer API - -```bash -BASE=http://localhost:9000 -KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" -EP=1 -TOKEN="" - -# Создать контейнер 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}\"], + \"Env\": [\"TUNNEL_TOKEN=$TOKEN\"], \"HostConfig\": { \"RestartPolicy\": {\"Name\": \"unless-stopped\"}, \"NetworkMode\": \"host\" } }" | jq -r '.Id') -# Запустить +echo "Container ID: $CID" + curl -s -X POST -H "X-API-Key: $KEY" \ "$BASE/api/endpoints/$EP/docker/containers/$CID/start" -echo "Container ID: $CID" +echo "Started." ``` -### Шаг 4 — Проверить логи +### Шаг 3 — Проверить логи ```bash 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') + +CID=$(curl -s -H "X-API-Key: $KEY" \ + "$BASE/api/endpoints/$EP/docker/containers/json" \ + | 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" + "$BASE/api/endpoints/$EP/docker/containers/$CID/logs?stdout=1&stderr=1&tail=30" \ + | strings ``` -Ожидаем в логах: +Ожидаем: ``` INF Registered tunnel connection connIndex=0 ... protocol=http2 -INF Connection ... registered connIndex=0 -INF Connection ... registered connIndex=1 -... +INF Registered tunnel connection connIndex=1 ... +INF Registered tunnel connection connIndex=2 ... +INF Registered tunnel connection connIndex=3 ... ``` -Если видишь все 4 соединения — туннель активен. +Все 4 соединения = туннель активен. Если видишь `ERR` — проверить токен. -### Шаг 5 — Обновить статус в этой заметке +### Шаг 4 — Сохранить compose-файл (опционально, для удобства в будущем) -Заменить `⏳ cloudflared pending` в заголовке на `✅ cloudflared запущен`. - ---- - -## Что делает Alex после запуска (вручную в CF Zero Trust) - -После того как cloudflared запущен и туннель появился как **Active** в Zero Trust: - -1. Открыть туннель `kraken` → **Edit** → вкладка **Public Hostname** → **Add a public hostname** -2. Добавить поддомен SSH: - - Subdomain: `ssh` - - Domain: `kraken.qentra.top` (создать заранее в DNS) - - Service type: **SSH** - - URL: `localhost:22` -3. Сохранить — DNS запись создаётся автоматически - -Повторить для других сервисов если нужно: -- `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 (с Eagle) - -После настройки поддомена добавить в `~/.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 +BASE=http://localhost:9000 +KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" +EP=1 +TOKEN="eyJhIjoiZjE0YTM3NWQ3NmU1ZTU4M2IzNjZhMDJhYjUyNDZiNDciLCJ0IjoiMmRkZDJiMjgtNjYwYy00NjQ0LTk1Y2ItY2Y1ZTQzNTBhMTA1IiwicyI6Ik5HVTRZalZtTURVdFptSm1OeTAwWVdGakxUaGlaamd0WmpFMFlUazVOekt3WTJWaCJ9" + +COMPOSE="services:\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=$TOKEN\n network_mode: host" + +ID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ + "$BASE/api/endpoints/$EP/docker/containers/create?name=alpine-cf-setup" \ + -d "{ + \"Image\": \"alpine\", + \"Cmd\": [\"sh\", \"-c\", \"mkdir -p /host/home/kraken/cloudflared && printf '$COMPOSE' > /host/home/kraken/cloudflared/docker-compose.yml && echo DONE\"], + \"HostConfig\": {\"Binds\": [\"/:/host\"], \"AutoRemove\": false} + }" | 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" ``` -> `cloudflared` на Eagle уже есть (используется для `zulip.qentra.top`) - ---- - -## Шаг 6 — Создать скилл по внешнему доступу (выполняет Кракен) - -После того как cloudflared запущен и SSH-поддомен работает — создать скилл чтобы знать как пользоваться внешним доступом в будущем. - -Скиллы живут в `/opt/data/skills/` (HDD-volume, переживают rebuild образа). Создать через terminal: +### Шаг 5 — Создать скилл по внешнему доступу ```bash mkdir -p /opt/data/skills/devops/kraken-external-access -cat > /opt/data/skills/devops/kraken-external-access/SKILL.md << 'EOF' +cat > /opt/data/skills/devops/kraken-external-access/SKILL.md << 'SKILLEOF' --- name: kraken-external-access category: devops -description: Внешний доступ к Kraken через Cloudflare Tunnel — SSH с Eagle, Portainer API, управление cloudflared контейнером. +description: Внешний доступ к Kraken через Cloudflare Tunnel — SSH с Eagle, Portainer API, управление cloudflared. triggers: - "доступ к кракену снаружи" - "ssh к кракену с орла" @@ -259,19 +178,13 @@ triggers: # Kraken — External Access via Cloudflare Tunnel -## SSH с Eagle +## SSH с Eagle (уже настроено в ~/.ssh/config на Eagle) ```bash -# ~/.ssh/config на Eagle (уже настроен): -# Host ssh.kraken.qentra.top -# ProxyCommand cloudflared access ssh --hostname %h -# User kraken -# IdentityFile ~/.ssh/id_rsa - -ssh ssh.kraken.qentra.top +ssh kraken.qentra.top ``` -## Portainer API (локально на Кракене) +## Portainer API ```bash BASE=http://localhost:9000 @@ -286,15 +199,11 @@ curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json?all ## Управление cloudflared ```bash -# Статус -curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json" \ - | jq '.[] | select(.Names[] | contains("cloudflared")) | {status: .Status}' - -# Логи (последние 30 строк) +# Статус + логи CID=$(curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json" \ | 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" + "$BASE/api/endpoints/$EP/docker/containers/$CID/logs?stdout=1&stderr=1&tail=30" | strings # Рестарт curl -s -X POST -H "X-API-Key: $KEY" \ @@ -303,22 +212,43 @@ curl -s -X POST -H "X-API-Key: $KEY" \ ## Питфолы -- cloudflared запускается с `network_mode: host` — видит все локальные порты Кракена -- Если туннель отвалился — проверить логи: ищи `ERR` и `failed to` +- `network_mode: host` — cloudflared видит все локальные порты Кракена напрямую - `restart: unless-stopped` — при падении поднимается автоматически -- Токен туннеля хранится в env `TUNNEL_TOKEN` контейнера — не менять без пересоздания контейнера -- CF Zero Trust tunnel: проверить статус на https://one.dash.cloudflare.com/ → Networks → Tunnels → kraken +- Токен в env `TUNNEL_TOKEN` — не менять без пересоздания контейнера +- Статус туннеля: https://one.dash.cloudflare.com/ → Networks → Tunnels → kraken +- Если туннель не поднимается — искать `ERR` в логах cloudflared -## Связанные заметки в vault +## Связанные заметки - `family/how-to/kraken-portainer-access.md` — полная инструкция по деплою - `family/how-to/openmediavault-rpi5.md` — структура Docker на Кракене -EOF -echo "Скилл создан: /opt/data/skills/devops/kraken-external-access/SKILL.md" +SKILLEOF +echo "Скилл создан." ``` -> ⚠️ `/opt/data/` — это HDD-backed volume, скилл переживёт `docker stop/start` и даже rebuild образа -> ⚠️ Путь внутри контейнера — `/opt/data/skills/`, на хосте это `/srv/dev-disk-by-uuid-49e8f586.../docker/hermes/config/skills/` +> ⚠️ `/opt/data/` — HDD-volume, скилл переживёт rebuild образа + +### Шаг 6 — Обновить статус в этой заметке + +Заменить первую строку `> Статус: ⏳ cloudflared pending deploy` на `> Статус: ✅ cloudflared запущен`. + +--- + +## SSH-доступ с Eagle (после деплоя) + +Добавить в `~/.ssh/config` на Eagle: + +``` +Host kraken.qentra.top + ProxyCommand cloudflared access ssh --hostname %h + User kraken + IdentityFile ~/.ssh/id_rsa + StrictHostKeyChecking no +``` + +Затем: `ssh kraken.qentra.top` + +> `cloudflared` на Eagle уже установлен (используется для `zulip.qentra.top`) ---