[2026-05-13] kraken-portainer-access: fix hostname, fill token, simplify steps

This commit is contained in:
Alexey Martemyanov
2026-05-13 14:27:32 +06:00
parent cc630a8767
commit 44817222f2
+102 -172
View File
@@ -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=<NAME>" \
-d '<JSON>'
# Запустить контейнер
curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/<ID>/start"
# Логи
# Логи контейнера
curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/<ID>/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/<ID>"
```
### Временный 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="<CF_TUNNEL_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="<CF_TUNNEL_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`)
---