diff --git a/PROXY_SETUP.md b/PROXY_SETUP.md index bc95ea85..4bb062a2 100644 --- a/PROXY_SETUP.md +++ b/PROXY_SETUP.md @@ -185,7 +185,7 @@ VITE_LANGUAGE_TOOL_URL=/api/languagetool VITE_TRANSLATE_URL=/api/translate ``` -**Production:** `docker compose -f docker-compose.prod.yml --profile editor-tools up -d languagetool libretranslate` publishes **127.0.0.1:8010** and **127.0.0.1:5000** (loopback-only). Proxy those paths from Apache/nginx to the SPA origin, and bake the client with `LANGUAGE_TOOL_URL=/api/languagetool` and `TRANSLATE_URL=/api/translate` when running `./scripts/build-and-push-prod.sh`. +**Production:** `docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d` starts the full stack (including LanguageTool on **127.0.0.1:8010** and LibreTranslate on **127.0.0.1:5000**). Run `bash scripts/ensure-libretranslate-dirs.sh` once on the server for LibreTranslate volume permissions. Proxy `/api/languagetool` and `/api/translate` from Apache/nginx to those ports, and bake the client with `LANGUAGE_TOOL_URL=/api/languagetool` and `TRANSLATE_URL=/api/translate` when running `./scripts/build-and-push-prod.sh`. **Notes:** LanguageTool’s JVM image often needs **~1–2 GiB** RAM. LibreTranslate **does not listen on port 5000 until models are ready**; without **`LT_LOAD_ONLY`** it may pull **many gigabytes** first, so the Vite proxy can show **`ECONNRESET` on `/translate`** while booting. Compose defaults **`LT_LOAD_ONLY`** to **ten** widely used codes (**en, de, es, fr, it, pt, ru, zh, ja, ar** — see `libretranslate` in `docker-compose.dev.yml`). Override with **`LT_LOAD_ONLY`** to add or remove codes; first start downloads packs for every listed code. **`LT_UPDATE_MODELS`** defaults to **`true`** so if you **expand** `LT_LOAD_ONLY` later, a **recreated** container still **installs missing** Argos packages into the bind-mounted `.local-libretranslate` tree (otherwise an older en/de-only cache sticks). Set **`LT_UPDATE_MODELS=false`** after everything is installed if you want faster routine restarts. Models are stored under **`.local-libretranslate/share`** and **`.local-libretranslate/cache`** (gitignored) with **bind mounts** so they survive **`docker compose down`**, image updates, and container recreate. **`scripts/ensure-libretranslate-dirs.sh`** (run automatically by **`npm run dev:all`**, **`npm run stack:remote`**, **`npm run docker:editor-tools`**, etc.) creates those dirs and **`chown`s them to UID 1032** via a short **Alpine** container so the LibreTranslate user can write. If you start **`libretranslate` by hand**, run **`npm run docker:prep-libretranslate`** once first. First download can still take **several minutes**; use **`docker logs -f jumble-libretranslate`** until **`curl http://127.0.0.1:5000/languages`** returns JSON. If logs show **`Cannot update models`** / **`Unavailable language codes: …`**, one bad token in **`LT_LOAD_ONLY` aborts the whole install** (you stay on whatever was already on disk, often en/de only). **Norwegian** must be **`nb`** (Bokmål), not ISO **`no`**. After you shrink **`LT_LOAD_ONLY`**, run **`npm run docker:prune-libretranslate-packages`** to remove leftover Argos package dirs under **`.local-libretranslate/share/argos-translate/packages`** (and unused **MiniSBD** `.onnx` files); the script briefly stops **`jumble-libretranslate`** or **`imwald-libretranslate`**. Override codes with **`LT_LOAD_ONLY=…`** on the same command if they differ from the compose default. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 643e78ef..f023279a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,6 +1,7 @@ -# Minimal compose for running the published image (e.g. on remote server). -# Full stack (jumble + NIP-66 + og-proxy + Piper + LanguageTool + LibreTranslate): npm run stack:remote -# Usage (app only): docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d +# Production stack: jumble + NIP-66 monitor + og-proxy + Wyoming Piper + Piper HTTP proxy + LanguageTool + LibreTranslate. +# Remote: docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d +# First-time LibreTranslate bind mounts: bash scripts/ensure-libretranslate-dirs.sh (see scripts/README-deploy.md). +# Images built/pushed by ./scripts/build-and-push-prod.sh (app, monitor, piper-tts-proxy); others pull from Hub. # # Apache (unchanged on your host) should keep (order: specific paths before catch-all /): # ProxyPass /sites/ http://127.0.0.1:8090/sites/ @@ -14,10 +15,8 @@ # - Cron service `jumble-nip66-monitor` (Imwald NIP-66 monitor image) uses NIP66_MONITOR_NSEC to publish 30166/10166; nsec never goes to the client. # - Set NIP66_MONITOR_NPUB (npub1... derived from the same key) so the relay info page shows the monitor's avatar and handle in the NIP-66 liveliness section. # -# Optional editor tools (LanguageTool + LibreTranslate): profile `editor-tools` -# docker compose -f docker-compose.prod.yml --profile editor-tools up -d languagetool libretranslate -# Then Apache (or nginx) must proxy same-origin paths baked into the SPA, e.g. /api/languagetool → http://127.0.0.1:8010 -# and /api/translate → http://127.0.0.1:5000. Build the app image with: +# Apache (or nginx) must proxy same-origin paths baked into the SPA, e.g. /api/languagetool → http://127.0.0.1:8010 +# and /api/translate → http://127.0.0.1:5000. Build the app with: # LANGUAGE_TOOL_URL=/api/languagetool TRANSLATE_URL=/api/translate ./scripts/build-and-push-prod.sh services: @@ -68,7 +67,6 @@ services: languagetool: image: silviof/docker-languagetool:latest - profiles: ['editor-tools'] container_name: imwald-languagetool ports: - '127.0.0.1:8010:8010' @@ -80,7 +78,6 @@ services: libretranslate: image: libretranslate/libretranslate:latest - profiles: ['editor-tools'] container_name: imwald-libretranslate ports: - '127.0.0.1:5000:5000' @@ -97,12 +94,11 @@ services: limits: memory: 2048M - # --- profile `stack`: OG proxy + Piper (Apache → 8090 / 9876). One command: npm run stack:remote --- - # First-party images: silberengel/* (override with OG_PROXY_IMAGE / WYOMING_PIPER_IMAGE / PIPER_HTTP_PROXY_IMAGE). + # OG proxy + Piper (Apache → 8090 / 9876). Piper HTTP image: silberengel/imwald-piper-tts-proxy (see build-and-push-prod.sh). og-proxy: image: ${OG_PROXY_IMAGE:-silberengel/wikistr:latest-og-proxy} - profiles: ['stack'] - container_name: og-proxy + # Distinct name so `docker compose` does not assume an unrelated `og-proxy` container belongs to this project. + container_name: imwald-og-proxy dns: - 1.1.1.1 - 8.8.8.8 @@ -119,8 +115,8 @@ services: piper-wyoming: image: ${WYOMING_PIPER_IMAGE:-silberengel/wyoming-piper:latest} - profiles: ['stack'] - container_name: piper-tts + # Distinct from ad-hoc stacks named `piper-tts`; Piper HTTP proxy reaches this service as `piper-wyoming`. + container_name: imwald-piper-wyoming command: - --voice - en_US-lessac-medium @@ -135,11 +131,7 @@ services: restart: unless-stopped piper-tts-proxy: - profiles: ['stack'] container_name: imwald-piper-tts-proxy - build: - context: . - dockerfile: services/piper-tts-proxy/Dockerfile image: ${PIPER_HTTP_PROXY_IMAGE:-silberengel/imwald-piper-tts-proxy:latest} environment: NODE_ENV: production diff --git a/package-lock.json b/package-lock.json index 02ef8719..98ef0b80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "imwald", - "version": "23.0.0", + "version": "23.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "imwald", - "version": "23.0.0", + "version": "23.0.1", "license": "MIT", "dependencies": { "@asciidoctor/core": "^3.0.4", diff --git a/package.json b/package.json index 935c5fe5..8f3957bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "imwald", - "version": "23.0.0", + "version": "23.0.1", "description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery", "private": true, "type": "module", diff --git a/scripts/README-deploy.md b/scripts/README-deploy.md index e213d05f..12903756 100644 --- a/scripts/README-deploy.md +++ b/scripts/README-deploy.md @@ -11,12 +11,13 @@ docker login # once, if needed ./scripts/build-and-push-prod.sh ``` -This builds both images and pushes two tags each (`latest` and the version from `package.json`, e.g. `17.0.0`): +This builds and pushes **three** images, each with `latest` and the version from `package.json` (e.g. `17.0.0`): - **Main app (Imwald):** `silberengel/imwald-jumble` -- **NIP-66 monitor:** `silberengel/imwald-jumble-nip66-monitor` +- **NIP-66 monitor:** `silberengel/imwald-jumble-nip66-monitor` +- **Piper HTTP proxy:** `silberengel/imwald-piper-tts-proxy` (Wyoming Piper still pulls `silberengel/wyoming-piper` and `silberengel/wikistr` on the server.) - Registry paths keep the historical `imwald-jumble` name; retagging to e.g. `silberengel/imwald` is optional and requires updating `docker-compose.prod.yml` and pull scripts. +Registry paths keep the historical `imwald-jumble` name; retagging is optional and requires updating `docker-compose.prod.yml`. ## Remote server: one-time setup @@ -36,17 +37,38 @@ This builds both images and pushes two tags each (`latest` and the version from NIP66_MONITOR_NPUB=npub1... ``` +4. **Once per machine** (LibreTranslate bind mounts need UID 1032 on the host dirs): + ```bash + bash scripts/ensure-libretranslate-dirs.sh + ``` + ## Remote server: pull and run +Use a **current** `docker-compose.prod.yml` from this repo (`git pull` in the clone). If `docker compose pull` only lists two images, the file is outdated and **LanguageTool / LibreTranslate will never start**. + After you’ve pushed from local: ```bash cd jumble +git pull +bash scripts/ensure-libretranslate-dirs.sh # once per host docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d ``` -The app is on **port 8089**. Both services use `:latest`; to pin a version, set the image in `docker-compose.prod.yml` to e.g. `silberengel/imwald-jumble:17.0.0` and `silberengel/imwald-jumble-nip66-monitor:17.0.0`. +That starts the **full** stack in `docker-compose.prod.yml` (app, NIP-66 monitor, OG proxy, Piper, LanguageTool, LibreTranslate). The SPA is on **port 8089**; see `docker-compose.prod.yml` header for Apache paths. + +**Grammar + translate in the browser** also require the SPA to be built with API paths baked in, for example: + +`LANGUAGE_TOOL_URL=/api/languagetool TRANSLATE_URL=/api/translate ./scripts/build-and-push-prod.sh` + +and Apache (or nginx) must proxy `/api/languagetool` → `127.0.0.1:8010` and `/api/translate` → `127.0.0.1:5000`. + +**Shared host:** if you already run another `og-proxy` on `127.0.0.0.1:8090` or another Wyoming Piper on the same ports, `docker compose up -d` can fail with a port or name conflict. Either stop the duplicates or start only the pieces you need, e.g. `docker compose up -d jumble jumble-nip66-monitor languagetool libretranslate` (and point `PIPER_TTS_HOST` / Apache at your existing Piper stack if applicable). + +Equivalent one-liner: `npm run stack:remote` + +Images default to `:latest`; to pin a version, set image tags in `docker-compose.prod.yml`. ## Useful commands (server) diff --git a/scripts/build-and-push-prod.sh b/scripts/build-and-push-prod.sh index ebb79cf8..1aec8a6e 100755 --- a/scripts/build-and-push-prod.sh +++ b/scripts/build-and-push-prod.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash -# Build main app and NIP-66 monitor images locally; push to silberengel/imwald-jumble and silberengel/imwald-jumble-nip66-monitor as :latest and :. +# Build and push first-party production images (same tags :latest and :): +# - silberengel/imwald-jumble (SPA) +# - silberengel/imwald-jumble-nip66-monitor +# - silberengel/imwald-piper-tts-proxy (HTTP → Wyoming Piper; matches docker-compose.prod.yml) # Then create git tag v and push it. +# Other compose services (og-proxy, wyoming-piper, LanguageTool, LibreTranslate) pull from Hub on the server. # Run from repo root. Requires: docker, docker login, git. # # Optional env: @@ -19,6 +23,7 @@ VERSION="$(node -p "require('./package.json').version")" GIT_TAG="v${VERSION}" IMAGE_APP="silberengel/imwald-jumble" IMAGE_MONITOR="silberengel/imwald-jumble-nip66-monitor" +IMAGE_PIPER_PROXY="silberengel/imwald-piper-tts-proxy" # OG / link-preview HTML: VITE_PROXY_SERVER is baked into the client bundle at image build time (not runtime). # Use public origin only (no /proxy path): web.service builds /sites/?url=… @@ -39,11 +44,18 @@ docker build \ echo "Building NIP-66 monitor (version: $VERSION)" docker build -t "$IMAGE_MONITOR:latest" -t "$IMAGE_MONITOR:$VERSION" ./nip66-cron -echo "Pushing $IMAGE_APP and $IMAGE_MONITOR" +echo "Building Piper HTTP TTS proxy (version: $VERSION)" +docker build \ + -f services/piper-tts-proxy/Dockerfile \ + -t "$IMAGE_PIPER_PROXY:latest" -t "$IMAGE_PIPER_PROXY:$VERSION" . + +echo "Pushing $IMAGE_APP, $IMAGE_MONITOR, and $IMAGE_PIPER_PROXY" docker push "$IMAGE_APP:latest" docker push "$IMAGE_APP:$VERSION" docker push "$IMAGE_MONITOR:latest" docker push "$IMAGE_MONITOR:$VERSION" +docker push "$IMAGE_PIPER_PROXY:latest" +docker push "$IMAGE_PIPER_PROXY:$VERSION" # --- Git tag (matches package.json version) --- if git rev-parse "$GIT_TAG" >/dev/null 2>&1; then @@ -62,5 +74,5 @@ git tag -a "$GIT_TAG" -m "Release $GIT_TAG" echo "Pushing tag $GIT_TAG to origin" git push origin "$GIT_TAG" -echo "Done. On the server: docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d" -echo "Optional LanguageTool + LibreTranslate: docker compose -f docker-compose.prod.yml --profile editor-tools up -d languagetool libretranslate (see PROXY_SETUP.md)" +echo "Done. On the server (repo clone): bash scripts/ensure-libretranslate-dirs.sh # once, for LibreTranslate volume permissions" +echo " docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d" diff --git a/scripts/stack-remote.sh b/scripts/stack-remote.sh index 9f7ed97f..0e4f4ae7 100644 --- a/scripts/stack-remote.sh +++ b/scripts/stack-remote.sh @@ -1,14 +1,12 @@ #!/usr/bin/env bash -# One remote command from repo clone: silberengel/imwald-jumble + nip66 + wikistr og-proxy + wyoming-piper + -# imwald-piper-tts-proxy (built here; push silberengel/imwald-piper-tts-proxy:latest to Hub for pull-only hosts) + -# LanguageTool + LibreTranslate. Apache → 8089 / 8090 / 9876. +# One remote command from repo clone: full docker-compose.prod.yml stack (pull + up). +# Same as: ensure-libretranslate-dirs.sh && docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d +# First-party images must be pushed from ./scripts/build-and-push-prod.sh before pull will get new app/monitor/piper-proxy revisions. set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" bash "$ROOT/scripts/ensure-libretranslate-dirs.sh" -COMPOSE=(docker compose -f docker-compose.prod.yml --profile stack --profile editor-tools) -# Pull Hub images only; piper-tts-proxy is built from this repo (push silberengel/imwald-piper-tts-proxy:latest when ready). -"${COMPOSE[@]}" pull jumble jumble-nip66-monitor og-proxy piper-wyoming languagetool libretranslate -"${COMPOSE[@]}" build piper-tts-proxy +COMPOSE=(docker compose -f docker-compose.prod.yml) +"${COMPOSE[@]}" pull "${COMPOSE[@]}" up -d echo "[stack:remote] jumble :8089 | og-proxy :8090 | piper HTTP :9876 | LT :8010 | translate :5000" diff --git a/src/lib/read-aloud.ts b/src/lib/read-aloud.ts index e2a8dffe..ae568510 100644 --- a/src/lib/read-aloud.ts +++ b/src/lib/read-aloud.ts @@ -1,5 +1,5 @@ import { ExtendedKind, READ_ALOUD_TTS_URL } from '@/constants' -import i18n, { LocalizedLanguageNames, normalizeToSupportedAppLanguage, type TLanguage } from '@/i18n' +import i18n, { LocalizedLanguageNames, normalizeToSupportedAppLanguage } from '@/i18n' import { getNoteTranslation } from '@/lib/note-translation-display' import { getPiperVoiceForChosenLanguage,