[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 # Kraken — Portainer Access & Cloudflared SSH Tunnel
> Статус: ⏳ cloudflared pending — ждём CF Tunnel token от Alex > Статус: ⏳ cloudflared pending deploy
## Portainer ## Portainer
@@ -8,31 +8,20 @@
|---|---| |---|---|
| URL | `http://localhost:9000` | | URL | `http://localhost:9000` |
| Auth header | `X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=` | | 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 ### Portainer API — Quick Reference
```bash ```bash
BASE=http://localhost:9000 BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1 # endpoint ID EP=1
# Список 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 -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" \ curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/<ID>/logs?stdout=1&stderr=1&tail=50" "$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>" "$BASE/api/endpoints/$EP/docker/containers/<ID>"
``` ```
### Временный Alpine-контейнер (запустить команду на хосте) ### Временный Alpine-контейнер (выполнить команду на хосте)
Для разовых задач (записать файл, проверить путь и т.д.) — alpine с bind `/:/host`: Для разовых задач — alpine с bind `/:/host`:
```bash ```bash
BASE=http://localhost:9000 BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1 EP=1
# 1. Создать
ID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ 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-$$" \ "$BASE/api/endpoints/$EP/docker/containers/create?name=alpine-tmp-$$" \
-d '{ -d '{
"Image": "alpine", "Image": "alpine",
"Cmd": ["sh", "-c", "echo hello from host > /host/tmp/test.txt && echo DONE"], "Cmd": ["sh", "-c", "YOUR_COMMAND_HERE && echo DONE"],
"HostConfig": { "HostConfig": {"Binds": ["/:/host"], "AutoRemove": false}
"Binds": ["/:/host"],
"AutoRemove": false
}
}' | jq -r '.Id') }' | jq -r '.Id')
# 2. Запустить
curl -s -X POST -H "X-API-Key: $KEY" \ curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID/start" "$BASE/api/endpoints/$EP/docker/containers/$ID/start"
# 3. Подождать завершения (1-2 сек) и забрать логи
sleep 2 sleep 2
curl -s -H "X-API-Key: $KEY" \ curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID/logs?stdout=1&stderr=1" "$BASE/api/endpoints/$EP/docker/containers/$ID/logs?stdout=1&stderr=1"
# 4. Удалить
curl -s -X DELETE -H "X-API-Key: $KEY" \ curl -s -X DELETE -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID" "$BASE/api/endpoints/$EP/docker/containers/$ID"
``` ```
> ⚠️ `AutoRemove: false` — обязательно, иначе контейнер удалится до того как получишь логи > ⚠️ `AutoRemove: false` — обязательно, иначе контейнер удалится до получения логов
> ⚠️ Используй `alpine`, не `alpine/git` и подобные — у них есть ENTRYPOINT, `sh -c` не сработает > ⚠️ Использовать `alpine`, не `alpine/git` — у git-образа свой ENTRYPOINT
--- ---
## Cloudflared — деплой ## Cloudflared — деплой (выполняет Кракен)
### Шаг 1 — Получить CF Tunnel token (делает Alex вручную) CF Tunnel уже создан Alex'ом (`kraken` в Zero Trust), hostname настроен: `kraken.qentra.top → SSH → localhost:22`.
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 Token:** **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 ```bash
BASE=http://localhost:9000 BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1 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" \ CID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
"$BASE/api/endpoints/$EP/docker/containers/create?name=cloudflared" \ "$BASE/api/endpoints/$EP/docker/containers/create?name=cloudflared" \
-d "{ -d "{
\"Image\": \"cloudflare/cloudflared:latest\", \"Image\": \"cloudflare/cloudflared:latest\",
\"Cmd\": [\"tunnel\", \"--no-autoupdate\", \"run\"], \"Cmd\": [\"tunnel\", \"--no-autoupdate\", \"run\"],
\"Env\": [\"TUNNEL_TOKEN=${TOKEN}\"], \"Env\": [\"TUNNEL_TOKEN=$TOKEN\"],
\"HostConfig\": { \"HostConfig\": {
\"RestartPolicy\": {\"Name\": \"unless-stopped\"}, \"RestartPolicy\": {\"Name\": \"unless-stopped\"},
\"NetworkMode\": \"host\" \"NetworkMode\": \"host\"
} }
}" | jq -r '.Id') }" | jq -r '.Id')
# Запустить echo "Container ID: $CID"
curl -s -X POST -H "X-API-Key: $KEY" \ curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$CID/start" "$BASE/api/endpoints/$EP/docker/containers/$CID/start"
echo "Container ID: $CID" echo "Started."
``` ```
### Шаг 4 — Проверить логи ### Шаг 3 — Проверить логи
```bash ```bash
BASE=http://localhost:9000 BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1 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" \ 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 Registered tunnel connection connIndex=0 ... protocol=http2
INF Connection ... registered connIndex=0 INF Registered tunnel connection connIndex=1 ...
INF Connection ... registered 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 ```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`) ### Шаг 5 — Создать скилл по внешнему доступу
---
## Шаг 6 — Создать скилл по внешнему доступу (выполняет Кракен)
После того как cloudflared запущен и SSH-поддомен работает — создать скилл чтобы знать как пользоваться внешним доступом в будущем.
Скиллы живут в `/opt/data/skills/` (HDD-volume, переживают rebuild образа). Создать через terminal:
```bash ```bash
mkdir -p /opt/data/skills/devops/kraken-external-access 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 name: kraken-external-access
category: devops category: devops
description: Внешний доступ к Kraken через Cloudflare Tunnel — SSH с Eagle, Portainer API, управление cloudflared контейнером. description: Внешний доступ к Kraken через Cloudflare Tunnel — SSH с Eagle, Portainer API, управление cloudflared.
triggers: triggers:
- "доступ к кракену снаружи" - "доступ к кракену снаружи"
- "ssh к кракену с орла" - "ssh к кракену с орла"
@@ -259,19 +178,13 @@ triggers:
# Kraken — External Access via Cloudflare Tunnel # Kraken — External Access via Cloudflare Tunnel
## SSH с Eagle ## SSH с Eagle (уже настроено в ~/.ssh/config на Eagle)
```bash ```bash
# ~/.ssh/config на Eagle (уже настроен): ssh kraken.qentra.top
# Host ssh.kraken.qentra.top
# ProxyCommand cloudflared access ssh --hostname %h
# User kraken
# IdentityFile ~/.ssh/id_rsa
ssh ssh.kraken.qentra.top
``` ```
## Portainer API (локально на Кракене) ## Portainer API
```bash ```bash
BASE=http://localhost:9000 BASE=http://localhost:9000
@@ -286,15 +199,11 @@ curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json?all
## Управление cloudflared ## Управление cloudflared
```bash ```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" \ CID=$(curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json" \
| jq -r '.[] | select(.Names[] | contains("cloudflared")) | .Id') | jq -r '.[] | select(.Names[] | contains("cloudflared")) | .Id')
curl -s -H "X-API-Key: $KEY" \ 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" \ 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` — видит все локальные порты Кракена - `network_mode: host` cloudflared видит все локальные порты Кракена напрямую
- Если туннель отвалился — проверить логи: ищи `ERR` и `failed to`
- `restart: unless-stopped` — при падении поднимается автоматически - `restart: unless-stopped` — при падении поднимается автоматически
- Токен туннеля хранится в env `TUNNEL_TOKEN` контейнера — не менять без пересоздания контейнера - Токен в env `TUNNEL_TOKEN` — не менять без пересоздания контейнера
- CF Zero Trust tunnel: проверить статус на https://one.dash.cloudflare.com/ → Networks → Tunnels → kraken - Статус туннеля: https://one.dash.cloudflare.com/ → Networks → Tunnels → kraken
- Если туннель не поднимается — искать `ERR` в логах cloudflared
## Связанные заметки в vault ## Связанные заметки
- `family/how-to/kraken-portainer-access.md` — полная инструкция по деплою - `family/how-to/kraken-portainer-access.md` — полная инструкция по деплою
- `family/how-to/openmediavault-rpi5.md` — структура Docker на Кракене - `family/how-to/openmediavault-rpi5.md` — структура Docker на Кракене
EOF SKILLEOF
echo "Скилл создан: /opt/data/skills/devops/kraken-external-access/SKILL.md" echo "Скилл создан."
``` ```
> ⚠️ `/opt/data/` — это HDD-backed volume, скилл переживёт `docker stop/start` и даже rebuild образа > ⚠️ `/opt/data/` — HDD-volume, скилл переживёт rebuild образа
> ⚠️ Путь внутри контейнера — `/opt/data/skills/`, на хосте это `/srv/dev-disk-by-uuid-49e8f586.../docker/hermes/config/skills/`
### Шаг 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`)
--- ---