Skip to content

fix: domain change exits early in non-TTY, engine never restarts#64

Merged
SamNet-dev merged 1 commit into
SamNet-dev:mainfrom
SPerekrestova:fix/domain-change-non-interactive
Apr 12, 2026
Merged

fix: domain change exits early in non-TTY, engine never restarts#64
SamNet-dev merged 1 commit into
SamNet-dev:mainfrom
SPerekrestova:fix/domain-change-non-interactive

Conversation

@SPerekrestova
Copy link
Copy Markdown
Contributor

Problem

When mtproxymax domain <new> is run without a TTY — for example via a non-interactive SSH command, a CI script, or the Telegram bot — the script exits silently before restarting the engine. The result: settings.conf and config.toml are updated with the new domain, but the running telemt container continues using the old domain in memory. Every client connection using the new domain's SNI is rejected as unknown SNI and forwarded to the masking target instead.

Root cause

The script uses set -eo pipefail (line 10). Line 7083 contains a bare read:

echo -en "  ${BOLD}Rotate all secrets for new domain? [Y/n]:${NC} "
local _rot; read -r _rot    # ← returns exit code 1 on EOF (no TTY)

When stdin is not a TTY, read receives immediate EOF and returns exit code 1. With set -e active, the script exits here — before save_secrets and before restart_proxy_container are ever reached.

The output appears successful ([✓] Domain changed to ...) because the log line runs before read, so callers have no indication anything went wrong.

Observed symptoms

# Non-interactive invocation:
$ ssh host 'mtproxymax domain www.yandex.ru'
  [✓] Domain changed to www.yandex.ru
  [!] Existing proxy links still encode the old domain
  [Y/n]:
# Script exits here. No restart. No secret rotation.

# Engine logs immediately after:
TLS handshake rejected by unknown SNI policy peer=<CLIENT_IP> sni=www.yandex.ru unknown_sni=true unknown_sni_action=Mask
# ↑ Every client with the new SNI is rejected until manual restart

Note: even sending SIGHUP (via mtproxymax secret add/remove) does not fix this — telemt does not reload tls_domain from SIGHUP, only secrets. A full container restart is required.

Fix

Check [ -t 0 ] before attempting read. Default to y (rotate + restart) in non-interactive mode, print an informational message so the caller knows what happened, and add a || _rot="y" guard on the interactive path as well.

# Before
echo -en "  ${BOLD}Rotate all secrets for new domain? [Y/n]:${NC} "
local _rot; read -r _rot

# After
local _rot="y"
if [ -t 0 ]; then
    echo -en "  ${BOLD}Rotate all secrets for new domain? [Y/n]:${NC} "
    read -r _rot || _rot="y"
else
    log_info "Non-interactive mode: rotating secrets and restarting automatically"
fi

The default of y (rotate) is the safest choice: after a domain change, old secrets encode the old domain, so rotating them ensures distributed links work immediately after restart.

Related

  • Discussed in Q: Hot reload without dropping connections? #31 — the maintainer explicitly listed domain change as requiring restart, but the non-interactive exit bug means that restart was never executed in that context.
  • The domain clear path (same file, a few lines above) correctly calls restart_proxy_container without any read guard — this fix brings the domain set path into parity.

Testing

Interactive (unchanged behaviour):

mtproxymax domain www.newdomain.com
# Prompts as before, rotates on Y, skips on N, restarts in both cases

Non-interactive (fixed):

ssh host 'mtproxymax domain www.newdomain.com'
# [i] Non-interactive mode: rotating secrets and restarting automatically
# [✓] All secrets rotated — share new links with your users
# [✓] Proxy stopped
# [✓] Proxy is running on port 443

When running without a TTY (e.g. ssh host 'mtproxymax domain newdomain'),
the bare 'read -r _rot' returns exit code 1 (EOF). With set -eo pipefail
active, this causes the script to exit immediately — before secret rotation
and before restart_proxy_container is called. The engine continues running
with the old domain in memory, silently rejecting all client connections
from the new SNI as 'unknown SNI'.

Fix:
- Check [ -t 0 ] before attempting read
- Default _rot to 'y' (rotate + restart) in non-interactive mode
- Add read || _rot='y' guard for interactive mode too (rare edge case)
- Print informational message so non-interactive callers know what happened
@SamNet-dev SamNet-dev merged commit ff05cb8 into SamNet-dev:main Apr 12, 2026
@SamNet-dev
Copy link
Copy Markdown
Owner

Great catch and excellent write-up. Merged.

@SPerekrestova SPerekrestova deleted the fix/domain-change-non-interactive branch April 12, 2026 16:13
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.

2 participants