---
pr: 51
title: expose port 4096 on 0.0.0.0 for NPM proxy
---

# PR #51: expose port 4096 on 0.0.0.0 for NPM proxy

## Что сделано
- `docker-compose.yml:46` — порт 4096 перевязан с `127.0.0.1:4096:4096` (localhost-only) на `0.0.0.0:4096:4096` (открыт наружу, все интерфейсы). Единственное изменение в compose-файле, больше ничего не трогалось.
- `tests/test_docker_compose.py` — новый файл, 5 тестов:
  - `test_docker_compose_exists` — файл существует на repo root
  - `test_port_exposed_on_all_interfaces` — parsed bindings содержит `0.0.0.0:4096:4096` (ручной YAML-парсер `ports:` блоков, без pyyaml)
  - `test_port_exposed_on_all_interfaces_raw` — raw text содержит `0.0.0.0:4096:4096` (belt-and-suspenders)
  - `test_no_localhost_only_binding` — parsed bindings НЕ содержит `127.0.0.1:4096:4096` (anti-regression)
  - `test_no_localhost_only_binding_raw` — raw text НЕ содержит `127.0.0.1:4096:4096`
  - Парсинг ручной (pyyaml не в прямых зависимостях), по паттерну `test_agent_frontmatter.py` (split по indent, отслеживание `ports:` блоков).
- ADR-021 + этот handoff.

## Почему
opencode-config мигрирует на linux-1 (`193.3.168.35`). Web UI opencode (порт 4096) должен быть доступен с linux-2, где NPM (Nginx Proxy Manager, контейнер `jc21/nginx-proxy-manager`) проксирует домен `opencode.slaid098.dev` → `193.3.168.35:4096`. При localhost-only биндинге (`127.0.0.1:4096`) NPM с другого хоста не достучится — соединение отвергается на интерфейсе loopback. `0.0.0.0:4096` биндит на все интерфейсы, NPM достучится через публичный IP linux-1.

Топология (из memory `technical/opencode-config-migration-state-2026-07-24.md`): NPM живёт на linux-2, opencode мигрирует на linux-1. Раньше opencode на linux-2 был доступен через Cloudflare named tunnel напрямую (localhost-only был ОК, т.к. cloudflared на том же хосте). После миграции на linux-1 публичный доступ идёт через NPM на linux-2 → нужен `0.0.0.0`.

## Pending
— (нет)

## Watch out
- **Безопасность: порт 4096 теперь открыт на всех интерфейсах** — `0.0.0.0:4096` доступен с любого IP, который может маршрутизироваться до linux-1. opencode serve имеет basic auth (`OPENCODE_SERVER_USERNAME`/`OPENCODE_SERVER_PASSWORD` в `.env`), но bare порт открыт. Для production рекомендуется firewall (ufw/iptables) на linux-1, разрешающий 4096 только с linux-2 IP (`92.119.114.77`), либо полагаться на NPM SSL-termination + path filtering. Этот PR не настраивает firewall — только Docker-биндинг.
- **Тесты НЕ парсят YAML через pyyaml** — pyyaml есть в lock-файле (transitive dep), но НЕ в прямых зависимостях `pyproject.toml`. Для консистентности с `test_agent_frontmatter.py` (и чтобы не добавлять dep ради одного теста) используется ручной парсер `ports:` блоков.
- **Anti-regression тесты (raw + parsed)** — дублирующие raw-проверки добавлены намеренно (как в `test_agent_frontmatter.py`): parsed-check может пропустить edge case (например, если кавычки или inline-формат изменятся), raw-check ловит строку напрямую. Оба должны оставаться.
- ADR number = 021 (sequential, следующий после 020), НЕ PR number.