# Kraken — Portainer Access & Cloudflared SSH Tunnel > Статус: ⏳ cloudflared pending deploy ## Portainer | Параметр | Значение | |---|---| | URL | `http://localhost:9000` | | Auth header | `X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=` | | 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 # Список контейнеров 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//logs?stdout=1&stderr=1&tail=50" # Удалить контейнер curl -s -X DELETE -H "X-API-Key: $KEY" \ "$BASE/api/endpoints/$EP/docker/containers/" ``` ### Временный Alpine-контейнер (выполнить команду на хосте) Для разовых задач — alpine с bind `/:/host`: ```bash BASE=http://localhost:9000 KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" EP=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", "YOUR_COMMAND_HERE && 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" ``` > ⚠️ `AutoRemove: false` — обязательно, иначе контейнер удалится до получения логов > ⚠️ Использовать `alpine`, не `alpine/git` — у git-образа свой ENTRYPOINT --- ## Cloudflared — деплой (выполняет Кракен) CF Tunnel уже создан Alex'ом (`kraken` в Zero Trust), hostname настроен: `kraken.qentra.top → SSH → localhost:22`. **CF Tunnel Token:** ``` eyJhIjoiZjE...TnpFNU5USTNNREl4WkRVeCJ9 ``` ### Шаг 1 — Убедиться что EP = 1 ```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="eyJhIjoiZjE0YTM3NWQ3NmU1ZTU4M2IzNjZhMDJhYjUyNDZiNDciLCJ0IjoiMmRkZDJiMjgtNjYwYy00NjQ0LTk1Y2ItY2Y1ZTQzNTBhMTA1IiwicyI6Ik5HVTRZalZtTURVdFptSm1OeTAwWVdGakxUaGlaamd0WmpFMFlUazVOekt3WTJWaCJ9" 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') echo "Container ID: $CID" curl -s -X POST -H "X-API-Key: $KEY" \ "$BASE/api/endpoints/$EP/docker/containers/$CID/start" echo "Started." ``` ### Шаг 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" \ | 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" \ | strings ``` Ожидаем: ``` INF Registered tunnel connection connIndex=0 ... protocol=http2 INF Registered tunnel connection connIndex=1 ... INF Registered tunnel connection connIndex=2 ... INF Registered tunnel connection connIndex=3 ... ``` Все 4 соединения = туннель активен. Если видишь `ERR` — проверить токен. ### Шаг 4 — Сохранить compose-файл (опционально, для удобства в будущем) ```bash 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" ``` ### Шаг 5 — Создать скилл по внешнему доступу ```bash mkdir -p /opt/data/skills/devops/kraken-external-access 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. triggers: - "доступ к кракену снаружи" - "ssh к кракену с орла" - "управление docker на кракене удалённо" - "cloudflared крякен" --- # Kraken — External Access via Cloudflare Tunnel ## SSH с Eagle (уже настроено в ~/.ssh/config на Eagle) ```bash ssh kraken.qentra.top ``` ## Portainer API ```bash BASE=http://localhost:9000 KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" EP=1 # Список контейнеров curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json?all=true" \ | jq '[.[] | {name: .Names[0], status: .Status}]' ``` ## Управление cloudflared ```bash # Статус + логи 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" | strings # Рестарт curl -s -X POST -H "X-API-Key: $KEY" \ "$BASE/api/endpoints/$EP/docker/containers/$CID/restart" ``` ## Питфолы - `network_mode: host` — cloudflared видит все локальные порты Кракена напрямую - `restart: unless-stopped` — при падении поднимается автоматически - Токен в env `TUNNEL_TOKEN` — не менять без пересоздания контейнера - Статус туннеля: https://one.dash.cloudflare.com/ → Networks → Tunnels → kraken - Если туннель не поднимается — искать `ERR` в логах cloudflared ## Связанные заметки - `family/how-to/kraken-portainer-access.md` — полная инструкция по деплою - `family/how-to/openmediavault-rpi5.md` — структура Docker на Кракене SKILLEOF echo "Скилл создан." ``` > ⚠️ `/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`) --- ## Связанные заметки - [[openmediavault-rpi5]] — настройка Kraken (Docker, Hermes, структура папок) - [[truenas-infrastructure]] — Taiga (аналогичный Portainer API подход) - [[obsidian-sync]] — синхронизация хранилища.