feat: caption-ninja recipe (web pages, private relay, Caption Local STT)
Self-hosted live captioning from steveseguin/captionninja and steveseguin/caption-local. Upstream publishes no images, so images/build.sh builds them from pinned commits.
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
|||||||
|
TYPE=caption-ninja
|
||||||
|
# First deploy downloads the Whisper model; give abra time to converge.
|
||||||
|
TIMEOUT=900
|
||||||
|
|
||||||
|
# caption.ninja pages (capture, editor, overlay) and the private relay at /relay
|
||||||
|
DOMAIN=caption-ninja.example.com
|
||||||
|
# Caption Local speech-to-text service (needs its own hostname: it uses absolute paths)
|
||||||
|
STT_DOMAIN=stt-caption-ninja.example.com
|
||||||
|
|
||||||
|
LETS_ENCRYPT_ENV=production
|
||||||
|
|
||||||
|
# Relay room tokens: 32-128 URL-safe chars, all four must differ.
|
||||||
|
# "source" = capture -> editor, "output" = editor -> overlay/OBS.
|
||||||
|
SECRET_SRC_READ_VERSION=v1 # length=48
|
||||||
|
SECRET_SRC_WRITE_VERSION=v1 # length=48
|
||||||
|
SECRET_OUT_READ_VERSION=v1 # length=48
|
||||||
|
SECRET_OUT_WRITE_VERSION=v1 # length=48
|
||||||
|
# Caption Local shared service token (>= 24 chars, no whitespace)
|
||||||
|
SECRET_STT_API_KEY_VERSION=v1 # length=48
|
||||||
|
|
||||||
|
## Speech-to-text tuning (CPU). See upstream docs/DEPLOYMENT-PROFILES.md.
|
||||||
|
## Whisper model: tiny, base, small (default), medium, large-v3. Bigger = more RAM + CPU.
|
||||||
|
#CAPTION_MODEL=small
|
||||||
|
## Decoder beam width 1-5; 1 is fastest
|
||||||
|
#CAPTION_BEAM_SIZE=5
|
||||||
|
## Concurrent model workers; threads are per worker. Keep workers*threads <= cores.
|
||||||
|
#CAPTION_WORKERS=1
|
||||||
|
#CAPTION_THREADS=4
|
||||||
|
#CAPTION_MAX_STREAMS=12
|
||||||
|
## Set to 1 once the model has downloaded into the volume, to stop Hugging Face lookups
|
||||||
|
#CAPTION_OFFLINE=0
|
||||||
|
## Metadata-only request logs (no audio/text/tokens)
|
||||||
|
#CAPTION_LOG_REQUESTS=0
|
||||||
|
|
||||||
|
## Extra exact origins (comma separated, e.g. https://steveseguin.github.io)
|
||||||
|
#RELAY_EXTRA_ORIGINS=
|
||||||
|
#STT_EXTRA_ORIGINS=
|
||||||
|
## Max relay sockets per client IP (everyone shares Traefik's IP)
|
||||||
|
#RELAY_PER_IP=512
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# CAPTION.Ninja + Caption Local
|
||||||
|
|
||||||
|
Self-hosted live captioning: [CAPTION.Ninja](https://github.com/steveseguin/captionninja)'s
|
||||||
|
capture/editor/overlay pages and private caption relay, plus
|
||||||
|
[Caption Local](https://github.com/steveseguin/caption-local) Whisper speech-to-text.
|
||||||
|
Nothing is sent to caption.ninja, Google or any cloud STT provider.
|
||||||
|
|
||||||
|
<!-- metadata -->
|
||||||
|
* **Category**: Apps
|
||||||
|
* **Status**: 1, alpha
|
||||||
|
* **Image**: [`git.autonomic.zone/recipe-maintainers/caption-ninja-*`](https://git.autonomic.zone/recipe-maintainers/-/packages), 1, built from upstream source (see [Images](#images))
|
||||||
|
* **Healthcheck**: Yes
|
||||||
|
* **Backups**: No (no user data; the model volume is a cache)
|
||||||
|
* **Email**: No
|
||||||
|
* **Tests**: 3
|
||||||
|
* **SSO**: No
|
||||||
|
<!-- endmetadata -->
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
| Service | Hostname | What |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `app` | `$DOMAIN` | nginx serving the caption.ninja pages |
|
||||||
|
| `relay` | `wss://$DOMAIN/relay` | private caption relay (Node, `ws`), rooms `source` and `output` |
|
||||||
|
| `stt` | `$STT_DOMAIN` | Caption Local (faster-whisper, CPU), also serves its own capture page |
|
||||||
|
|
||||||
|
Captions flow: **capture** (microphone → STT) publishes to room `source` → **editor**
|
||||||
|
reviews and forwards to room `output` → **overlay** (OBS browser source / audience).
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
1. Set up Docker Swarm, [`abra`][abra] and [`coop-cloud/traefik`][cc-traefik]
|
||||||
|
2. Make the images available on the server (see [Images](#images))
|
||||||
|
3. `abra app new caption-ninja --secrets`
|
||||||
|
4. `abra app config YOURAPPDOMAIN`: set `DOMAIN` and `STT_DOMAIN` (both need DNS).
|
||||||
|
The STT host needs RAM for the model: about 1.5 GB for `small`. Set `CAPTION_THREADS` to at most your core count.
|
||||||
|
5. `abra app deploy YOURAPPDOMAIN`. On first start `stt` downloads the Whisper
|
||||||
|
model into the `models` volume, so it can take several minutes to turn healthy.
|
||||||
|
|
||||||
|
## Using it
|
||||||
|
|
||||||
|
Read the tokens (keep them private; read tokens are for viewers, write tokens publish):
|
||||||
|
|
||||||
|
```
|
||||||
|
abra app run YOURAPPDOMAIN relay -- sh -c 'for s in src_read src_write out_read out_write; do echo "$s=$(cat /run/secrets/$s)"; done'
|
||||||
|
abra app run YOURAPPDOMAIN stt -- cat /run/secrets/stt_api_key
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Open `https://$STT_DOMAIN/`, enter the `stt_api_key` service token, then pick a microphone.
|
||||||
|
2. Under **Send captions to caption.ninja → Use a private relay**, enter:
|
||||||
|
relay `wss://$DOMAIN/relay`, caption website `https://$DOMAIN/`, source room
|
||||||
|
`source` + `src_write` token, editor output room `output`. Enable sharing.
|
||||||
|
3. Open the generated **editor** link and enter `src_read` and `out_write`.
|
||||||
|
4. In the editor, create the view-only OBS/overlay link with `out_read`.
|
||||||
|
|
||||||
|
Alternatively use `https://$DOMAIN/capture-local.html` with service address
|
||||||
|
`https://$STT_DOMAIN`.
|
||||||
|
|
||||||
|
**Note:** the other caption.ninja pages (`index.html`, `translate.html`, …) still
|
||||||
|
default to the public relay `wss://api.caption.ninja`. Only `capture-local.html`,
|
||||||
|
`editor.html` and `overlay.html` support the private relay.
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
Upstream publishes no Docker images, so `images/build.sh` builds them from pinned
|
||||||
|
upstream commits. It needs Docker, git and no registry credentials:
|
||||||
|
|
||||||
|
```
|
||||||
|
scp -r images SERVER:caption-ninja-build/
|
||||||
|
ssh SERVER 'bash caption-ninja-build/images/build.sh'
|
||||||
|
```
|
||||||
|
|
||||||
|
The images are tagged with their registry names, so a single-node swarm can deploy
|
||||||
|
straight from the local tags. To publish them to the registry from a machine
|
||||||
|
that has `docker login git.autonomic.zone`:
|
||||||
|
|
||||||
|
```
|
||||||
|
for img in $(ssh SERVER "docker images --format '{{.Repository}}:{{.Tag}}' | grep caption-ninja-"); do
|
||||||
|
ssh SERVER docker save "$img" | docker load && docker push "$img"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
To upgrade, bump the commits in `images/build.sh` and the tags in `compose.yml`.
|
||||||
|
|
||||||
|
## Security notes
|
||||||
|
|
||||||
|
* Caption Local only accepts `Host: localhost` (upstream expects an SSH tunnel), so
|
||||||
|
Traefik rewrites the Host header for `$STT_DOMAIN`; `stt` is still only reachable
|
||||||
|
through Traefik, and every API call needs the token.
|
||||||
|
* The STT token is a single shared token, not per-user auth. Anyone with it can use
|
||||||
|
your CPU for transcription. Rotate it with `abra app secret` + redeploy.
|
||||||
|
* Relay origins are `https://$DOMAIN` and `https://$STT_DOMAIN`; add more exact
|
||||||
|
origins with `RELAY_EXTRA_ORIGINS` (e.g. `https://steveseguin.github.io`).
|
||||||
|
* To revoke relay tokens, generate new secret versions and redeploy (restart
|
||||||
|
disconnects all clients and clears the 2-minute replay buffer).
|
||||||
|
|
||||||
|
[abra]: https://git.coopcloud.tech/coop-cloud/abra
|
||||||
|
[cc-traefik]: https://git.coopcloud.tech/coop-cloud/traefik
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export NGINX_CONF_VERSION=v1
|
||||||
|
export RELAY_ENTRYPOINT_VERSION=v1
|
||||||
|
export STT_ENTRYPOINT_VERSION=v2
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: git.autonomic.zone/recipe-maintainers/caption-ninja-web:2026.09.07-dfcee8f
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
configs:
|
||||||
|
- source: nginx_conf
|
||||||
|
target: /etc/nginx/conf.d/default.conf
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
deploy:
|
||||||
|
update_config:
|
||||||
|
failure_action: rollback
|
||||||
|
order: start-first
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}"
|
||||||
|
- "traefik.http.services.${STACK_NAME}.loadbalancer.server.port=80"
|
||||||
|
- "coop-cloud.${STACK_NAME}.version=0.1.0+v2026.09.07"
|
||||||
|
|
||||||
|
relay:
|
||||||
|
image: git.autonomic.zone/recipe-maintainers/caption-ninja-relay:0.2.0-dfcee8f
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
environment:
|
||||||
|
- DOMAIN
|
||||||
|
- STT_DOMAIN
|
||||||
|
- RELAY_EXTRA_ORIGINS
|
||||||
|
- RELAY_PER_IP
|
||||||
|
secrets:
|
||||||
|
- src_read
|
||||||
|
- src_write
|
||||||
|
- out_read
|
||||||
|
- out_write
|
||||||
|
configs:
|
||||||
|
- source: relay_entrypoint
|
||||||
|
target: /relay-entrypoint.sh
|
||||||
|
mode: 0555
|
||||||
|
entrypoint: /relay-entrypoint.sh
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8787/health').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
deploy:
|
||||||
|
update_config:
|
||||||
|
failure_action: rollback
|
||||||
|
order: start-first
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_relay.rule=Host(`${DOMAIN}`) && Path(`/relay`)"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_relay.entrypoints=web-secure"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_relay.tls.certresolver=${LETS_ENCRYPT_ENV}"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_relay.service=${STACK_NAME}_relay"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_relay.middlewares=${STACK_NAME}_relay_strip"
|
||||||
|
- "traefik.http.middlewares.${STACK_NAME}_relay_strip.stripprefix.prefixes=/relay"
|
||||||
|
- "traefik.http.services.${STACK_NAME}_relay.loadbalancer.server.port=8787"
|
||||||
|
|
||||||
|
stt:
|
||||||
|
image: git.autonomic.zone/recipe-maintainers/caption-ninja-stt:1.1.0-7d759fe
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
environment:
|
||||||
|
- DOMAIN
|
||||||
|
- STT_DOMAIN
|
||||||
|
- STT_EXTRA_ORIGINS
|
||||||
|
- CAPTION_MODEL=${CAPTION_MODEL:-small}
|
||||||
|
- CAPTION_BEAM_SIZE=${CAPTION_BEAM_SIZE:-5}
|
||||||
|
- CAPTION_WORKERS=${CAPTION_WORKERS:-1}
|
||||||
|
- CAPTION_THREADS=${CAPTION_THREADS:-4}
|
||||||
|
- CAPTION_MAX_STREAMS=${CAPTION_MAX_STREAMS:-12}
|
||||||
|
- CAPTION_COMPUTE_TYPE=${CAPTION_COMPUTE_TYPE:-auto}
|
||||||
|
- CAPTION_DEVICE=cpu
|
||||||
|
- CAPTION_OFFLINE=${CAPTION_OFFLINE:-0}
|
||||||
|
- CAPTION_LOG_REQUESTS=${CAPTION_LOG_REQUESTS:-0}
|
||||||
|
secrets:
|
||||||
|
- stt_api_key
|
||||||
|
configs:
|
||||||
|
- source: stt_entrypoint
|
||||||
|
target: /stt-entrypoint.sh
|
||||||
|
mode: 0555
|
||||||
|
entrypoint: /stt-entrypoint.sh
|
||||||
|
volumes:
|
||||||
|
- models:/models
|
||||||
|
healthcheck:
|
||||||
|
# The image's own healthcheck reads CAPTION_API_KEY from the environment,
|
||||||
|
# which only the entrypoint process has; feed it the secret here.
|
||||||
|
test: ["CMD", "sh", "-c", "CAPTION_API_KEY=$$(cat /run/secrets/stt_api_key) python healthcheck.py"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
# First start downloads the Whisper model into the volume.
|
||||||
|
start_period: 600s
|
||||||
|
stop_grace_period: 100s
|
||||||
|
deploy:
|
||||||
|
update_config:
|
||||||
|
failure_action: rollback
|
||||||
|
order: stop-first
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_stt.rule=Host(`${STT_DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_stt.entrypoints=web-secure"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_stt.tls.certresolver=${LETS_ENCRYPT_ENV}"
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_stt.service=${STACK_NAME}_stt"
|
||||||
|
# Caption Local only accepts Host: localhost (upstream expects an SSH tunnel)
|
||||||
|
- "traefik.http.routers.${STACK_NAME}_stt.middlewares=${STACK_NAME}_stt_host"
|
||||||
|
- "traefik.http.middlewares.${STACK_NAME}_stt_host.headers.customrequestheaders.Host=localhost"
|
||||||
|
- "traefik.http.services.${STACK_NAME}_stt.loadbalancer.server.port=8765"
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
src_read:
|
||||||
|
name: ${STACK_NAME}_src_read_${SECRET_SRC_READ_VERSION}
|
||||||
|
external: true
|
||||||
|
src_write:
|
||||||
|
name: ${STACK_NAME}_src_write_${SECRET_SRC_WRITE_VERSION}
|
||||||
|
external: true
|
||||||
|
out_read:
|
||||||
|
name: ${STACK_NAME}_out_read_${SECRET_OUT_READ_VERSION}
|
||||||
|
external: true
|
||||||
|
out_write:
|
||||||
|
name: ${STACK_NAME}_out_write_${SECRET_OUT_WRITE_VERSION}
|
||||||
|
external: true
|
||||||
|
stt_api_key:
|
||||||
|
name: ${STACK_NAME}_stt_api_key_${SECRET_STT_API_KEY_VERSION}
|
||||||
|
external: true
|
||||||
|
|
||||||
|
configs:
|
||||||
|
nginx_conf:
|
||||||
|
name: ${STACK_NAME}_nginx_conf_${NGINX_CONF_VERSION}
|
||||||
|
file: nginx.conf
|
||||||
|
relay_entrypoint:
|
||||||
|
name: ${STACK_NAME}_relay_entrypoint_${RELAY_ENTRYPOINT_VERSION}
|
||||||
|
file: relay-entrypoint.sh
|
||||||
|
stt_entrypoint:
|
||||||
|
name: ${STACK_NAME}_stt_entrypoint_${STT_ENTRYPOINT_VERSION}
|
||||||
|
file: stt-entrypoint.sh
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
models:
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the three caption-ninja images from pinned upstream commits.
|
||||||
|
#
|
||||||
|
# Upstream publishes no images, so we build them ourselves. Run this on a
|
||||||
|
# Docker host (it needs no registry credentials); the images are tagged with
|
||||||
|
# their final registry names so a single-node swarm can deploy them directly.
|
||||||
|
# See README.md "Images" for pushing them to the registry afterwards.
|
||||||
|
#
|
||||||
|
# usage: images/build.sh [workdir]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REGISTRY=git.autonomic.zone/recipe-maintainers
|
||||||
|
CAPTIONNINJA_COMMIT=dfcee8f551a2b175740ab9f73e3787e1458cce2d
|
||||||
|
CAPTION_LOCAL_COMMIT=7d759fe1eae100ccdb5de0eea82438c941b26582
|
||||||
|
RELAY_VERSION=0.2.0 # captionninja relay/package.json
|
||||||
|
CAPTION_LOCAL_VERSION=1.1.0 # caption-local VERSION
|
||||||
|
WEB_VERSION=2026.09.07 # captionninja has no releases: date of CAPTIONNINJA_COMMIT
|
||||||
|
|
||||||
|
here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
work="${1:-$(mktemp -d)}"
|
||||||
|
|
||||||
|
checkout() { # url commit dir
|
||||||
|
rm -rf "$3"
|
||||||
|
git init -q "$3"
|
||||||
|
git -C "$3" fetch -q --depth 1 "$1" "$2"
|
||||||
|
git -C "$3" checkout -q FETCH_HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
checkout https://github.com/steveseguin/captionninja "$CAPTIONNINJA_COMMIT" "$work/captionninja"
|
||||||
|
checkout https://github.com/steveseguin/caption-local "$CAPTION_LOCAL_COMMIT" "$work/caption-local"
|
||||||
|
|
||||||
|
cn="${CAPTIONNINJA_COMMIT:0:7}"
|
||||||
|
cl="${CAPTION_LOCAL_COMMIT:0:7}"
|
||||||
|
|
||||||
|
docker build -t "$REGISTRY/caption-ninja-web:$WEB_VERSION-$cn" \
|
||||||
|
-f "$here/web/Dockerfile" "$work/captionninja"
|
||||||
|
docker build -t "$REGISTRY/caption-ninja-relay:$RELAY_VERSION-$cn" \
|
||||||
|
"$work/captionninja/relay"
|
||||||
|
docker build -t "$REGISTRY/caption-ninja-stt:$CAPTION_LOCAL_VERSION-$cl" \
|
||||||
|
"$work/caption-local"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Built:"
|
||||||
|
echo " $REGISTRY/caption-ninja-web:$WEB_VERSION-$cn"
|
||||||
|
echo " $REGISTRY/caption-ninja-relay:$RELAY_VERSION-$cn"
|
||||||
|
echo " $REGISTRY/caption-ninja-stt:$CAPTION_LOCAL_VERSION-$cl"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Static caption.ninja pages. Build context: a captionninja checkout.
|
||||||
|
FROM alpine:3.22 AS src
|
||||||
|
COPY . /src
|
||||||
|
RUN cd /src && rm -rf .git .github relay scripts *.md test-security-warning.html
|
||||||
|
|
||||||
|
FROM nginx:1.31.6-alpine
|
||||||
|
COPY --from=src /src /usr/share/nginx/html
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# caption.ninja links omit the extension (e.g. /overlay?room=...).
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri.html $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Room tokens can arrive in query strings on some pages; keep them out of logs.
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# The relay reads one private JSON file holding allowed origins and per-room
|
||||||
|
# tokens. Build it from the Docker secrets at startup so tokens never live in
|
||||||
|
# the recipe or the env file. Writes to /tmp, readable only by the node user.
|
||||||
|
umask 077
|
||||||
|
node - > /tmp/rooms.json <<'JS'
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const secret = name => fs.readFileSync('/run/secrets/' + name, 'utf8').trim();
|
||||||
|
const origins = [process.env.DOMAIN, process.env.STT_DOMAIN]
|
||||||
|
.filter(Boolean).map(host => 'https://' + host)
|
||||||
|
.concat((process.env.RELAY_EXTRA_ORIGINS || '').split(',').map(s => s.trim()).filter(Boolean));
|
||||||
|
const config = {
|
||||||
|
origins: [...new Set(origins)],
|
||||||
|
rooms: {
|
||||||
|
source: {read: secret('src_read'), write: secret('src_write')},
|
||||||
|
output: {read: secret('out_read'), write: secret('out_write')},
|
||||||
|
},
|
||||||
|
// Every client arrives via Traefik, so they all share one source IP; the
|
||||||
|
// upstream default of 128 sockets per IP would cap the whole audience.
|
||||||
|
limits: {perIp: Number(process.env.RELAY_PER_IP || 512)},
|
||||||
|
};
|
||||||
|
process.stdout.write(JSON.stringify(config));
|
||||||
|
JS
|
||||||
|
|
||||||
|
export CAPTION_RELAY_CONFIG=/tmp/rooms.json
|
||||||
|
exec node server.cjs
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Caption Local takes its shared service token from CAPTION_API_KEY only.
|
||||||
|
if [ -f /run/secrets/stt_api_key ]; then
|
||||||
|
export CAPTION_API_KEY="$(cat /run/secrets/stt_api_key)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Let the caption.ninja pages on $DOMAIN call this service, plus any extras.
|
||||||
|
# Its own page on $STT_DOMAIN must be listed too: Traefik rewrites Host to
|
||||||
|
# localhost, so the app no longer sees that page as same-origin.
|
||||||
|
origins="https://${DOMAIN},https://${STT_DOMAIN}"
|
||||||
|
[ -n "$STT_EXTRA_ORIGINS" ] && origins="${origins},${STT_EXTRA_ORIGINS}"
|
||||||
|
export CAPTION_ALLOWED_ORIGINS="$origins"
|
||||||
|
|
||||||
|
exec python server.py --host 0.0.0.0 "$@"
|
||||||
Reference in New Issue
Block a user