Skip to content

deploy: expose port on 0.0.0.0, add OMNIVOICE_IDLE_TIMEOUT=0 - #125

Closed
abhid wants to merge 1 commit into
debpalash:mainfrom
abhid:abhid/local-patches
Closed

deploy: expose port on 0.0.0.0, add OMNIVOICE_IDLE_TIMEOUT=0#125
abhid wants to merge 1 commit into
debpalash:mainfrom
abhid:abhid/local-patches

Conversation

@abhid

@abhid abhid commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Two quality-of-life fixes for self-hosted deployments:

1. Expose port on 0.0.0.0 instead of 127.0.0.1

Allows the container to be reached from the local network / reverse proxy without needing extra Docker networking config. Users who want loopback-only can still override the host binding in their own compose override.

2. Add OMNIVOICE_IDLE_TIMEOUT=0

Prevents the server from unloading models during periods of inactivity. Essential for always-on deployments where startup latency on the first request is unacceptable.

Both CPU and GPU service profiles updated.

Summary by CodeRabbit

  • Chores
    • Updated deployment configuration to enable network access from other machines on the local area network by default, rather than restricting access to the local machine only.
    • Modified idle timeout setting to prevent automatic service shutdown during periods of inactivity.

Review Change Stack

- Expose port 3900 on 0.0.0.0 (LAN accessible via Tailscale)
- Add OMNIVOICE_IDLE_TIMEOUT=0 (prevent model unload)
- Mount patched client.js and model_manager.py into GPU container
- Inline HF_TOKEN for GPU/CPU profiles
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request updates the Docker Compose configuration to enable network-wide accessibility for OmniVoice services and prevent idle timeouts. Both the CPU and GPU service variants now bind to all network interfaces (0.0.0.0:3900:3900) instead of loopback-only, and set OMNIVOICE_IDLE_TIMEOUT=0 in their environment configuration.

Changes

Docker Compose Network and Idle Timeout Configuration

Layer / File(s) Summary
Network accessibility and idle timeout settings for CPU and GPU services
deploy/docker-compose.yml
CPU and GPU service port mappings change from 127.0.0.1:3900:3900 to 0.0.0.0:3900:3900 to enable LAN access. Both services now include OMNIVOICE_IDLE_TIMEOUT=0 environment variable, with environment variable comments updated for clarity.

🎯 1 (Trivial) | ⏱️ ~3 minutes

🐰 A config change both small and neat,
Network gates now open wide,
No timeouts to greet,
Both services now unified,
LAN access made complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers the main objectives and rationale but lacks the required PR template sections (Type, Testing, Checklist, Screenshots) that should be included per the repository's template. Fill in the remaining sections from the template: select a Type checkbox, add Testing details, verify all Checklist items, and note if Screenshots apply.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: exposing the port on 0.0.0.0 and adding OMNIVOICE_IDLE_TIMEOUT=0, matching the actual modifications in the Docker Compose configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/docker-compose.yml`:
- Line 49: The environment sets OMNIVOICE_IDLE_TIMEOUT=0 which, given the
model_manager idle check (uses _last_used and compares time.time() - _last_used
> _IDLE_TIMEOUT_SECONDS), causes immediate unloads; change the idle-worker logic
in backend/services/model_manager.py (the loop that checks _last_used and
_IDLE_TIMEOUT_SECONDS) to explicitly treat non-positive timeouts as “never
unload” (e.g., short-circuit when _IDLE_TIMEOUT_SECONDS <= 0) so a zero value
prevents unloading rather than triggering it.
- Around line 77-78: The inline comment incorrectly claims LAN reachability is
off while the port mapping uses 0.0.0.0:3900:3900 which binds to all host
interfaces; either change the comment to correctly state that 0.0.0.0 exposes
the service on all host interfaces (LAN reachable) or change the port mapping to
127.0.0.1:3900:3900 to restrict access to localhost; update the text near the
existing "0.0.0.0:3900:3900" mapping to reflect whichever fix you choose.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f30ff85-e3d7-4f9f-8702-1cb90cf2bd65

📥 Commits

Reviewing files that changed from the base of the PR and between b34dcd9 and 8d7e8d0.

📒 Files selected for processing (1)
  • deploy/docker-compose.yml

Comment thread deploy/docker-compose.yml
# OMNIVOICE_BIND_HOST=0.0.0.0 here only opens the container's own
# interface. The backend default is 127.0.0.1 (see backend/main.py).
- OMNIVOICE_BIND_HOST=0.0.0.0
- OMNIVOICE_IDLE_TIMEOUT=0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

OMNIVOICE_IDLE_TIMEOUT=0 currently causes faster unloads, not disabled unloads.

Given current backend logic, 0 makes time.time() - _last_used > 0 true on the first idle-worker cycle, so models unload shortly after inactivity instead of staying warm.

Suggested fix
-      - OMNIVOICE_IDLE_TIMEOUT=0
+      # Keep default (900s) until backend supports "disable idle unload" semantics.
+      # - OMNIVOICE_IDLE_TIMEOUT=0

If you want 0 to mean “never unload”, backend logic should explicitly short-circuit, e.g. in backend/services/model_manager.py:

if _IDLE_TIMEOUT_SECONDS <= 0:
    continue

Also applies to: 80-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/docker-compose.yml` at line 49, The environment sets
OMNIVOICE_IDLE_TIMEOUT=0 which, given the model_manager idle check (uses
_last_used and compares time.time() - _last_used > _IDLE_TIMEOUT_SECONDS),
causes immediate unloads; change the idle-worker logic in
backend/services/model_manager.py (the loop that checks _last_used and
_IDLE_TIMEOUT_SECONDS) to explicitly treat non-positive timeouts as “never
unload” (e.g., short-circuit when _IDLE_TIMEOUT_SECONDS <= 0) so a zero value
prevents unloading rather than triggering it.

Comment thread deploy/docker-compose.yml
Comment on lines +77 to 78
# service above. The host-side `0.0.0.0:3900:3900` mapping keeps
# LAN reachability off by default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inline comment contradicts actual host exposure behavior.

Line 77 says LAN reachability is off by default, but 0.0.0.0:3900:3900 exposes it on all host interfaces by default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/docker-compose.yml` around lines 77 - 78, The inline comment
incorrectly claims LAN reachability is off while the port mapping uses
0.0.0.0:3900:3900 which binds to all host interfaces; either change the comment
to correctly state that 0.0.0.0 exposes the service on all host interfaces (LAN
reachable) or change the port mapping to 127.0.0.1:3900:3900 to restrict access
to localhost; update the text near the existing "0.0.0.0:3900:3900" mapping to
reflect whichever fix you choose.

@debpalash

Copy link
Copy Markdown
Owner

The OMNIVOICE_IDLE_TIMEOUT=0 addition is fine and useful. The blocker is the host-port change 127.0.0.1:3900:3900 -> 0.0.0.0:3900:3900: that makes the Docker deploy LAN-reachable by default, and the API has no built-in auth (SECURITY.md: "Do not expose OmniVoice to the internet without authentication"). The file's own comment notes the 127.0.0.1 mapping is what "keeps LAN reachability off by default." Could we keep the loopback default and document 0.0.0.0 as opt-in (a commented line, or a separate docker-compose.lan.yml override) — ideally putting a reverse proxy with auth in front? Splitting the two changes lets the idle-timeout part merge immediately.

@debpalash

Copy link
Copy Markdown
Owner

Thanks for this — the OMNIVOICE_IDLE_TIMEOUT=0 addition is good and we'll take it (sensible for always-on self-host deploys).

The 0.0.0.0 host mapping I can't take as the default, though. The backend has loopback-gated admin endpoints (settings, token, etc. via require_loopback) and the TTS/dub routes are unauthenticated by design — the local-first model assumes the service is reachable only from the same machine. The compose deliberately maps 127.0.0.1:3900:3900; the very comment your diff edits says it "keeps LAN reachability off by default." Flipping the default to 0.0.0.0 would silently expose an unauthenticated service to the LAN for everyone who runs the shipped compose.

Could you rework it so LAN exposure is opt-in?

  • Keep the default mapping 127.0.0.1:3900:3900.
  • Document the LAN path: either a commented-out # - "0.0.0.0:3900:3900" line users can uncomment, or a ${OMNIVOICE_PUBLISH_HOST:-127.0.0.1}:3900:3900 mapping driven by an env var (default loopback).
  • Keep OMNIVOICE_IDLE_TIMEOUT=0 as-is.

That lands both QoL wins without changing the secure-by-default posture. Happy to merge once it's opt-in.

@debpalash

Copy link
Copy Markdown
Owner

Thanks @abhid! We're declining the raw host-mapping flip to 0.0.0.0:3900 — it would silently expose every default docker compose up to the LAN (and the edited comment no longer matched the behavior). Instead we're shipping this properly as an explicit, user-driven network-sharing toggle: loopback by default, PIN-gated LAN exposure with a QR code, plus Tailscale for private remote access. Design: docs/superpowers/specs/2026-05-30-network-sharing-design.md. The OMNIVOICE_IDLE_TIMEOUT=0 idea for long-running deploys is a supported env var operators can already set. Closing as superseded by that feature.

@debpalash debpalash closed this May 30, 2026
@abhid

abhid commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

@debpalash Thanks for the heads up. That makes total sense.

debpalash added a commit that referenced this pull request May 30, 2026
…125) (#159)

* docs(spec): network sharing + Tailscale remote access design

Same-state LAN sharing via a second in-process uvicorn listener on a
dedicated share port (no restart, model/jobs preserved), PIN-gated for
non-loopback clients, with QR + all-LAN-addresses panel. Tailscale serve
for private remote access. Supersedes the raw 0.0.0.0 default-flip in #125.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(spec): control endpoints reuse existing require_loopback gate

Security review of #157 confirmed the /system router is already loopback-gated
via Depends(require_loopback) (non-spoofable request.client.host). The network
control endpoints inherit it and /system/set-env is auto-protected from the
LAN listener — no new guard needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(network): share-listener module — LAN enumeration + PIN + lifecycle

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(network): loopback-only control endpoints + /system/info sharing fields

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(cjk): scan git-tracked files only, not untracked vendored dirs

The no-hardcoded-CJK guard walked the filesystem, so local untracked
vendored experiments (research/voice-pro etc. with JP issue templates)
caused false local failures while CI (committed files) passed. Scan via
git ls-files so local-only and CI behavior match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(network): PIN middleware — gate non-loopback API access when sharing on

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(network): inject X-OmniVoice-Pin globally + capture ?pin= from QR URL

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(network): remote PIN gate on 401

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(network): add qrcode dep for share QR

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(network): footer Local/Network toggle with LAN addresses, QR, copy/open

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(tailscale): CLI status + serve enable/disable + endpoints

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(network): Settings → Sharing & Remote Access panel (LAN + Tailscale)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(network): sharing & remote access guide (LAN PIN/QR + Tailscale)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(network): enable() tears down and raises if the share listener never binds

Defensive guard (spec §7): if the second uvicorn server doesn't reach
'started' (e.g. the share port was taken in the race after the free-port
probe), cancel the task, reset state, and raise — so the API surfaces the
failure and the UI stays Local rather than reporting a dead 'Network' state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(network): use globalThis (not Node global) in client.test.ts for tsc

CI runs 'tsc --noEmit --checkJs false', which type-checks .ts files; Node's
'global' isn't typed there (TS2304). vitest (esbuild) tolerated it locally.
Use globalThis (standard, typed) + cast the mock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(network): apiFetch leaves opts untouched when no PIN set

The unconditional headers merge changed the request shape for callers with
no headers (e.g. FormData posts), breaking the legacy 'apiPost passes
FormData without Content-Type override' node test. Only spread opts +
inject X-OmniVoice-Pin when a PIN is actually present; otherwise pass opts
through unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <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.

2 participants