feat: build from captions.coop/captions-deploy, translation models, relay auto-fill
- images/build.sh builds all three images from one pinned captions-deploy commit (submodules pin the captions.coop forks; web/Dockerfile is the UI). - app: UI image configured by env (landing page, site-config.js), translation models downloaded into a volume on start (TRANSLATION_MODELS). - stt: RELAY_AUTOFILL=source|all|off feeds the capture page's /relay-config. - Drops the recipe's own nginx.conf and web Dockerfile.
This commit is contained in:
@@ -32,6 +32,15 @@ SECRET_STT_API_KEY_VERSION=v1 # length=48
|
||||
## Metadata-only request logs (no audio/text/tokens)
|
||||
#CAPTION_LOG_REQUESTS=0
|
||||
|
||||
## Translation models served to browsers (downloaded on start, SHA-256 verified):
|
||||
## "all" (~660 MB), a list like "enes,esen", or "none"
|
||||
#TRANSLATION_MODELS=all
|
||||
|
||||
## Capture-page relay auto-fill: source (default; capture-room tokens only),
|
||||
## all (also editor output tokens: service-token holders can then publish to the
|
||||
## audience room without review), or off
|
||||
#RELAY_AUTOFILL=source
|
||||
|
||||
## Extra exact origins (comma separated, e.g. https://steveseguin.github.io)
|
||||
#RELAY_EXTRA_ORIGINS=
|
||||
#STT_EXTRA_ORIGINS=
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 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.
|
||||
Self-hosted live captioning from [captions.coop/captions-deploy](https://git.coopcloud.tech/captions.coop/captions-deploy):
|
||||
the captions.coop forks of [CAPTION.Ninja](https://github.com/steveseguin/captionninja)
|
||||
(capture/editor/overlay pages, private caption relay, local Bergamot translation)
|
||||
and [Caption Local](https://github.com/steveseguin/caption-local) (Whisper speech-to-text).
|
||||
Nothing is sent to caption.ninja, Google or any cloud STT/translation 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))
|
||||
* **Image**: [`git.autonomic.zone/recipe-maintainers/caption-ninja-*`](https://git.autonomic.zone/recipe-maintainers/-/packages), 1, built from captions-deploy (see [Images](#images))
|
||||
* **Healthcheck**: Yes
|
||||
* **Backups**: No (no user data; the model volume is a cache)
|
||||
* **Email**: No
|
||||
@@ -20,7 +21,7 @@ Nothing is sent to caption.ninja, Google or any cloud STT provider.
|
||||
|
||||
| Service | Hostname | What |
|
||||
| --- | --- | --- |
|
||||
| `app` | `$DOMAIN` | nginx serving the caption.ninja pages |
|
||||
| `app` | `$DOMAIN` | nginx serving the caption.ninja pages, landing page at `/`, translation models at `/models` |
|
||||
| `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 |
|
||||
|
||||
@@ -36,6 +37,8 @@ reviews and forwards to room `output` → **overlay** (OBS browser source / audi
|
||||
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.
|
||||
`app` downloads the translation models (~660 MB, or the pairs in
|
||||
`TRANSLATION_MODELS`) in the background; translation works once that finishes.
|
||||
|
||||
## Using it
|
||||
|
||||
@@ -46,12 +49,18 @@ abra app run YOURAPPDOMAIN relay -- sh -c 'for s in src_read src_write out_read
|
||||
abra app run YOURAPPDOMAIN stt -- cat /run/secrets/stt_api_key
|
||||
```
|
||||
|
||||
`https://$DOMAIN/` is a landing page linking to everything below.
|
||||
|
||||
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:
|
||||
With `RELAY_AUTOFILL` (default `source`) the page fills in the private relay,
|
||||
source room and publishing token itself and turns sharing on. With
|
||||
`RELAY_AUTOFILL=off`, open **Send captions → Use a private relay** and 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`.
|
||||
`source` + `src_write` token, editor output room `output`, then enable sharing.
|
||||
2. Open the generated **editor** link and enter `src_read` and `out_write`
|
||||
(`RELAY_AUTOFILL=all` puts them in the link, at the cost of giving every
|
||||
service-token holder the audience room's publishing token).
|
||||
3. 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`.
|
||||
@@ -62,8 +71,10 @@ default to the public relay `wss://api.caption.ninja`. Only `capture-local.html`
|
||||
|
||||
## Images
|
||||
|
||||
Upstream publishes no Docker images, so `images/build.sh` builds them from pinned
|
||||
upstream commits. It needs Docker, git and no registry credentials:
|
||||
Nobody publishes Docker images, so `images/build.sh` builds all three from a pinned
|
||||
[captions-deploy](https://git.coopcloud.tech/captions.coop/captions-deploy) commit
|
||||
(its submodules pin the forks; `web/Dockerfile` is the UI image). It needs
|
||||
Docker, git and no registry credentials:
|
||||
|
||||
```
|
||||
scp -r images SERVER:caption-ninja-build/
|
||||
@@ -80,7 +91,7 @@ for img in $(ssh SERVER "docker images --format '{{.Repository}}:{{.Tag}}' | gre
|
||||
done
|
||||
```
|
||||
|
||||
To upgrade, bump the commits in `images/build.sh` and the tags in `compose.yml`.
|
||||
To upgrade, bump `DEPLOY_COMMIT`/`VERSION` in `images/build.sh`, rebuild, and update the tags in `compose.yml`.
|
||||
|
||||
## Security notes
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export NGINX_CONF_VERSION=v1
|
||||
export RELAY_ENTRYPOINT_VERSION=v1
|
||||
export STT_ENTRYPOINT_VERSION=v2
|
||||
export STT_ENTRYPOINT_VERSION=v3
|
||||
|
||||
+17
-10
@@ -3,12 +3,16 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: git.autonomic.zone/recipe-maintainers/caption-ninja-web:2026.09.07-dfcee8f
|
||||
image: git.autonomic.zone/recipe-maintainers/caption-ninja-web:2026.10.13-12fe0fa
|
||||
networks:
|
||||
- proxy
|
||||
configs:
|
||||
- source: nginx_conf
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
environment:
|
||||
- RELAY_URL=wss://${DOMAIN}/relay
|
||||
- CAPTURE_URL=https://${STT_DOMAIN}/
|
||||
- RELAY_UPSTREAM=${STACK_NAME}_relay:8787
|
||||
- TRANSLATION_MODELS=${TRANSLATION_MODELS:-all}
|
||||
volumes:
|
||||
- translation_models:/usr/share/nginx/models
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/"]
|
||||
interval: 30s
|
||||
@@ -25,10 +29,10 @@ services:
|
||||
- "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"
|
||||
- "coop-cloud.${STACK_NAME}.version=0.2.0+v2026.10.13"
|
||||
|
||||
relay:
|
||||
image: git.autonomic.zone/recipe-maintainers/caption-ninja-relay:0.2.0-dfcee8f
|
||||
image: git.autonomic.zone/recipe-maintainers/caption-ninja-relay:2026.10.13-12fe0fa
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
@@ -67,7 +71,7 @@ services:
|
||||
- "traefik.http.services.${STACK_NAME}_relay.loadbalancer.server.port=8787"
|
||||
|
||||
stt:
|
||||
image: git.autonomic.zone/recipe-maintainers/caption-ninja-stt:1.1.0-7d759fe
|
||||
image: git.autonomic.zone/recipe-maintainers/caption-ninja-stt:2026.10.13-12fe0fa
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
@@ -83,8 +87,13 @@ services:
|
||||
- CAPTION_DEVICE=cpu
|
||||
- CAPTION_OFFLINE=${CAPTION_OFFLINE:-0}
|
||||
- CAPTION_LOG_REQUESTS=${CAPTION_LOG_REQUESTS:-0}
|
||||
- RELAY_AUTOFILL=${RELAY_AUTOFILL:-source}
|
||||
secrets:
|
||||
- stt_api_key
|
||||
- src_read
|
||||
- src_write
|
||||
- out_read
|
||||
- out_write
|
||||
configs:
|
||||
- source: stt_entrypoint
|
||||
target: /stt-entrypoint.sh
|
||||
@@ -135,9 +144,6 @@ secrets:
|
||||
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
|
||||
@@ -151,3 +157,4 @@ networks:
|
||||
|
||||
volumes:
|
||||
models:
|
||||
translation_models:
|
||||
|
||||
+23
-32
@@ -1,46 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the three caption-ninja images from pinned upstream commits.
|
||||
# Build the three caption-ninja images from a pinned captions-deploy commit.
|
||||
#
|
||||
# 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.
|
||||
# captions-deploy (git.coopcloud.tech/captions.coop/captions-deploy) pins the
|
||||
# captions.coop forks of captionninja and caption-local as submodules and owns
|
||||
# the UI image (web/Dockerfile). Nobody publishes images, so we build them here.
|
||||
# Run this on a Docker host; it needs no registry credentials. 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
|
||||
DEPLOY_REPO=https://git.coopcloud.tech/captions.coop/captions-deploy.git
|
||||
DEPLOY_COMMIT=12fe0fade41ca65e1dda3704eb4fd9e204fde698
|
||||
VERSION=2026.10.13 # date of DEPLOY_COMMIT; images share one semver-like tag
|
||||
|
||||
here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
work="${1:-$(mktemp -d)}"
|
||||
work="${1:-$(mktemp -d)}/captions-deploy"
|
||||
tag="$VERSION-${DEPLOY_COMMIT:0:7}"
|
||||
|
||||
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
|
||||
}
|
||||
rm -rf "$work"
|
||||
git init -q "$work"
|
||||
git -C "$work" fetch -q --depth 1 "$DEPLOY_REPO" "$DEPLOY_COMMIT"
|
||||
git -C "$work" checkout -q FETCH_HEAD
|
||||
# Submodule URLs are SSH; fetch them anonymously over HTTPS instead.
|
||||
git -C "$work" -c url."https://git.coopcloud.tech/".insteadOf="ssh://git@git.coopcloud.tech:2222/" \
|
||||
submodule update -q --init --depth 1
|
||||
git -C "$work" submodule status
|
||||
|
||||
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"
|
||||
docker build -t "$REGISTRY/caption-ninja-web:$tag" -f "$work/web/Dockerfile" "$work"
|
||||
docker build -t "$REGISTRY/caption-ninja-relay:$tag" "$work/captionninja/relay"
|
||||
docker build -t "$REGISTRY/caption-ninja-stt:$tag" "$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"
|
||||
for image in web relay stt; do echo " $REGISTRY/caption-ninja-$image:$tag"; done
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# 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
@@ -1,15 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -13,4 +13,19 @@ origins="https://${DOMAIN},https://${STT_DOMAIN}"
|
||||
[ -n "$STT_EXTRA_ORIGINS" ] && origins="${origins},${STT_EXTRA_ORIGINS}"
|
||||
export CAPTION_ALLOWED_ORIGINS="$origins"
|
||||
|
||||
# Relay auto-fill: the capture page fetches these from /relay-config (behind the
|
||||
# service token). "source" shares only the capture room's tokens; "all" also
|
||||
# shares the editor output tokens, letting service-token holders publish to the
|
||||
# audience room without review; "off" disables it.
|
||||
secret() { cat "/run/secrets/$1"; }
|
||||
case "${RELAY_AUTOFILL:-source}" in
|
||||
source|all)
|
||||
export RELAY_ADDRESS="wss://${DOMAIN}/relay" RELAY_SITE="https://${DOMAIN}/"
|
||||
export RELAY_SOURCE_WRITE="$(secret src_write)" RELAY_SOURCE_READ="$(secret src_read)"
|
||||
if [ "$RELAY_AUTOFILL" = "all" ]; then
|
||||
export RELAY_OUTPUT_WRITE="$(secret out_write)" RELAY_OUTPUT_READ="$(secret out_read)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exec python server.py --host 0.0.0.0 "$@"
|
||||
|
||||
Reference in New Issue
Block a user