Skip to content

fix(panel): hang up container terminal shells on close, keep Reload on the running build - #127

Merged
agentHits merged 1 commit into
AgentHits-Devfrom
fix/terminal-shell-and-reload
Oct 29, 2026
Merged

agentHits merged 1 commit into
AgentHits-Devfrom
fix/terminal-shell-and-reload

Conversation

@agentHits

Copy link
Copy Markdown
Collaborator

Зачем

После закрытия терминала контейнера в контейнере продолжал работать его bash. Панель завершала только свою локальную команду docker exec, а Docker держит терминал exec открытым, поэтому bash не получал сигнал отбоя. Каждое открытие терминала оставляло ещё один простаивающий процесс; на живой панели нашёлся bash, проработавший больше суток.

Пункт Server → Reload на канале AgentHits не перезапускал панель, а запускал команду обновления и скачивал самую свежую сборку agenthits-dev. Так панель могла незаметно перейти на сборку, которую ещё не выпускали.

Кто запустил обновление или Reload, нигде не записывалось: журнал аудита хранится только с enterprise-лицензией.

Что изменилось

Где Было Стало
Терминал контейнера, локальный и через SSH при закрытии завершалась только локальная docker exec, bash в контейнере оставался у каждой сессии есть случайная метка DOKPLOY_TERMINAL_SESSION; при закрытии панель посылает SIGHUP первому процессу этой сессии в контейнере, через SSH до закрытия соединения
Server → Reload на канале AgentHits команда обновления: скачать agenthits-dev и обновить сервис перезапуск той сборки, на которой панель работает сейчас, закреплённой по digest (или по ID образа для локальной сборки)
Update и Reload автор нигде не записан в лог панели пишется пользователь, роль, сессия браузера или API-ключ, адрес и user agent

Отбой получает только первый процесс сессии. Поэтому shell передаёт сигнал своим задачам, а процесс, запущенный через nohup, продолжает работать, как при закрытии SSH-сессии. Busybox sh не передаёт отбой фоновым задачам; так же он ведёт себя и при обрыве SSH.

Reload закрепляет образ по digest, потому что swarm заново скачивает образ по тегу при каждом запуске задачи. Для образа, указанного по digest или ID, повторной загрузки нет, если образ уже есть на хосте (pullImage и DOCKER_SERVICE_PREFER_OFFLINE_IMAGE в исходниках moby). Официальный канал Dokploy не тронут.

Пример строки в логе:

[panel] Reload requested by owner@example.com (owner) through a browser session from 198.51.100.7, user agent: Mozilla/5.0 …

Проверки

Что Результат
Новые тесты терминала: метка в docker exec, отбой при закрытии, порядок закрытия SSH, отказ канала ✅; на старом коде все 4 падают
Скрипт отбоя: синтаксис sh -n, без изменений после экранирования для SSH ✅
Reload на фейковом docker: digest, ID образа, нет запущенной задачи ✅ без docker pull; без задачи панель не перезапускается
Запись в лог: Update, Reload, API-ключ, адрес из x-forwarded-for и x-real-ip ✅
Живая проверка на Docker 29 в alpine (sh) и debian (bash) тем же путём, что в панели (node-pty, docker exec -it) ✅ без отбоя shell и фоновые задачи остаются; с отбоем shell завершается, в bash вместе с фоновой задачей, nohup продолжает работать
Полный набор тестов apps/dokploy ✅ 4337 прошли; 5 падений в vector-validate.test.ts повторяются на базе без этих правок (локальный Docker на Mac не видит временную папку теста), к PR не относятся
tsc для apps/dokploy и packages/server, biome ✅ без ошибок

Что проверить вручную на dev

  1. Открыть терминал любого контейнера, выполнить sleep 1000 &, закрыть окно. Через пару секунд в docker top <контейнер> не должно остаться ни bash, ни sleep 1000.
  2. Нажать Server → Reload. Панель должна подняться на той же сборке, а docker service inspect dokploy --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}' — показать образ с @sha256:.
  3. В логе панели должна появиться строка [panel] Reload requested by ….

🤖 Generated with Claude Code

…n the running build

Closing a container terminal killed only the local `docker exec` client.
The daemon keeps the exec terminal open, so the shell inside the container
never got a hangup and kept running: one more idle shell per opened
terminal, on local and remote servers alike.

Every terminal session now carries a random DOKPLOY_TERMINAL_SESSION marker
in its environment. When the terminal closes, the panel sends SIGHUP to the
session's first process in the container; over SSH it does so before it
ends the connection. The shell passes the hangup on to its jobs and a
process started with nohup keeps running, as when an SSH session closes.

Server -> Reload on the AgentHits channel ran the update command, so it
pulled the newest `agenthits-dev` build. It now restarts the build the panel
runs, pinned by digest (or image id for a locally built image), because
swarm pulls a tag again whenever it starts a task.

Update and Reload now log who started them: user, role, browser session or
API key, address and user agent. Audit entries are stored only with an
enterprise licence, so the panel log was the only place left for this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agentHits
agentHits merged commit 68efa30 into AgentHits-Dev Oct 29, 2026
7 checks passed
agentHits added a commit that referenced this pull request Oct 29, 2026
…ему тегу

Команда Update записывала в сервис dokploy плавающий тег
ghcr.io/agenthits/dokploy:agenthits-dev без digest. Swarm заново
скачивает образ по тегу при каждом старте задачи, поэтому падение
контейнера, healthcheck, перезагрузка сервера или рестарт Docker могли
молча поднять более новую сборку.

Теперь после docker pull скрипт читает RepoDigests скачанного образа,
берёт digest того же репозитория и запускает docker service update
--no-resolve-image --image <репозиторий>:<тег>@sha256:…. Swarm не
скачивает образ по digest, если он уже есть на сервере, а если его нет,
скачивает ровно эту сборку. Без digest этого репозитория в сервис
пишется ID образа, как в Reload. Если после pull не нашёлся и сам образ,
скрипт завершается с ошибкой и сервис не трогает.

Официальный канал работает так же: в ту же команду попадают canary и
feature, а это плавающие теги. Для тегов версий закрепление ничего не
меняет.

Маркер после pull, DOKPLOY_PREVIOUS_IMAGE, RELEASE_TAG,
DOKPLOY_OFFICIAL_VERSION, DOKPLOY_FORK_VERSION, DOKPLOY_KEEP_OLD_IMAGES,
stop-first и rollback остались прежними. Проверка обновлений уже брала
digest из <образ>@sha256:…, и тест подтверждает, что после обновления
панель не предлагает ту же сборку снова.

Зависит от #127: ветка начата от fix/terminal-shell-and-reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentHits added a commit that referenced this pull request Oct 29, 2026
…ему тегу

Команда Update записывала в сервис dokploy плавающий тег
ghcr.io/agenthits/dokploy:agenthits-dev без digest. Swarm заново
скачивает образ по тегу при каждом старте задачи, поэтому падение
контейнера, healthcheck, перезагрузка сервера или рестарт Docker могли
молча поднять более новую сборку.

Теперь после docker pull скрипт читает RepoDigests скачанного образа,
берёт digest того же репозитория и запускает docker service update
--no-resolve-image --image <репозиторий>:<тег>@sha256:…. Swarm не
скачивает образ по digest, если он уже есть на сервере, а если его нет,
скачивает ровно эту сборку. Без digest этого репозитория в сервис
пишется ID образа, как в Reload. Если после pull не нашёлся и сам образ,
скрипт завершается с ошибкой и сервис не трогает.

Официальный канал работает так же: в ту же команду попадают canary и
feature, а это плавающие теги. Для тегов версий закрепление ничего не
меняет.

Маркер после pull, DOKPLOY_PREVIOUS_IMAGE, RELEASE_TAG,
DOKPLOY_OFFICIAL_VERSION, DOKPLOY_FORK_VERSION, DOKPLOY_KEEP_OLD_IMAGES,
stop-first и rollback остались прежними. Проверка обновлений уже брала
digest из <образ>@sha256:…, и тест подтверждает, что после обновления
панель не предлагает ту же сборку снова.

Продолжает #127: там то же закрепление сделано для Server → Reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agentHits
agentHits deleted the fix/terminal-shell-and-reload branch October 29, 2026 15:44
agentHits added a commit that referenced this pull request Oct 29, 2026
…ему тегу (#128)

Команда Update записывала в сервис dokploy плавающий тег
ghcr.io/agenthits/dokploy:agenthits-dev без digest. Swarm заново
скачивает образ по тегу при каждом старте задачи, поэтому падение
контейнера, healthcheck, перезагрузка сервера или рестарт Docker могли
молча поднять более новую сборку.

Теперь после docker pull скрипт читает RepoDigests скачанного образа,
берёт digest того же репозитория и запускает docker service update
--no-resolve-image --image <репозиторий>:<тег>@sha256:…. Swarm не
скачивает образ по digest, если он уже есть на сервере, а если его нет,
скачивает ровно эту сборку. Без digest этого репозитория в сервис
пишется ID образа, как в Reload. Если после pull не нашёлся и сам образ,
скрипт завершается с ошибкой и сервис не трогает.

Официальный канал работает так же: в ту же команду попадают canary и
feature, а это плавающие теги. Для тегов версий закрепление ничего не
меняет.

Маркер после pull, DOKPLOY_PREVIOUS_IMAGE, RELEASE_TAG,
DOKPLOY_OFFICIAL_VERSION, DOKPLOY_FORK_VERSION, DOKPLOY_KEEP_OLD_IMAGES,
stop-first и rollback остались прежними. Проверка обновлений уже брала
digest из <образ>@sha256:…, и тест подтверждает, что после обновления
панель не предлагает ту же сборку снова.

Продолжает #127: там то же закрепление сделано для Server → Reload.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant